53

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!

Trevor Boyd Smith
  • 18,164
  • 32
  • 127
  • 177
pistacchio
  • 56,889
  • 107
  • 278
  • 420

2 Answers2

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 = {}
    ...
adw
  • 4,901
  • 1
  • 25
  • 18
  • maybe you'll see this :) Do you know of any way for postvars to be available outside of the handler class? – KevinDTimm Jun 01 '11 at 16:07
  • @KevinDTimm, this is... oh, around a year later, but if you add a [static member](http://stackoverflow.com/a/3506218/344286) to the handler class, then you can access it anywhere that can access the class. – Wayne Werner May 10 '12 at 12:23
  • @WayneWerner - I did see this (love the name tags!). Thanks. – KevinDTimm May 14 '12 at 14:47
  • 12
    Recent versions of Python have the parse_qs and parse_qsl functions in urlparse instead of cgi. – acrosman Jun 21 '12 at 12:56
  • Content-Length is not required for chunked request body – youfu Sep 13 '17 at 09:49
38

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()
        ...

    ...
Kara
  • 6,115
  • 16
  • 50
  • 57
d33tah
  • 10,999
  • 13
  • 68
  • 158
  • 3
    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? – Serge Rogatch Jun 18 '20 at 08:29
  • 2
    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. – Fusion Jun 26 '20 at 15:53
  • 1
    @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). – András Aszódi Dec 22 '22 at 16:00