Skip to content

Instantly share code, notes, and snippets.

@nitaku
Last active April 15, 2024 14:22
Show Gist options
  • Star 96 You must be signed in to star a gist
  • Fork 29 You must be signed in to fork a gist
  • Save nitaku/10d0662536f37a087e1b to your computer and use it in GitHub Desktop.
Save nitaku/10d0662536f37a087e1b to your computer and use it in GitHub Desktop.
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
Copy link

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
Copy link

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
Copy link
Author

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
Copy link

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
Copy link

Zrufy commented Sep 18, 2019

@wanderer06 @nitaku thanks for all!

@immartian
Copy link

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
Copy link

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
Copy link

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

@gitvipin
Copy link

@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
Copy link

@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
Copy link

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

@janzheng
Copy link

janzheng commented Jan 9, 2022

self.wfile.write(json.dumps({'hello': 'world', 'received': 'ok'}).encode('utf-8')) is incorrect — you have to encode the json itself, not the output. So this should actually be:
self.wfile.write(json.dumps({'hello': 'world', 'received': 'ok'}).encode('utf-8'))

@NateFerrero
Copy link

@janzheng your second example is the same code as your first if I'm seeing correctly

@janzheng
Copy link

self.wfile.write(json.dumps({'hello'

somehow I also missed the comment on Mar 24 by Alex. I'm going to excuse myself from this conversation hahah...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment