2011-01-13 38 views
26

Tengo una cadena de HTTP sin procesar y me gustaría representar los campos en un objeto. ¿Hay alguna forma de analizar los encabezados individuales desde una cadena HTTP?Parse HTTP encabezados sin procesar

'GET /search?sourceid=chrome&ie=UTF-8&q=ergterst HTTP/1.1\r\nHost: www.google.com\r\nConnection: keep-alive\r\nAccept: application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5\r\nUser-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_6; en-US) AppleWebKit/534.13 (KHTML, like Gecko) Chrome/9.0.597.45 Safari/534.13\r\nAccept-Encoding: gzip,deflate,sdch\r\nAvail-Dictionary: GeNLY2f-\r\nAccept-Language: en-US,en;q=0.8\r\n 
[...]' 

Respuesta

7

Esto parece funcionar bien si se tira de la línea GET:

import mimetools 
from StringIO import StringIO 

he = "Host: www.google.com\r\nConnection: keep-alive\r\nAccept: application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5\r\nUser-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_6; en-US) AppleWebKit/534.13 (KHTML, like Gecko) Chrome/9.0.597.45 Safari/534.13\r\nAccept-Encoding: gzip,deflate,sdch\r\nAvail-Dictionary: GeNLY2f-\r\nAccept-Language: en-US,en;q=0.8\r\n" 

m = mimetools.Message(StringIO(he)) 

print m.headers 

Una manera de analizar su ejemplo y añadir información desde la primera línea al objeto sería:

import mimetools 
from StringIO import StringIO 

he = 'GET /search?sourceid=chrome&ie=UTF-8&q=ergterst HTTP/1.1\r\nHost: www.google.com\r\nConnection: keep-alive\r\n' 

# Pop the first line for further processing 
request, he = he.split('\r\n', 1)  

# Get the headers 
m = mimetools.Message(StringIO(he)) 

# Add request information 
m.dict['method'], m.dict['path'], m.dict['http-version'] = request.split()  

print m['method'], m['path'], m['http-version'] 
print m['Connection'] 
print m.headers 
print m.dict 
+0

¿Hay una manera de hacer esto en python3? – Broseph

+0

mimetools está en desuso desde 2.3 –

+0

@Broseph Vea la respuesta de Gowtham. – JeromeJ

72

Existen excelentes herramientas en la biblioteca estándar para analizar los encabezados RFC 821 y también para analizar todas las solicitudes HTTP. He aquí un ejemplo de cadena pedido (tenga en cuenta que Python lo trata como una cadena grande, a pesar de que estamos rompiendo a través de varias líneas para facilitar la lectura) que podemos alimentar a mis ejemplos:

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' 
    ) 

Como @TryPyPy señala, puede utilizar mimetools.Message analizar los encabezados - aunque hay que añadir que las resultantes Message objeto actúa como un diccionario de cabeceras vez que haya terminado de crearlo:

# 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" 

Pero esto, por supuesto, ignora la línea de petición, o te hace analizarlo tú mismo. Resulta que hay una solución mucho mejor.

La biblioteca estándar analizará HTTP por usted si usa su BaseHTTPRequestHandler. Aunque su documentación es un poco oscura, un problema con todo el conjunto de herramientas HTTP y URL en la Biblioteca estándar, todo lo que tiene que hacer para analizar una cadena es (a) ajustar la cadena en un StringIO(), (b) leer el raw_requestline para que esté listo para ser analizado, y (c) capture cualquier código de error que ocurra durante el análisis en lugar de dejar que intente escribirlos de nuevo en el cliente (¡ya que no tenemos uno!).

Así que aquí es nuestra especialización de la clase Biblioteca Estándar:

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 

Una vez más, deseo que la gente de la biblioteca estándar se habían dado cuenta de que el análisis de HTTP debe ser roto a cabo de una manera que no nos requiere para escribir nueve líneas de código para llamarlo correctamente, pero ¿qué puedes hacer? Aquí es cómo se utiliza esta clase simple:

# 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" 

Si hay un error durante el análisis, la error_code no habrá None:

# 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')" 

Yo prefiero usar la biblioteca estándar como éste porque sospecho que ya han encontrado y resuelto cualquier caso límite que pueda afectarme si trato de volver a implementar una especificación de Internet con expresiones regulares.

+0

Esto es increíble, gracias! Sin embargo, necesito mantener el orden de la información archivada del encabezado HTTP (los diccionarios no mantienen el orden). ¿Hay alguna forma de hacer esto? – jeffrey

+1

¡No estoy seguro! En el fondo del 'Mensaje' y de las clases de solicitud que Python utiliza para hacer este análisis, supongo que debería haber una línea de código que crea el diccionario de encabezados. Si se pudiera decir que utilizara en su lugar un 'OrderedDict' en lugar de un' dict' simple, entonces sabría el orden, pero, después de haber recorrido el código brevemente, no pude decir dónde se creó el diccionario del encabezado. –

7

mimetools ha quedado obsoleto desde Python 2.3 y totalmente eliminado de Python 3 (link).

Aquí es cómo se debe hacer en Python 3:

import email 
import io 
import pprint 

# […] 

request_line, headers_alone = request_text.split('\r\n', 1) 
message = email.message_from_file(io.StringIO(headers_alone)) 
headers = dict(message.items()) 
pprint.pprint(headers, width=160) 
+0

'email.message_from_file (io.StringIO (headers_alone))' podría reemplazarse por 'email.message_from_string (headers_alone)'. – eigenein

Cuestiones relacionadas