libhttppp ..
Loading...
Searching...
No Matches
http.h
1/*******************************************************************************
2Copyright (c) 2014, Jan Koester jan.koester@gmx.net
3All rights reserved.
4
5Redistribution and use in source and binary forms, with or without
6modification, are permitted provided that the following conditions are met:
7 * Redistributions of source code must retain the above copyright
8 notice, this list of conditions and the following disclaimer.
9 * Redistributions in binary form must reproduce the above copyright
10 notice, this list of conditions and the following disclaimer in the
11 documentation and/or other materials provided with the distribution.
12 * Neither the name of the <organization> nor the
13 names of its contributors may be used to endorse or promote products
14 derived from this software without specific prior written permission.
15
16THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
20DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26*******************************************************************************/
27
28#include <stddef.h>
29#include <sys/types.h>
30
31#include <vector>
32#include <string>
33#include <memory>
34#include <deque>
35#include <map>
36
37#include <netplus/socket.h>
38#include <netplus/connection.h>
39#include <netplus/eventapi.h>
40#include <netplus/crypto/tls.h>
41
42#include "config.h"
43
44#include "httpdefinitions.h"
45#include "hpack.h"
46
47#pragma once
48
49namespace libhttppp::qpack { struct HeaderField; }
50
51namespace libhttppp {
52 class HttpRequest;
53
54 class HttpUrl {
55 public:
56 enum HttpProtocol{
57 HTTP=0,
58 HTTPS=1,
59 HTTP3=2
60 };
61
62 HttpUrl();
63 HttpUrl(const std::string &url,bool http3=false);
64 HttpUrl(const HttpUrl &src);
65 HttpUrl& operator=(const HttpUrl &src) = default;
66 ~HttpUrl();
67
68 bool operator==(const HttpUrl& other) const;
69 bool operator<(const HttpUrl& other) const;
70
71 int getProtocol() const;
72 const std::string &getHost() const;
73 int getPort() const;
74 const std::string &getPath() const;
75
76 void clear();
77
78 std::string print() const;
79
80 private:
81 int _protocol;
82 std::string _host;
83 int _port;
84 std::string _path;
85 };
86
87 class HttpResponse;
88
90 public:
91 // timeoutSec bounds the constructor's own eager connection attempt
92 // (see resetConnection()) in addition to being the initial value
93 // setTimeout() would otherwise set afterward -- too late to affect
94 // that first connect.
95 HttpClient( const HttpUrl &desturl, int vers = 2, int timeoutSec = 60);
96 ~HttpClient()=default;
97 void reconnect();
98 void setTimeout(int timeout_sec);
99 const std::vector<char> Get(HttpRequest &nreq, size_t maxTries=0);
100 const std::vector<char> Post(HttpRequest &nreq,const std::vector<char> &post, size_t maxTries=0);
101 const std::vector<char> Put(HttpRequest &nreq,const std::vector<char> &put, size_t maxTries=0);
102 const std::vector<char> Delete(HttpRequest &nreq, size_t maxTries=0);
103
104 // Streaming API: send request, return parsed response headers only.
105 // After this call, use readBodyChunk() to read body data incrementally.
106 HttpResponse GetStream(HttpRequest &nreq);
107
108 // Same as GetStream, but POSTs a body first (e.g. an OpenAI-compatible
109 // "stream": true chat-completions request) before entering streaming-
110 // read mode. HTTP/1.1 upstream connections only -- throws HTTPException
111 // for HTTP/2 or HTTP/3 connections (no redirect handling either, unlike
112 // Post()); local LLM inference backends are plain HTTP/1.1 services, so
113 // this deliberately doesn't replicate Post()'s H2/H3/redirect handling.
114 HttpResponse PostStream(HttpRequest &nreq, const std::vector<char> &postBody);
115
116 // Read the next chunk of body data (up to bufsize bytes).
117 // Returns number of bytes written to buf, 0 when body is complete.
118 size_t readBodyChunk(char *buf, size_t bufsize);
119
120 // Non-blocking variant: returns 0 immediately if no data available yet.
121 // Returns (size_t)-1 when stream is complete (no more data will come).
122 size_t readBodyChunkNonBlocking(char *buf, size_t bufsize);
123
124 // True while a streaming read is in progress.
125 bool isStreaming() const;
126
127 // Wait until upstream socket has data to read (or timeout expires).
128 // timeout_ms: -1 = infinite, 0 = return immediately, >0 = milliseconds
129 // Returns true if readable, false on timeout.
130 bool waitReadable(int timeout_ms);
131
132 // Override the maximum number of redirects to follow (default: 5).
133 // Set to 0 to disable automatic redirect following.
134 void setMaxRedirects(int max) { _maxRedirects = max; }
135
136 // Returns the HTTP status code from the last response.
137 int lastStatusCode() const { return _lastStatusCode; }
138
139 // Returns the Content-Type from the last response.
140 const std::string &lastContentType() const { return _lastContentType; }
141
142 // Returns the full parsed response from the last request.
143 const HttpResponse *lastResponse() const { return _lastResponse.get(); }
144
145 // Drop the current connection so the next request opens a fresh one.
146 void resetConnection();
147
148 // Shared TLS session cache — enables abbreviated TLS 1.2 handshakes
149 // across reconnects to the same host. One cache per process.
150 static netplus::TlsSessionCache& tlsSessionCache();
151 private:
152 void _ensureConnected();
153 // Thin wrapper around netplus::tcp::connectTimeout() using
154 // _recvTimeoutSec, translating its NetException into HTTPException.
155 void _connectTcp(netplus::tcp &sock);
156 bool tryHttp3First();
157 int readchunk(const char *data,int datasize,int &pos);
158
159 // Non-blocking I/O helpers using poll() for efficient waiting
160 size_t _recvBlocking(netplus::buffer &b, int timeout_sec = 0);
161 // Returns 0 on EAGAIN (no data yet), otherwise bytes read
162 size_t _recvNonBlocking(netplus::buffer &b);
163 void _sendAll(const char *data, size_t len);
164 void _sendAll(const std::string &data);
165
166 // Shared HTTP/1.x response reader (avoids code duplication)
167 std::vector<char> _h1ReadResponse(const std::string &label);
168
169 // HTTP/2 client helpers
170 bool _isH2 = false;
171 bool _h2PrefaceSent = false; // true after connection preface sent
172
173 // HTTP/3 client helpers
174 uint32_t _h2NextStreamId = 1; // next client-initiated stream ID (odd)
175 std::unique_ptr<hpack::Decoder> _h2Decoder; // persistent HPACK decoder for connection
176 const std::vector<char> _h2Request(const std::string &method,
177 HttpRequest &nreq,
178 const std::vector<char> *postBody = nullptr);
179 const std::vector<char> _h3Request(const std::string &method,
180 HttpRequest &nreq,
181 const std::vector<char> *postBody = nullptr);
182
183 // Streaming state
184 enum StreamMode { STREAM_NONE, STREAM_CONTENT_LENGTH, STREAM_CHUNKED, STREAM_EOF,
185 STREAM_H2, STREAM_H3 };
186 StreamMode _streamMode = STREAM_NONE;
187 size_t _streamRemaining = 0; // bytes left for content-length mode
188 std::vector<char> _streamBuf; // leftover data from header read
189 size_t _streamBufPos = 0;
190 // Chunked streaming sub-state
191 bool _streamChunkDone = false; // true after final 0-length chunk
192 size_t _streamChunkRemaining = 0; // bytes left in current chunk
193
194 // HTTP/2 streaming state
195 uint32_t _streamH2Sid = 0; // stream ID for active H2 stream
196 bool _streamH2EndStream = false;
197 std::vector<uint8_t> _streamH2Raw; // raw frame buffer
198
199 // HTTP/3 streaming state
200 uint64_t _streamH3Sid = 0; // stream ID for active H3 stream
201 bool _streamH3EndStream = false;
202 std::vector<uint8_t> _streamH3Raw; // raw frame buffer
203 std::vector<char> _streamH3Body; // decoded DATA frames not yet consumed
204 size_t _streamH3BodyPos = 0; // consumed offset into _streamH3Body
205 bool _streamH3InDataFrame = false; // true when inside a partial DATA frame
206 uint64_t _streamH3DataRemaining = 0; // bytes left in current DATA frame
207
208 private:
209 HttpUrl _url;
210 std::unique_ptr<netplus::socket> _cltsock;
211 netplus::socketwait _sw;
212 netplus::x509cert _cert;
213 int _recvTimeoutSec = 60;
214 int _sendTimeoutSec = 30;
215 int _vers = 2; // HTTP version preference (0=h1 only, 1=h1+h2, 2=h2 preferred, 3=h3, 4=internal h2-only probe)
216
217 // Response tracking (populated by _h1ReadResponse / _h2Request / _h3Request)
218 int _lastStatusCode = 0;
219 std::string _lastLocation;
220 std::string _lastContentType;
221 std::unique_ptr<HttpResponse> _lastResponse;
222 static constexpr int MAX_REDIRECTS = 5;
223 int _maxRedirects = MAX_REDIRECTS;
224 };
225
226
228 public:
230 public:
231 class Values{
232 public:
233 Values &operator=(const std::string &val);
234 Values &operator=(size_t val);
235 Values &operator=(int val);
236 Values& operator=(const Values &val);
237
238 Values &operator<<(const std::string &value);
239 Values &operator<<(size_t value);
240 Values &operator<<(int value);
241
242 const std::string &getvalue();
243 int getIntvalue();
244 size_t getSizetValue();
245
246 Values *nextvalue();
247 Values(const std::string& val);
248 Values(const Values& val);
249 Values()=default;
250 ~Values() = default;
251 private:
252 std::string _value;
253 std::unique_ptr<Values> _nextvalue=nullptr;
254 friend class HeaderData;
255 };
256
257 Values *getfirstValue();
258 Values &at(int pos);
259 Values &operator[](int pos);
260
261 void push_back(const Values &val);
262 void push_back(const std::string &val);
263 void push_back(const char* val);
264 void push_back(size_t val);
265 void push_back(int val);
266
267 bool empty();
268
269 void erase(int pos);
270
271 void clear();
272
273 const std ::string &getkey();
274
275 HeaderData *nextHeaderData();
276 HeaderData(const std ::string &key);
277 ~HeaderData() = default;
278 private:
279 std::string _Key;
280 std::unique_ptr<Values> _firstValue=nullptr;
281 Values *_lastValue=nullptr;
282 std::unique_ptr<HeaderData> _nextHeaderData=nullptr;
283 friend class HttpHeader;
284 };
285
286 HeaderData *getfirstHeaderData();
287 HeaderData *getHeaderData(const std ::string &key) const;
288 HeaderData *setHeaderData(const std ::string &key);
289
290 void deldata(const std ::string &key);
291 void deldata(HeaderData*pos);
292
293 size_t getElements();
294 size_t getHeaderSize();
295
296 void clear();
297 protected:
298 HttpHeader();
299 virtual ~HttpHeader()=default;
300 std::unique_ptr<HeaderData> _firstHeaderData;
301 HeaderData *_lastHeaderData;
302 };
303
304 class HttpResponse : public HttpHeader,public netplus::con {
305 public:
306 HttpResponse();
307 HttpResponse(const HttpResponse &src);
309
310 /*server methods*/
311 void setState(const std ::string &httpstate);
312 void setContentType(const std ::string &type);
313 void setContentLength(size_t len);
314 void setConnection(const std ::string &type);
315 void setTransferEncoding(const std ::string &enc);
316
317 /*client methods*/
318 const std ::string &getState() const;
319 int getStatusCode() const;
320 const std ::string &getContentType() const;
321 size_t getContentLength() const;
322 const std ::string &getConnection() const;
323 const std ::string &getVersion() const;
324 HttpHeader::HeaderData::Values *getTransferEncoding() const;
325
326 size_t printHeader(std::vector<char> &buffer);
327
328 /*server methods*/
329 void send(netplus::con &curconnection,const std::string &data,int datalen=0); //only use as server
330 void send(netplus::con &curconnection,const unsigned char *data,int datalen); //only use as server
331 void send(netplus::con &curconnection,const std::vector<char> &data,int datalen=0); //only use as server
332
333 // Outbound chunked-transfer-encoding streaming (HTTP/1.1 only -- caller
334 // is responsible for only using these on a connection confirmed to be
335 // HTTP/1.1; they don't go through _storeResponseInfo's H2/H3 path).
336 // Usage: sendChunkedHeaders() once, then sendChunk() any number of
337 // times as data becomes available, then endChunked() exactly once.
338 void sendChunkedHeaders(netplus::con &curconnection);
339 void sendChunk(netplus::con &curconnection, const char *data, size_t len);
340 void endChunked(netplus::con &curconnection);
341
342 /*client method*/
343 size_t parse(const char *in,size_t inlen);
344
345 private:
346 bool _storeResponseInfo(netplus::con &curconnection, int datalen);
347
348 std::string _State=HTTP200;
349 std::string _Version;
350 int _StatusCode=200;
351 HeaderData *_TransferEncoding;
352 HeaderData *_Connection;
353 HeaderData *_ContentType;
354 HeaderData *_ContentLength;
355 mutable std::string _ContentTypeCache;
356 };
357
358
359 class HttpRequest : public HttpHeader, public netplus::con{
360 public:
361 HttpRequest();
362 HttpRequest(netplus::eventapi *evapi);
363 ~HttpRequest();
364
365 void clear();
366
367 /*server methods*/
368
369 size_t parse(); //only use as server
370
371 /*protocol-specific parse helpers (all store into _firstHeaderData)*/
372 size_t parseH2(const std::vector<hpack::HeaderField> &headers, uint32_t stream_id = 0);
373 size_t parseH3(const std::vector<qpack::HeaderField> &headers);
374
375 void printHeader(std::string &buffer);
376 int getRequestType();
377 const std::string &getRequestURL();
378 const std::string &getRequest();
379 size_t getRequestLength();
380 const std::string &getRequestVersion();
381 const std::string &getHost();
382 size_t getContentLength();
383 size_t getMaxUploadSize();
384
385 /* HTTP/1.1 chunked request-body support (server side).
386 * Traefik and other reverse proxies downgrade HTTP/2 POSTs to HTTP/1.1
387 * using Transfer-Encoding: chunked (no Content-Length). These helpers let
388 * the server loop de-chunk the body before dispatching RequestEvent. */
389 bool isChunkedRequest();
390 int decodeChunkedBody();
391
392 /*mobilphone switch*/
393 bool isMobile();
394
395 /*Client methods*/
396 void setRequestType(int req);
397 void setRequestURL(const std::string &url);
398 void setRequestVersion(const std::string &version);
399 /*only for post Reuquesttype*/
400 void setRequestData(const std::string &data,size_t len);
401 void setMaxUploadSize(size_t upsize);
402
403 void send(const HttpUrl &dest,std::unique_ptr<netplus::socket> &sock);
404
405 private:
406 size_t parseH1(); // HTTP/1.x request parsing
407
408 /*
409 * Helper: extracts URL path from :path header (strips query string).
410 * Used by getRequestURL() and parse helpers.
411 */
412 static std::string extractPath(const std::string &target);
413
414 int _RequestType = PARSEREQUEST;
415 size_t _MaxUploadSize = DEFAULT_UPLOADSIZE;
416
417 // Cached strings derived from _firstHeaderData pseudo-headers.
418 // Populated by parseH1/parseH2/parseH3, read by getters.
419 mutable std::string _cachedRequestURL;
420 mutable std::string _cachedRequest;
421 mutable std::string _cachedRequestVersion;
422 mutable std::string _cachedHost;
423
424 // HTTP/2 and HTTP/3 protocol state (managed by HttpEvent).
425 // All H2-specific mutable state lives in a heap-allocated struct so
426 // that inline-layout corruption of HttpRequest cannot trash the
427 // deque / map / decoder internals.
428 int _httpProtocol = 0; // 0=HTTP/1.x, 1=HTTP/2, 2=HTTP/3
429
430 struct H2PendingResponse {
431 uint32_t streamId;
432 std::string body; // remaining body data to send as DATA frames
433 size_t offset = 0; // how far into body we've sent
434 };
435
436 // Active streaming response state — lives on the connection's H2State
437 // so Http2RequestEvent can resume sending after WINDOW_UPDATE.
438 struct H2StreamingResponse {
439 uint32_t streamId = 0;
440 size_t contentLength = 0;
441 size_t totalSent = 0;
442 std::string pendingData; // buffered DATA not yet framed
443 size_t pendingOffset = 0;
444 std::unique_ptr<HttpRequest> tempreq; // per-stream request for ResponseEvent
445 int tid = 0;
446 ULONG_PTR args = 0;
447 size_t emptyCount = 0;
448 unsigned int backoffMs = 1;
449 bool finished = false;
450 };
451
452 struct H2PendingIncoming {
453 std::vector<hpack::HeaderField> headers;
454 std::string body;
455 std::vector<uint8_t> rawHpack; // accumulates HPACK across CONTINUATION frames
456 bool headersComplete = false; // true once END_HEADERS received
457 bool endStreamOnHeaders = false; // END_STREAM was on HEADERS frame
458 bool streaming = false; // body handled by onH2DataChunk callback
459 };
460
461 struct H2State {
462 uint32_t streamId = 0;
463 bool headersSent = false;
464 bool serverPrefaceSent = false;
465 size_t expectedContentLength = 0;
466 size_t bodyBytesSent = 0;
467 std::deque<H2PendingResponse> pendingResponses;
468 std::map<uint32_t, H2PendingIncoming> pendingIncoming;
469 hpack::Decoder hpackDecoder;
470 // Peer flow-control windows (RFC 7540 §6.9)
471 int32_t peerConnWindow = 65535; // connection-level
472 int32_t peerInitialStreamWindow = 65535; // from peer SETTINGS
473 size_t peerMaxFrameSize = 16384; // from peer SETTINGS_MAX_FRAME_SIZE (0x05)
474 std::map<uint32_t, int32_t> peerStreamWindows; // per-stream
475 // Active streaming responses (one per stream)
476 std::map<uint32_t, std::shared_ptr<H2StreamingResponse>> activeStreams;
477 };
478
479 // Lazily allocated when the connection is upgraded to HTTP/2.
480 std::unique_ptr<H2State> _h2;
481
482 // Allocate H2 state if not yet present; return reference.
483 H2State &h2state() {
484 if (!_h2) _h2 = std::make_unique<H2State>();
485 return *_h2;
486 }
487
488 friend class HttpForm;
489 friend class HttpResponse;
490 friend class HttpEvent;
491 };
492
493 class HttpForm {
494 public:
495 // ─── Multipart form-data (RFC 2046) ───────────────────
497 struct Header {
498 std::string key; // lowercased header name (e.g. "content-disposition")
499 std::string value; // full header value
500 };
501
502 struct Disposition {
503 std::string key; // e.g. "name", "filename"
504 std::string value; // e.g. "field1", "upload.txt"
505 };
506
507 std::vector<Header> headers;
508 std::vector<Disposition> dispositions;
509 std::vector<char> value; // raw body (binary-safe for file uploads)
510 };
511
512 // ─── URL-encoded form data ────────────────────────────
513 struct UrlEntry {
514 std::string key;
515 std::string value;
516 };
517
518 HttpForm() = default;
519 ~HttpForm() = default;
520
521 void parse(HttpRequest &request);
522
523 // Accessors
524 const std::string &getContentType() const { return _contentType; }
525 const std::string &getBoundary() const { return _boundary; }
526 const std::vector<MultipartEntry> &multipartData() const { return _multipartEntries; }
527 const std::vector<UrlEntry> &urlData() const { return _urlEntries; }
528
529 // URL encoding / decoding utilities
530 static void urlDecode(const std::string &in, std::string &out);
531 static void urlEncode(const std::string &in, std::string &out);
532
533 private:
534 void _parseMultipart(const char *data, size_t len);
535 void _parseMultiSection(const char *data, size_t len, size_t start, size_t end);
536 void _parseUrlDecode(const char *data, size_t len);
537
538 std::string _boundary;
539 std::string _contentType;
540 std::vector<MultipartEntry> _multipartEntries;
541 std::vector<UrlEntry> _urlEntries;
542 };
543
545 public:
547 public:
548 CookieData *nextCookieData() const;
549 const std::string &getKey() const;
550 const std::string &getValue() const;
551 CookieData()=default;
552 CookieData(const CookieData& src);
553 ~CookieData();
554 private:
555 std::string _Key;
556 std::string _Value;
557 std::unique_ptr <CookieData> _nextCookieData=nullptr;
558
559 friend class HttpCookie;
560 };
561 HttpCookie();
562 ~HttpCookie();
563 void parse(libhttppp::HttpRequest& curreq);
564 void setcookie(libhttppp::HttpResponse& curresp,
565 const std::string &key,const std::string &value,
566 const std::string &comment="",const std::string &domain="",
567 int maxage=-1,const std::string &path="",
568 bool secure=false,const std::string &version="1",const std::string &samesite="",bool httponly=false);
569 CookieData *getfirstCookieData();
570 CookieData *getlastCookieData();
571 CookieData *addCookieData();
572 private:
573 std::unique_ptr <CookieData> _firstCookieData;
574 CookieData *_lastCookieData;
575 };
576
577#define BASICAUTH 0
578#define DIGESTAUTH 1
579#define NTLMAUTH 2
580
581 class HttpAuth {
582 public:
583 HttpAuth();
584 ~HttpAuth();
585 void parse(libhttppp::HttpRequest &curreq);
586 void setAuth(libhttppp::HttpResponse &curresp);
587
588 void setAuthType(int authtype);
589 void setRealm(const std::string &realm);
590 void setUsername(const std::string &username);
591 void setPassword(const std::string &password);
592
593 const std::string &getUsername();
594 const std::string &getPassword();
595 int getAuthType();
596 const std::string &getAuthRequest();
597
598 private:
599 int _Authtype;
600 std::string _Username;
601 std::string _Password;
602 std::string _Realm;
603 std::string _Nonce;
604
605 };
606};
Definition https.h:38
Definition http.h:581
Definition http.h:89
Definition http.h:544
Definition httpd.h:50
Definition http.h:493
Definition http.h:227
Definition http.h:359
Definition http.h:304
Definition http.h:54
Definition hpack.h:68
Definition http.h:513