/*****************************************************************************/ /* HTTP2.c HTTP/2 is a replacement for how HTTP is expressed "on the wire". It is not a ground-up rewrite of the protocol; HTTP methods, status codes and semantics are the same, and it should be possible to use the same APIs as HTTP/1.x (possibly with some small additions) to represent the protocol. The focus of the protocol is on performance; specifically, end-user perceived latency, network and server resource usage. One major goal is to allow the use of a single connection from browsers to a Web site. Some useful (and used) sites: https://tools.ietf.org/html/rfc7540 https://tools.ietf.org/html/rfc7541 https://en.wikipedia.org/wiki/HTTP/2 https://http2.github.io/ https://http2.github.io/faq/ http://http2-explained.haxx.se/ http://chimera.labs.oreilly.com/books/1230000000545/ch12.html https://insouciant.org/tech/http-slash-2-considerations-and-tradeoffs/ http://undertow.io/blog/2015/04/27/An-in-depth-overview-of-HTTP2.html https://blog.newrelic.com/2016/02/17/http2-production/ https://blog.newrelic.com/2016/02/09/http2-best-practices-web-performance/ https://blog.cloudflare.com/tools-for-debugging-testing-and-using-http-2/ https://nghttp2.org/ https://nghttp2.org/documentation/nghttp.1.html https://nghttp2.org/documentation/h2load.1.html https://nghttp2.org/blog/2014/11/29/test-your-http-slash-2-server-with-nghttp-client/ chrome://net-internals#http2 WATCHing via HTTP/2 ------------------- If a WATCHing request and one instantiated by Http2RequestBegin() share the HTTP/2 connection then none of the HTTP/2 WATCH points will be reported because down that particular rabbit hole is found only madness. TESTING ------- Needless to say the major browsers (Chrome, Edge, FireFox, Safari) all contributed to end-use development. Indispensible were the |nghttp| and the associated |h2load| tools running on a Linux Mint (17.3) VM. Many thanks to the developer(s) of this package. And of course for someone educated in computing during the (19)70s, the availability of VM technology for such purposes is just brilliant! "But you know, we were happy in those days, though we were poor." o exercise the basic HTTP/2 functionality nghttp -nv https://klaatu.private/ o exercise starting HTTP/2 for "http" URIs (RFC 7450 3.2) nghttp -nvu http://klaatu.private/ o exercise starting HTTP/2 (for "http") with prior knowledge (RFC 7450 3.4) nghttp -nv http://klaatu.private/ o headers with continuation frames (RFC 7450 6.10) nghttp --continuation https://klaatu.private/ o basic HTTP/2 loading (number, clients and streams bumped) h2load --requests=1000 --clients=5 --max-concurrent-streams=5 https://klaatu.private/ o HTTP/2 loading with defined URIs h2load --requests=1000 --clients=5 --threads=5 --max-concurrent-streams=5 --input-file=urls.txt o HTTP/1.1 loading with defined URIs h2load --h1 --requests=100 --clients=2 --threads=2 --max-concurrent-streams=5 --input-file=urls.txt The HTTP/2 and HTTP/1.1 with --input-file can be concurrently used in two separate sessions to simultaneously exercise the server against the two protocols. The -w17 -W17 (for example) switches can exercise flow control. When module WATCHing is compiled-in, defining the WASD_HTTP2_SUBTLE_BREAK logical name enables code to break request processing in specific but subtle ways to ensure the server's continued stability in the presence of network or client issues. It should be used under load from soemthing akin to |h2load|. FOR LOCAL REFERENCE ------------------- +-----------------------------------------------+ | Length (24) | +---------------+---------------+---------------+ | Type (8) | Flags (8) | +-+-------------+---------------+-------------------------------+ |R| Stream Identifier (31) | +=+=============================================================+ | Frame Payload (0...) ... +---------------------------------------------------------------+ VERSION HISTORY --------------- 23-DEC-2017 MGD bugfix; window update and flow control management 02-AUG-2015 MGD initial */ /*****************************************************************************/ #ifdef WASD_VMS_V7 # undef __VMS_VER # define __VMS_VER 70000000 # undef __CRTL_VER # define __CRTL_VER 70000000 #else # ifdef WASD_VMS_V7 # undef _VMS__V6__SOURCE # define _VMS__V6__SOURCE # undef __VMS_VER # define __VMS_VER 70000000 # undef __CRTL_VER # define __CRTL_VER 70000000 # endif #endif #include #include #include "wasd.h" #define WASD_MODULE "HTTP2" /******************/ /* global storage */ /******************/ BOOL Http2Enabled; int Http2CurrentConnected, Http2CurrentProcessing, Http2FlowControlCurrent, Http2PingTimerSeconds; uint Http2InitialWindowSize, Http2MaxConcurrentStreams, Http2MaxFrameSize, Http2MaxHeaderListSize, Http2MaxHeaderTableSize, Http2StreamIdent; /* used by VM.c */ const int Http2StructSize = sizeof(HTTP2_STRUCT); char Http2ClientPreface [] = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"; int Http2ClientPrefaceLength = sizeof(Http2ClientPreface)-1; char *Http2ErrorArray [HTTP2_ERROR_COUNT] = { "graceful shutdown", "protocol error detected", "implementation fault", "flow-control limits exceeded", "settings not acknowledged", "frame received for closed stream", "frame size incorrect", "stream not processed", "stream cancelled", "compression state not updated", "TCP connection error for CONNECT method", "processing capacity exceeded", "negotiated TLS parameters not acceptable", "use HTTP/1.1 for the request" }; LIST_HEAD Http2List; /********************/ /* external storage */ /********************/ extern int NetReadBufferSize; extern ulong HttpdTickSecond; extern ulong HttpdTime64 []; extern char ErrorSanityCheck []; #define acptr AccountingPtr extern ACCOUNTING_STRUCT *AccountingPtr; extern CONFIG_STRUCT Config; extern LIST_HEAD RequestList; extern WATCH_STRUCT Watch; /*****************************************************************************/ /* Initialise all things HTTP/2. */ void Http2Init () { /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_HTTP2)) WatchThis (WATCHALL, WATCH_MOD_HTTP2, "Http2Init()"); Http2Enabled = Config.cfHttp2.Enabled; FaoToStdout ("%HTTPD-I-HTTP2, !AZ\n", Http2Enabled ? "enabled" : "disabled"); Http2InitialWindowSize = Config.cfHttp2.InitialWindowSize; if (!Http2InitialWindowSize) Http2InitialWindowSize = HTTP2_DEFAULT_WINDOW_SIZE; else if (Http2InitialWindowSize < HTTP2_INITIAL_WINDOW_SIZE) Http2InitialWindowSize = HTTP2_INITIAL_WINDOW_SIZE; else if (Http2InitialWindowSize > HTTP2_MAX_WINDOW_SIZE) Http2InitialWindowSize = HTTP2_MAX_WINDOW_SIZE; Http2MaxConcurrentStreams = Config.cfHttp2.MaxConcurrentStreams; if (!Http2MaxConcurrentStreams) Http2MaxConcurrentStreams = HTTP2_INITIAL_MAX_CONC_STREAMS; if (Http2MaxConcurrentStreams < HTTP2_MIN_INITIAL_MAX_CONC_STREAMS) Http2MaxConcurrentStreams = HTTP2_MIN_INITIAL_MAX_CONC_STREAMS; Http2MaxFrameSize = Config.cfHttp2.MaxFrameSize; if (Http2MaxFrameSize < HTTP2_INITIAL_MAX_FRAME_SIZE) Http2MaxFrameSize = HTTP2_INITIAL_MAX_FRAME_SIZE; else if (Http2MaxFrameSize > HTTP2_MAX_FRAME_SIZE) Http2MaxFrameSize = HTTP2_MAX_FRAME_SIZE; if (Http2Enabled && NetReadBufferSize < Http2MaxFrameSize) { NetReadBufferSize = Http2MaxFrameSize; FaoToStdout ("%HTTPD-W-HTTP2, network/mailbox read buffer size \ increased to !UL bytes\n", NetReadBufferSize); } Http2MaxHeaderListSize = Config.cfHttp2.MaxHeaderListSize; if (Http2MaxHeaderListSize < HTTP2_INITIAL_HEAD_LIST_SIZE) Http2MaxHeaderListSize = HTTP2_INITIAL_HEAD_LIST_SIZE; else if (Http2MaxHeaderListSize > HTTP2_MAX_HEAD_LIST_SIZE) Http2MaxHeaderListSize = HTTP2_MAX_HEAD_LIST_SIZE; Http2MaxHeaderTableSize = Config.cfHttp2.MaxHeaderTableSize; if (Http2MaxHeaderTableSize < HTTP2_INITIAL_HEAD_TAB_SIZE) Http2MaxHeaderTableSize = HTTP2_INITIAL_HEAD_TAB_SIZE; else if (Http2MaxHeaderTableSize > HTTP2_MAX_HEAD_TAB_SIZE) Http2MaxHeaderTableSize = HTTP2_MAX_HEAD_TAB_SIZE; Http2PingTimerSeconds = Config.cfHttp2.PingTimerSeconds; if (Http2PingTimerSeconds == 0) Http2PingTimerSeconds = HTTP2_PING_SECONDS_DEFAULT; else if (Http2PingTimerSeconds < 0) Http2PingTimerSeconds = 0; /* "NONE" */ else if (Http2PingTimerSeconds > HTTP2_PING_SECONDS_MAX) Http2PingTimerSeconds = HTTP2_PING_SECONDS_MAX; if (!Http2Enabled) return; VmHttp2Init (); } /*****************************************************************************/ /* Called by WatchSetWatch(). */ void Http2SetWatch ( HTTP2_STRUCT *h2ptr, int item ) { h2ptr->WatchItem = item; if (h2ptr->NetIoPtr) if (h2ptr->NetIoPtr->SesolaPtr) SesolaSetWatch (h2ptr->NetIoPtr->SesolaPtr, item); else h2ptr->NetIoPtr->WatchItem = item; } /*****************************************************************************/ /* This is a TLS/SSL (https:) encrypted connection that has specified HTTP/2 via the TLS ALPN negotiation and transmitted the HTTP/2 client connection preface (RFC 7540 3.3) --OR-- a clear-text (http:) connection prior-knowledge (RFC 7540 3.4) connection preface. Create an HTTP/2 structure and then dispose of the request structure used to initiate the network connection. */ BOOL Http2Preface (REQUEST_STRUCT *rqptr) { int count, idx; HTTP2_STRUCT *h2ptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_HTTP2)) WatchThis (WATCHITM(rqptr), WATCH_MOD_HTTP2, "Http2Preface() preface:!UL read:!UL http2:!UL", Http2ClientPrefaceLength, rqptr->NetIoPtr->ReadCount, rqptr->NetIoPtr->ReadCount - Http2ClientPrefaceLength); if (WATCHING (rqptr, WATCH_CONNECT)) WatchThis (WATCHITM(rqptr), WATCH_CONNECT, "HTTP/2 connection preface (h2)"); if (!Http2Enabled) return (false); if (WATCHING (rqptr, WATCH_CONNECT)) WatchThis (WATCHITM(rqptr), WATCH_CONNECT, "HTTP/2 protocol"); h2ptr = Http2Create (rqptr); /* if HTTP/2 traffic has been pipelined with the preface */ if ((count = rqptr->NetIoPtr->ReadCount - Http2ClientPrefaceLength) > 0) { if (count > h2ptr->ReadBufferSize) ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI); memcpy (h2ptr->ReadBufferPtr, rqptr->rqNet.ReadBufferPtr + Http2ClientPrefaceLength, count); h2ptr->NetIoPtr->ReadStatus = SS$_NORMAL; h2ptr->NetIoPtr->ReadCount = count; SysDclAst (Http2NetClientReadAst, h2ptr); } else Http2NetClientRead (h2ptr); InstanceMutexLock (INSTANCE_MUTEX_HTTPD); NetUpdateConnected (rqptr, -1); NetUpdateConnected (h2ptr, +1); InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD); RequestEnd5 (rqptr); return (true); } /****************************************************************************/ /* This is an unencrypted connection (http:) with request header indicating a desire to upgrade to HTTP/2 (RFC 7540 3.2). Major browser communities indicate no interest in providing HTTP/2 over clear-text connections so it's a bit of a niche but supported by the RFC. Generate an HTTP/2 101 Switching Protocols response and then handle the original request in the HTTP/2 environment. Exercise using nghttp -nvu http:/// */ BOOL Http2SwitchResponse (REQUEST_STRUCT *rqptr) { static char Http2Switching [] = "HTTP/1.1 101 Switching Protocols\r\n\ Upgrade: h2c\r\n\ Connection: Upgrade\r\n\ \r\n"; int retval, SettingsLength; char *cptr; uchar SettingsBuffer [256]; HTTP2_STRUCT *h2ptr; HTTP2_STREAM_STRUCT *s2ptr; NETIO_STRUCT *ioptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_RESPONSE)) WatchThis (WATCHITM(rqptr), WATCH_MOD_RESPONSE, "Http2SwitchResponse()"); if (WATCHING (rqptr, WATCH_CONNECT)) WatchThis (WATCHITM(rqptr), WATCH_CONNECT, "HTTP/2 upgrade"); if (!Http2Enabled) return (false); if (rqptr->rqHeader.UpgradeHttp2onHttp && rqptr->ServicePtr->RequestScheme != SCHEME_HTTP) { rqptr->rqResponse.HttpStatus = 400; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_FORMAT), FI_LI); RequestEnd (rqptr); return (true); } if (rqptr->rqHeader.Http2SettingsPtr != NULL) { /* span the length of base-64 acceptable characters */ for (cptr = rqptr->rqHeader.Http2SettingsPtr; isalnum(*cptr) || *cptr == '+' || *cptr == '/' || *cptr == '='; *cptr++); SettingsLength = sizeof(SettingsBuffer); retval = base64_decode (SettingsBuffer, &SettingsLength, rqptr->rqHeader.Http2SettingsPtr, cptr - rqptr->rqHeader.Http2SettingsPtr); } if (rqptr->rqHeader.Http2SettingsPtr == NULL || retval != 0) { rqptr->rqResponse.HttpStatus = 400; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_FORMAT), FI_LI); RequestEnd (rqptr); return (true); } /****************/ /* 101 response */ /****************/ /* no update to status code counters as this is an intermediate response */ rqptr->rqResponse.HttpStatus = 101; rqptr->rqResponse.HeaderSent = true; /* blocking write (for the convenience) */ NetWrite (rqptr, NULL, Http2Switching, sizeof(Http2Switching)-1); rqptr->rqResponse.HttpStatus = 0; rqptr->rqResponse.HeaderSent = false; /******************/ /* HTTP/2 request */ /******************/ if (WATCHING (rqptr, WATCH_CONNECT)) WatchThis (WATCHITM(rqptr), WATCH_CONNECT, "HTTP/2 protocol"); /* these that got us here are definitely no longer the case */ DictRemove (rqptr->rqDictPtr, DICT_TYPE_REQUEST, "connection", 10); DictRemove (rqptr->rqDictPtr, DICT_TYPE_REQUEST, "http2-settings", 14); DictRemove (rqptr->rqDictPtr, DICT_TYPE_REQUEST, "upgrade", 7); /* and the associated flags */ rqptr->rqHeader.UpgradeHttp2onHttp = false; rqptr->rqHeader.ConnectionHttp2Settings = false; rqptr->rqHeader.ConnectionUpgrade = false; /* see Http2RequestBegin() for parallels with what's done here */ h2ptr = Http2Create (rqptr); h2ptr->ExpectingH2cPreface = true; /* attach the request to the HTTP/2 stream */ Http2RequestBegin2 (rqptr, h2ptr, 1); Http2RequestProcess (rqptr); Http2NetClientRead (h2ptr); /* adjust the connected from the request to the HTTP/2 connection */ InstanceMutexLock (INSTANCE_MUTEX_HTTPD); NetUpdateProcessing (rqptr, -1); NetUpdateConnected (rqptr, -1); NetUpdateConnected (h2ptr, +1); InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD); return (true); } /*****************************************************************************/ /* This is where the real fun begins. Create and initialise an HTTP/2 structure, using some data from the request structure. Return a pointer to the structure. */ HTTP2_STRUCT* Http2Create (REQUEST_STRUCT *rqptr) { HTTP2_STRUCT *h2ptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_HTTP2)) WatchThis (WATCHITM(rqptr), WATCH_MOD_HTTP2, "Http2Create()"); h2ptr = VmHttp2Get (); /* add to the HTTP/2 connection list */ ListAddHead (&Http2List, h2ptr, LIST_ENTRY_TYPE_HTTP2); h2ptr->NetIoPtr = rqptr->NetIoPtr; h2ptr->ConnectNumber = rqptr->ConnectNumber; h2ptr->WatchItem = rqptr->WatchItem; h2ptr->NetIoPtr->WatchItem = h2ptr->WatchItem; PUT_QUAD_QUAD (&rqptr->rqTime.BeginTime64, h2ptr->ConnectTime64); /* always use original client data (see MapUrl_SetClientAddress()) */ if (rqptr->ClientResetPtr) h2ptr->ClientPtr = rqptr->ClientResetPtr; else h2ptr->ClientPtr = rqptr->ClientPtr; /* use the same service of the upgrade request */ h2ptr->ServicePtr = rqptr->ServicePtr; /* intialise server protocol parameters */ h2ptr->ServerInitialWindowSize = Http2InitialWindowSize; h2ptr->ServerMaxConcStreams = Http2MaxConcurrentStreams; h2ptr->ServerMaxFrameSize = Http2MaxFrameSize; h2ptr->ServerMaxHeaderListSize = Http2MaxHeaderListSize; h2ptr->ServerMaxHeaderTableSize = Http2MaxHeaderTableSize; h2ptr->ServerPushPromise = 0; /* intialise client protocol parameters */ h2ptr->ClientInitialWindowSize = HTTP2_INITIAL_WINDOW_SIZE; h2ptr->ClientMaxConcStreams = HTTP2_INITIAL_MAX_CONC_STREAMS; h2ptr->ClientMaxFrameSize = HTTP2_INITIAL_MAX_FRAME_SIZE; h2ptr->ClientMaxHeaderListSize = HTTP2_INITIAL_HEAD_LIST_SIZE; h2ptr->ClientMaxHeaderTableSize = HTTP2_INITIAL_HEAD_TAB_SIZE; h2ptr->ClientPushPromise = 0; /* HPACK initialisation */ h2ptr->HpackClientTable.max = Http2MaxHeaderTableSize; h2ptr->HpackServerTable.max = Http2MaxHeaderTableSize; h2ptr->HpackClientTable.h2ptr = h2ptr->HpackServerTable.h2ptr = h2ptr; /* initial flow control */ h2ptr->ReadWindowSize == h2ptr->ServerInitialWindowSize; h2ptr->WriteWindowSize = h2ptr->ClientInitialWindowSize; /* send server settings */ Http2Settings (h2ptr, 0, NULL, 0); /* set the initial connection flow-control window size */ h2ptr->ReadWindowSize = h2ptr->ServerInitialWindowSize * h2ptr->ServerMaxConcStreams; Http2WindowUpdate (h2ptr, 0, h2ptr->ReadWindowSize, NULL, 0); /* buffer for multiplexed reads */ h2ptr->ReadBufferSize = h2ptr->ServerMaxFrameSize + 128; h2ptr->ReadBufferPtr = VmGet2Heap (h2ptr, h2ptr->ReadBufferSize); return (h2ptr); } /*****************************************************************************/ /* Close the HTTP/2 protocol connection with the client. Cancelling I/O will rundown any in-progress (primarily read) I/O, and the zeroed channel indicates the network connection has been severed. If there's still I/O in progress wait for it to rundown. If there are still requests associated with the connection wait for them to end (based on delivered CANCELed I/O). Then check for any connection management writes still queued. Finally a check for straggling connection I/O in progress. After that the connection is removed from the list and the associated memory released in an indepedent AST. This function should only be called the once by HTTP/2 processing code when the channel number is non-zero. After that the Http2Supervisor() calls it every second until all the above conditions are met. Once removed from the connection list it's gone! */ void Http2CloseConnection (HTTP2_STRUCT *h2ptr) { BOOL OneShot; int idx; HTTP2_STREAM_STRUCT *s2ptr; REQUEST_STRUCT *rqeptr; /*********/ /* begin */ /*********/ if (WATCHMOD (h2ptr, WATCH_MOD_HTTP2)) WatchThis (WATCHITM(h2ptr), WATCH_MOD_HTTP2, "Http2CloseConnection() !&F chan:!UL list:!UL read:!&B write:!&B", Http2CloseConnection, h2ptr->NetIoPtr->Channel, LIST_GET_COUNT (&h2ptr->StreamList), NETIO_READ_IN_PROGRESS(h2ptr->NetIoPtr), NETIO_WRITE_IN_PROGRESS(h2ptr->NetIoPtr)); /* the connection is now defunct */ if (!h2ptr->NetIoPtr->VmsStatus) NetIoCancel (h2ptr->NetIoPtr); /* if there are still requests associated with this connection */ if (LIST_NOT_EMPTY (&h2ptr->StreamList)) { /* the cascade of CANCELed I/O should result in requests ending */ for (s2ptr = LIST_GET_HEAD(&h2ptr->StreamList); s2ptr != NULL; s2ptr = LIST_GET_NEXT(s2ptr)) { rqeptr = (REQUEST_STRUCT*)s2ptr->RequestPtr; /* if the request stream is already being run down */ if (rqeptr->NetIoPtr->VmsStatus) continue; Http2RequestCancel (rqeptr); Http2RequestResetStream (rqeptr); } if (WATCHMOD (h2ptr, WATCH_MOD_HTTP2)) WatchThis (WATCHITM(h2ptr), WATCH_MOD_HTTP2, "streams: !UL", LIST_GET_COUNT (&h2ptr->StreamList)); return; } /* if there are still queued (connection management) writes waiting */ for (idx = HTTP2_WRITE_QUEUE_MAX; idx <= HTTP2_WRITE_QUEUE_LOW; idx++) if (LIST_NOT_EMPTY (&h2ptr->QueuedWriteList[idx])) return; /* any last gasp I/O still in progress */ if (NETIO_IN_PROGRESS (h2ptr->NetIoPtr)) return; /***********/ /* finally */ /***********/ if (WATCHPNT(h2ptr)) { if (WATCH_CATEGORY(WATCH_HTTP2)) WatchThis (WATCHITM(h2ptr), WATCH_HTTP2, "FRAMES tx:!UL rx:!UL REQUESTS tx:!UL rx:!UL total:!UL peak:!UL", h2ptr->FrameCountTx, h2ptr->FrameCountRx, h2ptr->FrameRequestCountTx, h2ptr->FrameRequestCountRx, h2ptr->RequestCount, h2ptr->RequestPeak); if (WATCH_CATEGORY(WATCH_HTTP2)) WatchThis (WATCHITM(h2ptr), WATCH_HTTP2, "HTTP/2 closed"); else if (WATCH_CATEGORY(WATCH_CONNECT)) WatchThis (WATCHITM(h2ptr), WATCH_CONNECT, "HTTP/2 closed"); } NetIoEnd (h2ptr->NetIoPtr); InstanceMutexLock (INSTANCE_MUTEX_HTTPD); NetUpdateConnected (h2ptr, -1); InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD); /* remove from the HTTP/2 connection list */ ListRemove (&Http2List, h2ptr); VmFree2Heap (h2ptr, FI_LI); /* if this HTTP/2 connection was being (one-shot) WATCHed */ OneShot = h2ptr->WatchItem & WATCH_ITEM_HTTP2_FLAG; VmHttp2Free (h2ptr, FI_LI); if (OneShot) { SysDclAst (RequestEnd, Watch.RequestPtr); WatchEnd (); } } /*****************************************************************************/ /* An HTTP/2 error has been encountered. Signal this to the client as a non-fatal stream error or a fatal connection error. Return zero to continue, non-zero to abort the connection. */ int Http2Error ( HTTP2_STRUCT *h2ptr, uint ident, uint error ) { int retval; char *cptr; /*********/ /* begin */ /*********/ if (WATCHMOD (h2ptr, WATCH_MOD_HTTP2)) WatchThis (WATCHITM(h2ptr), WATCH_MOD_HTTP2, "Http2Error() !SL", error); if (WATCHING (h2ptr, WATCH_HTTP2)) Http2WatchError (h2ptr, error); switch (error) { /* stream oriented non-fatal errors */ case HTTP2_ERROR_NONE : case HTTP2_ERROR_REFUSED : case HTTP2_ERROR_CANCEL : case HTTP2_ERROR_CALM : case HTTP2_ERROR_HTTP11 : retval = Http2ResetStream (h2ptr, ident, 0, NULL, 0); if (retval >= 0) return (0); Http2GoAway (h2ptr, -(int)retval, NULL, 0); return (HTTP2_ERROR_INTERNAL); break; /* connection breaking errors */ case HTTP2_ERROR_PROTOCOL : case HTTP2_ERROR_INTERNAL : case HTTP2_ERROR_FLOW : case HTTP2_ERROR_TIMEOUT : case HTTP2_ERROR_CLOSED : case HTTP2_ERROR_SIZE : case HTTP2_ERROR_COMPRESS : case HTTP2_ERROR_CONNECT : case HTTP2_ERROR_SECURITY : Http2GoAway (h2ptr, error, NULL, 0); return (error); break; default : Http2GoAway (h2ptr, HTTP2_ERROR_INTERNAL, NULL, 0); return (HTTP2_ERROR_INTERNAL); } } /*****************************************************************************/ /* Send and receive GOAWAY frames. */ int Http2GoAway ( HTTP2_STRUCT *h2ptr, uint error, uchar *bptr, uint blen ) { uint size, stream; uchar *cptr, *czptr, *sptr, *zptr; uchar buf [256]; HTTP2_WRITE_STRUCT *w2ptr; /*********/ /* begin */ /*********/ if (WATCHMOD (h2ptr, WATCH_MOD_HTTP2)) WatchThis (WATCHITM(h2ptr), WATCH_MOD_HTTP2, "Http2GoAway() blen:!UL", blen); if (bptr != NULL) { /**********************/ /* goaway from client */ /**********************/ if (blen < 8) return (-(HTTP2_ERROR_SIZE)); if (WATCHING (h2ptr, WATCH_HTTP2)) Http2WatchGoaway (h2ptr, bptr, blen); HTTP2_GET_32 (bptr, stream); stream &= 0x7fffffff; HTTP2_GET_32 (bptr, error); zptr = (sptr = buf) + sizeof(buf)-1; if (blen > 8) { czptr = (cptr = bptr) + blen - 8; while (cptr < czptr && sptr < zptr) *sptr++ = *cptr++; } *sptr = '\0'; if (stream) return (-(HTTP2_ERROR_PROTOCOL)); h2ptr->GoAwayLastStreamIdent = stream; } else { /********************/ /* goaway to client */ /********************/ h2ptr->GoAwayIdent = h2ptr->LastStreamIdent; if (!bptr) bptr = ""; for (cptr = bptr; *cptr; cptr++); size = cptr - bptr; if (size <= (HTTP2_WRITE_PAYLOAD - 8)) size = 0; w2ptr = VmGet2Heap (h2ptr, sizeof(HTTP2_WRITE_STRUCT)+size); sptr = bptr; bptr = w2ptr->payload; HTTP2_PUT_32 (bptr, h2ptr->GoAwayIdent); HTTP2_PUT_32 (bptr, error); while (*bptr) *bptr++ = *sptr++; *bptr = '\0'; HTTP2_PLACE_24 (w2ptr->length, bptr - w2ptr->payload); HTTP2_PLACE_8 (w2ptr->type, HTTP2_FRAME_GOAWAY); HTTP2_PLACE_8 (w2ptr->flags, 0); HTTP2_PLACE_32 (w2ptr->ident, 0); w2ptr->Http2Ptr = h2ptr; w2ptr->AstParam = h2ptr; /* send the goaway frame and once sent close the connection */ w2ptr->AstFunction = Http2CloseConnection; w2ptr->DataPtr = w2ptr->payload; w2ptr->DataLength = bptr - w2ptr->payload; if (WATCHING (h2ptr, WATCH_HTTP2)) Http2WatchGoaway (h2ptr, w2ptr->DataPtr, w2ptr->DataLength); Http2NetQueueWrite (h2ptr, w2ptr); } /* if not gone after this period then close the connection anyway */ h2ptr->GoAwaySecond = HttpdTickSecond + HTTP2_TIMEOUT_GOAWAY_SECONDS; return (0); } /*****************************************************************************/ /* Send and receive PING frames. */ int Http2Ping ( HTTP2_STRUCT *h2ptr, uint flags, uchar *bptr, uint blen ) { uchar *sptr; ulong ResultTime64 [QUAD2]; HTTP2_WRITE_STRUCT *w2ptr; /*********/ /* begin */ /*********/ if (WATCHMOD (h2ptr, WATCH_MOD_HTTP2)) WatchThis (WATCHITM(h2ptr), WATCH_MOD_HTTP2, "Http2Ping() blen:!UL", blen); if (bptr != NULL) { /********************/ /* ping from client */ /********************/ if (blen != 8) return (-(HTTP2_ERROR_PROTOCOL)); if (flags & HTTP2_FLAG_PING_ACK) { /*******************/ /* ack from client */ /*******************/ /* client acknowledging server ping */ if (QUAD_EQUAL_QUAD (bptr, h2ptr->PingTime64)) { /* the number of 100 nano-seconds (up to 100 seconds) */ sys$gettim (ResultTime64); SUB_QUAD_QUAD (h2ptr->PingTime64, ResultTime64); if (ResultTime64[1]) (int)h2ptr->PingMicroSeconds = -1; else h2ptr->PingMicroSeconds = ResultTime64[0] / 10; } else (int)h2ptr->PingMicroSeconds = -1; if (WATCHING (h2ptr, WATCH_HTTP2)) WatchThis (WATCHITM(h2ptr), WATCH_HTTP2, (int)h2ptr->PingMicroSeconds == -1 ? "PING client ERROR" : "PING !UL.!3ZLmS", h2ptr->PingMicroSeconds / 1000, h2ptr->PingMicroSeconds % 1000); h2ptr->PingBackTickSecond = 0; PUT_ZERO_QUAD (h2ptr->PingTime64); return (0); } /*****************/ /* ack to client */ /*****************/ if (WATCHING (h2ptr, WATCH_HTTP2)) WatchThis (WATCHITM(h2ptr), WATCH_HTTP2, "PING server ACKNOWLEDGE"); w2ptr = VmGet2Heap (h2ptr, sizeof(HTTP2_WRITE_STRUCT)); /* replay the client payload */ sptr = w2ptr->payload; while (blen--) *sptr++ = *bptr++; HTTP2_PLACE_24 (w2ptr->length, 8); HTTP2_PLACE_8 (w2ptr->type, HTTP2_FRAME_PING); HTTP2_PLACE_8 (w2ptr->flags, HTTP2_FLAG_PING_ACK); HTTP2_PLACE_32 (w2ptr->ident, 0); w2ptr->Http2Ptr = h2ptr; w2ptr->DataPtr = w2ptr->payload; w2ptr->DataLength = 8; } else { /******************/ /* ping to client */ /******************/ /* only one outstanding with the specified timeout */ if (h2ptr->PingBackTickSecond) if (HttpdTickSecond < h2ptr->PingBackTickSecond) return (0); w2ptr = VmGet2Heap (h2ptr, sizeof(HTTP2_WRITE_STRUCT)); /* generate and note the ping time */ sys$gettim (h2ptr->PingTime64); PUT_QUAD_QUAD (h2ptr->PingTime64, w2ptr->payload); h2ptr->PingBackTickSecond = HttpdTickSecond + HTTP2_PING_RESPONSE_MAX; HTTP2_PLACE_24 (w2ptr->length, 8); HTTP2_PLACE_8 (w2ptr->type, HTTP2_FRAME_PING); HTTP2_PLACE_8 (w2ptr->flags, 0); HTTP2_PLACE_32 (w2ptr->ident, 0); w2ptr->Http2Ptr = h2ptr; w2ptr->DataPtr = w2ptr->payload; w2ptr->DataLength = 8; if (WATCHING (h2ptr, WATCH_HTTP2)) WatchThis (WATCHITM(h2ptr), WATCH_HTTP2, "PING to client"); } Http2NetQueueWrite (h2ptr, w2ptr); return (0); } /*****************************************************************************/ /* HTTP/2 priority/dependency is not currently implemented. Just check the protocol parameters for error. Weight is weight plus one, 1..256. */ int Http2Priority ( HTTP2_STRUCT *h2ptr, uint flags, uchar *bptr, uint blen ) { uint stream, value, weight; /*********/ /* begin */ /*********/ if (WATCHMOD (h2ptr, WATCH_MOD_HTTP2)) WatchThis (WATCHITM(h2ptr), WATCH_MOD_HTTP2, "Http2Priority() blen:!UL", blen); if (blen != 5) return (-(HTTP2_ERROR_SIZE)); HTTP2_GET_32 (bptr, stream); HTTP2_GET_8 (bptr, weight); /* any stream up to maximum allowed */ if (stream & 0x80000000) return (-(HTTP2_ERROR_PROTOCOL)); return (0); } /*****************************************************************************/ /* Mark the stream as closed. When the server closes a stream (as when a request times out) send a RST_STREAM frame to the client. */ int Http2ResetStream ( HTTP2_STRUCT *h2ptr, uint ident, uint error, uchar *bptr, uint blen ) { HTTP2_STREAM_STRUCT *s2ptr; HTTP2_WRITE_STRUCT *w2ptr; REQUEST_STRUCT *rqeptr; /*********/ /* begin */ /*********/ if (WATCHMOD (h2ptr, WATCH_MOD_HTTP2)) WatchThis (WATCHITM(h2ptr), WATCH_MOD_HTTP2, "Http2ResetStream() blen:!UL ident:!UL error:!UL", blen, ident, error); if (ident == 0) return (-(HTTP2_ERROR_PROTOCOL)); /* locate the request corresponding to the stream */ for (s2ptr = LIST_GET_HEAD(&h2ptr->StreamList); s2ptr != NULL; s2ptr = LIST_GET_NEXT(s2ptr)) { rqeptr = (REQUEST_STRUCT*)s2ptr->RequestPtr; if (rqeptr->Http2Stream.Ident == ident) break; } if (s2ptr == NULL) { /* no stream found with that ident */ if (ident > h2ptr->LastStreamIdent) return (-(HTTP2_ERROR_PROTOCOL)); /* could be a hangover from a previous (closed) stream so ignore */ return (HTTP2_ERROR_NONE); } /* only do it the once */ if (rqeptr->NetIoPtr->VmsStatus == SS$_ABORT) return (HTTP2_ERROR_NONE); /* the stream is now defunct */ rqeptr->NetIoPtr->VmsStatus = SS$_ABORT; if (bptr != NULL) { /*******************/ /* reset by client */ /*******************/ if (blen != 4) return (-(HTTP2_ERROR_SIZE)); /* RFC 7540 6.4 */ if (rqeptr->Http2Stream.State == HTTP2_STATE_IDLE) return (-(HTTP2_ERROR_PROTOCOL)); Http2RequestCancel (rqeptr); } else { /*******************/ /* reset by server */ /*******************/ w2ptr = VmGet2Heap (h2ptr, sizeof(HTTP2_WRITE_STRUCT)); HTTP2_PLACE_24 (w2ptr->length, 4); HTTP2_PLACE_8 (w2ptr->type, HTTP2_FRAME_RST_STREAM); HTTP2_PLACE_8 (w2ptr->flags, 0); HTTP2_PLACE_32 (w2ptr->ident, ident); w2ptr->Http2Ptr = h2ptr; w2ptr->DataPtr = w2ptr->payload; w2ptr->DataLength = 4; HTTP2_PLACE_32 (w2ptr->payload, error); Http2NetQueueWrite (h2ptr, w2ptr); } return (HTTP2_ERROR_NONE); } /*****************************************************************************/ /* Send and receive HTTP/2 settings. */ int Http2Settings ( HTTP2_STRUCT *h2ptr, uint flags, uchar *bptr, uint blen ) { uint count, setting, value; uchar *aptr; HTTP2_WRITE_STRUCT *w2ptr; /*********/ /* begin */ /*********/ if (WATCHMOD (h2ptr, WATCH_MOD_HTTP2)) WatchThis (WATCHITM(h2ptr), WATCH_MOD_HTTP2, "Http2Settings() flags:0x!2XL len:!UL (!UL)", flags, blen, blen / 6); if (bptr != NULL) { /***************/ /* from client */ /***************/ /* all setting are in multiples of 6 octets */ if (blen % 6) return (-(HTTP2_ERROR_SIZE)); count = blen / 6; if (flags & HTTP2_FLAG_SETTINGS_ACK) { /**************/ /* client ack */ /**************/ /* payload must be empty */ if (count) return (-(HTTP2_ERROR_PROTOCOL)); return (0); } /*********************/ /* client setting(s) */ /*********************/ if (WATCHING (h2ptr, WATCH_HTTP2)) Http2WatchSettings (h2ptr, "client", bptr, blen); while (count--) { HTTP2_GET_8 (bptr, setting); HTTP2_GET_32 (bptr, value); /* process client requested settings */ switch (setting) { case HTTP2_SETTING_MAX_HEAD_TABLE_SIZE : h2ptr->ClientMaxHeaderTableSize = value; h2ptr->HpackClientTable.max = value; break; case HTTP2_SETTING_ENABLE_PUSH : h2ptr->ClientPushPromise = value; break; case HTTP2_SETTING_MAX_CONC_STREAMS : h2ptr->ClientMaxConcStreams = value; break; case HTTP2_SETTING_INIT_WIN_SIZE : Http2FlowControl (h2ptr, value); break; case HTTP2_SETTING_MAX_FRAME_SIZE : h2ptr->ClientMaxFrameSize = value; break; case HTTP2_SETTING_MAX_HEAD_LIST_SIZE : h2ptr->ClientMaxHeaderListSize = value; break; default : /* ignore unknown settings RFC 7540 6.5.2 */ } } /*****************/ /* ack to client */ /*****************/ if (WATCHING (h2ptr, WATCH_HTTP2)) WatchThis (WATCHITM(h2ptr), WATCH_HTTP2, "SETTINGS server ACKNOWLEDGE"); w2ptr = VmGet2Heap (h2ptr, sizeof(HTTP2_WRITE_STRUCT)); HTTP2_PLACE_24 (w2ptr->length, 0); HTTP2_PLACE_8 (w2ptr->type, HTTP2_FRAME_SETTINGS); HTTP2_PLACE_8 (w2ptr->flags, HTTP2_FLAG_SETTINGS_ACK); HTTP2_PLACE_32 (w2ptr->ident, 0); w2ptr->Http2Ptr = h2ptr; w2ptr->DataPtr = w2ptr->payload; w2ptr->DataLength = 0; } else { /*************/ /* to client */ /*************/ w2ptr = VmGet2Heap (h2ptr, sizeof(HTTP2_WRITE_STRUCT)); bptr = w2ptr->payload; HTTP2_PUT_16 (bptr, HTTP2_SETTING_MAX_HEAD_TABLE_SIZE); HTTP2_PUT_32 (bptr, h2ptr->ServerMaxHeaderTableSize); HTTP2_PUT_16 (bptr, HTTP2_SETTING_MAX_CONC_STREAMS); HTTP2_PUT_32 (bptr, h2ptr->ServerMaxConcStreams); HTTP2_PUT_16 (bptr, HTTP2_SETTING_INIT_WIN_SIZE); HTTP2_PUT_32 (bptr, h2ptr->ServerInitialWindowSize); HTTP2_PUT_16 (bptr, HTTP2_SETTING_MAX_FRAME_SIZE); HTTP2_PUT_32 (bptr, h2ptr->ServerMaxFrameSize); HTTP2_PUT_16 (bptr, HTTP2_SETTING_MAX_HEAD_LIST_SIZE); HTTP2_PUT_32 (bptr, h2ptr->ServerMaxHeaderListSize); HTTP2_PLACE_24 (w2ptr->length, bptr - w2ptr->payload); HTTP2_PLACE_8 (w2ptr->type, HTTP2_FRAME_SETTINGS); HTTP2_PLACE_8 (w2ptr->flags, 0); HTTP2_PLACE_32 (w2ptr->ident, 0); w2ptr->Http2Ptr = h2ptr; w2ptr->DataPtr = w2ptr->payload; w2ptr->DataLength = bptr - w2ptr->payload; if (WATCHING (h2ptr, WATCH_HTTP2)) Http2WatchSettings (h2ptr, "server", w2ptr->DataPtr, w2ptr->DataLength); } Http2NetQueueWrite (h2ptr, w2ptr); return (0); } /*****************************************************************************/ /* Client setting change to initial window size initiates connection-wide flow control window adjustment. The resulting window size can be negative (RFC 7540 6.9.2). Blocked flow control may now resume, or vice-versa. */ void Http2FlowControl ( HTTP2_STRUCT *h2ptr, uint value ) { int adjust; HTTP2_STREAM_STRUCT *s2ptr; /*********/ /* begin */ /*********/ if (WATCHMOD (h2ptr, WATCH_MOD_HTTP2)) WatchThis (WATCHITM(h2ptr), WATCH_MOD_HTTP2, "Http2FlowControl() !UL", value); adjust = -(h2ptr->ClientInitialWindowSize - value); for (s2ptr = LIST_GET_HEAD(&h2ptr->StreamList); s2ptr != NULL; s2ptr = LIST_GET_NEXT(s2ptr)) s2ptr->WriteWindowSize += adjust; h2ptr->ClientInitialWindowSize = value; /* resume writing if flow control was blocking and now permits */ Http2NetQueueWrite (h2ptr, NULL); } /*****************************************************************************/ /* Flow control feedback from the client. */ int Http2WindowUpdate ( HTTP2_STRUCT *h2ptr, uint ident, uint update, uchar *bptr, uint blen ) { uint size; HTTP2_STREAM_STRUCT *s2ptr; REQUEST_STRUCT *rqeptr; HTTP2_WRITE_STRUCT *w2ptr; /*********/ /* begin */ /*********/ if (WATCHMOD (h2ptr, WATCH_MOD_HTTP2)) WatchThis (WATCHITM(h2ptr), WATCH_MOD_HTTP2, "Http2WindowUpdate() blen:!UL ident:!UL", blen, ident); if (bptr != NULL) { /***************/ /* from client */ /***************/ if (blen != 4) return (-(HTTP2_ERROR_SIZE)); HTTP2_GET_32 (bptr, update); if (WATCHING (h2ptr, WATCH_HTTP2) && !WATCHING1S(h2ptr) && !WATCHING2(h2ptr)) WatchThis (WATCHITM(h2ptr), WATCH_HTTP2, "WINDOW client ident:!UL update:!UL", ident, update); if (update == 0 || update & 0x80000000) return (-(HTTP2_ERROR_PROTOCOL)); if (ident) { /**********/ /* stream */ /**********/ /* locate the request corresponding to the stream */ for (s2ptr = LIST_GET_HEAD(&h2ptr->StreamList); s2ptr != NULL; s2ptr = LIST_GET_NEXT(s2ptr)) { rqeptr = (REQUEST_STRUCT*)s2ptr->RequestPtr; if (rqeptr->Http2Stream.Ident == ident) break; } if (s2ptr == NULL) { /* no stream found with that ident */ if (ident > h2ptr->LastStreamIdent) return (-(HTTP2_ERROR_PROTOCOL)); /* could be a hangover from a previous (closed) stream so ignore */ return (HTTP2_ERROR_NONE); } size = (s2ptr->WriteWindowSize += update); } else { /**************/ /* connection */ /**************/ size = (h2ptr->WriteWindowSize += update); } /* the most significant bit indicates overflow */ if (size & 0x80000000) return (-(HTTP2_ERROR_PROTOCOL)); /* resume writing if flow control was blocking and now permits */ if (!NETIO_WRITE_IN_PROGRESS(h2ptr->NetIoPtr)) Http2NetQueueWrite (h2ptr, NULL); } else { /*************/ /* to client */ /*************/ if (WATCHING (h2ptr, WATCH_HTTP2) && !WATCHING1S(h2ptr) && !WATCHING2(h2ptr)) WatchThis (WATCHITM(h2ptr), WATCH_HTTP2, "WINDOW server ident:!UL update:!UL", ident, update); if (update == 0 || update & 0x80000000) { /* note and ignore */ ErrorNoticed (NULL, SS$_BUGCHECK, ErrorSanityCheck, FI_LI); return (0); } w2ptr = VmGet2Heap (h2ptr, sizeof(HTTP2_WRITE_STRUCT)); HTTP2_PLACE_24 (w2ptr->length, 4); HTTP2_PLACE_8 (w2ptr->type, HTTP2_FRAME_WINDOW_UPDATE); HTTP2_PLACE_8 (w2ptr->flags, 0); HTTP2_PLACE_32 (w2ptr->ident, ident); HTTP2_PLACE_32 (w2ptr->payload, update); w2ptr->Http2Ptr = h2ptr; w2ptr->DataPtr = w2ptr->payload; w2ptr->DataLength = 4; Http2NetQueueWrite (h2ptr, w2ptr); } return (0); } /*****************************************************************************/ /* Return a pointer to a string respresenting the supplied error code. */ char* Http2ErrorString (int error) { /*********/ /* begin */ /*********/ if (error > HTTP2_ERROR_COUNT) return ("*UNKNOWN*"); return (Http2ErrorArray[error]); } /*****************************************************************************/ /* Scan the HTTP/2 connection list for connections that have been idle for the timeout period and close them down elegantly. */ BOOL Http2Supervisor () { HTTP2_STRUCT *h2ptr, *next2; /*********/ /* begin */ /*********/ /** if (WATCH_MODULE(WATCH_MOD_HTTP2)) WatchThis (WATCHALL, WATCH_MOD_HTTP2, "Http2Supervisor()"); **/ /* process the connection list from least to most recent */ for (h2ptr = LIST_GET_HEAD(&Http2List); h2ptr != NULL; h2ptr = next2) { /* get (any) next in list in case the current connection is closed */ next2 = LIST_GET_NEXT(h2ptr); if (h2ptr->NetIoPtr->VmsStatus) { /* with each call check if the connection can be closed */ Http2CloseConnection (h2ptr); continue; } if (h2ptr->GoAwayLastStreamIdent) { /* connection has been told to goaway */ Http2CloseConnection (h2ptr); continue; } if (h2ptr->GoAwaySecond && h2ptr->GoAwaySecond < HttpdTickSecond) { if (WATCHING (h2ptr, WATCH_HTTP2)) WatchThis (WATCHITM(h2ptr), WATCH_HTTP2, "TIMEOUT goaway"); Http2CloseConnection (h2ptr); continue; } if (h2ptr->IdleSecond && h2ptr->IdleSecond < HttpdTickSecond) { if (WATCHING (h2ptr, WATCH_HTTP2)) WatchThis (WATCHITM(h2ptr), WATCH_HTTP2, "TIMEOUT idle"); Http2CloseConnection (h2ptr); continue; } if (h2ptr->PingBackTickSecond && h2ptr->PingBackTickSecond < HttpdTickSecond) { if (WATCHING (h2ptr, WATCH_HTTP2)) WatchThis (WATCHITM(h2ptr), WATCH_HTTP2, "TIMEOUT ping"); h2ptr->PingBackTickSecond = 0; } else if (Http2PingTimerSeconds && (h2ptr->PingSendTickSecond == 0 || h2ptr->PingSendTickSecond < HttpdTickSecond)) { /* send a ping every so-many seconds */ if (WATCHING (h2ptr, WATCH_HTTP2)) WatchThis (WATCHITM(h2ptr), WATCH_HTTP2, "TIME to ping"); Http2Ping (h2ptr, 0, NULL, 0); h2ptr->PingSendTickSecond = HttpdTickSecond + Http2PingTimerSeconds; } } return (LIST_NOT_EMPTY(&Http2List)); } /*****************************************************************************/ /* */ void Http2Report ( REQUEST_STRUCT *rqptr, REQUEST_AST NextTaskFunction ) { static char PageBeginFao [] = "

\n\ \n\
\n\ \n\ \ \ \ \ \n\ \ \ \ \ \n\ \ \ \n\ \n\ \n\ \ \ \n\ \n\ \n\ \ \ \n\ \n\ \n\ \ \n\ \n\ \n\ \n\ \n\ \n\ \
HTTP/2HTTP/1.n
Requests  /Total:!&L(!UL%)!&L(!UL%)
/Peak:!&L!&L
Bytes  /Min:!&,@SQ!&,@SQ
/Max:!&,@SQ!&,@SQ
/Ave:!&L!&L
Duration  /Min:!AZ!AZ
/Max:!AZ!AZ
/Ave:!AZ!AZ
Bytes/Sec  /Min:!&L!&L
/Max:!&L!&L
/Ave:!&L!&L
\ Frames  /Total:!&,@SQ
/Request:!&,@SQ(!UL%)
/Average:!&L
/Flow Cntrl:!&,@SQ(!UL%)
/Rx:!&,@SQ
/Tx:!&,@SQ
\n\
\n"; /* the final column just adds a little white-space on the page far right */ static char Http2TableFao [] = "

\n\ \ \ \ \ \ \ \n\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \n\ \n"; /* the empty 99% column just forces the rest left */ static char Http2EntryFao [] = "\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \n"; static char Http2EmptyFao [] = "\ \n"; static char PageEndFao [] = "
FramesBytesWrite QueueHPACKRequests
ServiceClientDurationRxTxFCRxTx0HNLPkRxTxTotPkCurWATCH
!3ZL!AZ//!AZ!AZ,!UL!AZ!&L!&L!UL!&,@SQ!&,@SQ!UL!UL!UL!UL!UL!UL%!UL%!&L!UL!UL!&@
000empty
\n\

\n\ \n\
\n\
\n\ \n\
\n\
\n\
\n\ \n\
\n\
\n\ !AZ\ \n\ \n\ \n"; int idx, status, EntryCount, RequestHttpCount; ulong Time64 [QUAD2], ConnectTime64 [QUAD2], FrameAverage [QUAD2], FrameRequest [QUAD2], FrameTotal [QUAD2]; ulong FaoVector [48]; ulong *vecptr; char *cptr; HTTP2_STRUCT *h2ptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_HTTP2)) WatchThis (WATCHITM(rqptr), WATCH_MOD_HTTP2, "Http2Report()"); AdminPageTitle (rqptr, "HTTP Report"); vecptr = FaoVector; InstanceMutexLock (INSTANCE_MUTEX_HTTPD); RequestHttpCount = acptr->RequestHttp2Count + acptr->RequestHttp11Count + acptr->RequestHttp10Count; *vecptr++ = acptr->RequestHttp2Count; *vecptr++ = PercentOf (acptr->RequestHttp2Count, RequestHttpCount); *vecptr++ = acptr->RequestHttp11Count + acptr->RequestHttp10Count; *vecptr++ = PercentOf (acptr->RequestHttp11Count + acptr->RequestHttp10Count, RequestHttpCount); *vecptr++ = acptr->ProcessingPeak[HTTP2]; *vecptr++ = acptr->ProcessingPeak[HTTP1]; *vecptr++ = acptr->BytesPerSecondMinBytes[HTTP2]; *vecptr++ = acptr->BytesPerSecondMinBytes[HTTP1]; *vecptr++ = acptr->BytesPerSecondMaxBytes[HTTP2]; *vecptr++ = acptr->BytesPerSecondMaxBytes[HTTP1]; if (acptr->RequestHttp2Count) *vecptr++ = (ulong)(FLOAT_FROM_QUAD(acptr->BytesPerSecondRawTotal[HTTP2]) / (float)acptr->RequestHttp2Count); else *vecptr++ = 0; if (acptr->RequestHttp11Count + acptr->RequestHttp10Count) *vecptr++ = (ulong)(FLOAT_FROM_QUAD(acptr->BytesPerSecondRawTotal[HTTP1]) / (float)(acptr->RequestHttp11Count + acptr->RequestHttp10Count)); else *vecptr++ = 0; *vecptr++ = DurationString (rqptr, acptr->ResponseDurationMin[HTTP2]); *vecptr++ = DurationString (rqptr, acptr->ResponseDurationMin[HTTP1]); *vecptr++ = DurationString (rqptr, acptr->ResponseDurationMax[HTTP2]); *vecptr++ = DurationString (rqptr, acptr->ResponseDurationMax[HTTP1]); *vecptr++ = AverageDurationString (rqptr, acptr->ResponseDuration[HTTP2], acptr->ResponseDurationCount[HTTP2]); *vecptr++ = AverageDurationString (rqptr, acptr->ResponseDuration[HTTP1], acptr->ResponseDurationCount[HTTP1]); *vecptr++ = acptr->BytesPerSecondMin[HTTP2]; *vecptr++ = acptr->BytesPerSecondMin[HTTP1]; *vecptr++ = acptr->BytesPerSecondMax[HTTP2]; *vecptr++ = acptr->BytesPerSecondMax[HTTP1]; *vecptr++ = acptr->BytesPerSecondAve[HTTP2]; *vecptr++ = acptr->BytesPerSecondAve[HTTP1]; PUT_QUAD_QUAD (acptr->Http2FrameCountRx, FrameTotal); ADD_QUAD_QUAD (acptr->Http2FrameCountTx, FrameTotal); *vecptr++ = FrameTotal; PUT_QUAD_QUAD (acptr->Http2FrameRequestCountRx, FrameRequest); ADD_QUAD_QUAD (acptr->Http2FrameRequestCountTx, FrameRequest); *vecptr++ = FrameRequest; *vecptr++ = QuadPercentOf (FrameRequest, FrameTotal); if (acptr->RequestHttp2Count) *vecptr++ = (ulong)(FLOAT_FROM_QUAD(FrameTotal) / (float)acptr->RequestHttp2Count); else *vecptr++ = 0; *vecptr++ = acptr->Http2FlowControlCount; *vecptr++ = QuadPercentOf (acptr->Http2FlowControlCount, acptr->Http2FlowFrameCount); *vecptr++ = acptr->Http2FrameCountRx; *vecptr++ = acptr->Http2FrameCountTx; InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD); FaoCheck (sizeof(FaoVector), &FaoVector, vecptr, FI_LI); status = FaolToNet (rqptr, PageBeginFao, &FaoVector); if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI); /***********************/ /* HTTP/2 list entries */ /***********************/ status = FaolToNet (rqptr, Http2TableFao, NULL); if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI); EntryCount = 0; sys$gettim (&Time64); /* process the request list from least to most recent */ for (h2ptr = LIST_GET_HEAD(&Http2List); h2ptr != NULL; h2ptr = LIST_GET_NEXT(h2ptr)) { EntryCount++; vecptr = FaoVector; status = lib$sub_times (&Time64, &h2ptr->ConnectTime64, &ConnectTime64); if (VMSnok (status)) ZERO_DELTA_TIME (ConnectTime64); *vecptr++ = EntryCount % 2 ? " class=\"hlght\"" : ""; *vecptr++ = EntryCount; *vecptr++ = h2ptr->ServicePtr->RequestSchemeNamePtr; *vecptr++ = h2ptr->ServicePtr->ServerHostPort; *vecptr++ = h2ptr->ClientPtr->Lookup.HostName; *vecptr++ = h2ptr->ClientPtr->IpPort; *vecptr++ = DurationString (rqptr, &ConnectTime64); *vecptr++ = h2ptr->FrameCountRx; *vecptr++ = h2ptr->FrameCountTx; *vecptr++ = h2ptr->FlowControlCount; *vecptr++ = &h2ptr->BytesRawRx; *vecptr++ = &h2ptr->BytesRawTx; for (idx = HTTP2_WRITE_QUEUE_MAX; idx <= HTTP2_WRITE_QUEUE_LOW; idx++) *vecptr++ = LIST_GET_COUNT (&h2ptr->QueuedWriteList[idx]); *vecptr++ = h2ptr->QueuedWritePeak; *vecptr++ = ADMIN_REPORT_HPACK; *vecptr++ = h2ptr->ConnectNumber; if (h2ptr->HpackClientOutputCount) *vecptr++ = (h2ptr->HpackClientInputCount * 100) / h2ptr->HpackClientOutputCount; else *vecptr++ = 0; *vecptr++ = ADMIN_REPORT_HPACK; *vecptr++ = h2ptr->ConnectNumber; if (h2ptr->HpackServerInputCount) *vecptr++ = (h2ptr->HpackServerOutputCount * 100) / h2ptr->HpackServerInputCount; else *vecptr++ = 0; *vecptr++ = h2ptr->RequestCount; *vecptr++ = h2ptr->RequestPeak; *vecptr++ = h2ptr->RequestCurrent; if (rqptr->Http2Stream.Http2Ptr != NULL && rqptr->Http2Stream.Http2Ptr->ConnectNumber == h2ptr->ConnectNumber) *vecptr++ = "current"; else { *vecptr++ = "P\ +\ W"; *vecptr++ = ADMIN_REPORT_WATCH; *vecptr++ = h2ptr->ConnectNumber; *vecptr++ = ADMIN_REPORT_WATCH; *vecptr++ = h2ptr->ConnectNumber; *vecptr++ = h2ptr->ConnectNumber; *vecptr++ = ADMIN_REPORT_WATCH; *vecptr++ = h2ptr->ConnectNumber; } FaoCheck (sizeof(FaoVector), &FaoVector, vecptr, FI_LI); status = FaolToNet (rqptr, Http2EntryFao, &FaoVector); if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI); } if (!EntryCount) FaolToNet (rqptr, Http2EmptyFao, NULL); /**************/ /* end report */ /**************/ vecptr = FaoVector; *vecptr++ = ADMIN_CONTROL_HTTP2_PURGE; *vecptr++ = ADMIN_CONTROL_HTTP2_PURGE_ALL, *vecptr++ = AdminRefresh(); FaolToNet (rqptr, PageEndFao, FaoVector); rqptr->rqResponse.PreExpired = PRE_EXPIRE_ADMIN; ResponseHeader200 (rqptr, "text/html", NULL); SysDclAst (NextTaskFunction, rqptr); } /*****************************************************************************/