更新: 现在是2019年,我已经为Python 3重新编写了这个答案,因为一位程序员试图使用代码时发生了混淆的评论。原始的Python 2代码现在放在答案底部。
标准库中有优秀的工具,既能够解析RFC 821头文件,也可以解析整个HTTP请求。这里有一个示例请求字符串(请注意,Python将其视为一个大字符串,即使我们将其分成几行以提高可读性),我们可以将其提供给我的示例:
request_text = (
b'GET /who/ken/trust.html HTTP/1.1\r\n'
b'Host: cm.bell-labs.com\r\n'
b'Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3\r\n'
b'Accept: text/html;q=0.9,text/plain\r\n'
b'\r\n'
)
正如@TryPyPy所指出的,你可以使用Python的电子邮件消息库来解析标题 - 尽管我们应该补充一点,一旦你完成创建它,生成的对象就像一个头部字典:
from email.parser import BytesParser
request_line, headers_alone = request_text.split(b'\r\n', 1)
headers = BytesParser().parsebytes(headers_alone)
print(len(headers))
print(headers.keys())
print(headers['Host'])
不过,这种方法忽略了请求行,或者让您自己解析它。事实证明,有一个更好的解决方案。
如果使用标准库的BaseHTTPRequestHandler
,它将为您解析HTTP。虽然其文档有点晦涩——这是标准库中整个HTTP和URL工具套件的问题——但要使其解析字符串,您只需(a)将字符串包装在BytesIO()
中,(b)读取raw_requestline
,以便它准备好被解析,并(c)捕获在解析期间发生的任何错误代码,而不是让它尝试将它们写回客户端(因为我们没有客户端!)。
以下是我们对标准库类的特殊化:
from http.server import BaseHTTPRequestHandler
from io import BytesIO
class HTTPRequest(BaseHTTPRequestHandler):
def __init__(self, request_text):
self.rfile = BytesIO(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
我希望标准库的开发人员能够意识到HTTP解析应该以一种不需要我们编写九行代码才能正确调用的方式分离出来,但是我们能做什么呢?以下是您如何使用这个简单类:
request = HTTPRequest(request_text)
print(request.error_code)
print(request.command)
print(request.path)
print(request.request_version)
print(len(request.headers))
print(request.headers.keys())
print(request.headers['host'])
如果在解析过程中出现错误,error_code
将不会是 None
:
request = HTTPRequest(b'GET\r\nHeader: Value\r\n\r\n')
print(request.error_code)
print(request.error_message)
我更喜欢像这样使用标准库,因为我怀疑如果我试图使用正则表达式重新实现一个互联网规范,那么可能会遇到一些问题,而标准库已经遇到并解决了这些问题。
旧的Python 2代码
以下是我最初编写答案时的原始代码:
request_text = (
'GET /who/ken/trust.html HTTP/1.1\r\n'
'Host: cm.bell-labs.com\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'
)
而且:
from mimetools import Message
from StringIO import StringIO
request_line, headers_alone = request_text.split('\r\n', 1)
headers = Message(StringIO(headers_alone))
print len(headers)
print headers.keys()
print headers['Host']
并且:
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
print request.command
print request.path
print request.request_version
print len(request.headers)
print request.headers.keys()
print request.headers['host']
而且:
request = HTTPRequest('GET\r\nHeader: Value\r\n\r\n')
print request.error_code
print request.error_message
Message
和请求类内部应该有一行代码来创建头字典。如果可以告诉它使用OrderedDict
而不是普通的dict
,那么您就会知道顺序 - 但是,我刚刚简要地浏览了代码,无法确定头字典是在哪里创建的。 - Brandon Rhodes