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