Add HTTPRequest/HTTPResponse: real HTTP parsing on the Parser/StreamReader stack
Stage 2 of the APIServer plan. This codebase has only ever sat behind a CGI gateway - IMFRequest/IMFResponse/IMFMessage predate the StreamReader/Parser rebuild and are stubs (IMFResponse's header-emission calls addHeader()/output(), neither of which exist on the class; IMFRequest is a fragile non-resumable 3-token line split). Nothing in this repo has ever actually parsed a raw HTTP request off a socket. HTTPRequest : public Parser fills that gap the same way every other Parser here is built: REQUEST_LINE -> HEADERS -> BODY -> DONE, chaining LineParser (request line) and IMFHeader (headers, reused as-is - HTTP header syntax is IMF-derived, nothing email-specific in IMFHeader itself) via checkParse() over one shared StreamReader, exactly the pattern CGIFormData already established for chaining IMFHeader/ IMFBoundary. The BODY state dispatches on Content-Type as CGIFormData itself does for CGI bodies - application/x-www-form-urlencoded and multipart/form-data delegate directly to CGIFormData (reused, not reimplemented), anything else is captured as a raw body (e.g. JSON). Query string is split off the path before anything else sees it. getHeader() does a case-insensitive lookup (HTTP header names are case-insensitive per RFC 7230, unlike IMFHeader's own std::map, which compares byte-for-byte) since a route handler constantly needs this and getting it wrong would be a silent framing bug, not a cosmetic one. The boundary-extraction code mirrors __jet.cpp's own fix exactly (same cursor-mutation trap: ZString::find() leaves the searched string's cursor at the match, and CGIFormData's constructor copy-constructs its own contentType member from whatever's passed in, inheriting that same moved cursor unless reset() first - would otherwise silently miss its own startsWith() dispatch checks). HTTPResponse is a small, new, output-only builder (status line + a real headers container + body, Content-Length auto-computed unless set explicitly) - deliberately not patched from IMFResponse, which has nothing safe to extend. v1 limitations, flagged not fixed (matching the standard already set by other Parser subclasses here for known-but-out-of-scope gaps): chunked Transfer-Encoding isn't supported, and repeated header names last-wins (inherited from IMFHeader's own std::map). Verified live via testing/http_test.cpp (34 assertions, new - now wired into testing/compile): byte-at-a-time GET with a query string and case-insensitive header lookup; a GET with no query string; a POST with an urlencoded body deliberately split mid-body (not just mid-header) across two feed() calls, confirming the CGIFormData delegation resumes correctly; a raw JSON POST body; testing/http_request.data (a real fixture, previously unused - normalized from its stored LF line endings to real CRLF, since HTTP requires CRLF and IMFHeader enforces that strictly) fed byte-at-a-time; and HTTPResponse::serialize() both with an auto-computed and an explicitly-set Content-Length (no duplicate header). Also reran the full existing testing/compile suite (zstring/mstring/jstring/mfile/jfile) against the rebuilt libCoreUtils.a to confirm no regression from the two new files. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6e4e463a96
commit
cb845efe69
7 files changed
+521
No files matched your search
@@ -0,0 +1,51 @@
|
||||
#ifndef __HTTPResponse_h__
|
||||
#define __HTTPResponse_h__
|
||||
|
||||
#include "MString.h"
|
||||
#include <map>
|
||||
|
||||
namespace coreutils {
|
||||
|
||||
///
|
||||
/// HTTPResponse builds a raw HTTP/1.1 response (status line + headers +
|
||||
/// body) ready to write to a socket. Output-only, not a Parser - there's
|
||||
/// nothing to resume here, the whole thing is built and serialized in
|
||||
/// one pass once a handler has finished producing its body.
|
||||
///
|
||||
/// Deliberately new rather than built on/patched from IMFResponse -
|
||||
/// IMFResponse's own header-emission code is dead (calls addHeader()/
|
||||
/// output(), neither of which exist on that class) and it never grew a
|
||||
/// real headers container, so there's nothing there safe to extend.
|
||||
///
|
||||
|
||||
class HTTPResponse {
|
||||
|
||||
public:
|
||||
|
||||
HTTPResponse();
|
||||
|
||||
void setStatus(int code, ZString text);
|
||||
void setHeader(ZString name, ZString value);
|
||||
void setBody(ZString body);
|
||||
|
||||
///
|
||||
/// Serializes the full response - status line, headers (an explicit
|
||||
/// Content-Length header is respected as-is; otherwise one is
|
||||
/// computed from the body automatically), the blank line, and the
|
||||
/// body - ready to write to a socket.
|
||||
///
|
||||
|
||||
MString serialize();
|
||||
|
||||
private:
|
||||
|
||||
int statusCode = 200;
|
||||
MString statusText;
|
||||
std::map<MString, MString> headers;
|
||||
MString body;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
Reference in new issue
Block a user