package org.webpieces.httpfrontend.api; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutionException; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.webpieces.data.api.BufferCreationPool; import org.webpieces.data.api.DataWrapper; import org.webpieces.data.api.DataWrapperGenerator; import org.webpieces.data.api.DataWrapperGeneratorFactory; import org.webpieces.httpcommon.Requests; import org.webpieces.httpcommon.Responses; import org.webpieces.httpparser.api.HttpParser; import org.webpieces.httpparser.api.HttpParserFactory; import org.webpieces.httpparser.api.Memento; import org.webpieces.httpparser.api.common.Header; import org.webpieces.httpparser.api.common.KnownHeaderName; import org.webpieces.httpparser.api.dto.HttpPayload; import org.webpieces.httpparser.api.dto.HttpRequest; import org.webpieces.httpparser.api.dto.HttpResponse; import org.webpieces.httpparser.api.dto.KnownHttpMethod; import org.webpieces.httpparser.api.dto.KnownStatusCode; import org.webpieces.nio.api.handlers.DataListener; import com.twitter.hpack.Decoder; import com.webpieces.hpack.api.HpackParser; import com.webpieces.hpack.api.HpackParserFactory; import com.webpieces.hpack.api.UnmarshalState; import com.webpieces.hpack.api.dto.Http2Headers; import com.webpieces.hpack.api.dto.Http2Push; import com.webpieces.http2parser.api.dto.DataFrame; import com.webpieces.http2parser.api.dto.SettingsFrame; import com.webpieces.http2parser.api.dto.lib.Http2Msg; import com.webpieces.http2parser.api.dto.lib.Http2Setting; import com.webpieces.http2parser.api.dto.lib.SettingsParameter; public class TestRequestResponse { private HttpParser httpParser; private HpackParser http2Parser; private DataWrapperGenerator dataGen = DataWrapperGeneratorFactory.createDataWrapperGenerator(); private SettingsFrame settingsFrame = new SettingsFrame(); private Decoder decoder; private List<Http2Setting> settings = new ArrayList<>(); private static String blahblah = "blah blah blah"; @Before public void setup() { BufferCreationPool pool = new BufferCreationPool(); httpParser = HttpParserFactory.createParser(pool); http2Parser = HpackParserFactory.createParser(pool, true); decoder = new Decoder(4096, 4096); settings.add(new Http2Setting(SettingsParameter.SETTINGS_MAX_FRAME_SIZE, 16384L)); settingsFrame.setSettings(settings); } private ByteBuffer processRequestWithRequestListener(HttpRequest request, RequestListenerForTest requestListenerForTest) throws InterruptedException, ExecutionException { MockServer mockServer = new MockServer(80, false, requestListenerForTest); DataListener dataListener = mockServer.getDataListener(); ByteBuffer buffer = httpParser.marshalToByteBuffer(request); dataListener.incomingData(mockServer.getMockTcpChannel(), buffer); // TODO: fix this to wait until we're done, not just sleep, which is fragile. Thread.sleep(1000); return mockServer.getMockTcpChannel().getWriteLog(); } private void simpleHttp11RequestWithListener(RequestListenerForTest listener) throws InterruptedException, ExecutionException { HttpRequest request = Requests.createRequest(KnownHttpMethod.GET, "/"); ByteBuffer bytesWritten = processRequestWithRequestListener(request, listener); Assert.assertTrue(new String(bytesWritten.array()).contains("HTTP/1.1 200 OK\r\n" + "Content-Length: 0\r\n")); } private void upgradeHttp2RequestWithListener(RequestListenerForTest listener) throws InterruptedException, ExecutionException { HttpRequest request = Requests.createRequest(KnownHttpMethod.GET, "/"); request.addHeader(new Header(KnownHeaderName.UPGRADE, "h2c")); request.addHeader(new Header(KnownHeaderName.CONNECTION, "Upgrade, HTTP2-Settings")); String base64 = http2Parser.marshalSettingsPayload(settingsFrame.getSettings()); request.addHeader(new Header(KnownHeaderName.HTTP2_SETTINGS, base64 + " ")); ByteBuffer bytesWritten = processRequestWithRequestListener(request, listener); Memento memento = httpParser.prepareToParse(); httpParser.parse(memento, dataGen.wrapByteBuffer(bytesWritten)); List<HttpPayload> parsedMessages = memento.getParsedMessages(); DataWrapper leftOverData = memento.getLeftOverData(); // Check that we got an approved upgrade Assert.assertEquals(parsedMessages.size(), 1); Assert.assertTrue(HttpResponse.class.isInstance(parsedMessages.get(0))); HttpResponse responseGot = (HttpResponse) parsedMessages.get(0); Assert.assertEquals(responseGot.getStatusLine().getStatus().getKnownStatus(), KnownStatusCode.HTTP_101_SWITCHING_PROTOCOLS); UnmarshalState result = http2Parser.prepareToUnmarshal(4096, 4096, Integer.MAX_VALUE); // Check that we got a settings frame, a headers frame, and a data frame result = http2Parser.unmarshal(result, leftOverData); List<Http2Msg> frames = result.getParsedFrames(); Assert.assertEquals(3, frames.size()); Assert.assertTrue(SettingsFrame.class.isInstance(frames.get(0))); Assert.assertTrue(Http2Headers.class.isInstance(frames.get(1))); Assert.assertTrue(DataFrame.class.isInstance(frames.get(2))); } private void http2ResponseWithData(RequestListenerForTest listener) throws InterruptedException, ExecutionException { HttpRequest request = Requests.createRequest(KnownHttpMethod.GET, "/"); request.addHeader(new Header(KnownHeaderName.UPGRADE, "h2c")); request.addHeader(new Header(KnownHeaderName.CONNECTION, "Upgrade, HTTP2-Settings")); String base64 = http2Parser.marshalSettingsPayload(settingsFrame.getSettings()); request.addHeader(new Header(KnownHeaderName.HTTP2_SETTINGS, base64 + " ")); ByteBuffer bytesWritten = processRequestWithRequestListener(request, listener); Memento memento = httpParser.prepareToParse(); httpParser.parse(memento, dataGen.wrapByteBuffer(bytesWritten)); List<HttpPayload> parsedMessages = memento.getParsedMessages(); DataWrapper leftOverData = memento.getLeftOverData(); // Check that we got an approved upgrade Assert.assertEquals(parsedMessages.size(), 1); Assert.assertTrue(HttpResponse.class.isInstance(parsedMessages.get(0))); HttpResponse responseGot = (HttpResponse) parsedMessages.get(0); Assert.assertEquals(responseGot.getStatusLine().getStatus().getKnownStatus(), KnownStatusCode.HTTP_101_SWITCHING_PROTOCOLS); UnmarshalState result = http2Parser.prepareToUnmarshal(4096, 4096, Integer.MAX_VALUE); // Check that we got a settings frame, a headers frame, and a data frame result = http2Parser.unmarshal(result, leftOverData); List<Http2Msg> frames = result.getParsedFrames(); Assert.assertEquals(3, frames.size()); Assert.assertTrue(SettingsFrame.class.isInstance(frames.get(0))); Assert.assertTrue(Http2Headers.class.isInstance(frames.get(1))); Assert.assertTrue(DataFrame.class.isInstance(frames.get(2))); Assert.assertArrayEquals(((DataFrame) frames.get(2)).getData().createByteArray(), blahblah.getBytes()); } private void http2WithPushPromise(RequestListenerForTest listener) throws InterruptedException, ExecutionException { HttpRequest request = Requests.createRequest(KnownHttpMethod.GET, "/"); request.addHeader(new Header(KnownHeaderName.UPGRADE, "h2c")); request.addHeader(new Header(KnownHeaderName.CONNECTION, "Upgrade, HTTP2-Settings")); String base64 = http2Parser.marshalSettingsPayload(settingsFrame.getSettings()); request.addHeader(new Header(KnownHeaderName.HTTP2_SETTINGS, base64 + " ")); ByteBuffer bytesWritten = processRequestWithRequestListener(request, listener); Memento memento = httpParser.prepareToParse(); httpParser.parse(memento, dataGen.wrapByteBuffer(bytesWritten)); List<HttpPayload> parsedMessages = memento.getParsedMessages(); DataWrapper leftOverData = memento.getLeftOverData(); // Check that we got an approved upgrade Assert.assertEquals(parsedMessages.size(), 1); Assert.assertTrue(HttpResponse.class.isInstance(parsedMessages.get(0))); HttpResponse responseGot = (HttpResponse) parsedMessages.get(0); Assert.assertEquals(responseGot.getStatusLine().getStatus().getKnownStatus(), KnownStatusCode.HTTP_101_SWITCHING_PROTOCOLS); UnmarshalState result = http2Parser.prepareToUnmarshal(4096, 4096, Integer.MAX_VALUE); // Check that we got a settings frame, a headers frame, and a data frame, then a push promise frame // then a headers then a data frame result = http2Parser.unmarshal(result, leftOverData); List<Http2Msg> frames = result.getParsedFrames(); Assert.assertEquals(6, frames.size()); Assert.assertTrue(SettingsFrame.class.isInstance(frames.get(0))); Assert.assertTrue(Http2Headers.class.isInstance(frames.get(1))); Assert.assertTrue(DataFrame.class.isInstance(frames.get(2))); Assert.assertTrue(Http2Push.class.isInstance(frames.get(3))); Assert.assertTrue(Http2Headers.class.isInstance(frames.get(4))); Assert.assertTrue(DataFrame.class.isInstance(frames.get(5))); } @Test public void testSimpleHttp11Request() throws InterruptedException, ExecutionException { HttpResponse response = Responses.createResponse(KnownStatusCode.HTTP_200_OK, dataGen.emptyWrapper()); RequestListenerForTest listenerChunked = new RequestListenerForTestWithResponses(response, true); simpleHttp11RequestWithListener(listenerChunked); RequestListenerForTest listenerNotChunked = new RequestListenerForTestWithResponses(response, false); simpleHttp11RequestWithListener(listenerNotChunked); } @Test public void testUpgradeHttp2Request() throws InterruptedException, ExecutionException { HttpResponse response = Responses.createResponse(KnownStatusCode.HTTP_200_OK, dataGen.emptyWrapper()); RequestListenerForTest listenerChunked = new RequestListenerForTestWithResponses(response, true); upgradeHttp2RequestWithListener(listenerChunked); RequestListenerForTest listenerNotChunked = new RequestListenerForTestWithResponses(response, false); upgradeHttp2RequestWithListener(listenerNotChunked); } @Test public void testHttp2ResponseWithData() throws InterruptedException, ExecutionException { HttpResponse response = Responses.createResponse(KnownStatusCode.HTTP_200_OK, dataGen.wrapByteArray(blahblah.getBytes())); RequestListenerForTest listenerChunked = new RequestListenerForTestWithResponses(response, true); http2ResponseWithData(listenerChunked); RequestListenerForTest listenerNotChunked = new RequestListenerForTestWithResponses(response, false); http2ResponseWithData(listenerNotChunked); } @Test public void testHttp2WithPushPromiseResponses() throws InterruptedException, ExecutionException { HttpResponse response = Responses.createResponse(KnownStatusCode.HTTP_200_OK, dataGen.wrapByteArray(blahblah.getBytes())); List<HttpResponse> responses = new ArrayList<>(); responses.add(response); responses.add(response); RequestListenerForTest listenerChunked = new RequestListenerForTestWithResponses(responses, true); http2WithPushPromise(listenerChunked); RequestListenerForTest listenerNotChunked = new RequestListenerForTestWithResponses(responses, false); http2WithPushPromise(listenerNotChunked); } }