Tiny lightweight, flexible HTTP framework.
Project description
PyMicroHTTP
PyMicroHTTP is a lightweight, flexible HTTP framework built from scratch in Python. It provides a simple way to create HTTP services without heavy external dependencies, making it ideal for learning purposes or small projects.
NOTE: this is a toy project and not production ready.
Content Table
PyMicroHTTP
- PyMicroHTTP
- What is PyMicroHTTP? (Introduction)
- Features (List of functionalities)
- Installation (How to install)
- Quick Start (Getting started example)
- Running the example
- Routing (Defining routes)
- Basic Routing Syntax
- Path Parameters (Accessing dynamic parts of the URL)
- Query Parameters (Accessing data after the '?' in the URL)
- Request Object (Understanding the request data)
- Available properties in the request object
- Accessing headers, path parameters, query parameters, body and verb
- Response Handling (Sending data back to the client)
- Returning dictionaries (converted to JSON)
- Returning strings
- Returning custom status codes and headers
- Middleware (Adding custom logic before request handling)
- Creating middleware functions
- Before all (Running middleware before every request)
- Middleware chaining
- Running the Server (Starting the server)
- Contributing (How to contribute to the project)
Features
- Built on raw TCP sockets
- Routing with HTTP verb and path matching
- Middleware support with easy chaining
- JSON response handling
- Zero external dependencies
Installation
You can install the package via pip:
$ pip install pymicrohttp
Quick Start
Here's a simple example to get you started:
from pymicrohttp.server import Server
s = Server()
@s.register('GET /hello')
def hello(request):
return {"message": "Hello, World!"}
@s.register('GET /hello/:name')
def hello_name(request):
name = request['params'].get('name')
return {"message": f"Hello, {name}!"}
if __name__ == "__main__":
s.start_server(port=8080)
Run this script, and you'll have a server running on http://localhost:8080. Access it with:
curl http://localhost:8080/hello
Routing
Routes are defined using the @s.register decorator:
@s.register('GET /ping')
def ping_handler(request):
return "pong"
Following this syntax:
VERB /<PATH>
Supported verbs:
*: to match any verb- GET
- POST
- PUT
- PATCH
- DELETE
- HEAD
- OPTIONS
With a signle space separating between the verb and the request path.
Example:
@s.register('POST /login')
def login_handler(request):
try:
body = json.loads(request['body'])
if 'username' not in body or 'password' not in body:
# do somthing
except:
return { 'error': 'invalid data' }
Path parameters
You can declare dynamic path params using a colon, for example:
GET /users/:group/:channel
To read these params you can access them via the request object:
@s.register('GET /users/:group/:channel')
def handler(request):
...
group = request['params']['group']
channel = request['params']['channel']
...
Query parameters
You can read query parameters via the request obejct:
@s.register('GET /products')
def handler(request):
...
name = request['query'].get('name', '')
category = request['query'].get('category', 'shoes')
...
Note that it is better to use .get(key, default_value) because query params are optional and may not exist, and accessing them without the .get() method may result in key errors.
Request Object
The request object is a dict containing these key and value:
{
'verb': ...
'path': ...
'body': ...
'headers': ... # { 'key': 'value' }
'params': ... # { 'key': 'value' }
'query': ... # { 'key': 'value' }
}
You can access it via the handler:
@s.register('* /ping')
def ping_handler(request):
# accessing request headers
if 'double' in request['headers']:
return "pong-pong"
return "pong"
Examples:
-
Accessing headers:
# say hello s.register('GET /hello/:name') def hello(request): name = request['params']['name'] return "Hello " + name
-
Accessing dynamic path params:
# say hello `n` times s.register('GET /hello/:name/:n') def hello(request): name, n = request['params']['name'], request['params']['n'] return "Hello " * int(n) + name
-
Accessing query params:
# say hello `n` times # read n from query params # with default value of 3 s.register('GET /hello/:name') def hello(request): name = request['params']['name'] n = request['query'].get('n', 3) return "Hello " * n + name
Response Handling
The framework supports different types of responses:
-
Dictionary (automatically converted to JSON):
return {"key": "value"}
-
String:
return "Hello, World!"
-
Tuple for custom status codes and headers:
return "Not Found", 404 # or return "Created", 201, {"Location": "/resource/1"}
Middleware
Middleware functions can be used to add functionality to your routes:
def log_middleware(next):
def handler(request):
print(f"Request: {request['verb']} {request['path']}")
return next(request)
return handler
@s.register('GET /logged', log_middleware)
def logged_route(request):
return {"message": "This is a logged route"}
Before all
If you want to run a middleware before every single request you can use the s.beforeAll() decorator:
@s.beforeAll()
def logger(next):
def handler(request):
verb, path = request['verb'], request['path']
print(f'{datetime.datetime.now()} {verb} {path}')
return next(request)
return handler
Middleware chaining
You can chain multiple middlwares together
def log_middleware(next):
def handler(request):
# do your logging logic here
return next(request)
return handler
def auth_middleware(next):
def handler(request):
# do your auth logic here
return next(request)
return handler
@s.register('GET /protected', [log_middleware, auth_middleware])
def protected_route(request):
return {"message": "This is a protected route"}
Running the Server
To run the server:
if __name__ == "__main__":
s = Server()
# Register your routes here
s.start_server(port=8080)
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Hashes for pymicrohttp-0.1.3-py3-none-any.whl
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 | 98098ab954cd901f86a044fd9f266e869ac67fa13e563a1feced1ea4c8f271ae |
|
| MD5 | 1043b88b46222752ab759f2e7b425eb1 |
|
| BLAKE2b-256 | a40ecd533107aa518f0869e63b66d16f66e3f43dc5d490d7a249271b154c9c5e |