diff --git a/moto/moto_server/threaded_moto_server.py b/moto/moto_server/threaded_moto_server.py index af4f347968ea..9a6ea62db36f 100644 --- a/moto/moto_server/threaded_moto_server.py +++ b/moto/moto_server/threaded_moto_server.py @@ -1,11 +1,26 @@ from threading import Event, Thread from typing import Optional -from werkzeug.serving import BaseWSGIServer, make_server +from werkzeug.serving import BaseWSGIServer, WSGIRequestHandler, make_server from .werkzeug_app import DomainDispatcherApplication, create_backend_app +class _MotoRequestHandler(WSGIRequestHandler): + def handle_expect_100(self) -> bool: + """Suppress the duplicate ``100 Continue`` from BaseHTTPRequestHandler. + + Werkzeug's ``run_wsgi()`` already sends its own ``100 Continue`` + when it sees the ``Expect`` header. The default + ``BaseHTTPRequestHandler.parse_request()`` sends a second one, + resulting in two ``100 Continue`` responses on the wire. + + Returning ``True`` without writing anything prevents the duplicate + while letting werkzeug send exactly one ``100 Continue`` later. + """ + return True + + class ThreadedMotoServer: def __init__( self, ip_address: str = "0.0.0.0", port: int = 5000, verbose: bool = True @@ -21,7 +36,13 @@ def __init__( def _server_entry(self) -> None: app = DomainDispatcherApplication(create_backend_app) - self._server = make_server(self._ip_address, self._port, app, True) + self._server = make_server( + self._ip_address, + self._port, + app, + threaded=True, + request_handler=_MotoRequestHandler, + ) self._server_ready_event.set() self._server.serve_forever() diff --git a/moto/server.py b/moto/server.py index 133e909952fd..ca0d348827b7 100644 --- a/moto/server.py +++ b/moto/server.py @@ -9,6 +9,7 @@ # Expose ThreadedMotoServer as a public symbol according to PEP-561 from moto.moto_server.threaded_moto_server import ( ThreadedMotoServer as ThreadedMotoServer, + _MotoRequestHandler, ) from moto.moto_server.werkzeug_app import ( DomainDispatcherApplication, @@ -83,6 +84,7 @@ def main(argv: Optional[list[str]] = None) -> None: threaded=True, use_reloader=args.reload, ssl_context=ssl_context, + request_handler=_MotoRequestHandler, )