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
A FastAPI WebSocket application that makes use of ncellapp package by @hemantapkh

ncellFastAPI author: @awebisam Used FastAPI to create WS application. Ncellapp module by @hemantapkh NOTE: Not following best practices and, needs ref

Aashish Bhandari 7 Oct 01, 2021
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
Mixer -- Is a fixtures replacement. Supported Django, Flask, SqlAlchemy and custom python objects.

The Mixer is a helper to generate instances of Django or SQLAlchemy models. It's useful for testing and fixture replacement. Fast and convenient test-

Kirill Klenov 871 Dec 25, 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
cookiecutter template for web API with python

Python project template for Web API with cookiecutter What's this This provides the project template including minimum test/lint/typechecking package

Hitoshi Manabe 4 Jan 28, 2021
Complete Fundamental to Expert Codes of FastAPI for creating API's

FastAPI FastAPI is a modern, fast (high-performance), web framework for building APIs with Python 3 based on standard Python type hints. The key featu

Pranav Anand 1 Nov 28, 2021
Practice-python is a simple Fast api project for dealing with modern rest api technologies.

Practice Python Practice-python is a simple Fast api project for dealing with modern rest api technologies. Deployment with docker Go to the project r

0 Sep 19, 2022
Github timeline htmx based web app rewritten from Common Lisp to Python FastAPI

python-fastapi-github-timeline Rewrite of Common Lisp htmx app _cl-github-timeline into Python using FastAPI. This project tries to prove, that with h

Jan Vlčinský 4 Mar 25, 2022
A minimal FastAPI implementation for Django !

Caution!!! This project is in early developing stage. So use it at you own risk. Bug reports / Fix PRs are welcomed. Installation pip install django-m

toki 23 Dec 24, 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
Single Page App with Flask and Vue.js

Developing a Single Page App with FastAPI and Vue.js Want to learn how to build this? Check out the post. Want to use this project? Build the images a

91 Jan 05, 2023
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
Code Specialist 27 Oct 16, 2022
Opinionated authorization package for FastAPI

FastAPI Authorization Installation pip install fastapi-authorization Usage Currently, there are two models available: RBAC: Role-based Access Control

Marcelo Trylesinski 18 Jul 04, 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
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
An alternative implement of Imjad API | Imjad API 的开源替代

HibiAPI An alternative implement of Imjad API. Imjad API 的开源替代. 前言 由于Imjad API这是什么?使用人数过多, 致使调用超出限制, 所以本人希望提供一个开源替代来供社区进行自由的部署和使用, 从而减轻一部分该API的使用压力 优势

Mix Technology 450 Dec 29, 2022
Flask-vs-FastAPI - Understanding Flask vs FastAPI Web Framework. A comparison of two different RestAPI frameworks.

Flask-vs-FastAPI Understanding Flask vs FastAPI Web Framework. A comparison of two different RestAPI frameworks. IntroductionIn Flask is a popular mic

Mithlesh Navlakhe 1 Jan 01, 2022
Pagination support for flask

flask-paginate Pagination support for flask framework (study from will_paginate). It supports several css frameworks. It requires Python2.6+ as string

Lix Xu 264 Nov 07, 2022
Hyperlinks for pydantic models

Hyperlinks for pydantic models In a typical web application relationships between resources are modeled by primary and foreign keys in a database (int

Jaakko Moisio 10 Apr 18, 2022