Skip to content

Instantly share code, notes, and snippets.

@nitaku

nitaku/README.md

Last active Jul 18, 2021
Embed
What would you like to do?
Minimal JSON HTTP server in python

A minimal HTTP server in python. It sends a JSON Hello World for GET requests, and echoes back JSON for POST requests.

python server.py 8009
Starting httpd on port 8009...
curl http://localhost:8009
{"received": "ok", "hello": "world"}
curl --data "{\"this\":\"is a test\"}" --header "Content-Type: application/json" http://localhost:8009
{"this": "is a test", "received": "ok"}

Adapted from this Gist, with the addition of code for reading the request body taken from this article.

Please be careful when using a server like this on production environments, because it lacks many important features (threading to name one). You can consult the python documentation about BaseHTTPServer to learn something useful to improve it.

If you are on Ubuntu, you can install this code as a service with an init script (hopefully, with some modifications that make it actually do something useful). Just modify the include server.conf to suit your needs (possibly renaming it and redirecting output to some log files instead of /dev/null), and copy it into /etc/init/. You can then start/stop/restart the server with the usual service command:

sudo service server start
description "Example JSON HTTP server"
author "nitaku - matteo.abrate@gmail.com"
start on started mountall
stop on shutdown
respawn
respawn limit 99 5
script
exec sudo -u www-data /usr/bin/python /data/examples/python_minimal_http/server.py 8009 >> /dev/null 2>> /dev/null
end script
post-start script
end script
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
import SocketServer
import json
import cgi
class Server(BaseHTTPRequestHandler):
def _set_headers(self):
self.send_response(200)
self.send_header('Content-type', 'application/json')
self.end_headers()
def do_HEAD(self):
self._set_headers()
# GET sends back a Hello world message
def do_GET(self):
self._set_headers()
self.wfile.write(json.dumps({'hello': 'world', 'received': 'ok'}))
# POST echoes the message adding a JSON field
def do_POST(self):
ctype, pdict = cgi.parse_header(self.headers.getheader('content-type'))
# refuse to receive non-json content
if ctype != 'application/json':
self.send_response(400)
self.end_headers()
return
# read the message and convert it into a python dictionary
length = int(self.headers.getheader('content-length'))
message = json.loads(self.rfile.read(length))
# add a property to the object, just to mess with data
message['received'] = 'ok'
# send the message back
self._set_headers()
self.wfile.write(json.dumps(message))
def run(server_class=HTTPServer, handler_class=Server, port=8008):
server_address = ('', port)
httpd = server_class(server_address, handler_class)
print 'Starting httpd on port %d...' % port
httpd.serve_forever()
if __name__ == "__main__":
from sys import argv
if len(argv) == 2:
run(port=int(argv[1]))
else:
run()
@fahadysf

This comment has been minimized.

Copy link

@fahadysf fahadysf commented May 26, 2016

Brilliant. Used it as the basis for a Multiprocess task broker which uses this example as the basis for its simple REST API.

https://gist.github.com/fahadysf/d80b99685ea3cfe3de4631f60e0136cc

@Zrufy

This comment has been minimized.

Copy link

@Zrufy Zrufy commented Sep 17, 2019

When i run the server when i send the json i have this error:

httpmessage' object has no attribute 'getheader'

any help?

@nitaku

This comment has been minimized.

Copy link
Owner Author

@nitaku nitaku commented Sep 17, 2019

Sorry, I wrote this gist many years ago and I have no clue... Should self.headers be an httpmessage object though? Did you modify the code?

Or... maybe the libraries I used have been modified during these years, I don't know...

@wanderer06

This comment has been minimized.

Copy link

@wanderer06 wanderer06 commented Sep 18, 2019

Thanks for writing this @nitaku it really helped me out.
If anyone is interested, self.header now has a get intead of getheader

length = int(self.headers.get('content-length'))
payload_string = self.rfile.read(length).decode('utf-8')
payload = json.loads(payload_string) if payload_string else None
@Zrufy

This comment has been minimized.

Copy link

@Zrufy Zrufy commented Sep 18, 2019

@wanderer06 @nitaku thanks for all!

@immartian

This comment has been minimized.

Copy link

@immartian immartian commented Jul 13, 2020

not working on Python 3 due to the following errors:

$ sudo pip3 install BaseHTTPServer
ERROR: Could not find a version that satisfies the requirement BaseHTTPServer (from versions: none)
ERROR: No matching distribution found for BaseHTTPServer
@mfickett

This comment has been minimized.

Copy link

@mfickett mfickett commented Jul 14, 2020

Thanks @nitaku for the example. I expanded it a little to provide Access-Control-Allow-Origin headers so that local development with XHRs from a browser don't get CORS errors, as well as updating for python3.

#!/usr/bin/env python3
"""An example HTTP server with GET and POST endpoints."""

from http.server import HTTPServer, BaseHTTPRequestHandler
from http import HTTPStatus
import json
import time


# Sample blog post data similar to
# https://ordina-jworks.github.io/frontend/2019/03/04/vue-with-typescript.html#4-how-to-write-your-first-component
_g_posts = [
    {
        'title': 'My first blogpost ever!',
        'body': 'Lorem ipsum dolor sit amet.',
        'author': 'Elke',
        'date_ms': 1593607500000,  # 2020 July 1 8:45 AM Eastern
    },
    {
        'title': 'Look I am blogging!',
        'body': 'Hurray for me, this is my second post!',
        'author': 'Elke',
        'date_ms': 1593870300000, # 2020 July 4 9:45 AM Eastern
    },
    {
        'title': 'Another one?!',
        'body': 'Another one!',
        'author': 'Elke',
        'date_ms': 1594419000000, # 2020 July 10 18:10 Eastern
    }
]


class _RequestHandler(BaseHTTPRequestHandler):
    # Borrowing from https://gist.github.com/nitaku/10d0662536f37a087e1b
    def _set_headers(self):
        self.send_response(HTTPStatus.OK.value)
        self.send_header('Content-type', 'application/json')
        # Allow requests from any origin, so CORS policies don't
        # prevent local development.
        self.send_header('Access-Control-Allow-Origin', '*')
        self.end_headers()

    def do_GET(self):
        self._set_headers()
        self.wfile.write(json.dumps(_g_posts).encode('utf-8'))

    def do_POST(self):
        length = int(self.headers.get('content-length'))
        message = json.loads(self.rfile.read(length))
        message['date_ms'] = int(time.time()) * 1000
        _g_posts.append(message)
        self._set_headers()
        self.wfile.write(json.dumps({'success': True}).encode('utf-8'))

    def do_OPTIONS(self):
        # Send allow-origin header for preflight POST XHRs.
        self.send_response(HTTPStatus.NO_CONTENT.value)
        self.send_header('Access-Control-Allow-Origin', '*')
        self.send_header('Access-Control-Allow-Methods', 'GET, POST')
        self.send_header('Access-Control-Allow-Headers', 'content-type')
        self.end_headers()


def run_server():
    server_address = ('', 8001)
    httpd = HTTPServer(server_address, _RequestHandler)
    print('serving at %s:%d' % server_address)
    httpd.serve_forever()


if __name__ == '__main__':
    run_server()
@saiprasad2210

This comment has been minimized.

Copy link

@saiprasad2210 saiprasad2210 commented Jul 29, 2020

How to expand this to say a custom get request.
curl http://localhost:8009/get_all_configs

@gitvipin

This comment has been minimized.

Copy link

@gitvipin gitvipin commented Dec 31, 2020

@mfickett : We also need to convert response to bytes as following. Without it we get error , TypeError: a bytes-like object is required, not 'str'

    def do_GET(self):
        self._set_headers()
        response = json.dumps({'hello': 'world', 'received': 'ok'})
        response = bytes(response, 'utf-8')
        self.wfile.write(response)
@gitvipin

This comment has been minimized.

Copy link

@gitvipin gitvipin commented Dec 31, 2020

@saiprasad2210 : You get access to path as self.path inside handler. You can process it as you like. An example is here : https://github.com/gitvipin/sql30/blob/master/sql30/api.py#L45

@alexzanderr

This comment has been minimized.

Copy link

@alexzanderr alexzanderr commented Mar 24, 2021

in do_GET function:
this line is missing encoding to bytes object

self.wfile.write(json.dumps({'hello': 'world', 'received': 'ok'}))

should be:

self.wfile.write(json.dumps({'hello': 'world', 'received': 'ok'}).encode('utf-8'))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment