├── .gitignore ├── README.md ├── pom.xml └── src └── main └── java └── com └── luoxq └── http └── proxy ├── Common.java ├── client ├── SocketAgent.java └── ProxyAgent.java └── server └── ProxyServer.java /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | keystore 3 | *.jar 4 | *.sh 5 | *.tar 6 | *.log 7 | *.iml 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # A Very Simple Http Proxy Server 4 | 5 | There are two components in this project: 6 | ## A Http Proxy Server which provides Proxy Service with SSL enabled. 7 | ## A Client Side Agent which connects to the Proxy Server and provide a non-SSL local service. 8 | 9 | This is super simple which can work in real world. It is a demostration of how a forward proxy works. 10 | There is no performance optimization, nor NIO, so it may not able to handle massive connections. 11 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.luoxq 8 | HttpProxy 9 | 1.0-SNAPSHOT 10 | 11 | 12 | 1.8 13 | 14 | 15 | 16 | 17 | junit 18 | junit 19 | 4.13.1 20 | 21 | 22 | 23 | 24 | 25 | 26 | maven-compiler-plugin 27 | 28 | 1.8 29 | 1.8 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/main/java/com/luoxq/http/proxy/Common.java: -------------------------------------------------------------------------------- 1 | package com.luoxq.http.proxy; 2 | 3 | import java.io.Closeable; 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | import java.io.OutputStream; 7 | import java.util.concurrent.*; 8 | 9 | public class Common { 10 | 11 | public static void setDefaultProperties(String key, String value) { 12 | System.setProperty(key, System.getProperty(key, value)); 13 | } 14 | 15 | 16 | public static void concurrent(Runnable... cs) { 17 | ExecutorService exe = Executors.newFixedThreadPool(cs.length); 18 | Future[] futures = new Future[cs.length]; 19 | for (int i = 0; i < cs.length; i++) { 20 | futures[i] = exe.submit(cs[i]); 21 | } 22 | for (Future f : futures) { 23 | try { 24 | f.get(); 25 | } catch (InterruptedException e) { 26 | e.printStackTrace(); 27 | } catch (ExecutionException e) { 28 | e.printStackTrace(); 29 | } 30 | } 31 | } 32 | 33 | public static void closeAll(Closeable... cs) { 34 | for (Closeable c : cs) { 35 | try { 36 | if (c != null) c.close(); 37 | } catch (IOException e) { 38 | e.printStackTrace(); 39 | } 40 | } 41 | } 42 | 43 | public static void transfer(InputStream in, OutputStream out) throws IOException { 44 | byte[] buf = new byte[1024 * 4]; 45 | int read = 0; 46 | while ((read = in.read(buf)) >= 0) { 47 | out.write(buf, 0, read); 48 | out.flush(); 49 | } 50 | } 51 | 52 | 53 | public static String readLine(InputStream in) throws IOException { 54 | StringBuilder b = new StringBuilder(50); 55 | 56 | int c = 0; 57 | while ((c = in.read()) >= 0) { 58 | b.append((char) c); 59 | if (c == '\n') { 60 | break; 61 | } 62 | } 63 | return b.toString(); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/com/luoxq/http/proxy/client/SocketAgent.java: -------------------------------------------------------------------------------- 1 | package com.luoxq.http.proxy.client; 2 | 3 | import javax.net.ServerSocketFactory; 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | import java.io.OutputStream; 7 | import java.net.ServerSocket; 8 | import java.net.Socket; 9 | 10 | import static com.luoxq.http.proxy.Common.*; 11 | 12 | /** 13 | * A port forwarding program. Listen on a local port and forward all the content in the socket in to a specific server 14 | * socket address and return the data from server to the client. 15 | */ 16 | public class SocketAgent extends Thread { 17 | 18 | static String server; 19 | static int port; 20 | Socket cSock; 21 | Socket sSock; 22 | InputStream cin; 23 | OutputStream cout; 24 | 25 | public SocketAgent(Socket s) { 26 | this.cSock = s; 27 | } 28 | 29 | public static void main(String[] args) throws Exception { 30 | 31 | if (args.length != 3) { 32 | System.err.println("Usage: SocketAgent Server ServerPort LocalPort"); 33 | System.exit(0); 34 | } 35 | 36 | server = args[0]; 37 | port = Integer.valueOf(args[1]); 38 | 39 | 40 | ServerSocket ss; 41 | Integer localPort = Integer.valueOf(args[2]); 42 | ss = ServerSocketFactory.getDefault().createServerSocket(localPort); 43 | ss.setReceiveBufferSize(1024 * 1024); 44 | while (true) { 45 | Socket s = ss.accept(); 46 | new SocketAgent(s).start(); 47 | } 48 | } 49 | 50 | public void run() { 51 | try { 52 | cin = cSock.getInputStream(); 53 | cout = cSock.getOutputStream(); 54 | doConnect(); 55 | } catch (Exception ex) { 56 | ex.printStackTrace(); 57 | } finally { 58 | try { 59 | cSock.close(); 60 | } catch (IOException e) { 61 | e.printStackTrace(); 62 | } 63 | } 64 | } 65 | 66 | private void doConnect() throws Exception { 67 | sSock = new Socket(server, port); 68 | sSock.setReceiveBufferSize(1024 * 1024); 69 | InputStream sin = sSock.getInputStream(); 70 | OutputStream sout = sSock.getOutputStream(); 71 | doTransfer(sin, sout); 72 | } 73 | 74 | private void doTransfer(InputStream sin, OutputStream sout) { 75 | concurrent( 76 | () -> { 77 | try { 78 | transfer(cin, sout); 79 | } catch (IOException e) { 80 | close(); 81 | } 82 | }, 83 | () -> { 84 | try { 85 | transfer(sin, cout); 86 | } catch (IOException e) { 87 | close(); 88 | } 89 | } 90 | 91 | ); 92 | } 93 | 94 | void close() { 95 | closeAll(cSock, sSock); 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/com/luoxq/http/proxy/client/ProxyAgent.java: -------------------------------------------------------------------------------- 1 | package com.luoxq.http.proxy.client; 2 | 3 | import javax.net.ServerSocketFactory; 4 | import javax.net.ssl.SSLContext; 5 | import javax.net.ssl.SSLSocketFactory; 6 | import javax.net.ssl.TrustManager; 7 | import javax.net.ssl.X509TrustManager; 8 | import java.io.IOException; 9 | import java.io.InputStream; 10 | import java.io.OutputStream; 11 | import java.net.ServerSocket; 12 | import java.net.Socket; 13 | import java.security.KeyManagementException; 14 | import java.security.NoSuchAlgorithmException; 15 | import java.security.SecureRandom; 16 | import java.security.cert.X509Certificate; 17 | 18 | import static com.luoxq.http.proxy.Common.*; 19 | 20 | public class ProxyAgent extends Thread { 21 | 22 | static String server; 23 | static int port; 24 | static SSLSocketFactory sslSocketFactory; 25 | Socket cSock; 26 | Socket sSock; 27 | InputStream cin; 28 | OutputStream cout; 29 | 30 | public ProxyAgent(Socket s) { 31 | this.cSock = s; 32 | } 33 | 34 | /** 35 | * Start a Local port with Non-SSL service and proxy all data to a remote SSL server socket. 36 | */ 37 | public static void main(String[] args) throws Exception { 38 | 39 | if (args.length < 3) { 40 | System.err.println("Usage: ProxyAgent Server ServerPort LocalPort"); 41 | System.exit(0); 42 | } 43 | 44 | server = args[0]; 45 | port = Integer.valueOf(args[1]); 46 | 47 | sslSocketFactory = getSslSocketFactory(); 48 | 49 | ServerSocket ss; 50 | Integer localPort = Integer.valueOf(args[2]); 51 | ss = ServerSocketFactory.getDefault().createServerSocket(localPort); 52 | ss.setReceiveBufferSize(1024 * 1024); 53 | while (true) { 54 | Socket s = ss.accept(); 55 | new ProxyAgent(s).start(); 56 | } 57 | } 58 | 59 | static private SSLSocketFactory getSslSocketFactory() throws NoSuchAlgorithmException, KeyManagementException { 60 | SSLContext ssl_ctx = SSLContext.getInstance("TLS"); 61 | TrustManager[] certs = new TrustManager[]{new X509TrustManager() { 62 | public X509Certificate[] getAcceptedIssuers() { 63 | return null; 64 | } 65 | 66 | public void checkClientTrusted(X509Certificate[] certs, String t) { 67 | } 68 | 69 | public void checkServerTrusted(X509Certificate[] certs, String t) { 70 | } 71 | }}; 72 | ssl_ctx.init(null, certs, new SecureRandom()); 73 | 74 | return ssl_ctx.getSocketFactory(); 75 | } 76 | 77 | public void run() { 78 | try { 79 | cin = cSock.getInputStream(); 80 | cout = cSock.getOutputStream(); 81 | doConnect(); 82 | } catch (Exception ex) { 83 | ex.printStackTrace(); 84 | } finally { 85 | try { 86 | cSock.close(); 87 | } catch (IOException e) { 88 | e.printStackTrace(); 89 | } 90 | } 91 | } 92 | 93 | private void doConnect() throws Exception { 94 | sSock = sslSocketFactory.createSocket(server, port); 95 | sSock.setReceiveBufferSize(1024 * 1024); 96 | InputStream sin = sSock.getInputStream(); 97 | OutputStream sout = sSock.getOutputStream(); 98 | doTransfer(sin, sout); 99 | } 100 | 101 | private void doTransfer(InputStream sin, OutputStream sout) { 102 | concurrent( 103 | () -> { 104 | try { 105 | transfer(cin, sout); 106 | } catch (IOException e) { 107 | close(); 108 | } 109 | }, 110 | () -> { 111 | try { 112 | transfer(sin, cout); 113 | } catch (IOException e) { 114 | close(); 115 | } 116 | } 117 | 118 | ); 119 | } 120 | 121 | void close() { 122 | closeAll(cSock, sSock); 123 | } 124 | 125 | } 126 | -------------------------------------------------------------------------------- /src/main/java/com/luoxq/http/proxy/server/ProxyServer.java: -------------------------------------------------------------------------------- 1 | package com.luoxq.http.proxy.server; 2 | 3 | import java.io.BufferedInputStream; 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | import java.io.OutputStream; 7 | import java.net.ServerSocket; 8 | import java.net.Socket; 9 | import java.net.UnknownHostException; 10 | 11 | import javax.net.ssl.SSLServerSocketFactory; 12 | 13 | import static com.luoxq.http.proxy.Common.*; 14 | 15 | /** 16 | * Open a SSL TCP port and provide the HTTP Proxy service. 17 | */ 18 | public class ProxyServer extends Thread { 19 | 20 | public static void main(String[] args) throws Exception { 21 | 22 | if (args.length != 1) { 23 | System.err.println("Usage: ProxyServer Port"); 24 | System.exit(1); 25 | } 26 | 27 | setDefaultProperties("javax.net.ssl.keyStore", "keystore"); 28 | setDefaultProperties("javax.net.ssl.keyStorePassword", "changeit"); 29 | setDefaultProperties("javax.net.ssl.trustStore", "keystore"); 30 | setDefaultProperties("javax.net.ssl.trustStorePassword", "changeit"); 31 | 32 | ServerSocket ss = SSLServerSocketFactory.getDefault() 33 | .createServerSocket(Integer.valueOf(args[0])); 34 | ss.setReceiveBufferSize(1024 * 1024); 35 | while (true) { 36 | Socket s = ss.accept(); 37 | new ProxyServer(s).start(); 38 | } 39 | } 40 | 41 | 42 | Socket cSock; 43 | Socket sSock; 44 | InputStream cin; 45 | OutputStream cout; 46 | 47 | public ProxyServer(Socket s) { 48 | this.cSock = s; 49 | } 50 | 51 | String getHost(String command) { 52 | int start = command.indexOf("//"); 53 | if (start <= 0) { 54 | throw new IllegalArgumentException(); 55 | } 56 | start += 2; 57 | int end = command.indexOf("/", start + 1); 58 | if (end < 0) { 59 | end = command.length(); 60 | } 61 | return command.substring(start, end); 62 | } 63 | 64 | public void run() { 65 | 66 | try { 67 | cin = new BufferedInputStream(cSock.getInputStream()); 68 | cout = cSock.getOutputStream(); 69 | 70 | String firstLine = readLine(cin); 71 | 72 | if (firstLine.toUpperCase().startsWith("CONNECT")) { 73 | doConnect(firstLine); 74 | } else { 75 | doProxy(firstLine); 76 | } 77 | } catch (Exception ex) { 78 | ex.printStackTrace(); 79 | } finally { 80 | try { 81 | cSock.close(); 82 | } catch (IOException e) { 83 | e.printStackTrace(); 84 | } 85 | } 86 | } 87 | 88 | private void doProxy(String firstLine) 89 | throws UnknownHostException, IOException { 90 | String host = getHost(firstLine); 91 | int port = 80; 92 | int ind = host.indexOf(':'); 93 | if (ind > 0) { 94 | port = Integer.valueOf(host.substring(ind)); 95 | 96 | host = host.substring(0, ind); 97 | } 98 | sSock = new Socket(host, port); 99 | sSock.setReceiveBufferSize(1024 * 1024); 100 | InputStream sin = sSock.getInputStream(); 101 | OutputStream sout = sSock.getOutputStream(); 102 | 103 | sout.write(firstLine.replaceFirst("http:\\/\\/[^\\/]+","").getBytes()); 104 | 105 | doTransfer(sin, sout); 106 | } 107 | 108 | private void doConnect(String firstLine) 109 | throws IOException { 110 | String hostPort = getConnectHostPort(firstLine); 111 | int ind = hostPort.indexOf(':'); 112 | String host = hostPort.substring(0, ind); 113 | int port = Integer.valueOf(hostPort.substring(ind + 1)); 114 | sSock = new Socket(host, port); 115 | sSock.setReceiveBufferSize(1024 * 1024); 116 | InputStream sin = sSock.getInputStream(); 117 | OutputStream sout = sSock.getOutputStream(); 118 | 119 | while (!readLine(cin).trim().isEmpty()) 120 | ; 121 | cout.write("HTTP/1.1 200 Connection Established\r\n\r\n".getBytes()); 122 | cout.flush(); 123 | 124 | doTransfer(sin, sout); 125 | 126 | } 127 | 128 | private void doTransfer(InputStream sin, OutputStream sout) { 129 | concurrent( 130 | () -> { 131 | try { 132 | transfer(cin, sout); 133 | } catch (IOException e) { 134 | close(); 135 | } 136 | }, 137 | () -> { 138 | try { 139 | transfer(sin, cout); 140 | } catch (IOException e) { 141 | close(); 142 | } 143 | } 144 | 145 | ); 146 | } 147 | 148 | String getConnectHostPort(String command) { 149 | return command.split("\\s")[1]; 150 | } 151 | 152 | void close() { 153 | closeAll(cSock, sSock); 154 | } 155 | 156 | } 157 | --------------------------------------------------------------------------------