【Python】简单Web框架从零开始(二):Http请求格式
目录
一、前言
上一篇文章已经搭建了一个简单的http的网络服务器,可以接收浏览器的请求并简单响应。这期开始介绍http协议的具体格式以及如何解析,通过理解http的协议格式,就可以正确识别出客户端的请求内容,并根据对应的处理方法做出正确的响应。
二、HTTP请求格式介绍
为了可以快速掌握,可以启动第一期的服务程序,然后通过浏览器访问对应的地址(http://127.0.0.1),服务器会在接收到客户端数据后将其打印出来,从而方便的进行结构分析。
图1
图1展示了浏览器访问http://127.0.0.1/地址的请求数据内容,具体内容解释如下:
1. 第一行为请求行包含:请求方法(本图为GET),访问服务器的地址路径(本图为根路径/),HTTP的版本(本图为1.1),这3个数据之间使用空格分隔开。
2. 中间为实体头信息包含通用头信息、请求头信息、实体头信息。其中部分是客户端的相关信息及功能支持情况,还有一些特殊的例如Cookie和Cache相关的信息。
3. 最后会有一个空行(\r\n)表示实体头内容的结束标记。
4. 请求数据体,本图中由于没有任何额外的数据,因此没有后续内容输出。
图2
图2展示了浏览器带参数的请求,请求地址为http://127.0.0.1/test?username=abc&password=123456。其他内容都是和图1差不多一样的,注意后面的空行是存在的。这种请求的方式参数都显示在url地址中,如果参数够多整个url就非常的长长长
图3
图3展示了浏览器POST带参数请求,请求地址为http://127.0.0.1/test,参数数据存放在实体头的后面即请求数据体,参数的数据格式与图3放在地址中的内容是一样的,这样就可以让url看起来简单,并且“隐藏”参数信息。注意这里实体头里面有一个Content-Length属性数据表示后面HTTP数据体的长度,Content-Type属性表示HTTP数据体的格式类型。
图4
图4是另一种格式的POST请求,Content-Type为multipart/form-data,并且额外带有boundary的属性值。HTTP数据体的内容格式稍微复杂了一些,采用[--boundary]进行分割,并对每个参数可以额外指定类型,包括支持上传文件。
图5
图5是访问静态文件的请求格式,与图1基本一致,只是地址中包含了要请求下载的文件名和文件格式,服务器只需要按照正确的格式将需要的静态文件内容发送给客户端就可以完成下载。
下图是完整的HTTP请求格式说明:
三、解析HTTP请求
快速理解客户端请求的格式后,就可以进行请求数据解析。每条连接发送请求数据后,服务器读取数据并开始进行解析,然后调用对应的处理方法后返回正确的响应数据就可以完成交互通讯。
1. 从首行内容解析开始,利用了将socket的recv方式改为文件的read方式,可以简化处理过程,如下代码:
# 使用文件的方式操作socket rfile = self.sock.makefile() #mode="r" 读取 "w"写入 # 解析第一行 first_line = rfile.readline() # GET/POST/HEAD url&args HTTP/1.1 method_location_version = first_line.split()
2. 实体头内容格式是固定的,每一行格式为:[name]:[空格][数据][\r\n] 表示属性name的对应数据值,部分数据值需要继续拆分。实体头内容完后会跟着一个独立的空行[\r\n]。可以以这个作为实体头结束的标记
while True: line = fd.readline().strip() if not line : break name_value = line.split(": ", 1) if not name_value or len(name_value) != 2: break # error format name = name_value[0] value = name_value[1] self.request_headers[name] = value.strip()
3. 最后就是解析参数了,类似uesrname=abc&password=123456这样的格式可以使用urlparse.parse_qs进行解析成一个字典{"username"=["abc"],"password"=["123456"]},但multipart/form-data格式的解析就需要自己实现,我在自带库没有找到相关的解析方法,如果有知道的希望可以告诉我一下
# url中存放参数 /xxxx?a=v1&b=v2... index = self.request_route.find("?") if index > 0 and len(self.request_route) > index: arguments = urlparse.parse_qs(self.request_route[index+1:]) self.request_arguments.update(arguments) # 解析请求内容的格式 form_str_type = self.request_headers.get("Content-Type", None) if not form_str_type: return # 如果没有指定类型则不理会了 # 将参数放在正文中,但格式和放在url里面的一样 if form_str_type.find("application/x-www-form-urlencoded") >= 0: arguments = urlparse.parse_qs(self.raw_body_data) self.request_arguments.update(arguments) # 将参数放在正文中,采用多附件表单格式 elif form_str_type.find("multipart/form-data") >= 0: boundary_index = form_str_type.rfind("boundary=") if boundary_index < 0: print("Invalid multipart/form-data: no boundary") return arguments = self.parse_multipart_formdata(form_str_type[boundary_index+len("boundary="):]) self.request_arguments.update(arguments)
四、后记
截至目前介绍了http服务的创建和客户端请求格式的解析,接下来会继续介绍http响应的格式以及如何处理客户端的请求,从而真正完成网站的基本交互逻辑。另外我实现的这个简单web框架仅支持HTTP/1.0和HTTP/1.1的请求,最新的HTTP/2还没接触过。
欢迎微信搜索"游戏测试开发"关注一起沟通交流。