mafayaz / simpleRestfulHttpServerInPython
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
There are many already existing powerful http servers that can be used in python e.g. gevent, twisted web server. However, they are a bit complex to use and you cannot start them in a thread other than the main thread. |
Here is a sample of basic http server using «BaseHTTPRequestHandler». The example exposed two rest interfaces: |
To ingest records into the web server |
To retrieve already ingested records from the web server |
The records are assumed to be JSON based, however, any type of records can be ingested. |
[sourcecode language=»python» wraplines=»false» collapse=»false»] |
from BaseHTTPServer import BaseHTTPRequestHandler,HTTPServer |
from SocketServer import ThreadingMixIn |
import threading |
import argparse |
import re |
import cgi |
class LocalData(object): |
records = <> |
class HTTPRequestHandler(BaseHTTPRequestHandler): |
def do_POST(self): |
if None != re.search(‘/api/v1/addrecord/*’, self.path): |
ctype, pdict = cgi.parse_header(self.headers.getheader(‘content-type’)) |
if ctype == ‘application/json’: |
length = int(self.headers.getheader(‘content-length’)) |
data = cgi.parse_qs(self.rfile.read(length), keep_blank_values=1) |
recordID = self.path.split(‘/’)[-1] |
LocalData.records[recordID] = data |
print «record %s is added successfully» % recordID |
else: |
data = <> |
self.send_response(200) |
self.end_headers() |
else: |
self.send_response(403) |
self.send_header(‘Content-Type’, ‘application/json’) |
self.end_headers() |
return |
def do_GET(self): |
if None != re.search(‘/api/v1/getrecord/*’, self.path): |
recordID = self.path.split(‘/’)[-1] |
if LocalData.records.has_key(recordID): |
self.send_response(200) |
self.send_header(‘Content-Type’, ‘application/json’) |
self.end_headers() |
self.wfile.write(LocalData.records[recordID]) |
else: |
self.send_response(400, ‘Bad Request: record does not exist’) |
self.send_header(‘Content-Type’, ‘application/json’) |
self.end_headers() |
else: |
self.send_response(403) |
self.send_header(‘Content-Type’, ‘application/json’) |
self.end_headers() |
return |
class ThreadedHTTPServer(ThreadingMixIn, HTTPServer): |
allow_reuse_address = True |
def shutdown(self): |
self.socket.close() |
HTTPServer.shutdown(self) |
class SimpleHttpServer(): |
def __init__(self, ip, port): |
self.server = ThreadedHTTPServer((ip,port), HTTPRequestHandler) |
def start(self): |
self.server_thread = threading.Thread(target=self.server.serve_forever) |
self.server_thread.daemon = True |
self.server_thread.start() |
def waitForThread(self): |
self.server_thread.join() |
def addRecord(self, recordID, jsonEncodedRecord): |
LocalData.records[recordID] = jsonEncodedRecord |
def stop(self): |
self.server.shutdown() |
self.waitForThread() |
if __name__==’__main__’: |
parser = argparse.ArgumentParser(description=’HTTP Server’) |
parser.add_argument(‘port’, type=int, help=’Listening port for HTTP Server’) |
parser.add_argument(‘ip’, help=’HTTP Server IP’) |
args = parser.parse_args() |
server = SimpleHttpServer(args.ip, args.port) |
print ‘HTTP Server Running. ‘ |
server.start() |
server.waitForThread() |
[/sourcecode] |
Usage: |
Copy paste the code above and name it simplewebserver.py |
Starting WebServer: |
python simplewebserver.py |
POST addrecord example using curl: |
curl -X POST http://localhost:8080/api/v1/addrecord/1 -d » -H «Content-Type: application/json» |
GET record example using curl: |
curl -X GET http://localhost:8080/api/v1/getrecord/test -H «Content-Type: application/json» |
nitaku / README.md
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 --data "" --header "Content-Type: application/json" http://localhost:8009
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
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 () |
Brilliant. Used it as the basis for a Multiprocess task broker which uses this example as the basis for its simple REST API.
When i run the server when i send the json i have this error:
httpmessage’ object has no attribute ‘getheader’
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.
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
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
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().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()
Python JSON Server
Python implementation of Node JSON Server (Flask as backend).
About
Make a full REST API without coding!
pyjserver is a python implementation of Node JSON Sever, which creates a full REST api (with GET, POST, PUT and DELETE methods) based on json file.
What’s Next
- PATCH HTTP method.
- Filter data with GET HTTP method.
- Pagination data with GET HTTP method.
Install
Python JSON Server library is on PyPi repository, so it can be installed with pip .
Get stated
After install pyjserver, create db.json file with the endpoints and data.
Run the pyjserver cli command.
>>> python -m pyjserver my-server ./db.json
>>> python3 -m pyjserver my-server ./db.json
And the follow output will be displayed:
* Serving Flask app "my-server" (lazy loading) * Environment: production WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. * Debug mode: off * Running on http://localhost:5000/ (Press CTRL+C to quit)
Now to test the API, if you go to http://localhost:5000/python-libs you will get the same json in db.json.
With the same URL, you can send POST requests to add, PUT requests to edit and DELETE requests to remove from json file.
TIP for each json key, it will be created an endpoint with GET, POST, PUT and DELETE HTTP methods. You can also see all available endpoints in root url (http://localhost:5000/).
Format: JSON object with a list of JSON objects.
Main JSON key: name of the endpoint
JSON list value: list with all records for the endpoint. To start with no data, just place an empty list []
JSONs inside the list: each record for the endpoint. DO NOT FORGET TO ADD SEQUENTIAL ID KEY FOR EACH RECORD
So the JSON above will create 3 endpoints /endpoint-1 (with 2 record data), endpoint-2 (with 1 record data) and endpoint-3 (with no record data). The home page for this json file will display:
For each endpoint created, is allowed GET , POST , PUT and DELETE HTTP method.
>>> curl http://localhost:5000/endpoint-1/ curl -d ' -H -X POST http://localhost:5000/endpoint-1/ curl http://localhost:5000/endpoint-1/ curl -d ' -H -X PUT http://localhost:5000/endpoint-1/2 curl http://localhost:5000/endpoint-1/ curl -X DELETE http://localhost:5000/endpoint-1/2 curl http://localhost:5000/endpoint-1/ REMEMBER: all the data is inside the db.json file, so any change with the HTTP method it will also change the file. Consequently, if change the file, it will change the API.
NOTE: if you edit the json file and add an endpoint with pyjserver running, it will be necessary to restat the application to it load the new endpoint. But it is not necessary if you edit the file and add new records.