Python: How do I get key/value pairs from the BaseHTTPRequestHandler HTTP POST handler?
Asked Answered
C

2

55

given the simplest HTTP server, how do I get post variables in a BaseHTTPRequestHandler?

from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer

class Handler(BaseHTTPRequestHandler):
    def do_POST(self):
        # post variables?!

server = HTTPServer(('', 4444), Handler)
server.serve_forever()

# test with:
# curl -d "param1=value1&param2=value2" http://localhost:4444

I would simply like to able to get the values of param1 and param2. Thanks!

Critique answered 20/11, 2010 at 14:36 Comment(0)
V
62
def do_POST(self):
    ctype, pdict = cgi.parse_header(self.headers.getheader('content-type'))
    if ctype == 'multipart/form-data':
        postvars = cgi.parse_multipart(self.rfile, pdict)
    elif ctype == 'application/x-www-form-urlencoded':
        length = int(self.headers.getheader('content-length'))
        postvars = cgi.parse_qs(self.rfile.read(length), keep_blank_values=1)
    else:
        postvars = {}
    ...
Vasoconstrictor answered 20/11, 2010 at 15:25 Comment(5)
maybe you'll see this :) Do you know of any way for postvars to be available outside of the handler class?Aniakudo
@KevinDTimm, this is... oh, around a year later, but if you add a static member to the handler class, then you can access it anywhere that can access the class.Spellbound
@WayneWerner - I did see this (love the name tags!). Thanks.Aniakudo
Recent versions of Python have the parse_qs and parse_qsl functions in urlparse instead of cgi.Primal
Content-Length is not required for chunked request bodyKeyhole
G
39

Here's my version of this code that should work on Python 2.7 and 3.2:

from sys import version as python_version
from cgi import parse_header, parse_multipart

if python_version.startswith('3'):
    from urllib.parse import parse_qs
    from http.server import BaseHTTPRequestHandler
else:
    from urlparse import parse_qs
    from BaseHTTPServer import BaseHTTPRequestHandler

class RequestHandler(BaseHTTPRequestHandler):

    ...

    def parse_POST(self):
        ctype, pdict = parse_header(self.headers['content-type'])
        if ctype == 'multipart/form-data':
            postvars = parse_multipart(self.rfile, pdict)
        elif ctype == 'application/x-www-form-urlencoded':
            length = int(self.headers['content-length'])
            postvars = parse_qs(
                    self.rfile.read(length), 
                    keep_blank_values=1)
        else:
            postvars = {}
        return postvars

    def do_POST(self):
        postvars = self.parse_POST()
        ...

    ...
Gradate answered 11/11, 2012 at 10:50 Comment(4)
In Python 3.8 urllib.parse.parse_qs() returns bytes rather than strings for me, both as keys and as values of the dictionary. How to make it to return a dictionary with keys and values as strings?Teahouse
For those who are interested WHY it works: You can strangely notice that parse_reader() starts reading exactly at position where the request body is located. This may be because some code before read the top line and then request headers. Each read like this increased position of read/write pointer. In case you will do some reading of self.rfile between parsing headers and this code, this solution will not work as expected. Be aware.Unsling
@SergeRogatch Valid observation (also in Python 3.10). Quick fix: convert the bytes to strings using the bytes.decode() method which by default uses UTF-8 . One-liner: pstr = { key.decode(): [ v.decode() for v in vals ] for key, vals in postvars.items() } and then return pstr (which makes it a two-liner).Maria
Doesn't work in Python 3.11. cgi is deprecated and I get an error trying to parse form data in a POST request.Choanocyte

© 2022 - 2024 — McMap. All rights reserved.