@@ -91,6 +91,44 @@ ubyte[] buildHeaderFrame(alias type)(string statusLine, InetHeaderMap headers,
9191 return buildHeaderFrame! type(statusLine, headers, context, context.table, alloc);
9292}
9393
94+ // / Splits an HPACK-encoded header block into a HEADERS frame followed by
95+ // / zero or more CONTINUATION frames, respecting maxFrameSize.
96+ // / extraFlags are ORed into the HEADERS frame flags (e.g., END_STREAM).
97+ // / END_HEADERS is automatically set on the last frame.
98+ // / Returns the complete serialized frame sequence.
99+ package ubyte [] buildSplitHeaderFrames(ubyte [] hpackPayload, uint maxFrameSize, uint streamId,
100+ ubyte extraFlags, scope IAllocator alloc) @safe
101+ {
102+ auto result = AllocAppender! (ubyte [])(alloc);
103+
104+ if (hpackPayload.length <= maxFrameSize) {
105+ result.createHTTP2FrameHeader(cast (uint ) hpackPayload.length, HTTP2FrameType.HEADERS ,
106+ cast (ubyte )(HTTP2FrameFlag.END_HEADERS | extraFlags), streamId);
107+ result.put(hpackPayload);
108+ } else {
109+ // First HEADERS frame without END_HEADERS
110+ result.createHTTP2FrameHeader(cast (uint ) maxFrameSize, HTTP2FrameType.HEADERS ,
111+ extraFlags, streamId);
112+ result.put(hpackPayload[0 .. maxFrameSize]);
113+
114+ auto remaining = hpackPayload[maxFrameSize .. $];
115+
116+ while (remaining.length > 0 ) {
117+ auto len = remaining.length > maxFrameSize ? maxFrameSize : remaining.length;
118+ bool isLast = (len == remaining.length);
119+ ubyte flags = isLast ? HTTP2FrameFlag.END_HEADERS : 0x0 ;
120+
121+ result.createHTTP2FrameHeader(cast (uint ) len, HTTP2FrameType.CONTINUATION ,
122+ flags, streamId);
123+ result.put(remaining[0 .. len]);
124+
125+ remaining = remaining[len .. $];
126+ }
127+ }
128+
129+ return result.data;
130+ }
131+
94132// / generates an HTTP/2 pseudo-header representation to encode a HTTP/1.1 start message line
95133private void convertStartMessage (T)(string src, ref T dst, ref IndexingTable table, StartLine type, bool isTLS = true ) @safe
96134{
@@ -155,6 +193,122 @@ unittest {
155193 assert (res == expected);
156194}
157195
196+ // Tests for buildSplitHeaderFrames
197+ unittest {
198+ import std.experimental.allocator.mallocator ;
199+
200+ scope alloc = new RegionListAllocator! (shared (Mallocator), false )(1024 , Mallocator.instance);
201+
202+ // helper: parse a frame header from raw bytes
203+ HTTP2FrameHeader parseHeader (ubyte [] data) {
204+ return unpackHTTP2FrameHeader (data);
205+ }
206+
207+ // Small payload fits in a single HEADERS frame
208+ {
209+ ubyte [] payload = [0x88 , 0x86 , 0x84 ]; // 3 bytes
210+ auto result = buildSplitHeaderFrames(payload, 16384 , 1 , 0x0 , alloc);
211+
212+ assert (result.length == HTTP2HeaderLength + 3 );
213+ auto hdr = parseHeader(result);
214+ assert (hdr.type == HTTP2FrameType.HEADERS );
215+ assert (hdr.payloadLength == 3 );
216+ assert (hdr.flags == HTTP2FrameFlag.END_HEADERS );
217+ assert (hdr.streamId == 1 );
218+ assert (result[HTTP2HeaderLength .. $] == payload);
219+ }
220+
221+ // Single frame with END_STREAM flag
222+ {
223+ ubyte [] payload = [0x88 ];
224+ auto result = buildSplitHeaderFrames(payload, 16384 , 5 , HTTP2FrameFlag.END_STREAM , alloc);
225+
226+ auto hdr = parseHeader(result);
227+ assert (hdr.type == HTTP2FrameType.HEADERS );
228+ assert (hdr.flags == (HTTP2FrameFlag.END_HEADERS | HTTP2FrameFlag.END_STREAM ));
229+ assert (hdr.streamId == 5 );
230+ }
231+
232+ // Payload exactly at maxFrameSize boundary: single frame
233+ {
234+ auto payload = new ubyte [](10 );
235+ payload[] = 0xAB ;
236+ auto result = buildSplitHeaderFrames(payload, 10 , 3 , 0x0 , alloc);
237+
238+ assert (result.length == HTTP2HeaderLength + 10 );
239+ auto hdr = parseHeader(result);
240+ assert (hdr.type == HTTP2FrameType.HEADERS );
241+ assert (hdr.flags == HTTP2FrameFlag.END_HEADERS );
242+ assert (hdr.payloadLength == 10 );
243+ }
244+
245+ // Payload exceeds maxFrameSize: HEADERS + 1 CONTINUATION
246+ {
247+ auto payload = new ubyte [](15 );
248+ foreach (i, ref b; payload) b = cast (ubyte )(i & 0xFF );
249+ auto result = buildSplitHeaderFrames(payload, 10 , 7 , 0x0 , alloc);
250+
251+ // First frame: HEADERS, 10 bytes, no END_HEADERS
252+ assert (result.length == 2 * HTTP2HeaderLength + 15 );
253+ auto hdr1 = parseHeader(result);
254+ assert (hdr1.type == HTTP2FrameType.HEADERS );
255+ assert (hdr1.payloadLength == 10 );
256+ assert (hdr1.flags == 0x0 );
257+ assert (hdr1.streamId == 7 );
258+ assert (result[HTTP2HeaderLength .. HTTP2HeaderLength + 10 ] == payload[0 .. 10 ]);
259+
260+ // Second frame: CONTINUATION, 5 bytes, END_HEADERS
261+ auto frame2 = result[HTTP2HeaderLength + 10 .. $];
262+ auto hdr2 = parseHeader(frame2);
263+ assert (hdr2.type == HTTP2FrameType.CONTINUATION );
264+ assert (hdr2.payloadLength == 5 );
265+ assert (hdr2.flags == HTTP2FrameFlag.END_HEADERS );
266+ assert (hdr2.streamId == 7 );
267+ assert (frame2[HTTP2HeaderLength .. $] == payload[10 .. 15 ]);
268+ }
269+
270+ // Payload needs 3 frames: HEADERS + 2 CONTINUATION
271+ {
272+ auto payload = new ubyte [](25 );
273+ payload[] = 0xCC ;
274+ auto result = buildSplitHeaderFrames(payload, 10 , 1 , HTTP2FrameFlag.END_STREAM , alloc);
275+
276+ assert (result.length == 3 * HTTP2HeaderLength + 25 );
277+
278+ // Frame 1: HEADERS, 10 bytes, END_STREAM only (no END_HEADERS)
279+ auto hdr1 = parseHeader(result);
280+ assert (hdr1.type == HTTP2FrameType.HEADERS );
281+ assert (hdr1.payloadLength == 10 );
282+ assert (hdr1.flags == HTTP2FrameFlag.END_STREAM );
283+
284+ // Frame 2: CONTINUATION, 10 bytes, no flags
285+ auto f2 = result[HTTP2HeaderLength + 10 .. $];
286+ auto hdr2 = parseHeader(f2);
287+ assert (hdr2.type == HTTP2FrameType.CONTINUATION );
288+ assert (hdr2.payloadLength == 10 );
289+ assert (hdr2.flags == 0x0 );
290+
291+ // Frame 3: CONTINUATION, 5 bytes, END_HEADERS
292+ auto f3 = f2[HTTP2HeaderLength + 10 .. $];
293+ auto hdr3 = parseHeader(f3);
294+ assert (hdr3.type == HTTP2FrameType.CONTINUATION );
295+ assert (hdr3.payloadLength == 5 );
296+ assert (hdr3.flags == HTTP2FrameFlag.END_HEADERS );
297+ }
298+
299+ // Empty payload: single HEADERS frame with 0 length
300+ {
301+ ubyte [] payload = [];
302+ auto result = buildSplitHeaderFrames(payload, 16384 , 1 , 0x0 , alloc);
303+
304+ assert (result.length == HTTP2HeaderLength);
305+ auto hdr = parseHeader(result);
306+ assert (hdr.type == HTTP2FrameType.HEADERS );
307+ assert (hdr.payloadLength == 0 );
308+ assert (hdr.flags == HTTP2FrameFlag.END_HEADERS );
309+ }
310+ }
311+
158312/* ======================================================= */
159313/* HTTP/2 REQUEST HANDLING */
160314/* ======================================================= */
@@ -373,15 +527,9 @@ bool handleHTTP2Request(UStream)(ref HTTP2ConnectionStream!UStream stream,
373527 h2context, table, alloc, istls);
374528 }();
375529
376- // send HEADERS frame
377- if (headerFrame.length < h2context.settings.maxFrameSize) {
378- headerFrame[4 ] += 0x4 ; // set END_HEADERS flag (sending complete header)
379- cstream.write(headerFrame);
380-
381- } else {
382- // TODO CONTINUATION frames
383- assert (false );
384- }
530+ // send HEADERS frame (+ CONTINUATION if needed)
531+ cstream.write(buildSplitHeaderFrames(headerFrame[HTTP2HeaderLength .. $],
532+ h2context.settings.maxFrameSize, stream.streamId, 0x0 , alloc));
385533
386534 logDebug(" Sent HEADERS frame on streamID " ~ stream.streamId.to! string );
387535
@@ -485,7 +633,7 @@ bool handleHTTP2Request(UStream)(ref HTTP2ConnectionStream!UStream stream,
485633 // spawn the asynchronous data sender
486634 sendDataTask();
487635
488- } else if (dataWriter.data.length > 0 ) { // HEAD response, HEADERS frame, no DATA
636+ } else { // HEAD response or no body (e.g. 204): HEADERS frame only , no DATA
489637
490638 // write the status line
491639 writeLine(" %s %d %s" ,
@@ -499,46 +647,19 @@ bool handleHTTP2Request(UStream)(ref HTTP2ConnectionStream!UStream stream,
499647 h2context, table, alloc, istls);
500648 }();
501649
502- // send HEADERS frame
503- if (headerFrame.length < h2context.settings.maxFrameSize) {
504- headerFrame[4 ] += 0x5 ; // set END_HEADERS, END_STREAM flag
505- cstream.write(headerFrame);
506- } else {
507- // TODO CONTINUATION frames
508- assert (false );
509- }
650+ // send HEADERS frame with END_STREAM (+ CONTINUATION if needed)
651+ cstream.write(buildSplitHeaderFrames(headerFrame[HTTP2HeaderLength .. $],
652+ h2context.settings.maxFrameSize, stream.streamId, HTTP2FrameFlag.END_STREAM , alloc));
510653
511654 logDebug(" Sent HEADERS frame on streamID " ~ stream.streamId.to! string );
512655
513- logDebug(" [Data] No DATA frame to send" );
514-
515656 if (stream.state == HTTP2StreamState.HALF_CLOSED_REMOTE ) {
516657 stream.state = HTTP2StreamState.CLOSED ;
517658 } else {
518659 stream.state = HTTP2StreamState.HALF_CLOSED_LOCAL ;
519660 }
520661 closeStream(h2context.multiplexer, stream.streamId);
521662
522- } else { // 404: no DATA for the given path
523-
524- writeLine(" %s %d %s" ,
525- " HTTP/2" ,
526- 404 ,
527- " Not Found" );
528-
529- // build the HEADERS frame
530- () @trusted {
531- headerFrame = buildHeaderFrame! (StartLine.RESPONSE )(statusLine.data, res.headers,
532- h2context, table, alloc, istls);
533- }();
534-
535- if (headerFrame.length < h2context.settings.maxFrameSize) {
536- headerFrame[4 ] += 0x5 ; // set END_HEADERS, END_STREAM flag
537- cstream.write(headerFrame);
538- }
539-
540- logDebug(" No response: sent 404 HEADERS frame" );
541-
542663 }
543664
544665 return true ;
0 commit comments