LCOV - code coverage report
Current view: top level - waltz/http - fd_http_server.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 717 1177 60.9 %
Date: 2026-08-13 04:56:22 Functions: 25 37 67.6 %

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

Generated by: LCOV version 1.14