Advertisement
Guest User

Untitled

a guest
Nov 18th, 2019
91
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 8.01 KB | None | 0 0
  1. # python3
  2.  
  3.  
  4. import json
  5. import socket
  6. import sys
  7. from email.parser import Parser
  8. from functools import lru_cache
  9. from urllib.parse import parse_qs, urlparse
  10.  
  11. MAX_LINE = 64 * 1024
  12. MAX_HEADERS = 100
  13.  
  14.  
  15.  
  16. class MyHTTPServer:
  17.  
  18. def __init__(self, _host, _port, server_name):
  19. self._host = host
  20. self._port = port
  21. self._server_name = server_name
  22. self._users = {}
  23.  
  24. def serve_forever(self):
  25. serv_sock = socket.socket(
  26. socket.AF_INET,
  27. socket.SOCK_STREAM,
  28. proto=0)
  29.  
  30. try:
  31. serv_sock.bind((self._host, self._port))
  32. serv_sock.listen()
  33.  
  34. while True:
  35. conn, _ = serv_sock.accept()
  36. try:
  37. self.serve_client(conn)
  38. except Exception as e:
  39. print('Client serving failed', e)
  40. finally:
  41. serv_sock.close()
  42.  
  43. def parse_request(self, conn,):
  44. rfile = conn.makefile('rb')
  45. method, target, ver = self.parse_request_line(rfile)
  46. headers = self.parse_headers(rfile)
  47. _host = headers.get('Host')
  48. if not _host:
  49. raise HTTPError(400, 'Bad request',
  50. 'Host header is missing')
  51. if _host not in (self._server_name,
  52. f'{self._server_name}:{self._port}'):
  53. raise HTTPError(404, 'Not found')
  54. return Request(method, target, ver, headers, rfile)
  55.  
  56.  
  57. def serve_client(self, conn):
  58. try:
  59. req = self.parse_request(conn)
  60. resp = self.handle_request(req)
  61. self.send_response(conn, resp)
  62. except ConnectionResetError:
  63. conn = None
  64. except Exception as e:
  65. self.send_error(conn, e)
  66.  
  67. if conn:
  68. req.rfile.close()
  69. conn.close()
  70.  
  71. @staticmethod
  72. def parse_request_line(rfile):
  73. raw = rfile.readline(MAX_LINE + 1)
  74. if len(raw) > MAX_LINE:
  75. raise HTTPError(400, 'Bad request',
  76. 'Request line is too long')
  77.  
  78. req_line = str(raw, 'iso-8859-1')
  79. words = req_line.split()
  80. if len(words) != 3:
  81. raise HTTPError(400, 'Bad request',
  82. 'Malformed request line')
  83.  
  84. method, target, ver = words
  85. if ver != 'HTTP/1.1':
  86. raise HTTPError(505, 'HTTP Version Not Supported')
  87. return method, target, ver
  88.  
  89. @staticmethod
  90. def parse_headers(rfile):
  91. headers = []
  92. while True:
  93. line = rfile.readline(MAX_LINE + 1)
  94. if len(line) > MAX_LINE:
  95. raise HTTPError(494, 'Request header too large')
  96.  
  97. if line in (b'\r\n', b'\n', b''):
  98. break
  99.  
  100. headers.append(line)
  101. if len(headers) > MAX_HEADERS:
  102. raise HTTPError(494, 'Too many headers')
  103.  
  104. sheaders = b''.join(headers).decode('iso-8859-1')
  105. return Parser().parsestr(sheaders)
  106.  
  107. def handle_request(self, req):
  108. if req.path == '/users' and req.method == 'POST':
  109. return self.handle_post_users(req)
  110.  
  111. if req.path == '/users' and req.method == 'GET':
  112. return self.handle_get_users(req)
  113.  
  114. if req.path.startswith('/users/'):
  115. user_id = req.path[len('/users/'):]
  116. if user_id.isdigit():
  117. return self.handle_get_user(req, user_id)
  118.  
  119. raise Exception('Not found')
  120.  
  121. @staticmethod
  122. def send_response(conn, resp):
  123. wfile = conn.makefile('wb')
  124. status_line = f'HTTP/1.1 {resp.status} {resp.reason}\r\n'
  125. wfile.write(status_line.encode('iso-8859-1'))
  126.  
  127. if resp.headers:
  128. for (key, value) in resp.headers:
  129. header_line = f'{key}: {value}\r\n'
  130. wfile.write(header_line.encode('iso-8859-1'))
  131.  
  132. wfile.write(b'\r\n')
  133.  
  134. if resp.body:
  135. wfile.write(resp.body)
  136.  
  137. wfile.flush()
  138. wfile.close()
  139.  
  140. def send_error(self, conn, err):
  141. # noinspection PyBroadException
  142. try:
  143. status = err.status
  144. reason = err.reason
  145. body = (err.body or err.reason).encode('utf-8')
  146. except:
  147. status = 500
  148. reason = b'Internal Server Error'
  149. body = (err.args[0]).encode('utf-8')
  150. resp = Response(status, reason,
  151. [('Content-Length', len(body))],
  152. body)
  153. self.send_response(conn, resp)
  154.  
  155. def handle_post_users(self, req):
  156. user_id = len(self._users) + 1
  157. self._users[user_id] = {'id': user_id,
  158. 'name': req.query['name'][0],
  159. 'age': req.query['age'][0]}
  160. return Response(204, 'Created')
  161.  
  162. def handle_get_users(self, req):
  163. accept = req.headers.get('Accept')
  164. if 'text/html' in accept:
  165. contentType = 'text/html; charset=utf-8'
  166. body = '<html><head></head><body>'
  167. body += f'<div>Пользователи ({len ( self._users )})</div>'
  168. body += '<ul>'
  169. for u in self._users.values():
  170. body += f'<li>#{u["id"]} {u["name"]}, {u["age"]}</li>'
  171. body += '</ul>'
  172. body += '</body></html>'
  173.  
  174. elif 'application/json' in accept:
  175. contentType = 'application/json; charset=utf-8'
  176. body = json.dumps(self._users)
  177.  
  178. else:
  179. # https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/406
  180. return Response(406, 'Not Acceptable')
  181.  
  182. body = body.encode('utf-8')
  183. headers = [('Content-Type', contentType),
  184. ('Content-Length', len(body))]
  185. return Response(200, 'OK', headers, body)
  186.  
  187. def handle_get_user(self, req, user_id):
  188. user = self._users.get(int(user_id))
  189. if not user:
  190. raise HTTPError(404, 'Not found')
  191.  
  192. accept = req.headers.get('Accept')
  193. if 'text/html' in accept:
  194. contentType = 'text/html; charset=utf-8'
  195. body = '<html><head></head><body>'
  196. body += f'#{user["id"]} {user["name"]}, {user["age"]}'
  197. body += '</body></html>'
  198.  
  199. elif 'application/json' in accept:
  200. contentType = 'application/json; charset=utf-8'
  201. body = json.dumps(user)
  202.  
  203. else:
  204. # https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/406
  205. return Response(406, 'Not Acceptable')
  206.  
  207. body = body.encode('utf-8')
  208. headers = [('Content-Type', contentType),
  209. ('Content-Length', len(body))]
  210. return Response(200, 'OK', headers, body)
  211.  
  212.  
  213. class Request:
  214. def __init__(self, method, target, version, headers, rfile):
  215. self.method = method
  216. self.target = target
  217. self.version = version
  218. self.headers = headers
  219. self.rfile = rfile
  220.  
  221. @property
  222. def path(self):
  223. return self.url.path
  224.  
  225. @property
  226. @lru_cache(maxsize=None)
  227. def query(self):
  228. return parse_qs(self.url.query)
  229.  
  230. @property
  231. @lru_cache(maxsize=None)
  232. def url(self):
  233. return urlparse(self.target)
  234.  
  235. def body(self):
  236. size = self.headers.get('Content-Length')
  237. if not size:
  238. return None
  239. return self.rfile.read(size)
  240.  
  241.  
  242. class Response:
  243. def __init__(self, status, reason, headers=None, body=None):
  244. self.status = status
  245. self.reason = reason
  246. self.headers = headers
  247. self.body = body
  248.  
  249.  
  250. class HTTPError (Exception):
  251. def __init__(self, status, reason, body=None):
  252. super()
  253. self.status = status
  254. self.reason = reason
  255. self.body = body
  256.  
  257.  
  258. if __name__ == '__main__':
  259. host = '127.0.0.1'
  260. port = 8080
  261. name = 'example.local'
  262.  
  263. serv = MyHTTPServer(host, port, name)
  264. try:
  265. serv.serve_forever()
  266. except KeyboardInterrupt:
  267. pass
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement