Author: markt Date: Wed May 20 22:21:41 2015 New Revision: 1680690 URL: http://svn.apache.org/r1680690 Log: Get responses without bodies working (e.g. redirect) Lots of TODOs remain including fixing a couple of hacks that will only work when there is no response body.
Modified: tomcat/trunk/java/org/apache/coyote/http2/ByteUtil.java tomcat/trunk/java/org/apache/coyote/http2/Http2UpgradeHandler.java tomcat/trunk/java/org/apache/coyote/http2/LocalStrings.properties tomcat/trunk/java/org/apache/coyote/http2/Stream.java Modified: tomcat/trunk/java/org/apache/coyote/http2/ByteUtil.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/http2/ByteUtil.java?rev=1680690&r1=1680689&r2=1680690&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/coyote/http2/ByteUtil.java (original) +++ tomcat/trunk/java/org/apache/coyote/http2/ByteUtil.java Wed May 20 22:21:41 2015 @@ -37,6 +37,14 @@ public class ByteUtil { } + public static void set31Bits(byte[] input, int firstByte, int value) { + input[firstByte] = (byte) ((value & 0x7F000000) >> 24); + input[firstByte + 1] = (byte) ((value & 0xFF0000) >> 16); + input[firstByte + 2] = (byte) ((value & 0xFF00) >> 8); + input[firstByte + 3] = (byte) (value & 0xFF); + } + + public static int getOneByte(byte[] input, int pos) { return (input[pos] & 0xFF); } @@ -53,6 +61,13 @@ public class ByteUtil { } + public static void setThreeBytes(byte[] input, int firstByte, int value) { + input[firstByte] = (byte) ((value & 0xFF0000) >> 16); + input[firstByte + 1] = (byte) ((value & 0xFF00) >> 8); + input[firstByte + 2] = (byte) (value & 0xFF); + } + + public static int getFourBytes(byte[] input, int firstByte) { return ((input[firstByte] & 0xFF) << 24) + ((input[firstByte + 1] & 0xFF) << 16) + ((input[firstByte + 2] & 0xFF) << 8) + (input[firstByte + 3] & 0xFF); Modified: tomcat/trunk/java/org/apache/coyote/http2/Http2UpgradeHandler.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/http2/Http2UpgradeHandler.java?rev=1680690&r1=1680689&r2=1680690&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/coyote/http2/Http2UpgradeHandler.java (original) +++ tomcat/trunk/java/org/apache/coyote/http2/Http2UpgradeHandler.java Wed May 20 22:21:41 2015 @@ -29,10 +29,13 @@ import java.util.concurrent.atomic.Atomi import javax.servlet.http.WebConnection; import org.apache.coyote.Adapter; +import org.apache.coyote.Response; import org.apache.coyote.http11.upgrade.InternalHttpUpgradeHandler; +import org.apache.coyote.http2.HpackEncoder.State; import org.apache.coyote.http2.WriteStateMachine.WriteState; import org.apache.juli.logging.Log; import org.apache.juli.logging.LogFactory; +import org.apache.tomcat.util.http.MimeHeaders; import org.apache.tomcat.util.net.AbstractEndpoint.Handler.SocketState; import org.apache.tomcat.util.net.SocketStatus; import org.apache.tomcat.util.net.SocketWrapperBase; @@ -59,13 +62,18 @@ public class Http2UpgradeHandler extends private static final AtomicInteger connectionIdGenerator = new AtomicInteger(0); private static final Integer STREAM_ID_ZERO = Integer.valueOf(0); + private static final int FLAG_END_OF_STREAM = 1; + private static final int FLAG_END_OF_HEADERS = 4; + private static final int FRAME_TYPE_HEADERS = 1; private static final int FRAME_TYPE_PRIORITY = 2; private static final int FRAME_TYPE_SETTINGS = 4; private static final int FRAME_TYPE_WINDOW_UPDATE = 8; + private static final int FRAME_TYPE_CONTINUATION = 9; private static final byte[] SETTINGS_EMPTY = { 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00 }; private static final byte[] SETTINGS_ACK = { 0x00, 0x00, 0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00 }; + private static final byte[] GOAWAY = { 0x07, 0x00, 0x00, 0x00, 0x00 }; private final int connectionId; @@ -159,6 +167,13 @@ public class Http2UpgradeHandler extends try { while (processFrame()) { } + + // We are on a container thread. There is no more data to read + // so check for writes (more efficient than dispatching to a new + // thread). + if (writeStateMachine.endRead()) { + processWrites(); + } } catch (Http2Exception h2e) { if (h2e.getStreamId() == 0) { // Connection error @@ -171,23 +186,41 @@ public class Http2UpgradeHandler extends } } catch (IOException ioe) { if (log.isDebugEnabled()) { - log.debug(sm.getString("upgradeHandler.processFrame.ioerror"), ioe); + log.debug(sm.getString("upgradeHandler.ioerror", + Long.toString(connectionId)), ioe); } close(); result = SocketState.CLOSED; break; } - if (writeStateMachine.endRead()) { - processWrites(); - } - result = SocketState.UPGRADED; break; case OPEN_WRITE: if (writeStateMachine.startWrite()) { - processWrites(); + try { + processWrites(); + } catch (Http2Exception h2e) { + if (h2e.getStreamId() == 0) { + // Connection error + log.warn(sm.getString("upgradeHandler.connectionError"), h2e); + close(h2e); + break; + } else { + // Stream error + // TODO Reset stream + } + } catch (IOException ioe) { + if (log.isDebugEnabled()) { + log.debug(sm.getString("upgradeHandler.ioerror", + Long.toString(connectionId)), ioe); + } + close(); + result = SocketState.CLOSED; + break; + } + } result = SocketState.UPGRADED; break; @@ -611,7 +644,52 @@ public class Http2UpgradeHandler extends } - private void processWrites() { + void writeHeaders(Stream stream, Response coyoteResponse) throws IOException { + if (log.isDebugEnabled()) { + log.debug(sm.getString("upgradeHandler.writeHeaders", + Integer.toString(connectionId), stream.getIdentifier())); + } + MimeHeaders headers = coyoteResponse.getMimeHeaders(); + // Add the pseudo header for status + headers.addValue(":status").setString(Integer.toString(coyoteResponse.getStatus())); + // This ensures the Stream processing thread has control of the socket. + synchronized (socketWrapper) { + // Frame sizes are allowed to be bigger than 4k but for headers that + // should be plenty + byte[] header = new byte[9]; + ByteBuffer target = ByteBuffer.allocate(4 * 1024); + boolean first = true; + State state = null; + while (state != State.COMPLETE) { + state = hpackEncoder.encode(coyoteResponse.getMimeHeaders(), target); + target.flip(); + ByteUtil.setThreeBytes(header, 0, target.limit()); + if (first) { + header[3] = FRAME_TYPE_HEADERS; + } else { + header[3] = FRAME_TYPE_CONTINUATION; + } + if (state == State.COMPLETE) { + // TODO Determine end of stream correctly + header[4] = FLAG_END_OF_HEADERS + FLAG_END_OF_STREAM; + } + if (log.isDebugEnabled()) { + log.debug(target.limit() + " bytes"); + } + ByteUtil.set31Bits(header, 5, stream.getIdentifier().intValue()); + socketWrapper.write(true, header, 0, header.length); + socketWrapper.write(true, target.array(), target.arrayOffset(), target.limit()); + socketWrapper.flush(true); + } + } + } + + private void processWrites() throws IOException { + if (socketWrapper.flush(false)) { + socketWrapper.registerWriteInterest(); + return; + } + Object obj; while ((obj = getThingToWrite()) != null) { // TODO Modified: tomcat/trunk/java/org/apache/coyote/http2/LocalStrings.properties URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/http2/LocalStrings.properties?rev=1680690&r1=1680689&r2=1680690&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/coyote/http2/LocalStrings.properties (original) +++ tomcat/trunk/java/org/apache/coyote/http2/LocalStrings.properties Wed May 20 22:21:41 2015 @@ -38,9 +38,9 @@ streamProcessor.httpupgrade.notsupported upgradeHandler.connectionError=An error occurred that requires the HTTP/2 connection to be closed. upgradeHandler.init=Connection [{0}] +upgradeHandler.ioerror=Connection [{0}] upgradeHandler.payloadTooBig=The payload is [{0}] bytes long but the maximum frame size is [{1}] upgradeHandler.processFrame=Connection [{0}], Stream [{1}], Flags [{2}], Payload size [{3}] -upgradeHandler.processFrame.ioerror=An I/O error occurred while reading an incoming HTTP/2 frame upgradeHandler.processFrameHeaders.invalidStream=Headers frame received for stream [0] upgradeHandler.processFrameHeaders.decodingFailed=There was an error during the HPACK decoding of HTTP headers upgradeHandler.processFrameHeaders.decodingDataLeft=Data left over after HPACK decoding - it should have been consumed @@ -59,7 +59,7 @@ upgradeHandler.unexpectedEos=Unexpected upgradeHandler.unexpectedStatus=An unexpected value of status ([{0}]) was passed to this method upgradeHandler.upgradeDispatch.entry=Entry, Connection [{0}], SocketStatus [{1}] upgradeHandler.upgradeDispatch.exit=Exit, Connection [{0}], SocketState [{1}] - +upgradeHandler.writeHeaders=Connection [{0}], Stream [{1}] writeStateMachine.endWrite.ise=It is illegal to specify [{0}] for the new state once a write has completed writeStateMachine.ise=It is illegal to call [{0}()] in state [{1}] \ No newline at end of file Modified: tomcat/trunk/java/org/apache/coyote/http2/Stream.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/http2/Stream.java?rev=1680690&r1=1680689&r2=1680690&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/coyote/http2/Stream.java (original) +++ tomcat/trunk/java/org/apache/coyote/http2/Stream.java Wed May 20 22:21:41 2015 @@ -96,13 +96,12 @@ public class Stream extends AbstractStre void writeHeaders() { - if (log.isDebugEnabled()) { - log.debug(sm.getString("stream.write", - Long.toString(getConnectionId()), getIdentifier())); + try { + handler.writeHeaders(this, coyoteResponse); + } catch (IOException e) { + // TODO Handle this + e.printStackTrace(); } - // Format the frames. - // TODO - handler.addWrite("HEADERS"); } --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org