add code
This commit is contained in:
251
network.py
Normal file
251
network.py
Normal 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.')
|
||||
Reference in New Issue
Block a user