Comparing master...fix/stream-error-body-and-framing · mcpplibs/tinyhttps · GitHub
Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: mcpplibs/tinyhttps
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: master
Choose a base ref
...
head repository: mcpplibs/tinyhttps
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: fix/stream-error-body-and-framing
Choose a head ref
Checking mergeability… Don’t worry, you can still create the pull request.
  • 2 commits
  • 3 files changed
  • 2 contributors

Commits on Aug 29, 2026

  1. fix(http): keep the error body of a failed streaming request

    send() fills HttpResponse::body on every path including failures; send_stream()
    was the one entry point that dropped it. A non-2xx answer to a streaming request
    is an error document, not an event stream: SseParser finds no event boundary in
    it, emits nothing, and the bytes stay in its private buffer. Callers were left
    with a status line and no reason.
    
    Capture the body when the status is not 2xx. Events are still parsed and
    dispatched exactly as before, and nothing is copied on a 2xx stream, so the
    success path is byte-identical.
    
    The copy is bounded by stream_error_body_limit (1 MiB) so a server answering 5xx
    with an endless body cannot grow the buffer without limit. Truncation lives in an
    exported append_within_limit, in the same spirit as parse_chunk_size_line, with
    three unit tests for under, across and past the limit; a live test against
    httpbin's /status/418 covers the wiring.
    yspbwx2010 authored and Sunrisepeak committed Aug 29, 2026
    Configuration menu
    Copy the full SHA
    f99fe73 View commit details
    Browse the repository at this point in the history
  2. Honour a declared Content-Length in send_stream, and reject a chunk s…

    …ize rather than salvaging it
    
    Review of the change this branch already carries. The defect it reports is real
    and the fix is placed correctly --- `dispatch` is the single funnel for every
    body byte on both framing paths, and `captureBody` is decided after the headers
    are read, where the status is final. Measured against master, one program, one
    source file:
    
        master   status=418 events=0 body.size()=0
        this     status=418 events=0 body.size()=135
    
    What follows is what that fix could not do on its own.
    
    --- 1. A DECLARED LENGTH, WHICH IS WHY THE TEST HAD TO CLOSE THE CONNECTION ----
    
    `send_stream` had no branch for `Content-Length`: a response that was not
    chunked was read until the connection closed, whatever its headers said. On this
    library's own defaults --- `keepAlive = true`, so the request carries
    `Connection: keep-alive` --- the server does not close, and the read loop ran
    until `readTimeoutMs` expired. Measured against httpbin's `/status/418`:
    
        keepAlive = false   status=418 body=135   elapsed  1370 ms
        keepAlive = true    status=418 body=135   elapsed  9379 ms   (timeout 8000)
    
    The error body arrived either way, and on the defaults it arrived a full read
    timeout late --- sixty seconds, as the defaults stand. `send()` has had this
    branch throughout, which is the same asymmetry between the two entry points that
    this branch exists to remove.
    
    The live test set `keepAlive = false`, "so the server closes and the read loop
    ends". That comment was the defect, and the test was examining the one
    arrangement in which it does not appear. It now runs on the defaults and asserts
    the elapsed time.
    
        after: keepAlive = true    status=418 body=135   elapsed  1192 ms
    
    --- 2. A CHUNK SIZE THAT DOES NOT PARSE IS NOT A TERMINAL CHUNK ---------------
    
    `parse_hex` returns what it accumulated when it meets a character it does not
    recognise, and zero for an empty line --- and `read_line` returns an empty line
    on a timeout or a closed connection. So a stream that was cut short read as a
    stream that ended cleanly and this loop reported success. #9 established
    `parse_chunk_size_line` for exactly this and it reached `download_to_file`
    alone; `send` and `send_stream` were left on the old one.
    
    --- 3. Content-Length WAS PARSED BY KEEPING THE DIGITS ------------------------
    
    Measured, by compiling that parser on its own:
    
        "135"                  -> 135
        "abc"                  -> 0                     <- a refusal read as a real zero
        "12abc"                -> 12                    <- stops twelve bytes in
        "-1"                   -> 1                     <- the sign is discarded
        "99999999999999999999" -> 7766279631452241919   <- wraps, in silence
    
    The last two are the ones no care at the call site could recover from, because
    what it receives is a plausible number. `parse_content_length` is exported and
    shaped like `parse_chunk_size_line`, for the reason #9 gave: it is the half of
    the body framing that can be examined without a server. Both readers use it.
    
    --- criteria -----------------------------------------------------------------
    
    Six unit tests over the two pure parsers, and two live ones: the failed stream
    now runs on the DEFAULT configuration with the elapsed time asserted, and a
    chunked 2xx is asserted to leave `body` empty and to return promptly --- the
    success path is the one this change restructured around, so it is observed
    rather than assumed.
    
    17 tests from 6 suites pass, plus 3 in test_resolver.
    Sunrisepeak committed Aug 29, 2026
    Configuration menu
    Copy the full SHA
    dd1df30 View commit details
    Browse the repository at this point in the history
Loading