打造mvc框架之解析socket底层的http协议

打造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.


大家觉得文章对你有些作用! 如果想赏钱,可以用微信扫描下面的二维码,感谢!
另外再次标注博客原地址  xiaorui.cc

发表评论

邮箱地址不会被公开。 必填项已用*标注