/*****************************************************************************/ /* 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 --------------- 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 uint VmStructSize; 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 (); /* 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()"); /* 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); rqptr->ConnectNumber = ++ConnectCountTotal; /* 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) { WatchFilterHttpProtocol (rqptr); WatchFilterClientService (rqptr); } 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_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 request network read buffer must be (at least) |h2ptr->ServerMaxFrameSize| (SETTINGS_MAX_FRAME_SIZE) so that a maximum client data frame can be completely copied into the request buffer when it is available. 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). So there needs to be FIFO store for client supplied data and the request read deferred if that store is empty. In addition the request needs to be notified when the data is exhausted (ENDOFILE). LOTS of memory copying :-{ */ int Http2RequestData ( HTTP2_STRUCT *h2ptr, uint flags, uint ident, uchar *BufferPtr, uint BufferLength ) { uint padlen, status, DataLength; uchar *bptr; 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", BufferPtr ? "DATA" : "REQUEST"); /* 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 (-HTTP2_ERROR_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 (bptr = BufferPtr) { /********/ /* 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); if (ioptr->ReadAstFunction) { /* request already waiting for it */ if (WATCH_MODULE(WATCH_MOD_HTTP2)) WatchThis (WATCHALL, WATCH_MOD_HTTP2, "length:!UL size:!UL", DataLength, ioptr->ReadSize); if (h2ptr->NetIoPtr->VmsStatus) { /* HTTP/2 connection (error) status */ ioptr->ReadIOsb.Status = h2ptr->NetIoPtr->VmsStatus; } else if (s2ptr->RequestPtr->NetIoPtr->VmsStatus) { /* commonly SS$_CANCEL */ ioptr->ReadIOsb.Status = s2ptr->RequestPtr->NetIoPtr->VmsStatus; } else if (DataLength <= ioptr->ReadSize) { /* client data can fit into the buffer */ ioptr->ReadIOsb.Count = DataLength; ioptr->ReadIOsb.Status = SS$_NORMAL; memcpy (ioptr->ReadPtr, bptr, DataLength); } else if (DataLength) { /* request buffer must accomodate maximum frame size */ ioptr->ReadIOsb.Status = SS$_BUGCHECK; ErrorNoticed (rqeptr, SS$_BUGCHECK, ErrorSanityCheck, FI_LI); } else if (flags & HTTP2_FLAG_DATA_END_STR) { flags = 0; /* only the once */ ioptr->ReadIOsb.Status = SS$_ENDOFFILE; } /* deliver via the NETIO AST delivery decoupler */ NetIoReadAst (ioptr); } else if (DataLength) { /* REQUEST MEMORY! request not waiting, (re)buffer and queue */ r2ptr = VmGetHeap (rqeptr, sizeof(HTTP2_READ_STRUCT) + DataLength); memcpy (r2ptr->DataPtr, bptr, DataLength); r2ptr->DataLength = DataLength; ListAddTail (&s2ptr->ReadList, r2ptr, LIST_ENTRY_TYPE_READ2); if (flags & HTTP2_FLAG_DATA_END_STR) { /* queue a zero-length buffer representing EOF */ r2ptr = VmGetHeap (rqeptr, sizeof(HTTP2_READ_STRUCT)); ListAddTail (&s2ptr->ReadList, r2ptr, LIST_ENTRY_TYPE_READ2); } } else if (flags & HTTP2_FLAG_DATA_END_STR) { /* queue a zero-length buffer representing EOF */ r2ptr = VmGetHeap (rqeptr, sizeof(HTTP2_READ_STRUCT)); ListAddTail (&s2ptr->ReadList, r2ptr, LIST_ENTRY_TYPE_READ2); } if (DataLength) { /****************/ /* flow control */ /****************/ /* stream */ s2ptr->ReadWindowSize -= DataLength; if (s2ptr->ReadWindowSize <= 0) { s2ptr->ReadWindowSize = h2ptr->ServerInitialWindowSize; Http2WindowUpdate (h2ptr, s2ptr->Ident, s2ptr->ReadWindowSize, NULL, 0); } /* HTTP/2 connection */ h2ptr->ReadWindowSize -= DataLength; if (h2ptr->ReadWindowSize <= 0) { h2ptr->ReadWindowSize = h2ptr->ServerInitialWindowSize * h2ptr->ServerMaxConcStreams; Http2WindowUpdate (h2ptr, 0, h2ptr->ReadWindowSize, NULL, 0); } } } else { /***********/ /* request */ /***********/ if (WATCH_MODULE(WATCH_MOD_HTTP2)) WatchThis (WATCHALL, WATCH_MOD_HTTP2, "size:!UL qlen:!UL", ioptr->ReadSize, LIST_GET_COUNT (&s2ptr->ReadList)); /* request looking for data (called from Http2NetRead()) */ if (LIST_NOT_EMPTY (&s2ptr->ReadList)) { /* here's one we prepared earlier :-) */ r2ptr = LIST_GET_HEAD (&s2ptr->ReadList); if (WATCH_MODULE(WATCH_MOD_HTTP2)) WatchThis (WATCHALL, WATCH_MOD_HTTP2, "length:!UL size:!UL", r2ptr->DataLength, ioptr->ReadSize); if (h2ptr->NetIoPtr->VmsStatus) { /* HTTP/2 connection (error) status */ ioptr->ReadIOsb.Status = h2ptr->NetIoPtr->VmsStatus; } else if (s2ptr->RequestPtr->NetIoPtr->VmsStatus) { /* commonly SS$_CANCEL */ ioptr->ReadIOsb.Status = s2ptr->RequestPtr->NetIoPtr->VmsStatus; } else if ((DataLength = r2ptr->DataLength) == 0) { /* zero-length buffer represents EOF */ ioptr->ReadIOsb.Status = SS$_ENDOFFILE; } else if (DataLength <= ioptr->ReadSize) { /* client data can fit into the buffer */ ioptr->ReadIOsb.Count = DataLength; ioptr->ReadIOsb.Status = SS$_NORMAL; memcpy (ioptr->ReadPtr, r2ptr->DataPtr, DataLength); /* as if it originated from the network */ ADD_LONG_QUAD (DataLength, rqeptr->BytesRx) } else { /* request buffer must accomodate maximum frame size */ ioptr->ReadIOsb.Status = SS$_BUGCHECK; ErrorNoticed (rqeptr, SS$_BUGCHECK, ErrorSanityCheck, FI_LI); } ListRemove (&s2ptr->ReadList, r2ptr); /* REQUEST MEMORY! */ VmFreeFromHeap (rqeptr, r2ptr, FI_LI); /* deliver via the NETIO AST delivery decoupler */ NetIoReadAst (ioptr); } else if (h2ptr->NetIoPtr->VmsStatus) { /* HTTP/2 connection (error) status */ ioptr->ReadIOsb.Status = h2ptr->NetIoPtr->VmsStatus; /* deliver via the NETIO AST delivery decoupler */ NetIoReadAst (ioptr); } else if (s2ptr->RequestPtr->NetIoPtr->VmsStatus) { /* commonly SS$_CANCEL */ ioptr->ReadIOsb.Status = s2ptr->RequestPtr->NetIoPtr->VmsStatus; /* deliver via the NETIO AST delivery decoupler */ NetIoReadAst (ioptr); } } return (0); } /*****************************************************************************/ /* Cancel any outstanding data read (queued but not delivered). Cancel any queued but outstanding writes. */ void Http2RequestCancel (REQUEST_STRUCT *rqptr) { HTTP2_STRUCT *h2ptr; HTTP2_READ_STRUCT *r2ptr; HTTP2_WRITE_STRUCT *next2, *w2ptr, *w22ptr; HTTP2_STREAM_STRUCT *s2ptr; NETIO_STRUCT *ioptr; VM_STRUCT *vmptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_HTTP2)) WatchThis (WATCHITM(rqptr), WATCH_MOD_HTTP2, "Http2RequestCancel()"); ioptr = rqptr->NetIoPtr; s2ptr = &rqptr->Http2Stream; h2ptr = s2ptr->Http2Ptr; rqptr->Http2Stream.State = HTTP2_STATE_CLOSED; /* cleanout unread data */ while (r2ptr = LIST_GET_HEAD (&s2ptr->ReadList)) { ListRemove (&s2ptr->ReadList, r2ptr); VmFreeFromHeap (rqptr, r2ptr, FI_LI); } if (NETIO_READ_IN_PROGRESS(ioptr)) { /* list empty and request waiting for or having data delivered */ if (!ioptr->ReadIOsb.Status) { /* request waiting for data so deliver a cancel */ ioptr->ReadIOsb.Count = 0; ioptr->ReadIOsb.Status = ioptr->VmsStatus = SS$_CANCEL; SysDclAst (NetIoReadAst, ioptr); } } /* cleanout unwritten data */ w22ptr = LIST_GET_HEAD (&s2ptr->WriteList); while (w22ptr) { /* first retrieve the next placeholder (if any) */ next2 = LIST_GET_NEXT (w22ptr); /* get the actual write entry from the placeholder */ w2ptr = w22ptr->Write2Ptr; /* if not HTTP/2 write in progress */ if (h2ptr->NetIoPtr->WriteAstParam != w2ptr) { /* remove from the HTTP/2 connection write list */ ListRemove (&h2ptr->QueuedWriteList[w2ptr->WriteQueue], w2ptr); /* remove and free the placemarker from the request's write list */ ListRemove (&s2ptr->WriteList, w22ptr); VmFreeFrom2Heap (h2ptr, w22ptr, FI_LI); if (w2ptr->InbuiltPtr) { /* restore the HTTP/2 header space magic */ vmptr = (VM_STRUCT*)((uchar*)w2ptr->DataPtr - VmStructSize); vmptr->h2magic = VM_H2MAGIC; } VmFreeFrom2Heap (h2ptr, w2ptr, FI_LI); } else if (!ioptr->Channel && !h2ptr->NetIoPtr->Channel) { /* no channel means no write possible */ if (!ioptr->WriteIOsb.Status) { /* status not set so delivery is not in progress */ ioptr->WriteIOsb.Count = 0; ioptr->WriteIOsb.Status = SS$_CANCEL; SysDclAst (NetIoWriteAst, ioptr); } } /* use previously retrieved next placeholder */ w22ptr = next2; } if (NETIO_IN_PROGRESS(ioptr)) ioptr->VmsStatus = SS$_CANCEL; else HttpdTimerSet (rqptr, TIMER_TERMINATE, 1); } /*****************************************************************************/ /* 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->DataPtr = w2ptr->payload; w2ptr->DataLength = value; if (!(w2ptr->AstFunction = AstFunction)) w2ptr->AstFunction = w2ptr->AstParam = Http2Net_BLOCKING_WRITE_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); } 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->DataPtr = w2ptr->payload; w2ptr->DataLength = length; if (headers) w2ptr->AstFunction = w2ptr->AstParam = Http2Net_BLOCKING_WRITE_AST; else { if (!(w2ptr->AstFunction = AstFunction)) w2ptr->AstFunction = w2ptr->AstParam = Http2Net_BLOCKING_WRITE_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); } /* 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()"); Http2RequestCancel (rqptr); retval = Http2ResetStream (h2ptr, ident, error, NULL, 0); return (retval); } /*****************************************************************************/ /* Called from RequestEnd() during request run-down. */ BOOL Http2RequestEnd (REQUEST_STRUCT *rqptr) { /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_HTTP2)) WatchThis (WATCHITM(rqptr), WATCH_MOD_HTTP2, "Http2RequestEnd() !&F ident:!UL state:!UL read:!&B write:!&B", Http2RequestEnd, rqptr->Http2Stream.Ident, rqptr->Http2Stream.State, !LIST_IS_EMPTY (&rqptr->Http2Stream.ReadList), !LIST_IS_EMPTY (&rqptr->Http2Stream.WriteList)); if (rqptr->Http2Stream.State != HTTP2_STATE_CLOSED && rqptr->Http2Stream.State != HTTP2_STATE_CLOSED_LOC) { rqptr->Http2Stream.State = HTTP2_STATE_CLOSED_LOC; Http2RequestCancel (rqptr); Http2NetWriteEnd (rqptr); } if (LIST_NOT_EMPTY (&rqptr->Http2Stream.ReadList) || LIST_NOT_EMPTY (&rqptr->Http2Stream.WriteList) || NETIO_IN_PROGRESS (rqptr->NetIoPtr)) return (false); return (true); } /*****************************************************************************/ /* Final stage of request run-down corresponding to RequestEnd5(). */ void Http2RequestEnd5 (REQUEST_STRUCT *rqptr) { 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; RequestEnd5 (rqptr); if (h2ptr->GoAwayLastStreamIdent) Http2CloseConnection (h2ptr); } /*****************************************************************************/