@Test public void testResponseWithChunkedContent() throws Exception { ByteBuffer header = BufferUtils.allocate(4096); ByteBuffer chunk = BufferUtils.allocate(HttpGenerator.CHUNK_SIZE); ByteBuffer content0 = BufferUtils.toBuffer("Hello World! "); ByteBuffer content1 = BufferUtils.toBuffer("The quick brown fox jumped over the lazy dog. "); HttpGenerator gen = new HttpGenerator(); HttpGenerator.Result result = gen.generateResponse(null, null, null, content0, false); assertEquals(HttpGenerator.Result.NEED_INFO, result); assertEquals(HttpGenerator.State.START, gen.getState()); MetaData.Response info = new MetaData.Response(HttpVersion.HTTP_1_1, 200, null, new HttpFields(), -1); info.getFields().add("Last-Modified", DateGenerator.__01Jan1970); result = gen.generateResponse(info, null, null, content0, false); assertEquals(HttpGenerator.Result.NEED_HEADER, result); assertEquals(HttpGenerator.State.START, gen.getState()); result = gen.generateResponse(info, header, null, content0, false); assertEquals(HttpGenerator.Result.FLUSH, result); assertEquals(HttpGenerator.State.COMMITTED, gen.getState()); String out = BufferUtils.toString(header); BufferUtils.clear(header); out += BufferUtils.toString(content0); BufferUtils.clear(content0); result = gen.generateResponse(null, null, chunk, content1, false); assertEquals(HttpGenerator.Result.FLUSH, result); assertEquals(HttpGenerator.State.COMMITTED, gen.getState()); out += BufferUtils.toString(chunk); BufferUtils.clear(chunk); out += BufferUtils.toString(content1); BufferUtils.clear(content1); result = gen.generateResponse(null, null, chunk, null, true); assertEquals(HttpGenerator.Result.CONTINUE, result); assertEquals(HttpGenerator.State.COMPLETING, gen.getState()); result = gen.generateResponse(null, null, chunk, null, true); assertEquals(HttpGenerator.Result.FLUSH, result); assertEquals(HttpGenerator.State.COMPLETING, gen.getState()); out += BufferUtils.toString(chunk); BufferUtils.clear(chunk); result = gen.generateResponse(null, null, chunk, null, true); assertEquals(HttpGenerator.Result.DONE, result); assertEquals(HttpGenerator.State.END, gen.getState()); assertThat(out, containsString("HTTP/1.1 200 OK")); assertThat(out, containsString("Last-Modified: Thu, 01 Jan 1970 00:00:00 GMT")); assertThat(out, not(containsString("Content-Length"))); assertThat(out, containsString("Transfer-Encoding: chunked")); assertThat(out, containsString("\r\n\r\nD\r\n")); assertThat(out, containsString("\r\nHello World! \r\n")); assertThat(out, containsString("\r\n2E\r\n")); assertThat(out, containsString("\r\nThe quick brown fox jumped over the lazy dog. \r\n")); assertThat(out, containsString("\r\n0\r\n")); }
@Test public void test204() throws Exception { ByteBuffer header = BufferUtils.allocate(8096); ByteBuffer content = BufferUtils.toBuffer("0123456789"); HttpGenerator gen = new HttpGenerator(); MetaData.Response info = new MetaData.Response(HttpVersion.HTTP_1_1, 204, "Foo", new HttpFields(), 10); info.getFields().add("Content-Type", "test/data"); info.getFields().add("Last-Modified", DateGenerator.__01Jan1970); HttpGenerator.Result result = gen.generateResponse(info, header, null, content, true); assertEquals(gen.isNoContent(), true); assertEquals(HttpGenerator.Result.FLUSH, result); assertEquals(HttpGenerator.State.COMPLETING, gen.getState()); String responseheaders = BufferUtils.toString(header); BufferUtils.clear(header); result = gen.generateResponse(null, null, null, content, false); assertEquals(HttpGenerator.Result.DONE, result); assertEquals(HttpGenerator.State.END, gen.getState()); assertThat(responseheaders, containsString("HTTP/1.1 204 Foo")); assertThat(responseheaders, containsString("Last-Modified: Thu, 01 Jan 1970 00:00:00 GMT")); assertThat(responseheaders, not(containsString("Content-Length: 10"))); // Note: the HttpConnection.process() method is responsible for actually // excluding the content from the response based on // generator.isNoContent()==true }
@Test public void testResponseUpgrade() throws Exception { ByteBuffer header = BufferUtils.allocate(8096); HttpGenerator gen = new HttpGenerator(); HttpGenerator.Result result = gen.generateResponse(null, null, null, null, true); assertEquals(HttpGenerator.Result.NEED_INFO, result); assertEquals(HttpGenerator.State.START, gen.getState()); MetaData.Response info = new MetaData.Response(HttpVersion.HTTP_1_1, 101, null, new HttpFields(), -1); info.getFields().add("Upgrade", "WebSocket"); info.getFields().add("Connection", "Upgrade"); info.getFields().add("Sec-WebSocket-Accept", "123456789=="); result = gen.generateResponse(info, header, null, null, true); assertEquals(HttpGenerator.Result.FLUSH, result); assertEquals(HttpGenerator.State.COMPLETING, gen.getState()); String head = BufferUtils.toString(header); BufferUtils.clear(header); result = gen.generateResponse(info, null, null, null, false); assertEquals(HttpGenerator.Result.DONE, result); assertEquals(HttpGenerator.State.END, gen.getState()); assertEquals(0, gen.getContentPrepared()); assertThat(head, startsWith("HTTP/1.1 101 Switching Protocols")); assertThat(head, containsString("Upgrade: WebSocket\r\n")); assertThat(head, containsString("Connection: Upgrade\r\n")); }
@Test public void testResponseNoContent() throws Exception { ByteBuffer header = BufferUtils.allocate(8096); HttpGenerator gen = new HttpGenerator(); HttpGenerator.Result result = gen.generateResponse(null, null, null, null, true); assertEquals(HttpGenerator.Result.NEED_INFO, result); assertEquals(HttpGenerator.State.START, gen.getState()); MetaData.Response info = new MetaData.Response(HttpVersion.HTTP_1_1, 200, null, new HttpFields(), -1); info.getFields().add("Last-Modified", DateGenerator.__01Jan1970); result = gen.generateResponse(info, null, null, null, true); assertEquals(HttpGenerator.Result.NEED_HEADER, result); result = gen.generateResponse(info, header, null, null, true); assertEquals(HttpGenerator.Result.FLUSH, result); assertEquals(HttpGenerator.State.COMPLETING, gen.getState()); String head = BufferUtils.toString(header); BufferUtils.clear(header); result = gen.generateResponse(null, null, null, null, false); assertEquals(HttpGenerator.Result.DONE, result); assertEquals(HttpGenerator.State.END, gen.getState()); assertEquals(0, gen.getContentPrepared()); assertThat(head, containsString("HTTP/1.1 200 OK")); assertThat(head, containsString("Last-Modified: Thu, 01 Jan 1970 00:00:00 GMT")); assertThat(head, containsString("Content-Length: 0")); }
@Test public void testSendServerXPoweredBy() throws Exception { ByteBuffer header = BufferUtils.allocate(8096); MetaData.Response info = new MetaData.Response(HttpVersion.HTTP_1_1, 200, null, new HttpFields(), -1); HttpFields fields = new HttpFields(); fields.add(HttpHeader.SERVER, "SomeServer"); fields.add(HttpHeader.X_POWERED_BY, "SomePower"); MetaData.Response infoF = new MetaData.Response(HttpVersion.HTTP_1_1, 200, null, fields, -1); String head; HttpGenerator gen = new HttpGenerator(true, true); gen.generateResponse(info, header, null, null, true); head = BufferUtils.toString(header); BufferUtils.clear(header); assertThat(head, containsString("HTTP/1.1 200 OK")); assertThat(head, containsString("Server: Firefly 3.0")); assertThat(head, containsString("X-Powered-By: Firefly 3.0")); gen.reset(); gen.generateResponse(infoF, header, null, null, true); head = BufferUtils.toString(header); BufferUtils.clear(header); assertThat(head, containsString("HTTP/1.1 200 OK")); assertThat(head, not(containsString("Server: Firefly 3.0"))); assertThat(head, containsString("Server: SomeServer")); assertThat(head, containsString("X-Powered-By: Firefly 3.0")); assertThat(head, containsString("X-Powered-By: SomePower")); gen.reset(); gen = new HttpGenerator(false, false); gen.generateResponse(info, header, null, null, true); head = BufferUtils.toString(header); BufferUtils.clear(header); assertThat(head, containsString("HTTP/1.1 200 OK")); assertThat(head, not(containsString("Server: Firefly 3.0"))); assertThat(head, not(containsString("X-Powered-By: Firefly 3.0"))); gen.reset(); gen.generateResponse(infoF, header, null, null, true); head = BufferUtils.toString(header); BufferUtils.clear(header); assertThat(head, containsString("HTTP/1.1 200 OK")); assertThat(head, not(containsString("Server: Firefly 3.0"))); assertThat(head, containsString("Server: SomeServer")); assertThat(head, not(containsString("X-Powered-By: Firefly 3.0"))); assertThat(head, containsString("X-Powered-By: SomePower")); gen.reset(); }
@Test public void testComplexChars() throws Exception { ByteBuffer header = BufferUtils.allocate(8096); ByteBuffer content = BufferUtils.toBuffer("0123456789"); HttpGenerator gen = new HttpGenerator(); HttpGenerator.Result result = gen.generateResponse(null, null, null, content, true); assertEquals(HttpGenerator.Result.NEED_INFO, result); assertEquals(HttpGenerator.State.START, gen.getState()); MetaData.Response info = new MetaData.Response(HttpVersion.HTTP_1_1, 200, null, new HttpFields(), 10); info.getFields().add("Content-Type", "test/data;\r\nextra=value"); info.getFields().add("Last-Modified", DateGenerator.__01Jan1970); result = gen.generateResponse(info, null, null, content, true); assertEquals(HttpGenerator.Result.NEED_HEADER, result); result = gen.generateResponse(info, header, null, content, true); assertEquals(HttpGenerator.Result.FLUSH, result); assertEquals(HttpGenerator.State.COMPLETING, gen.getState()); String response = BufferUtils.toString(header); BufferUtils.clear(header); response += BufferUtils.toString(content); BufferUtils.clear(content); result = gen.generateResponse(null, null, null, content, false); assertEquals(HttpGenerator.Result.DONE, result); assertEquals(HttpGenerator.State.END, gen.getState()); assertEquals(10, gen.getContentPrepared()); assertThat(response, containsString("HTTP/1.1 200 OK")); assertThat(response, containsString("Last-Modified: Thu, 01 Jan 1970 00:00:00 GMT")); assertThat(response, containsString("Content-Type: test/data; extra=value")); assertThat(response, containsString("Content-Length: 10")); assertThat(response, containsString("\r\n0123456789")); }
protected boolean parseContent(ByteBuffer buffer) { int remaining = buffer.remaining(); if (remaining == 0 && _state == State.CONTENT) { long content = _contentLength - _contentPosition; if (content == 0) { setState(State.END); return _handler.messageComplete(); } } // Handle _content byte ch; while (_state.ordinal() < State.END.ordinal() && remaining > 0) { switch (_state) { case EOF_CONTENT: _contentChunk = buffer.asReadOnlyBuffer(); _contentPosition += remaining; buffer.position(buffer.position() + remaining); if (_handler.content(_contentChunk)) return true; break; case CONTENT: { long content = _contentLength - _contentPosition; if (content == 0) { setState(State.END); return _handler.messageComplete(); } else { _contentChunk = buffer.asReadOnlyBuffer(); // limit content by expected size if (remaining > content) { // We can cast remaining to an int as we know that it is // smaller than // or equal to length which is already an int. _contentChunk.limit(_contentChunk.position() + (int) content); } _contentPosition += _contentChunk.remaining(); buffer.position(buffer.position() + _contentChunk.remaining()); if (_handler.content(_contentChunk)) return true; if (_contentPosition == _contentLength) { setState(State.END); return _handler.messageComplete(); } } break; } case CHUNKED_CONTENT: { ch = next(buffer); if (ch > HttpTokens.SPACE) { _chunkLength = TypeUtils.convertHexDigit(ch); _chunkPosition = 0; setState(State.CHUNK_SIZE); } break; } case CHUNK_SIZE: { ch = next(buffer); if (ch == 0) break; if (ch == HttpTokens.LINE_FEED) { if (_chunkLength == 0) setState(State.CHUNK_END); else setState(State.CHUNK); } else if (ch <= HttpTokens.SPACE || ch == HttpTokens.SEMI_COLON) setState(State.CHUNK_PARAMS); else _chunkLength = _chunkLength * 16 + TypeUtils.convertHexDigit(ch); break; } case CHUNK_PARAMS: { ch = next(buffer); if (ch == HttpTokens.LINE_FEED) { if (_chunkLength == 0) setState(State.CHUNK_END); else setState(State.CHUNK); } break; } case CHUNK: { int chunk = _chunkLength - _chunkPosition; if (chunk == 0) { setState(State.CHUNKED_CONTENT); } else { _contentChunk = buffer.asReadOnlyBuffer(); if (remaining > chunk) _contentChunk.limit(_contentChunk.position() + chunk); chunk = _contentChunk.remaining(); _contentPosition += chunk; _chunkPosition += chunk; buffer.position(buffer.position() + chunk); if (_handler.content(_contentChunk)) return true; } break; } case CHUNK_END: { // TODO handle chunk trailer ch = next(buffer); if (ch == 0) break; if (ch == HttpTokens.LINE_FEED) { setState(State.END); return _handler.messageComplete(); } throw new IllegalCharacterException(_state, ch, buffer); } case CLOSED: { BufferUtils.clear(buffer); return false; } default: break; } remaining = buffer.remaining(); } return false; }
/** * Parse until next Event. * * @param buffer the buffer to parse * @return True if an {@link RequestHandler} method was called and it returned true; */ public boolean parseNext(ByteBuffer buffer) { if (DEBUG) log.debug("parseNext s={} {}", _state, BufferUtils.toDetailString(buffer)); try { // Start a request/response if (_state == State.START) { _version = null; _method = null; _methodString = null; _endOfContent = EndOfContent.UNKNOWN_CONTENT; _header = null; if (quickStart(buffer)) return true; } // Request/response line if (_state.ordinal() >= State.START.ordinal() && _state.ordinal() < State.HEADER.ordinal()) { if (parseLine(buffer)) return true; } // parse headers if (_state.ordinal() >= State.HEADER.ordinal() && _state.ordinal() < State.CONTENT.ordinal()) { if (parseHeaders(buffer)) return true; } // parse content if (_state.ordinal() >= State.CONTENT.ordinal() && _state.ordinal() < State.END.ordinal()) { // Handle HEAD response if (_responseStatus > 0 && _headResponse) { setState(State.END); return _handler.messageComplete(); } else { if (parseContent(buffer)) return true; } } // handle end states if (_state == State.END) { // eat white space while (buffer.remaining() > 0 && buffer.get(buffer.position()) <= HttpTokens.SPACE) buffer.get(); } else if (_state == State.CLOSE) { // Seeking EOF if (BufferUtils.hasContent(buffer)) { // Just ignore data when closed _headerBytes += buffer.remaining(); BufferUtils.clear(buffer); if (_maxHeaderBytes > 0 && _headerBytes > _maxHeaderBytes) { // Don't want to waste time reading data of a closed // request throw new IllegalStateException("too much data seeking EOF"); } } } else if (_state == State.CLOSED) { BufferUtils.clear(buffer); } // Handle EOF if (_eof && !buffer.hasRemaining()) { switch (_state) { case CLOSED: break; case START: setState(State.CLOSED); _handler.earlyEOF(); break; case END: case CLOSE: setState(State.CLOSED); break; case EOF_CONTENT: setState(State.CLOSED); return _handler.messageComplete(); case CONTENT: case CHUNKED_CONTENT: case CHUNK_SIZE: case CHUNK_PARAMS: case CHUNK: setState(State.CLOSED); _handler.earlyEOF(); break; default: if (DEBUG) log.debug("{} EOF in {}", this, _state); setState(State.CLOSED); _handler.badMessage(400, null); break; } } } catch (BadMessageException e) { BufferUtils.clear(buffer); Throwable cause = e.getCause(); boolean stack = log.isDebugEnable() || (!(cause instanceof NumberFormatException) && (cause instanceof RuntimeException || cause instanceof Error)); if (stack) log.warn( "bad HTTP parsed: " + e._code + (e.getReason() != null ? " " + e.getReason() : "") + " for " + _handler, e); else log.warn( "bad HTTP parsed: " + e._code + (e.getReason() != null ? " " + e.getReason() : "") + " for " + _handler); setState(State.CLOSE); _handler.badMessage(e.getCode(), e.getReason()); } catch (NumberFormatException | IllegalStateException e) { BufferUtils.clear(buffer); log.warn("parse exception: {} in {} for {}", e.toString(), _state, _handler); if (DEBUG) log.debug("parse exception", e); switch (_state) { case CLOSED: break; case CLOSE: _handler.earlyEOF(); break; default: setState(State.CLOSE); _handler.badMessage(400, null); } } catch (Exception | Error e) { BufferUtils.clear(buffer); log.warn("parse exception: " + e.toString() + " for " + _handler, e); switch (_state) { case CLOSED: break; case CLOSE: _handler.earlyEOF(); break; default: setState(State.CLOSE); _handler.badMessage(400, null); } } return false; }