打造mvc框架是个连载的博文,虽然说是要造轮子,但其实更多的是学习web框架的具体实现原理。 其实我发现很多web框架没有直接解析http协议的方法,像bottle,flask都是记住于wsgi server来实现的。 关于wsgi协议,我会专门再讲解一次。
正事因为别的web框架没有直接去实现解析http协议的方法,所以引起了不少的兴趣。 其实单纯对于http协议来说,不是所有人都懂其数据格式。 在chrome的开发者工具network是可以看到一些http数据库,只是数据包是被解析后的了。
该文章写的有些乱,欢迎来喷 ! 另外文章后续不断更新中,请到原文地址查看更新。 http://xiaorui.cc/?p=3193
请求报文的样式:
GET方法的HTTP请求体格式.
#xiaorui.cc GET /search?kw=xiaorui.cc HTTP/1.1 Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, application/x-silverlight, application/x-shockwave-flash, */* Accept-Language: zh-cn Accept-Encoding: gzip, deflate User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727; TheWorld) Host: www.xiaorui.cc Connection: Keep-Alive Cookie: PREF=ID=80a06da87be9ae3c:U=f7167333e2c3b714:NW=1:TM=1261551909:LM=1261551917:S=ybYcq2wpfefs4V9g; NID=31=ojj8d-IygaEtSxLgaJmqSjVhCspkviJrB6omjamNrSm8lZhKy_yMfO2M4QMRKcH1g0iQv9u-2hfBW7bUFwVh7pGaRUb0RnHcJU37y- FxlRugatx63JLv7CWMD6UB_O_r
POST HTTP请求体的格式.
POST /search HTTP/1.1 Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, application/x-silverlight, application/x-shockwave-flash, */* Referer: http://xiaorui.cc Accept-Language: zh-cn Accept-Encoding: gzip, deflate User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727; TheWorld) Host: xiaorui.cc Connection: Keep-Alive Cookie: PREF=ID=80a06da87be9ae3c:U=f7167333e2c3b714:NW=1:TM=1261551909:LM=1261551917:S=ybYcq2wpfefs4V9g; NID=31=ojjd8d-IygaEtSxLgaJmqSjVhCsdfpkviJrB6omjdfamNrSdfm8lZhKy_yMfO2M4QMRKcH1g0iQv9u-2hfBW7bUFwLv7CWMD6xiaoruicc keyword=etcd&range=100
关于response返回值的http格式,headers跟html body是有空格的.
HTTP/1.1 200 OK Date: Sat, 31 Dec 2016 23:59:59 GMT Content-Type: text/html;charset=ISO-8859-1 Content-Length: 122 <html> <head> <title>Golang vs python in xiaorui.cc</title> </head> <body> <h3>xiaorui.cc</h3>h3> </body> </html>
常见http server response status code归类:
1xx:指示信息–表示请求已接收,继续处理。
2xx:成功–表示请求已被成功接收、理解、接受。
3xx:重定向–要完成请求必须进行更进一步的操作。
4xx:客户端错误–请求有语法错误或请求无法实现。
5xx:服务器端错误–服务器未能实现合法的请求。
这里首先使用python标准库mimetools解析http,当然你完全可以用正则匹配的方式来实现,python有标准模块可以解析http协议的。
mimetools源码地址: https://github.com/python-git/python/blob/master/Lib/mimetools.py
#xiaorui.cc from mimetools import Message from StringIO import StringIO request_text = ( 'GET /search?kw=xiaorui.cc&range=10&addr=beijing HTTP/1.1\r\n' 'Host: xiaorui.cc\r\n' 'Connection: Keep-Alive\r\n' 'Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3\r\n' 'Accept: text/html;q=0.9,text/plain\r\n' '\r\n' ) request_line, headers_alone = request_text.split('\r\n', 1) method, path, http_version = request_lone.split() headers = Message(StringIO(headers_alone)) print len(headers) # -> "4" print headers.keys() # -> ['accept-charset', 'host', 'accept','Connection'] print headers['Host'] # -> "xiaorui.cc"
除了使用mimetools Message解析的http协议外,还能使用BaseHTTPServer模块的BaseHTTPRequestHandler基类来解析http包,BaseHTTPServer本身是python内置web服务器,所以他懂得如何解析http协议。
#xiaorui.cc from BaseHTTPServer import BaseHTTPRequestHandler from StringIO import StringIO class HTTPRequest(BaseHTTPRequestHandler): def __init__(self, request_text): self.rfile = StringIO(request_text) self.raw_requestline = self.rfile.readline() self.error_code = self.error_message = None self.parse_request() def send_error(self, code, message): self.error_code = code self.error_message = message request = HTTPRequest(request_text) print request.error_code # None ,说明解析http协议没有出错 print request.command # "GET" print request.path # "/search?kw=xiaorui.cc&range=10&addr=beijing" print request.request_version # "HTTP/1.1" print len(request.headers) # 4 print request.headers.keys() # ['accept-charset', 'host', 'accept'] print request.headers['host'] # "xiaorui.cc" print request.headers.dict # {'accept': 'text/html;q=0.9,text/plain','accept-charset': 'ISO-8859-1,utf-8;q=0.7,*;q=0.3','connection': 'Keep-Alive','host': 'xiaorui.ccr'}
# 如果你的http包体不标准,会出现下面的问题。 一般直接解析来自socket的http包不会出现这问题。
request = HTTPRequest('GET\r\nHeader: Value\r\n\r\n') print request.error_code # 400 print request.error_message # "Bad request syntax ('GET')"
request body已经解析成功了,我们接着来讲cookies和解析url字段。 先手url字段的解析,在python2.6的时候可以使用urllib.parse来解析url中的kv,自从2.7后直接把urlparse独立出来。 urlparse跟mimetools功能有类似,parse_qs方法用来解析kv的。
#xiaorui.cc from urlparse import urlparse,parse_qs o = urlparse('http://xiaorui.cc/search?dummy1=0&dummy2=1') print o #arseResult(scheme='http', netloc='xiaorui.cc', path='/search', params='', query='dummy1=0&dummy2=1', fragment='') print(o.scheme) #http print(o.netloc) #xiaorui.cc print(o.path) #/search print(o.query) #dummy1=0&dummy2=1 parse_qs(o.query) #{'dummy1': ['0'], 'dummy2': ['1']}
cookie我们可以借助于标准库Cookie来实现创建和解析cookie, wsgiref用的是cookie,而flask werkzeug用的是cookielib实现的,反正大同小异没啥好说的。
#xiaorui.cc import Cookie HTTP_COOKIE = "HSID=ASkYmWtLmmlQExHtI; SSID=AaQj0-o-J9uAvpjTf; APISID=zs27PSYVegI0SlwC/Ae5iVNB0VJi28pkim; SAPISID=jVxPJ5etH6QMd_q6/AVwCItf41sQDd5pE1; OGPC=5061968-6:; SID=DQAAABIBAABlCmj19sfpr4sxJvZb8XE7y10DQOJsbckjYfhH_EII3Y_mCTMAdIZ2Hwe3tecCgf7lQgMAWIalNLpj1rL5JgObTWZGdMkkBaCQW1Gxdzuw7l32a6ISyJlnEdiMapoq_V3R0-bCeaILV_XHqjTMI_jRZA8bUpRBwl3oZGPAZoWNv02zQ1A1Ig-wLez2AACNecdjBSPcDC4Qf_s0IaSMsBL_k1QLxjES0xpSOJHa98AShB9HgXSCtEbnm41f8eDpUvRttV2UAViLXtx8jyStnoM7sam9JI4sWWfql3OjCVzTOsGoBIHEiXnFwgNAmLvjjNSA6AZw885mlvVE6Um-1Yx0n5xBGxpXvS0lRIXgvXiJ8Ha49DrsFecCG-UE1dOt5jQ; NID=78=LCX6AhGCMIsDhPnZogH78AfgL_tZl-w6ViwIfQLCjr4CDCMEbnDvpkbJriDJ4HCHlzaKsmAxxap0RWm3_WLZJtqW3C_DWOF8rzUy4JOGCY3iIYzFn977uYuPNRX1p2FjGXspxypDbqFVw12sM7cgz-ueAOvaj8pdu8eDe3lakSkDoeGXcypApf7P2xuqW0uxQp5CAEWwq9ATY3BRH4uWZjhCJaSYUvmF2Be-cKwKjGuiTbUAYlCy_PrvqKLegFt9TyJXPYT3mSgH_g; DV=olnCZbEeK0M7ZDJOLOrsKW3SQXcgqBK1kOCkfffAXQAAAG6E282OSdGtGgAAAA" print 'From constructor:' c = Cookie.SimpleCookie(HTTP_COOKIE) print c print 'From load():' c = Cookie.SimpleCookie() c.load(HTTP_COOKIE) #装载cookie字符串,创建可解析对象 print c #拿到的是字典 c.keys()
到这里为止关于http的解析我们已经说完了,那么我们怎么发给对方,其实跟解析http的过程类似,都是拼凑http包体格式就可以了。 下面函数是我在gevent wsgi找到的,一个函数就说明如何拼凑http response包体。
#xiaorui.cc def finish_response(self, result): try: status, response_headers = self.headers_set response = 'HTTP/1.1 {status}\r\n'.format(status=status) for header in response_headers: response += '{0}: {1}\r\n'.format(*header) response += '\r\n' for data in result: response += data print(''.join( '> {line}\n'.format(line=line) for line in response.splitlines() )) self.client_connection.sendall(response) finally: self.client_connection.close()
不知道别人是否我个一样,每当搞懂某个算法和数据结构时会异常的高兴,远比看懂一个模块的使用有意思的。 这次”打造mvc架构”重在体会web框架是如何开发出来的,原理是怎么一回事。 连flask,bottle都不自己写http解析模块,我虽然意在造轮子,但也不会这么偏执。 现在的python web框架都有wsgi的支持,我们下次聊聊wsgi带给web框架的好处。
END.