/* * Copyright (C) 2015 SoftIndex LLC. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.datakernel.http; import io.datakernel.async.IgnoreCompletionCallback; import io.datakernel.async.ResultCallback; import io.datakernel.async.ResultCallbackFuture; import io.datakernel.bytebuf.ByteBuf; import io.datakernel.bytebuf.ByteBufPool; import io.datakernel.eventloop.Eventloop; import org.junit.Before; import org.junit.Test; import java.io.DataInputStream; import java.io.IOException; import java.io.InputStream; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket; import static io.datakernel.bytebuf.ByteBufPool.getPoolItemsString; import static io.datakernel.bytebuf.ByteBufStrings.*; import static io.datakernel.eventloop.FatalErrorHandlers.rethrowOnAnyError; import static io.datakernel.http.TestUtils.readFully; import static io.datakernel.http.TestUtils.toByteArray; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; public class HttpTolerantApplicationTest { @Before public void before() { ByteBufPool.clear(); ByteBufPool.setSizes(0, Integer.MAX_VALUE); } public static AsyncHttpServer asyncHttpServer(final Eventloop primaryEventloop, int port) { AsyncServlet servlet = new AsyncServlet() { @Override public void serve(final HttpRequest request, final ResultCallback<HttpResponse> callback) { primaryEventloop.post(new Runnable() { @Override public void run() { HttpResponse content = HttpResponse.ok200().withBody(encodeAscii(request.getUrl().getPathAndQuery())); callback.setResult(content); } }); } }; return AsyncHttpServer.create(primaryEventloop, servlet).withListenPort(port); } private static void write(Socket socket, String string) throws IOException { ByteBuf buf = ByteBuf.wrapForReading(encodeAscii(string)); socket.getOutputStream().write(buf.array(), buf.readPosition(), buf.readRemaining()); } private static void readAndAssert(InputStream is, String expected) throws IOException { byte[] bytes = new byte[expected.length()]; readFully(is, bytes); assertEquals(expected, decodeAscii(bytes)); } @Test public void testTolerantServer() throws Exception { Eventloop eventloop = Eventloop.create().withFatalErrorHandler(rethrowOnAnyError()); int port = (int) (System.currentTimeMillis() % 1000 + 40000); AsyncHttpServer server = asyncHttpServer(eventloop, port); server.listen(); Thread thread = new Thread(eventloop); thread.start(); Socket socket = new Socket(); socket.connect(new InetSocketAddress(port)); write(socket, "GET /abc HTTP/1.1\nHost: \tlocalhost\n\n"); readAndAssert(socket.getInputStream(), "HTTP/1.1 200 OK\r\nConnection: keep-alive\r\nContent-Length: 4\r\n\r\n/abc"); write(socket, "GET /abc HTTP/1.0\nHost: \tlocalhost \t \nConnection: keep-alive\n\n"); readAndAssert(socket.getInputStream(), "HTTP/1.1 200 OK\r\nConnection: keep-alive\r\nContent-Length: 4\r\n\r\n/abc"); write(socket, "GET /abc HTTP1.1\nHost: \tlocalhost \t \n\n"); readAndAssert(socket.getInputStream(), "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 4\r\n\r\n/abc"); assertTrue(toByteArray(socket.getInputStream()).length == 0); socket.close(); server.closeFuture().get(); thread.join(); assertEquals(getPoolItemsString(), ByteBufPool.getCreatedItems(), ByteBufPool.getPoolItems()); } private static ServerSocket socketServer(int port, final String testResponse) throws IOException { final ServerSocket listener = new ServerSocket(port); new Thread(new Runnable() { @Override public void run() { while (!Thread.currentThread().isInterrupted()) { try (Socket socket = listener.accept()) { System.out.println("accept: " + socket); DataInputStream in = new DataInputStream(socket.getInputStream()); readHttpMessage(in); System.out.println("write: " + socket); write(socket, testResponse); } catch (IOException ignored) { } } } public void readHttpMessage(DataInputStream in) throws IOException { //noinspection StatementWithEmptyBody int eofCounter = 0; while (eofCounter < 2) { int i = in.read(); if (i == -1) break; if (i == LF) { eofCounter++; } else { if (i != CR) eofCounter = 0; } } } }).start(); return listener; } @Test public void testTolerantClient() throws Exception { int port = (int) (System.currentTimeMillis() % 1000 + 40000); final ResultCallbackFuture<String> resultObserver = ResultCallbackFuture.create(); Eventloop eventloop = Eventloop.create().withFatalErrorHandler(rethrowOnAnyError()); try (ServerSocket ignored = socketServer(port, "HTTP/1.1 200 OK\nContent-Type: \t text/html; charset=UTF-8\nContent-Length: 4\n\n/abc")) { final AsyncHttpClient httpClient = AsyncHttpClient.create(eventloop); httpClient.send(HttpRequest.get("http://127.0.0.1:" + port), new ResultCallback<HttpResponse>() { @Override protected void onResult(HttpResponse response) { resultObserver.setResult(response.getHeader(HttpHeaders.CONTENT_TYPE)); httpClient.stop(IgnoreCompletionCallback.create()); } @Override protected void onException(Exception exception) { resultObserver.setException(exception); httpClient.stop(IgnoreCompletionCallback.create()); } }); eventloop.run(); } assertEquals("text/html; charset=UTF-8", resultObserver.get()); assertEquals(getPoolItemsString(), ByteBufPool.getCreatedItems(), ByteBufPool.getPoolItems()); } }