/*****************************************************************************/ /* HTTP2request.c Conjure up a request structure, populate the dictionary with its request headers, supply it with a request body if required, execute the request, populate the (HTTP/2) response header with response header fields and then run-down the request. There are obviously life-cycle parallels with its companion HTTP/1.1 processing. VERSION HISTORY --------------- 17-AUG-2020 MGD Http2RequestData() reduce memory consumption 09-AUG-2020 MGD bugfix; Http2RequestCancel() cancel and abort 29-JUN-2020 MGD bugfix; Http2RequestData() flow control 06-FEB-2020 MGD Http2RequestData() reworked from list to simple buffer 31-JAN-2020 MGD fiddle with the code (perhaps optimisations, even bugfixes) 18-JAN-2019 MGD status code 418 (teapot) forces connection drop to bugfix; Http2RequestEnd() copy tally rx/tx to request 20-APR-2018 MGD bugfix; Http2RequestDictHeader() keep accounting abreast 12-MAR-2018 MGD refactor Http2RequestCancel() into Http2RequestCancelRead() and Http2RequestCancelWrite() 23-DEC-2017 MGD bugfix; window update and flow control management 15-MAR-2017 MGD bugfix; Http2RequestEnd() end-of-request (control) frame independent of request itself 06-AUG-2016 MGD Http2RequestBegin() ensure stream ident not reused bugfix; Http2RequestData() always deliver via NetIoReadAst() 16-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" #include "hpack.h" #define WASD_MODULE "HTTP2REQUEST" /******************/ /* global storage */ /******************/ /********************/ /* external storage */ /********************/ extern BOOL HttpdTicking, NetCurrentProcessing; extern int ConnectCountTotal, HttpdTickSecond, NetReadBufferSize; extern int ToLowerCase[], ToUpperCase[]; extern char ErrorSanityCheck[]; #define acptr AccountingPtr extern ACCOUNTING_STRUCT *AccountingPtr; extern CONFIG_STRUCT Config; extern HTTPD_GBLSEC *HttpdGblSecPtr; extern LIST_HEAD RequestList; extern WATCH_STRUCT Watch; /*****************************************************************************/ /* A HEADERS frame has been received from the client and HpackProcess() calls this function to initiate request processing. There are obvious parallels between the processing in this and Http2RequestBegin2(), and in RequestBegin(). */ REQUEST_STRUCT* Http2RequestBegin ( HTTP2_STRUCT *h2ptr, uint ident ) { REQUEST_STRUCT *rqptr; /*********/ /* begin */ /*********/ if (WATCHMOD (h2ptr, WATCH_MOD_HTTP2)) WatchThis (WATCHITM(h2ptr), WATCH_MOD_HTTP2, "Http2RequestBegin()"); if (h2ptr->NetIoPtr->VmsStatus) { /* refuse streams on a connection with an explicit status */ if (WATCHING (h2ptr, WATCH_HTTP2)) WatchThis (WATCHITM(h2ptr), WATCH_HTTP2, "HTTP/2 status !&S", h2ptr->NetIoPtr->VmsStatus); return (NULL); } if (h2ptr->GoAwayLastStreamIdent) { /* refuse streams on a connection told to goaway! */ if (WATCHING (h2ptr, WATCH_HTTP2)) WatchThis (WATCHITM(h2ptr), WATCH_HTTP2, "HTTP/2 goaway ident !UL", h2ptr->GoAwayLastStreamIdent); return (NULL); } if (LIST_GET_COUNT (&h2ptr->StreamList) > h2ptr->ServerMaxConcStreams) { /* refuse streams exceeding the connection maximum */ if (WATCHING (h2ptr, WATCH_HTTP2)) WatchThis (WATCHITM(h2ptr), WATCH_HTTP2, "HTTP/2 stream exceeds !UL", h2ptr->ServerMaxConcStreams); return (NULL); } if (ident <= h2ptr->LastStreamIdent) { /* refuse a stream ident that is less than that already established */ if (WATCHING (h2ptr, WATCH_HTTP2)) WatchThis (WATCHITM(h2ptr), WATCH_HTTP2, "HTTP/2 stream !UL less than !UL", ident, h2ptr->LastStreamIdent); return (NULL); } /* note the most recent stream ident */ h2ptr->LastStreamIdent = ident; /* create a request structure */ rqptr = VmGetRequest (++ConnectCountTotal); /* add entry to the top of the request list */ ListAddHead (&RequestList, rqptr, LIST_ENTRY_TYPE_REQUEST); /* timestamp the request */ sys$gettim (&rqptr->rqTime.BeginTime64); sys$numtim (&rqptr->rqTime.BeginNumTime, &rqptr->rqTime.BeginTime64); HttpGmTimeString (rqptr->rqTime.GmDateTime, &rqptr->rqTime.BeginTime64); Http2RequestBegin2 (rqptr, h2ptr, ident); return (rqptr); } /*****************************************************************************/ /* Take the the supplied request and attach it to the supplied HTTP/2 stream. Called from Http2RequestBegin() and Http2SwitchResponse(). */ void Http2RequestBegin2 ( REQUEST_STRUCT *rqptr, HTTP2_STRUCT *h2ptr, uint ident ) { HTTP2_STREAM_STRUCT *s2ptr; NETIO_STRUCT *ioptr; /*********/ /* begin */ /*********/ if (WATCHMOD (h2ptr, WATCH_MOD_HTTP2)) WatchThis (WATCHITM(h2ptr), WATCH_MOD_HTTP2, "Http2RequestBegin2()"); rqptr->NetworkHttp = 2; rqptr->RequestState = REQUEST_STATE_HEADER; #if SESOLA_MEMORY SesolaMemoryControl (-1, FI_LI); #endif SESOLA_MEMORY /* create network I/O structure (see NetIoBegin()) */ ioptr = VmGet (sizeof(NETIO_STRUCT)); /* populate request structure */ rqptr->NetIoPtr = ioptr; rqptr->ClientPtr = h2ptr->ClientPtr; rqptr->ServicePtr = h2ptr->ServicePtr; rqptr->rqDictPtr = DictCreate (rqptr, -1); /* add request (stream) to HTTP/2 list and populate stream structure */ s2ptr = &rqptr->Http2Stream; ListAddHead (&h2ptr->StreamList, s2ptr, LIST_ENTRY_TYPE_REQUEST); s2ptr->RequestPtr = rqptr; s2ptr->Http2Ptr = h2ptr; s2ptr->Ident = ident; /* WASD streams start open, never idle */ s2ptr->State = HTTP2_STATE_OPEN; /* populate network I/O structure (see NetAccept()) */ ioptr->ClientPtr = h2ptr->ClientPtr; ioptr->ServicePtr = h2ptr->ServicePtr; ioptr->Http2StreamPtr = s2ptr; if (Watch.Category && Watch.Category != WATCH_ONE_SHOT_CAT) if (Watch.FilterSet) { WatchFilterHttpProtocol (rqptr); WatchFilterClientService (rqptr); } else WatchSetWatch (rqptr, WATCH_NEW_ITEM); else if (h2ptr->WatchItem & WATCH_ITEM_HTTP2_FLAG) WatchSetWatch (rqptr, WATCH_NEW_ITEM); /* set the initial stream flow-control window size */ s2ptr->ReadWindowSize = h2ptr->ServerInitialWindowSize; s2ptr->WriteWindowSize = h2ptr->WriteWindowSize; /* inform the client of this */ Http2WindowUpdate (h2ptr, s2ptr->Ident, s2ptr->ReadWindowSize, NULL, 0); /* if WATCHing HTTP/2 get a ping from the client to measure RTT */ if (WATCHMOD (h2ptr, WATCH_MOD_HTTP2)) Http2Ping (h2ptr, 0, NULL, 0); h2ptr->RequestCount++; h2ptr->RequestCurrent++; if (h2ptr->RequestCurrent > h2ptr->RequestPeak) h2ptr->RequestPeak = h2ptr->RequestCurrent; InstanceMutexLock (INSTANCE_MUTEX_HTTPD); acptr->Http2RequestCount++; if (h2ptr->RequestPeak > acptr->Http2RequestPeak) acptr->Http2RequestPeak = h2ptr->RequestPeak; InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD); if (Config.cfTimeout.Http2Idle) h2ptr->IdleSecond = HttpdTickSecond + Config.cfTimeout.Http2Idle; else h2ptr->IdleSecond = HttpdTickSecond + HTTP2_TIMEOUT_IDLE_SECONDS; /* if it's not already running kick-off the HTTPd ticker */ if (!HttpdTicking) HttpdTick (0); if (WATCHMOD (h2ptr, WATCH_MOD_HTTP2)) WatchThis (WATCHITM(h2ptr), WATCH_MOD_HTTP2, "streams: !UL", LIST_GET_COUNT (&h2ptr->StreamList)); } /*****************************************************************************/ /* After the header has been placed in the dictionary continue processing the request. Parallels processing performed in RequestParseHttp(). When moving from WATCH report generation back to the WATCH selection request processing needs to be delayed to allow the WATCHing request to receive the RST_STREAM frame and shut down WATCHing. Otherwise the HTTP/2 WATCH rabbit hole interferes with the new request because the previous request often hasn't yet concluded WATCHing due to frame multiplex ordering and/or transmission latency. This small delay is obvious to the user. */ void Http2RequestProcess (REQUEST_STRUCT *rqptr) { static ulong OneSecondDelta [2] = { -10000000, -1 }; int status; ushort slen; char string [32]; DICT_ENTRY_STRUCT *denptr; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_HTTP2)) WatchThis (WATCHALL, WATCH_MOD_HTTP2, "Http2RequestProcess()"); InstanceMutexLock (INSTANCE_MUTEX_HTTPD); NetUpdateProcessing (rqptr, +1); InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD); if (Watch.Category && Watch.Category != WATCH_ONE_SHOT_CAT) WatchFilterRequestHeader (rqptr); if (rqptr->Http2Stream.Http2Ptr->PingMicroSeconds) { FaoToBuffer (string, sizeof(string), &slen, "!UL.!3ZL", rqptr->Http2Stream.Http2Ptr->PingMicroSeconds / 1000, rqptr->Http2Stream.Http2Ptr->PingMicroSeconds % 1000); DictInsert (rqptr->rqDictPtr, DICT_TYPE_INTERNAL, "http2_ping", 10, string, slen); } if (WATCHING (rqptr, WATCH_CONNECT)) WatchThis (WATCHITM(rqptr), WATCH_CONNECT, "HTTP/2 begin !UL with !AZ,!UL", rqptr->Http2Stream.Ident, rqptr->ClientPtr->Lookup.HostName, rqptr->ClientPtr->IpPort); if (WATCHING (rqptr, WATCH_REQUEST)) if (!rqptr->rqHeader.WatchNewRequest) { rqptr->rqHeader.WatchNewRequest = true; WatchDataFormatted ("|!#*+\n", 38 + Watch.ItemDigits); } if (WATCHING (rqptr, WATCH_REQUEST_HEADER)) if (denptr = RequestDictHeader (rqptr)) { WatchThis (WATCHITM(rqptr), WATCH_REQUEST_HEADER, "HEADER !UL bytes", DICT_GET_VALUE_LEN(denptr)); WatchData (DICT_GET_VALUE(denptr), DICT_GET_VALUE_LEN(denptr)); } if (Watch.RequestPtr && Watch.RequestPtr->Http2Stream.Http2Ptr && Watch.RequestPtr->Http2Stream.Http2Ptr == rqptr->Http2Stream.Http2Ptr) { /* delay request processing when WATCHing on same HTTP/2 connection */ status = sys$setimr (0, &OneSecondDelta, RequestParseDictionary, rqptr, 0); if (VMSnok (status)) ErrorExitVmsStatus (status, NULL, FI_LI); } else RequestParseDictionary (rqptr); } /*****************************************************************************/ /* Request body PUTed or POSTed by the client. A simple, single buffer space is used, with additional data appended to any existing and with partial reads causing the content to be moved down to fill what's been read. Client data is not buffered if a read I/O with sufficient space is already queued. LOTS of memory copying :-{ The client supplying request body data and the server consuming that data are completely asynchronous. As a result client data can arrive before a request has attempted to read it (and need to be stored) or after the server initiated a read. Looking at it the other way, the server can attempt to read data that hasn't yet been sent by the client (and need to wait for it), or attempt to read data that has already been received from the client (and been stored). In addition the request needs to be notified when the data is exhausted (ENDOFILE). */ int Http2RequestData ( HTTP2_STRUCT *h2ptr, uint flags, uint ident, uchar *BufferPtr, uint BufferLength ) { uint drlength, drsize, padlen, status, DataLength; uchar *bptr, *drptr; HTTP2_READ_STRUCT *r2ptr; HTTP2_STREAM_STRUCT *s2ptr; NETIO_STRUCT *ioptr; REQUEST_STRUCT *rqeptr; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_HTTP2)) WatchThis (WATCHALL, WATCH_MOD_HTTP2, "Http2RequestData() !AZ !SL !UL", BufferPtr ? "DATA" : "REQUEST", BufferPtr, BufferLength); /* 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) { if (BufferPtr) { /* request shutdown while data in transit? */ return (SS$_CANCEL); } /* must be a stream (request) with that ident */ ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI); } /* ensure it looks reset (to start with) */ ioptr = rqeptr->NetIoPtr; ioptr->ReadIOsb.Count = 0; ioptr->ReadIOsb.Status = 0; if (BufferPtr == (void*)-1) { /**************/ /* cancel I/O */ /**************/ if (WATCH_MODULE(WATCH_MOD_HTTP2)) WatchThis (WATCHALL, WATCH_MOD_HTTP2, "!&A !&S", ioptr->ReadAstFunction, ioptr->VmsStatus); /* remove any previously received and buffered data */ if (s2ptr->DataReadPtr) { /* REQUEST MEMORY! */ VmFreeFromHeap (rqeptr, s2ptr->DataReadPtr, FI_LI); s2ptr->DataReadPtr = NULL; s2ptr->DataReadLength = s2ptr->DataReadSize = 0; } if (ioptr->ReadAstFunction) { /* request already waiting */ if (ioptr->VmsStatus) ioptr->ReadIOsb.Status = ioptr->VmsStatus; else ioptr->ReadIOsb.Status = SS$_CANCEL; /* deliver via the NETIO AST delivery decoupler */ NetIoReadAst (ioptr); } return (ioptr->ReadIOsb.Status); } /* data received buffer */ drptr = s2ptr->DataReadPtr; drsize = s2ptr->DataReadSize; drlength = s2ptr->DataReadLength; if (bptr = BufferPtr) { /***************/ /* client data */ /***************/ /* client data being made available */ if (flags & HTTP2_FLAG_DATA_PADDED) { HTTP2_GET_8 (bptr, padlen); DataLength = BufferLength - padlen - 1; } else DataLength = BufferLength; /* keep the request accounting representative */ ADD_LONG_QUAD (1, ioptr->BlocksRawRx); ADD_LONG_QUAD (1, ioptr->BlocksTallyRx); ADD_LONG_QUAD (DataLength, ioptr->BytesRawRx); ADD_LONG_QUAD (DataLength, ioptr->BytesTallyRx); if (WATCH_MODULE(WATCH_MOD_HTTP2)) WatchThis (WATCHALL, WATCH_MOD_HTTP2, "length:!UL size:!UL eof:!&B request:!&B", DataLength, ioptr->ReadSize, flags & HTTP2_FLAG_DATA_END_STR, ioptr->ReadAstFunction); /****************/ /* flow control */ /****************/ /* stream */ s2ptr->ReadWindowSize -= DataLength; /* HTTP/2 connection */ h2ptr->ReadWindowSize -= DataLength; if (h2ptr->ReadWindowSize <= 0) { h2ptr->ReadWindowSize = h2ptr->ServerInitialWindowSize * h2ptr->ServerMaxConcStreams; Http2WindowUpdate (h2ptr, 0, h2ptr->ReadWindowSize, NULL, 0); } if (ioptr->ReadAstFunction) { /***************************/ /* request already waiting */ /***************************/ if (WATCH_MODULE(WATCH_MOD_HTTP2)) WatchThis (WATCHALL, WATCH_MOD_HTTP2, "length:!UL size:!UL", DataLength, ioptr->ReadSize); if (!drlength) { /****************************/ /* no data already buffered */ /****************************/ if (DataLength <= ioptr->ReadSize) { /***********************************/ /* data can fit into the I/O space */ /***********************************/ ioptr->ReadIOsb.Status = SS$_NORMAL; ioptr->ReadIOsb.Count = DataLength; memcpy (ioptr->ReadPtr, bptr, DataLength); NetIoReadAst (ioptr); /* if window needs update */ if (s2ptr->ReadWindowSize <= 0) { s2ptr->ReadWindowSize = h2ptr->ServerInitialWindowSize; Http2WindowUpdate (h2ptr, s2ptr->Ident, s2ptr->ReadWindowSize, NULL, 0); } return (ioptr->ReadIOsb.Status); } } } if (DataLength) { /**************************/ /* buffer REQUEST MEMORY! */ /**************************/ if (!drsize) { /* fresh buffer */ drsize = h2ptr->ServerInitialWindowSize; drptr = VmGetHeap (rqeptr, drsize); } else if (drlength + DataLength > drsize) { /* insufficient space in current buffer */ drsize += h2ptr->ServerInitialWindowSize; drptr = VmReallocHeap (rqeptr, drptr, drsize, FI_LI); } if (WATCHING (h2ptr, WATCH_HTTP2)) WatchThis (WATCHITM(h2ptr), WATCH_HTTP2, "BUFFER !UL !UL/!UL", DataLength, drlength, drsize); memcpy (drptr + drlength, bptr, DataLength); drlength += DataLength; s2ptr->DataReadPtr = drptr; s2ptr->DataReadSize = drsize; s2ptr->DataReadLength = drlength; } if (DataLength) { /*******************/ /* still possible? */ /*******************/ /* half-closed local can still receive frames (RFC7540 5.1) */ if (s2ptr->State == HTTP2_STATE_CLOSED || s2ptr->State == HTTP2_STATE_CLOSED_REM) return (0); /****************/ /* flow control */ /****************/ /* stream */ s2ptr->ReadWindowSize -= DataLength; /* HTTP/2 connection */ h2ptr->ReadWindowSize -= DataLength; if (h2ptr->ReadWindowSize <= 0) { h2ptr->ReadWindowSize = h2ptr->ServerInitialWindowSize * h2ptr->ServerMaxConcStreams; Http2WindowUpdate (h2ptr, 0, h2ptr->ReadWindowSize, NULL, 0); } } if (flags & HTTP2_FLAG_DATA_END_STR) s2ptr->State = HTTP2_STATE_CLOSED_REM; /* if not a request already waiting */ if (!ioptr->ReadAstFunction) return (ioptr->ReadIOsb.Status); /* otherwise drop through to partially fill the I/O */ } /***********/ /* request */ /***********/ /* request looking for data (called from Http2NetRead()) */ if (drlength) { /* there is buffered data */ ioptr->ReadIOsb.Status = SS$_NORMAL; if (drlength <= ioptr->ReadSize) ioptr->ReadIOsb.Count = drlength; else ioptr->ReadIOsb.Count = ioptr->ReadSize; memcpy (ioptr->ReadPtr, drptr, ioptr->ReadIOsb.Count); s2ptr->DataReadLength -= ioptr->ReadIOsb.Count; if (s2ptr->DataReadLength) { /* inefficient but straightforward - shuffle it down */ memmove (drptr, drptr + ioptr->ReadIOsb.Count, drlength - ioptr->ReadIOsb.Count); } if (WATCHING (h2ptr, WATCH_HTTP2)) WatchThis (WATCHITM(h2ptr), WATCH_HTTP2, "UNBUFFER !UL !UL/!UL", ioptr->ReadIOsb.Count, drlength, drsize); /* if all the data has been read and the window needs update */ if (!s2ptr->DataReadLength && s2ptr->ReadWindowSize <= 0) { s2ptr->ReadWindowSize = h2ptr->ServerInitialWindowSize; Http2WindowUpdate (h2ptr, s2ptr->Ident, s2ptr->ReadWindowSize, NULL, 0); } NetIoReadAst (ioptr); return (ioptr->ReadIOsb.Status); } if (h2ptr->NetIoPtr->VmsStatus) { /* HTTP/2 connection (error) status */ ioptr->ReadIOsb.Status = h2ptr->NetIoPtr->VmsStatus; NetIoReadAst (ioptr); } else if (ioptr->VmsStatus) { /* commonly SS$_CANCEL */ ioptr->ReadIOsb.Status = ioptr->VmsStatus; NetIoReadAst (ioptr); } else if (s2ptr->State == HTTP2_STATE_CLOSED || s2ptr->State == HTTP2_STATE_CLOSED_LOC || s2ptr->State == HTTP2_STATE_CLOSED_REM) { ioptr->ReadIOsb.Status = SS$_ENDOFFILE; NetIoReadAst (ioptr); } return (ioptr->ReadIOsb.Status); } /*****************************************************************************/ /* Cancel any outstanding request I/O (read and write). Then if recalled ensure an aborted request is rundown and ended. */ void Http2RequestCancel (REQUEST_STRUCT *rqptr) { HTTP2_STREAM_STRUCT *s2ptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_HTTP2)) WatchThis (WATCHITM(rqptr), WATCH_MOD_HTTP2, "Http2RequestCancel()"); s2ptr = &rqptr->Http2Stream; if (s2ptr->State != HTTP2_STATE_CLOSED && rqptr->NetIoPtr->VmsStatus != SS$_CANCEL) { /**********/ /* cancel */ /**********/ s2ptr->State = HTTP2_STATE_CLOSED; rqptr->NetIoPtr->VmsStatus = SS$_CANCEL; Http2NetCancelWrite (rqptr); Http2RequestData (s2ptr->Http2Ptr, 0, s2ptr->Ident, (void*)-1, 0); Http2RequestResetStream (rqptr); } else if (rqptr->NetIoPtr->VmsStatus == SS$_CANCEL) { /*********/ /* abort */ /*********/ if (rqptr->RequestState >= REQUEST_STATE_ABORT) if (!NETIO_IN_PROGRESS (rqptr->NetIoPtr)) if (RequestRundown (rqptr)) { /* only want to do this the once! */ rqptr->NetIoPtr->VmsStatus = SS$_CANCEL | STS$M_INHIB_MSG; SysDclAst (RequestEnd2, rqptr); } } } /*****************************************************************************/ /* Http2ResponsetDictHeader() ... yes, I know. Response HEADERS frame with table based compression. Called from NetWrite() this function needs to emulate NetIoWrite(). */ int Http2ResponseDictHeader ( REQUEST_STRUCT *rqptr, REQUEST_AST AstFunction ) { BOOL headers, indexit; uint index, length, nlen, nlen2, retval, size, value, vlen; uchar *bptr, *bzptr, *cptr, *nptr, *nptr2, *vptr; DICT_ENTRY_STRUCT *denptr; HPACK_TABLE_STRUCT *tabptr; HTTP2_STRUCT *h2ptr; HTTP2_WRITE_STRUCT *w2ptr, *w22ptr; NETIO_STRUCT *ioptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_HTTP2)) WatchThis (WATCHITM(rqptr), WATCH_MOD_HTTP2, "Http2ResponseDictHeader()"); if (rqptr->rqPathSet.ResponseHeaderNone && rqptr->rqResponse.HttpStatus / 100 == 2) return (SS$_NORMAL); h2ptr = rqptr->Http2Stream.Http2Ptr; /* treat subsequent response header as data (for WASD "Xray" facility) */ if (rqptr->Http2Stream.HeaderSent) { denptr = ResponseDictHeader (rqptr); NetIoWrite (rqptr->NetIoPtr, AstFunction, rqptr, DICT_GET_VALUE(denptr), DICT_GET_VALUE_LEN(denptr)); return (SS$_NORMAL); } rqptr->Http2Stream.HeaderSent = true; tabptr = &h2ptr->HpackServerTable; /* with compression this should always be (way more than) enough space */ size = 8; DictIterate (rqptr->rqDictPtr, NULL); while ((denptr = DictIterate (rqptr->rqDictPtr, DICT_TYPE_RESPONSE)) != NULL) size += DICT_GET_KEY_LEN(denptr) + DICT_GET_VALUE_LEN(denptr) + 4; w2ptr = w22ptr = VmGet2Heap (h2ptr, sizeof(HTTP2_WRITE_STRUCT) + size); bzptr = (bptr = w2ptr->payload) + size; denptr = DictLookup (rqptr->rqDictPtr, DICT_TYPE_INTERNAL, "response_status", 15); /* if a header has not been generated */ if (denptr == NULL) return (SS$_ABORT); *bptr++ = (uchar)0x08; /* static index ":status" without indexing */ *bptr++ = (uchar)DICT_GET_VALUE_LEN(denptr); for (cptr = DICT_GET_VALUE(denptr); *cptr; *bptr++ = (uchar)*cptr++); /* set the baseline before these table searches */ HpackFindInTable (tabptr, NULL, 0, NULL, 0); /* calculate the HTTP/1.n equivalent */ length = sizeof("\r\n")-1; DictIterate (rqptr->rqDictPtr, NULL); while ((denptr = DictIterate (rqptr->rqDictPtr, DICT_TYPE_RESPONSE)) != NULL) { nptr = DICT_GET_KEY(denptr); nlen = DICT_GET_KEY_LEN(denptr); vptr = DICT_GET_VALUE(denptr); vlen = DICT_GET_VALUE_LEN(denptr); /* e.g. |set-cookie| field begins with ":" to allow multiples */ if (isdigit(*nptr)) { nptr2 = nptr; nlen2 = nlen; while ((isdigit(*nptr2)) && nlen2) { nptr2++; nlen2--; } if (nlen2 && *nptr2 == ':') { nptr = nptr2 + 1; nlen = nlen2 - 1; } /* ignore if somehow just ":" */ if (!nlen || !nlen2) continue; } length += nlen + vlen + sizeof(": \r\n")-1; /* look for the name plus value entry */ index = HpackFindInTable (tabptr, nptr, nlen, vptr, vlen); if (index) { /* indexed header field, indexed name and value (RFC7541 6.1) */ *bptr = 0x80; retval = HpackEncodeInt32 (h2ptr, &bptr, bzptr, 7, index); if (retval < 0) return (SS$_BUGCHECK); } else { /* if no name plus value entry look for just the name */ index = HpackFindInTable (tabptr, nptr, nlen, NULL, 0); /* observation has demonstrated these are not worth caching */ if (MATCH15 (nptr, "content-length")) indexit = false; else if (MATCH5 (nptr, "date")) indexit = false; else if (MATCH5 (nptr, "etag")) indexit = false; else if (MATCH14 (nptr, "last-modified")) indexit = false; else indexit = true; /* if it won't fit into the table! */ if (nlen + vlen + 32 > h2ptr->HpackServerTable.max) indexit = false; if (indexit) { /* indexed header field, index then value (RFC7541 6.2.1) */ if (index) { *bptr = 0x40; retval = HpackEncodeInt32 (h2ptr, &bptr, bzptr, 6, index); if (retval < 0) return (SS$_BUGCHECK); } else { *bptr++ = 0x40; retval = HpackEncodeString (h2ptr, &bptr, bzptr, nptr, nlen); if (retval < 0) return (SS$_BUGCHECK); } retval = HpackEncodeString (h2ptr, &bptr, bzptr, vptr, vlen); if (retval < 0) return (SS$_BUGCHECK); HpackAddToTable (tabptr, nptr, nlen, vptr, vlen); } else { /* literal header field without indexing (RFC7541 6.2.2) */ if (index) { *bptr = 0x00; retval = HpackEncodeInt32 (h2ptr, &bptr, bzptr, 4, index); if (retval < 0) return (SS$_BUGCHECK); } else { *bptr++ = 0x00; retval = HpackEncodeString (h2ptr, &bptr, bzptr, nptr, nlen); if (retval < 0) return (SS$_BUGCHECK); } retval = HpackEncodeString (h2ptr, &bptr, bzptr, vptr, vlen); if (retval < 0) return (SS$_BUGCHECK); } } if (bptr > w2ptr->payload + size) ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI); } value = bptr - w2ptr->payload; /* as if it originated from the network */ ADD_LONG_QUAD (value, rqptr->BytesRx) h2ptr->HpackServerInputCount += length; h2ptr->HpackServerOutputCount += value; if (WATCHING (h2ptr, WATCH_HTTP2) && !WATCHING1S(h2ptr) && !WATCHING2(h2ptr)) { WatchThis (WATCHITM(h2ptr), WATCH_HTTP2, "RESPONSE header !UL->!UL !UL%", length, value, value * 100 / length); if (rqptr->rqPathSet.Http2WriteQueue) { if (rqptr->rqPathSet.Http2WriteQueue == HTTP2_WRITE_QUEUE_HIGH) cptr = "high"; else if (rqptr->rqPathSet.Http2WriteQueue == HTTP2_WRITE_QUEUE_NORMAL) cptr = "normal"; else cptr = "low"; WatchThis (WATCHITM(h2ptr), WATCH_HTTP2, "WRITE queue !AZ", cptr); } } if (WATCHMOD (rqptr, WATCH_MOD_HTTP2)) { int retval; retval = HpackHeadersFrame (&h2ptr->HpackServerTable, (int)-1, rqptr->Http2Stream.Ident, w2ptr->payload, value); if (retval < 0) { WatchThis (WATCHITM(rqptr), WATCH_MOD_HTTP2, "HPACK error:!UL \"!AZ\"", -(retval), Http2ErrorString(-(retval))); return (SS$_ABORT); } WatchData (w2ptr->payload, value); } if (WATCHING (rqptr, WATCH_RESPONSE_HEADER)) { denptr = ResponseDictHeader (rqptr); WatchThis (WATCHITM(rqptr), WATCH_RESPONSE_HEADER, "HEADER !UL bytes", DICT_GET_VALUE_LEN(denptr)); WatchData (DICT_GET_VALUE(denptr), DICT_GET_VALUE_LEN(denptr)); } /* this is the underlying request's I/O structure */ ioptr = rqptr->NetIoPtr; /* most headers are going to be without continuation */ if (value <= h2ptr->ClientMaxFrameSize) { /********************/ /* just one headers */ /********************/ /* use the originally allocate write structure */ HTTP2_PLACE_24 (w2ptr->length, value); HTTP2_PLACE_8 (w2ptr->type, HTTP2_FRAME_HEADERS); HTTP2_PLACE_8 (w2ptr->flags, HTTP2_FLAG_HEAD_END_HEAD); HTTP2_PLACE_32 (w2ptr->ident, rqptr->Http2Stream.Ident); w2ptr->Http2Ptr = h2ptr; w2ptr->RequestPtr = rqptr; w2ptr->HeaderPtr = w2ptr->header; w2ptr->DataPtr = w2ptr->payload; w2ptr->DataLength = value; if (!(w2ptr->AstFunction = AstFunction)) w2ptr->AstFunction = w2ptr->AstParam = Http2Net_WRITE_NO_AST; else w2ptr->AstParam = rqptr; /* called from NetWrite() this needs to emulate NetIoWrite() */ if (ioptr->WriteAstFunction = AstFunction) ioptr->WriteAstParam = rqptr; /* let's give response headers a bit of an edge */ w2ptr->WriteQueue = HTTP2_WRITE_QUEUE_HIGH; if (WATCHMOD (rqptr, WATCH_MOD_HTTP2)) WatchThis (WATCHITM(rqptr), WATCH_MOD_HTTP2, "h2ptr:!&X w2ptr:!&X", h2ptr, w2ptr); Http2NetQueueWrite (h2ptr, w2ptr); /* keep the request accounting representative */ ADD_LONG_QUAD (1, ioptr->BlocksRawTx); ADD_LONG_QUAD (1, ioptr->BlocksTallyTx); ADD_LONG_QUAD (length, ioptr->BytesRawTx); ADD_LONG_QUAD (length, ioptr->BytesTallyTx); } else { /*********************/ /* with continuation */ /*********************/ /* the "original" write structure payload will be cannibalised */ cptr = w22ptr->payload; headers = true; while (value) { if (value <= h2ptr->ClientMaxFrameSize) length = value; else length = h2ptr->ClientMaxFrameSize; value -= length; w2ptr = VmGet2Heap (h2ptr, sizeof(HTTP2_WRITE_STRUCT) + length); HTTP2_PLACE_24 (w2ptr->length, length); if (headers) HTTP2_PLACE_8 (w2ptr->type, HTTP2_FRAME_HEADERS) else HTTP2_PLACE_8 (w2ptr->type, HTTP2_FRAME_CONTINUATION) headers = false; if (!value) HTTP2_PLACE_8 (w2ptr->flags, HTTP2_FLAG_HEAD_END_HEAD); HTTP2_PLACE_32 (w2ptr->ident, rqptr->Http2Stream.Ident); memcpy (w2ptr->payload, cptr, length); cptr += length; w2ptr->Http2Ptr = h2ptr; w2ptr->RequestPtr = rqptr; w2ptr->HeaderPtr = w2ptr->header; w2ptr->DataPtr = w2ptr->payload; w2ptr->DataLength = length; if (headers) w2ptr->AstFunction = w2ptr->AstParam = Http2Net_WRITE_NO_AST; else { if (!(w2ptr->AstFunction = AstFunction)) w2ptr->AstFunction = w2ptr->AstParam = Http2Net_WRITE_NO_AST; else w2ptr->AstParam = rqptr; /* called from NetWrite() this needs to emulate NetIoWrite() */ if (ioptr->WriteAstFunction = AstFunction) ioptr->WriteAstParam = rqptr; } /* let's give response headers a bit of an edge */ w2ptr->WriteQueue = HTTP2_WRITE_QUEUE_HIGH; if (WATCHMOD (rqptr, WATCH_MOD_HTTP2)) WatchThis (WATCHITM(rqptr), WATCH_MOD_HTTP2, "h2ptr:!&X w2ptr:!&X", h2ptr, w2ptr); Http2NetQueueWrite (h2ptr, w2ptr); /* keep the request accounting representative */ ADD_LONG_QUAD (1, ioptr->BlocksRawTx); ADD_LONG_QUAD (1, ioptr->BlocksTallyTx); ADD_LONG_QUAD (length, ioptr->BytesRawTx); ADD_LONG_QUAD (length, ioptr->BytesTallyTx); } /* free the cannibalised write structure */ VmFreeFrom2Heap (h2ptr, w22ptr, FI_LI); } return (SS$_NORMAL); } /*****************************************************************************/ /* Reset the stream associated with this request. */ int Http2RequestResetStream (REQUEST_STRUCT *rqptr) { int retval; uint error, ident; HTTP2_STRUCT *h2ptr; /*********/ /* begin */ /*********/ h2ptr = rqptr->Http2Stream.Http2Ptr; ident = rqptr->Http2Stream.Ident; error = HTTP2_ERROR_CANCEL; if (WATCHMOD (h2ptr, WATCH_MOD_HTTP2)) WatchThis (WATCHITM(h2ptr), WATCH_MOD_HTTP2, "Http2RequestResetStream()"); retval = Http2ResetStream (h2ptr, ident, error, NULL, 0); return (retval); } /*****************************************************************************/ /* Called from RequestEnd2() and Http2NetWriteDataAst() during request run-down. */ void Http2RequestEnd2 (REQUEST_STRUCT *rqptr) { HTTP2_STRUCT *h2ptr; HTTP2_STREAM_STRUCT *s2ptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_HTTP2)) WatchThis (WATCHITM(rqptr), WATCH_MOD_HTTP2, "Http2RequestEnd2() !&F ident:!UL state:!UL read:!UL/!UL write:!UL", Http2RequestEnd2, rqptr->Http2Stream.Ident, rqptr->Http2Stream.State, rqptr->Http2Stream.DataReadLength, rqptr->Http2Stream.DataReadSize, rqptr->Http2Stream.QueuedWriteCount); s2ptr = &rqptr->Http2Stream; h2ptr = s2ptr->Http2Ptr; /* cancel request data being read */ Http2RequestData (h2ptr, 0, s2ptr->Ident, (void*)-1, 0); if (!s2ptr->RequestEnd) { s2ptr->RequestEnd = true; if (!(s2ptr->State == HTTP2_STATE_CLOSED || s2ptr->State == HTTP2_STATE_CLOSED_LOC)) Http2NetWriteEnd (rqptr); } if (s2ptr->DataReadSize || s2ptr->QueuedWriteCount || NETIO_IN_PROGRESS (rqptr->NetIoPtr)) return; ADD_QUAD_QUAD (s2ptr->Http2Ptr->BytesRawTallyRx, rqptr->NetIoPtr->BytesRawRx); ADD_QUAD_QUAD (s2ptr->Http2Ptr->BytesRawTallyTx, rqptr->NetIoPtr->BytesRawTx); RequestEnd2 (rqptr); } /*****************************************************************************/ /* Final stage of request run-down corresponding to RequestEnd5(). */ void Http2RequestEnd5 (REQUEST_STRUCT *rqptr) { int HttpStatus; HTTP2_STRUCT *h2ptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_HTTP2)) WatchThis (WATCHITM(rqptr), WATCH_MOD_HTTP2, "Http2RequestEnd5() chan:!UL ident:!UL", rqptr->Http2Stream.Http2Ptr->NetIoPtr->Channel, rqptr->Http2Stream.Ident); if (WATCHING (rqptr, WATCH_CONNECT)) WatchThis (WATCHITM(rqptr), WATCH_CONNECT, "HTTP/2 end !UL with !AZ,!UL", rqptr->Http2Stream.Ident, rqptr->ClientPtr->Lookup.HostName, rqptr->ClientPtr->IpPort); /* dispose of the network I/O structure */ NetIoEnd (rqptr->NetIoPtr); h2ptr = rqptr->Http2Stream.Http2Ptr; ListRemove (&h2ptr->StreamList, &rqptr->Http2Stream); if (h2ptr->RequestCurrent) h2ptr->RequestCurrent--; rqptr->Http2Stream.Ident = 0; rqptr->Http2Stream.Http2Ptr = NULL; InstanceMutexLock (INSTANCE_MUTEX_HTTPD); ADD_LONG_QUAD (h2ptr->FrameTallyRx, acptr->Http2FrameCountRx); ADD_LONG_QUAD (h2ptr->FrameTallyTx, acptr->Http2FrameCountTx); ADD_LONG_QUAD (h2ptr->FrameRequestTallyRx, acptr->Http2FrameRequestCountRx); ADD_LONG_QUAD (h2ptr->FrameRequestTallyTx, acptr->Http2FrameRequestCountTx); ADD_LONG_QUAD (h2ptr->FlowFrameTally, acptr->Http2FlowFrameCount); ADD_LONG_QUAD (h2ptr->FlowControlTally, acptr->Http2FlowControlCount); h2ptr->FlowFrameCount += h2ptr->FlowFrameTally; h2ptr->FlowFrameTally = 0; h2ptr->FlowControlCount += h2ptr->FlowControlTally; h2ptr->FlowControlTally = 0; ADD_QUAD_QUAD (h2ptr->BytesRawTallyRx, acptr->BytesRawRx[HTTP2]); ADD_QUAD_QUAD (h2ptr->BytesRawTallyTx, acptr->BytesRawTx[HTTP2]); InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD); h2ptr->FrameTallyRx = 0; h2ptr->FrameTallyTx = 0; h2ptr->FrameRequestTallyRx = 0; h2ptr->FrameRequestTallyTx = 0; PUT_ZERO_QUAD (h2ptr->BytesRawTallyRx); PUT_ZERO_QUAD (h2ptr->BytesRawTallyTx); if (WATCHMOD (h2ptr, WATCH_MOD_HTTP2)) WatchThis (WATCHITM(h2ptr), WATCH_MOD_HTTP2, "streams: !UL", LIST_GET_COUNT (&h2ptr->StreamList)); /* if WATCHing the HTTP/2 connection then reset the request item */ if (h2ptr->WatchItem & WATCH_ITEM_HTTP2_FLAG) rqptr->WatchItem = 0; #if SESOLA_MEMORY SesolaMemoryControl (-1, FI_LI); #endif SESOLA_MEMORY HttpStatus = rqptr->rqResponse.HttpStatus; RequestEnd5 (rqptr); /* 418 can be used to drop a connection using a "standard" code */ if (h2ptr->GoAwayLastStreamIdent || HttpStatus == 418) Http2CloseConnection (h2ptr); } /*****************************************************************************/