This commit is contained in:
mingyang 2021-01-27 12:10:51 +08:00
parent fb02534cd4
commit 4c1915a2d3
3 changed files with 671 additions and 0 deletions

201
index.html Normal file
View File

@ -0,0 +1,201 @@
<!DOCTYPE html>
<style>
body {
margin: 0;
background-color: #e2e2e2;
}
.subfolders {
display: flex;
background-color: #e2e2e2;
padding: 1.0rem;
justify-content: center;
flex-wrap: wrap;
border-bottom: solid;
border-bottom-width: 2.0px;
border-bottom-color: #c0c0c0;
}
.subfolders > a {
margin: 0.5rem;
padding: 0.5rem;
text-decoration: none;
color: #2e2e22;
font-size: large;
background-color: #ffffff;
outline-color: #202020;
border-radius: 0.3rem;
padding: 1.0rem;
box-shadow: 0 0 0.5rem 0.5rem #e1e1e1;
}
.subfolders > a:hover {
background-color: #2e2e22;
color: #e2e2e2;
}
.images {
display: flex;
margin-top: 1.0rem;
flex-wrap: wrap;
justify-content: center;
}
.imgcontainer {
display: flex;
flex-direction: column;
margin: 0.3rem;
padding: 0.3rem;
background-color: #ffffff;
border-radius: 0.3rem;
justify-content: center;
}
.imgcontainer > a {
display: flex;
justify-content: center;
}
.imgcontainer > p {
text-align: center;
margin: 0.2rem;
}
.more {
display: flex;
justify-content: center;
background-color: #2e2e2e;
color: #e2e2e2;
font-size: x-large;
padding: 1.0rem;
cursor: pointer;
}
</style>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<title>ImageServer</title>
</head>
<body>
<div class="subfolders" id="subfolders">
<!-- <a href="test">..[Parent Dir]</a> -->
</div>
<div class="images" id="images">
<!-- <div class="imgcontainer"><img src="torch_tf.png" height="400px"><p>test1.png</p></div> -->
</div>
<div class="more" onclick="show_more_image()">
加载更多
</div>
</body>
</html>
<script>
function parse_url(url) {
if (url == '') {
return {}
}
urls = url.substr(1).split('&')
params = {}
for (var item of urls) {
delemeter_pos = item.indexOf('=')
key = null, value = null
if (delemeter_pos == -1) {
key = item
} else {
key = item.substr(0, delemeter_pos)
value = item.substr(delemeter_pos + 1)
}
params[key] = value
}
return params
}
function encode_url(params) {
url = []
for (var key in params) {
  url.push(key + '=' + params[key])
}
return url.join('&')
}
function show_directory(dirs, folder_node) {
folder_node.innerHTML = ''
image_node.innerHTML = ''
path = params['path']
path = path.split('/')
path = path.slice(0, path.length - 1).join('/')
this_params = JSON.parse(JSON.stringify(params))
this_params['path'] = path
this_node = document.createElement('a')
this_node.href = host + "/?" + encode_url(this_params)
this_node.innerHTML = '..[ParentDir]'
folder_node.appendChild(this_node)
dirs.forEach(element => {
this_node = document.createElement('a')
elem_split = element.split('/')
this_params = JSON.parse(JSON.stringify(params))
this_params['path'] = element
this_node.href = host + "/?" + encode_url(this_params)
this_node.innerHTML = elem_split[elem_split.length-1]
folder_node.appendChild(this_node)
});
}
function show_image(imgs, image_node, start, max) {
this_imgs = imgs.slice(start, start + max)
this_imgs.forEach(element => {
elem_split = element.split('/')
elem_name = elem_split[elem_split.length-1]
this_node = document.createElement('div')
this_node.className = 'imgcontainer'
img_url = host + `/img?path=${element}`
rawurl = img_url
size_attr = ''
if (params.hasOwnProperty('width')) {
width = params['width']
img_url += `&width=${width}`
size_attr += 'width=${width}px'
}
if (params.hasOwnProperty('height')) {
height = params['height']
img_url += `&height=${height}`
size_attr += `height=${height}px`
}
this_node.innerHTML = `<a href="${rawurl}" target="_blank"><img src="${img_url}" ${size_attr}"></a><p>${elem_name}</p>`
image_node.appendChild(this_node)
});
}
function show_more_image() {
start = parseInt(params['start'])
max = parseInt(params['max'])
params['start'] = (start + max).toString()
show_image(imgs, image_node, start, max)
}
folder_node = document.getElementById('subfolders')
image_node = document.getElementById('images')
var search = window.location.search
var host = window.location.protocol + '//' + window.location.host
params = parse_url(search)
var start = '0'
var max = '50'
if (!params.hasOwnProperty('start')){
params['start'] = start
}
if (!params.hasOwnProperty('max')) {
params['max'] = max
}
if (!params.hasOwnProperty('path')) {
if (!(params.hasOwnProperty('width') | params.hasOwnProperty())) {
params['height'] = '256'
}
params['path'] = '/'
encoded = encode_url(params)
window.location.search = encoded
}
document.title = 'IM: ' + params['path']
var http = new XMLHttpRequest()
http.open("GET", host + '/directory?path=' + params['path'])
http.send()
http.onloadend = (e) => {
response = JSON.parse(http.responseText)
dirs = response['dirs']
imgs = response['imgs']
show_directory(dirs, folder_node)
var start = parseInt(params['start'])
var max = parseInt(params['max'])
show_image(imgs, image_node, start, max)
}
</script>

251
network.py Normal file
View File

@ -0,0 +1,251 @@
import socket
import threading
import traceback
from http import HTTPStatus
def parse_address(address):
try:
ip = address.split(':')[0]
port = int(address.split(':')[1])
return ip, port
except:
print('Invalid address [{0}]').format(address)
print('exception information:')
print(traceback.format_exc())
raise ValueError
class BasicTCPServer():
def __init__(self, address="127.0.0.1:12345", handler=None):
self.ip, self.port = parse_address(address)
self.handler = handler if handler is not None else lambda clientsocket, addr:None
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.socket.bind((self.ip, self.port))
self.socket.listen(5)
self.socket.settimeout(0.5)
self.terminate = False
def __del__(self):
self.stop()
def set_handler(self, handler):
self.handler = handler
def handle_message(self, clientsocket, addr):
self.handler(clientsocket, addr)
def loop(self):
try:
while not self.terminate:
try:
clientsocket,addr = self.socket.accept()
t = threading.Thread(
target=self.handle_message,
args=[clientsocket, addr],
name='Client[{0}]'.format(addr),
daemon=True
)
t.start()
except socket.timeout:
pass
except socket.timeout:
pass
except (Exception, KeyboardInterrupt):
self.socket.close()
print('Bye~')
def start(self, back=True):
if back:
t = threading.Thread(target=self.loop, name='SocketMainLoop', daemon=True)
t.start()
else:
self.loop()
def stop(self):
self.terminate = True
self.socket.close()
class HTTPBasicHeader():
def __init__(self, words=None, content=None):
if content is None:
content = {}
self.words = words
self.content = content
def encode(self):
contents = [' '.join([str(w) for w in self.words])]
contents += ['{0}: {1}'.format(name, value) for name, value in self.content.items()]
header_message = '\r\n'.join(contents)
header_message = header_message.encode('utf-8')
return header_message
def decode(self, message):
if type(message) is bytes:
message = message.decode('utf-8')
contents = message.split('\r\n')
contents = [line.strip() for line in contents]
contents = [line for line in contents if line != '']
header_line = contents[0]
contents = contents[1:]
words = header_line.split(' ')
valid_contents = {}
invalid_lines = []
for line in contents:
delpos = line.find(':')
if delpos == -1:
invalid_lines.append(line)
else:
key = line[:delpos].strip()
value = line[delpos+1:].strip()
valid_contents[key] = value
if len(invalid_lines) > 0:
print('Warning: in-completed line found:')
print(invalid_lines)
return words, valid_contents
class InvalidHTTPHeaderError(Exception):
def __init__(self, message=None):
pass
class HTTPHeaderDictInterface():
def __init__(self, content):
self.content = content
def __getitem__(self, index):
return self.content[index]
def __setitem__(self, index, value):
self.content[index] = value
def __contains__(self, index):
return index in self.content
def __iter__(self, index):
for key in self.content:
yield key
class HTTPRequestHeader(HTTPHeaderDictInterface):
methods = ['GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'CONNECT', 'OPTIONS', 'TRACE', 'PATCH']
def __init__(self, method=None, url=None, version='HTTP/1.1', content=None):
if content is None:
content = {}
self.method = method
self.url = url
self.version = version
self.content = content
def check_valid(self):
if self.method is None or self.url is None:
return False
elif self.method not in HTTPRequestHeader.methods:
return False
else:
return True
def encode(self):
if not self.check_valid():
raise InvalidHTTPHeaderError('Invalid header, method and url should at least be provided.')
words = [self.method, self.url, self.version]
content = self.content
message = HTTPBasicHeader(words=words, content=content).encode()
return message
def decode(self, message):
words, content = HTTPBasicHeader().decode(message)
if len(words) != 3:
raise InvalidHTTPHeaderError
self.method, self.url, self.version = words
self.content = content
class HTTPResponseHeader(HTTPHeaderDictInterface):
def __init__(self, code=None, version='HTTP/1.1', content=None):
if content is None:
content = {}
self.code = code
self.version = version
self.content = content
def check_valid(self):
if self.code is None:
return False
else:
return True
def encode(self):
if not self.check_valid():
raise InvalidHTTPHeaderError('Invalid header, code is required for a http header.')
words = [self.version, self.code, HTTPStatus(self.code).phrase]
content = self.content
message = HTTPBasicHeader(words=words, content=content).encode()
return message
def decode(self, message):
words, content = HTTPBasicHeader().decode(message)
if len(words) != 3:
raise InvalidHTTPHeaderError
self.version, self.code, _ = words
self.content = content
class SingleHTTPConnection():
def __init__(self, header, cached, connection):
self.header = header
self.connection = connection # connection is a basic socket connection.
self.cached = cached
self.length = 0
if 'Content-Length' in self.header:
self.length = self.header['Content-Length'] # the remeaning legth of the connection.
# To ensure all data is send, so we use sendall here.
def write(self, message):
self.connection.sendall(message)
# this function will read fixed length from the socket.
def read_fixed_size(self, size):
if size <= 0:
return b''
recvd = b''
while len(recvd) < size:
this_message = self.connection.recv(size - len(recvd))
if len(this_message) == 0:
break
recvd += this_message
return recvd
def read(self, size=None):
if size is None:
self.length = 0
return self.cached + self.read_fixed_size(self.length - self.cached)
if size > self.length:
size = self.length
message = b''
message = self.cached[:size]
if len(message) < size:
message += self.read_fixed_size(size - len(message))
self.length -= size
return message
class HTTPBaseServer(BasicTCPServer):
def __init__(self, request_handler, bind_addr='127.0.0.1:80'):
if not callable(request_handler):
raise ValueError('You must provide an callable request handler.')
super(HTTPBaseServer, self).__init__(address=bind_addr)
self.request_handler = request_handler
def handle_message(self, sock, addr):
# print('processing connection from ', addr)
# recieve until header ends.
delemeter = b'\r\n\r\n'
header_text = b''
# print('New connection established from', addr)
while True:
# wait for the end of the
while header_text.find(delemeter) == -1:
this_mesage = sock.recv(8192)
if len(this_mesage) == 0:
# print('connection exited. addr:', addr)
return
header_text += this_mesage
delpos = header_text.find(delemeter)
content = header_text[delpos + len(delemeter):]
header_text = header_text[:delpos]
header = HTTPRequestHeader()
header.decode(header_text)
header_text = b''
wraped_connection = SingleHTTPConnection(header, content, sock)
self.request_handler(wraped_connection) # the request handler can only read limited data, once finish, send, and return, we will move on.
# print('request handler finished.')

219
server.py Normal file
View File

@ -0,0 +1,219 @@
import os
import io
import json
import time
import threading
import queue
from http import HTTPStatus
from urllib.parse import unquote
from PIL import Image
from network import HTTPBaseServer, HTTPResponseHeader
app_dir = os.path.split(os.path.realpath(__file__))[0]
index_path = os.path.join(app_dir, 'index.html')
def loadfile(path):
with open(path, 'r', encoding='utf-8') as f:
return f.read()
class HTTPImageServer():
def __init__(self, bind_addr, imgroot='.'):
self.server = HTTPBaseServer(request_handler=self.handle, bind_addr=bind_addr)
self.imgroot = imgroot
self.img_extension = ['png', 'jpg', 'jpeg', 'tiff', 'webp', 'bmp']
self.print_lock = threading.Lock()
self.logqueue = queue.Queue()
def start(self, back=True):
t = threading.Thread(target=self.logger, name='Logger thread', daemon=True)
t.start()
self.server.start(back=back)
def logger(self):
while True:
try:
msg = self.logqueue.get(timeout=1)
print(msg)
except queue.Empty:
pass
@staticmethod
def parse_url(url):
location = url.split('?')[0]
params_str = url[len(location)+1:]
location = unquote(location)
params = {}
splits = params_str.split('&')
for split in splits:
split = unquote(split)
eq_pos = split.find('=')
if eq_pos == -1:
params[split] = None
continue
else:
key = split[:eq_pos]
value = split[eq_pos+1:]
params[key] = value
return location, params
def log(self, msg):
self.logqueue.put(msg)
def response(self, connection, header, content):
msg = '[{time}] {method}: {url} - {stat}'.format(
time = time.strftime("%H:%M:%S", time.localtime()),
method = connection.header.method,
url = connection.header.url,
stat = '{0}({1})'.format(header.code, HTTPStatus(header.code).phrase)
)
self.log(msg)
header['Content-Length'] = len(content)
connection.write(header.encode() + b'\r\n\r\n')
connection.write(content)
def response_404(self, connection):
header = HTTPResponseHeader(404)
content = b'404 Not Found'
self.response(connection, header, content)
@staticmethod
def safe_path(path):
path = '/'.join(path.split('\\'))
path = path.split('/')
path = [p for p in path if p not in ['', '..', '.']]
path = '/'.join(path)
return path
def handle_index(self, params):
if 'path' not in params:
return HTTPResponseHeader(404), b'404 Not Found'
directory = params['path']
while '\\' in directory:
directory = directory.replace('\\', '/')
directory = self.safe_path(directory)
disk_directory = os.path.join(self.imgroot, directory)
filenames = []
try:
filenames = os.listdir(disk_directory)
filenames.sort()
except Exception:
pass
response = {"dirs": [], "imgs": []}
for filename in filenames:
full_path = os.path.join(disk_directory, filename)
request_path = '/{0}/{1}'.format(directory, filename)
request_path = '/' + request_path.strip('/\\')
if os.path.isdir(full_path):
response['dirs'].append(request_path)
else:
if filename.split('.')[-1] in self.img_extension:
response['imgs'].append(request_path)
response = json.dumps(response).encode('utf-8')
return HTTPResponseHeader(200), response
def handle_image(self, params):
invalid_request = False
if 'path' not in params:
invalid_request = True
filepath = params['path']
filepath = self.safe_path(filepath)
full_path = os.path.join(self.imgroot, filepath)
if filepath.split('.')[-1] not in self.img_extension:
invalid_request = True
elif not os.path.isfile(full_path):
invalid_request = True
# parse height and width limit.
max_h, max_w = None, None
try:
if 'height' in params:
max_h = int(params['height'])
elif 'width' in params:
max_w = int(params['width'])
except Exception:
invalid_request = True
if invalid_request:
return HTTPResponseHeader(404), b'404 Not Found'
header = HTTPResponseHeader(200)
content = b''
if max_h is not None or max_w is not None:
img = Image.open(full_path)
real_w, real_h = img.size
h_ratio = None
w_ratio = None
if max_h is not None:
h_ratio = max_h / real_h
h_ratio = h_ratio if h_ratio < 1 else 1
if max_w is not None:
w_ratio = max_w / real_w
w_ratio = w_ratio if w_ratio < 1 else 1
max_ratio = 0
if h_ratio is None:
max_ratio = w_ratio
elif w_ratio is None:
max_ratio = h_ratio
else:
max_ratio = h_ratio if h_ratio < w_ratio else w_ratio
new_h, new_w = (real_h * max_ratio, real_w * max_ratio)
img = img.resize((int(new_w), int(new_h)))
img_stream = io.BytesIO()
img = img.save(img_stream, format='webp')
content = img_stream.getvalue()
else:
with open(full_path, 'rb') as f:
content = f.read()
return header, content
"""
request_type:
request index: http://domain.com/directory?path=relative/path/to/file
request image: http://domain.com/img?path=relative/path/to/file&height=100px&width=200px
"""
def handle(self, connection):
method = connection.header.method
if method != 'GET':
self.response_404(connection)
return
url = connection.header.url
location, params = self.parse_url(url)
location = location.strip('/\\')
header, content = None, None
if location == 'directory':
header, content = self.handle_index(params)
elif location == 'img':
header, content = self.handle_image(params)
elif location in ['', 'index', 'index.html']:
header = HTTPResponseHeader(200)
content = loadfile(index_path).encode('utf-8')
else:
header = HTTPResponseHeader(404)
content = b'Please Do Not Try To Access Non-Image File!'
self.response(connection, header, content)
if __name__ == '__main__':
import sys
args= sys.argv[1:]
port = 80
root = '.'
if len(args) > 0:
try:
port = int(args[0])
except Exception:
print('Port {0} not understood, use 80 instead'.format(args[0]), file=sys.stderr)
if len(args) > 1:
root = args[1]
if not os.path.isdir(root):
print('Path {0} is not a valid path, use current directory instead.'.format(root), file=sys.stderr)
root = '.'
print('Start HTTP server on port {0} and use web root as {1}'.format(port, root))
server = HTTPImageServer(bind_addr='0.0.0.0:{0}'.format(port), imgroot=root)
server.start(back=False)