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 HttpEvent(std::vector<netplus::socket*> serversocket,int timeout = 1000,
79 size_t h2OffloadThreads = 0, int idleTimeoutSeconds = 0,
80 size_t h1OffloadThreads = 0, size_t h2DispatchQueueMax = 0);
81
82 // Return true to have this stream's RequestEvent run on the H2
83 // offload thread pool instead of inline in the frame-processing
84 // loop. Called right after the per-stream request's headers have
85 // been parsed (so :path/:method are already available), before
86 // RequestEvent runs. Defaults to false — every route stays on the
87 // original synchronous path unless a subclass opts a specific
88 // route in. Has no effect if h2OffloadThreads is 0.
89 virtual bool shouldOffloadH2Dispatch(HttpRequest &tempreq, uint32_t streamId) const {
90 return false;
91 }
92
93 // Return true to have this HTTP/1.x request's RequestEvent run on the H1 offload
94 // thread pool instead of inline on the epoll/kqueue worker that read it. Called right
95 // after the request is fully parsed (headers and, for a body-bearing method, the
96 // complete body already buffered), before RequestEvent runs. Defaults to false --
97 // every route stays on the original synchronous path unless a subclass opts in. Has
98 // no effect if h1OffloadThreads is 0.
99 virtual bool shouldOffloadH1Dispatch(HttpRequest &cureq) const {
100 return false;
101 }
102
103 virtual void RequestEvent(HttpRequest &curreq,const int tid,ULONG_PTR args);
104 virtual void ResponseEvent(HttpRequest &curreq,const int tid,ULONG_PTR args);
105 virtual void ConnectEvent(HttpRequest &curreq,const int tid,ULONG_PTR args);
106 virtual void DisconnectEvent(HttpRequest &curreq,const int tid,ULONG_PTR args);
107
108 virtual bool Http2RequestEvent(netplus::con &curcon,
109 const int tid,
110 ULONG_PTR args,
111 const std::string &alpn,
112 const netplus::ssl::FramingCallback &frame_cb);
113 virtual void Http3StreamEvent(netplus::socket *sock,
114 uint64_t stream_id,
115 const std::vector<uint8_t> &data,
116 bool fin);
117
118 // Streaming body callbacks for H2/H3.
119 // Called when headers are complete for a body-bearing stream.
120 // Return true to handle body data via onH2DataChunk/onH3DataChunk
121 // instead of buffering the full body.
122 virtual bool onH2StreamHeaders(HttpRequest &conn, uint32_t streamId,
123 const std::vector<hpack::HeaderField> &headers);
124 virtual bool onH3StreamHeaders(netplus::socket *sock, uint64_t streamId,
125 const std::vector<qpack::HeaderField> &headers);
126
127 // Called for each body data chunk when streaming is enabled.
128 // endStream/fin: true on the last chunk.
129 virtual void onH2DataChunk(HttpRequest &conn, uint32_t streamId,
130 const char *data, size_t len, bool endStream,
131 std::string &h2out, const int tid, ULONG_PTR args);
132 virtual void onH3DataChunk(netplus::socket *sock, uint64_t streamId,
133 const char *data, size_t len, bool fin);
134
135 protected:
136 // Helpers for sending a complete response on an H2/H3 stream.
137 void sendH2StreamResponse(std::string &h2out, uint32_t streamId,
138 uint16_t status, const std::string &contentType,
139 const std::string &body);
140 void sendH3StreamResponse(netplus::socket *sock, uint64_t streamId,
141 uint16_t status, const std::string &contentType,
142 const std::string &body);
143 virtual void CreateConnection(std::shared_ptr<netplus::con> &res);
144
145 virtual void RequestEvent(netplus::con &curcon, const int tid, ULONG_PTR args);
146 virtual void ResponseEvent(netplus::con &curcon,const int tid,ULONG_PTR args);
147 virtual void ConnectEvent(netplus::con &curcon,const int tid,ULONG_PTR args);
148 virtual void DisconnectEvent(netplus::con &curcon,const int tid,ULONG_PTR args);
149
150 std::string _altSvcH3; // Alt-Svc value for HTTP/3 advertisement
151
152 // TLS session cache — shared across all accepted SSL connections
153 // to enable abbreviated TLS 1.2 handshakes on client reconnection.
154 netplus::TlsSessionCache _tlsSessionCache;
155 private:
156 // Per-stream state for HTTP/3: accumulates data and supports
157 // incremental H3 frame parsing for streaming body callbacks.
158 struct H3StreamState {
159 std::vector<uint8_t> data;
160 bool headersParsed = false;
161 bool streaming = false;
162 size_t parseOffset = 0;
163 };
164 std::mutex _h3BufferMutex;
165 std::map<uint64_t, H3StreamState> _h3StreamStates;
166 std::atomic<int> _h3NextTid{0};
167
168 // Non-null only when h2OffloadThreads > 0 was passed to the
169 // constructor. See shouldOffloadH2Dispatch / _dispatchH2Stream.
170 std::unique_ptr<netplus::ThreadPool> _h2DispatchPool;
171
172 // Non-null only when h1OffloadThreads > 0 was passed to the constructor. See
173 // shouldOffloadH1Dispatch / _dispatchH1Request.
174 std::unique_ptr<netplus::ThreadPool> _h1DispatchPool;
175
176 // Always constructed (unlike _h2DispatchPool, which is opt-in):
177 // every streaming H3 response needs somewhere to run its
178 // continuation loop (see Http3StreamEvent). Bounded so a burst of
179 // concurrent large/slow responses spawns at most this many OS
180 // threads instead of one raw detached thread per response.
181 std::unique_ptr<netplus::ThreadPool> _h3StreamPool;
182
183 // Returns true if the stream was handed off to the background
184 // offload pool instead of being finished synchronously — callers
185 // must treat that as "progress" for reprocess-loop purposes even
186 // though it leaves `out` unchanged, since the frame was still
187 // consumed from RecvData either way.
188 bool _dispatchH2Stream(HttpRequest &cureq, std::string &out,
189 uint32_t sid,
190 const std::vector<hpack::HeaderField> &decoded,
191 const std::string &reqBody,
192 const int tid, ULONG_PTR args);
193 // Runs RequestEvent(cureq,...) for a fully-parsed HTTP/1.x request, offloading to
194 // _h1DispatchPool when shouldOffloadH1Dispatch() opts in. consumeBodyBytes is how many
195 // already-fully-buffered request body bytes to erase from RecvData once RequestEvent
196 // has returned (0 for GET/DELETE/OPTIONS/HEAD, which never have one) -- mirrors
197 // exactly what each REQUESTHANDLING case in RequestEvent(netplus::con&,...) used to do
198 // inline before this existed.
199 //
200 // Unlike _dispatchH2Stream (which hands a throwaway per-stream tempreq to the pool,
201 // since H2 multiplexes many streams per connection), H1's cureq *is* the connection --
202 // there is no separate object to hand off while leaving the epoll worker free to keep
203 // servicing the same fd. Offloading therefore round-trips through
204 // netplus::detachConnection()/reattachConnection(): detach before submitting (so the
205 // fd leaves the epoll/kqueue interest set and no second dispatch can ever race the
206 // in-flight one), run RequestEvent + the body-erase + the response flush on the pool
207 // thread with the socket in blocking mode (HttpResponse::send() only ever appends to
208 // SendData -- something has to actually write it, and off the event loop nothing will
209 // do that later the way EPOLLOUT normally would), then reattach so keep-alive/
210 // pipelining resumes normally. A peer that stops reading mid-flush past the bounded
211 // timeout, or any exception, closes the connection instead of reattaching it
212 // half-sent.
213 //
214 // Always leaves cureq fully handled by the time this returns: either it ran
215 // (synchronously) right here, or it's been handed to the pool and the caller must not
216 // touch cureq again.
217 void _dispatchH1Request(HttpRequest &cureq, size_t consumeBodyBytes,
218 const int tid, ULONG_PTR args);
219 // The part of stream dispatch that must run on the connection's
220 // owning thread: extracts the plugin's :res-* response headers off
221 // an already-completed tempreq, HPACK-encodes them, and frames the
222 // response (or sets up activeStreams for a streaming response).
223 // Shared by both the synchronous path and the offload-completion
224 // path so they behave identically once RequestEvent has returned.
225 void _finishH2Dispatch(HttpRequest &cureq, std::string &out,
226 uint32_t sid,
227 std::unique_ptr<HttpRequest> tempreq,
228 const int tid, ULONG_PTR args);
229 // Factored out of the synchronous dispatch loop's immediate-flush
230 // step so the offload-completion path (which has no "loop" to fall
231 // through to) can reuse the exact same send behavior.
232 bool _flushSendDataNow(HttpRequest &cureq);
233 void _resumeH2Streams(HttpRequest &cureq, std::string &out,
234 const int tid, ULONG_PTR args);
235 void _reapStalledH2Streams(HttpRequest &cureq, std::string &out);
236 };
237
238 // netplus::quic leaves sendControlStreams() (RFC 9114 §6.2.1: control +
239 // QPACK encoder/decoder streams) as a no-op — that framing is HTTP/3
240 // application-layer knowledge, which belongs here alongside qpack.h/
241 // hpack.h rather than in the QUIC transport library. This override
242 // holds the real implementation.
243 class Http3QuicSocket : public netplus::quic {
244 public:
245 using netplus::quic::quic;
246 void sendControlStreams() override;
247 std::vector<uint8_t> buildAlpnExtension(const std::string& alpn) override;
248 std::string selectStreamProtocol(const std::string& proto) override;
249 std::shared_ptr<netplus::quic> createChild() const override;
250 private:
251 bool _ctrlStreamsSent = false;
252 };
253
254 class HttpD {
255 public:
256 HttpD(int argc, char** argv);
257 HttpD(const std::string &httpaddr, int port, int maxconnections, const std::string &sslcertpath, const std::string &sslkeypath, const std::string &sslpassword = "");
258 // SNI virtual hosting: certsByHostname is keyed by the SNI hostname each bundle should
259 // be presented for (unlike the single-cert ctor above, whose _certBundle ends up keyed
260 // by httpaddr) -- netplus::ssl/quic pick a bundle per-connection from this map by
261 // matching the ClientHello's requested hostname, see netplus::tls::cert_map.
262 HttpD(const std::string &httpaddr, int port, int maxconnections,
263 const std::map<std::string, netplus::ssl::CertificateBundle> &certsByHostname);
264 ~HttpD();
265 std::vector<netplus::socket*> getServerSockets();
266
267 // Reload SSL certificates from file(s). Updates all ssl/quic server sockets.
268 bool reloadCertificates(const std::string &certpath, const std::string &keypath, const std::string &password = "");
269
270 // Reload just one SNI hostname's bundle (for a multi-hostname HttpD from the ctor
271 // above) without disturbing any other hostname's certificate.
272 bool reloadCertificate(const std::string &hostname, const std::string &certpath,
273 const std::string &keypath, const std::string &password = "");
274 protected:
275 void FileServer();
276 private:
277
278 bool _fileServer;
279 std::vector<std::unique_ptr<netplus::socket>> _ServerSockets;
280 std::map<std::string, netplus::ssl::CertificateBundle> _certBundle;
281 HTTPException _httpexception;
282 };
283};
Definition exception.h:43
Definition httpd.h:243
Definition httpd.h:254
Definition httpd.h:51
Definition http.h:470