Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 37 additions & 1 deletion src/Horse.Request.pas
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,25 @@ THorseRequest = class
FCSPathInfo: string;
FCSContentType: string;
FCSRemoteAddr: string;
{ ===========================================================================
FIX-WS-8441 transport-signalled WebSocket upgrade.

IsWebSocket tests for `Upgrade: websocket`, which HTTP/2 cannot carry:
connection-specific headers are forbidden outright (RFC 9113 8.2.2), and a
conforming HTTP/2 server must reject a request that sends one. RFC 8441
replaces the whole mechanism with extended CONNECT `:method CONNECT` plus
a `:protocol: websocket` pseudo-header.

Pseudo-headers are deliberately stripped before they reach Req.Headers (they
are not real headers and would confuse Indy-style middleware), so core
cannot see `:protocol`. A provider that recognises the upgrade at transport
level sets this flag instead during request population.

Deliberately protocol-agnostic: HTTP/3 uses the same extended-CONNECT
mechanism, and a boolean covers both without core knowing either. Defaults
to False, so every existing provider behaves exactly as before.
=========================================================================== }
FWebSocketUpgrade: Boolean;
{ PATCH-REQ-9 cached decoded body string for the CrossSocket path.
Populated once at populate time by SetBodyString (called from
TRequestBridge.MapBody); returned directly by Body: string so the stream
Expand Down Expand Up @@ -123,6 +142,9 @@ THorseRequest = class
function ContentType: string; virtual;
function Host: string; virtual;
function IsWebSocket: Boolean; virtual;
{ FIX-WS-8441 set by providers whose transport signals a WebSocket upgrade by
means other than the HTTP/1.1 `Upgrade` header. See FWebSocketUpgrade. }
procedure SetWebSocketUpgrade(const AValue: Boolean); virtual;
function WebSocketKey: string; virtual;
property Arena: THorseArenaAllocator read GetArena write FArena;
function PathInfo: string; virtual;
Expand Down Expand Up @@ -366,6 +388,10 @@ procedure THorseRequest.Clear;
FCSContentType := '';
FCSRemoteAddr := '';
{ end PATCH-REQ-3 }
{ FIX-WS-8441 must be wiped with the other shadow fields. THorseRequest is
pooled; leaving this set would make the next request served by this instance
report IsWebSocket=True with no upgrade of its own. }
FWebSocketUpgrade := False;
{ PATCH-REQ-8 free the per-request TWebRequest adapter (owned).
Nil on the Indy path (never assigned there); owned on CrossSocket path. }
if Assigned(FCSRawWebRequest) then
Expand Down Expand Up @@ -964,7 +990,17 @@ procedure THorseRequest.SetBodyString(const AValue: string);

function THorseRequest.IsWebSocket: Boolean;
begin
Result := Headers.ContainsKey('upgrade') and (Pos('websocket', LowerCase(Headers['upgrade'])) > 0);
{ FIX-WS-8441 the flag is checked first and short-circuits, so an HTTP/2
provider never pays the header lookup. On HTTP/1.1 nothing sets it and the
original test runs unchanged. }
Result := FWebSocketUpgrade
or (Headers.ContainsKey('upgrade') and (Pos('websocket', LowerCase(Headers['upgrade'])) > 0));
end;

{ FIX-WS-8441 }
procedure THorseRequest.SetWebSocketUpgrade(const AValue: Boolean);
begin
FWebSocketUpgrade := AValue;
end;

function THorseRequest.WebSocketKey: string;
Expand Down