libhttppp ..
Loading...
Searching...
No Matches
httpd.h
1/*******************************************************************************
2 * Copyright (c) 2014, Jan Koester jan.koester@gmx.net
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, 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 *
16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
20 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 * ON 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
25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 *******************************************************************************/
27
28#include <netplus/socket.h>
29#include <netplus/eventapi.h>
30#include <netplus/threadpool.h>
31#include <atomic>
32#include <cstdint>
33#include <map>
34#include <memory>
35#include <mutex>
36#include <set>
37#include <string>
38#include <vector>
39
40#include "http.h"
41#include "qpack.h"
42#include "exception.h"
43
44#pragma once
45
46namespace cmdplus {
47 class CmdController;
48}
49
50namespace libhttppp {
51 class HttpEvent : public netplus::event {
52 public:
53 // h2OffloadThreads: when non-zero, spins up a background thread pool
54 // (see shouldOffloadH2Dispatch) that lets HTTP/2 streams whose
55 // RequestEvent does slow, blocking work (e.g. a backend network
56 // round-trip) run without blocking every other stream multiplexed
57 // on the same connection. Zero (the default) preserves the original
58 // fully-synchronous H2 dispatch behavior for every existing caller.
59 // idleTimeoutSeconds: forwarded to netplus::event's own idleTimeoutSeconds (see
60 // eventapi.h's doc comment) -- closes an accepted connection once it's gone this long
61 // with no genuine read/write activity. 0 (the default) preserves the original
62 // behavior: a connection stays open until the peer closes it or a transport error
63 // occurs, however long that takes.
64 // h1OffloadThreads: HTTP/1.x analogue of h2OffloadThreads (see shouldOffloadH1Dispatch)
65 // -- zero (the default) preserves the original fully-synchronous H1 dispatch behavior
66 // for every existing caller. Unlike H2, an H1 connection has no per-stream separation
67 // (cureq *is* the connection, one request in flight at a time), so offloading it
68 // safely needs a detach/reattach round trip through netplus::detachConnection()/
69 // reattachConnection() rather than H2's "hand off a throwaway tempreq" trick -- see
70 // _dispatchH1Request's doc comment for the full mechanics.
71 // h2DispatchQueueMax: caps how many H2 streams may be queued in _h2DispatchPool waiting
72 // for a worker (see netplus::ThreadPool's own max_queue_size). 0 (the default) keeps the
73 // pool unbounded for every existing caller -- exactly today's behavior, where a stuck or
74 // saturated pool means every subsequent stream on every connection waits forever with no
75 // way to notice. A positive value makes _dispatchH2Stream respond 503 immediately once
76 // the pool is this full instead of queuing behind it. Has no effect if h2OffloadThreads
77 // is 0 (nothing to bound).
78 // h1DispatchQueueMax: H1 analogue of h2DispatchQueueMax, bounding _h1DispatchPool (see
79 // netplus::ThreadPool's own max_queue_size). 0 (the default) keeps it unbounded for
80 // every existing caller. Before this existed, a saturated _h1DispatchPool left every
81 // subsequent offloaded request's socket detached from the event loop (invisible to the
82 // idle reaper) and un-timed until a worker finally freed up -- if the peer gave up
83 // first, proxyplus discovered it far too late and started its own close against a
84 // peer that might already be gone, producing sockets stuck in LAST_ACK. A positive
85 // value makes _dispatchH1Request answer 503 immediately (without ever calling the
86 // possibly-slow RequestEvent) once the pool is this full, mirroring H2's overload
87 // response. Has no effect if h1OffloadThreads is 0.
88 HttpEvent(std::vector<netplus::socket*> serversocket,int timeout = 1000,
89 size_t h2OffloadThreads = 0, int idleTimeoutSeconds = 0,
90 size_t h1OffloadThreads = 0, size_t h2DispatchQueueMax = 0,
91 size_t h1DispatchQueueMax = 0);
92
93 // Return true to have this stream's RequestEvent run on the H2
94 // offload thread pool instead of inline in the frame-processing
95 // loop. Called right after the per-stream request's headers have
96 // been parsed (so :path/:method are already available), before
97 // RequestEvent runs. Defaults to false — every route stays on the
98 // original synchronous path unless a subclass opts a specific
99 // route in. Has no effect if h2OffloadThreads is 0.
100 virtual bool shouldOffloadH2Dispatch(HttpRequest &tempreq, uint32_t streamId) const {
101 return false;
102 }
103
104 // Return true to have this HTTP/1.x request's RequestEvent run on the H1 offload
105 // thread pool instead of inline on the epoll/kqueue worker that read it. Called right
106 // after the request is fully parsed (headers and, for a body-bearing method, the
107 // complete body already buffered), before RequestEvent runs. Defaults to false --
108 // every route stays on the original synchronous path unless a subclass opts in. Has
109 // no effect if h1OffloadThreads is 0.
110 virtual bool shouldOffloadH1Dispatch(HttpRequest &cureq) const {
111 return false;
112 }
113
114 virtual void RequestEvent(HttpRequest &curreq,const int tid,ULONG_PTR args);
115 virtual void ResponseEvent(HttpRequest &curreq,const int tid,ULONG_PTR args);
116 virtual void ConnectEvent(HttpRequest &curreq,const int tid,ULONG_PTR args);
117 virtual void DisconnectEvent(HttpRequest &curreq,const int tid,ULONG_PTR args);
118
119 virtual bool Http2RequestEvent(netplus::con &curcon,
120 const int tid,
121 ULONG_PTR args,
122 const std::string &alpn,
123 const netplus::ssl::FramingCallback &frame_cb);
124 virtual void Http3StreamEvent(netplus::socket *sock,
125 uint64_t stream_id,
126 const std::vector<uint8_t> &data,
127 bool fin);
128
129 // Streaming body callbacks for H2/H3.
130 // Called when headers are complete for a body-bearing stream.
131 // Return true to handle body data via onH2DataChunk/onH3DataChunk
132 // instead of buffering the full body.
133 virtual bool onH2StreamHeaders(HttpRequest &conn, uint32_t streamId,
134 const std::vector<hpack::HeaderField> &headers);
135 virtual bool onH3StreamHeaders(netplus::socket *sock, uint64_t streamId,
136 const std::vector<qpack::HeaderField> &headers);
137
138 // Called for each body data chunk when streaming is enabled.
139 // endStream/fin: true on the last chunk.
140 virtual void onH2DataChunk(HttpRequest &conn, uint32_t streamId,
141 const char *data, size_t len, bool endStream,
142 std::string &h2out, const int tid, ULONG_PTR args);
143 virtual void onH3DataChunk(netplus::socket *sock, uint64_t streamId,
144 const char *data, size_t len, bool fin);
145
146 protected:
147 // Helpers for sending a complete response on an H2/H3 stream.
148 void sendH2StreamResponse(std::string &h2out, uint32_t streamId,
149 uint16_t status, const std::string &contentType,
150 const std::string &body);
151 void sendH3StreamResponse(netplus::socket *sock, uint64_t streamId,
152 uint16_t status, const std::string &contentType,
153 const std::string &body);
154 virtual void CreateConnection(std::shared_ptr<netplus::con> &res);
155
156 virtual void RequestEvent(netplus::con &curcon, const int tid, ULONG_PTR args);
157 virtual void ResponseEvent(netplus::con &curcon,const int tid,ULONG_PTR args);
158 virtual void ConnectEvent(netplus::con &curcon,const int tid,ULONG_PTR args);
159 virtual void DisconnectEvent(netplus::con &curcon,const int tid,ULONG_PTR args);
160
161 std::string _altSvcH3; // Alt-Svc value for HTTP/3 advertisement
162
163 // TLS session cache — shared across all accepted SSL connections
164 // to enable abbreviated TLS 1.2 handshakes on client reconnection.
165 netplus::TlsSessionCache _tlsSessionCache;
166 private:
167 // Per-stream state for HTTP/3: accumulates data and supports
168 // incremental H3 frame parsing for streaming body callbacks.
169 struct H3StreamState {
170 std::vector<uint8_t> data;
171 bool headersParsed = false;
172 bool streaming = false;
173 size_t parseOffset = 0;
174 };
175 std::mutex _h3BufferMutex;
176 std::map<uint64_t, H3StreamState> _h3StreamStates;
177 std::atomic<int> _h3NextTid{0};
178
179 // Non-null only when h2OffloadThreads > 0 was passed to the
180 // constructor. See shouldOffloadH2Dispatch / _dispatchH2Stream.
181 std::unique_ptr<netplus::ThreadPool> _h2DispatchPool;
182
183 // Non-null only when h1OffloadThreads > 0 was passed to the constructor. See
184 // shouldOffloadH1Dispatch / _dispatchH1Request.
185 std::unique_ptr<netplus::ThreadPool> _h1DispatchPool;
186
187 // Always constructed (unlike _h2DispatchPool, which is opt-in):
188 // every streaming H3 response needs somewhere to run its
189 // continuation loop (see Http3StreamEvent). Bounded so a burst of
190 // concurrent large/slow responses spawns at most this many OS
191 // threads instead of one raw detached thread per response.
192 std::unique_ptr<netplus::ThreadPool> _h3StreamPool;
193
194 // Returns true if the stream was handed off to the background
195 // offload pool instead of being finished synchronously — callers
196 // must treat that as "progress" for reprocess-loop purposes even
197 // though it leaves `out` unchanged, since the frame was still
198 // consumed from RecvData either way.
199 bool _dispatchH2Stream(HttpRequest &cureq, std::string &out,
200 uint32_t sid,
201 const std::vector<hpack::HeaderField> &decoded,
202 const std::string &reqBody,
203 const int tid, ULONG_PTR args);
204 // Runs RequestEvent(cureq,...) for a fully-parsed HTTP/1.x request, offloading to
205 // _h1DispatchPool when shouldOffloadH1Dispatch() opts in. consumeBodyBytes is how many
206 // already-fully-buffered request body bytes to erase from RecvData once RequestEvent
207 // has returned (0 for GET/DELETE/OPTIONS/HEAD, which never have one) -- mirrors
208 // exactly what each REQUESTHANDLING case in RequestEvent(netplus::con&,...) used to do
209 // inline before this existed.
210 //
211 // Unlike _dispatchH2Stream (which hands a throwaway per-stream tempreq to the pool,
212 // since H2 multiplexes many streams per connection), H1's cureq *is* the connection --
213 // there is no separate object to hand off while leaving the epoll worker free to keep
214 // servicing the same fd. Offloading therefore round-trips through
215 // netplus::detachConnection()/reattachConnection(): detach before submitting (so the
216 // fd leaves the epoll/kqueue interest set and no second dispatch can ever race the
217 // in-flight one), run RequestEvent + the body-erase + the response flush on the pool
218 // thread with the socket in blocking mode (HttpResponse::send() only ever appends to
219 // SendData -- something has to actually write it, and off the event loop nothing will
220 // do that later the way EPOLLOUT normally would), then reattach so keep-alive/
221 // pipelining resumes normally. A peer that stops reading mid-flush past the bounded
222 // timeout, or any exception, closes the connection instead of reattaching it
223 // half-sent.
224 //
225 // Always leaves cureq fully handled by the time this returns: either it ran
226 // (synchronously) right here, or it's been handed to the pool and the caller must not
227 // touch cureq again.
228 void _dispatchH1Request(HttpRequest &cureq, size_t consumeBodyBytes,
229 const int tid, ULONG_PTR args);
230 // The part of stream dispatch that must run on the connection's
231 // owning thread: extracts the plugin's :res-* response headers off
232 // an already-completed tempreq, HPACK-encodes them, and frames the
233 // response (or sets up activeStreams for a streaming response).
234 // Shared by both the synchronous path and the offload-completion
235 // path so they behave identically once RequestEvent has returned.
236 void _finishH2Dispatch(HttpRequest &cureq, std::string &out,
237 uint32_t sid,
238 std::unique_ptr<HttpRequest> tempreq,
239 const int tid, ULONG_PTR args);
240 // Factored out of the synchronous dispatch loop's immediate-flush
241 // step so the offload-completion path (which has no "loop" to fall
242 // through to) can reuse the exact same send behavior.
243 bool _flushSendDataNow(HttpRequest &cureq);
244 void _resumeH2Streams(HttpRequest &cureq, std::string &out,
245 const int tid, ULONG_PTR args);
246 void _reapStalledH2Streams(HttpRequest &cureq, std::string &out);
247 };
248
249 // netplus::quic leaves sendControlStreams() (RFC 9114 §6.2.1: control +
250 // QPACK encoder/decoder streams) as a no-op — that framing is HTTP/3
251 // application-layer knowledge, which belongs here alongside qpack.h/
252 // hpack.h rather than in the QUIC transport library. This override
253 // holds the real implementation.
254 class Http3QuicSocket : public netplus::quic {
255 public:
256 using netplus::quic::quic;
257 void sendControlStreams() override;
258 std::vector<uint8_t> buildAlpnExtension(const std::string& alpn) override;
259 std::string selectStreamProtocol(const std::string& proto) override;
260 std::shared_ptr<netplus::quic> createChild() const override;
261 private:
262 bool _ctrlStreamsSent = false;
263 };
264
265 class HttpD {
266 public:
267 HttpD(int argc, char** argv);
268 HttpD(const std::string &httpaddr, int port, int maxconnections, const std::string &sslcertpath, const std::string &sslkeypath, const std::string &sslpassword = "");
269 // SNI virtual hosting: certsByHostname is keyed by the SNI hostname each bundle should
270 // be presented for (unlike the single-cert ctor above, whose _certBundle ends up keyed
271 // by httpaddr) -- netplus::ssl/quic pick a bundle per-connection from this map by
272 // matching the ClientHello's requested hostname, see netplus::tls::cert_map.
273 HttpD(const std::string &httpaddr, int port, int maxconnections,
274 const std::map<std::string, netplus::ssl::CertificateBundle> &certsByHostname);
275 ~HttpD();
276 std::vector<netplus::socket*> getServerSockets();
277
278 // Reload SSL certificates from file(s). Updates all ssl/quic server sockets.
279 bool reloadCertificates(const std::string &certpath, const std::string &keypath, const std::string &password = "");
280
281 // Reload just one SNI hostname's bundle (for a multi-hostname HttpD from the ctor
282 // above) without disturbing any other hostname's certificate.
283 bool reloadCertificate(const std::string &hostname, const std::string &certpath,
284 const std::string &keypath, const std::string &password = "");
285 protected:
286 void FileServer();
287 private:
288
289 bool _fileServer;
290 std::vector<std::unique_ptr<netplus::socket>> _ServerSockets;
291 std::map<std::string, netplus::ssl::CertificateBundle> _certBundle;
292 HTTPException _httpexception;
293 };
294};
Definition exception.h:43
Definition httpd.h:254
Definition httpd.h:265
Definition httpd.h:51
Definition http.h:489