/* * Copyright (c) 2016 AsyncHttpClient Project. All rights reserved. * * This program is licensed to you under the Apache License Version 2.0, * and you may not use this file except in compliance with the Apache License Version 2.0. * You may obtain a copy of the Apache License Version 2.0 at * http://www.apache.org/licenses/LICENSE-2.0. * * Unless required by applicable law or agreed to in writing, * software distributed under the Apache License Version 2.0 is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. */ package org.asynchttpclient.testserver; import static io.netty.handler.codec.http.HttpHeaderNames.LOCATION; import static org.asynchttpclient.test.TestUtils.*; import java.io.Closeable; import java.io.IOException; import java.util.Enumeration; import java.util.Map.Entry; import java.util.concurrent.ConcurrentLinkedQueue; import javax.servlet.ServletException; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.handler.AbstractHandler; public class HttpServer implements Closeable { private int httpPort; private int httpsPort; private Server server; private final ConcurrentLinkedQueue<Handler> handlers = new ConcurrentLinkedQueue<>(); @FunctionalInterface public interface HttpServletResponseConsumer { void apply(HttpServletResponse response) throws IOException, ServletException; } public HttpServer() { } public HttpServer(int httpPort, int httpsPort) { this.httpPort = httpPort; this.httpsPort = httpsPort; } public void start() throws Exception { server = new Server(); ServerConnector httpConnector = addHttpConnector(server); if (httpPort != 0) { httpConnector.setPort(httpPort); } server.setHandler(new QueueHandler()); ServerConnector httpsConnector = addHttpsConnector(server); if (httpsPort != 0) { httpsConnector.setPort(httpsPort); } server.start(); httpPort = httpConnector.getLocalPort(); httpsPort = httpsConnector.getLocalPort(); } public void enqueue(Handler handler) { handlers.offer(handler); } public void enqueueOk() { enqueueResponse(response -> response.setStatus(200)); } public void enqueueResponse(HttpServletResponseConsumer c) { handlers.offer(new ConsumerHandler(c)); } public void enqueueEcho() { handlers.offer(new EchoHandler()); } public void enqueueRedirect(int status, String location) { enqueueResponse(response -> { response.setStatus(status); response.setHeader(LOCATION.toString(), location); }); } public int getHttpPort() { return httpPort; } public int getsHttpPort() { return httpsPort; } public String getHttpUrl() { return "http://localhost:" + httpPort; } public String getHttpsUrl() { return "https://localhost:" + httpsPort; } public void reset() { handlers.clear(); } @Override public void close() throws IOException { if (server == null) { try { server.stop(); } catch (Exception e) { throw new IOException(e); } } } private class QueueHandler extends AbstractHandler { @Override public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { Handler handler = HttpServer.this.handlers.poll(); if (handler == null) { response.sendError(500, "No handler enqueued"); response.getOutputStream().flush(); response.getOutputStream().close(); } else { handler.handle(target, baseRequest, request, response); } } } public static abstract class AutoFlushHandler extends AbstractHandler { private final boolean closeAfterResponse; public AutoFlushHandler() { this(false); } public AutoFlushHandler(boolean closeAfterResponse) { this.closeAfterResponse = closeAfterResponse; } @Override public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { handle0(target, baseRequest, request, response); response.getOutputStream().flush(); if (closeAfterResponse) { response.getOutputStream().close(); } } protected abstract void handle0(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException; } private static class ConsumerHandler extends AutoFlushHandler { private final HttpServletResponseConsumer c; public ConsumerHandler(HttpServletResponseConsumer c) { this(c, false); } public ConsumerHandler(HttpServletResponseConsumer c, boolean closeAfterResponse) { super(closeAfterResponse); this.c = c; } @Override protected void handle0(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { c.apply(response); } } public static class EchoHandler extends AutoFlushHandler { @Override protected void handle0(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { String delay = request.getHeader("X-Delay"); if (delay != null) { try { Thread.sleep(Long.parseLong(delay)); } catch (NumberFormatException | InterruptedException e1) { throw new ServletException(e1); } } response.setStatus(200); if (request.getMethod().equalsIgnoreCase("OPTIONS")) { response.addHeader("Allow", "GET,HEAD,POST,OPTIONS,TRACE"); } response.setContentType(request.getHeader("X-IsoCharset") != null ? TEXT_HTML_CONTENT_TYPE_WITH_ISO_8859_1_CHARSET : TEXT_HTML_CONTENT_TYPE_WITH_UTF_8_CHARSET); response.addHeader("X-ClientPort", String.valueOf(request.getRemotePort())); String pathInfo = request.getPathInfo(); if (pathInfo != null) response.addHeader("X-PathInfo", pathInfo); String queryString = request.getQueryString(); if (queryString != null) response.addHeader("X-QueryString", queryString); Enumeration<String> headerNames = request.getHeaderNames(); while (headerNames.hasMoreElements()) { String headerName = headerNames.nextElement(); response.addHeader("X-" + headerName, request.getHeader(headerName)); } for (Entry<String, String[]> e : baseRequest.getParameterMap().entrySet()) { response.addHeader("X-" + e.getKey(), e.getValue()[0]); } Cookie[] cs = request.getCookies(); if (cs != null) { for (Cookie c : cs) { response.addCookie(c); } } Enumeration<String> parameterNames = request.getParameterNames(); StringBuilder requestBody = new StringBuilder(); while (parameterNames.hasMoreElements()) { String param = parameterNames.nextElement(); response.addHeader("X-" + param, request.getParameter(param)); requestBody.append(param); requestBody.append("_"); } if (requestBody.length() > 0) { response.getOutputStream().write(requestBody.toString().getBytes()); } int size = 16384; if (request.getContentLength() > 0) { size = request.getContentLength(); } if (size > 0) { byte[] bytes = new byte[size]; int read = 0; while (read > -1) { read = request.getInputStream().read(bytes); if (read > 0) { response.getOutputStream().write(bytes, 0, read); } } } } } }