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
High-performance Async REST API, in Python. FastAPI + GINO + Arq + Uvicorn (w/ Redis and PostgreSQL).

fastapi-gino-arq-uvicorn High-performance Async REST API, in Python. FastAPI + GINO + Arq + Uvicorn (powered by Redis & PostgreSQL). Contents Get Star

Leo Sussan 351 Jan 04, 2023
Prometheus exporter for several chia node statistics

prometheus-chia-exporter Prometheus exporter for several chia node statistics It's assumed that the full node, the harvester and the wallet run on the

30 Sep 19, 2022
Easily integrate socket.io with your FastAPI app 🚀

fastapi-socketio Easly integrate socket.io with your FastAPI app. Installation Install this plugin using pip: $ pip install fastapi-socketio Usage To

Srdjan Stankovic 210 Dec 23, 2022
Redis-based rate-limiting for FastAPI

Redis-based rate-limiting for FastAPI

Glib 6 Nov 14, 2022
FastAPI framework plugins

Plugins for FastAPI framework, high performance, easy to learn, fast to code, ready for production fastapi-plugins FastAPI framework plugins Cache Mem

RES 239 Dec 28, 2022
FastAPI CRUD template using Deta Base

Deta Base FastAPI CRUD FastAPI CRUD template using Deta Base Setup Install the requirements for the CRUD: pip3 install -r requirements.txt Add your D

Sebastian Ponce 2 Dec 15, 2021
Simple notes app backend using Python's FastAPI framework.

my-notes-app Simple notes app backend using Python's FastAPI framework. Route "/": User login (GET): return 200, list of all of their notes; User sign

José Gabriel Mourão Bezerra 2 Sep 17, 2022
SuperSaaSFastAPI - Python SaaS Boilerplate for building Software-as-Service (SAAS) apps with FastAPI, Vue.js & Tailwind

Python SaaS Boilerplate for building Software-as-Service (SAAS) apps with FastAP

Rudy Bekker 31 Jan 10, 2023
Fastapi-ml-template - Fastapi ml template with python

FastAPI ML Template Run Web API Local $ sh run.sh # poetry run uvicorn app.mai

Yuki Okuda 29 Nov 20, 2022
스타트업 개발자 채용

스타트업 개발자 채용 大 박람회 Seed ~ Series B에 있는 스타트업을 위한 채용정보 페이지입니다. Back-end, Frontend, Mobile 등 개발자를 대상으로 진행하고 있습니다. 해당 스타트업에 종사하시는 분뿐만 아니라 채용 관련 정보를 알고 계시다면

JuHyun Lee 58 Dec 14, 2022
A rate limiter for Starlette and FastAPI

SlowApi A rate limiting library for Starlette and FastAPI adapted from flask-limiter. Note: this is alpha quality code still, the API may change, and

Laurent Savaete 562 Jan 01, 2023
Keycloack plugin for FastApi.

FastAPI Keycloack Keycloack plugin for FastApi. Your aplication receives the claims decoded from the access token. Usage Run keycloak on port 8080 and

Elber 4 Jun 24, 2022
✨️🐍 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
CLI and Streamlit applications to create APIs from Excel data files within seconds, using FastAPI

FastAPI-Wrapper CLI & APIness Streamlit App Arvindra Sehmi, Oxford Economics Ltd. | Website | LinkedIn (Updated: 21 April, 2021) fastapi-wrapper is mo

Arvindra 49 Dec 03, 2022
京东图片点击验证码识别

京东图片验证码识别 本项目是@yqchilde 大佬的 JDMemberCloseAccount 识别图形验证码(#45)思路验证,若你也有思路可以提交Issue和PR也可以在 @yqchilde 的 TG群 找到我 声明 本脚本只是为了学习研究使用 本脚本除了采集处理验证码图片没有其他任何功能,也

AntonVanke 37 Dec 22, 2022
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
MQTT FastAPI Wrapper With Python

mqtt-fastapi-wrapper Quick start Create mosquitto.conf with the following content: ➜ /tmp cat mosquitto.conf persistence false allow_anonymous true

Vitalii Kulanov 3 May 09, 2022
Get MODBUS data from Sofar (K-TLX) inverter through LSW-3 or LSE module

SOFAR Inverter + LSW-3/LSE Small utility to read data from SOFAR K-TLX inverters through the Solarman (LSW-3/LSE) datalogger. Two scripts to get inver

58 Dec 29, 2022
Code for my FastAPI tutorial

FastAPI tutorial Code for my video tutorial FastAPI tutorial What is FastAPI? FastAPI is a high-performant REST API framework for Python. It's built o

José Haro Peralta 9 Nov 15, 2022
API for Submarino store

submarino-api API for the submarino e-commerce documentation read the documentation in: https://submarino-api.herokuapp.com/docs or in https://submari

Miguel 1 Oct 14, 2021