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 application and service structure for a more maintainable codebase

Abstracting FastAPI Services See this article for more information: https://camillovisini.com/article/abstracting-fastapi-services/ Poetry poetry inst

Camillo Visini 309 Jan 04, 2023
Ready-to-use and customizable users management for FastAPI

FastAPI Users Ready-to-use and customizable users management for FastAPI Documentation: https://fastapi-users.github.io/fastapi-users/ Source Code: ht

FastAPI Users 2.3k Dec 30, 2022
A simple Redis Streams backed Chat app using Websockets, Asyncio and FastAPI/Starlette.

redis-streams-fastapi-chat A simple demo of Redis Streams backed Chat app using Websockets, Python Asyncio and FastAPI/Starlette. Requires Python vers

ludwig404 135 Dec 19, 2022
Flask-Bcrypt is a Flask extension that provides bcrypt hashing utilities for your application.

Flask-Bcrypt Flask-Bcrypt is a Flask extension that provides bcrypt hashing utilities for your application. Due to the recent increased prevelance of

Max Countryman 310 Dec 14, 2022
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
Toolkit for developing and maintaining ML models

modelkit Python framework for production ML systems. modelkit is a minimalist yet powerful MLOps library for Python, built for people who want to depl

140 Dec 27, 2022
Fetching Cryptocurrency Prices from Coingecko and Displaying them on Grafana

cryptocurrency-prices-grafana Fetching Cryptocurrency Prices from Coingecko and Displaying them on Grafana About This stack consists of: Prometheus (t

Ruan Bekker 7 Aug 01, 2022
A web application using [FastAPI + streamlit + Docker] Neural Style Transfer (NST) refers to a class of software algorithms that manipulate digital images

Neural Style Transfer Web App - [FastAPI + streamlit + Docker] NST - application based on the Perceptual Losses for Real-Time Style Transfer and Super

Roman Spiridonov 3 Dec 05, 2022
Docker image with Uvicorn managed by Gunicorn for high-performance FastAPI web applications in Python 3.6 and above with performance auto-tuning. Optionally with Alpine Linux.

Supported tags and respective Dockerfile links python3.8, latest (Dockerfile) python3.7, (Dockerfile) python3.6 (Dockerfile) python3.8-slim (Dockerfil

Sebastián Ramírez 2.1k Dec 31, 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
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
Prometheus exporter for Starlette and FastAPI

starlette_exporter Prometheus exporter for Starlette and FastAPI. The middleware collects basic metrics: Counter: starlette_requests_total Histogram:

Steve Hillier 225 Jan 05, 2023
Opinionated set of utilities on top of FastAPI

FastAPI Contrib Opinionated set of utilities on top of FastAPI Free software: MIT license Documentation: https://fastapi-contrib.readthedocs.io. Featu

identix.one 543 Jan 05, 2023
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
Instrument your FastAPI app

Prometheus FastAPI Instrumentator A configurable and modular Prometheus Instrumentator for your FastAPI. Install prometheus-fastapi-instrumentator fro

Tim Schwenke 441 Jan 05, 2023
Signalling for FastAPI.

fastapi-signals Signalling for FastAPI.

Henshal B 7 May 04, 2022
Qwerkey is a social media platform for connecting and learning more about mechanical keyboards built on React and Redux in the frontend and Flask in the backend on top of a PostgreSQL database.

Flask React Project This is the backend for the Flask React project. Getting started Clone this repository (only this branch) git clone https://github

Peter Mai 22 Dec 20, 2022
Utils for fastapi based services.

Installation pip install fastapi-serviceutils Usage For more details and usage see: readthedocs Development Getting started After cloning the repo

Simon Kallfass 31 Nov 25, 2022
Light, Flexible and Extensible ASGI API framework

Starlite Starlite is a light and flexible ASGI API framework. Using Starlette and pydantic as foundations. Check out the Starlite documentation 📚 Cor

1.5k Jan 04, 2023
Fastapi performans monitoring

Fastapi-performans-monitoring This project is a simple performance monitoring for FastAPI. License This project is licensed under the terms of the MIT

bilal alpaslan 11 Dec 31, 2022