private static void assertHeaders(Object msg, int streamId, boolean last, SpdyHeaders headers) { assertNotNull(msg); assertTrue(msg instanceof SpdyHeadersFrame); SpdyHeadersFrame spdyHeadersFrame = (SpdyHeadersFrame) msg; assertEquals(spdyHeadersFrame.getStreamId(), streamId); assertEquals(spdyHeadersFrame.isLast(), last); for (String name : headers.names()) { List<String> expectedValues = headers.getAll(name); List<String> receivedValues = spdyHeadersFrame.headers().getAll(name); assertTrue(receivedValues.containsAll(expectedValues)); receivedValues.removeAll(expectedValues); assertTrue(receivedValues.isEmpty()); spdyHeadersFrame.headers().remove(name); } assertTrue(spdyHeadersFrame.headers().isEmpty()); }
@Override protected void decode( ChannelHandlerContext ctx, SpdyDataOrControlFrame msg, MessageBuf<Object> out) throws Exception { if (msg instanceof SpdySynStreamFrame) { // HTTP requests/responses are mapped one-to-one to SPDY streams. SpdySynStreamFrame spdySynStreamFrame = (SpdySynStreamFrame) msg; int streamId = spdySynStreamFrame.getStreamId(); if (SpdyCodecUtil.isServerId(streamId)) { // SYN_STREAM frames initiated by the server are pushed resources int associatedToStreamId = spdySynStreamFrame.getAssociatedToStreamId(); // If a client receives a SYN_STREAM with an Associated-To-Stream-ID of 0 // it must reply with a RST_STREAM with error code INVALID_STREAM if (associatedToStreamId == 0) { SpdyRstStreamFrame spdyRstStreamFrame = new DefaultSpdyRstStreamFrame(streamId, SpdyStreamStatus.INVALID_STREAM); ctx.write(spdyRstStreamFrame); } String URL = SpdyHeaders.getUrl(spdyVersion, spdySynStreamFrame); // If a client receives a SYN_STREAM without a 'url' header // it must reply with a RST_STREAM with error code PROTOCOL_ERROR if (URL == null) { SpdyRstStreamFrame spdyRstStreamFrame = new DefaultSpdyRstStreamFrame(streamId, SpdyStreamStatus.PROTOCOL_ERROR); ctx.write(spdyRstStreamFrame); } try { FullHttpResponse httpResponseWithEntity = createHttpResponse(spdyVersion, spdySynStreamFrame); // Set the Stream-ID, Associated-To-Stream-ID, Priority, and URL as headers SpdyHttpHeaders.setStreamId(httpResponseWithEntity, streamId); SpdyHttpHeaders.setAssociatedToStreamId(httpResponseWithEntity, associatedToStreamId); SpdyHttpHeaders.setPriority(httpResponseWithEntity, spdySynStreamFrame.getPriority()); SpdyHttpHeaders.setUrl(httpResponseWithEntity, URL); if (spdySynStreamFrame.isLast()) { HttpHeaders.setContentLength(httpResponseWithEntity, 0); out.add(httpResponseWithEntity); } else { // Response body will follow in a series of Data Frames putMessage(streamId, httpResponseWithEntity); } } catch (Exception e) { SpdyRstStreamFrame spdyRstStreamFrame = new DefaultSpdyRstStreamFrame(streamId, SpdyStreamStatus.PROTOCOL_ERROR); ctx.write(spdyRstStreamFrame); } } else { // SYN_STREAM frames initiated by the client are HTTP requests try { FullHttpRequest httpRequestWithEntity = createHttpRequest(spdyVersion, spdySynStreamFrame); // Set the Stream-ID as a header SpdyHttpHeaders.setStreamId(httpRequestWithEntity, streamId); if (spdySynStreamFrame.isLast()) { out.add(httpRequestWithEntity); } else { // Request body will follow in a series of Data Frames putMessage(streamId, httpRequestWithEntity); } } catch (Exception e) { // If a client sends a SYN_STREAM without all of the getMethod, url (host and path), // scheme, and version headers the server must reply with a HTTP 400 BAD REQUEST reply. // Also sends HTTP 400 BAD REQUEST reply if header name/value pairs are invalid SpdySynReplyFrame spdySynReplyFrame = new DefaultSpdySynReplyFrame(streamId); spdySynReplyFrame.setLast(true); SpdyHeaders.setStatus(spdyVersion, spdySynReplyFrame, HttpResponseStatus.BAD_REQUEST); SpdyHeaders.setVersion(spdyVersion, spdySynReplyFrame, HttpVersion.HTTP_1_0); ctx.write(spdySynReplyFrame); } } } else if (msg instanceof SpdySynReplyFrame) { SpdySynReplyFrame spdySynReplyFrame = (SpdySynReplyFrame) msg; int streamId = spdySynReplyFrame.getStreamId(); try { FullHttpResponse httpResponseWithEntity = createHttpResponse(spdyVersion, spdySynReplyFrame); // Set the Stream-ID as a header SpdyHttpHeaders.setStreamId(httpResponseWithEntity, streamId); if (spdySynReplyFrame.isLast()) { HttpHeaders.setContentLength(httpResponseWithEntity, 0); out.add(httpResponseWithEntity); } else { // Response body will follow in a series of Data Frames putMessage(streamId, httpResponseWithEntity); } } catch (Exception e) { // If a client receives a SYN_REPLY without valid getStatus and version headers // the client must reply with a RST_STREAM frame indicating a PROTOCOL_ERROR SpdyRstStreamFrame spdyRstStreamFrame = new DefaultSpdyRstStreamFrame(streamId, SpdyStreamStatus.PROTOCOL_ERROR); ctx.write(spdyRstStreamFrame); } } else if (msg instanceof SpdyHeadersFrame) { SpdyHeadersFrame spdyHeadersFrame = (SpdyHeadersFrame) msg; int streamId = spdyHeadersFrame.getStreamId(); FullHttpMessage fullHttpMessage = getMessage(streamId); // If message is not in map discard HEADERS frame. if (fullHttpMessage == null) { return; } for (Map.Entry<String, String> e : spdyHeadersFrame.headers().entries()) { fullHttpMessage.headers().add(e.getKey(), e.getValue()); } if (spdyHeadersFrame.isLast()) { HttpHeaders.setContentLength(fullHttpMessage, fullHttpMessage.content().readableBytes()); removeMessage(streamId); out.add(fullHttpMessage); } } else if (msg instanceof SpdyDataFrame) { SpdyDataFrame spdyDataFrame = (SpdyDataFrame) msg; int streamId = spdyDataFrame.getStreamId(); FullHttpMessage fullHttpMessage = getMessage(streamId); // If message is not in map discard Data Frame. if (fullHttpMessage == null) { return; } ByteBuf content = fullHttpMessage.content(); if (content.readableBytes() > maxContentLength - spdyDataFrame.content().readableBytes()) { removeMessage(streamId); throw new TooLongFrameException( "HTTP content length exceeded " + maxContentLength + " bytes."); } ByteBuf spdyDataFrameData = spdyDataFrame.content(); int spdyDataFrameDataLen = spdyDataFrameData.readableBytes(); content.writeBytes(spdyDataFrameData, spdyDataFrameData.readerIndex(), spdyDataFrameDataLen); if (spdyDataFrame.isLast()) { HttpHeaders.setContentLength(fullHttpMessage, content.readableBytes()); removeMessage(streamId); out.add(fullHttpMessage); } } else if (msg instanceof SpdyRstStreamFrame) { SpdyRstStreamFrame spdyRstStreamFrame = (SpdyRstStreamFrame) msg; int streamId = spdyRstStreamFrame.getStreamId(); removeMessage(streamId); } }
private static void testSpdySessionHandler(SpdyVersion version, boolean server) { EmbeddedChannel sessionHandler = new EmbeddedChannel( new SpdySessionHandler(version, server), new EchoHandler(closeSignal, server)); while (sessionHandler.readOutbound() != null) { continue; } int localStreamId = server ? 1 : 2; int remoteStreamId = server ? 2 : 1; SpdySynStreamFrame spdySynStreamFrame = new DefaultSpdySynStreamFrame(localStreamId, 0, (byte) 0); spdySynStreamFrame.headers().set("Compression", "test"); SpdyDataFrame spdyDataFrame = new DefaultSpdyDataFrame(localStreamId); spdyDataFrame.setLast(true); // Check if session handler returns INVALID_STREAM if it receives // a data frame for a Stream-ID that is not open sessionHandler.writeInbound(new DefaultSpdyDataFrame(localStreamId)); assertRstStream(sessionHandler.readOutbound(), localStreamId, SpdyStreamStatus.INVALID_STREAM); assertNull(sessionHandler.readOutbound()); // Check if session handler returns PROTOCOL_ERROR if it receives // a data frame for a Stream-ID before receiving a SYN_REPLY frame sessionHandler.writeInbound(new DefaultSpdyDataFrame(remoteStreamId)); assertRstStream(sessionHandler.readOutbound(), remoteStreamId, SpdyStreamStatus.PROTOCOL_ERROR); assertNull(sessionHandler.readOutbound()); remoteStreamId += 2; // Check if session handler returns PROTOCOL_ERROR if it receives // multiple SYN_REPLY frames for the same active Stream-ID sessionHandler.writeInbound(new DefaultSpdySynReplyFrame(remoteStreamId)); assertNull(sessionHandler.readOutbound()); sessionHandler.writeInbound(new DefaultSpdySynReplyFrame(remoteStreamId)); assertRstStream(sessionHandler.readOutbound(), remoteStreamId, SpdyStreamStatus.STREAM_IN_USE); assertNull(sessionHandler.readOutbound()); remoteStreamId += 2; // Check if frame codec correctly compresses/uncompresses headers sessionHandler.writeInbound(spdySynStreamFrame); assertSynReply( sessionHandler.readOutbound(), localStreamId, false, spdySynStreamFrame.headers()); assertNull(sessionHandler.readOutbound()); SpdyHeadersFrame spdyHeadersFrame = new DefaultSpdyHeadersFrame(localStreamId); spdyHeadersFrame.headers().add("HEADER", "test1"); spdyHeadersFrame.headers().add("HEADER", "test2"); sessionHandler.writeInbound(spdyHeadersFrame); assertHeaders(sessionHandler.readOutbound(), localStreamId, false, spdyHeadersFrame.headers()); assertNull(sessionHandler.readOutbound()); localStreamId += 2; // Check if session handler closed the streams using the number // of concurrent streams and that it returns REFUSED_STREAM // if it receives a SYN_STREAM frame it does not wish to accept spdySynStreamFrame.setStreamId(localStreamId); spdySynStreamFrame.setLast(true); spdySynStreamFrame.setUnidirectional(true); sessionHandler.writeInbound(spdySynStreamFrame); assertRstStream(sessionHandler.readOutbound(), localStreamId, SpdyStreamStatus.REFUSED_STREAM); assertNull(sessionHandler.readOutbound()); // Check if session handler rejects HEADERS for closed streams int testStreamId = spdyDataFrame.getStreamId(); sessionHandler.writeInbound(spdyDataFrame); assertDataFrame(sessionHandler.readOutbound(), testStreamId, spdyDataFrame.isLast()); assertNull(sessionHandler.readOutbound()); spdyHeadersFrame.setStreamId(testStreamId); sessionHandler.writeInbound(spdyHeadersFrame); assertRstStream(sessionHandler.readOutbound(), testStreamId, SpdyStreamStatus.INVALID_STREAM); assertNull(sessionHandler.readOutbound()); // Check if session handler drops active streams if it receives // a RST_STREAM frame for that Stream-ID sessionHandler.writeInbound(new DefaultSpdyRstStreamFrame(remoteStreamId, 3)); assertNull(sessionHandler.readOutbound()); remoteStreamId += 2; // Check if session handler honors UNIDIRECTIONAL streams spdySynStreamFrame.setLast(false); sessionHandler.writeInbound(spdySynStreamFrame); assertNull(sessionHandler.readOutbound()); spdySynStreamFrame.setUnidirectional(false); // Check if session handler returns PROTOCOL_ERROR if it receives // multiple SYN_STREAM frames for the same active Stream-ID sessionHandler.writeInbound(spdySynStreamFrame); assertRstStream(sessionHandler.readOutbound(), localStreamId, SpdyStreamStatus.PROTOCOL_ERROR); assertNull(sessionHandler.readOutbound()); localStreamId += 2; // Check if session handler returns PROTOCOL_ERROR if it receives // a SYN_STREAM frame with an invalid Stream-ID spdySynStreamFrame.setStreamId(localStreamId - 1); sessionHandler.writeInbound(spdySynStreamFrame); assertRstStream( sessionHandler.readOutbound(), localStreamId - 1, SpdyStreamStatus.PROTOCOL_ERROR); assertNull(sessionHandler.readOutbound()); spdySynStreamFrame.setStreamId(localStreamId); // Check if session handler returns PROTOCOL_ERROR if it receives // an invalid HEADERS frame spdyHeadersFrame.setStreamId(localStreamId); spdyHeadersFrame.setInvalid(); sessionHandler.writeInbound(spdyHeadersFrame); assertRstStream(sessionHandler.readOutbound(), localStreamId, SpdyStreamStatus.PROTOCOL_ERROR); assertNull(sessionHandler.readOutbound()); sessionHandler.finish(); }
@Override protected void encode(ChannelHandlerContext ctx, HttpObject msg, List<Object> out) throws Exception { boolean valid = false; boolean last = false; if (msg instanceof HttpRequest) { HttpRequest httpRequest = (HttpRequest) msg; SpdySynStreamFrame spdySynStreamFrame = createSynStreamFrame(httpRequest); out.add(spdySynStreamFrame); last = spdySynStreamFrame.isLast(); valid = true; } if (msg instanceof HttpResponse) { HttpResponse httpResponse = (HttpResponse) msg; if (httpResponse.headers().contains(SpdyHttpHeaders.Names.ASSOCIATED_TO_STREAM_ID)) { SpdySynStreamFrame spdySynStreamFrame = createSynStreamFrame(httpResponse); last = spdySynStreamFrame.isLast(); out.add(spdySynStreamFrame); } else { SpdySynReplyFrame spdySynReplyFrame = createSynReplyFrame(httpResponse); last = spdySynReplyFrame.isLast(); out.add(spdySynReplyFrame); } valid = true; } if (msg instanceof HttpContent && !last) { HttpContent chunk = (HttpContent) msg; chunk.content().retain(); SpdyDataFrame spdyDataFrame = new DefaultSpdyDataFrame(currentStreamId, chunk.content()); spdyDataFrame.setLast(chunk instanceof LastHttpContent); if (chunk instanceof LastHttpContent) { LastHttpContent trailer = (LastHttpContent) chunk; HttpHeaders trailers = trailer.trailingHeaders(); if (trailers.isEmpty()) { out.add(spdyDataFrame); } else { // Create SPDY HEADERS frame out of trailers SpdyHeadersFrame spdyHeadersFrame = new DefaultSpdyHeadersFrame(currentStreamId); for (Map.Entry<String, String> entry : trailers) { spdyHeadersFrame.headers().add(entry.getKey(), entry.getValue()); } // Write HEADERS frame and append Data Frame out.add(spdyHeadersFrame); out.add(spdyDataFrame); } } else { out.add(spdyDataFrame); } valid = true; } if (!valid) { throw new UnsupportedMessageTypeException(msg); } }