web-dev-qa-db-fra.com

Analyser les en-têtes HTTP bruts

J'ai une chaîne de HTTP brute et j'aimerais représenter les champs d'un objet. Est-il possible d'analyser les en-têtes individuels à partir d'une chaîne HTTP?

'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
[...]'
31
Cev

La bibliothèque standard contient d’excellents outils pour l’analyse des en-têtes RFC 821 et pour l’analyse de requêtes HTTP complètes. Voici un exemple de chaîne de requête (notez que Python la traite comme une grande chaîne, même si nous la découpons sur plusieurs lignes pour plus de lisibilité) que nous pourrons alimenter mes exemples:

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

Comme @TryPyPy le fait remarquer, vous pouvez utiliser mimetools.Message pour analyser les en-têtes - bien que nous devions ajouter que l'objet Message résultant agit comme un dictionnaire d'en-têtes une fois que vous l'avez créé:

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

Mais ceci, bien sûr, ignore la ligne de demande ou vous oblige à l'analyser vous-même. Il s'avère qu'il existe une bien meilleure solution.

La bibliothèque standard analysera HTTP pour vous si vous utilisez sa BaseHTTPRequestHandler. Bien que sa documentation soit un peu obscure - un problème avec l'ensemble des outils HTTP et URL de la bibliothèque standard - tout ce que vous avez à faire pour lui permettre d'analyser une chaîne est (a) d'envelopper votre chaîne dans un StringIO(), (b) read le raw_requestline pour qu'il soit prêt à être analysé, et (c) capturez tous les codes d'erreur qui se produisent pendant l'analyse au lieu de le laisser essayer de les écrire au client (puisque nous n'en avons pas!).

Donc, voici notre spécialisation de la classe Standard Library:

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

Encore une fois, j'aurais aimé que les gens de la bibliothèque standard aient compris que l'analyse HTTP devait être décomposée de manière à ne pas nécessiter l'écriture de neuf lignes de code pour l'appeler correctement, mais que pouvez-vous faire? Voici comment vous utiliseriez cette classe 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"

S'il y a une erreur lors de l'analyse, le error_code ne sera pas 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')"

Je préfère utiliser la bibliothèque standard comme celle-ci car je soupçonne qu'ils ont déjà rencontré et résolu tous les cas Edge qui pourraient me piquer si j'essaie de ré-implémenter moi-même une spécification Internet avec des expressions régulières.

77
Brandon Rhodes

mimetools est obsolète depuis Python 2.3 et totalement supprimé de Python 3 ( link ).

Voici comment vous devriez faire dans 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)
10
Gowtham

Cela semble fonctionner correctement si vous supprimez la ligne 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

Une façon d'analyser votre exemple et d'ajouter des informations de la première ligne à l'objet serait:

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
7
TryPyPy

Utiliser python3.7, urllib3.HTTPResponse, http.client.parse_headers et avec l'explication du drapeau curl ici :

curl -i -L -X GET "http://httpbin.org/relative-redirect/3" |  python -c '
import sys
from io import BytesIO
from urllib3 import HTTPResponse
from http.client import parse_headers

rawresponse = sys.stdin.read().encode("utf8")
redirects = []

while True:
    header, body = rawresponse.split(b"\r\n\r\n", 1)
    if body[:4] == b"HTTP":
        redirects.append(header)
        rawresponse = body
    else:
        break

f = BytesIO(header)
# read one line for HTTP/2 STATUSCODE MESSAGE
requestline = f.readline().split(b" ")
protocol, status = requestline[:2]
headers = parse_headers(f)

resp = HTTPResponse(body, headers=headers)
resp.status = int(status)

print("headers")
print(resp.headers)

print("redirects")
print(redirects)
'

Sortie:

  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
100   215  100   215    0     0    435      0 --:--:-- --:--:-- --:--:--   435

headers
HTTPHeaderDict({'Connection': 'keep-alive', 'Server': 'gunicorn/19.9.0', 'Date': 'Thu, 20 Sep 2018 05:39:25 GMT', 'Content-Type': 'application/json', 'Content-Length': '215', 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Credentials': 'true', 'Via': '1.1 vegur'})
redirects
[b'HTTP/1.1 302 FOUND\r\nConnection: keep-alive\r\nServer: gunicorn/19.9.0\r\nDate: Thu, 20 Sep 2018 05:39:24 GMT\r\nContent-Type: text/html; charset=utf-8\r\nContent-Length: 0\r\nLocation: /relative-redirect/2\r\nAccess-Control-Allow-Origin: *\r\nAccess-Control-Allow-Credentials: true\r\nVia: 1.1 vegur',
 b'HTTP/1.1 302 FOUND\r\nConnection: keep-alive\r\nServer: gunicorn/19.9.0\r\nDate: Thu, 20 Sep 2018 05:39:24 GMT\r\nContent-Type: text/html; charset=utf-8\r\nContent-Length: 0\r\nLocation: /relative-redirect/1\r\nAccess-Control-Allow-Origin: *\r\nAccess-Control-Allow-Credentials: true\r\nVia: 1.1 vegur',
 b'HTTP/1.1 302 FOUND\r\nConnection: keep-alive\r\nServer: gunicorn/19.9.0\r\nDate: Thu, 20 Sep 2018 05:39:24 GMT\r\nContent-Type: text/html; charset=utf-8\r\nContent-Length: 0\r\nLocation: /get\r\nAccess-Control-Allow-Origin: *\r\nAccess-Control-Allow-Credentials: true\r\nVia: 1.1 vegur']

remarques:

0
jmunsch