LCOV - code coverage report
Current view: top level - waltz/http - fd_http_server.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 1382 0.0 %
Date: 2026-10-22 18:10:43 Functions: 0 44 0.0 %

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

Generated by: LCOV version 1.14