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
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 1, 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
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 3, 2022
Opentracing support for Starlette and FastApi

Starlette-OpenTracing OpenTracing support for Starlette and FastApi. Inspired by: Flask-OpenTracing OpenTracing implementations exist for major distri

Rene Dohmen 63 Dec 30, 2022
Opentracing support for Starlette and FastApi

Starlette-OpenTracing OpenTracing support for Starlette and FastApi. Inspired by: Flask-OpenTracing OpenTracing implementations exist for major distri

Rene Dohmen 26 Feb 11, 2021
Stac-fastapi built on Tile38 and Redis to support caching

stac-fastapi-caching Stac-fastapi built on Tile38 to support caching. This code is built on top of stac-fastapi-elasticsearch 0.1.0 with pyle38, a Pyt

Jonathan Healy 4 Apr 11, 2022
api versioning for fastapi web applications

fastapi-versioning api versioning for fastapi web applications Installation pip install fastapi-versioning Examples from fastapi import FastAPI from f

Dean Way 472 Jan 2, 2023
api versioning for fastapi web applications

fastapi-versioning api versioning for fastapi web applications Installation pip install fastapi-versioning Examples from fastapi import FastAPI from f

Dean Way 127 Feb 17, 2021
Socket.IO integration for Flask applications.

Flask-SocketIO Socket.IO integration for Flask applications. Installation You can install this package as usual with pip: pip install flask-socketio

Miguel Grinberg 4.9k Jan 3, 2023
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 7, 2023
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 7, 2022
Adds GraphQL support to your Flask application.

Flask-GraphQL Adds GraphQL support to your Flask application. Usage Just use the GraphQLView view from flask_graphql from flask import Flask from flas

GraphQL Python 1.3k Dec 31, 2022
A FastAPI Plug-In to support authentication authorization using the Microsoft Authentication Library (MSAL)

FastAPI/MSAL - MSAL (Microsoft Authentication Library) plugin for FastAPI FastAPI - https://github.com/tiangolo/fastapi FastAPI is a modern, fast (hig

Dudi Levy 15 Jul 20, 2022
FastAPI Server Session is a dependency-based extension for FastAPI that adds support for server-sided session management

FastAPI Server-sided Session FastAPI Server Session is a dependency-based extension for FastAPI that adds support for server-sided session management.

DevGuyAhnaf 5 Dec 23, 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
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
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 7, 2022
🍃 A comprehensive monitoring and alerting solution for the status of your Chia farmer and harvesters.

chia-monitor A monitoring tool to collect all important metrics from your Chia farming node and connected harvesters. It can send you push notificatio

Philipp Normann 153 Oct 21, 2022
FastAPI-Amis-Admin is a high-performance, efficient and easily extensible FastAPI admin framework. Inspired by django-admin, and has as many powerful functions as django-admin.

简体中文 | English 项目介绍 FastAPI-Amis-Admin fastapi-amis-admin是一个拥有高性能,高效率,易拓展的fastapi管理后台框架. 启发自Django-Admin,并且拥有不逊色于Django-Admin的强大功能. 源码 · 在线演示 · 文档 · 文

AmisAdmin 318 Dec 31, 2022