package io.vertx.test.core; import java.net.UnknownHostException; import java.util.Base64; import io.vertx.core.Handler; import io.vertx.core.MultiMap; import io.vertx.core.Vertx; import io.vertx.core.http.HttpClient; import io.vertx.core.http.HttpClientRequest; import io.vertx.core.http.HttpMethod; import io.vertx.core.http.HttpServer; import io.vertx.core.http.HttpServerOptions; import io.vertx.core.logging.Logger; import io.vertx.core.logging.LoggerFactory; import io.vertx.core.net.NetClient; import io.vertx.core.net.NetClientOptions; import io.vertx.core.net.NetSocket; import io.vertx.core.streams.Pump; /** * Http Proxy for testing * * <p> * A simple Http proxy for testing http proxy functionality. HTTP server running on localhost allowing CONNECT and GET * requests only. * CONNECT is basically a socket forwarding protocol allowing to use the proxy server to connect to the internet, * e.g. CONNECT www.google.com:443 HTTP/1.1. * GET accepts an absolute url and gets the url from the origin server, e.g. GET http://www.google.de/ HTTP/1.1. * <p> * Usually the server will be started in @Before and stopped in @After for a unit test using HttpClient with the * setProxyXXX methods. * <p> * The proxy is not useful for anything except testing, since it lacks most security checks like client acls, however in a * test scenario it will bind to localhost only. * <p> * @author <a href="http://oss.lehmann.cx/">Alexander Lehmann</a> */ public class HttpProxy extends TestProxyBase { private static final int PORT = 13128; private static final Logger log = LoggerFactory.getLogger(HttpProxy.class); private HttpServer server; private int error = 0; private MultiMap lastRequestHeaders = null; private HttpMethod lastMethod; public HttpProxy(String username) { super(username); } /** * Start the server. * * @param vertx * Vertx instance to use for creating the server and client * @param finishedHandler * will be called when the server has started */ @Override public void start(Vertx vertx, Handler<Void> finishedHandler) { HttpServerOptions options = new HttpServerOptions(); options.setHost("localhost").setPort(PORT); server = vertx.createHttpServer(options); server.requestHandler(request -> { HttpMethod method = request.method(); String uri = request.uri(); if (username != null) { String auth = request.getHeader("Proxy-Authorization"); String expected = "Basic " + Base64.getEncoder().encodeToString((username + ":" + username).getBytes()); if (auth == null || !auth.equals(expected)) { request.response().setStatusCode(407).end("proxy authentication failed"); return; } } lastRequestHeaders = MultiMap.caseInsensitiveMultiMap().addAll(request.headers()); if (error != 0) { request.response().setStatusCode(error).end("proxy request failed"); } else if (method == HttpMethod.CONNECT) { if (!uri.contains(":")) { request.response().setStatusCode(403).end("invalid request"); } else { lastUri = uri; lastMethod = HttpMethod.CONNECT; if (forceUri != null) { uri = forceUri; } String[] split = uri.split(":"); String host = split[0]; int port; try { port = Integer.parseInt(split[1]); } catch (NumberFormatException ex) { port = 443; } // deny ports not considered safe to connect // this will deny access to e.g. smtp port 25 to avoid spammers if (port == 8080 || port < 1024 && port != 443) { request.response().setStatusCode(403).end("access to port denied"); return; } NetSocket serverSocket = request.netSocket(); NetClientOptions netOptions = new NetClientOptions(); NetClient netClient = vertx.createNetClient(netOptions); netClient.connect(port, host, result -> { if (result.succeeded()) { NetSocket clientSocket = result.result(); serverSocket.write("HTTP/1.0 200 Connection established\n\n"); serverSocket.closeHandler(v -> clientSocket.close()); clientSocket.closeHandler(v -> serverSocket.close()); Pump.pump(serverSocket, clientSocket).start(); Pump.pump(clientSocket, serverSocket).start(); } else { request.response().setStatusCode(403).end("request failed"); } }); } } else if (method == HttpMethod.GET) { lastUri = uri; lastMethod = HttpMethod.GET; if (forceUri != null) { uri = forceUri; } HttpClient client = vertx.createHttpClient(); HttpClientRequest clientRequest = client.getAbs(uri, resp -> { for (String name : resp.headers().names()) { request.response().putHeader(name, resp.headers().getAll(name)); } resp.bodyHandler(body -> { request.response().end(body); }); }); for (String name : request.headers().names()) { if (!name.equals("Proxy-Authorization")) { clientRequest.putHeader(name, request.headers().getAll(name)); } } clientRequest.exceptionHandler(e -> { log.debug("exception", e); int status; if (e instanceof UnknownHostException) { status = 504; } else { status = 400; } request.response().setStatusCode(status).end(e.toString() + " on client request"); }); clientRequest.end(); } else { request.response().setStatusCode(405).end("method not supported"); } }); server.listen(server -> { finishedHandler.handle(null); }); } /** * Stop the server. * <p> * Doesn't wait for the close operation to finish */ @Override public void stop() { if (server != null) { server.close(); server = null; } } @Override public int getPort() { return PORT; } @Override public HttpMethod getLastMethod() { return lastMethod; } @Override public MultiMap getLastRequestHeaders() { return lastRequestHeaders; } public HttpProxy setError(int error) { this.error = error; return this; } }