test/jdk/java/net/httpclient/ProxyServer.java
changeset 47216 71c04702a3d5
parent 36131 379db4b2f95d
child 48083 b1c1b4ef4be2
child 55764 34d7cc00f87a
equal deleted inserted replaced
47215:4ebc2e2fb97c 47216:71c04702a3d5
       
     1 /*
       
     2  * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
       
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
       
     4  *
       
     5  * This code is free software; you can redistribute it and/or modify it
       
     6  * under the terms of the GNU General Public License version 2 only, as
       
     7  * published by the Free Software Foundation.
       
     8  *
       
     9  * This code is distributed in the hope that it will be useful, but WITHOUT
       
    10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
       
    11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
       
    12  * version 2 for more details (a copy is included in the LICENSE file that
       
    13  * accompanied this code).
       
    14  *
       
    15  * You should have received a copy of the GNU General Public License version
       
    16  * 2 along with this work; if not, write to the Free Software Foundation,
       
    17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
       
    18  *
       
    19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
       
    20  * or visit www.oracle.com if you need additional information or have any
       
    21  * questions.
       
    22  */
       
    23 
       
    24 import java.net.*;
       
    25 import java.io.*;
       
    26 import java.util.*;
       
    27 import java.security.*;
       
    28 
       
    29 /**
       
    30  * A minimal proxy server that supports CONNECT tunneling. It does not do
       
    31  * any header transformations. In future this could be added.
       
    32  * Two threads are created per client connection. So, it's not
       
    33  * intended for large numbers of parallel connections.
       
    34  */
       
    35 public class ProxyServer extends Thread implements Closeable {
       
    36 
       
    37     ServerSocket listener;
       
    38     int port;
       
    39     volatile boolean debug;
       
    40 
       
    41     /**
       
    42      * Create proxy on port (zero means don't care). Call getPort()
       
    43      * to get the assigned port.
       
    44      */
       
    45     public ProxyServer(Integer port) throws IOException {
       
    46         this(port, false);
       
    47     }
       
    48 
       
    49     public ProxyServer(Integer port, Boolean debug) throws IOException {
       
    50         this.debug = debug;
       
    51         listener = new ServerSocket(port);
       
    52         this.port = listener.getLocalPort();
       
    53         setName("ProxyListener");
       
    54         setDaemon(true);
       
    55         connections = new LinkedList<>();
       
    56         start();
       
    57     }
       
    58 
       
    59     public ProxyServer(String s) {  }
       
    60 
       
    61     /**
       
    62      * Returns the port number this proxy is listening on
       
    63      */
       
    64     public int getPort() {
       
    65         return port;
       
    66     }
       
    67 
       
    68     /**
       
    69      * Shuts down the proxy, probably aborting any connections
       
    70      * currently open
       
    71      */
       
    72     public void close() throws IOException {
       
    73         if (debug) System.out.println("Proxy: closing");
       
    74             done = true;
       
    75         listener.close();
       
    76         for (Connection c : connections) {
       
    77             if (c.running()) {
       
    78                 c.close();
       
    79             }
       
    80         }
       
    81     }
       
    82 
       
    83     List<Connection> connections;
       
    84 
       
    85     volatile boolean done;
       
    86 
       
    87     public void run() {
       
    88         if (System.getSecurityManager() == null) {
       
    89             execute();
       
    90         } else {
       
    91             // so calling domain does not need to have socket permission
       
    92             AccessController.doPrivileged(new PrivilegedAction<Void>() {
       
    93                 public Void run() {
       
    94                     execute();
       
    95                     return null;
       
    96                 }
       
    97             });
       
    98         }
       
    99     }
       
   100 
       
   101     public void execute() {
       
   102         try {
       
   103             while(!done) {
       
   104                 Socket s = listener.accept();
       
   105                 if (debug)
       
   106                     System.out.println("Client: " + s);
       
   107                 Connection c = new Connection(s);
       
   108                 connections.add(c);
       
   109             }
       
   110         } catch(Throwable e) {
       
   111             if (debug && !done) {
       
   112                 System.out.println("Fatal error: Listener: " + e);
       
   113                 e.printStackTrace();
       
   114             }
       
   115         }
       
   116     }
       
   117 
       
   118     /**
       
   119      * Transparently forward everything, once we know what the destination is
       
   120      */
       
   121     class Connection {
       
   122 
       
   123         Socket clientSocket, serverSocket;
       
   124         Thread out, in;
       
   125         volatile InputStream clientIn, serverIn;
       
   126         volatile OutputStream clientOut, serverOut;
       
   127 
       
   128         boolean forwarding = false;
       
   129 
       
   130         final static int CR = 13;
       
   131         final static int LF = 10;
       
   132 
       
   133         Connection(Socket s) throws IOException {
       
   134             this.clientSocket= s;
       
   135             this.clientIn = new BufferedInputStream(s.getInputStream());
       
   136             this.clientOut = s.getOutputStream();
       
   137             init();
       
   138         }
       
   139 
       
   140         byte[] readHeaders(InputStream is) throws IOException {
       
   141             byte[] outbuffer = new byte[8000];
       
   142             int crlfcount = 0;
       
   143             int bytecount = 0;
       
   144             int c;
       
   145             while ((c=is.read()) != -1 && bytecount < outbuffer.length) {
       
   146                 outbuffer[bytecount++] = (byte)c;
       
   147                 if (debug) System.out.write(c);
       
   148                 // were looking for CRLFCRLF sequence
       
   149                 if (c == CR || c == LF) {
       
   150                     switch(crlfcount) {
       
   151                         case 0:
       
   152                             if (c == CR) crlfcount ++;
       
   153                             break;
       
   154                         case 1:
       
   155                             if (c == LF) crlfcount ++;
       
   156                             break;
       
   157                         case 2:
       
   158                             if (c == CR) crlfcount ++;
       
   159                             break;
       
   160                         case 3:
       
   161                             if (c == LF) crlfcount ++;
       
   162                             break;
       
   163                     }
       
   164                 } else {
       
   165                     crlfcount = 0;
       
   166                 }
       
   167                 if (crlfcount == 4) {
       
   168                     break;
       
   169                 }
       
   170             }
       
   171             byte[] ret = new byte[bytecount];
       
   172             System.arraycopy(outbuffer, 0, ret, 0, bytecount);
       
   173             return ret;
       
   174         }
       
   175 
       
   176         boolean running() {
       
   177             return out.isAlive() || in.isAlive();
       
   178         }
       
   179 
       
   180         public void close() throws IOException {
       
   181             if (debug) System.out.println("Closing connection (proxy)");
       
   182             if (serverSocket != null) serverSocket.close();
       
   183             if (clientSocket != null) clientSocket.close();
       
   184         }
       
   185 
       
   186         int findCRLF(byte[] b) {
       
   187             for (int i=0; i<b.length-1; i++) {
       
   188                 if (b[i] == CR && b[i+1] == LF) {
       
   189                     return i;
       
   190                 }
       
   191             }
       
   192             return -1;
       
   193         }
       
   194 
       
   195         public void init() {
       
   196             try {
       
   197                 byte[] buf = readHeaders(clientIn);
       
   198                 int p = findCRLF(buf);
       
   199                 if (p == -1) {
       
   200                     close();
       
   201                     return;
       
   202                 }
       
   203                 String cmd = new String(buf, 0, p, "US-ASCII");
       
   204                 String[] params = cmd.split(" ");
       
   205                 if (params[0].equals("CONNECT")) {
       
   206                     doTunnel(params[1]);
       
   207                 } else {
       
   208                     doProxy(params[1], buf, p, cmd);
       
   209                 }
       
   210             } catch (IOException e) {
       
   211                 if (debug) {
       
   212                     System.out.println (e);
       
   213                 }
       
   214                 try {close(); } catch (IOException e1) {}
       
   215             }
       
   216         }
       
   217 
       
   218         void doProxy(String dest, byte[] buf, int p, String cmdLine)
       
   219             throws IOException
       
   220         {
       
   221             try {
       
   222                 URI uri = new URI(dest);
       
   223                 if (!uri.isAbsolute()) {
       
   224                     throw new IOException("request URI not absolute");
       
   225                 }
       
   226                 dest = uri.getAuthority();
       
   227                 // now extract the path from the URI and recreate the cmd line
       
   228                 int sp = cmdLine.indexOf(' ');
       
   229                 String method = cmdLine.substring(0, sp);
       
   230                 cmdLine = method + " " + uri.getPath() + " HTTP/1.1";
       
   231                 int x = cmdLine.length() - 1;
       
   232                 int i = p;
       
   233                 while (x >=0) {
       
   234                     buf[i--] = (byte)cmdLine.charAt(x--);
       
   235                 }
       
   236                 i++;
       
   237 
       
   238                 commonInit(dest, 80);
       
   239                 serverOut.write(buf, i, buf.length-i);
       
   240                 proxyCommon();
       
   241 
       
   242             } catch (URISyntaxException e) {
       
   243                 throw new IOException(e);
       
   244             }
       
   245         }
       
   246 
       
   247         void commonInit(String dest, int defaultPort) throws IOException {
       
   248             int port;
       
   249             String[] hostport = dest.split(":");
       
   250             if (hostport.length == 1) {
       
   251                 port = defaultPort;
       
   252             } else {
       
   253                 port = Integer.parseInt(hostport[1]);
       
   254             }
       
   255             if (debug) System.out.printf("Server: (%s/%d)\n", hostport[0], port);
       
   256             serverSocket = new Socket(hostport[0], port);
       
   257             serverOut = serverSocket.getOutputStream();
       
   258 
       
   259             serverIn = new BufferedInputStream(serverSocket.getInputStream());
       
   260         }
       
   261 
       
   262         void proxyCommon() throws IOException {
       
   263             out = new Thread(() -> {
       
   264                 try {
       
   265                     byte[] bb = new byte[8000];
       
   266                     int n;
       
   267                     while ((n = clientIn.read(bb)) != -1) {
       
   268                         serverOut.write(bb, 0, n);
       
   269                     }
       
   270                     serverSocket.close();
       
   271                     clientSocket.close();
       
   272                 } catch (IOException e) {
       
   273                     if (debug) {
       
   274                         System.out.println (e);
       
   275                     }
       
   276                 }
       
   277             });
       
   278             in = new Thread(() -> {
       
   279                 try {
       
   280                     byte[] bb = new byte[8000];
       
   281                     int n;
       
   282                     while ((n = serverIn.read(bb)) != -1) {
       
   283                         clientOut.write(bb, 0, n);
       
   284                     }
       
   285                     serverSocket.close();
       
   286                     clientSocket.close();
       
   287                 } catch (IOException e) {
       
   288                     if (debug) {
       
   289                         System.out.println(e);
       
   290                         e.printStackTrace();
       
   291                     }
       
   292                 }
       
   293             });
       
   294             out.setName("Proxy-outbound");
       
   295             out.setDaemon(true);
       
   296             in.setDaemon(true);
       
   297             in.setName("Proxy-inbound");
       
   298             out.start();
       
   299             in.start();
       
   300         }
       
   301 
       
   302         void doTunnel(String dest) throws IOException {
       
   303             commonInit(dest, 443);
       
   304             clientOut.write("HTTP/1.1 200 OK\r\n\r\n".getBytes());
       
   305             proxyCommon();
       
   306         }
       
   307     }
       
   308 
       
   309     public static void main(String[] args) throws Exception {
       
   310         int port = Integer.parseInt(args[0]);
       
   311         boolean debug = args.length > 1 && args[1].equals("-debug");
       
   312         System.out.println("Debugging : " + debug);
       
   313         ProxyServer ps = new ProxyServer(port, debug);
       
   314         System.out.println("Proxy server listening on port " + ps.getPort());
       
   315         while (true) {
       
   316             Thread.sleep(5000);
       
   317         }
       
   318     }
       
   319 }