从python BaseHTTPServer上深入理解HTTP协议
因为项目关系,需要写一个简单的web服务做测试桩,为了开发和部署方便,选择了Python开发,及python自带的BaseHTTPServer,不依赖其他三方件。
关于如何用Python编写一个简单的Http Server,可以自行搜索,我参考了这篇文章:https://blog.csdn.net/u010087956/article/details/50500071
奇怪的错误
在给浏览器返回response时,碰到了奇怪的错误,折腾了很久:
try:
self.send_header("Content-type", "text/xml")
self.send_header("Content-Length", str(len(content)))
self.end_headers()
self.wfile.write(content)
self.send_response(200)
except Exception, e:
print >> sys.stderr, "request failed: %s" % e
sys.exit(1)
总是报10053错误,没有任何错误message。最后仔细看了BaseHTTPRequestHandler的源码,调整try的语句顺序,成功!
try:
self.send_response(200)
self.send_header("Content-type", "text/xml")
self.send_header("Content-Length", str(len(content)))
self.end_headers()
self.wfile.write(content)
except Exception, e:
print >> sys.stderr, "request failed: %s" % e
sys.exit(1)
协议
要理解这个问题,得先从http协议关于response的定义说起,HTTP基本上就是一个文本协议,HTTP响应由四个部分组成,分别是:状态行、消息报头、空行和响应正文。
示例:
HTTP/1.1 200 OK
Date: Mon, 27 Jul 2009 12:28:53 GMT
Server: Apache
Content-Length: 51
Content-Type: text/xml
<html>
<body>
<p>Hello, world!</p>
</body>
</html>
- 第一部分:状态行由HTTP协议版本号, 状态码, 状态消息 三部分组成。
第一行为状态行,(HTTP/1.1)表明HTTP版本为1.1版本,状态码为200,状态消息为(ok) - 第二部分:消息报头,用来说明客户端要使用的一些附加信息
第二到第五行为消息报头,Date:生成响应的日期和时间;Content-Type:指定了MIME类型的HTML(text/html);Server:服务器类型;Content-Length: 消息大小。 - 第三部分:空行,消息报头后面的空行是必须的
- 第四部分:响应正文,服务器返回给客户端的文本信息。
空行后面的html部分为响应正文。
源码分析
self.send_response(200)
对应的源码:
if self.request_version != 'HTTP/0.9':
self.wfile.write("%s %d %s\r\n" %
(self.protocol_version, code, message))
# print (self.protocol_version, code, message)
self.send_header('Server', self.version_string())
self.send_header('Date', self.date_time_string())
send_response()会先输出状态行,然后换行写入两个报头行(‘Server’和’Date’)。
def send_header(self, keyword, value):
"""Send a MIME header."""
if self.request_version != 'HTTP/0.9':
self.wfile.write("%s: %s\r\n" % (keyword, value))
send_header就是按行以"key: value"格式写入消息头。
def end_headers(self):
"""Send the blank line ending the MIME headers."""
if self.request_version != 'HTTP/0.9':
self.wfile.write("\r\n")
end_headers则是直接写入一个空行。
self.wfile.write(page)则是在socket连接上写入内容。
Cookie
搞定报文返回后,又碰到cookie设置的问题了。本以为cookie会有单独的API设置,结果找来找去都没找到,查看资料才发现需要通过header设置,好吧,又是拼基础知识了,拜读了这篇文章:https://www.cnblogs.com/ajianbeyourself/p/4900140.html。
cookie就是存储在用户主机浏览器中的一小段文本文件。Cookies是纯文本形式,它们不包含任何可执行代码。一个Web页面或服务器告之浏览器来将这些信息存储并且基于一系列规则在之后的每个请求中都将该信息返回至服务器。Web服务器之后可以利用这些信息来标识用户。
创建cookie
服务端创建cookie,通过HTTP的Set-Cookie消息头,Web服务器可以指定存储一个cookie。Set-Cookie消息的格式如下面的字符串(中括号中的部分都是可选的)
Set-Cookie:value [ ;expires=date][ ;domain=domain][ ;path=path][ ;secure]
例如
self.send_header('Set-Cookie', 'session_id=' + self.succ_session + ';expires=Mon, 22 Jul 2019 07:52:50 GMT;domain=abc.com;path=/;HTTPOnly;')
读取cookie
通过HTTPHeader的cookie消息头来读取,例如
self.headers['Cookie']
注意(坑)
- secure选项表示cookie只通过https协议传输。http协议下设置的cookie不会被浏览器返回。
- domain选项表示cookie作用域,不支持IP。Set-Cookie成功,但是不会被浏览器返回给服务端。
总结
从BaseHTTPServer源码可以看到,代码完全根据http协议一行行的输出,几个函数调用要保持固定调用顺序。
平时使用java的spring boot等等封装好了的类库,完全忽略了协议的本身;这次使用最原始的工具加深了对协议的理解!