Path: blob/master/test/jdk/java/net/Socket/HttpProxy.java
41149 views
/*1* Copyright (c) 2010, 2019, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation.7*8* This code is distributed in the hope that it will be useful, but WITHOUT9* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or10* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License11* version 2 for more details (a copy is included in the LICENSE file that12* accompanied this code).13*14* You should have received a copy of the GNU General Public License version15* 2 along with this work; if not, write to the Free Software Foundation,16* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.17*18* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA19* or visit www.oracle.com if you need additional information or have any20* questions.21*/2223/*24* @test25* @bug 6370908 822066326* @library /test/lib27* @summary Add support for HTTP_CONNECT proxy in Socket class.28* This test uses the wildcard address and is susceptible to fail intermittently.29* @key intermittent30* @modules java.base/sun.net.www31* @run main HttpProxy32* @run main/othervm -Djava.net.preferIPv4Stack=true HttpProxy33* @run main/othervm -Djava.net.preferIPv6Addresses=true HttpProxy34*/3536import java.io.IOException;37import java.io.InputStream;38import java.io.OutputStream;39import java.io.PrintWriter;40import static java.lang.System.out;41import java.net.InetAddress;42import java.net.InetSocketAddress;43import java.net.Proxy;44import java.net.ServerSocket;45import java.net.Socket;46import java.net.SocketAddress;47import java.util.ArrayList;48import java.util.List;49import jdk.test.lib.net.IPSupport;50import sun.net.www.MessageHeader;5152public class HttpProxy {53final String proxyHost;54final int proxyPort;55static final int SO_TIMEOUT = 15000;5657public static void main(String[] args) throws Exception {58IPSupport.throwSkippedExceptionIfNonOperational();5960String host;61int port;62ConnectProxyTunnelServer proxy = null;63if (args.length == 0) {64// Start internal proxy65proxy = new ConnectProxyTunnelServer();66proxy.start();67host = InetAddress.getLoopbackAddress().getHostAddress();68port = proxy.getLocalPort();69out.println("Running with internal proxy: " + host + ":" + port);70} else if (args.length == 2) {71host = args[0];72port = Integer.valueOf(args[1]);73out.println("Running against specified proxy server: " + host + ":" + port);74} else {75System.err.println("Usage: java HttpProxy [<proxy host> <proxy port>]");76return;77}7879try {80HttpProxy p = new HttpProxy(host, port);81p.test();82} finally {83if (proxy != null)84proxy.close();85}86}8788public HttpProxy(String proxyHost, int proxyPort) {89this.proxyHost = proxyHost;90this.proxyPort = proxyPort;91}9293static boolean canUseIPv6() {94return IPSupport.hasIPv6() && !IPSupport.preferIPv4Stack();95}9697void test() throws Exception {98InetSocketAddress proxyAddress = new InetSocketAddress(proxyHost, proxyPort);99Proxy httpProxy = new Proxy(Proxy.Type.HTTP, proxyAddress);100101// Wildcard address is needed here102try (ServerSocket ss = new ServerSocket(0)) {103List<InetSocketAddress> externalAddresses = new ArrayList<>();104externalAddresses.add(105new InetSocketAddress(InetAddress.getLocalHost(), ss.getLocalPort()));106107if (canUseIPv6()) {108byte[] bytes = new byte[] {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1};109var address = InetAddress.getByAddress(bytes);110externalAddresses.add(111new InetSocketAddress(address, ss.getLocalPort()));112}113114for (SocketAddress externalAddress : externalAddresses) {115try (Socket sock = new Socket(httpProxy)) {116sock.setSoTimeout(SO_TIMEOUT);117sock.setTcpNoDelay(false);118119out.println("Trying to connect to server socket on " + externalAddress);120sock.connect(externalAddress);121try (Socket externalSock = ss.accept()) {122// perform some simple checks123check(sock.isBound(), "Socket is not bound");124check(sock.isConnected(), "Socket is not connected");125check(!sock.isClosed(), "Socket should not be closed");126check(sock.getSoTimeout() == SO_TIMEOUT,127"Socket should have a previously set timeout");128check(sock.getTcpNoDelay() == false, "NODELAY should be false");129130simpleDataExchange(sock, externalSock);131}132}133}134}135}136137static void check(boolean condition, String message) {138if (!condition) out.println(message);139}140141static Exception unexpected(Exception e) {142out.println("Unexpected Exception: " + e);143e.printStackTrace();144return e;145}146147// performs a simple exchange of data between the two sockets148// and throws an exception if there is any problem.149void simpleDataExchange(Socket s1, Socket s2) throws Exception {150try (final InputStream i1 = s1.getInputStream();151final InputStream i2 = s2.getInputStream();152final OutputStream o1 = s1.getOutputStream();153final OutputStream o2 = s2.getOutputStream()) {154startSimpleWriter("simpleWriter1", o1, 100);155startSimpleWriter("simpleWriter2", o2, 200);156simpleRead(i2, 100);157simpleRead(i1, 200);158}159}160161void startSimpleWriter(String threadName, final OutputStream os, final int start) {162(new Thread(new Runnable() {163public void run() {164try { simpleWrite(os, start); }165catch (Exception e) {unexpected(e); }166finally { out.println(threadName + ": done"); }167}}, threadName)).start();168}169170void simpleWrite(OutputStream os, int start) throws Exception {171byte b[] = new byte[2];172for (int i=start; i<start+100; i++) {173b[0] = (byte) (i / 256);174b[1] = (byte) (i % 256);175os.write(b);176}177out.println("Wrote " + start + " -> " + (start + 100));178}179180void simpleRead(InputStream is, int start) throws Exception {181byte b[] = new byte [2];182for (int i=start; i<start+100; i++) {183int x = is.read(b);184if (x == 1)185x += is.read(b,1,1);186if (x!=2)187throw new Exception("read error");188int r = bytes(b[0], b[1]);189if (r != i)190throw new Exception("read " + r + " expected " +i);191}192out.println("Read " + start + " -> " + (start + 100));193}194195int bytes(byte b1, byte b2) {196int i1 = (int)b1 & 0xFF;197int i2 = (int)b2 & 0xFF;198return i1 * 256 + i2;199}200201static class ConnectProxyTunnelServer extends Thread implements AutoCloseable {202203private final ServerSocket ss;204private volatile boolean closed;205206public ConnectProxyTunnelServer() throws IOException {207ss = new ServerSocket(0, 0, InetAddress.getLoopbackAddress());208}209210@Override211public void run() {212try {213while (!closed) {214try (Socket clientSocket = ss.accept()) {215processRequest(clientSocket);216}217}218} catch (Exception e) {219if (!closed) {220out.println("Proxy Failed: " + e);221e.printStackTrace();222}223} finally {224if (!closed)225try { ss.close(); } catch (IOException x) { unexpected(x); }226}227}228229/**230* Returns the port on which the proxy is accepting connections.231*/232public int getLocalPort() {233return ss.getLocalPort();234}235236@Override237public void close() throws Exception {238closed = true;239ss.close();240}241242/*243* Processes the CONNECT request244*/245private void processRequest(Socket clientSocket) throws Exception {246MessageHeader mheader = new MessageHeader(clientSocket.getInputStream());247String statusLine = mheader.getValue(0);248249if (!statusLine.startsWith("CONNECT")) {250out.println("proxy server: processes only "251+ "CONNECT method requests, received: "252+ statusLine);253return;254}255256// retrieve the host and port info from the status-line257InetSocketAddress serverAddr = getConnectInfo(statusLine);258out.println("Proxy serving CONNECT request to " + serverAddr);259260//open socket to the server261try (Socket serverSocket = new Socket(serverAddr.getAddress(),262serverAddr.getPort())) {263Forwarder clientFW = new Forwarder(clientSocket.getInputStream(),264serverSocket.getOutputStream());265Thread clientForwarderThread = new Thread(clientFW, "ClientForwarder");266clientForwarderThread.start();267send200(clientSocket);268Forwarder serverFW = new Forwarder(serverSocket.getInputStream(),269clientSocket.getOutputStream());270serverFW.run();271clientForwarderThread.join();272}273}274275private void send200(Socket clientSocket) throws IOException {276OutputStream out = clientSocket.getOutputStream();277PrintWriter pout = new PrintWriter(out);278279pout.println("HTTP/1.1 200 OK");280pout.println();281pout.flush();282}283284/*285* This method retrieves the hostname and port of the tunnel destination286* from the request line.287* @param connectStr288* of the form: <i>CONNECT server-name:server-port HTTP/1.x</i>289*/290static InetSocketAddress getConnectInfo(String connectStr)291throws Exception292{293try {294int starti = connectStr.indexOf(' ');295int endi = connectStr.lastIndexOf(' ');296String connectInfo = connectStr.substring(starti+1, endi).trim();297// retrieve server name and port298endi = connectInfo.lastIndexOf(':');299String name = connectInfo.substring(0, endi);300301if (name.contains(":")) {302if (!(name.startsWith("[") && name.endsWith("]"))) {303throw new IOException("Invalid host:" + name);304}305name = name.substring(1, name.length() - 1);306}307int port = Integer.parseInt(connectInfo.substring(endi+1));308return new InetSocketAddress(name, port);309} catch (Exception e) {310out.println("Proxy received a request: " + connectStr);311throw unexpected(e);312}313}314}315316/* Reads from the given InputStream and writes to the given OutputStream */317static class Forwarder implements Runnable318{319private final InputStream in;320private final OutputStream os;321322Forwarder(InputStream in, OutputStream os) {323this.in = in;324this.os = os;325}326327@Override328public void run() {329try {330byte[] ba = new byte[1024];331int count;332while ((count = in.read(ba)) != -1) {333os.write(ba, 0, count);334}335} catch (IOException e) {336unexpected(e);337}338}339}340}341342343