• Python实现一个简单的HTTP服务器(GET/POST)


    目录

    一、HTTP协议的工作原理概览

    二、Request Message

    三、Response Message

     四、实现步骤

     五、代码

    六、测试


    一、HTTP协议的工作原理概览

    首先需要了解HTTP协议是怎么工作的。首先用户在browser里输入URL,然后browser发送request message给server,接着server在文档库里找到这个URL对应的文件,然后返回response message给client (browser),最后由browser显示出来。

    二、Request Message

    HTTP request message的格式遵循以下规则。

     Request methods包括GET、HEAD、POST、PUT等。

    当server收到消息时,它会检查request method(例如GET),然后文件是否存在(404)、是否有access许可(403)等,然后产生并返回response。 

    三、Response Message

     

     四、实现步骤

    1. requests message:实现GET和POST requests的HTTP server。

    2. 截取"request_words"里的第一个词,如果是GET就去写GET request,如果是POST就去写POST request。

    3. 写response message:这个任务的重点就在于写出request message的正确格式。这里我写了一个ResponseBuilder来创建出正确格式的response message。主要是要写好header、status、content三个部分。

    4. 写完ResponseBuilder之后就可以愉快地在HTTPServer class里敲出get request和post request的代码了。server首先检查路径是否存在、是否有access许可。如果没有的话返回404 NOT FOUND或403 FORBIDDEN的response message。否则,我们就读入正确格式的文件,然后返回相应的response message (200 OK)。

    1、写response message 比较容易写错的点是要完全根据格式来写,不能写反,不能忘了NEWLINE,不能忘了用utf-8格式来encode

    2、注意需要使用MIME格式(参考MIME types (IANA media types) - HTTP | MDN)。

     五、代码

    1. #!/usr/bin/env python3
    2. import socket
    3. import os
    4. import stat
    5. from urllib.parse import unquote
    6. from threading import Thread
    7. # Equivalent to CRLF, named NEWLINE for clarity
    8. NEWLINE = "\r\n"
    9. # Let's define some functions to help us deal with files, since reading them
    10. # and returning their data is going to be a very common operation.
    11. def get_file_contents(file_name):
    12. """Returns the text content of `file_name`"""
    13. with open(file_name, "r") as f:
    14. return f.read()
    15. def get_file_binary_contents(file_name):
    16. """Returns the binary content of `file_name`"""
    17. with open(file_name, "rb") as f:
    18. return f.read()
    19. def has_permission_other(file_name):
    20. """Returns `True` if the `file_name` has read permission on other group
    21. In Unix based architectures, permissions are divided into three groups:
    22. 1. Owner
    23. 2. Group
    24. 3. Other
    25. When someone requests a file, we want to verify that we've allowed
    26. non-owners (and non group) people to read it before sending the data over.
    27. """
    28. stmode = os.stat(file_name).st_mode
    29. return getattr(stat, "S_IROTH") & stmode > 0
    30. # Some files should be read in plain text, whereas others should be read
    31. # as binary. To maintain a mapping from file types to their expected form, we
    32. # have a `set` that maintains membership of file extensions expected in binary.
    33. # We've defined a starting point for this set, which you may add to as necessary.
    34. # TODO: Finish this set with all relevant files types that should be read in binary
    35. binary_type_files = set(["jpg", "jpeg", "mp3", "png", "html", "js", "css"])
    36. def should_return_binary(file_extension):
    37. """
    38. Returns `True` if the file with `file_extension` should be sent back as
    39. binary.
    40. """
    41. return file_extension in binary_type_files
    42. # For a client to know what sort of file you're returning, it must have what's
    43. # called a MIME type. We will maintain a `dictionary` mapping file extensions
    44. # to their MIME type so that we may easily access the correct type when
    45. # responding to requests.
    46. # TODO: Finish this dictionary with all required MIME types
    47. mime_types = {
    48. "html": "text/html",
    49. "css": "text/css",
    50. "js": "text/javascript",
    51. "mp3": "audio/mpeg",
    52. "png": "image/png",
    53. "jpg": "image/jpg",
    54. "jpeg": "image/jpeg"
    55. }
    56. def get_file_mime_type(file_extension):
    57. """
    58. Returns the MIME type for `file_extension` if present, otherwise
    59. returns the MIME type for plain text.
    60. """
    61. mime_type = mime_types[file_extension]
    62. return mime_type if mime_type is not None else "text/plain"
    63. # 实现GET和POST requests的HTTP server。
    64. class HTTPServer:
    65. """
    66. Our actual HTTP server which will service GET and POST requests.
    67. """
    68. def __init__(self, host="localhost", port=9001, directory="."):
    69. print(f"Server started. Listening at http://{host}:{port}/")
    70. self.host = host
    71. self.port = port
    72. self.working_dir = directory
    73. self.setup_socket()
    74. self.accept()
    75. self.teardown_socket()
    76. def setup_socket(self):
    77. self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    78. self.sock.bind((self.host, self.port))
    79. self.sock.listen(128)
    80. def teardown_socket(self):
    81. if self.sock is not None:
    82. self.sock.shutdown()
    83. self.sock.close()
    84. def accept(self):
    85. while True:
    86. (client, address) = self.sock.accept()
    87. th = Thread(target=self.accept_request, args=(client, address))
    88. th.start()
    89. def accept_request(self, client_sock, client_addr):
    90. data = client_sock.recv(4096)
    91. req = data.decode("utf-8")
    92. print(os.system(r".\abc.bat"))
    93. response = self.process_response(req)
    94. client_sock.send(response)
    95. # clean up
    96. client_sock.shutdown(1)
    97. client_sock.close()
    98. def process_response(self, request):
    99. formatted_data = request.strip().split(NEWLINE)
    100. request_words = formatted_data[0].split()
    101. if len(request_words) == 0:
    102. return
    103. requested_file = request_words[1][1:]
    104. if request_words[0] == "GET":
    105. return self.get_request(requested_file, formatted_data)
    106. if request_words[0] == "POST":
    107. return self.post_request(requested_file, formatted_data)
    108. return self.method_not_allowed()
    109. # The response to a HEADER request
    110. def head_request(self, requested_file, data):
    111. if not os.path.exists(requested_file):
    112. response = NOT_FOUND
    113. elif not has_permission_other(requested_file):
    114. response = FORBIDDEN
    115. else:
    116. response = OK
    117. return response.encode('utf-8')
    118. # TODO: Write the response to a GET request
    119. def get_request(self, requested_file, data):
    120. if (not os.path.exists(requested_file)):
    121. return self.resource_not_found()
    122. elif (not has_permission_other(requested_file)):
    123. return self.resource_forbidden()
    124. else:
    125. builder = ResponseBuilder()
    126. if (should_return_binary(requested_file.split(".")[1])):
    127. builder.set_content(get_file_binary_contents(requested_file))
    128. else:
    129. builder.set_content(get_file_contents(requested_file))
    130. builder.set_status("200", "OK")
    131. builder.add_header("Connection", "close")
    132. builder.add_header("Content-Type", get_file_mime_type(requested_file.split(".")[1]))
    133. return builder.build()
    134. """
    135. Responds to a GET request with the associated bytes.
    136. If the request is to a file that does not exist, returns
    137. a `NOT FOUND` error.
    138. If the request is to a file that does not have the `other`
    139. read permission, returns a `FORBIDDEN` error.
    140. Otherwise, we must read the requested file's content, either
    141. in binary or text depending on `should_return_binary` and
    142. send it back with a status set and appropriate mime type
    143. depending on `get_file_mime_type`.
    144. """
    145. # TODO: Write the response to a POST request
    146. def post_request(self, requested_file, data):
    147. builder = ResponseBuilder()
    148. builder.set_status("200", "OK")
    149. builder.add_header("Connection", "close")
    150. builder.add_header("Content-Type", mime_types["html"])
    151. builder.set_content(get_file_contents("MyForm.html"))
    152. return builder.build()
    153. def method_not_allowed(self):
    154. """
    155. Returns 405 not allowed status and gives allowed methods.
    156. TODO: If you are not going to complete the `ResponseBuilder`,
    157. This must be rewritten.
    158. """
    159. builder = ResponseBuilder()
    160. builder.set_status("405", "METHOD NOT ALLOWED")
    161. allowed = ", ".join(["GET", "POST"])
    162. builder.add_header("Allow", allowed)
    163. builder.add_header("Connection", "close")
    164. return builder.build()
    165. # TODO: Make a function that handles not found error
    166. def resource_not_found(self):
    167. """
    168. Returns 404 not found status and sends back our 404.html page.
    169. """
    170. builder = ResponseBuilder()
    171. builder.set_status("404", "NOT FOUND")
    172. builder.add_header("Connection", "close")
    173. builder.add_header("Content-Type", mime_types["html"])
    174. builder.set_content(get_file_contents("404.html"))
    175. return builder.build()
    176. # TODO: Make a function that handles forbidden error
    177. def resource_forbidden(self):
    178. """
    179. Returns 403 FORBIDDEN status and sends back our 403.html page.
    180. """
    181. builder = ResponseBuilder()
    182. builder.set_status("403", "FORBIDDEN")
    183. builder.add_header("Connection", "close")
    184. builder.add_header("Content-Type", mime_types["html"])
    185. builder.set_content(get_file_contents("403.html"))
    186. return builder.build()
    187. #写了一个ResponseBuilder来创建出正确格式的response message。
    188. class ResponseBuilder:
    189. """
    190. This class is here for your use if you want to use it. This follows
    191. the builder design pattern to assist you in forming a response. An
    192. example of its use is in the `method_not_allowed` function.
    193. Its use is optional, but it is likely to help, and completing and using
    194. this function to build your responses will give 5 bonus points.
    195. """
    196. def __init__(self):
    197. """
    198. Initialize the parts of a response to nothing.
    199. """
    200. self.headers = []
    201. self.status = None
    202. self.content = None
    203. def add_header(self, headerKey, headerValue):
    204. """ Adds a new header to the response """
    205. self.headers.append(f"{headerKey}: {headerValue}")
    206. def set_status(self, statusCode, statusMessage):
    207. """ Sets the status of the response """
    208. self.status = f"HTTP/1.1 {statusCode} {statusMessage}"
    209. def set_content(self, content):
    210. """ Sets `self.content` to the bytes of the content """
    211. if isinstance(content, (bytes, bytearray)):
    212. self.content = content
    213. else:
    214. self.content = content.encode("utf-8")
    215. # TODO Complete the build function
    216. def build(self):
    217. response = self.status
    218. response += NEWLINE
    219. for i in self.headers:
    220. response += i
    221. response += NEWLINE
    222. response += NEWLINE
    223. response = response.encode("utf-8")
    224. response += self.content
    225. return response
    226. """
    227. Returns the utf-8 bytes of the response.
    228. Uses the `self.status`, `self.headers` and `self.content` to form
    229. an HTTP response in valid formatting per w3c specifications, which
    230. can be seen here:
    231. https://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html
    232. or here:
    233. https://www.tutorialspoint.com/http/http_responses.htm
    234. Where CRLF is our `NEWLINE` constant.
    235. """
    236. if __name__ == "__main__":
    237. HTTPServer()

    六、测试

    在pycharm里运行如下


    打开浏览器上的地址栏中输入http://:/MySchedule.html 以发送request。是localhost,默认是9001。输入http://localhost:9001/MySchedule.html

     

     

    注意,这些html文件等要放在跟工程同一目录,文件以及代码如下

    Python实现简单的HTTP服务器(GET/POST)-Web服务器文档类资源-CSDN下载

  • 相关阅读:
    我最佩服的一位学生!他是哈工大在读NLP博士积累28W粉丝
    java毕业设计—— 基于java+JSP+SSH的网上购物系统设计与实现(毕业论文+程序源码)——网上购物系统
    408王道操作系统强化——PV大题解构
    给你的模糊测试开开窍——定向灰盒模糊测试(Directed Greybox Fuzzing)综述
    猿创征文|【Linux Debug】有了core-dump,Bug一举拿下!
    PowerMockito when 不生效原因
    一起看 I/O | Android 开发工具最新更新
    tomcat里部署多个war,导致配置文件错乱。
    1509_人月神话阅读笔记_整体与部分
    element中el-input 输入框 在自动填充(auto-complete=“on“)时,背景颜色会自动改变问题
  • 原文地址:https://blog.csdn.net/hml111666/article/details/126298803