LCOV - code coverage report
Current view: top level - waltz/http - fd_http_server.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 977 1367 71.5 %
Date: 2026-09-17 04:28:31 Functions: 34 44 77.3 %

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

Generated by: LCOV version 1.14