Line data Source code
1 : #define _GNU_SOURCE
2 : #include "fd_http_server_private.h"
3 :
4 : #include "../../third_party/picohttpparser/picohttpparser.h"
5 : #include "../../ballet/sha1/fd_sha1.h"
6 : #include "../../ballet/base64/fd_base64.h"
7 : #include "../../util/net/fd_ip4.h"
8 : #include "fd_http.h"
9 :
10 : #include <stdarg.h>
11 : #include <stdio.h>
12 : #include <errno.h>
13 : #include <unistd.h>
14 : #include <poll.h>
15 : #include <stdlib.h>
16 : #include <strings.h>
17 : #include <sys/socket.h>
18 : #include <sys/epoll.h>
19 : #include <netinet/in.h>
20 : #include <netinet/tcp.h>
21 :
22 132 : #define FD_HTTP_ZSTD_COMPRESSION_LEVEL 3
23 : /* levels 4-15 are compiled out of libfd_zstd (third_party/zstd/Local.mk) */
24 : FD_STATIC_ASSERT( FD_HTTP_ZSTD_COMPRESSION_LEVEL<=3 || FD_HTTP_ZSTD_COMPRESSION_LEVEL>=16, zstd_level );
25 : #define ZSTD_STATIC_LINKING_ONLY
26 : #include <zstd.h>
27 :
28 : #define POOL_NAME ws_conn_pool
29 132 : #define POOL_T struct fd_http_server_ws_connection
30 : #define POOL_IDX_T ushort
31 69 : #define POOL_NEXT parent
32 : #include "../../util/tmpl/fd_pool.c"
33 :
34 : #define POOL_NAME conn_pool
35 132 : #define POOL_T struct fd_http_server_connection
36 : #define POOL_IDX_T ushort
37 300 : #define POOL_NEXT parent
38 : #include "../../util/tmpl/fd_pool.c"
39 :
40 : #define TREAP_NAME ws_conn_treap
41 : #define TREAP_T struct fd_http_server_ws_connection
42 : #define TREAP_QUERY_T void * /* We don't use query ... */
43 : #define TREAP_CMP(q,e) (__extension__({ (void)(q); (void)(e); -1; })) /* which means we don't need to give a real
44 : implementation to cmp either */
45 60 : #define TREAP_IDX_T ushort
46 : #define TREAP_OPTIMIZE_ITERATION 1
47 0 : #define TREAP_LT(e0,e1) ((e0)->send_frames[ (e0)->send_frame_idx ].off<(e1)->send_frames[ (e1)->send_frame_idx ].off)
48 :
49 : #include "../../util/tmpl/fd_treap.c"
50 :
51 : #define TREAP_NAME conn_treap
52 : #define TREAP_T struct fd_http_server_connection
53 : #define TREAP_QUERY_T void * /* We don't use query ... */
54 : #define TREAP_CMP(q,e) (__extension__({ (void)(q); (void)(e); -1; })) /* which means we don't need to give a real
55 : implementation to cmp either */
56 1530 : #define TREAP_IDX_T ushort
57 : #define TREAP_OPTIMIZE_ITERATION 1
58 0 : #define TREAP_LT(e0,e1) ((e0)->response._body_off<(e1)->response._body_off)
59 :
60 : #include "../../util/tmpl/fd_treap.c"
61 :
62 : #define FD_HTTP_SERVER_DEBUG 0
63 :
64 : FD_FN_CONST char const *
65 0 : fd_http_server_connection_close_reason_str( int reason ) {
66 0 : switch( reason ) {
67 0 : case FD_HTTP_SERVER_CONNECTION_CLOSE_OK: return "OK-Connection was closed normally";
68 0 : case FD_HTTP_SERVER_CONNECTION_CLOSE_EVICTED: return "EVICTED-Connection was evicted to make room for a new one";
69 0 : case FD_HTTP_SERVER_CONNECTION_CLOSE_TOO_SLOW: return "TOO_SLOW-Client was too slow and did not read the response in time";
70 0 : case FD_HTTP_SERVER_CONNECTION_CLOSE_EXPECTED_EOF: return "EXPECTED_EOF-Client continued to send data when we expected no more";
71 0 : case FD_HTTP_SERVER_CONNECTION_CLOSE_PEER_RESET: return "PEER_RESET-Connection was reset by peer";
72 0 : case FD_HTTP_SERVER_CONNECTION_CLOSE_LARGE_REQUEST: return "LARGE_REQUEST-Request body was too large";
73 0 : case FD_HTTP_SERVER_CONNECTION_CLOSE_BAD_REQUEST: return "BAD_REQUEST-Request was malformed";
74 0 : case FD_HTTP_SERVER_CONNECTION_CLOSE_MISSING_CONTENT_LENGTH_HEADER: return "MISSING_CONTENT_LENGTH_HEADER-Missing Content-Length header field";
75 0 : case FD_HTTP_SERVER_CONNECTION_CLOSE_UNKNOWN_METHOD: return "UNKNOWN_METHOD-Request method was not recognized";
76 0 : case FD_HTTP_SERVER_CONNECTION_CLOSE_PATH_TOO_LONG: return "PATH_TOO_LONG-Request path was too long";
77 0 : case FD_HTTP_SERVER_CONNECTION_CLOSE_WS_BAD_KEY: return "WS_BAD_KEY-Malformed Sec-WebSocket-Key header field";
78 0 : case FD_HTTP_SERVER_CONNECTION_CLOSE_WS_UNEXPECTED_VERSION: return "WS_UNEXPECTED_VERSION-Unexpected Sec-Websocket-Version field";
79 0 : case FD_HTTP_SERVER_CONNECTION_CLOSE_WS_MISSING_KEY_HEADER: return "WS_MISSING_KEY_HEADER-Missing Sec-WebSocket-Key header field";
80 0 : case FD_HTTP_SERVER_CONNECTION_CLOSE_WS_MISSING_VERSION_HEADER: return "WS_MISSING_VERSION_HEADER-Missing Sec-WebSocket-Version header field";
81 0 : case FD_HTTP_SERVER_CONNECTION_CLOSE_WS_BAD_MASK: return "WS_BAD_MASK-Got frame from client without mask flag set";
82 0 : case FD_HTTP_SERVER_CONNECTION_CLOSE_WS_UNKNOWN_OPCODE: return "WS_UNKNOWN_OPCODE-Unknown opcode in websocket frame";
83 0 : case FD_HTTP_SERVER_CONNECTION_CLOSE_WS_OVERSIZE_FRAME: return "WS_OVERSIZE_FRAME-Websocket frame was too large";
84 0 : case FD_HTTP_SERVER_CONNECTION_CLOSE_WS_CLIENT_TOO_SLOW: return "WS_CLIENT_TOO_SLOW-Client was too slow to keep up with sender";
85 0 : case FD_HTTP_SERVER_CONNECTION_CLOSE_WS_MISSING_UPGRADE: return "WS_MISSING_UPGRADE-Missing Upgrade header field";
86 0 : case FD_HTTP_SERVER_CONNECTION_CLOSE_WS_EXPECTED_CONT_OPCODE: return "WS_EXPECTED_CONT_OPCODE-Expected continuation opcode in websocket frame";
87 0 : case FD_HTTP_SERVER_CONNECTION_CLOSE_WS_EXPECTED_TEXT_OPCODE: return "WS_EXPECTED_TEXT_OPCODE-Expected text opcode in websocket frame";
88 0 : case FD_HTTP_SERVER_CONNECTION_CLOSE_WS_CONTROL_FRAME_TOO_LARGE: return "WS_CONTROL_FRAME_TOO_LARGE-Websocket control frame was too large";
89 0 : case FD_HTTP_SERVER_CONNECTION_CLOSE_UNSUPPORTED_TRANSFER_ENCODING: return "UNSUPPORTED_TRANSFER_ENCODING-Transfer-Encoding is not supported";
90 0 : default: break;
91 0 : }
92 :
93 0 : return "unknown";
94 0 : }
95 :
96 : FD_FN_CONST char const *
97 0 : fd_http_server_method_str( uchar method ) {
98 0 : switch( method ) {
99 0 : case FD_HTTP_SERVER_METHOD_GET: return "GET";
100 0 : case FD_HTTP_SERVER_METHOD_POST: return "POST";
101 0 : case FD_HTTP_SERVER_METHOD_PUT: return "PUT";
102 0 : default: break;
103 0 : }
104 :
105 0 : return "unknown";
106 0 : }
107 :
108 : FD_FN_CONST ulong
109 342 : fd_http_server_align( void ) {
110 342 : return FD_HTTP_SERVER_ALIGN;
111 342 : }
112 :
113 : FD_FN_CONST ulong
114 69 : fd_http_server_footprint( fd_http_server_params_t params ) {
115 69 : ulong l = FD_LAYOUT_INIT;
116 69 : l = FD_LAYOUT_APPEND( l, FD_HTTP_SERVER_ALIGN, sizeof( fd_http_server_t ) );
117 69 : l = FD_LAYOUT_APPEND( l, conn_pool_align(), conn_pool_footprint( params.max_connection_cnt ) );
118 69 : l = FD_LAYOUT_APPEND( l, ws_conn_pool_align(), ws_conn_pool_footprint( params.max_ws_connection_cnt ) );
119 69 : l = FD_LAYOUT_APPEND( l, conn_treap_align(), conn_treap_footprint( params.max_connection_cnt ) );
120 69 : l = FD_LAYOUT_APPEND( l, ws_conn_treap_align(), ws_conn_treap_footprint( params.max_ws_connection_cnt ) );
121 69 : l = FD_LAYOUT_APPEND( l, alignof( struct pollfd ), (params.max_connection_cnt+params.max_ws_connection_cnt+1UL)*sizeof( struct pollfd ) );
122 69 : l = FD_LAYOUT_APPEND( l, 1UL, params.max_request_len*params.max_connection_cnt );
123 69 : l = FD_LAYOUT_APPEND( l, 1UL, params.max_ws_recv_frame_len*params.max_ws_connection_cnt );
124 69 : l = FD_LAYOUT_APPEND( l, alignof( struct fd_http_server_ws_frame ), params.max_ws_send_frame_cnt*params.max_ws_connection_cnt*sizeof( struct fd_http_server_ws_frame ) );
125 69 : l = FD_LAYOUT_APPEND( l, 1UL, params.outgoing_buffer_sz );
126 69 : l = FD_LAYOUT_APPEND( l, 16UL, ZSTD_estimateCCtxSize( FD_HTTP_ZSTD_COMPRESSION_LEVEL ) );
127 69 : return FD_LAYOUT_FINI( l, fd_http_server_align() );
128 69 : }
129 :
130 : void *
131 : fd_http_server_new( void * shmem,
132 : fd_http_server_params_t params,
133 : fd_http_server_callbacks_t callbacks,
134 66 : void * callback_ctx ) {
135 66 : if( FD_UNLIKELY( !shmem ) ) {
136 0 : FD_LOG_WARNING(( "NULL shmem" ));
137 0 : return NULL;
138 0 : }
139 :
140 66 : if( FD_UNLIKELY( params.send_buffer_sz>(ulong)INT_MAX ) ) {
141 0 : FD_LOG_WARNING(( "oversize send_buffer_sz" ));
142 0 : return NULL;
143 0 : }
144 :
145 66 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shmem, fd_http_server_align() ) ) ) {
146 0 : FD_LOG_WARNING(( "misaligned shmem" ));
147 0 : return NULL;
148 0 : }
149 :
150 66 : if( FD_UNLIKELY( params.max_ws_connection_cnt && params.max_ws_recv_frame_len<params.max_request_len ) ) {
151 0 : FD_LOG_WARNING(( "max_ws_recv_frame_len<max_request_len" ));
152 0 : return NULL;
153 0 : }
154 :
155 66 : FD_SCRATCH_ALLOC_INIT( l, shmem );
156 66 : fd_http_server_t * http = FD_SCRATCH_ALLOC_APPEND( l, FD_HTTP_SERVER_ALIGN, sizeof(fd_http_server_t) );
157 66 : void * conn_pool = FD_SCRATCH_ALLOC_APPEND( l, conn_pool_align(), conn_pool_footprint( params.max_connection_cnt ) );
158 66 : void * ws_conn_pool = FD_SCRATCH_ALLOC_APPEND( l, ws_conn_pool_align(), ws_conn_pool_footprint( params.max_ws_connection_cnt ) );
159 66 : http->conn_treap = FD_SCRATCH_ALLOC_APPEND( l, conn_treap_align(), conn_treap_footprint( params.max_connection_cnt ) );
160 66 : http->ws_conn_treap = FD_SCRATCH_ALLOC_APPEND( l, ws_conn_treap_align(), ws_conn_treap_footprint( params.max_ws_connection_cnt ) );
161 66 : http->pollfds = FD_SCRATCH_ALLOC_APPEND( l, alignof(struct pollfd), (params.max_connection_cnt+params.max_ws_connection_cnt+1UL)*sizeof( struct pollfd ) );
162 66 : char * _request_bytes = FD_SCRATCH_ALLOC_APPEND( l, 1UL, params.max_request_len*params.max_connection_cnt );
163 66 : uchar * _ws_recv_bytes = FD_SCRATCH_ALLOC_APPEND( l, 1UL, params.max_ws_recv_frame_len*params.max_ws_connection_cnt );
164 66 : struct fd_http_server_ws_frame * _ws_send_frames = FD_SCRATCH_ALLOC_APPEND( l, alignof(struct fd_http_server_ws_frame), params.max_ws_send_frame_cnt*params.max_ws_connection_cnt*sizeof(struct fd_http_server_ws_frame) );
165 66 : http->oring = FD_SCRATCH_ALLOC_APPEND( l, 1UL, params.outgoing_buffer_sz );
166 66 : uchar * _zstd_ctx = FD_SCRATCH_ALLOC_APPEND( l, 16UL, ZSTD_estimateCCtxSize( FD_HTTP_ZSTD_COMPRESSION_LEVEL ) );
167 66 : http->oring_sz = params.outgoing_buffer_sz;
168 66 : http->stage_err = 0;
169 66 : http->stage_off = 0UL;
170 66 : http->stage_len = 0UL;
171 66 : http->stage_comp_len = 0UL;
172 :
173 66 : http->callbacks = callbacks;
174 66 : http->callback_ctx = callback_ctx;
175 66 : http->evict_conn_id = 0UL;
176 66 : http->evict_ws_conn_id = 0UL;
177 66 : http->epoll_fd = -1;
178 66 : http->max_conns = params.max_connection_cnt;
179 66 : http->max_ws_conns = params.max_ws_connection_cnt;
180 66 : http->max_request_len = params.max_request_len;
181 66 : http->max_ws_recv_frame_len = params.max_ws_recv_frame_len;
182 66 : http->max_ws_send_frame_cnt = params.max_ws_send_frame_cnt;
183 66 : http->send_buffer_sz = params.send_buffer_sz;
184 66 : http->compress_websocket = params.compress_websocket;
185 :
186 66 : http->zstd_ctx = ZSTD_initStaticCCtx( _zstd_ctx, ZSTD_estimateCCtxSize( FD_HTTP_ZSTD_COMPRESSION_LEVEL ) );
187 66 : FD_TEST( http->zstd_ctx );
188 66 : ulong err = ZSTD_CCtx_setParameter( http->zstd_ctx, 100, FD_HTTP_ZSTD_COMPRESSION_LEVEL );
189 66 : if( FD_UNLIKELY( ZSTD_isError( err ) ) )
190 0 : FD_LOG_ERR(( "ZSTD_CCtx_setParameter failed (%s)", ZSTD_getErrorName( err ) ) );
191 :
192 66 : http->conns = conn_pool_join( conn_pool_new( conn_pool, params.max_connection_cnt ) );
193 66 : conn_treap_join( conn_treap_new( http->conn_treap, params.max_connection_cnt ) );
194 :
195 66 : http->ws_conns = ws_conn_pool_join( ws_conn_pool_new( ws_conn_pool, params.max_ws_connection_cnt ) );
196 66 : ws_conn_treap_join( ws_conn_treap_new( http->ws_conn_treap, params.max_ws_connection_cnt ) );
197 :
198 147 : for( ulong i=0UL; i<params.max_connection_cnt; i++ ) {
199 81 : http->pollfds[ i ].fd = -1;
200 81 : http->pollfds[ i ].events = POLLIN;
201 81 : http->conns[ i ] = (struct fd_http_server_connection){
202 81 : .request_bytes = _request_bytes+i*params.max_request_len,
203 81 : .parent = http->conns[ i ].parent,
204 81 : };
205 81 : }
206 :
207 117 : for( ulong i=0UL; i<params.max_ws_connection_cnt; i++ ) {
208 51 : http->pollfds[ params.max_connection_cnt+i ].fd = -1;
209 51 : http->pollfds[ params.max_connection_cnt+i ].events = POLLIN;
210 51 : http->ws_conns[ i ] = (struct fd_http_server_ws_connection){
211 51 : .recv_bytes = _ws_recv_bytes+i*params.max_ws_recv_frame_len,
212 51 : .send_frames = _ws_send_frames+i*params.max_ws_send_frame_cnt,
213 51 : .parent = http->ws_conns[ i ].parent,
214 51 : };
215 51 : }
216 66 : conn_treap_seed ( http->conns, params.max_connection_cnt, params.treap_seed );
217 66 : ws_conn_treap_seed( http->ws_conns, params.max_ws_connection_cnt, params.treap_seed );
218 :
219 66 : http->pollfds[ params.max_connection_cnt+params.max_ws_connection_cnt ].fd = -1;
220 66 : http->pollfds[ params.max_connection_cnt+params.max_ws_connection_cnt ].events = POLLIN;
221 :
222 66 : memset( &http->metrics, 0, sizeof( http->metrics ) );
223 :
224 66 : FD_COMPILER_MFENCE();
225 66 : FD_VOLATILE( http->magic ) = FD_HTTP_SERVER_MAGIC;
226 66 : FD_COMPILER_MFENCE();
227 :
228 66 : return (void *)http;
229 66 : }
230 :
231 : fd_http_server_t *
232 66 : fd_http_server_join( void * shhttp ) {
233 :
234 66 : if( FD_UNLIKELY( !shhttp ) ) {
235 0 : FD_LOG_WARNING(( "NULL shhttp" ));
236 0 : return NULL;
237 0 : }
238 :
239 66 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shhttp, fd_http_server_align() ) ) ) {
240 0 : FD_LOG_WARNING(( "misaligned shhttp" ));
241 0 : return NULL;
242 0 : }
243 :
244 66 : fd_http_server_t * http = (fd_http_server_t *)shhttp;
245 :
246 66 : if( FD_UNLIKELY( http->magic!=FD_HTTP_SERVER_MAGIC ) ) {
247 0 : FD_LOG_WARNING(( "bad magic" ));
248 0 : return NULL;
249 0 : }
250 :
251 66 : return http;
252 66 : }
253 :
254 : void *
255 63 : fd_http_server_leave( fd_http_server_t * http ) {
256 :
257 63 : if( FD_UNLIKELY( !http ) ) {
258 0 : FD_LOG_WARNING(( "NULL http" ));
259 0 : return NULL;
260 0 : }
261 :
262 63 : return (void *)http;
263 63 : }
264 :
265 : void *
266 63 : fd_http_server_delete( void * shhttp ) {
267 :
268 63 : if( FD_UNLIKELY( !shhttp ) ) {
269 0 : FD_LOG_WARNING(( "NULL shhttp" ));
270 0 : return NULL;
271 0 : }
272 :
273 63 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shhttp, fd_http_server_align() ) ) ) {
274 0 : FD_LOG_WARNING(( "misaligned shhttp" ));
275 0 : return NULL;
276 0 : }
277 :
278 63 : fd_http_server_t * http = (fd_http_server_t *)shhttp;
279 :
280 63 : if( FD_UNLIKELY( http->magic!=FD_HTTP_SERVER_MAGIC ) ) {
281 0 : FD_LOG_WARNING(( "bad magic" ));
282 0 : return NULL;
283 0 : }
284 :
285 63 : FD_COMPILER_MFENCE();
286 63 : FD_VOLATILE( http->magic ) = 0UL;
287 63 : FD_COMPILER_MFENCE();
288 :
289 63 : return (void *)http;
290 63 : }
291 :
292 : int
293 168 : fd_http_server_fd( fd_http_server_t * http ) {
294 168 : return http->socket_fd;
295 168 : }
296 :
297 : fd_http_server_t *
298 : fd_http_server_listen( fd_http_server_t * http,
299 : int epoll_fd,
300 : uint address,
301 57 : ushort port ) {
302 57 : fd_ip6_addr_t addr6 = {0};
303 57 : fd_ip6_addr_ip4_mapped( addr6.addr, address );
304 57 : return fd_http_server_listen6( http, epoll_fd, &addr6, port );
305 57 : }
306 :
307 : fd_http_server_t *
308 : fd_http_server_listen6( fd_http_server_t * http,
309 : int epoll_fd,
310 : fd_ip6_addr_t const * address,
311 57 : ushort port ) {
312 57 : FD_TEST( -1!=epoll_fd );
313 57 : http->epoll_fd = epoll_fd;
314 :
315 : /* An IPv4 address is served by an AF_INET socket, anything else by a
316 : dual stack AF_INET6 socket */
317 57 : int ip4 = fd_ip6_addr_is_ip4_mapped( address->addr ) && !address->scope_id;
318 :
319 57 : int sockfd = socket( ip4 ? AF_INET : AF_INET6, SOCK_STREAM | SOCK_NONBLOCK, 0 );
320 57 : if( FD_UNLIKELY( -1==sockfd ) ) FD_LOG_ERR(( "socket failed (%i-%s)", errno, strerror( errno ) ));
321 :
322 57 : int optval = 1;
323 57 : if( FD_UNLIKELY( -1==setsockopt( sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof( optval ) ) ) )
324 0 : FD_LOG_ERR(( "setsockopt failed (%i-%s)", errno, strerror( errno ) ));
325 :
326 : /* Pipelines responses: without this Nagle holds each sub-MSS tail
327 : for an RTT waiting on the prior segment's ACK */
328 57 : if( FD_UNLIKELY( -1==setsockopt( sockfd, IPPROTO_TCP, TCP_NODELAY, &optval, sizeof( optval ) ) ) )
329 0 : FD_LOG_ERR(( "setsockopt(TCP_NODELAY) failed (%i-%s)", errno, strerror( errno ) ));
330 :
331 57 : if( FD_UNLIKELY( http->send_buffer_sz ) ) {
332 3 : int sndbuf = (int)http->send_buffer_sz;
333 3 : if( FD_UNLIKELY( -1==setsockopt( sockfd, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof( sndbuf ) ) ) )
334 0 : FD_LOG_ERR(( "setsockopt(SO_SNDBUF) failed (%i-%s)", errno, strerror( errno ) ));
335 3 : }
336 :
337 57 : union {
338 57 : struct sockaddr_in ip4;
339 57 : struct sockaddr_in6 ip6;
340 57 : } addr = {0};
341 57 : ulong addr_sz;
342 :
343 57 : if( ip4 ) {
344 57 : addr.ip4 = (struct sockaddr_in){
345 57 : .sin_family = AF_INET,
346 57 : .sin_port = fd_ushort_bswap( port ),
347 57 : .sin_addr.s_addr = fd_ip6_addr_to_ip4( address->addr ),
348 57 : };
349 57 : addr_sz = sizeof(struct sockaddr_in);
350 57 : } else {
351 : /* Accept IPv4 clients too (as IPv4-mapped peers) if the socket is
352 : bound to the wildcard address */
353 0 : int v6only = 0;
354 0 : if( FD_UNLIKELY( -1==setsockopt( sockfd, IPPROTO_IPV6, IPV6_V6ONLY, &v6only, sizeof( v6only ) ) ) )
355 0 : FD_LOG_ERR(( "setsockopt(IPV6_V6ONLY) failed (%i-%s)", errno, strerror( errno ) ));
356 :
357 0 : addr.ip6 = (struct sockaddr_in6){
358 0 : .sin6_family = AF_INET6,
359 0 : .sin6_port = fd_ushort_bswap( port ),
360 0 : .sin6_scope_id = address->scope_id,
361 0 : };
362 0 : memcpy( addr.ip6.sin6_addr.s6_addr, address->addr, 16UL );
363 0 : addr_sz = sizeof(struct sockaddr_in6);
364 0 : }
365 :
366 57 : if( FD_UNLIKELY( -1==bind( sockfd, fd_type_pun( &addr ), (uint)addr_sz ) ) ) {
367 0 : char addr_cstr[ FD_IP6_ADDR_CSTR_MAX ];
368 0 : FD_LOG_ERR(( "bind(%i,%s:%u) failed (%i-%s)",
369 0 : sockfd, fd_ip6_addr_cstr( addr_cstr, address ), port,
370 0 : errno, fd_io_strerror( errno ) ));
371 0 : }
372 57 : if( FD_UNLIKELY( -1==listen( sockfd, (int)http->max_conns ) ) ) FD_LOG_ERR(( "listen failed (%i-%s)", errno, fd_io_strerror( errno ) ));
373 :
374 57 : http->socket_fd = sockfd;
375 57 : http->pollfds[ http->max_conns+http->max_ws_conns ].fd = http->socket_fd;
376 57 : struct epoll_event ev = { .events = EPOLLIN, .data.u64 = http->max_conns+http->max_ws_conns };
377 57 : if( FD_UNLIKELY( -1==epoll_ctl( http->epoll_fd, EPOLL_CTL_ADD, sockfd, &ev ) ) ) FD_LOG_ERR(( "epoll_ctl(ADD) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
378 :
379 57 : return http;
380 57 : }
381 :
382 : int
383 : fd_http_server_etag_matches( char const * if_none_match,
384 135 : char const * etag ) {
385 135 : ulong etag_len = strlen( etag );
386 135 : char const * p = if_none_match;
387 138 : while( *p==' ' || *p=='\t' ) p++;
388 135 : if( *p=='*' ) {
389 9 : p++;
390 12 : while( *p==' ' || *p=='\t' ) p++;
391 9 : return !*p;
392 9 : }
393 126 : int matched = 0;
394 201 : while( *p ) {
395 132 : while( *p==' ' || *p=='\t' || *p==',' ) p++;
396 90 : if( !*p ) break;
397 87 : if( p[ 0 ]=='W' && p[ 1 ]=='/' ) p += 2;
398 87 : if( FD_UNLIKELY( *p!='"' ) ) return 0;
399 84 : char const * end = strchr( p+1, '"' );
400 84 : if( FD_UNLIKELY( !end ) ) return 0;
401 81 : ulong len = (ulong)(end-p)+1UL;
402 81 : if( len==etag_len && !memcmp( p, etag, len ) ) matched = 1;
403 81 : p = end+1;
404 87 : while( *p==' ' || *p=='\t' ) p++;
405 81 : if( FD_UNLIKELY( *p && *p!=',' ) ) return 0;
406 81 : }
407 114 : return matched;
408 126 : }
409 :
410 : int
411 : fd_http_server_accept_encoding_q( char const * accept_encoding,
412 1845 : char const * coding ) {
413 1845 : ulong coding_len = strlen( coding );
414 1845 : char const * p = accept_encoding;
415 3222 : while( *p ) {
416 3960 : while( *p==' ' || *p=='\t' || *p==',' ) p++;
417 2439 : if( !*p ) break;
418 2439 : char const * tok = p;
419 13269 : while( *p && *p!=',' && *p!=';' && *p!=' ' && *p!='\t' ) p++;
420 2439 : ulong tok_len = (ulong)(p-tok);
421 2439 : char const * end = strchr( p, ',' );
422 2439 : if( !end ) end = p+strlen( p );
423 2439 : if( tok_len==coding_len && !strncasecmp( tok, coding, coding_len ) ) {
424 1062 : char const * q = p;
425 1113 : while( q<end ) { /* ;name=value parameters */
426 618 : if( *q++!=';' ) continue;
427 576 : while( q<end && (*q==' ' || *q=='\t') ) q++;
428 573 : if( end-q<2L || (*q!='q' && *q!='Q') || q[ 1 ]!='=' ) continue;
429 567 : q += 2;
430 : /* qvalue = ( "0" [ "." 0*3DIGIT ] ) / ( "1" [ "." 0*3"0" ] ) */
431 567 : int weight = 0;
432 567 : if( q<end && *q=='1' ) { weight = 1000; q++; }
433 495 : else if( q<end && *q=='0' ) q++;
434 6 : else return 0;
435 591 : if( q<end && *q=='.' ) for( int scale=100; ++q<end && *q>='0' && *q<='9' && scale; scale/=10 ) weight += (*q-'0')*scale;
436 561 : return fd_int_min( weight, 1000 );
437 567 : }
438 495 : return 1000;
439 1062 : }
440 1377 : p = end;
441 1377 : }
442 783 : return 0;
443 1845 : }
444 :
445 : static void
446 : close_conn( fd_http_server_t * http,
447 : ulong conn_idx,
448 90 : int reason ) {
449 90 : FD_TEST( http->pollfds[ conn_idx ].fd!=-1 );
450 : #if FD_HTTP_SERVER_DEBUG
451 : FD_LOG_NOTICE(( "Closing connection %lu (fd=%d) (%d-%s)", conn_idx, http->pollfds[ conn_idx ].fd, reason, fd_http_server_connection_close_reason_str( reason ) ));
452 : #endif
453 :
454 90 : if( FD_UNLIKELY( -1==close( http->pollfds[ conn_idx ].fd ) ) ) FD_LOG_ERR(( "close failed (%i-%s)", errno, strerror( errno ) ));
455 :
456 90 : http->pollfds[ conn_idx ].fd = -1;
457 90 : http->pollfds[ conn_idx ].events = POLLIN;
458 90 : if( FD_LIKELY( conn_idx<http->max_conns ) ) {
459 87 : if( FD_LIKELY( http->callbacks.close ) ) http->callbacks.close( conn_idx, reason, http->callback_ctx );
460 87 : } else {
461 3 : if( FD_LIKELY( http->callbacks.ws_close ) ) http->callbacks.ws_close( conn_idx-http->max_conns, reason, http->callback_ctx );
462 3 : }
463 :
464 90 : if( FD_UNLIKELY( conn_idx<http->max_conns ) ) {
465 87 : struct fd_http_server_connection * conn = &http->conns[ conn_idx ];
466 87 : if( FD_LIKELY( (conn->state==FD_HTTP_SERVER_CONNECTION_STATE_WRITING_HEADER || conn->state==FD_HTTP_SERVER_CONNECTION_STATE_WRITING_BODY)
467 87 : && !conn->response.static_body && conn->response._body_len ) ) {
468 21 : conn_treap_ele_remove( http->conn_treap, conn, http->conns );
469 21 : }
470 87 : conn_pool_ele_release( http->conns, conn );
471 87 : } else {
472 3 : struct fd_http_server_ws_connection * ws_conn = &http->ws_conns[ conn_idx-http->max_conns ];
473 3 : if( FD_LIKELY( ws_conn->send_frame_cnt ) ) ws_conn_treap_ele_remove( http->ws_conn_treap, ws_conn, http->ws_conns );
474 3 : ws_conn_pool_ele_release( http->ws_conns, ws_conn );
475 3 : }
476 :
477 90 : if( FD_LIKELY( conn_idx<http->max_conns ) ) http->metrics.connection_cnt--;
478 3 : else http->metrics.ws_connection_cnt--;
479 90 : }
480 :
481 : static void
482 : epoll_conn_add( fd_http_server_t * http,
483 117 : ulong conn_idx ) {
484 117 : struct epoll_event ev = { .events = EPOLLIN, .data.u64 = conn_idx };
485 117 : if( FD_UNLIKELY( -1==epoll_ctl( http->epoll_fd, EPOLL_CTL_ADD, http->pollfds[ conn_idx ].fd, &ev ) ) )
486 0 : FD_LOG_ERR(( "epoll_ctl(ADD) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
487 117 : http->pollfds[ conn_idx ].events = POLLIN;
488 117 : }
489 :
490 : static void
491 : epoll_conn_arm( fd_http_server_t * http,
492 : ulong conn_idx,
493 267 : short events ) {
494 267 : if( FD_UNLIKELY( -1==http->pollfds[ conn_idx ].fd ) ) return;
495 267 : if( FD_LIKELY( http->pollfds[ conn_idx ].events==events ) ) return;
496 42 : struct epoll_event ev = {
497 42 : .events = ((events & POLLIN) ? EPOLLIN : 0U) | ((events & POLLOUT) ? EPOLLOUT : 0U),
498 42 : .data.u64 = conn_idx,
499 42 : };
500 42 : if( FD_UNLIKELY( -1==epoll_ctl( http->epoll_fd, EPOLL_CTL_MOD, http->pollfds[ conn_idx ].fd, &ev ) ) )
501 0 : FD_LOG_ERR(( "epoll_ctl(MOD) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
502 42 : http->pollfds[ conn_idx ].events = events;
503 42 : }
504 :
505 : static void
506 : epoll_conn_move( fd_http_server_t * http,
507 15 : ulong conn_idx ) {
508 15 : struct epoll_event ev = { .events = EPOLLIN, .data.u64 = conn_idx };
509 15 : if( FD_UNLIKELY( -1==epoll_ctl( http->epoll_fd, EPOLL_CTL_MOD, http->pollfds[ conn_idx ].fd, &ev ) ) )
510 0 : FD_LOG_ERR(( "epoll_ctl(MOD) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
511 15 : http->pollfds[ conn_idx ].events = POLLIN;
512 15 : }
513 :
514 : static void
515 : epoll_update_out( fd_http_server_t * http,
516 324 : ulong conn_idx ) {
517 324 : if( FD_UNLIKELY( conn_idx>=http->max_conns+http->max_ws_conns ) ) return;
518 324 : if( FD_UNLIKELY( -1==http->pollfds[ conn_idx ].fd ) ) return;
519 :
520 267 : short events;
521 267 : if( FD_LIKELY( conn_idx<http->max_conns ) ) {
522 165 : struct fd_http_server_connection * conn = &http->conns[ conn_idx ];
523 165 : if( FD_LIKELY( conn->state==FD_HTTP_SERVER_CONNECTION_STATE_READING ) ) events = POLLIN;
524 : /* While a response is being written, readable bytes are pipelined
525 : requests that cannot be serviced yet, except during a websocket
526 : upgrade, whose reads must keep surfacing an early close. */
527 108 : else events = (short)(POLLOUT | (conn->response.upgrade_websocket ? POLLIN : 0));
528 165 : } else {
529 102 : struct fd_http_server_ws_connection * conn = &http->ws_conns[ conn_idx-http->max_conns ];
530 102 : int pending = conn->send_frame_cnt || conn->pong_state!=FD_HTTP_SERVER_PONG_STATE_NONE;
531 102 : events = (short)(pending ? (POLLIN|POLLOUT) : POLLIN);
532 102 : }
533 267 : epoll_conn_arm( http, conn_idx, events );
534 267 : }
535 :
536 : void
537 : fd_http_server_close( fd_http_server_t * http,
538 : ulong conn_id,
539 6 : int reason ) {
540 6 : close_conn( http, conn_id, reason );
541 6 : }
542 :
543 : void
544 : fd_http_server_ws_close( fd_http_server_t * http,
545 : ulong ws_conn_id,
546 0 : int reason ) {
547 0 : close_conn( http, http->max_conns+ws_conn_id, reason );
548 0 : }
549 :
550 : /* These are the expected network errors which just mean the connection
551 : should be closed. Any errors from an accept(2), read(2), or send(2)
552 : that are not expected here will be considered fatal and terminate the
553 : server. */
554 :
555 : static inline int
556 3 : is_expected_network_error( int err ) {
557 3 : return
558 3 : err==ENETDOWN ||
559 3 : err==EPROTO ||
560 3 : err==ENOPROTOOPT ||
561 3 : err==EHOSTDOWN ||
562 3 : err==ENONET ||
563 3 : err==EHOSTUNREACH ||
564 3 : err==EOPNOTSUPP ||
565 3 : err==ENETUNREACH ||
566 3 : err==ETIMEDOUT ||
567 3 : err==ENETRESET ||
568 3 : err==ECONNABORTED ||
569 3 : err==ECONNRESET ||
570 3 : err==EPIPE ||
571 3 : err==EPERM || /* iptables */
572 3 : err==ENOMEM; /* net stack OOM */
573 3 : }
574 :
575 : static void
576 114 : accept_conns( fd_http_server_t * http ) {
577 231 : for(;;) {
578 231 : int fd = accept4( http->socket_fd, NULL, NULL, SOCK_NONBLOCK|SOCK_CLOEXEC );
579 :
580 231 : if( FD_UNLIKELY( -1==fd ) ) {
581 114 : if( FD_LIKELY( EAGAIN==errno ) ) break;
582 0 : else if( FD_LIKELY( is_expected_network_error( errno ) ) ) continue;
583 0 : else FD_LOG_ERR(( "accept failed (%i-%s)", errno, strerror( errno ) ));
584 114 : }
585 :
586 117 : if( FD_UNLIKELY( !conn_pool_free( http->conns ) ) ) {
587 0 : conn_treap_fwd_iter_t it = conn_treap_fwd_iter_init( http->conn_treap, http->conns );
588 0 : if( FD_LIKELY( !conn_treap_fwd_iter_done( it ) ) ) {
589 0 : ulong conn_id = conn_treap_fwd_iter_idx( it );
590 0 : close_conn( http, conn_id, FD_HTTP_SERVER_CONNECTION_CLOSE_EVICTED );
591 0 : } else {
592 : /* If nobody is slow to read, just evict round robin */
593 0 : close_conn( http, http->evict_conn_id, FD_HTTP_SERVER_CONNECTION_CLOSE_EVICTED );
594 0 : http->evict_conn_id = (http->evict_conn_id+1UL) % http->max_conns;
595 0 : }
596 0 : }
597 :
598 117 : ulong conn_id = conn_pool_idx_acquire( http->conns );
599 :
600 117 : http->pollfds[ conn_id ].fd = fd;
601 117 : http->pollfds[ conn_id ].events = POLLIN;
602 117 : epoll_conn_add( http, conn_id );
603 117 : http->conns[ conn_id ].state = FD_HTTP_SERVER_CONNECTION_STATE_READING;
604 117 : http->conns[ conn_id ].request_bytes_read = 0UL;
605 117 : http->conns[ conn_id ].request_bytes_off = 0UL;
606 117 : http->conns[ conn_id ].request_expected_len = 0UL;
607 117 : http->conns[ conn_id ].response_bytes_written = 0UL;
608 :
609 117 : if( FD_UNLIKELY( http->callbacks.open ) ) {
610 0 : http->callbacks.open( conn_id, fd, http->callback_ctx );
611 0 : }
612 :
613 117 : http->metrics.connection_cnt++;
614 : #if FD_HTTP_SERVER_DEBUG
615 : FD_LOG_NOTICE(( "Accepted connection %lu (fd=%d)", conn_id, fd ));
616 : #endif
617 117 : }
618 114 : }
619 :
620 : /* last_len is the request length at the previous parse that returned
621 : incomplete, or 0: picohttpparser then only scans the new bytes for
622 : the end of the headers (its slowloris countermeasure). */
623 :
624 : static void
625 : parse_conn_http( fd_http_server_t * http,
626 : ulong conn_idx,
627 252 : ulong last_len ) {
628 252 : struct fd_http_server_connection * conn = &http->conns[ conn_idx ];
629 :
630 252 : char const * method;
631 252 : ulong method_len;
632 252 : char const * path;
633 252 : ulong path_len;
634 252 : int minor_version;
635 252 : struct phr_header headers[ 32 ];
636 252 : ulong num_headers = 32UL;
637 252 : int result = phr_parse_request( conn->request_bytes+conn->request_bytes_off,
638 252 : conn->request_bytes_read-conn->request_bytes_off,
639 252 : &method, &method_len,
640 252 : &path, &path_len,
641 252 : &minor_version,
642 252 : headers, &num_headers,
643 252 : last_len );
644 252 : if( FD_UNLIKELY( -2==result ) ) return; /* Request still partial, wait for more data */
645 246 : else if( FD_UNLIKELY( -1==result ) ) {
646 0 : close_conn( http, conn_idx, FD_HTTP_SERVER_CONNECTION_CLOSE_BAD_REQUEST );
647 0 : return;
648 0 : }
649 :
650 246 : FD_TEST( result>0 && (ulong)result<=conn->request_bytes_read-conn->request_bytes_off );
651 :
652 246 : uchar method_enum = UCHAR_MAX;
653 246 : if( FD_LIKELY( method_len==3UL && !strncmp( method, "GET", method_len ) ) ) method_enum = FD_HTTP_SERVER_METHOD_GET;
654 15 : else if( FD_LIKELY( method_len==4UL && !strncmp( method, "POST", method_len ) ) ) method_enum = FD_HTTP_SERVER_METHOD_POST;
655 0 : else if( FD_LIKELY( method_len==7UL && !strncmp( method, "OPTIONS", method_len ) ) ) method_enum = FD_HTTP_SERVER_METHOD_OPTIONS;
656 0 : else if( FD_LIKELY( method_len==3UL && !strncmp( method, "PUT", method_len ) ) ) method_enum = FD_HTTP_SERVER_METHOD_PUT;
657 :
658 246 : if( FD_UNLIKELY( method_enum==UCHAR_MAX ) ) {
659 0 : close_conn( http, conn_idx, FD_HTTP_SERVER_CONNECTION_CLOSE_UNKNOWN_METHOD );
660 0 : return;
661 0 : }
662 :
663 : /* RFC 7230 s3.3.3 */
664 627 : for( ulong i=0UL; i<num_headers; i++ ) {
665 384 : if( FD_UNLIKELY( headers[ i ].name_len==17UL && !strncasecmp( headers[ i ].name, "Transfer-Encoding", 17UL ) ) ) {
666 3 : close_conn( http, conn_idx, FD_HTTP_SERVER_CONNECTION_CLOSE_UNSUPPORTED_TRANSFER_ENCODING );
667 3 : return;
668 3 : }
669 384 : }
670 :
671 243 : ulong content_len = 0UL;
672 243 : int found = 0;
673 615 : for( ulong i=0UL; i<num_headers; i++ ) {
674 378 : if( FD_LIKELY( headers[ i ].name_len==14UL && !strncasecmp( headers[ i ].name, "Content-Length", 14UL ) ) ) {
675 21 : ulong this_content_len = 0UL;
676 21 : int parse_err = fd_http_parse_content_len( headers[ i ].value, (ulong)headers[ i ].value_len, &this_content_len );
677 21 : if( FD_UNLIKELY( parse_err ) ) {
678 3 : close_conn( http, conn_idx, fd_int_if( parse_err==FD_HTTP_PARSE_CONTENT_LEN_OVERFLOW, FD_HTTP_SERVER_CONNECTION_CLOSE_LARGE_REQUEST, FD_HTTP_SERVER_CONNECTION_CLOSE_BAD_REQUEST ) );
679 3 : return;
680 3 : }
681 18 : if( FD_UNLIKELY( found && this_content_len!=content_len ) ) {
682 : /* RFC 7230 s3.3.3 rule 4: reject if duplicate Content-Length
683 : values differ */
684 3 : close_conn( http, conn_idx, FD_HTTP_SERVER_CONNECTION_CLOSE_BAD_REQUEST );
685 3 : return;
686 3 : }
687 15 : content_len = this_content_len;
688 15 : found = 1;
689 15 : }
690 378 : }
691 :
692 237 : if( FD_UNLIKELY( !found && (method_enum==FD_HTTP_SERVER_METHOD_POST || method_enum==FD_HTTP_SERVER_METHOD_PUT) ) ) {
693 0 : close_conn( http, conn_idx, FD_HTTP_SERVER_CONNECTION_CLOSE_MISSING_CONTENT_LENGTH_HEADER );
694 0 : return;
695 0 : }
696 :
697 237 : ulong total_len = (ulong)result+content_len;
698 :
699 237 : if( FD_UNLIKELY( total_len<content_len || total_len>http->max_request_len ) ) { /* Overflow */
700 0 : close_conn( http, conn_idx, FD_HTTP_SERVER_CONNECTION_CLOSE_LARGE_REQUEST );
701 0 : return;
702 0 : }
703 :
704 :
705 237 : if( FD_UNLIKELY( conn->request_bytes_read-conn->request_bytes_off<(ulong)result+content_len ) ) {
706 6 : conn->request_expected_len = (ulong)result+content_len; /* body pending, no need to reparse until it is all here */
707 6 : return;
708 6 : }
709 :
710 231 : char content_type_nul_terminated[ 128 ] = {0};
711 231 : char accept_encoding_nul_terminated[ 128 ] = {0};
712 231 : ulong accept_encoding_len = 0UL;
713 579 : for( ulong i=0UL; i<num_headers; i++ ) {
714 351 : if( FD_LIKELY( headers[ i ].name_len==12UL && !strncasecmp( headers[ i ].name, "Content-Type", 12UL ) ) ) {
715 9 : if( FD_UNLIKELY( headers[ i ].value_len>(sizeof(content_type_nul_terminated)-1UL) ) ) {
716 0 : close_conn( http, conn_idx, FD_HTTP_SERVER_CONNECTION_CLOSE_BAD_REQUEST );
717 0 : return;
718 0 : }
719 9 : memcpy( content_type_nul_terminated, headers[ i ].value, headers[ i ].value_len );
720 9 : content_type_nul_terminated[ headers[ i ].value_len ] = '\0';
721 342 : } else if( FD_LIKELY( headers[ i ].name_len==15UL && !strncasecmp( headers[ i ].name, "Accept-Encoding", 15UL ) ) ) {
722 : /* repeated list fields combine (RFC 9110 s5.3) */
723 15 : ulong sep_len = accept_encoding_len ? 2UL : 0UL;
724 15 : if( FD_UNLIKELY( accept_encoding_len+sep_len+headers[ i ].value_len>sizeof(accept_encoding_nul_terminated)-1UL ) ) {
725 3 : close_conn( http, conn_idx, FD_HTTP_SERVER_CONNECTION_CLOSE_BAD_REQUEST );
726 3 : return;
727 3 : }
728 12 : if( FD_LIKELY( sep_len ) ) { memcpy( accept_encoding_nul_terminated+accept_encoding_len, ", ", 2UL ); accept_encoding_len += 2UL; }
729 12 : memcpy( accept_encoding_nul_terminated+accept_encoding_len, headers[ i ].value, headers[ i ].value_len );
730 12 : accept_encoding_len += headers[ i ].value_len;
731 12 : }
732 351 : }
733 :
734 228 : char if_none_match_nul_terminated[ 128 ] = {0};
735 228 : ulong if_none_match_len = 0UL;
736 570 : for( ulong i=0UL; i<num_headers; i++ ) {
737 345 : if( FD_UNLIKELY( headers[ i ].name_len==13UL && !strncasecmp( headers[ i ].name, "If-None-Match", 13UL ) ) ) {
738 30 : ulong sep_len = if_none_match_len ? 2UL : 0UL;
739 30 : if( FD_UNLIKELY( if_none_match_len+sep_len+headers[ i ].value_len>sizeof(if_none_match_nul_terminated)-1UL ) ) {
740 3 : close_conn( http, conn_idx, FD_HTTP_SERVER_CONNECTION_CLOSE_BAD_REQUEST );
741 3 : return;
742 3 : }
743 27 : if( FD_LIKELY( sep_len ) ) { memcpy( if_none_match_nul_terminated+if_none_match_len, ", ", 2UL ); if_none_match_len += 2UL; }
744 27 : memcpy( if_none_match_nul_terminated+if_none_match_len, headers[ i ].value, headers[ i ].value_len );
745 27 : if_none_match_len += headers[ i ].value_len;
746 27 : }
747 345 : }
748 :
749 225 : char path_nul_terminated[ 128 ] = {0};
750 225 : if( FD_UNLIKELY( path_len>(sizeof( path_nul_terminated )-1UL) ) ) {
751 0 : close_conn( http, conn_idx, FD_HTTP_SERVER_CONNECTION_CLOSE_PATH_TOO_LONG );
752 0 : return;
753 0 : }
754 225 : memcpy( path_nul_terminated, path, path_len );
755 :
756 225 : char const * upgrade_key = NULL;
757 483 : for( ulong i=0UL; i<num_headers; i++ ) {
758 279 : if( FD_LIKELY( headers[ i ].name_len==7UL && !strncasecmp( headers[ i ].name, "Upgrade", 7UL ) && headers[ i ].value_len==9UL ) ) {
759 21 : upgrade_key = headers[ i ].value;
760 21 : break;
761 21 : }
762 279 : }
763 :
764 225 : conn->upgrade_websocket = 0;
765 225 : int compress_websocket = 0;
766 225 : if( FD_UNLIKELY( upgrade_key && !strncasecmp( upgrade_key, "websocket", 9UL ) ) ) {
767 21 : conn->request_bytes_len = conn->request_bytes_off+(ulong)result;
768 21 : conn->upgrade_websocket = 1;
769 :
770 126 : for( ulong i=0UL; i<num_headers; i++ ) {
771 105 : if( FD_LIKELY( headers[ i ].name_len==22UL && !strncasecmp( headers[ i ].name, "Sec-WebSocket-Protocol", 22UL ) &&
772 105 : headers[ i ].value_len==13UL && !strncmp( headers[ i ].value, "compress-zstd", 13UL ) ) ) {
773 0 : compress_websocket = 1;
774 0 : }
775 105 : }
776 :
777 21 : char const * sec_websocket_key = NULL;
778 84 : for( ulong i=0UL; i<num_headers; i++ ) {
779 84 : if( FD_LIKELY( headers[ i ].name_len==17UL && !strncasecmp( headers[ i ].name, "Sec-WebSocket-Key", 17UL ) ) ) {
780 21 : sec_websocket_key = headers[ i ].value;
781 21 : if( FD_UNLIKELY( headers[ i ].value_len!=24 ) ) {
782 0 : close_conn( http, conn_idx, FD_HTTP_SERVER_CONNECTION_CLOSE_WS_BAD_KEY );
783 0 : return;
784 0 : }
785 : /* RFC 6455 s4.2.1: Sec-WebSocket-Key must base64-decode to
786 : exactly 16 bytes. fd_base64_decode also validates the
787 : alphabet and padding rules. */
788 21 : uchar decoded_key[ FD_BASE64_DEC_SZ( 24UL ) ];
789 21 : if( FD_UNLIKELY( 16L!=fd_base64_decode( decoded_key, sec_websocket_key, 24UL ) ) ) {
790 3 : close_conn( http, conn_idx, FD_HTTP_SERVER_CONNECTION_CLOSE_WS_BAD_KEY );
791 3 : return;
792 3 : }
793 18 : break;
794 21 : }
795 84 : }
796 :
797 18 : char const * sec_websocket_version = NULL;
798 90 : for( ulong i=0UL; i<num_headers; i++ ) {
799 90 : if( FD_LIKELY( headers[ i ].name_len==21UL && !strncasecmp( headers[ i ].name, "Sec-Websocket-Version", 21UL ) ) ) {
800 18 : sec_websocket_version = headers[ i ].value;
801 18 : if( FD_UNLIKELY( headers[ i ].value_len!=2 || strncmp( sec_websocket_version, "13", 2UL ) ) ) {
802 0 : close_conn( http, conn_idx, FD_HTTP_SERVER_CONNECTION_CLOSE_WS_UNEXPECTED_VERSION );
803 0 : return;
804 0 : }
805 18 : break;
806 18 : }
807 90 : }
808 :
809 18 : if( FD_UNLIKELY( !sec_websocket_key ) ) {
810 0 : close_conn( http, conn_idx, FD_HTTP_SERVER_CONNECTION_CLOSE_WS_MISSING_KEY_HEADER );
811 0 : return;
812 0 : }
813 :
814 18 : if( FD_UNLIKELY( !sec_websocket_version ) ) {
815 0 : close_conn( http, conn_idx, FD_HTTP_SERVER_CONNECTION_CLOSE_WS_MISSING_VERSION_HEADER );
816 0 : return;
817 0 : }
818 :
819 18 : conn->sec_websocket_key = sec_websocket_key;
820 :
821 : /* RFC 6455 s4.2.2: the client must not send WebSocket frames until
822 : after it receives the 101 response. */
823 18 : if( FD_UNLIKELY( conn->request_bytes_read>conn->request_bytes_len ) ) {
824 3 : close_conn( http, conn_idx, FD_HTTP_SERVER_CONNECTION_CLOSE_BAD_REQUEST );
825 3 : return;
826 3 : }
827 18 : }
828 :
829 219 : int connection_close = 0;
830 531 : for( ulong i=0UL; i<num_headers; i++ ) {
831 312 : if( FD_UNLIKELY( headers[ i ].name_len==10UL && !strncasecmp( headers[ i ].name, "Connection", 10UL ) ) ) {
832 54 : char const * value = headers[ i ].value;
833 54 : ulong j = 0UL;
834 78 : while( j<headers[ i ].value_len ) {
835 72 : while( j<headers[ i ].value_len && (value[ j ]==',' || value[ j ]==' ' || value[ j ]=='\t') ) j++;
836 60 : ulong k = j;
837 432 : while( k<headers[ i ].value_len && value[ k ]!=',' && value[ k ]!=' ' && value[ k ]!='\t' ) k++;
838 60 : if( FD_UNLIKELY( k-j==5UL && !strncasecmp( value+j, "close", 5UL ) ) ) { connection_close = 1; break; }
839 24 : j = k;
840 24 : }
841 54 : }
842 312 : }
843 :
844 219 : conn->keep_alive = minor_version==1 && !connection_close && !conn->upgrade_websocket;
845 219 : conn->request_consumed = conn->request_bytes_off+(ulong)result+content_len;
846 219 : conn->request_expected_len = 0UL;
847 :
848 219 : fd_http_server_request_t request = {
849 219 : .connection_id = conn_idx,
850 :
851 219 : .method = method_enum,
852 219 : .path = path_nul_terminated,
853 219 : .path_raw = path,
854 219 : .path_len = path_len,
855 :
856 219 : .ctx = http->callback_ctx,
857 :
858 219 : .headers.content_type = content_type_nul_terminated,
859 219 : .headers.accept_encoding = accept_encoding_nul_terminated,
860 219 : .headers.if_none_match = if_none_match_nul_terminated,
861 219 : .headers.compress_websocket = compress_websocket,
862 219 : .headers.upgrade_websocket = conn->upgrade_websocket,
863 219 : };
864 :
865 219 : switch( method_enum ) {
866 3 : case FD_HTTP_SERVER_METHOD_POST:
867 3 : case FD_HTTP_SERVER_METHOD_PUT: {
868 3 : request.post.body = (uchar*)conn->request_bytes+conn->request_bytes_off+result;
869 3 : request.post.body_len = content_len;
870 3 : } break;
871 216 : default: break;
872 219 : }
873 :
874 219 : fd_http_server_response_t response = http->callbacks.request( &request );
875 219 : if( FD_UNLIKELY( http->pollfds[ conn_idx ].fd==-1 ) ) return; /* Connection was closed by callback */
876 213 : conn->response = response;
877 213 : conn->state = FD_HTTP_SERVER_CONNECTION_STATE_WRITING_HEADER;
878 :
879 213 : switch( conn->response.status ) {
880 180 : case 200: case 204: case 400: break;
881 33 : default:
882 33 : conn->response.static_body_len = 0UL;
883 33 : conn->response._body_len = 0UL;
884 33 : break;
885 213 : }
886 :
887 : #if FD_HTTP_SERVER_DEBUG
888 : FD_LOG_NOTICE(( "Received %s request \"%s\" from %lu (fd=%d) response code %lu", fd_http_server_method_str( method_enum ), path_nul_terminated, conn_idx, http->pollfds[ conn_idx ].fd, conn->response.status ));
889 : #endif
890 :
891 213 : if( FD_LIKELY( !conn->response.static_body && conn->response._body_len ) ) conn_treap_ele_insert( http->conn_treap, conn, http->conns );
892 213 : }
893 :
894 : static void
895 : read_conn_http( fd_http_server_t * http,
896 159 : ulong conn_idx ) {
897 159 : struct fd_http_server_connection * conn = &http->conns[ conn_idx ];
898 :
899 159 : if( FD_UNLIKELY( conn->state!=FD_HTTP_SERVER_CONNECTION_STATE_READING ) ) {
900 0 : close_conn( http, conn_idx, FD_HTTP_SERVER_CONNECTION_CLOSE_EXPECTED_EOF );
901 0 : return;
902 0 : }
903 :
904 159 : if( FD_UNLIKELY( conn->request_bytes_off ) ) {
905 33 : memmove( conn->request_bytes, conn->request_bytes+conn->request_bytes_off, conn->request_bytes_read-conn->request_bytes_off );
906 33 : conn->request_bytes_read -= conn->request_bytes_off;
907 33 : conn->request_bytes_off = 0UL;
908 33 : }
909 :
910 159 : long sz = read( http->pollfds[ conn_idx ].fd, conn->request_bytes+conn->request_bytes_read, http->max_request_len-conn->request_bytes_read );
911 159 : if( FD_UNLIKELY( -1==sz && errno==EAGAIN ) ) return; /* No data to read, continue. */
912 159 : else if( FD_UNLIKELY( !sz || (-1==sz && is_expected_network_error( errno ) ) ) ) {
913 21 : close_conn( http, conn_idx, FD_HTTP_SERVER_CONNECTION_CLOSE_PEER_RESET );
914 21 : return;
915 21 : }
916 138 : else if( FD_UNLIKELY( -1==sz ) ) FD_LOG_ERR(( "read failed (%i-%s)", errno, strerror( errno ) )); /* Unexpected programmer error, abort */
917 :
918 : /* New data was read... process it */
919 138 : http->metrics.bytes_read += (ulong)sz;
920 138 : conn->request_bytes_read += (ulong)sz;
921 :
922 : /* Once the headers parsed, a non-zero last_len would make
923 : picohttpparser scan the body for their terminator and never finish */
924 138 : if( conn->request_expected_len ) {
925 6 : if( conn->request_bytes_read<conn->request_expected_len ) return;
926 6 : parse_conn_http( http, conn_idx, 0UL );
927 132 : } else {
928 132 : parse_conn_http( http, conn_idx, conn->request_bytes_read-(ulong)sz );
929 132 : }
930 :
931 : /* A full buffer may hold multiple valid pipelined requests, so it is
932 : only oversized if parsing left it full and still incomplete */
933 138 : if( FD_UNLIKELY( http->pollfds[ conn_idx ].fd!=-1 &&
934 138 : conn->state==FD_HTTP_SERVER_CONNECTION_STATE_READING &&
935 138 : conn->request_bytes_read==http->max_request_len ) ) {
936 0 : close_conn( http, conn_idx, FD_HTTP_SERVER_CONNECTION_CLOSE_LARGE_REQUEST );
937 0 : }
938 138 : }
939 :
940 : static void
941 : read_conn_ws( fd_http_server_t * http,
942 90 : ulong conn_idx ) {
943 90 : struct fd_http_server_ws_connection * conn = &http->ws_conns[ conn_idx-http->max_conns ];
944 :
945 90 : long sz = read( http->pollfds[ conn_idx ].fd, conn->recv_bytes+conn->recv_bytes_parsed+conn->recv_bytes_read, http->max_ws_recv_frame_len-conn->recv_bytes_parsed-conn->recv_bytes_read );
946 90 : if( FD_UNLIKELY( -1==sz && errno==EAGAIN ) ) return; /* No data to read, continue. */
947 90 : else if( FD_UNLIKELY( !sz || (-1==sz && is_expected_network_error( errno ) ) ) ) {
948 0 : close_conn( http, conn_idx, FD_HTTP_SERVER_CONNECTION_CLOSE_PEER_RESET );
949 0 : return;
950 0 : }
951 90 : else if( FD_UNLIKELY( -1==sz ) ) FD_LOG_ERR(( "read failed (%i-%s)", errno, strerror( errno ) )); /* Unexpected programmer error, abort */
952 :
953 : /* New data was read... process it */
954 90 : conn->recv_bytes_read += (ulong)sz;
955 90 : http->metrics.bytes_read += (ulong)sz;
956 108 : again:
957 108 : if( FD_UNLIKELY( conn->recv_bytes_read<2UL ) ) return; /* Need at least 2 bytes to determine frame length */
958 :
959 99 : int is_mask_set = conn->recv_bytes[ conn->recv_bytes_parsed+1UL ] & 0x80;
960 99 : if( FD_UNLIKELY( !is_mask_set ) ) {
961 0 : close_conn( http, conn_idx, FD_HTTP_SERVER_CONNECTION_CLOSE_WS_BAD_MASK );
962 0 : return;
963 0 : }
964 :
965 99 : int opcode = conn->recv_bytes[ conn->recv_bytes_parsed ] & 0x0F;
966 99 : if( FD_UNLIKELY( opcode!=0x0 && opcode!=0x1 && opcode!=0x2 && opcode!=0x8 && opcode!=0x9 && opcode!=0xA ) ) {
967 0 : close_conn( http, conn_idx, FD_HTTP_SERVER_CONNECTION_CLOSE_WS_UNKNOWN_OPCODE );
968 0 : return;
969 0 : }
970 :
971 99 : ulong payload_len = conn->recv_bytes[ conn->recv_bytes_parsed+1UL ] & 0x7F;
972 99 : if( FD_UNLIKELY( (payload_len==126 || payload_len==127) && (opcode==0x8 || opcode==0x9 || opcode==0xA) ) ) {
973 0 : close_conn( http, conn_idx, FD_HTTP_SERVER_CONNECTION_CLOSE_WS_CONTROL_FRAME_TOO_LARGE );
974 0 : return;
975 0 : }
976 :
977 99 : ulong len_bytes;
978 99 : if( FD_LIKELY( payload_len<126UL ) ) {
979 99 : len_bytes = 1UL;
980 99 : } else if( FD_LIKELY( payload_len==126 ) ) {
981 0 : if( FD_UNLIKELY( conn->recv_bytes_read<4UL ) ) return; /* Need at least 4 bytes to determine frame length */
982 0 : payload_len = ((ulong)conn->recv_bytes[ conn->recv_bytes_parsed+2UL ]<<8UL) | (ulong)conn->recv_bytes[ conn->recv_bytes_parsed+3UL ];
983 0 : len_bytes = 3UL;
984 0 : } else if( FD_LIKELY( payload_len==127 ) ) {
985 0 : if( FD_UNLIKELY( conn->recv_bytes_read<10UL ) ) return; /* Need at least 10 bytes to determine frame length */
986 0 : payload_len = ((ulong)conn->recv_bytes[ conn->recv_bytes_parsed+2UL ]<<56UL) | ((ulong)conn->recv_bytes[ conn->recv_bytes_parsed+3UL ]<<48UL) | ((ulong)conn->recv_bytes[ conn->recv_bytes_parsed+4UL ]<<40UL) | ((ulong)conn->recv_bytes[ conn->recv_bytes_parsed+5UL ]<<32UL) |
987 0 : ((ulong)conn->recv_bytes[ conn->recv_bytes_parsed+6UL ]<<24UL) | ((ulong)conn->recv_bytes[ conn->recv_bytes_parsed+7UL ]<<16UL) | ((ulong)conn->recv_bytes[ conn->recv_bytes_parsed+8UL ]<<8UL ) | (ulong)conn->recv_bytes[ conn->recv_bytes_parsed+9UL ];
988 0 : len_bytes = 9UL;
989 0 : } else {
990 0 : FD_LOG_ERR(( "unexpected payload_len %lu", payload_len )); /* Silence clang sanitizer, not possible */
991 0 : }
992 :
993 99 : ulong header_len = 1UL+len_bytes+4UL;
994 99 : ulong frame_len = header_len+payload_len;
995 99 : if( FD_UNLIKELY( frame_len<header_len ) ) { /* Overflow */
996 0 : close_conn( http, conn_idx, FD_HTTP_SERVER_CONNECTION_CLOSE_WS_OVERSIZE_FRAME );
997 0 : return;
998 0 : }
999 :
1000 99 : if( FD_UNLIKELY( conn->recv_bytes_parsed+frame_len+1UL>http->max_ws_recv_frame_len ) ) {
1001 0 : close_conn( http, conn_idx, FD_HTTP_SERVER_CONNECTION_CLOSE_WS_OVERSIZE_FRAME );
1002 0 : return;
1003 0 : }
1004 :
1005 99 : if( FD_UNLIKELY( conn->recv_bytes_read<frame_len ) ) return; /* Need more data to read the full frame */
1006 :
1007 : /* Data frame, process it */
1008 :
1009 36 : int is_fin_set = conn->recv_bytes[ conn->recv_bytes_parsed+0UL ] & 0x80;
1010 :
1011 36 : uchar * mask = conn->recv_bytes+conn->recv_bytes_parsed+1UL+len_bytes;
1012 36 : uchar mask_copy[ 4 ] = { mask[ 0 ], mask[ 1 ], mask[ 2 ], mask[ 3 ] }; /* Bytes will be overwritten by the memmove below */
1013 :
1014 36 : uchar * payload = conn->recv_bytes+conn->recv_bytes_parsed+header_len;
1015 210 : for( ulong i=0UL; i<payload_len; i++ ) conn->recv_bytes[ conn->recv_bytes_parsed+i ] = payload[ i ] ^ mask_copy[ i % 4 ];
1016 :
1017 36 : http->metrics.frames_read++;
1018 :
1019 : /* Frame is complete, process it */
1020 :
1021 36 : if( FD_UNLIKELY( opcode==0x8 ) ) {
1022 0 : close_conn( http, conn_idx, FD_HTTP_SERVER_CONNECTION_CLOSE_PEER_RESET );
1023 0 : return;
1024 36 : } else if( FD_UNLIKELY( opcode==0x9 ) ) {
1025 : /* Ping frame, queue pong unless we are already sending one */
1026 6 : uchar * trailing_data = conn->recv_bytes+conn->recv_bytes_parsed+frame_len;
1027 6 : ulong trailing_data_len = conn->recv_bytes_read-frame_len;
1028 6 : if( FD_LIKELY( conn->pong_state!=FD_HTTP_SERVER_PONG_STATE_WAITING ) ) {
1029 6 : conn->pong_state = FD_HTTP_SERVER_PONG_STATE_WAITING;
1030 6 : conn->pong_data_len = payload_len;
1031 6 : FD_TEST( payload_len<=125UL );
1032 6 : memcpy( conn->pong_data, conn->recv_bytes+conn->recv_bytes_parsed, payload_len );
1033 6 : }
1034 6 : conn->recv_bytes_read = trailing_data_len;
1035 6 : if( FD_UNLIKELY( trailing_data_len ) ) {
1036 3 : memmove( conn->recv_bytes+conn->recv_bytes_parsed, trailing_data, trailing_data_len );
1037 3 : goto again; /* Might be another frame in the buffer to process */
1038 3 : }
1039 3 : return;
1040 30 : } else if( FD_UNLIKELY( opcode==0xA ) ) {
1041 : /* Pong frame, ignore */
1042 3 : uchar * trailing_data = conn->recv_bytes+conn->recv_bytes_parsed+frame_len;
1043 3 : ulong trailing_data_len = conn->recv_bytes_read-frame_len;
1044 3 : conn->recv_bytes_read = trailing_data_len;
1045 3 : if( FD_UNLIKELY( trailing_data_len ) ) {
1046 3 : memmove( conn->recv_bytes+conn->recv_bytes_parsed, trailing_data, trailing_data_len );
1047 3 : goto again; /* Might be another frame in the buffer to process */
1048 3 : }
1049 0 : return;
1050 3 : }
1051 :
1052 27 : if( FD_UNLIKELY( conn->recv_started_msg ) ) {
1053 18 : if( FD_UNLIKELY( opcode!=0x0 ) ) {
1054 0 : close_conn( http, conn_idx, FD_HTTP_SERVER_CONNECTION_CLOSE_WS_EXPECTED_CONT_OPCODE );
1055 0 : return;
1056 0 : }
1057 18 : } else {
1058 9 : if( FD_UNLIKELY( opcode!=0x1 && opcode!=0x2 ) ) {
1059 0 : close_conn( http, conn_idx, FD_HTTP_SERVER_CONNECTION_CLOSE_WS_EXPECTED_TEXT_OPCODE );
1060 0 : return;
1061 0 : }
1062 9 : }
1063 :
1064 : /* Check if this is a complete message */
1065 :
1066 27 : if( FD_UNLIKELY( !is_fin_set ) ) {
1067 18 : uchar * trailing_data = conn->recv_bytes+conn->recv_bytes_parsed+frame_len;
1068 18 : ulong trailing_data_len = conn->recv_bytes_read-frame_len;
1069 18 : conn->recv_started_msg = 1;
1070 18 : conn->recv_bytes_parsed += payload_len;
1071 18 : conn->recv_bytes_read = trailing_data_len;
1072 18 : if( FD_UNLIKELY( trailing_data_len ) ) {
1073 12 : memmove( conn->recv_bytes+conn->recv_bytes_parsed, trailing_data, trailing_data_len );
1074 12 : goto again; /* Might be another frame in the buffer to process */
1075 12 : }
1076 6 : return; /* Not a complete message yet */
1077 18 : }
1078 :
1079 : /* Complete message, process it */
1080 :
1081 9 : uchar * trailing_data = conn->recv_bytes+conn->recv_bytes_parsed+frame_len;
1082 9 : ulong trailing_data_len = conn->recv_bytes_read-frame_len;
1083 :
1084 9 : conn->recv_bytes_parsed += payload_len;
1085 9 : conn->recv_bytes_read -= frame_len;
1086 :
1087 9 : uchar tmp = conn->recv_bytes[ conn->recv_bytes_parsed ];
1088 9 : conn->recv_bytes[ conn->recv_bytes_parsed ] = 0; /* NUL terminate */
1089 9 : http->callbacks.ws_message( conn_idx-http->max_conns, conn->recv_bytes, conn->recv_bytes_parsed, http->callback_ctx );
1090 9 : if( FD_UNLIKELY( -1==http->pollfds[ conn_idx ].fd ) ) return; /* Connection was closed by callback */
1091 9 : conn->recv_bytes[ conn->recv_bytes_parsed ] = tmp;
1092 :
1093 9 : conn->recv_started_msg = 0;
1094 9 : conn->recv_bytes_parsed = 0UL;
1095 9 : if( FD_UNLIKELY( trailing_data_len ) ) {
1096 0 : memmove( conn->recv_bytes, trailing_data, trailing_data_len );
1097 0 : goto again; /* Might be another message in the buffer to process */
1098 0 : }
1099 9 : }
1100 :
1101 : static void
1102 : read_conn( fd_http_server_t * http,
1103 249 : ulong conn_idx ) {
1104 249 : if( FD_LIKELY( conn_idx<http->max_conns ) ) read_conn_http( http, conn_idx );
1105 90 : else read_conn_ws( http, conn_idx );
1106 249 : }
1107 :
1108 : static void
1109 : write_conn_http( fd_http_server_t * http,
1110 411 : ulong conn_idx ) {
1111 411 : struct fd_http_server_connection * conn = &http->conns[ conn_idx ];
1112 :
1113 411 : char header_buf[ 2048 ];
1114 :
1115 411 : uchar const * response;
1116 411 : ulong response_len;
1117 411 : switch( conn->state ) {
1118 0 : case FD_HTTP_SERVER_CONNECTION_STATE_READING:
1119 0 : return; /* No data staged for write yet. */
1120 213 : case FD_HTTP_SERVER_CONNECTION_STATE_WRITING_HEADER:
1121 213 : switch( conn->response.status ) {
1122 168 : case 200:
1123 168 : if( FD_UNLIKELY( conn->response.upgrade_websocket ) ) {
1124 15 : if( FD_UNLIKELY( !conn->upgrade_websocket ) ) {
1125 0 : close_conn( http, conn_idx, FD_HTTP_SERVER_CONNECTION_CLOSE_WS_MISSING_UPGRADE );
1126 0 : return;
1127 0 : }
1128 :
1129 15 : uchar sec_websocket_key[ 60 ];
1130 15 : fd_memcpy( sec_websocket_key, conn->sec_websocket_key, 24 );
1131 15 : fd_memcpy( sec_websocket_key+24, "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", 36 );
1132 :
1133 15 : uchar sec_websocket_accept[ 20 ];
1134 15 : fd_sha1_hash( sec_websocket_key, 60, sec_websocket_accept );
1135 15 : char sec_websocket_accept_base64[ FD_BASE64_ENC_SZ( 20 ) ];
1136 15 : ulong encoded_len = fd_base64_encode( sec_websocket_accept_base64, sec_websocket_accept, 20 );
1137 15 : FD_TEST( fd_cstr_printf_check( header_buf, sizeof( header_buf ), &response_len, "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: %.*s\r\n", (int)encoded_len, sec_websocket_accept_base64 ) );
1138 153 : } else {
1139 153 : ulong body_len = conn->response.static_body ? conn->response.static_body_len : conn->response._body_len;
1140 153 : FD_TEST( fd_cstr_printf_check( header_buf, sizeof( header_buf ), &response_len, "HTTP/1.1 200 OK\r\nContent-Length: %lu\r\n", body_len ) );
1141 153 : }
1142 168 : break;
1143 168 : case 204: {
1144 0 : ulong body_len = conn->response.static_body ? conn->response.static_body_len : conn->response._body_len;
1145 0 : FD_TEST( fd_cstr_printf_check( header_buf, sizeof( header_buf ), &response_len, "HTTP/1.1 204 No Content\r\nContent-Length: %lu\r\n", body_len ) );
1146 0 : break;
1147 0 : }
1148 9 : case 302:
1149 9 : FD_TEST( fd_cstr_printf_check( header_buf, sizeof( header_buf ), &response_len, "HTTP/1.1 302 Found\r\nContent-Length: 0\r\n" ) );
1150 9 : break;
1151 24 : case 304:
1152 : /* RFC 9110 s15.4.5: no body, no Content-Length */
1153 24 : FD_TEST( fd_cstr_printf_check( header_buf, sizeof( header_buf ), &response_len, "HTTP/1.1 304 Not Modified\r\n" ) );
1154 24 : break;
1155 24 : case 400: {
1156 12 : ulong body_len = conn->response.static_body ? conn->response.static_body_len : conn->response._body_len;
1157 12 : FD_TEST( fd_cstr_printf_check( header_buf, sizeof( header_buf ), &response_len, "HTTP/1.1 400 Bad Request\r\nContent-Length: %lu\r\n", body_len ) );
1158 12 : break;
1159 12 : }
1160 12 : case 403:
1161 0 : FD_TEST( fd_cstr_printf_check( header_buf, sizeof( header_buf ), &response_len, "HTTP/1.1 403 Forbidden\r\nContent-Length: 0\r\n" ) );
1162 0 : break;
1163 0 : case 404:
1164 0 : FD_TEST( fd_cstr_printf_check( header_buf, sizeof( header_buf ), &response_len, "HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\n" ) );
1165 0 : break;
1166 0 : case 405:
1167 0 : FD_TEST( fd_cstr_printf_check( header_buf, sizeof( header_buf ), &response_len, "HTTP/1.1 405 Method Not Allowed\r\nContent-Length: 0\r\n" ) );
1168 0 : break;
1169 0 : case 500:
1170 0 : FD_TEST( fd_cstr_printf_check( header_buf, sizeof( header_buf ), &response_len, "HTTP/1.1 500 Internal Server Error\r\nContent-Length: 0\r\n" ) );
1171 0 : break;
1172 0 : case 501:
1173 0 : FD_TEST( fd_cstr_printf_check( header_buf, sizeof( header_buf ), &response_len, "HTTP/1.1 501 Not Implemented\r\nContent-Length: 0\r\n" ) );
1174 0 : break;
1175 0 : default:
1176 0 : FD_TEST( fd_cstr_printf_check( header_buf, sizeof( header_buf ), &response_len, "HTTP/1.1 500 Internal Server Error\r\nContent-Length: 0\r\n" ) );
1177 0 : break;
1178 213 : }
1179 :
1180 213 : if( FD_LIKELY( conn->response.compress_websocket ) ) {
1181 0 : ulong compress_websocket_len;
1182 0 : FD_TEST( fd_cstr_printf_check( header_buf+response_len, sizeof( header_buf )-response_len, &compress_websocket_len, "Sec-WebSocket-Protocol: compress-zstd\r\n" ) );
1183 0 : response_len += compress_websocket_len;
1184 0 : }
1185 213 : if( FD_LIKELY( conn->response.content_type ) ) {
1186 153 : ulong content_type_len;
1187 153 : FD_TEST( fd_cstr_printf_check( header_buf+response_len, sizeof( header_buf )-response_len, &content_type_len, "Content-Type: %s\r\n", conn->response.content_type ) );
1188 153 : response_len += content_type_len;
1189 153 : }
1190 213 : if( FD_LIKELY( conn->response.cache_control ) ) {
1191 0 : ulong cache_control_len;
1192 0 : FD_TEST( fd_cstr_printf_check( header_buf+response_len, sizeof( header_buf )-response_len, &cache_control_len, "Cache-Control: %s\r\n", conn->response.cache_control ) );
1193 0 : response_len += cache_control_len;
1194 0 : }
1195 213 : if( FD_LIKELY( conn->response.link ) ) {
1196 0 : ulong link_len;
1197 0 : FD_TEST( fd_cstr_printf_check( header_buf+response_len, sizeof( header_buf )-response_len, &link_len, "Link: %s\r\n", conn->response.link ) );
1198 0 : response_len += link_len;
1199 0 : }
1200 213 : if( FD_LIKELY( conn->response.content_encoding ) ) {
1201 3 : ulong content_encoding_len;
1202 3 : FD_TEST( fd_cstr_printf_check( header_buf+response_len, sizeof( header_buf )-response_len, &content_encoding_len, "Content-Encoding: %s\r\n", conn->response.content_encoding ) );
1203 3 : response_len += content_encoding_len;
1204 3 : }
1205 213 : if( FD_LIKELY( conn->response.vary ) ) {
1206 15 : ulong vary_len;
1207 15 : FD_TEST( fd_cstr_printf_check( header_buf+response_len, sizeof( header_buf )-response_len, &vary_len, "Vary: %s\r\n", conn->response.vary ) );
1208 15 : response_len += vary_len;
1209 15 : }
1210 213 : if( FD_LIKELY( conn->response.location[ 0 ] ) ) {
1211 9 : ulong location_len;
1212 9 : FD_TEST( conn->response.location_len[0]<=(ulong)INT_MAX );
1213 9 : FD_TEST( conn->response.location_len[1]<=(ulong)INT_MAX );
1214 9 : FD_TEST( fd_cstr_printf_check( header_buf+response_len, sizeof( header_buf )-response_len, &location_len,
1215 9 : "Location: %.*s%.*s\r\n",
1216 9 : (int)conn->response.location_len[0],
1217 9 : conn->response.location[0],
1218 9 : (int)conn->response.location_len[1],
1219 9 : conn->response.location[1] ? conn->response.location[1] : "" ) );
1220 9 : response_len += location_len;
1221 9 : }
1222 213 : if( FD_LIKELY( conn->response.access_control_allow_origin ) ) {
1223 0 : ulong access_control_allow_origin_len;
1224 0 : FD_TEST( fd_cstr_printf_check( header_buf+response_len, sizeof( header_buf )-response_len, &access_control_allow_origin_len, "Access-Control-Allow-Origin: %s\r\n", conn->response.access_control_allow_origin ) );
1225 0 : response_len += access_control_allow_origin_len;
1226 0 : }
1227 213 : if( FD_LIKELY( conn->response.access_control_allow_methods ) ) {
1228 0 : ulong access_control_allow_methods_len;
1229 0 : FD_TEST( fd_cstr_printf_check( header_buf+response_len, sizeof( header_buf )-response_len, &access_control_allow_methods_len, "Access-Control-Allow-Methods: %s\r\n", conn->response.access_control_allow_methods ) );
1230 0 : response_len += access_control_allow_methods_len;
1231 0 : }
1232 213 : if( FD_LIKELY( conn->response.access_control_allow_headers ) ) {
1233 0 : ulong access_control_allow_headers_len;
1234 0 : FD_TEST( fd_cstr_printf_check( header_buf+response_len, sizeof( header_buf )-response_len, &access_control_allow_headers_len, "Access-Control-Allow-Headers: %s\r\n", conn->response.access_control_allow_headers ) );
1235 0 : response_len += access_control_allow_headers_len;
1236 0 : }
1237 213 : if( FD_LIKELY( !conn->response.upgrade_websocket ) ) {
1238 198 : ulong connection_len;
1239 198 : FD_TEST( fd_cstr_printf_check( header_buf+response_len, sizeof( header_buf )-response_len, &connection_len, "Connection: %s\r\n", conn->keep_alive ? "keep-alive" : "close" ) );
1240 198 : response_len += connection_len;
1241 198 : }
1242 213 : if( FD_LIKELY( conn->response.etag ) ) {
1243 27 : ulong etag_len;
1244 27 : FD_TEST( fd_cstr_printf_check( header_buf+response_len, sizeof( header_buf )-response_len, &etag_len, "ETag: %s\r\n", conn->response.etag ) );
1245 27 : response_len += etag_len;
1246 27 : }
1247 213 : if( FD_LIKELY( conn->response.access_control_max_age ) ) {
1248 0 : ulong access_control_max_age_len;
1249 0 : FD_TEST( fd_cstr_printf_check( header_buf+response_len, sizeof( header_buf )-response_len, &access_control_max_age_len, "Access-Control-Max-Age: %lu\r\n", conn->response.access_control_max_age ) );
1250 0 : response_len += access_control_max_age_len;
1251 0 : }
1252 213 : FD_TEST( fd_cstr_printf_check( header_buf+response_len, sizeof( header_buf )-response_len, NULL, "\r\n" ) );
1253 213 : response_len += 2UL;
1254 :
1255 213 : response = (uchar const *)header_buf;
1256 213 : break;
1257 198 : case FD_HTTP_SERVER_CONNECTION_STATE_WRITING_BODY:
1258 198 : if( FD_UNLIKELY( conn->response.static_body ) ) {
1259 0 : response = conn->response.static_body;
1260 0 : response_len = conn->response.static_body_len;
1261 198 : } else {
1262 198 : response = http->oring+(conn->response._body_off%http->oring_sz);
1263 198 : response_len = conn->response._body_len;
1264 198 : }
1265 198 : break;
1266 0 : default:
1267 0 : FD_LOG_ERR(( "invalid server state (%d)", conn->state ));
1268 411 : }
1269 :
1270 411 : long sz = send( http->pollfds[ conn_idx ].fd, response+conn->response_bytes_written, response_len-conn->response_bytes_written, MSG_NOSIGNAL );
1271 411 : if( FD_UNLIKELY( -1==sz && errno==EAGAIN ) ) return; /* No data was written, continue. */
1272 411 : if( FD_UNLIKELY( -1==sz && is_expected_network_error( errno ) ) ) {
1273 0 : close_conn( http, conn_idx, FD_HTTP_SERVER_CONNECTION_CLOSE_PEER_RESET );
1274 0 : return;
1275 0 : }
1276 411 : if( FD_UNLIKELY( -1==sz ) ) FD_LOG_ERR(( "write failed (%i-%s)", errno, strerror( errno ) )); /* Unexpected programmer error, abort */
1277 :
1278 411 : http->metrics.bytes_written += (ulong)sz;
1279 411 : conn->response_bytes_written += (ulong)sz;
1280 411 : if( FD_UNLIKELY( conn->response_bytes_written==response_len ) ) {
1281 411 : switch( conn->state ) {
1282 213 : case FD_HTTP_SERVER_CONNECTION_STATE_WRITING_HEADER:
1283 213 : if( FD_UNLIKELY( conn->response.upgrade_websocket ) ) {
1284 15 : if( FD_UNLIKELY( !conn->upgrade_websocket ) ) {
1285 0 : close_conn( http, conn_idx, FD_HTTP_SERVER_CONNECTION_CLOSE_WS_MISSING_UPGRADE );
1286 0 : return;
1287 0 : }
1288 :
1289 15 : int fd = http->pollfds[ conn_idx ].fd;
1290 15 : http->pollfds[ conn_idx ].fd = -1;
1291 15 : http->pollfds[ conn_idx ].events = POLLIN;
1292 :
1293 15 : struct fd_http_server_connection * conn = &http->conns[ conn_idx ];
1294 :
1295 15 : int ws_compress = conn->response.compress_websocket;
1296 15 : ulong req_bytes_read = conn->request_bytes_read;
1297 15 : ulong req_bytes_len = conn->request_bytes_len;
1298 :
1299 15 : if( FD_LIKELY( !conn->response.static_body && conn->response._body_len ) ) conn_treap_ele_remove( http->conn_treap, conn, http->conns );
1300 15 : conn_pool_ele_release( http->conns, conn );
1301 :
1302 15 : if( FD_UNLIKELY( !ws_conn_pool_free( http->ws_conns ) ) ) {
1303 0 : ws_conn_treap_rev_iter_t it = ws_conn_treap_rev_iter_init( http->ws_conn_treap, http->ws_conns );
1304 0 : if( FD_LIKELY( !ws_conn_treap_rev_iter_done( it ) ) ) {
1305 0 : ulong ws_conn_id = ws_conn_treap_rev_iter_idx( it );
1306 0 : close_conn( http, http->max_conns+ws_conn_id, FD_HTTP_SERVER_CONNECTION_CLOSE_EVICTED );
1307 0 : } else {
1308 0 : close_conn( http, http->max_conns+http->evict_ws_conn_id, FD_HTTP_SERVER_CONNECTION_CLOSE_EVICTED );
1309 0 : http->evict_ws_conn_id = (http->evict_ws_conn_id+1UL) % http->max_ws_conns;
1310 0 : }
1311 0 : }
1312 :
1313 15 : ulong ws_conn_id = ws_conn_pool_idx_acquire( http->ws_conns );
1314 15 : http->pollfds[ http->max_conns+ws_conn_id ].fd = fd;
1315 15 : http->pollfds[ http->max_conns+ws_conn_id ].events = POLLIN;
1316 15 : epoll_conn_move( http, http->max_conns+ws_conn_id );
1317 :
1318 15 : http->ws_conns[ ws_conn_id ].pong_state = FD_HTTP_SERVER_PONG_STATE_NONE;
1319 15 : http->ws_conns[ ws_conn_id ].send_frame_cnt = 0UL;
1320 15 : http->ws_conns[ ws_conn_id ].send_frame_state = FD_HTTP_SERVER_SEND_FRAME_STATE_HEADER;
1321 15 : http->ws_conns[ ws_conn_id ].send_frame_idx = 0UL;
1322 15 : http->ws_conns[ ws_conn_id ].recv_started_msg = 0;
1323 15 : http->ws_conns[ ws_conn_id ].recv_bytes_parsed = 0UL;
1324 15 : http->ws_conns[ ws_conn_id ].recv_bytes_read = 0UL;
1325 15 : http->ws_conns[ ws_conn_id ].send_frame_bytes_written = 0UL;
1326 15 : http->ws_conns[ ws_conn_id ].compress_websocket = ws_compress;
1327 :
1328 15 : http->metrics.connection_cnt--;
1329 15 : http->metrics.ws_connection_cnt++;
1330 :
1331 : /* Trailing data after the HTTP request was already rejected
1332 : in read_conn_http, so req_bytes_read==req_bytes_len. */
1333 15 : FD_TEST( req_bytes_read==req_bytes_len );
1334 :
1335 : #if FD_HTTP_SERVER_DEBUG
1336 : FD_LOG_WARNING(( "Upgraded connection %lu (fd=%d) to websocket connection %lu", conn_idx, fd, ws_conn_id ));
1337 : #endif
1338 :
1339 15 : if( FD_LIKELY( http->callbacks.ws_open ) ) http->callbacks.ws_open( ws_conn_id, http->callback_ctx );
1340 198 : } else {
1341 198 : conn->state = FD_HTTP_SERVER_CONNECTION_STATE_WRITING_BODY;
1342 198 : conn->response_bytes_written = 0UL;
1343 198 : write_conn_http( http, conn_idx );
1344 198 : }
1345 213 : break;
1346 213 : case FD_HTTP_SERVER_CONNECTION_STATE_WRITING_BODY:
1347 198 : if( FD_LIKELY( conn->keep_alive ) ) {
1348 159 : if( FD_LIKELY( !conn->response.static_body && conn->response._body_len ) ) conn_treap_ele_remove( http->conn_treap, conn, http->conns );
1349 159 : ulong remaining = conn->request_bytes_read-conn->request_consumed;
1350 159 : conn->state = FD_HTTP_SERVER_CONNECTION_STATE_READING;
1351 159 : conn->request_bytes_off = conn->request_consumed;
1352 159 : conn->response_bytes_written = 0UL;
1353 159 : if( FD_UNLIKELY( remaining ) ) parse_conn_http( http, conn_idx, 0UL );
1354 159 : } else {
1355 39 : close_conn( http, conn_idx, FD_HTTP_SERVER_CONNECTION_CLOSE_OK );
1356 39 : }
1357 198 : break;
1358 411 : }
1359 411 : }
1360 411 : }
1361 :
1362 : static int
1363 : maybe_write_pong( fd_http_server_t * http,
1364 6 : ulong conn_idx ) {
1365 6 : struct fd_http_server_ws_connection * conn = &http->ws_conns[ conn_idx-http->max_conns ];
1366 :
1367 : /* No need to pong if ....
1368 :
1369 : Client has not sent a ping */
1370 6 : if( FD_LIKELY( conn->pong_state==FD_HTTP_SERVER_PONG_STATE_NONE ) ) return 0;
1371 : /* We are in the middle of writing a data frame */
1372 3 : if( FD_LIKELY( conn->send_frame_cnt && (conn->send_frame_state==FD_HTTP_SERVER_SEND_FRAME_STATE_DATA || conn->send_frame_bytes_written ) ) ) return 0;
1373 :
1374 : /* Otherwise, we need to pong */
1375 3 : if( FD_LIKELY( conn->pong_state==FD_HTTP_SERVER_PONG_STATE_WAITING ) ) {
1376 3 : conn->pong_state = FD_HTTP_SERVER_PONG_STATE_WRITING;
1377 3 : conn->pong_bytes_written = 0UL;
1378 3 : }
1379 :
1380 3 : uchar frame[ 2UL+125UL ];
1381 3 : frame[ 0 ] = 0x80 | 0x0A; /* FIN, 0xA for pong. */
1382 3 : frame[ 1 ] = (uchar)conn->pong_data_len;
1383 3 : fd_memcpy( frame+2UL, conn->pong_data, conn->pong_data_len );
1384 :
1385 3 : long sz = send( http->pollfds[ conn_idx ].fd, frame+conn->pong_bytes_written, 2UL+conn->pong_data_len-conn->pong_bytes_written, MSG_NOSIGNAL );
1386 3 : if( FD_UNLIKELY( -1==sz && errno==EAGAIN ) ) return 1; /* No data was written, continue. */
1387 3 : else if( FD_UNLIKELY( -1==sz && is_expected_network_error( errno ) ) ) {
1388 0 : close_conn( http, conn_idx, FD_HTTP_SERVER_CONNECTION_CLOSE_PEER_RESET );
1389 0 : return 1;
1390 0 : }
1391 3 : else if( FD_UNLIKELY( -1==sz ) ) FD_LOG_ERR(( "write failed (%i-%s)", errno, strerror( errno ) )); /* Unexpected programmer error, abort */
1392 :
1393 3 : http->metrics.bytes_written += (ulong)sz;
1394 3 : conn->pong_bytes_written += (ulong)sz;
1395 3 : if( FD_UNLIKELY( conn->pong_bytes_written==2UL+conn->pong_data_len ) ) {
1396 3 : conn->pong_state = FD_HTTP_SERVER_PONG_STATE_NONE;
1397 3 : return 0;
1398 3 : }
1399 :
1400 0 : return 1;
1401 3 : }
1402 :
1403 : static void
1404 : write_conn_ws( fd_http_server_t * http,
1405 6 : ulong conn_idx ) {
1406 6 : struct fd_http_server_ws_connection * conn = &http->ws_conns[ conn_idx-http->max_conns ];
1407 :
1408 6 : if( FD_UNLIKELY( maybe_write_pong( http, conn_idx ) ) ) return;
1409 6 : if( FD_UNLIKELY( !conn->send_frame_cnt ) ) {
1410 3 : return;
1411 3 : }
1412 :
1413 3 : struct iovec iovecs[ 512UL*2UL ];
1414 3 : uchar headers[ 512UL ][ 10UL ];
1415 :
1416 3 : ulong batch_cnt = fd_ulong_min( conn->send_frame_cnt, 512UL );
1417 3 : ulong out_idx = 0UL;
1418 6 : for( ulong i=0UL; i<batch_cnt; i++ ) {
1419 3 : fd_http_server_ws_frame_t * frame = &conn->send_frames[ (conn->send_frame_idx+i) % http->max_ws_send_frame_cnt ];
1420 3 : if( FD_UNLIKELY( i || conn->send_frame_state==FD_HTTP_SERVER_SEND_FRAME_STATE_HEADER ) ) {
1421 3 : ulong header_len;
1422 3 : headers[ i ][ 0 ] = 0x80 | fd_uchar_if(frame->compressed, 0x02, 0x01); /* FIN, 0x1 for text, 0x2 for binary */
1423 3 : if( FD_LIKELY( frame->len<126UL ) ) {
1424 3 : headers[ i ][ 1 ] = (uchar)frame->len;
1425 3 : header_len = 2UL;
1426 3 : } else if( FD_LIKELY( frame->len<65536UL ) ) {
1427 0 : headers[ i ][ 1 ] = 126;
1428 0 : headers[ i ][ 2 ] = (uchar)(frame->len>>8);
1429 0 : headers[ i ][ 3 ] = (uchar)(frame->len);
1430 0 : header_len = 4UL;
1431 0 : } else {
1432 0 : headers[ i ][ 1 ] = 127;
1433 0 : headers[ i ][ 2 ] = (uchar)(frame->len>>56);
1434 0 : headers[ i ][ 3 ] = (uchar)(frame->len>>48);
1435 0 : headers[ i ][ 4 ] = (uchar)(frame->len>>40);
1436 0 : headers[ i ][ 5 ] = (uchar)(frame->len>>32);
1437 0 : headers[ i ][ 6 ] = (uchar)(frame->len>>24);
1438 0 : headers[ i ][ 7 ] = (uchar)(frame->len>>16);
1439 0 : headers[ i ][ 8 ] = (uchar)(frame->len>>8);
1440 0 : headers[ i ][ 9 ] = (uchar)(frame->len);
1441 0 : header_len = 10UL;
1442 0 : }
1443 :
1444 3 : ulong header_bytes_written = fd_ulong_if( i==0UL, conn->send_frame_bytes_written, 0UL );
1445 :
1446 3 : iovecs[ out_idx ].iov_base = headers[ i ]+header_bytes_written;
1447 3 : iovecs[ out_idx ].iov_len = header_len-header_bytes_written;
1448 3 : out_idx++;
1449 3 : }
1450 :
1451 3 : ulong data_bytes_written = fd_ulong_if( i==0UL && conn->send_frame_state==FD_HTTP_SERVER_SEND_FRAME_STATE_DATA, conn->send_frame_bytes_written, 0UL );
1452 3 : iovecs[ out_idx ].iov_base = http->oring+(frame->off%http->oring_sz)+data_bytes_written;
1453 3 : iovecs[ out_idx ].iov_len = frame->len-data_bytes_written;
1454 3 : out_idx++;
1455 3 : }
1456 :
1457 3 : struct msghdr msg = {0};
1458 3 : msg.msg_iov = iovecs;
1459 3 : msg.msg_iovlen = out_idx;
1460 :
1461 3 : long sz = sendmsg( http->pollfds[ conn_idx ].fd, &msg, MSG_NOSIGNAL );
1462 3 : if( FD_UNLIKELY( -1==sz && errno==EAGAIN ) ) return; /* No data was written, continue. */
1463 3 : else if( FD_UNLIKELY( -1==sz && is_expected_network_error( errno ) ) ) {
1464 0 : close_conn( http, conn_idx, FD_HTTP_SERVER_CONNECTION_CLOSE_PEER_RESET );
1465 0 : return;
1466 0 : }
1467 3 : else if( FD_UNLIKELY( -1==sz ) ) FD_LOG_ERR(( "write failed (%i-%s)", errno, fd_io_strerror( errno ) )); /* Unexpected programmer error, abort */
1468 :
1469 3 : ulong sent = (ulong)sz;
1470 3 : http->metrics.bytes_written += sent;
1471 :
1472 9 : for( ulong i=0UL; i<out_idx; i++ ) {
1473 6 : ulong iov_len = iovecs[ i ].iov_len;
1474 6 : if( FD_LIKELY( sent>=iov_len ) ) {
1475 6 : conn->send_frame_bytes_written = 0UL;
1476 :
1477 6 : if( FD_LIKELY( conn->send_frame_state==FD_HTTP_SERVER_SEND_FRAME_STATE_DATA ) ) {
1478 3 : conn->send_frame_state = FD_HTTP_SERVER_SEND_FRAME_STATE_HEADER;
1479 3 : conn->send_frame_idx = (conn->send_frame_idx+1UL) % http->max_ws_send_frame_cnt;
1480 3 : conn->send_frame_cnt--;
1481 :
1482 3 : ws_conn_treap_ele_remove( http->ws_conn_treap, conn, http->ws_conns );
1483 3 : if( FD_LIKELY( conn->send_frame_cnt ) ) ws_conn_treap_ele_insert( http->ws_conn_treap, conn, http->ws_conns );
1484 :
1485 3 : http->metrics.frames_written++;
1486 3 : } else {
1487 3 : conn->send_frame_state = FD_HTTP_SERVER_SEND_FRAME_STATE_DATA;
1488 3 : }
1489 :
1490 6 : sent -= iov_len;
1491 6 : } else {
1492 0 : conn->send_frame_bytes_written += sent;
1493 0 : break;
1494 0 : }
1495 6 : }
1496 :
1497 3 : }
1498 :
1499 : static void
1500 : write_conn( fd_http_server_t * http,
1501 219 : ulong conn_idx ) {
1502 219 : if( FD_LIKELY( conn_idx<http->max_conns ) ) write_conn_http( http, conn_idx );
1503 6 : else write_conn_ws( http, conn_idx );
1504 219 : }
1505 :
1506 : int
1507 : fd_http_server_epoll_poll( fd_http_server_t * http,
1508 666 : ulong conn_max ) {
1509 666 : FD_TEST( -1!=http->epoll_fd );
1510 :
1511 666 : struct epoll_event evs[ 64 ];
1512 666 : int max_events = (int)fd_ulong_min( fd_ulong_max( conn_max, 1UL ), 64UL );
1513 666 : int nfds = epoll_pwait( http->epoll_fd, evs, max_events, 0, NULL );
1514 666 : if( FD_UNLIKELY( -1==nfds ) ) {
1515 0 : if( FD_LIKELY( errno==EINTR ) ) return 0;
1516 0 : FD_LOG_ERR(( "epoll_pwait failed (%i-%s)", errno, fd_io_strerror( errno ) ));
1517 0 : }
1518 666 : if( FD_LIKELY( !nfds ) ) return 0;
1519 :
1520 474 : ulong listener_idx = http->max_conns+http->max_ws_conns;
1521 951 : for( int i=0; i<nfds; i++ ) {
1522 477 : ulong conn_idx = evs[ i ].data.u64;
1523 477 : if( FD_UNLIKELY( conn_idx==listener_idx ) ) {
1524 114 : accept_conns( http );
1525 114 : continue;
1526 114 : }
1527 363 : if( FD_UNLIKELY( -1==http->pollfds[ conn_idx ].fd ) ) continue;
1528 363 : uint ev = evs[ i ].events;
1529 363 : int http_writing = conn_idx<http->max_conns && http->conns[ conn_idx ].state!=FD_HTTP_SERVER_CONNECTION_STATE_READING;
1530 363 : if( FD_LIKELY( (ev & EPOLLIN) || ((ev & (EPOLLHUP|EPOLLERR)) && !http_writing) ) ) read_conn( http, conn_idx );
1531 363 : if( FD_UNLIKELY( -1==http->pollfds[ conn_idx ].fd ) ) continue;
1532 318 : http_writing = conn_idx<http->max_conns && http->conns[ conn_idx ].state!=FD_HTTP_SERVER_CONNECTION_STATE_READING;
1533 318 : if( FD_LIKELY( (ev & EPOLLOUT) || http_writing ) ) write_conn( http, conn_idx );
1534 318 : epoll_update_out( http, conn_idx );
1535 318 : }
1536 :
1537 474 : return 1;
1538 666 : }
1539 :
1540 : static void
1541 : fd_http_server_evict_until( fd_http_server_t * http,
1542 87963 : ulong off ) {
1543 87963 : conn_treap_fwd_iter_t next;
1544 87963 : for( conn_treap_fwd_iter_t it=conn_treap_fwd_iter_init( http->conn_treap, http->conns ); !conn_treap_fwd_iter_done( it ); it=next ) {
1545 0 : next = conn_treap_fwd_iter_next( it, http->conns );
1546 0 : struct fd_http_server_connection * conn = conn_treap_fwd_iter_ele( it, http->conns );
1547 :
1548 0 : if( FD_UNLIKELY( conn->response._body_off<off ) ) {
1549 0 : close_conn( http, conn_treap_fwd_iter_idx( it ), FD_HTTP_SERVER_CONNECTION_CLOSE_EVICTED );
1550 0 : } else {
1551 0 : break;
1552 0 : }
1553 0 : }
1554 :
1555 87963 : ws_conn_treap_fwd_iter_t ws_next;
1556 87966 : for( ws_conn_treap_fwd_iter_t it=ws_conn_treap_fwd_iter_init( http->ws_conn_treap, http->ws_conns ); !ws_conn_treap_fwd_iter_done( it ); it=ws_next ) {
1557 3 : ws_next = ws_conn_treap_fwd_iter_next( it, http->ws_conns );
1558 3 : struct fd_http_server_ws_connection * conn = ws_conn_treap_fwd_iter_ele( it, http->ws_conns );
1559 :
1560 3 : if( FD_UNLIKELY( conn->send_frames[ conn->send_frame_idx ].off<off ) ) {
1561 3 : close_conn( http, ws_conn_treap_fwd_iter_idx( it )+http->max_conns, FD_HTTP_SERVER_CONNECTION_CLOSE_WS_CLIENT_TOO_SLOW );
1562 3 : } else {
1563 0 : break;
1564 0 : }
1565 3 : }
1566 87963 : }
1567 :
1568 : static void
1569 : fd_http_server_reserve( fd_http_server_t * http,
1570 88041 : ulong len ) {
1571 : /* fd_http_server_reserve should not be called after
1572 : fd_http_ws_compress_maybe */
1573 88041 : FD_TEST( http->stage_comp_len == 0 );
1574 :
1575 88041 : ulong remaining = http->oring_sz-((http->stage_off%http->oring_sz)+http->stage_len);
1576 88041 : if( FD_UNLIKELY( len>remaining ) ) {
1577 : /* Appending the format string into the hcache would go past the end
1578 : of the buffer... two cases, */
1579 21759 : if( FD_UNLIKELY( http->stage_len+len>http->oring_sz ) ) {
1580 : /* Case 1: The snap is going to be larger than the entire buffer,
1581 : there's no way to fit it even if we evict everything
1582 : else. Mark the hcache as errored and exit. */
1583 :
1584 78 : FD_LOG_WARNING(( "tried to reserve %lu bytes for an outgoing message which exceeds the entire data size", http->stage_len+len ));
1585 78 : FD_LOG_HEXDUMP_WARNING(( "start of message", http->oring+(http->stage_off%http->oring_sz), fd_ulong_min( 500UL, http->oring_sz-(http->stage_off%http->oring_sz) ) ));
1586 78 : FD_LOG_HEXDUMP_WARNING(( "start of buffer", http->oring, fd_ulong_min( 500UL, http->oring_sz ) ));
1587 78 : http->stage_err = 1;
1588 78 : return;
1589 21681 : } else {
1590 : /* Case 2: The snap can fit if we relocate it to the start of the
1591 : buffer and evict whatever was there. We also evict the
1592 : rest of the buffer behind where the snap was to
1593 : preserve the invariant that snaps are always evicted in
1594 : circular order. */
1595 :
1596 21681 : ulong stage_end = http->stage_off+remaining+http->stage_len+len;
1597 21681 : ulong clamp = fd_ulong_if( stage_end>=http->oring_sz, stage_end-http->oring_sz, 0UL );
1598 21681 : fd_http_server_evict_until( http, clamp );
1599 21681 : memmove( http->oring, http->oring+(http->stage_off%http->oring_sz), http->stage_len );
1600 21681 : http->stage_off += http->stage_len+remaining;
1601 21681 : }
1602 66282 : } else {
1603 : /* The snap can fit in the buffer, we just need to evict whatever
1604 : was there before. */
1605 66282 : ulong stage_end = http->stage_off+http->stage_len+len;
1606 66282 : ulong clamp = fd_ulong_if( stage_end>=http->oring_sz, stage_end-http->oring_sz, 0UL );
1607 66282 : fd_http_server_evict_until( http, clamp );
1608 66282 : }
1609 88041 : }
1610 :
1611 : static int
1612 0 : fd_http_ws_compress_maybe( fd_http_server_t * http ) {
1613 : /* we don't compress if the message is small, or if compression is
1614 : disabled in the config */
1615 0 : if( FD_LIKELY( !http->compress_websocket || http->stage_len <= 200 || http->stage_err ) ) return 0;
1616 :
1617 0 : ulong worst_case_compressed_sz = ZSTD_compressBound( http->stage_len );
1618 0 : fd_http_server_reserve( http, worst_case_compressed_sz );
1619 :
1620 0 : if( FD_UNLIKELY( http->stage_err ) ) return 0;
1621 :
1622 0 : ulong compressed_sz = ZSTD_compress2( http->zstd_ctx, http->oring+(http->stage_off%http->oring_sz)+http->stage_len, worst_case_compressed_sz, http->oring+(http->stage_off%http->oring_sz), http->stage_len );
1623 0 : if( FD_UNLIKELY( ZSTD_isError( compressed_sz ) ) ) {
1624 0 : FD_LOG_WARNING(( "ZSTD_compress2 failed (%s)", ZSTD_getErrorName( compressed_sz ) ) );
1625 0 : http->stage_err = 1;
1626 0 : return 0;
1627 0 : }
1628 0 : FD_TEST( compressed_sz <= worst_case_compressed_sz );
1629 :
1630 0 : http->stage_comp_len = compressed_sz;
1631 :
1632 0 : return 1;
1633 0 : }
1634 :
1635 : uchar *
1636 : fd_http_server_append_start( fd_http_server_t * http,
1637 0 : ulong len ) {
1638 0 : fd_http_server_reserve( http, len );
1639 0 : if( FD_UNLIKELY( http->stage_err ) ) return NULL;
1640 0 : return http->oring+(http->stage_off%http->oring_sz)+http->stage_len;
1641 0 : }
1642 :
1643 : void
1644 : fd_http_server_append_end( fd_http_server_t * http,
1645 0 : ulong len ) {
1646 0 : http->stage_len += len;
1647 0 : }
1648 :
1649 : int
1650 : fd_http_server_ws_send( fd_http_server_t * http,
1651 9 : ulong ws_conn_id ) {
1652 9 : struct fd_http_server_ws_connection * conn = &http->ws_conns[ ws_conn_id ];
1653 9 : int compressed = conn->compress_websocket;
1654 9 : if( FD_LIKELY( compressed ) ) compressed = fd_http_ws_compress_maybe( http );
1655 :
1656 : /* It is possible that ws_conn_id has already been closed by
1657 : fd_http_server_reserve during staging. If the staging buffer is
1658 : full, the incoming frame is added to the beginning of the buffer,
1659 : and any connections that were previously using that allotted space
1660 : are closed. There is a small chance that ws_conn_id is one of
1661 : those connections, and has therefore already been closed. */
1662 9 : if( FD_LIKELY( http->pollfds[ http->max_conns+ws_conn_id ].fd==-1 ) ) {
1663 3 : http->stage_err = 0;
1664 3 : http->stage_len = 0;
1665 3 : http->stage_comp_len = 0;
1666 3 : return 0;
1667 3 : }
1668 :
1669 6 : if( FD_UNLIKELY( http->stage_err ) ) {
1670 0 : http->stage_err = 0;
1671 0 : http->stage_len = 0;
1672 0 : http->stage_comp_len = 0;
1673 0 : return -1;
1674 0 : }
1675 :
1676 6 : if( FD_UNLIKELY( conn->send_frame_cnt==http->max_ws_send_frame_cnt ) ) {
1677 0 : close_conn( http, ws_conn_id+http->max_conns, FD_HTTP_SERVER_CONNECTION_CLOSE_WS_CLIENT_TOO_SLOW );
1678 0 : http->stage_len = 0;
1679 0 : http->stage_comp_len = 0;
1680 0 : return 0;
1681 0 : }
1682 :
1683 : /* A frame is compressed only if the connection is configured to do
1684 : so, and if the compression step wasn't skipped (i.e. stage_len>200) */
1685 6 : fd_http_server_ws_frame_t frame = {
1686 6 : .off = fd_ulong_if(compressed, http->stage_off+http->stage_len, http->stage_off),
1687 6 : .len = fd_ulong_if(compressed, http->stage_comp_len, http->stage_len),
1688 6 : .compressed = compressed,
1689 6 : };
1690 :
1691 6 : conn->send_frames[ (conn->send_frame_idx+conn->send_frame_cnt) % http->max_ws_send_frame_cnt ] = frame;
1692 6 : conn->send_frame_cnt++;
1693 :
1694 6 : if( FD_LIKELY( conn->send_frame_cnt==1UL ) ) {
1695 6 : ws_conn_treap_ele_insert( http->ws_conn_treap, conn, http->ws_conns );
1696 6 : }
1697 6 : epoll_update_out( http, http->max_conns+ws_conn_id );
1698 :
1699 6 : http->stage_off += http->stage_len+http->stage_comp_len;
1700 6 : http->stage_len = 0;
1701 6 : http->stage_comp_len = 0;
1702 :
1703 6 : return 0;
1704 6 : }
1705 :
1706 : int
1707 0 : fd_http_server_ws_broadcast( fd_http_server_t * http ) {
1708 0 : int compressed = fd_http_ws_compress_maybe( http );
1709 :
1710 0 : if( FD_UNLIKELY( http->stage_err ) ) {
1711 0 : http->stage_err = 0;
1712 0 : http->stage_len = 0;
1713 0 : http->stage_comp_len = 0;
1714 0 : return -1;
1715 0 : }
1716 :
1717 0 : for( ulong i=0UL; i<http->max_ws_conns; i++ ) {
1718 0 : if( FD_LIKELY( http->pollfds[ http->max_conns+i ].fd==-1 ) ) continue;
1719 :
1720 0 : struct fd_http_server_ws_connection * conn = &http->ws_conns[ i ];
1721 0 : if( FD_UNLIKELY( conn->send_frame_cnt==http->max_ws_send_frame_cnt ) ) {
1722 0 : close_conn( http, i+http->max_conns, FD_HTTP_SERVER_CONNECTION_CLOSE_WS_CLIENT_TOO_SLOW );
1723 0 : continue;
1724 0 : }
1725 :
1726 0 : fd_http_server_ws_frame_t frame = {
1727 0 : .off = fd_ulong_if(conn->compress_websocket && compressed, http->stage_off+http->stage_len, http->stage_off),
1728 0 : .len = fd_ulong_if(conn->compress_websocket && compressed, http->stage_comp_len, http->stage_len),
1729 0 : .compressed = conn->compress_websocket && compressed,
1730 0 : };
1731 :
1732 0 : conn->send_frames[ (conn->send_frame_idx+conn->send_frame_cnt) % http->max_ws_send_frame_cnt ] = frame;
1733 0 : conn->send_frame_cnt++;
1734 :
1735 0 : if( FD_LIKELY( conn->send_frame_cnt==1UL ) ) {
1736 0 : ws_conn_treap_ele_insert( http->ws_conn_treap, conn, http->ws_conns );
1737 0 : }
1738 0 : epoll_update_out( http, http->max_conns+i );
1739 0 : }
1740 :
1741 0 : http->stage_off += http->stage_len+http->stage_comp_len;
1742 0 : http->stage_len = 0;
1743 0 : http->stage_comp_len = 0;
1744 :
1745 0 : return 0;
1746 0 : }
1747 :
1748 : void
1749 : fd_http_server_stage_trunc( fd_http_server_t * http,
1750 0 : ulong len ) {
1751 0 : http->stage_comp_len = 0;
1752 0 : http->stage_len = len;
1753 0 : }
1754 :
1755 : ulong
1756 0 : fd_http_server_stage_len( fd_http_server_t * http ) {
1757 0 : return http->stage_len;
1758 0 : }
1759 :
1760 : void
1761 : fd_http_server_printf( fd_http_server_t * http,
1762 : char const * fmt,
1763 88791 : ... ) {
1764 88791 : if( FD_UNLIKELY( http->stage_err ) ) return;
1765 :
1766 : /* Speculatively reserve enough for the common case and print
1767 : directly into the ring, re-sizing exactly only on overflow. */
1768 87963 : ulong spec_sz = fd_ulong_min( 1024UL, http->oring_sz-http->stage_len );
1769 87963 : fd_http_server_reserve( http, spec_sz );
1770 87963 : if( FD_UNLIKELY( http->stage_err ) ) return;
1771 :
1772 87963 : va_list ap;
1773 87963 : va_start( ap, fmt );
1774 87963 : ulong printed_len = (ulong)vsnprintf( (char *)http->oring+(http->stage_off%http->oring_sz)+http->stage_len,
1775 87963 : spec_sz,
1776 87963 : fmt,
1777 87963 : ap );
1778 87963 : va_end( ap );
1779 :
1780 87963 : if( FD_UNLIKELY( printed_len+1UL>spec_sz ) ) {
1781 78 : fd_http_server_reserve( http, printed_len+1UL );
1782 78 : if( FD_UNLIKELY( http->stage_err ) ) return;
1783 :
1784 78 : va_start( ap, fmt );
1785 0 : vsnprintf( (char *)http->oring+(http->stage_off%http->oring_sz)+http->stage_len,
1786 0 : INT_MAX, /* We already proved it's going to fit above */
1787 0 : fmt,
1788 0 : ap );
1789 0 : va_end( ap );
1790 0 : }
1791 :
1792 87885 : http->stage_len += printed_len;
1793 87885 : }
1794 :
1795 : void
1796 : fd_http_server_memcpy( fd_http_server_t * http,
1797 : uchar const * data,
1798 0 : ulong data_len ) {
1799 0 : fd_http_server_reserve( http, data_len );
1800 0 : if( FD_UNLIKELY( http->stage_err ) ) return;
1801 :
1802 0 : fd_memcpy( (char *)http->oring+(http->stage_off%http->oring_sz)+http->stage_len,
1803 0 : data,
1804 0 : data_len );
1805 0 : http->stage_len += data_len;
1806 0 : }
1807 :
1808 : void
1809 6 : fd_http_server_unstage( fd_http_server_t * http ) {
1810 6 : http->stage_err = 0;
1811 6 : http->stage_len = 0UL;
1812 6 : http->stage_comp_len = 0UL;
1813 6 : }
1814 :
1815 : int
1816 : fd_http_server_stage_body( fd_http_server_t * http,
1817 21768 : fd_http_server_response_t * response ) {
1818 21768 : if( FD_UNLIKELY( http->stage_err ) ) {
1819 75 : http->stage_err = 0;
1820 75 : http->stage_len = 0UL;
1821 75 : http->stage_comp_len = 0UL;
1822 75 : return -1;
1823 75 : }
1824 :
1825 21693 : response->_body_off = http->stage_off;
1826 21693 : response->_body_len = http->stage_len;
1827 21693 : http->stage_off += http->stage_len;
1828 21693 : http->stage_len = 0;
1829 21693 : return 0;
1830 21768 : }
|