Created
October 9, 2018 22:00
-
-
Save willianantunes/1d10b71e88ac18744974dae0daf05911 to your computer and use it in GitHub Desktop.
Connection to a SOCKET SERVER via HTTP proxy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.mysql.cj.protocol; | |
import com.mysql.cj.conf.PropertyDefinitions; | |
import java.io.IOException; | |
import java.io.InputStream; | |
import java.lang.reflect.InvocationHandler; | |
import java.lang.reflect.Method; | |
import java.net.InetSocketAddress; | |
import java.net.Socket; | |
import java.net.SocketException; | |
import java.util.Optional; | |
import java.util.Properties; | |
public class HttpConnectProxyFactory extends StandardSocketFactory { | |
private static int HTTP_DEFAULT_PORT = 80; | |
private static int HTTPS_DEFAULT_PORT = 443; | |
@Override | |
protected Socket createSocket(Properties props) { | |
Optional<InetSocketAddress> optionalInetSocketAddress = getOptionalInetSocketAddressFrom( | |
props.getProperty(PropertyDefinitions.PNAME_httpProxyHost), | |
props.getProperty(PropertyDefinitions.PNAME_httpProxyPort), | |
HTTP_DEFAULT_PORT); | |
InetSocketAddress proxyAddress = optionalInetSocketAddress.orElse(getOptionalInetSocketAddressFrom( | |
props.getProperty(PropertyDefinitions.PNAME_httpsProxyHost), | |
props.getProperty(PropertyDefinitions.PNAME_httpsProxyPort), | |
HTTPS_DEFAULT_PORT).orElseThrow(IllegalArgumentException::new)); | |
return (Socket) java.lang.reflect.Proxy.newProxyInstance( | |
Socket.class.getClassLoader(), | |
new Class[]{Socket.class}, | |
new ProxyableSocket(new Socket(), proxyAddress) | |
); | |
} | |
/** | |
* Proxy which handle socket connect through HTTP CONNECTION standard. | |
* | |
* @see <a href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.9">CONNECT</a> | |
* @see <a href="https://stackoverflow.com/a/15869788">How to connect a Socket server via HTTP proxy</a> | |
*/ | |
public static class ProxyableSocket implements InvocationHandler { | |
private static String METHOD_CONNECT = "connect"; | |
private final Socket target; | |
private final InetSocketAddress proxyAddress; | |
public ProxyableSocket(Socket target, InetSocketAddress proxyAddress) { | |
this.target = target; | |
this.proxyAddress = proxyAddress; | |
} | |
@Override | |
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { | |
if (isEligible(method, args)) { | |
InetSocketAddress finalDestination = (InetSocketAddress) args[0]; | |
return method.invoke(openTunnelAndGetRefreshedSocket(proxyAddress, finalDestination), args); | |
} | |
return method.invoke(target, args); | |
} | |
/** | |
* Base its judgment from StandardSocketFactory. | |
* | |
* @see <a href="https://github.com/mysql/mysql-connector-j/blob/fe1903b1ecb4a96a917f7ed3190d80c049b1de29/src/com/mysql/jdbc/StandardSocketFactory.java#L211">StandardSocketFactory</a> | |
*/ | |
private boolean isEligible(Method method, Object[] args) { | |
return method.getName().equalsIgnoreCase(METHOD_CONNECT) && args.length == 2; | |
} | |
private Socket openTunnelAndGetRefreshedSocket(InetSocketAddress proxyAddress, InetSocketAddress destination) throws IOException { | |
Socket socket = new Socket(proxyAddress.getHostName(), proxyAddress.getPort()); | |
String proxyConnect = buildConnectRequest(destination); | |
socket.getOutputStream().write(proxyConnect.getBytes()); | |
byte[] tmpBuffer = new byte[512]; | |
InputStream socketInput = socket.getInputStream(); | |
int len = socketInput.read(tmpBuffer, 0, tmpBuffer.length); | |
if (len == 0) { | |
throw new SocketException("Invalid response from proxy"); | |
} | |
String proxyResponse = new String(tmpBuffer, 0, len, "UTF-8"); | |
if (isResponseValidFromHttpProxy(proxyResponse)) { | |
flushAnyOutstandingMessageInBuffer(socketInput); | |
return socket; | |
} else { | |
throw new SocketException("Fail to create Socket: " + proxyResponse); | |
} | |
} | |
private String buildConnectRequest(InetSocketAddress destination) { | |
return "CONNECT " + destination.getHostName() + ":" + destination.getPort(); | |
} | |
private void flushAnyOutstandingMessageInBuffer(InputStream socketInput) throws IOException { | |
if (socketInput.available() > 0) { | |
socketInput.skip(socketInput.available()); | |
} | |
} | |
/** | |
* Expects HTTP/1.x 200 OK. | |
*/ | |
private boolean isResponseValidFromHttpProxy(String proxyResponse) { | |
return proxyResponse.indexOf("200") != -1; | |
} | |
} | |
private Optional<InetSocketAddress> getOptionalInetSocketAddressFrom(String host, String port, int defaultPort) { | |
return Optional.ofNullable(host) | |
.map(v -> new InetSocketAddress(v, stringAsIntegerOrStandard(port, defaultPort))); | |
} | |
private Integer stringAsIntegerOrStandard(String string, int standardValue) { | |
return Optional.ofNullable(string) | |
.map(Integer::valueOf) | |
.orElse(standardValue); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment