Skip to content

Suppress duplicate 100-continue response#9975

Open
brasky wants to merge 1 commit into
getmoto:masterfrom
brasky:fix_duplicate_100_continue
Open

Suppress duplicate 100-continue response#9975
brasky wants to merge 1 commit into
getmoto:masterfrom
brasky:fix_duplicate_100_continue

Conversation

@brasky
Copy link
Copy Markdown

@brasky brasky commented Apr 16, 2026

I have been having an issue integrating with moto using the S3 async client. I did some digging and it appears that when the S3 client includes Expect: 100-continue in its PUT requests it gets back two responses which prevents the client from getting the actual response. It seems to be because Werkzeug and BaseHTTPRequestHandler both return a 100 continue response. The AWS SDK v2 async Java client handles a single 100 correctly but misinterprets the second one as the final response and throws S3Exception: null (Service: S3, Status Code: 100, Request ID: null), which has been super frustrating post-localstack migration debugging this.

I believe this is a reasonable fix where we are just suppressing one of the responses. My tests pass with this change at least :)

Here's a repro of the issue

"""
Reproduce: moto sends duplicate HTTP 100 Continue responses.
"""
import socket
import time

from moto.moto_server.threaded_moto_server import ThreadedMotoServer

server = ThreadedMotoServer(port=15555, verbose=False)
server.start()


def raw_request(method, path, body=None, headers=None):
    sock = socket.create_connection(("127.0.0.1", 15555))
    lines = [f"{method} {path} HTTP/1.1", "Host: localhost:15555"]
    for k, v in (headers or {}).items():
        lines.append(f"{k}: {v}")
    if body:
        lines.append(f"Content-Length: {len(body)}")
    lines.append("")
    lines.append("")
    sock.sendall("\r\n".join(lines).encode())
    if body:
        time.sleep(0.1)
        sock.sendall(body)
    sock.settimeout(2)
    chunks = []
    try:
        while True:
            data = sock.recv(4096)
            if not data:
                break
            chunks.append(data)
    except socket.timeout:
        pass
    sock.close()
    return b"".join(chunks)


# Create bucket
raw_request("PUT", "/test-bucket")

# PUT with Expect: 100-continue
resp = raw_request(
    "PUT",
    "/test-bucket/key",
    body=b'{"test": true}',
    headers={"Content-Type": "application/json", "Expect": "100-continue"},
)

server.stop()

status_lines = [
    l for l in resp.decode(errors="replace").split("\r\n") if l.startswith("HTTP/")
]
print("Response status lines:")
for line in status_lines:
    print(f"  {line}")

n_100 = sum(1 for l in status_lines if "100" in l)
assert n_100 <= 1, f"Expected at most 1 '100 Continue', got {n_100}"
print(f"\nPASS: got {n_100} '100 Continue' response(s)")

Without the patch you get:

127.0.0.1 - - [15/Apr/2026 18:33:39] "PUT /test-bucket HTTP/1.1" 200 -
127.0.0.1 - - [15/Apr/2026 18:33:39] "PUT /test-bucket/key HTTP/1.1" 200 -
Response status lines:
  HTTP/1.1 100 Continue
  HTTP/1.1 100 Continue
  HTTP/1.1 200 OK
Traceback (most recent call last):
  File "C:\Users\elliot.dematteis\repos\moto\repro.py", line 61, in <module>
    assert n_100 <= 1, f"Expected at most 1 '100 Continue', got {n_100}"
           ^^^^^^^^^^
AssertionError: Expected at most 1 '100 Continue', got 2

With the patch you get:

127.0.0.1 - - [15/Apr/2026 18:34:42] "PUT /test-bucket HTTP/1.1" 200 -
127.0.0.1 - - [15/Apr/2026 18:34:42] "PUT /test-bucket/key HTTP/1.1" 200 -
Response status lines:
  HTTP/1.1 100 Continue
  HTTP/1.1 200 OK

PASS: got 1 '100 Continue' response(s)

@bpandola
Copy link
Copy Markdown
Collaborator

Looks like this was a bug in Werkzeug (pallets/werkzeug/issues/3138) that has recently been fixed (but not yet released). I'd prefer this be handled upstream rather than with a change to Moto. Hopefully their next release will be out soon.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants