http协议¶
1. 基本概念¶
HTTP协议(HyperText Transfer Protocol,超文本传输协议),由蒂姆·伯纳斯-李于1989年为了推广互联网技术而推出的一种无状态网络应用协议,HTTP协议构建于TCP/IP协议族之上,属于应用层协议。主要用于传输与超文本相关的资源文件,如HTML网页,css,js,图片,视频,音频等。
现行的HTTP协议基于数据是否加密/加壳,主要分两种。
直接构建于TCP协议之上,没有进行加密传输数据的是HTTP协议,默认通信端口是80端口,常用端口有:3000,5000,8000,8080等。
而构建于TLS或SSL协议之上,对数据使用SSL加壳加密传输数据的是HTTPS协议,默认通信端口是443端口,常用端口8443。
注意:
在本地电脑上双击html网页也可以通过浏览器来查看HTML网页信息,但是这种方式是采用file协议来访问的。 file协议叫本地文件浏览器协议,顾名思义,是只能访问本地电脑中的路径文件,用户无法通过file协议访问另一台电脑中的文件。
除了http或https协议可以提供html等资源的浏览服务以外,还有ws(websocket)或wss (websocket ssl)也可以用于在浏览器中提供网页服务,但是ws或wss一般用于web网络即时通信比较多。
所谓的无状态是指http协议默认情况下,服务端不识别客户端的。在客户端多次发起请求到同一个服务器,服务端接收到客户端的请求在处理完成以后就会主动断开(短连接)。所以对于客户端的每一次请求,对于服务端来说,都是一次新的客户端请求。也就是说,服务端无法区分多次请求的客户端是否同一个客户端。
2. 请求响应模型¶
http协议是基于客户端请求,服务端响应的流程来实现的,这个流程一般也称之为"请求响应模型"。所以对于http协议通信的双方来说,发起请求的一方只能是客户端,而响应数据的一方只能是服务端。
HTTP协议永远都是客户端发起请求,服务器回送响应。
2.1 请求报文格式¶
对于客户端的http请求格式一般分三部分组成,分别是请求行、请求头、请求体(部分请求方法不具有请求体)。一般如下:
```http request 请求方法 请求路径 http协议版本 <---- http响应的一行内容,也叫请求行 请求头选项1: 选项值 请求头选项:2 选项值 .... 请求头选项n: 选项值
请求体(可以有多行,前后必有空行)
我们可以借助telnet工具或curl工具对远程的web服务器发起http或https请求。telnet默认是关闭的,所以需要手动打开。

例如,发起http请求访问,基本的http请求格式如下:
```http request
GET /get HTTP/1.1
Host: httpbin.org
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36", "X-Amzn-Trace-Id": "Root=1-65081979-7a855b8b1663809455a7929b"
2.3 响应报文格式¶
上面看到的内容实际上就是远程http服务器通过http通信返回的响应报文。格式如下:
```http response 协议版本 响应状态码 响应文本提示 <----- http响应的第一行内容,响应行 响应头选项1: 选项值 响应头选项2: 选项值 .... 响应头选项n: 选项值
响应体(服务端返回的正文信息,前后必有空行)
### 2.2 发送请求
#### 2.2.1 发送GET请求
```http request
telnet httpbin.org 80
GET /get HTTP/1.1
Host: httpbin.org
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36
请求效果如下:
2.2.2 发送POST请求¶
发送POST请求,并发送json数据格式。
POST /post HTTP/1.1
Host: httpbin.org
Content-Type: application/json
Content-Length: 44
{"name": "xiaoming", "age": 16,"pwd": "123"}
当然使用telnel工具非常原始,所以我们也可以通过安装测试工具,如postman或Apifox,来查看并学习http协议的请求与响应格式。
2.2.3 HTTP请求的生命周期¶
3. 协议报文详解¶
前面我们提到,http协议是应用层协议,是基于tcp协议。在前面的网络编程曾经学习过tcp协议的通信是可靠安全的,所以实际上来说,因为http协议是基于TCP协议来传输数据的,所以http协议就具有了有所请求必有所回应的特点,也因此我们可以说,http协议一次通信的最基本的流程都是具有请求报文与响应报文的。请求报文与响应报文就组成http协议的通信内容(HTTP报文)。不管是请求报文还是响应报文在上面的发送请求与查看服务器响应报文,我们都可以看到,实际上HTTP报文就是一段连续的多行文本。
3.1 请求报文¶
请求报文(HTTP Rquest)主要通过客户端发送用于表达对服务器资源的操作。基本格式:
请求方法 请求路径 http协议版本 <---- http响应的一行内容,也叫请求行
请求头选项1: 选项值
请求头选项:2 选项值
....
请求头选项n: 选项值
请求体(可以有多行,前后必有空行)
3.1.1 请求行¶
请求行(HTTP Request Line),表示请求报文的首行,主要三部分组成,使用单个空格隔开,分别是:请求方法 请求路径 HTTP协议版本。
3.1.1.1 请求方法¶
HTTP请求方法(HTTP Request Method),表示客户端希望对服务器指定资源进行哪一种类型的操作,存在多种HTTP请求方法表达增删查改。常见请求方法如下:
| 请求方法 | 描述 | 报文中是否包含请求体 |
|---|---|---|
| GET | 表示客户端希望从服务器中获取或下载资源信息 | Flase |
| POST | 表示客户端希望上传文件或通过请求在服务器创建资源信息。 | True |
| PUT | 表示客户端希望修改或更新服务器资源(表示修改全部资源信息,例如数据表的一整个记录) | True |
| PATCH | 表示客户端希望修改或更新服务器资源(表示修改部分资源信息,例如数据表的一个记录里面某个属性值) | True |
| DELETE | 表示客户端希望删除或废弃服务器资源 | Flase |
| OPTION | 表示客户端希望获取服务器所支持的请求方法列表 | Flase |
| HEAD | 表示客户端希望获取服务器支持的跨域地址列表 | Flase |
3.1.1.2 请求路径¶
表示远程web服务器的一个可访问资源。一般就是代表的就是一个服务器的具体文件或数据表中的记录信息,或一个服务端里面的函数或方法。
3.1.1.3 http协议版本¶
表示客户端目前使用的HTTP协议版本,并期望服务端也采用同样版本的协议与客户端进行通信。
http协议发送至今已经到了HTTP2.0版本,目前主流的使用版本有:HTTP/1.0 、HTTP/1.1。
3.1.2 请求头¶
HTTP请求头(Request Head),主要对客户端请求操作进行限制条件与补充说明。常见的标准HTTP请求头:
| 选项 | 描述 | 值 |
|---|---|---|
| Host | 指定客户端请求的服务器的域名和端口号。 | www.baidu.com |
| Content-Type | 告诉服务器,客户端请求携带的请求体数据的媒体类型信息(MIME类型) | |
| User-Agent | 告知服务器HTTP 客户端网络代理程序的版本信息,一般就是浏览器的版本信息。 | |
| Authorization | 告知服务器客户端的Web认证信息。 | |
| Content-Length | HTTP报文中请求体的大小,以字节为单位。 | |
| Referer | 告诉服务器该网页是从哪个页面链接过来。也就是上一页页面的地址。 | |
| Accept | 指定客户端能够接收并理解的媒体类型类型(MIME类型),用于表达希望服务端的返回资源格式。 | |
| Accept-Encoding | 指定浏览器可以支持的web服务器返回内容压缩编码类型。 | gzip, deflate, br |
| Pragma | 指定服务端控制缓存行为。http/1.0以前的字段。 | Pragme: no-cache |
| Cache-Control | 指定服务端控制缓存行为。http/1.1以后的字段。 | Cache-Control: no-cache |
| Upgrade | 向服务器请求在当前http协议的基础上升级采用新的某种传输协议以便服务器进行转换 | 常用于http协议升级到ws协议。 |
| Connection | 指定本次http通信结束以后,是否关闭TCP网络连接。如果设置持久连接,则可以在一次会话中,可以使用同一个TCP连接,进行多次的HTTP通信,提高效率。 | 持久连接,Connection: keep-alive 非持久连接,Connection: close |
注意:在http通信过程中,请求头也是可以自定义的,但是不能出现多字节编码字符,例如中文等。
3.1.2.1 常见的MIME格式¶
| 类型 | 描述 | 别名 |
|---|---|---|
| text/html | HTML网页 | |
| application/json | json文本 | text/json |
| text/plain | 纯文本,普通文本 | |
| text/xml | xml文档 | |
| application/javascript | js脚本 | text/javascript |
| text/css | css样式 | |
| image/png | png格式图片 | |
| image/jpeg | jpg格式图片 | |
| image/gif | gif格式图片 | |
| application/x-gzip | gzip格式压缩包 | |
| application/msword | doc文档 | |
| application/vnd.openxmlformats-officedocument.wordprocessingml.document | docx文档 | |
| application/vnd.ms-excel | xls文档 | |
| application/vnd.openxmlformats-officedocument.spreadsheetml.sheet | xlsx文档 | |
| application/pdf | pdf文档 | |
| audio/mpeg | mp3音频 | |
| video/mp4 | mp4视频 |
3.1 响应报文¶
HTTP响应报文(HTTP Response),是服务端在接收理解、处理并返回客户端操作的结果。主要有三部分组成:响应行,响应头与响应体。注意:针对部分请求方法(delete、OPTION、HEAD)的返回结果有时候是没有响应体的。
协议版本 响应状态码 响应文本提示 <----- http响应的第一行内容,响应行
响应头选项1: 选项值
响应头选项2: 选项值
....
响应头选项n: 选项值
响应体(服务端返回的正文信息,前后必有空行)
3.2.1 响应行¶
HTTP响应行(HTTP Request Line),是HTTP响应报文的首行,由三部分组成,使用单个空格隔开:HTTP协议版本 响应状态码 响应信息。
3.2.1.1 HTTP协议版本¶
响应报文的HTTP协议版本,与客户端的协议版本保持一致。
3.2.1.2 响应状态码¶
状态码(Status Code),用于表达本次服务端在接收客户端请求之后的操作结果的成功或失败结果,由三位整数组成。
| 状态码类型 | 描述 |
|---|---|
| 1xx | 告诉客户端,本次请求,服务端还在持续处理中,并没有结束。 |
| 2xx | 告诉客户端,本次请求,服务端已经接收并成功受理了。 |
| 3xx | 告诉客户端,服务端位置发生改变,希望客户端重定向访问跳转新的服务器地址进行请求 |
| 4xx | 告诉客户端,本次请求有误,服务器无法处理。 |
| 5xx | 告诉客户端,本次请求服务端在处理过程中服务端出错了。 |
3.2.1.3 常见的HTTP状态码¶
| 状态码 | 响应信息 | 描述 |
|---|---|---|
| 101 | Switching Protocols | 服务器已经理解了客户端的请求,并将通过Upgrade消息头通知客户端采用升级协议来完成请求。 |
| 200 | OK | 请求已成功,请求所希望的响应头或数据体将随此响应返回。出现此状态码是表示正常状态。 |
| 201 | Created | 请求已成功,请求的资源已经创建成功或更新完成,常用于POST,PUT或PATCH |
| 204 | No Content | 请求已成功,但是没有任何内容返回。常用于DELETE |
| 301 | Moved Permanently | 永久重定向,表示当前客户端请求的资源地址已经永久发生改变。 |
| 302 | Move Temporarily | 临时重定向,表示当前客户端请求的资源地址还存在,但是访问客户端达不到访问资源的条件,所以暂时无法访问。 |
| 304 | Not Modified | 表示本次客户端请求的资源,并非来自服务端,而是本地缓存。多数情况下有益,少数情况下有害。 |
| 305 | Use Proxy | 被请求的资源必须通过指定的代理才能被访问。 |
| 307 | Temporary Redirect | 请求的资源临时从不同的URI 响应请求。 |
| 400 | Bad Request | 本次请求,报文语义有误或请求参数有误,当前请求无法被服务器理解。 |
| 401 | Unauthorized | 本次请求,需要需要用户验证,但用户并没有提供认证。 |
| 403 | Forbidden | 服务器已经理解请求,但是拒绝执行它。一般是因为没有权限导致的。 |
| 404 | Not Found | 请求失败,请求所希望得到的资源未被在服务器上发现,请求路径不存在。 |
| 405 | Method Not Allowed | 请求行中指定的请求方法不能被用于请求相应的资源。使用了错误的请求方法。 |
| 500 | Internal Server Error | 服务器遇到了一个未曾预料的状况,导致了它无法完成对请求的处理。就是服务端的代码报错了。 |
| 502 | Bad Gateway | 网关宕机,作为网关或者代理工作的服务器尝试执行请求时,从上游服务器接收到无效的响应。一般就是大量访问请求导致服务器瘫痪或宕机了。 |
| 503 | Service Unavailable | 网关过载,由于临时的服务器维护或者过载,服务器当前无法处理请求。 |
| 504 | Gateway Timeout | 网关超时,作为网关或者代理工作的服务器尝试执行请求时,未能及时从上游服务器或者辅助服务器(例如DNS)收到响应。 |
| 507 | Insufficient Storage | 服务器无法存储完成请求所必须的内容。 |
3.2.2 响应头¶
HTTP响应头(Response Head),主要是服务器返回的内容进行补充说明,并提供下一次请求的一些辅助信息。
| 选项 | 描述 |
|---|---|
| Content-Type | 告知客户端,响应数据的MIME类型 |
| Content-Length | 告知客户端,响应数据的字节大小 |
| Content-Encoding | 告知客户端,响应数据采用的压缩格式 |
| Server | 告知客户端,响应服务器的名字或类型 |
| Date | 告知客户端,响应请求的时间 |
| Location | 告知客户端,实际要请求的资源地址(用于301或302进行页面跳转) |
| Cache-Control | 告知客户端,响应数据的缓存机制 |
| Refresh | 告知客户端,要定时刷新的事件间隔 |
| Connection | 告知客户端,本次HTTP通信完成以后是否要保持TCP连接。 |
| Transfer-Encoding | 告知客户端,数据是以分块方式响应回来的。 |
| Content-Disposition | 告知客户端,以下载方式打数据的,格式:Content-Disposition: attachment;filename=文件名 |
| Expires | 告知客户端,响应数据的过期事件,-1表示马上过期,客户端不要缓存当前响应数据。 |
| Retry-After | 告知客户端,应该在多久之后再次发送请求。常见于服务端限流,或503中。 |
| Access-Control-Allow-Origin | 指定客户端通过哪些域名下的脚本可以访问服务器资源。 |
| Access-Control-Allow-Methods | 指定客户端通过哪些HTTP请求方法访问服务端资源。 |
| Access-Control-Allow-Headers | 指定客户端请求服务器的报文中,允许包含哪些请求头。 |
4. 实现http服务器¶
4.1 接收http请求报文的服务端¶
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(("127.0.0.1", 8000))
sock.listen(5)
while True:
try:
c, c_addr = sock.accept()
context = c.recv(1024).decode("utf-8")
print(context)
c.close()
except KeyboardInterrupt:
break
sock.close()
4.2 发送http请求报文的客户端¶
import socket
sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
msg = """POST / HTTP/1.1
User-Agent: PostmanRuntime/7.26.10
Accept: */*
Postman-Token: 522b0792-e19d-4913-a277-d491edf40ce1
Host: 127.0.0.1:8000
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Content-Length: 0
"""
sock.connect(('127.0.0.1', 8000))
sock.send(msg.encode())
4.3 解析http请求报文服务端¶
import socket
import json
class Request(object):
def __init__(self, context):
self.context = context
self._line, self._request = self.context.split("\r\n", maxsplit=1)
self._header, self._body = self._request.rsplit("\r\n\r\n")
self.parse_line()
self.parse_header()
self.parse_body()
def parse_line(self):
"""解析请求报文的首行内容"""
self.method, self.path, self.http_version = self._line.split(" ")
def parse_header(self):
"""解析请求报文的请求头"""
self.header = dict([item.split(": ") for item in self._header.split("\r\n")])
def parse_body(self):
"""解析情趣报文中的请求体"""
self.body = self._body
self.json = {}
if "json" in self.header.get("Content-Type").lower():
self.json = json.loads(self._body)
class Server(object):
def __init__(self, address="0.0.0.0", port=8000):
self.address = address
self.port = port
self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
def bind(self, address=None, port=None):
if address is not None:
self.address = address
if port is not None:
self.port = port
self.server.bind((self.address, self.port))
self.server.listen(5)
def run(self):
print("http server is running....")
print(f"please open the browser: http://{self.address}:{self.port}")
while True:
try:
self.client, self.client_addrinfo = self.server.accept()
self.client_address, self.client_port = self.client_addrinfo
context = self.client.recv(1024).decode("utf-8")
self.request = Request(context)
self.client.close()
except KeyboardInterrupt:
break
self.close()
def close(self):
self.server.close()
if __name__ == '__main__':
server = Server()
server.bind(address="127.0.0.1", port=5001)
server.run()
4.4 响应http请求服务端¶
import socket
import json
class Request(object):
def __init__(self, context):
self.context = context
self.parse_line()
self.parse_header()
self.parse_body()
def parse_line(self):
"""解析请求报文的首行内容"""
self._line, self._request = self.context.split("\n", 1)
self.method, self.path, self.http_version = self._line.split(" ")
def parse_header(self):
"""解析请求报文的请求头"""
lines = self._request.splitlines()
self.header = {}
for line in lines:
res = line.split(": ", 1)
if len(res) < 2:
break
self.header[res[0]] = res[1]
def parse_body(self):
res = self._request.split("\r\n\r\n", 1)
if len(res) == 2:
self._body = res[-1]
elif len(res) < 2:
self._body = self._request.split("\n\n", 1)[-1]
self.json = {}
if "json" in self.header.get("Content-Type", "").lower():
self.json = json.loads(self._body)
print(self._body)
class Response(object):
def __init__(self):
pass
def __str__(self):
return """HTTP/1.1 200 OK
Server: HttpServer
Content-Type: text/html;charset=utf-8
Connection: close
<h1>hello</h1>
"""
class Server(object):
def __init__(self, address="0.0.0.0", port=8000):
self.address = address
self.port = port
self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
def bind(self, address=None, port=None):
if address is not None:
self.address = address
if port is not None:
self.port = port
self.server.bind((self.address, self.port))
self.server.listen(5)
def run(self):
print("http server is running....")
print(f"please open the browser: http://{self.address}:{self.port}")
while True:
try:
self.client, self.client_addrinfo = self.server.accept()
self.client_address, self.client_port = self.client_addrinfo
context = self.client.recv(1024).decode("utf-8")
self.request = Request(context)
self.response = Response()
self.client.send(self.response.__str__().encode("utf-8"))
self.client.close()
except KeyboardInterrupt:
break
self.close()
def close(self):
self.server.close()
if __name__ == '__main__':
server = Server()
server.bind(address="127.0.0.1", port=5001)
# server.bind(address="127.0.0.1", port=5002)
server.run()
4.5 组装http响应报文的服务端¶
import socket
import json
class Request(object):
def __init__(self, context):
self.context = context
self.parse_line()
self.parse_header()
self.parse_body()
def parse_line(self):
"""解析请求报文的首行内容"""
self._line, self._request = self.context.split("\n", 1)
self.method, self.path, self.http_version = self._line.split(" ")
def parse_header(self):
"""解析请求报文的请求头"""
lines = self._request.splitlines()
self.header = {}
for line in lines:
res = line.split(": ", 1)
if len(res) < 2:
break
self.header[res[0]] = res[1]
def parse_body(self):
res = self._request.split("\r\n\r\n", 1)
if len(res) == 2:
self._body = res[-1]
elif len(res) < 2:
self._body = self._request.split("\n\n", 1)[-1]
self.json = {}
if "json" in self.header.get("Content-Type", "").lower():
self.json = json.loads(self._body)
class Response(object):
STATUS = {
200: "OK"
}
def __init__(self, content, status_code, headers=None, content_type=None):
self._status_code = status_code
self._reason = self.STATUS[status_code]
if headers is not None:
self._headers = headers
self.content = content
self.content_type = content_type
def get_request(self, request):
self.request = request
def parse_line(self):
"""组装响应报文的响应行"""
self._line = f"{self.request.http_version} {self._status_code} {self._reason}"
def parse_header(self):
"""组装响应报文的响应头"""
if self.content_type is None:
self.content_type = self.request.header.get("Content-Type", "text/html")
self.headers = {
"Server": "HttpServer",
"Content-Type": self.content_type,
}
self._headers = ""
for key,value in self.headers.items():
self._headers += f"{key}: {value}\n"
def parse_body(self):
"""组装响应报文的响应体"""
self._body = self.content
def __str__(self):
self.parse_line()
self.parse_header()
self.parse_body()
if self._body:
return f"""{self._line}\n{self._headers}\n\n{self._body}"""
return f"""{self._line}\n{self._headers}"""
class Server(object):
def __init__(self, address="0.0.0.0", port=8000):
self.address = address
self.port = port
self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
def bind(self, address=None, port=None):
if address is not None:
self.address = address
if port is not None:
self.port = port
self.server.bind((self.address, self.port))
self.server.listen(5)
def run(self):
print("http server is running....")
print(f"please open the browser: http://{self.address}:{self.port}")
while True:
try:
self.client, self.client_addrinfo = self.server.accept()
self.client_address, self.client_port = self.client_addrinfo
context = self.client.recv(1024).decode("utf-8")
self.request = Request(context)
self.response = Response("<h1>hello!!</h1>", 200)
self.response.get_request(self.request)
self.client.send(self.response.__str__().encode("utf-8"))
self.client.close()
except KeyboardInterrupt:
break
self.close()
def close(self):
self.server.close()
if __name__ == '__main__':
server = Server()
# server.bind(address="127.0.0.1", port=5001)
server.bind(address="127.0.0.1", port=5002)
server.run()
4.6 实现路由分发¶
import socket
import json
from http import server
class Request(object):
def __init__(self, context):
self.context = context
self.parse_line()
self.parse_header()
self.parse_body()
def parse_line(self):
"""解析请求报文的首行内容"""
self._line, self._request = self.context.split("\n", 1)
self.method, self.path, self.http_version = self._line.split(" ")
self.http_version = self.http_version.strip()
def parse_header(self):
"""解析请求报文的请求头"""
lines = self._request.splitlines()
self.header = {}
for line in lines:
res = line.split(": ", 1)
if len(res) < 2:
break
self.header[res[0]] = res[1]
def parse_body(self):
res = self._request.split("\r\n\r\n", 1)
if len(res) == 2:
self._body = res[-1]
elif len(res) < 2:
self._body = self._request.split("\n\n", 1)[-1]
self.json = {}
if "json" in self.header.get("Content-Type", "").lower():
self.json = json.loads(self._body)
class Response(object):
STATUS = {
200: "OK",
201: "Created",
404: "Not Found",
405: "Method Not Allowed",
}
def __init__(self, content="", status_code=200, headers=None, content_type=None):
self._status_code = status_code
self._reason = self.STATUS[status_code]
if headers is not None:
self._headers = headers
self.content = content
self.content_type = content_type
def get_request(self, request):
self.request = request
def parse_line(self):
"""组装响应报文的响应行"""
self._line = fr"{self.request.http_version} {self._status_code} {self._reason}"
def parse_header(self):
"""组装响应报文的响应头"""
if self.content_type is None:
self.content_type = self.request.header.get("Content-Type", "text/html;charset=utf-8")
self.headers = {
"Server": "HttpServer",
"Content-Type": self.content_type,
}
self._headers = ""
for key,value in self.headers.items():
self._headers += f"{key}: {value}\n"
def parse_body(self):
"""组装响应报文的响应体"""
self._body = self.content
def __str__(self):
self.parse_line()
self.parse_header()
self.parse_body()
if self._body:
return f"""{self._line}\n{self._headers}\n\n{self._body}"""
return f"""{self._line}\n{self._headers}"""
class Router(object):
def __init__(self, url, handle, methods):
self.url = url
self.handle = handle
self.methods = methods
def __str__(self):
return f"<Router url={self.url} methods={self.methods} handle={self.handle.__name__}>"
class Server(object):
def __init__(self, address="0.0.0.0", port=8000, routes=None):
self.address = address
self.port = port
self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.routes = []
if routes is not None:
self.routes += routes # 收集所有的url地址与处理程序[函数。类方法]之间的路由关系
def bind(self, address=None, port=None):
if address is not None:
self.address = address
if port is not None:
self.port = port
self.server.bind((self.address, self.port))
self.server.listen(5)
def run(self):
print("http server is running....")
print(f"please open the browser: http://{self.address}:{self.port}")
while True:
try:
self.client, self.client_addrinfo = self.server.accept()
self.client_address, self.client_port = self.client_addrinfo
context = self.client.recv(1024).decode("utf-8")
if context:
self.request = Request(context)
self.response = self.dispatch()
self.response.get_request(self.request)
self.client.send(self.response.__str__().encode("utf-8"))
self.client.close()
except KeyboardInterrupt:
break
self.close()
def dispatch(self):
"""路由分发机制"""
for route in self.routes:
if self.request.path == route.url:
if self.request.method in route.methods:
return route.handle(self.request)
else:
return Response(status_code=405)
else:
return Response(status_code=404)
def close(self):
self.server.close()
def index(request):
return Response("index", 200)
def login(request):
return Response("login", 200)
def create(request):
return Response("<h1>创建成功</h1>", 201)
urlparrents = [
Router("/", index, methods=["GET"]),
Router("/login", login, methods=["POST", "GET"]),
Router("/create", create, methods=["POST"])
]
if __name__ == '__main__':
server = Server(routes=urlparrents)
# server.bind(address="127.0.0.1", port=5001)
server.bind(address="127.0.0.1", port=5002)
server.run()
4.7 实现HTML模板加载¶
import socket
import json
class Request(object):
def __init__(self, context):
self.context = context
self.parse_line()
self.parse_header()
self.parse_body()
def parse_line(self):
"""解析请求报文的首行内容"""
self._line, self._request = self.context.split("\n", 1)
self.method, self.path, self.http_version = self._line.split(" ")
self.http_version = self.http_version.strip()
def parse_header(self):
"""解析请求报文的请求头"""
lines = self._request.splitlines()
self.header = {}
for line in lines:
res = line.split(": ", 1)
if len(res) < 2:
break
self.header[res[0]] = res[1]
def parse_body(self):
res = self._request.split("\r\n\r\n", 1)
if len(res) == 2:
self._body = res[-1]
elif len(res) < 2:
self._body = self._request.split("\n\n", 1)[-1]
self.json = {}
if "json" in self.header.get("Content-Type", "").lower():
self.json = json.loads(self._body)
class Response(object):
STATUS = {
200: "OK",
201: "Created",
404: "Not Found",
405: "Method Not Allowed",
}
def __init__(self, content=None, status_code=200, headers=None, content_type=None):
self._status_code = status_code
self._reason = self.STATUS[status_code]
if headers is not None:
self._headers = headers
self.content = content
self.content_type = content_type
def get_request(self, request):
self.request = request
def parse_line(self):
"""组装响应报文的响应行"""
self._line = fr"{self.request.http_version} {self._status_code} {self._reason}"
def parse_header(self):
"""组装响应报文的响应头"""
if self.content_type is None:
self.content_type = self.request.header.get("Content-Type", "text/html;charset=utf-8")
self.headers = {
"Server": "HttpServer",
"Content-Type": self.content_type,
}
self._headers = ""
for key,value in self.headers.items():
self._headers += f"{key}: {value}\n"
def parse_body(self):
"""组装响应报文的响应体"""
self._body = self.content
def __str__(self):
self.parse_line()
self.parse_header()
self.parse_body()
if self._body:
return f"""{self._line}\n{self._headers}\n\n{self._body}"""
return f"""{self._line}\n{self._headers}"""
class HTMLResponse(Response):
def __init__(self, document, data=None, status_code=200, headers=None, content_type=None):
content = self.load_template(document, data)
super().__init__(content, status_code, headers, content_type)
def load_template(self, document, data):
with open(document, "r") as f:
content = f.read()
for key,value in data.items():
content = content.replace(f"{{{{{key}}}}}", value)
return content
class JsonResponse(Response):
def __init__(self, content, status_code=200, headers=None):
if type(content) is not str:
content = json.dumps(content)
super().__init__(content, status_code, headers, content_type="application/json")
class Router(object):
def __init__(self, url, handle, methods):
self.url = url
self.handle = handle
self.methods = methods
def __str__(self):
return f"<Router url={self.url} methods={self.methods} handle={self.handle.__name__}>"
class Server(object):
def __init__(self, address="0.0.0.0", port=8000, routes=None):
self.address = address
self.port = port
self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 就是它,在bind前加
self.routes = []
if routes is not None:
self.routes += routes # 收集所有的url地址与处理程序[函数。类方法]之间的路由关系
def bind(self, address=None, port=None):
if address is not None:
self.address = address
if port is not None:
self.port = port
self.server.bind((self.address, self.port))
self.server.listen(5)
def run(self):
print("http server is running....")
print(f"please open the browser: http://{self.address}:{self.port}")
while True:
try:
self.client, self.client_addrinfo = self.server.accept()
self.client_address, self.client_port = self.client_addrinfo
context = self.client.recv(1024).decode("utf-8")
if context:
self.request = Request(context)
self.response = self.dispatch()
self.response.get_request(self.request)
self.client.send(self.response.__str__().encode("utf-8"))
self.client.close()
except KeyboardInterrupt:
break
self.close()
def dispatch(self):
"""路由分发机制"""
for route in self.routes:
if self.request.path == route.url:
if self.request.method in route.methods:
return route.handle(self.request)
else:
return Response(status_code=405)
else:
return Response(status_code=404)
def close(self):
self.server.close()
def index(request):
return Response("index", 200)
def login(request):
return JsonResponse({"name":"xiaoming", "age": 16}, 200)
def create(request):
data = {
"title1": "我的第1篇文章标题",
"title2": "我的第2篇文章标题",
}
return HTMLResponse("templates/index.html", data=data, status_code=201)
urlparrents = [
Router("/", index, methods=["GET"]),
Router("/login", login, methods=["POST", "GET"]),
Router("/create", create, methods=["GET"]),
]
if __name__ == '__main__':
server = Server(routes=urlparrents)
server.bind(address="127.0.0.1", port=5001)
# server.bind(address="127.0.0.1", port=5002)
server.run()





