Drop-in MessagePack support for ASGI applications and frameworks

Overview

msgpack-asgi

Build Status Coverage Package version

msgpack-asgi allows you to add automatic MessagePack content negotiation to ASGI applications (Starlette, FastAPI, Quart, etc.), with a single line of code:

app.add_middleware(MessagePackMiddleware)

(You may want to adapt this snippet to your framework-specific middleware API.)

This gives you the bandwitdth usage reduction benefits of MessagePack without having to change existing code.

Note: this comes at a CPU usage cost, since MessagePackMiddleware will perform MsgPack decoding while your application continues to decode and encode JSON data (see also How it works). If your use case is CPU-sensitive, rather than strictly focused on reducing network bandwidth, this package may not be for you.

Installation

Install with pip:

pip install "msgpack-asgi==1.*"

Quickstart

First, you'll need an ASGI application. Let's use this sample application, which exposes an endpoint that returns JSON data:

# For convenience, we use some ASGI components from Starlette.
# Install with: `$ pip install starlette`.
from starlette.requests import Request
from starlette.responses import JSONResponse


async def get_response(request):
    if request.method == "POST":
        data = await request.json()
        return JSONResponse({"data": data})
    else:
        return JSONResponse({"message": "Hello, msgpack!"})


async def app(scope, receive, send):
    assert scope["type"] == "http"
    request = Request(scope=scope, receive=receive)
    response = await get_response(request)
    await response(scope, receive, send)

Then, wrap your application around MessagePackMiddleware:

from msgpack_asgi import MessagePackMiddleware

app = MessagePackMiddleware(app)

Serve your application using an ASGI server, for example with Uvicorn:

uvicorn app:app

Now, let's make a request that accepts MessagePack data in response:

curl -i http://localhost:8000 -H "Accept: application/x-msgpack"

You should get the following output:

HTTP/1.1 200 OK
date: Fri, 01 Nov 2019 17:40:14 GMT
server: uvicorn
content-length: 25
content-type: application/x-msgpack

��message�Hello, msgpack!

What happened? Since we told the application that we accepted MessagePack-encoded responses, msgpack-asgi automatically converted the JSON data returned by the Starlette application to MessagePack.

We can make sure the response contains valid MessagePack data by making the request again in Python, and decoding the response content:

>>> import requests
>>> import msgpack
>>> url = "http://localhost:8000"
>>> headers = {"accept": "application/x-msgpack"}
>>> r = requests.get(url, headers=headers)
>>> r.content
b'\x81\xa7message\xafHello, msgpack!'
>>> msgpack.unpackb(r.content, raw=False)
{'message': 'Hello, msgpack!'}

msgpack-asgi also works in reverse: it will automatically decode MessagePack-encoded data sent by the client to JSON. We can try this out by making a POST request to our sample application with a MessagePack-encoded body:

>>> import requests
>>> import msgpack
>>> url = "http://localhost:8000"
>>> data = msgpack.packb({"message": "Hi, there!"})
>>> headers = {"content-type": "application/x-msgpack"}
>>> r = requests.post(url, data=data, headers=headers)
>>> r.json()
{'data': {'message': 'Hi, there!'}}

That's all there is to it! You can now go reduce the size of your payloads.

Limitations

msgpack-asgi does not support request or response streaming. This is because the full request and response body content has to be loaded in memory before it can be re-encoded.

How it works

An ASGI application wrapped around MessagePackMiddleware will perform automatic content negotiation based on the client's capabilities. More precisely:

  • If the client sends MessagePack-encoded data with the application/x-msgpack content type, msgpack-asgi will automatically re-encode it to JSON for your application to consume.
  • If the client sent the Accept: application/x-msgpack header, msgpack-asgi will automatically re-encode any JSON response data to MessagePack for the client to consume.

(In other cases, msgpack-asgi won't intervene at all.)

License

MIT

Comments
  • Supporting alternative msgpack implementations

    Supporting alternative msgpack implementations

    ormsgpack is a faster alternative to python-msgpack and since speed is critical/required in Web/ASGI application I think it would be nice to have support for using ormsgpack!

    My current idea is to provide ormsgpack as an extra/optional dependency to msgpack-asgi (installed like pip install msgpack-asgi[ormsgpack]?) and provide two more classes to the public API ORMessagePackMiddleware and ORMessagePackResponse if ormsgpack can be imported.

    I will happily create a PR and start working on this feature if this is something can be added to the project! :smile:

    enhancement 
    opened by FaresAhmedb 11
  • Re-write request Content-Type when decoding

    Re-write request Content-Type when decoding

    Refs #23

    This pull request makes it so that applications see Content-Type: application/json (instead of Content-Type: application/x-msgpack) in requests.

    The motivation is to make content and Content-Type consistent from the point of view of the application, which may do additional consistency checks, eg for security purposes. FastAPI 0.65.2+ has such a check that prevents a CSRF vulnerability when a client sends JSON data with text/plain, which is exempted from CSRF checks.

    Still pondering, but I think this might be an acceptable, perhaps necessary option. The idea behind msgpack-asgi is to serve as a "msgpack-to/from-JSON gateway" afterall.

    cc @einfachTobi — I'd be happy to hear what you think about this. :-)

    opened by florimondmanca 2
  • Drop unadvertised MessagePackResponse component

    Drop unadvertised MessagePackResponse component

    This PR drops the undocumented MessagePackResponse class. This response is also somewhat outside the scope of this package which focuses on the "automatic content negotiation" aspect. It's a source of feature creep that we'd probably deny anyway, for the sake of maintenance burden.

    Search in publicly indexed code does not show any public uses: https://grep.app/search?q=MessagePackResponse&filter[lang][0]=Python

    Could be released in a 1.1.0 version (minor bump) with a copy-paste of the MessagePackResponse code, to help any unseen users with migration.

    opened by florimondmanca 2
  • Release 1.1.0

    Release 1.1.0

    1.1.0 - 2021-10-26

    Added

    • Support custom encoding/decoding implementation via the packb=... and unpackb=... optional parameters, allowing the use of alternative msgpack libraries. (Pull #20 - Thanks @FaresAhmedb!)

    Fixed

    • Properly re-write request Content-Type to application/json. (Pull #24)
    opened by florimondmanca 1
  • Always getting 442 Unprocessable entity

    Always getting 442 Unprocessable entity

    I cannot get msgpack-asgi running with FastAPI. Any request with msgpack-bytes is returned with a "422 Unprocessable Entity" error. The following minimal example will show the problem:

    from pathlib import Path
    import uvicorn
    from fastapi import FastAPI
    from pydantic import BaseModel
    from msgpack_asgi import MessagePackMiddleware
    
    
    class Foo(BaseModel):
        bar: int
    
    
    app = FastAPI()
    app.add_middleware(MessagePackMiddleware)
    
    
    @app.post("/")
    def index(thing: Foo):
        return thing
    
    
    if __name__ == "__main__":
        uvicorn.run(f"{Path(__file__).stem}:app", host="0.0.0.0", port=5001, log_level="debug", reload=True)
    

    The data is sent with the following snippet:

    import requests
    import msgpack
    
    url = "http://127.0.0.1:5001/"
    headers = {"content-type": "application/x-msgpack"}
    data_raw = {"bar": 23}
    data_packed = msgpack.packb(data_raw)
    
    response_json = requests.post(url, json=data_raw)
    response_msgpack = requests.post(url, data=data_packed, headers=headers)
    

    Resulting in:

    INFO:     127.0.0.1:54682 - "POST / HTTP/1.1" 200 OK
    INFO:     127.0.0.1:54684 - "POST / HTTP/1.1" 422 Unprocessable Entity
    

    So the data is accepted as json but refused as msgpack-bytes. May there be an incompatibility with newser versions of FastAPI or pydantic? Or am I just using this completely wrong?

    question 
    opened by einfachTobi 1
  • HTTPS support

    HTTPS support

    1. Why it does not support HTTPS?
    2. How to make it working with HTTPS?
    class MessagePackMiddleware:
        def __init__(self, app: ASGIApp) -> None:
            self.app = app
    
        async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
            if scope["type"] == "http":
                responder = _MessagePackResponder(self.app)
                await responder(scope, receive, send)
                return
            await self.app(scope, receive, send)
    
    question 
    opened by AIGeneratedUsername 1
  • Release 1.0.0

    Release 1.0.0

    1.0.0 - 2020-26-03

    First production/stable release.

    Changed

    • Switch to private module naming. Components should now be imported from the root package, e.g. from msgpack_asgi import MessagePackMiddleware. (Pull #5)

    Fixed

    • Add missing MANIFEST.in. (Pull #4)
    opened by florimondmanca 1
  • Document custom serialization support

    Document custom serialization support

    Closes #21

    This PR wraps up #20 by documenting the new packb/unpackb callables options, allowing users to override the default msgpack implementation.

    Should be released in a minor version bump, currently 1.1.0.

    cc @FaresAhmedb

    documentation 
    opened by florimondmanca 0
  • Custom serialization support should be documented

    Custom serialization support should be documented

    #20 was merged, but it needs a docs update before releasing. Opening this issue to track this.

    We can mention a few possible alternatives, and provide a customization example for each, such as:

    • ormsgpack - https://github.com/aviramha/ormsgpack
    • msgspec - https://jcristharif.com/msgspec
    documentation 
    opened by florimondmanca 0
  • Implementation flaw of the middleware prevents concurrent requests

    Implementation flaw of the middleware prevents concurrent requests

    I think the current vesion of the msgpack middleware has a serious implementaion flaw that will cause errors when parallel requests are processed.

    During each request, some request scoped variables, like receive, send, should_decode_from_msgpack_to_json, initial_message etc, are stored on the middleware instance itself:

            self.should_decode_from_msgpack_to_json = (
                "application/x-msgpack" in headers.get("content-type", "")
            )
            # Take an initial guess, although we eventually may not
            # be able to do the conversion.
            self.should_encode_from_json_to_msgpack = (
                "application/x-msgpack" in headers.getlist("accept")
            )
            self.receive = receive
            self.send = send
    

    The problem is that there is only one instance of the middleware, but multiple parallel requests are normally in progress, so these variables will get mixed up between the requests. When for example receive_with_msgpack is called to process a request, self.receive could already have been overwritten by a subsequent request.

    The proper way to pass request scoped values between the various instance methods would be to use request or function scoped storage, like scope, wrapped function or partial function.

    bug 
    opened by hongyuan1306 2
  • Support for Content-Type: application/msgpack header

    Support for Content-Type: application/msgpack header

    As evidenced by https://github.com/msgpack/msgpack/issues/194, there still is no clear answer on the "proper" MIME type for msgpack. Fluent-bit's HTTP output plugin uses application/msgpack for the content-type instead of aapplication/x-msgpack, so the msgpack-asgi middleware doesn't attempt to unpack requests from fluent-bit POST's

    enhancement good first issue 
    opened by astephon88 3
  • Support for large requests (more_body=True)

    Support for large requests (more_body=True)

    This library seemed to hit the spot for drop-in support for msgpack with FastAPI. I am using the following to enable the msgpack interface: app.add_middleware(MessagePackMiddleware)

    Unfortunately, large client requests are failing. Running uvicorn with --log-level trace I see that the request is being chunked:

    TRACE:    127.0.0.1:42088 - Connection made
    TRACE:    10.60.1.118:0 - ASGI [4] Started scope={'type': 'http', 'asgi': {'version': '3.0', 'spec_version': '2.1'}, 'http_version': '1.0', 'server': ('127.0.0.1', 8901), 'client': ('10.60.1.118', 0), 'scheme': 'https', 'method': 'POST', 'root_path': '/app/<redacted>', 'path': '<redacted> 'raw_path': b'/<redacted>', 'query_string': b'', 'headers': '<...>'}
    TRACE:    10.60.1.118:0 - ASGI [4] Receive {'type': 'http.request', 'body': '<65150 bytes>', 'more_body': True}
    TRACE:    10.60.1.118:0 - ASGI [4] Receive {'type': 'http.request', 'body': '<65482 bytes>', 'more_body': True}
    TRACE:    10.60.1.118:0 - ASGI [4] Send {'type': 'http.response.start', 'status': 400, 'headers': '<...>'}
    INFO:     10.60.1.118:0 - "POST<redacted> HTTP/1.0" 400 Bad Request
    TRACE:    10.60.1.118:0 - ASGI [4] Send {'type': 'http.response.body', 'body': '<45 bytes>'}
    TRACE:    10.60.1.118:0 - ASGI [4] Completed
    TRACE:    127.0.0.1:42088 - Connection lost
    

    Requests under 64k work fine. JSON requests of any size are also fine. The request is being sent from the client as a regular POST.

    I assume this is related the comments about more_body not being implemented in the source code.

    I am still getting up to speed with ASGI. Is this something that should be fixed with this middleware, or should I look at figuring out how to increase the buffer size elsewhere?

    enhancement 
    opened by perlman 4
Releases(1.1.0)
  • 1.1.0(Oct 26, 2021)

    1.1.0 - 2021-10-26

    Added

    • Support custom encoding/decoding implementation via the packb=... and unpackb=... optional parameters, allowing the use of alternative msgpack libraries. (Pull #20)

    Fixed

    • Properly re-write request Content-Type to application/json. (Pull #24)
    Source code(tar.gz)
    Source code(zip)
Owner
Florimond Manca
Pythonista, open source developer, casual tech blogger. Idealist on a journey, and it’s good fun!
Florimond Manca
FastAPI Skeleton App to serve machine learning models production-ready.

FastAPI Model Server Skeleton Serving machine learning models production-ready, fast, easy and secure powered by the great FastAPI by Sebastián Ramíre

268 Jan 01, 2023
Simple example of FastAPI + Celery + Triton for benchmarking

You can see the previous work from: https://github.com/Curt-Park/producer-consumer-fastapi-celery https://github.com/Curt-Park/triton-inference-server

Jinwoo Park (Curt) 37 Dec 29, 2022
API written using Fast API to manage events and implement a leaderboard / badge system.

Open Food Facts Events API written using Fast API to manage events and implement a leaderboard / badge system. Installation To run the API locally, ru

Open Food Facts 5 Jan 07, 2023
Full stack, modern web application generator. Using FastAPI, PostgreSQL as database, Docker, automatic HTTPS and more.

Full Stack FastAPI and PostgreSQL - Base Project Generator Generate a backend and frontend stack using Python, including interactive API documentation

Sebastián Ramírez 10.8k Jan 08, 2023
✨️🐍 SPARQL endpoint built with RDFLib to serve machine learning models, or any other logic implemented in Python

✨ SPARQL endpoint for RDFLib rdflib-endpoint is a SPARQL endpoint based on a RDFLib Graph to easily serve machine learning models, or any other logic

Vincent Emonet 27 Dec 19, 2022
FastAPI Socket.io with first-class documentation using AsyncAPI

fastapi-sio Socket.io FastAPI integration library with first-class documentation using AsyncAPI The usage of the library is very familiar to the exper

Marián Hlaváč 9 Jan 02, 2023
A request rate limiter for fastapi

fastapi-limiter Introduction FastAPI-Limiter is a rate limiting tool for fastapi routes. Requirements redis Install Just install from pypi pip insta

long2ice 200 Jan 08, 2023
🐍Pywork is a Yeoman generator to scaffold a Bare-bone Python Application

Pywork python app yeoman generator Yeoman | Npm Pywork | Home PyWork is a Yeoman generator for a basic python-worker project that makes use of Pipenv,

Vu Tran 10 Dec 16, 2022
Generate Class & Decorators for your FastAPI project ✨🚀

Classes and Decorators to use FastAPI with class based routing. In particular this allows you to construct an instance of a class and have methods of that instance be route handlers for FastAPI & Pyt

Yasser Tahiri 34 Oct 27, 2022
Sample project showing reliable data ingestion application using FastAPI and dramatiq

Create and deploy a reliable data ingestion service with FastAPI, SQLModel and Dramatiq This is the source code for the data ingestion service explain

François Voron 31 Nov 30, 2022
A simple example of deploying FastAPI as a Zeit Serverless Function

FastAPI Zeit Now Deploy a FastAPI app as a Zeit Serverless Function. This repo deploys the FastAPI SQL Databases Tutorial to demonstrate how a FastAPI

Paul Weidner 26 Dec 21, 2022
Feature rich robust FastAPI template.

Flexible and Lightweight general-purpose template for FastAPI. Usage ⚠️ Git, Python and Poetry must be installed and accessible ⚠️ Poetry version must

Pavel Kirilin 588 Jan 04, 2023
Prometheus integration for Starlette.

Starlette Prometheus Introduction Prometheus integration for Starlette. Requirements Python 3.6+ Starlette 0.9+ Installation $ pip install starlette-p

José Antonio Perdiguero 229 Dec 21, 2022
A RESTful API for creating and monitoring resource components of a hypothetical build system. Built with FastAPI and pydantic. Complete with testing and CI.

diskspace-monitor-CRUD Background The build system is part of a large environment with a multitude of different components. Many of the components hav

Nick Hopewell 67 Dec 14, 2022
REST API with FastAPI and PostgreSQL

REST API with FastAPI and PostgreSQL To have the same data in db: create table CLIENT_DATA (id SERIAL PRIMARY KEY, fullname VARCHAR(50) NOT NULL,email

Luis Quiñones Requelme 1 Nov 11, 2021
A server hosts a FastAPI application and multiple clients can be connected to it via SocketIO.

FastAPI_and_SocketIO A server hosts a FastAPI application and multiple clients can be connected to it via SocketIO. Executing server.py sets up the se

Ankit Rana 2 Mar 04, 2022
This is an API developed in python with the FastApi framework and putting into practice the recommendations of the book Clean Architecture in Python by Leonardo Giordani,

This is an API developed in python with the FastApi framework and putting into practice the recommendations of the book Clean Architecture in Python by Leonardo Giordani,

0 Sep 24, 2022
Adds simple SQLAlchemy support to FastAPI

FastAPI-SQLAlchemy FastAPI-SQLAlchemy provides a simple integration between FastAPI and SQLAlchemy in your application. It gives access to useful help

Michael Freeborn 465 Jan 07, 2023
API using python and Fastapi framework

Welcome 👋 CFCApi is a API DEVELOPMENT PROJECT UNDER CODE FOR COMMUNITY ! Project Walkthrough 🚀 CFCApi run on Python using FASTapi Framework Docs The

Abhishek kushwaha 7 Jan 02, 2023
Restful Api developed with Flask using Prometheus and Grafana for monitoring and containerization with Docker :rocket:

Hephaestus 🚀 In Greek mythology, Hephaestus was either the son of Zeus and Hera or he was Hera's parthenogenous child. ... As a smithing god, Hephaes

Yasser Tahiri 16 Oct 07, 2022