Ответ 1
Обновление: Это 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, вы можете использовать библиотеку почтовых сообщений Pythons для анализа заголовков - хотя мы должны добавить, что результирующий объект Message
действует как словарь заголовков, как только вы закончите его создание:
from email.parser import BytesParser
request_line, headers_alone = request_text.split(b'\r\n', 1)
headers = BytesParser().parsebytes(headers_alone)
print(len(headers)) # -> "3"
print(headers.keys()) # -> ['Host', 'Accept-Charset', 'Accept']
print(headers['Host']) # -> "cm.bell-labs.com"
Но это, конечно, игнорирует строку запроса или заставляет вас разобрать ее самостоятельно. Оказывается, что есть гораздо лучшее решение.
Стандартная библиотека проанализирует HTTP для вас, если вы используете ее BaseHTTPRequestHandler
. Хотя его документация немного неясна - проблема со всем набором инструментов 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 должен выполняться таким образом, чтобы не требовалось, чтобы мы написали девять строк кода для его правильного вызова, но что вы можете сделать? Вот как бы вы использовали этот простой класс:
# Using this new class is really easy!
request = HTTPRequest(request_text)
print(request.error_code) # None (check this first)
print(request.command) # "GET"
print(request.path) # "/who/ken/trust.html"
print(request.request_version) # "HTTP/1.1"
print(len(request.headers)) # 3
print(request.headers.keys()) # ['Host', 'Accept-Charset', 'Accept']
print(request.headers['host']) # "cm.bell-labs.com"
Если во время синтаксического анализа произошла ошибка, error_code
не будет None
:
# Parsing can result in an error code and message
request = HTTPRequest(b'GET\r\nHeader: Value\r\n\r\n')
print(request.error_code) # 400
print(request.error_message) # "Bad request syntax ('GET')"
Я предпочитаю использовать Стандартную библиотеку, как это, потому что я подозреваю, что они уже сталкивались и решали любые крайние случаи, которые могут укусить меня, если я попытаюсь заново реализовать спецификацию Интернета с помощью регулярных выражений.
Старый код 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'
)
А:
# Ignore the request line and parse only the headers
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) # -> "3"
print headers.keys() # -> ['accept-charset', 'host', 'accept']
print headers['Host'] # -> "cm.bell-labs.com"
А:
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
А:
# Using this new class is really easy!
request = HTTPRequest(request_text)
print request.error_code # None (check this first)
print request.command # "GET"
print request.path # "/who/ken/trust.html"
print request.request_version # "HTTP/1.1"
print len(request.headers) # 3
print request.headers.keys() # ['accept-charset', 'host', 'accept']
print request.headers['host'] # "cm.bell-labs.com"
А:
# Parsing can result in an error code and message
request = HTTPRequest('GET\r\nHeader: Value\r\n\r\n')
print request.error_code # 400
print request.error_message # "Bad request syntax ('GET')"