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