libhttppp (20260731+13) unstable; urgency=high

  * HttpEvent: fix H2 offloaded streaming responses (e.g. mediadb previews)
    stalling permanently once the peer's initial flow-control window is
    exhausted, whenever the response was dispatched to the H2 offload pool
    (shouldOffloadH2Dispatch() == true, shipped in 20260731+12). A client's
    WINDOW_UPDATE for that stream could arrive and be processed before the
    background render finished, at which point peerStreamWindows had no
    entry yet for that stream id — the credit was silently discarded, and
    _finishH2Dispatch then unconditionally reset the window to the default
    once the render completed, permanently losing it. A well-behaved peer
    never repeats a grant it believes already landed, so any response body
    over ~64KB (the default window) hung forever with no timeout and no
    error. _dispatchH2Stream now seeds peerStreamWindows[sid] via
    try_emplace synchronously before handing the request to the offload
    pool (safe: all frame dispatch for a connection, including a later
    WINDOW_UPDATE, is serialized through the same connection event_mutex),
    and _finishH2Dispatch's two window-init sites use try_emplace instead
    of unconditional assignment so they no longer clobber credit gained in
    the meantime. New test: httpd_h2_window_credit_race_test.cpp.

 -- Jan Koester <jan.koester@tuxist.de>  Fri, 31 Jul 2026 09:45:00 +0200

libhttppp (20260731+12) unstable; urgency=high

  * HttpEvent: fix H2 streaming responses (large images/video) stalling
    permanently after the first chunk whenever the upstream/handler had
    no more data ready yet — _finishH2Dispatch had no else branch (H2)
    and Http3StreamEvent's had an empty one, so if the app handler never
    called HttpResponse::send() nothing was ever sent back, not even an
    error; now sends RST_STREAM(INTERNAL_ERROR) for H2 and
    resetStream(H3_INTERNAL_ERROR) for H3 so the stream terminates
    deterministically instead of hanging the client forever.
  * Http2RequestEvent: the reprocess-count fairness cap (16 per call)
    could strand a >17th buffered simple request on one multiplexed
    connection with no way to ever get processed, since SendData was
    already flushed and empty by the time control returned to epoll. Now
    explicitly sets ResponsePending + calls requestWritablePoll when the
    cap is hit while more complete-frame data is still buffered.
  * HttpEvent: exported threadpool.h-based H2 dispatch offload
    infrastructure (con::ownerPollFd, netplus::lookupConnection(fd),
    netplus::requestWritablePoll(con&)) so a blocking per-request handler
    can run off the event-loop thread without blocking sibling streams on
    the same connection — root cause of the H2 "staircase" (streams on
    one connection served strictly sequentially instead of concurrently).
    New test: httpd_h2_concurrency_test.cpp.

 -- Jan Koester <jan.koester@tuxist.de>  Fri, 31 Jul 2026 08:05:00 +0200

libhttppp (20260718+11) unstable; urgency=high

  * HttpUrl: track the original URL scheme (http/https) separately from
    _protocol, which the http3=true constructor flag permanently pins to
    HTTP3. Add HttpUrl::getOriginalProtocol() for the fallback decisions
    below, which need to know the actual scheme regardless of that flag.
  * HttpClient: fix HTTP/3 clients constructed against a non-QUIC endpoint
    (e.g. mediadb::Client(url, ..., /*http3=*/true) against a plain HTTP
    server) always failing outright instead of falling back to HTTP/1.1.
    tryHttp3First() correctly detected and cached the QUIC failure, but the
    ctor's own fallback-strategy check (getProtocol() == HTTPS, meant to
    tell "try HTTP/2 next" apart from "go cleartext") could never see HTTPS
    once http3=true had pinned that field to HTTP3, so cleartext targets
    always fell into the cleartext branch and called resetConnection() --
    which re-derived the transport from that same permanently-HTTP3 field
    and re-entered the QUIC branch a second time. That second attempt runs
    quic::connect() in blocking mode with no surrounding try/catch, so it
    blocks for the full 5s handshake timeout and then throws instead of
    falling back, surfacing on every request as "HttpClient ctor connect
    failed: quic::completeHandshake: handshake timed out". resetConnection()
    and reconnect() now only take the QUIC branch when _vers == 3 (HTTP/3 is
    still what's wanted, not a downgraded fallback attempt), and choose
    between TLS and plain TCP based on the URL's original scheme instead of
    the pinned field.

 -- Jan Koester <jan.koester@tuxist.de>  Sat, 18 Jul 2026 14:50:00 +0200

libhttppp (20260707+10) unstable; urgency=medium

  * HttpEvent::_resumeH2Streams: on a genuine upstream stall (emptyCount >
    500) with the response short of the advertised Content-Length, send
    RST_STREAM(INTERNAL_ERROR) instead of END_STREAM. Ending the stream
    normally made the client accept the truncated body as a complete
    response (e.g. a corrupted image); aborting the stream now surfaces
    the failed transfer instead.

 -- Jan Koester <jan.koester@tuxist.de>  Tue, 07 Jul 2026 12:11:54 +0200

libhttppp (20260704+9) unstable; urgency=high

  * HttpClient::GetStream: fix unbounded blocking while reading response
    headers — each call to the header-read loop restarted a fresh 10s
    timeout on every partial chunk, so a peer trickling a byte every few
    seconds could hold the loop (and the calling thread) open indefinitely.
    Now uses a single 10s deadline for the whole header, throwing
    HTTPException on expiry instead of resetting the clock per chunk.
  * HttpEvent::_resumeH2Streams: fix HTTP/2 streaming responses (video,
    large previews) spinning forever instead of failing when the upstream
    stalls. The stall counter (emptyCount, capped at 500) was reset to 0
    every time by checking ss->tempreq's own ResponsePending flag — but
    tempreq is a synthetic per-stream request with no external writer of
    that flag; it was actually being set, on every empty read, by the
    app-level ResponseEvent handler we had just called (a "rearm me for a
    real connection" signal, not a "new data arrived" signal), so the
    check always read true and the counter could never advance. Combined
    with the epoll layer re-arming EPOLLOUT every such cycle (always
    ready, since nothing is being written), a stalled upstream produced
    an unconditional, zero-backoff CPU spin with no progress on the
    stream. emptyCount now increments unconditionally on empty reads, so
    the existing 500-attempt cap actually terminates a genuinely stalled
    stream with END_STREAM instead of hanging.

 -- Jan Koester <jan.koester@tuxist.de>  Sat, 04 Jul 2026 06:02:49 +0200

libhttppp (20260629+8) unstable; urgency=medium

  * HTTPException: derive from std::exception and change what() to the
    standard "const char* noexcept" signature instead of returning a
    "const std::string&". Callers catching libhttppp::HTTPException by a
    generic "catch(std::exception&)" now work correctly, and the httpform,
    httpauth and httpclient examples were updated to use the new signature
    (httpauth/httpclient now catch std::exception instead of HTTPException
    so unrelated std:: exceptions are no longer left uncaught).
  * HttpClient: when an HTTP/3 connection attempt fails, probe HTTP/2 via
    ALPN before falling back to HTTP/1.1 instead of dropping straight to
    HTTP/1.1. If that HTTP/2 probe also fails to negotiate "h2", the
    constructor now throws HTTPException instead of silently retrying the
    connection over HTTP/1.1, since a silent downgrade masked negotiation
    failures that should be surfaced to the caller.

 -- Jan Koester <jan.koester@tuxist.de>  Mon, 29 Jun 2026 12:00:00 +0200

libhttppp (20260605+7) unstable; urgency=medium

  * HttpD: decode HTTP/1.1 chunked request bodies on the server side. Reverse
    proxies such as Traefik downgrade HTTP/2 POSTs to HTTP/1.1 using
    Transfer-Encoding: chunked (no Content-Length); the server loop now
    de-chunks the body before dispatching RequestEvent and rewrites the
    Content-Length header, fixing form submissions that were forwarded with
    raw chunk framing and could not be parsed by the backend.

 -- Jan Koester <jan.koester@tuxist.de>  Fri, 05 Jun 2026 12:00:00 +0200

libhttppp (20260521+6) unstable; urgency=medium

  * HttpD: add reloadCertificates() for runtime TLS certificate hot-reload

 -- Jan Koester <jan.koester@tuxist.de>  Wed, 21 May 2026 12:00:00 +0200

libhttppp (20260515+5) unstable; urgency=medium

  * Rebuild

 -- Jan Koester <jan.koester@tuxist.de>  Thu, 15 May 2026 12:00:00 +0200

libhttppp (20260514+4) unstable; urgency=medium

  * HttpD: improve "invalid certfile" error message — include certificate CN
    and system_time for diagnosing clock skew in containers

 -- Jan Koester <jan.koester@tuxist.de>  Wed, 14 May 2026 14:00:00 +0200

libhttppp (20260505+3) unstable; urgency=medium

  * HttpClient: add null guard for _cltsock in _recvNonBlocking() to
    prevent SIGSEGV when readBodyChunkNonBlocking() is called on a
    client whose socket has been closed or was never connected

 -- Jan Koester <jan.koester@tuxist.de>  Mon, 05 May 2026 17:30:00 +0200

libhttppp (20260505+2) unstable; urgency=high

  * Rebuild against libnetplus 20260505+12 (fixes crash "free(): invalid
    pointer" in quic::~quic() — child connections double-closing parent
    socket fd; flow control double-counting; stream resurrection;
    sendStreamData silent data loss; uni-stream dispatch with incomplete
    data; FIN-only frame not sent on wire)

 -- Jan Koester <jan.koester@tuxist.de>  Mon, 05 May 2026 16:30:00 +0200

libhttppp (20260502+1) unstable; urgency=medium

  * Rebuild against libnetplus 20260502+1 (QUIC performance improvements)

 -- Jan Koester <jan.koester@tuxist.de>  Sat, 02 May 2026 12:00:00 +0200

libhttppp (20260424+9) unstable; urgency=medium

  * Flush H2 SendData after each stream dispatch to prevent serialised
    preview responses from timing out on multiplexed connections

 -- Jan Koester <jan.koester@tuxist.de>  Thu, 24 Apr 2026 00:00:00 +0200

libhttppp (20260423+8) unstable; urgency=medium

  * Add missing #include <netplus/crypto/tls.h> for TlsSessionCache

 -- Jan Koester <jan.koester@tuxist.de>  Thu, 23 Apr 2026 00:00:00 +0200

libhttppp (20260410+7) unstable; urgency=medium

  * Use CertificateBundle::loadFromFile() for automatic PEM/DER/P12 detection
  * Add optional sslpassword parameter to HttpD constructor for PKCS#12 support

 -- Jan Koester <jan.koester@tuxist.de>  Thu, 10 Apr 2026 00:00:00 +0200

libhttppp (20260409+6) unstable; urgency=medium

  * Rebuild against libnetplus 20260409+14 (BLOCKSIZE 65536)

 -- Jan Koester <jan.koester@tuxist.de>  Wed, 09 Apr 2026 00:00:00 +0200

libhttppp (20260409+5) unstable; urgency=medium

  * Add getHost() method to HttpRequest (reads host / :authority header)

 -- Jan Koester <jan.koester@tuxist.de>  Thu, 09 Apr 2026 00:00:00 +0200

libhttppp (20260408+4) unstable; urgency=medium

  * New release

 -- Jan Koester <jan.koester@tuxist.de>  Wed, 08 Apr 2026 00:00:00 +0200

libhttppp (20260407+3) unstable; urgency=medium

  * New release

 -- Jan Koester <jan.koester@tuxist.de>  Tue, 07 Apr 2026 00:00:00 +0200

libhttppp (20260404+2) unstable; urgency=medium

  * Remove all debug std::cerr logging from Http2RequestEvent/ResponseEvent
    to eliminate data races on _ZSt4cerr across worker threads

 -- Jan Koester <jan.koester@tuxist.de>  Fri, 04 Apr 2026 00:00:00 +0000

libhttppp (20260404+1) unstable; urgency=medium

  * Add streaming body callbacks: onH2StreamHeaders, onH3StreamHeaders,
    onH2DataChunk, onH3DataChunk for incremental body processing
  * Add sendH2StreamResponse / sendH3StreamResponse helpers
  * H2 DATA frames routed to onH2DataChunk when streaming=true
  * H3 incremental frame parsing with onH3DataChunk support

 -- Jan Koester <jan.koester@tuxist.de>  Fri, 04 Apr 2026 00:00:00 +0200

libhttppp (20260404) unstable; urgency=medium

  * Make RequestEvent/ResponseEvent/ConnectEvent/DisconnectEvent(con&)
    virtual in HttpEvent to allow subclass overrides

 -- Jan Koester <jan.koester@tuxist.de>  Fri, 04 Apr 2026 00:00:00 +0200

libhttppp (20260401) unstable; urgency=medium

  * Initial Debian packaging with multiarch support.

 -- Jan Koester <jan.koester@tuxist.de>  Wed, 01 Apr 2026 00:00:00 +0200
