spec-first/connexion

GZipMiddleware cannot be used with response validation

sgranel-fsk opened this issue · 2 comments

Description

It is not possible to activate response validation (validate_responses parameter of the Flask app), and use the GZipMiddleware suggested here https://connexion.readthedocs.io/en/latest/middleware.html#list-of-useful-middleware.
As it is not possible to insert a middleware after response validation (or I didn't find it), the response validation tries to decode binary data as utf-8. Content-encoding header does not seems to be considered.

Expected behaviour

Be able to activate both GZipMiddleware and response validation.

Actual behaviour

Internal server error is raised:

[27/Mar/2024:16:31:43 +0100] - PFSK00081 - forsk.explorer - connexion.middleware.exceptions -  ERROR   - n/a -- UnicodeDecodeError('utf-8', b'\x1f\x8b\x08\x00\xdc;\x04f\x02\xff\xed\xcd\xb1j\xc30\x14@\xd1\xbd_a4\x87\xe2\x86\x90\xa1k\xa1s\xf7\x92\xc1$J1\xb8\x96Q\xe5!\x18\xff{m\x87.]\xf2\x03g{\xe8]\xbd3\x85r\x1bbx\xad\xc2{l\xca\x98\xe3[\xea\xbax.m\xea\xc3\xae\n\xd7\xfb\xe3\xcf\x12|N\xff\xd35\x18r\x1ab.\xed\x96L\xa1\xe9J[\xc6\xcbZ\xf5c\xd7\xcdK\xf1\x15\xd3w,\xf9\xb6\xed\xff.|\xa4\xb6/\xeb\xffsJ\xf9\xd2\xf6M\xb9\x1b\x87\x97\xe3\xbe\xae\x9f\xeb]u<\xec\xebm<\xcd\xcb\x156\x9b\xcdf\xb3\xd9l6\x9b\xcdf\xb3\xd9l6\x9b\xcdf\xb3\xd9l6\x9b\xcdf\xb3\xd9l6\x9b\xcdf\xb3\xd9l6\x9b\xcdf\xb3\xd9l6\x9b\xcdf\xb3\xd9l6\x9b\xcdf\xb3\xd9l6\x9b\xcdf\xb3\xd9l6\x9b\xcdf\xb3\xd9l6\x9b\xcdf\xb3\xd9l6\x9b\xcdf\xb3\xd9l6\x9b\xcdf\xb3\xd9l6\x9b\xcdf\xb3\xd9l6\x9b\xcdf\xb3\xd9l6\x9b\xcdf\xb3\xd9l6\x9b\xcdf\xb3\xd9l6\x9b\xcdf\xb3\xd9l6\x9b\xcdf\xb3\xd9l6\x9b\xcdf\xb3\xd9l6\x9b\xcdf\xb3\xd9l6\x9b\xcdf\xb3\xd9l6\x9b\xcdf\xb3\xd9l6\x9b\xcdf\xb3\xd9l6\x9b\xcdf\xb3\xd9l6\x9b\xcdf\xb3\xd9l6\x9b\xcdf\xb3\xd9l6\x9b\xcdf\xb3\xd9l6\x9b\xcdf\xb3\xd9l6\x9b\xcdf\xb3\xd9l6\x9b\xcdf\xb3\xd9l6\x9b\xcdf\xb3\xd9l6\x9b\xcdf\xb3\xd9l6\x9b\xcdf\xb3\xd9l6\x9b\xcdf\xb3\xd9l6\x9b\xcdf\xb3\xd9l6\x9b\xcdf\xb3\xd9l6\x9b\xcdf\xb3\xd9l6\x9b\xcdf\xb3\xd9l6\x9b\xcdf\xb3\xd9l6\x9b\xcdf\xb3\xd9l6\x9b\xcdf\xb3\xd9l6\x9b\xcdf\xb3\xd9l6\x9b\xcdf\xb3\xd9l6\x9b\xcdf\xb3\xd9l6\x9b\xcdf\xb3\xd9l6\x9b\xcdf\xb3\xd9l6\x9b\xcdf\xb3\xd9l6\x9b\xcdf\xb3\xd9l6\x9b\xcdf\xb3\xd9l6\x9b\xcdf\xb3\xd9l6\x9b\xcdf\xb3\xd9l6\x9b\xcdf\xb3\xd9l6\x9b\xcdf\xb3\xd9l6\x9b\xcdf\xb3\xd9l6\x9b\xcdf\xb3\xd9l6\x9b\xcdf\xb3\xd9l6\x9b\xcdf\xb3\xd9l6\x9b\xcdf\xb3\xd9l6\x9b\xcdf\xb3\xd9\xec\xc7\xf6i~\xfa\x05P\x95\xa9\xda\x8c\xe4\x01\x00', 1, 2, 'invalid start byte')
Traceback (most recent call last):
  File "/home/GIT/naos-explorer/.venv_connexion3/lib/python3.11/site-packages/starlette/_exception_handler.py", line 53, in wrapped_app
    await app(scope, receive, sender)
  File "/home/GIT/naos-explorer/.venv_connexion3/lib/python3.11/site-packages/connexion/middleware/swagger_ui.py", line 222, in __call__
    await self.router(scope, receive, send)
  File "/home/GIT/naos-explorer/.venv_connexion3/lib/python3.11/site-packages/starlette/routing.py", line 758, in __call__
    await self.middleware_stack(scope, receive, send)
  File "/home/GIT/naos-explorer/.venv_connexion3/lib/python3.11/site-packages/starlette/routing.py", line 778, in app
    await route.handle(scope, receive, send)
  File "/home/GIT/naos-explorer/.venv_connexion3/lib/python3.11/site-packages/starlette/routing.py", line 487, in handle
    await self.app(scope, receive, send)
  File "/home/GIT/naos-explorer/.venv_connexion3/lib/python3.11/site-packages/starlette/routing.py", line 758, in __call__
    await self.middleware_stack(scope, receive, send)
  File "/home/GIT/naos-explorer/.venv_connexion3/lib/python3.11/site-packages/starlette/routing.py", line 808, in app
    await self.default(scope, receive, send)
  File "/home/GIT/naos-explorer/.venv_connexion3/lib/python3.11/site-packages/connexion/middleware/swagger_ui.py", line 235, in default_fn
    await self.app(original_scope, receive, send)
  File "/home/GIT/naos-explorer/.venv_connexion3/lib/python3.11/site-packages/connexion/middleware/routing.py", line 154, in __call__
    await self.router(scope, receive, send)
  File "/home/GIT/naos-explorer/.venv_connexion3/lib/python3.11/site-packages/starlette/routing.py", line 758, in __call__
    await self.middleware_stack(scope, receive, send)
  File "/home/GIT/naos-explorer/.venv_connexion3/lib/python3.11/site-packages/starlette/routing.py", line 778, in app
    await route.handle(scope, receive, send)
  File "/home/GIT/naos-explorer/.venv_connexion3/lib/python3.11/site-packages/starlette/routing.py", line 487, in handle
    await self.app(scope, receive, send)
  File "/home/GIT/naos-explorer/.venv_connexion3/lib/python3.11/site-packages/starlette/routing.py", line 758, in __call__
    await self.middleware_stack(scope, receive, send)
  File "/home/GIT/naos-explorer/.venv_connexion3/lib/python3.11/site-packages/starlette/routing.py", line 778, in app
    await route.handle(scope, receive, send)
  File "/home/GIT/naos-explorer/.venv_connexion3/lib/python3.11/site-packages/starlette/routing.py", line 299, in handle
    await self.app(scope, receive, send)
  File "/home/GIT/naos-explorer/.venv_connexion3/lib/python3.11/site-packages/connexion/middleware/routing.py", line 48, in __call__
    await self.next_app(original_scope, receive, send)
  File "/home/GIT/naos-explorer/.venv_connexion3/lib/python3.11/site-packages/connexion/middleware/abstract.py", line 264, in __call__
    return await operation(scope, receive, send)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/GIT/naos-explorer/.venv_connexion3/lib/python3.11/site-packages/connexion/middleware/security.py", line 111, in __call__
    await self.next_app(scope, receive, send)
  File "/home/GIT/naos-service-commons/naos_base_app/validators.py", line 107, in __call__
    await self.app(scope, receive, send)
  File "/home/GIT/naos-explorer/.venv_connexion3/lib/python3.11/site-packages/connexion/middleware/abstract.py", line 264, in __call__
    return await operation(scope, receive, send)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/GIT/naos-explorer/.venv_connexion3/lib/python3.11/site-packages/connexion/middleware/request_validation.py", line 142, in __call__
    await self.next_app(scope, receive, send)
  File "/home/GIT/naos-explorer/.venv_connexion3/lib/python3.11/site-packages/connexion/middleware/abstract.py", line 264, in __call__
    return await operation(scope, receive, send)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/GIT/naos-explorer/.venv_connexion3/lib/python3.11/site-packages/connexion/middleware/response_validation.py", line 127, in __call__
    await self.next_app(scope, receive, wrapped_send)
  File "/home/GIT/naos-explorer/.venv_connexion3/lib/python3.11/site-packages/connexion/middleware/lifespan.py", line 26, in __call__
    await self.next_app(scope, receive, send)
  File "/home/GIT/naos-explorer/.venv_connexion3/lib/python3.11/site-packages/connexion/middleware/abstract.py", line 264, in __call__
    return await operation(scope, receive, send)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/GIT/naos-explorer/.venv_connexion3/lib/python3.11/site-packages/connexion/middleware/context.py", line 25, in __call__
    await self.next_app(scope, receive, send)
  File "/home/GIT/naos-explorer/.venv_connexion3/lib/python3.11/site-packages/starlette/middleware/gzip.py", line 24, in __call__
    await responder(scope, receive, send)
  File "/home/GIT/naos-explorer/.venv_connexion3/lib/python3.11/site-packages/starlette/middleware/gzip.py", line 44, in __call__
    await self.app(scope, receive, self.send_with_gzip)
  File "/home/GIT/naos-explorer/.venv_connexion3/lib/python3.11/site-packages/connexion/apps/flask.py", line 151, in __call__
    return await self.asgi_app(scope, receive, send)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/GIT/naos-explorer/.venv_connexion3/lib/python3.11/site-packages/a2wsgi/wsgi.py", line 165, in __call__
    return await responder(scope, receive, send)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/GIT/naos-explorer/.venv_connexion3/lib/python3.11/site-packages/a2wsgi/wsgi.py", line 206, in __call__
    await sender
  File "/home/GIT/naos-explorer/.venv_connexion3/lib/python3.11/site-packages/a2wsgi/wsgi.py", line 226, in sender
    await send(message)
  File "/home/GIT/naos-explorer/.venv_connexion3/lib/python3.11/site-packages/starlette/middleware/gzip.py", line 109, in send_with_gzip
    await self.send(message)
  File "/home/GIT/naos-explorer/.venv_connexion3/lib/python3.11/site-packages/connexion/middleware/response_validation.py", line 125, in wrapped_send
    return await send(message)
           ^^^^^^^^^^^^^^^^^^^
  File "/home/GIT/naos-explorer/.venv_connexion3/lib/python3.11/site-packages/connexion/validators/abstract.py", line 201, in send_
    body = self._parse(stream)
           ^^^^^^^^^^^^^^^^^^^
  File "/home/GIT/naos-explorer/.venv_connexion3/lib/python3.11/site-packages/connexion/validators/json.py", line 117, in _parse
    body = b"".join(stream).decode(self._encoding)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
UnicodeDecodeError: 'utf-8' codec can't decode byte 0x8b in position 1: invalid start byte

Steps to reproduce

Create a Flask app with the validate_responses activated and the starlette GZipMiddleware :

default_middleware = ConnexionMiddleware.default_middlewares
default_middleware.append(GZipMiddleware)

connexion_app = connexion.FlaskApp('test', specification_dir='./specs',
                                                          middlewares=default_middleware,
                                                          validate_responses=True))

The route need to send a large response for the zip to be triggered, and you will get a 500 internal server error.

Additional info:

Seems similar to #1860 bur for response validation.

Output of the commands:

  • python --version
    Python 3.11.5
  • pip show connexion | grep "^Version\:"
    Version: 3.0.6

Hi @sgranel-fsk, can you try to insert the GZIPMiddleware in front of the ValidationMiddleware? As noted in the documentation, "responses hit the middlewares in reversed order.".

Hi, thank you for the hint it is working !
Sorry I missed this point in documentation, I am closing this issue.