Skip to content

Commit dca3a46

Browse files
committed
optimize headers only response on http2. must read all inbound
1 parent 4fc6fbb commit dca3a46

File tree

5 files changed

+35
-12
lines changed

5 files changed

+35
-12
lines changed

src/main/java/robaho/net/httpserver/Http2ExchangeImpl.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ public void sendResponseHeaders(int rCode, long responseLength) throws IOExcepti
8888
}
8989
response.set(":status",Long.toString(rCode));
9090
responseCode = rCode;
91-
stream.writeResponseHeaders();
91+
stream.writeResponseHeaders(responseLength==-1);
9292
}
9393

9494
@Override

src/main/java/robaho/net/httpserver/ServerImpl.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -574,7 +574,7 @@ public void handleStream(HTTP2Stream stream,InputStream in, OutputStream out) th
574574
if (ctx == null) {
575575
logger.log(Level.DEBUG, "No context found for request "+uriPath+", rejecting as not found");
576576
response.set(":status","404");
577-
stream.writeResponseHeaders();
577+
stream.writeResponseHeaders(true);
578578
out.close();
579579
return;
580580
}

src/main/java/robaho/net/httpserver/http2/HTTP2Connection.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,7 @@ private void processFrames() throws Exception {
362362
logger.log(Level.TRACE,() -> "received WINDOW_UPDATE on closed stream "+streamId);
363363
continue;
364364
}
365-
throw new HTTP2Exception(HTTP2ErrorCode.STREAM_CLOSED, "frame "+frame.getHeader().getType()+ ", stream " + streamId + " is closed");
365+
throw new HTTP2Exception(HTTP2ErrorCode.STREAM_CLOSED, "frame "+frame.getHeader().getType()+ ", length "+ frame.getHeader().getLength()+", stream " + streamId + " is closed");
366366
}
367367
throw new HTTP2Exception(HTTP2ErrorCode.PROTOCOL_ERROR, "frame "+frame.getHeader().getType()+", stream "+streamId+" not in order");
368368
}

src/main/java/robaho/net/httpserver/http2/HTTP2Stream.java

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ public class HTTP2Stream {
4949
private volatile Thread thread;
5050
private volatile boolean streamOpen = true;
5151
private volatile boolean halfClosed = false;
52+
private volatile boolean streamOutClosed = false;
5253
private volatile AtomicBoolean handlingRequest = new AtomicBoolean(false);
5354

5455
private long dataInSize = 0;
@@ -218,16 +219,26 @@ private void performRequest(boolean halfClosed) throws IOException, HTTP2Excepti
218219
}
219220
});
220221
}
221-
public void writeResponseHeaders() throws IOException {
222-
if(headersSent.compareAndSet(false,true)) {
222+
/**
223+
* @param closeStream if true the output stream is closed, and any attempts
224+
* to write data to the stream will fail. This is an optimization that
225+
* allows the CLOSE_STREAM bit to be set in the Headers frame, reducing the
226+
* packet count.
227+
*/
228+
public void writeResponseHeaders(boolean closeStream) throws IOException {
229+
if (headersSent.compareAndSet(false, true)) {
223230
connection.lock();
224231
try {
225-
HPackContext.writeHeaderFrame(responseHeaders,connection.outputStream,streamId);
232+
HPackContext.writeHeaderFrame(responseHeaders, connection.outputStream, streamId, closeStream);
233+
if (closeStream) {
234+
streamOutClosed = true;
235+
}
226236
} finally {
227237
connection.unlock();
228238
}
229239
}
230240
}
241+
231242
public InetSocketAddress getLocalAddress() {
232243
return connection.getLocalAddress();
233244
}
@@ -267,7 +278,10 @@ public void write(byte[] b, int off, int len) throws IOException {
267278
connection.stats.pauses.incrementAndGet();
268279
LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(1));
269280
}
270-
writeResponseHeaders();
281+
writeResponseHeaders(false);
282+
if(streamOutClosed) {
283+
throw new IOException("output stream was closed during headers send");
284+
}
271285
while(len>0) {
272286
int _len = Math.min(Math.min(len,max_frame_size),(int)Math.min(connection.sendWindow.get(),sendWindow.get()));
273287
if(_len<=0) {
@@ -322,19 +336,25 @@ public void close() throws IOException {
322336
}
323337
return;
324338
}
325-
writeResponseHeaders();
339+
writeResponseHeaders(false);
326340
connection.lock();
327341
boolean lastRequest = connection.requestsInProgress.decrementAndGet() == 0;
328342
try {
329-
FrameHeader.writeTo(connection.outputStream, 0, FrameType.DATA, END_STREAM, streamId);
330-
connection.stats.framesSent.incrementAndGet();
343+
if(!streamOutClosed) {
344+
FrameHeader.writeTo(connection.outputStream, 0, FrameType.DATA, END_STREAM, streamId);
345+
connection.stats.framesSent.incrementAndGet();
346+
}
331347
if(lastRequest) {
332348
connection.outputStream.flush();
333349
connection.stats.flushes.incrementAndGet();
334350
}
335351
} finally {
336352
connection.unlock();
337353
}
354+
// same as http1, read all incoming frames when closing the output stream.
355+
// TODO review this, as the http2 stream is bidirectional and the spec may allow the server to continue to process inbound frames
356+
// after closing the outbound stream - similar to a http2 client
357+
dataIn.readAllBytes();
338358
} finally {
339359
connection.stats.activeStreams.decrementAndGet();
340360
closed=true;
@@ -355,6 +375,7 @@ public DataIn() {
355375
}
356376

357377
void enqueue(byte[] data) {
378+
if(closed) return;
358379
queue.add(data);
359380
LockSupport.unpark(reader);
360381
}

src/main/java/robaho/net/httpserver/http2/hpack/HPackContext.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import robaho.net.httpserver.http2.frame.FrameFlag;
1919
import robaho.net.httpserver.http2.frame.FrameHeader;
2020
import robaho.net.httpserver.http2.Utils;
21+
import robaho.net.httpserver.http2.frame.FrameFlag;
2122
import robaho.net.httpserver.http2.frame.FrameFlag.FlagSet;
2223
import robaho.net.httpserver.http2.frame.FrameType;
2324

@@ -210,7 +211,7 @@ private int decodeLiteralFieldNeverIndexed(byte[] buffer, int index, HTTP2Header
210211
}
211212

212213
/** this method is optimized for a server implementation and is not suitable for generic http2 hpack header encoding */
213-
public static void writeHeaderFrame(Headers headers, OutputStream outputStream, int streamId) throws IOException {
214+
public static void writeHeaderFrame(Headers headers, OutputStream outputStream, int streamId,boolean closeStream) throws IOException {
214215
ByteArrayOutputStream fields = new ByteArrayOutputStream(256); // average http headers length is 800 bytes
215216

216217
// ':status' is required and the only allowed outbound pseudo headers
@@ -238,7 +239,7 @@ public static void writeHeaderFrame(Headers headers, OutputStream outputStream,
238239
}
239240
});
240241

241-
FrameHeader.writeTo(outputStream, fields.size(),FrameType.HEADERS, END_OF_HEADERS, streamId);
242+
FrameHeader.writeTo(outputStream, fields.size(),FrameType.HEADERS,closeStream ? END_OF_HEADERS_AND_STREAM : END_OF_HEADERS, streamId);
242243
fields.writeTo(outputStream);
243244
}
244245

@@ -275,6 +276,7 @@ public static void writeGenericHeaderFrame(Headers headers, OutputStream outputS
275276
}
276277

277278
private static final FlagSet END_OF_HEADERS = FlagSet.of(FrameFlag.END_HEADERS);
279+
private static final FlagSet END_OF_HEADERS_AND_STREAM = FlagSet.of(FrameFlag.END_HEADERS,FrameFlag.END_STREAM);
278280

279281
public static List<byte[]> encodeHeadersFrame(Headers headers,int streamId) {
280282
byte[] buffer = encodeHeaders(headers);

0 commit comments

Comments
 (0)