-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathCorHttp.h
More file actions
288 lines (234 loc) · 10.5 KB
/
Copy pathCorHttp.h
File metadata and controls
288 lines (234 loc) · 10.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
#ifndef CORHTTP_CORHTTP_H_
#define CORHTTP_CORHTTP_H_
//
// FILE CorHttp.h
//
// AUTHOR Ken Zangelin
//
// Copyright 2026 Seamware
// SPDX-License-Identifier: Apache-2.0
//
// corHttp - an HTTP/1.1 server, and nothing else.
//
// It accepts connections, parses requests, and writes responses. It does not
// know what NGSI-LD is, it does not route, and it does not parse JSON: a
// request arrives at one callback with its method, path, query, headers and
// body, and the caller decides everything from there. Routing belongs to the
// layer that owns the service table, and putting it here would mean two of
// them.
//
// ZERO COPY is the property to preserve when changing anything below. The
// method, path, query, header keys and values and the body are all POINTERS
// INTO THE CONNECTION'S READ BUFFER, NUL-terminated in place by the parser.
// Nothing is duplicated, and nothing survives the callback returning - a caller
// that needs a value afterwards has to copy it.
//
#include <stdbool.h> // bool
#include <stdint.h> // uint64_t
#include "kalloc/KAlloc.h" // KAlloc
// -----------------------------------------------------------------------------
//
// Limits
//
// Fixed arrays rather than growth: a request that needs more than this is not a
// request this broker wants to serve, and the alternative is an allocation on
// the hot path for a case that does not occur. Exceeding them is a 431/413, not
// a realloc.
//
#define COR_HTTP_MAX_HEADERS 64
#define COR_HTTP_MAX_URI_PARAMS 32
#define COR_HTTP_INITIAL_BUF_SIZE 8192
#define COR_HTTP_CONN_POOL_SIZE 1024
#define COR_HTTP_KEEPALIVE_TIMEOUT 30 // seconds
// -----------------------------------------------------------------------------
//
// CorHttpStatus - what an internal operation did
//
// Not HTTP status codes. CorHttpAgain is the one that carries the design: a
// partially-arrived request is the normal case on a non-blocking socket, not an
// error, and the parser says so rather than blocking or failing.
//
typedef enum CorHttpStatus
{
CorHttpOk = 0,
CorHttpAgain, // incomplete - read more and re-parse
CorHttpError,
CorHttpClosed,
CorHttpParseError,
CorHttpTooLarge,
CorHttpOutOfMemory
} CorHttpStatus;
// -----------------------------------------------------------------------------
//
// CorHttpSlice - a piece of the read buffer
//
// Both a pointer and a length, and the pointer is NUL-terminated too. The
// length is there so a caller can compare without strlen; the terminator is
// there so a caller can pass it straight to something that expects a C string.
//
typedef struct CorHttpSlice
{
char* s;
int len;
} CorHttpSlice;
// -----------------------------------------------------------------------------
//
// CorHttpKeyValue - a header or a URI parameter
//
typedef struct CorHttpKeyValue
{
CorHttpSlice key;
CorHttpSlice value;
} CorHttpKeyValue;
// -----------------------------------------------------------------------------
//
// CorHttpConn - one connection, and the request currently on it
//
// Pre-allocated in a pool and REUSED, so everything here is reset per request
// rather than freed. 'alloc' is a per-request pool with an inline buffer: it is
// bulk-reset when the response goes out, which is why nothing in the request
// path has to remember what it allocated.
//
typedef struct CorHttpConn
{
int fd;
int state; // internal; see corHttpInternal.h
// Read buffer. Grows on demand up to the server's maxRequestSize.
char* buf;
int bufSize;
int bufUsed;
// The parsed request - all of it pointing into buf.
CorHttpSlice method;
CorHttpSlice path;
CorHttpSlice query;
CorHttpSlice version;
CorHttpSlice body;
CorHttpKeyValue header[COR_HTTP_MAX_HEADERS];
int headers;
bool headersTruncated; // more than COR_HTTP_MAX_HEADERS arrived
CorHttpKeyValue uriParam[COR_HTTP_MAX_URI_PARAMS];
int uriParams;
bool uriParamsTruncated;
int contentLength; // -1 when the header was absent
bool bodyRefused; // Content-Length over the cap: headers delivered, body never read
// The response the callback fills in, via corHttpResponse*().
int statusCode;
CorHttpKeyValue respHeader[COR_HTTP_MAX_HEADERS];
int respHeaders;
char* respBody;
int respBodyLen;
// Outgoing bytes, for the partial writes a non-blocking socket will hand us.
char* writeBuf;
int writeLen;
int writePos;
KAlloc alloc;
char allocBuf[8 * 1024];
bool expectContinue; // client sent Expect: 100-continue and is waiting
bool continueSent; // ... and we have already answered it
bool keepAlive;
int requests; // served on this connection
uint64_t lastActivity; // CLOCK_MONOTONIC ms, for the idle sweep
void* userData; // the caller's, untouched here
struct CorHttpServer* serverP; // the loop this connection belongs to
struct CorHttpConn* next; // free-list linkage
} CorHttpConn;
// -----------------------------------------------------------------------------
//
// CorHttpRequestCb - the one callback
//
// Called once per complete request, on the thread that runs corHttpServe(). The
// callback fills the response through corHttpResponse*() and returns; returning
// without setting a status is a 500, because a request that produced no answer
// is a bug in the caller and not something to hide behind an empty 200.
//
typedef void (*CorHttpRequestCb)(CorHttpConn* connP);
// -----------------------------------------------------------------------------
//
// CorHttpDoneCb - the request is over and its bytes are on the wire
//
// Optional, and it exists for the one thing the request callback cannot do: a
// caller that hung per-request state on connP->userData has to free it, and the
// only safe moment is after the response has been WRITTEN - the response
// headers and body are borrowed from that state, not copied.
//
// Called exactly once per request that reached the request callback, on the
// loop thread: after the last byte goes out, or when the connection dies with a
// request still on it. Never for a request the engine answered by itself (a
// parse error, a 413), because those never reached the caller and there is
// nothing of the caller's to free.
//
typedef void (*CorHttpDoneCb)(CorHttpConn* connP);
// -----------------------------------------------------------------------------
//
// CorHttpServer
//
typedef struct CorHttpServer
{
int listenFd;
int epollFd;
unsigned short port;
CorHttpConn* connPool; // the whole pool, one allocation
CorHttpConn* freeConns; // free list head
int connPoolSize;
int activeConns;
CorHttpRequestCb requestCb;
CorHttpDoneCb doneCb; // optional; see CorHttpDoneCb
int keepAliveTimeout; // seconds; 0 disables keep-alive
//
// maxRequestSize - the largest request this server will hold, in bytes
//
// Enforced at the ANNOUNCEMENT where it can be: a Content-Length over this is
// refused before a byte of the body is read, and the request reaches the
// callback with bodyRefused set and no body, so the caller answers it in its
// own words rather than being handed a bare status by the engine. Only a
// client that lies about its length gets as far as the buffer limit.
//
int maxRequestSize; // bytes; 0 = no cap
bool running;
} CorHttpServer;
// -----------------------------------------------------------------------------
//
// Server lifecycle
//
extern CorHttpStatus corHttpInit(CorHttpServer* serverP, unsigned short port, int connPoolSize, CorHttpRequestCb cb);
extern CorHttpStatus corHttpServe(CorHttpServer* serverP); // runs until corHttpStop
extern void corHttpStop(CorHttpServer* serverP);
extern void corHttpRelease(CorHttpServer* serverP);
// -----------------------------------------------------------------------------
//
// Request accessors - convenience over the arrays above, not a second source
//
extern const char* corHttpHeader(CorHttpConn* connP, const char* key); // case-insensitive
extern const char* corHttpUriParam(CorHttpConn* connP, const char* key);
// -----------------------------------------------------------------------------
//
// corHttpSuspend / corHttpResume - hand a request to another thread and back
//
// The pair that lets the caller answer off this library's event-loop thread,
// which is what a broker that does I/O of its own during a request needs: a
// database round-trip or a forward to another context source cannot run on the
// loop without stopping every other connection for its duration.
//
// corHttpSuspend is called from INSIDE the callback and means "I am not
// answering now". The connection leaves the loop's care entirely - no epoll
// interest, no idle timeout, not reused - until it comes back.
//
// corHttpResume is the ONLY function here that may be called from another
// thread. It queues the connection and pokes the loop; it does not write to the
// socket, because the socket belongs to the loop and two threads writing one
// response is how two answers end up interleaved on one connection.
//
extern void corHttpSuspend(CorHttpConn* connP);
extern void corHttpResume(CorHttpConn* connP);
// -----------------------------------------------------------------------------
//
// Response
//
// corHttpResponseHeader and corHttpResponseBody do NOT copy: what is passed
// must outlive the callback, and the natural way to guarantee that is to
// allocate it from connP->alloc, which lives exactly that long.
//
extern void corHttpResponseStatus(CorHttpConn* connP, int statusCode);
extern void corHttpResponseHeader(CorHttpConn* connP, const char* key, const char* value);
extern void corHttpResponseBody(CorHttpConn* connP, char* body, int bodyLen);
#endif // CORHTTP_CORHTTP_H_