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