As I explore different ways of doing Web programming in Python via different Frameworks, I kept finding the need to examine HTTP server variables, specifically the server hostname, path, and query string. The method to do this varies quite a bit by framework.
Given the following URL: http://www.mydomain.com:8000/derp/test.py?name=Harry&occupation=Hit%20Man
I want to create the following variables with the following values:
- server_host is ‘www.mydomain.com’
- server_port is 8000
- path is ‘/derp/test.py’
- query_params is this dictionary: {‘name’: ‘Harry’, ‘occupation’: ‘Hit Man’}
Old School CGI
cgi.FieldStorage() is the easy way to do this, but it returns a list of tuples, and must be converted to a dictionary.
#!/usr/bin/env python3
if __name__ == "__main__":
import os, cgi
server_host = os.environ.get('HTTP_HOST', 'localhost')
server_port = os.environ.get('SERVER_PORT', 80)
path = os.environ.get('SCRIPT_URL', '/')
query_params = {}
_ = cgi.FieldStorage()
for key in _:
query_params[key] = str(_[key].value)
Note this will convert all values to strings. By default, cgi.FieldStorage() create numberic values as int or float.
WSGI
Similar to CGI, but environment variables get passed simply in a dictionary as the first parameter. There is no need to load the os module.
def application(environ, start_response):
from urllib import parse
server_host = environ.get('HTTP_HOST', 'localhost')
server_port = environ.get('SERVER_PORT', 80)
path = environ.get('SCRIPT_URL', '/')
query_params = {}
if '?' in environ.get('REQUEST_URI', '/'):
query_params = dict(parse.parse_qsl(parse.urlsplit(environ['REQUEST_URI']).query))
Since the CGI Headers don’t exist, urllib.parse can be used to analyze the REQUEST_URI environment variable in order to form the dictionary.
Flask
Flask makes this very easy. The only real trick comes with path; the ‘/’ gets removed, so it must be re-added
from flask import Flask, request
app = Flask(__name__)
# Route all possible paths here
@app.route("/", defaults={"path": ""})
@app.route('/<string:path>')
@app.route("/<path:path>")
def index(path):
[server_host, server_port] = request.host.split(':')
path = "/" + path
query_params = request.args
FastAPI
This one’s a slightly different because the main variable to examine actually a QueryParams object with is a form of MultiDict
from fastapi import FastAPI, Request
app = FastAPI()
# Route all possible paths here
@app.get("/")
@app.get("/{path:path}")
def root(path, req: Request):
[server_host, server_port] = req.headers['host'].split(':')
path = "/" + path
query_params = dict(req.query_params)
AWS Lambda
Lambda presents a dictionary called ‘event’ to the handler and it’s simply a matter of grabbing the right keys:
def lambda_handler(event, context):
server_host = event['headers']['host']
server_port = event['headers']['X-Forwarded-Port']
path = event['path']
query_params = event['queryStringParameters']
If multiValueheaders are enabled, some of the variables come in as lists, which in turn may have a list as values, even if there’s only one item.
server_host = event['multiValueHeaders']['host'][0]
query_params = {}
for _ in event["multiValueQueryStringParameters"].items():
query_params[_[0]] = _[1][0]