Skip to content

Instantly share code, notes, and snippets.

@willianantunes
Created October 9, 2018 22:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save willianantunes/1d10b71e88ac18744974dae0daf05911 to your computer and use it in GitHub Desktop.
Save willianantunes/1d10b71e88ac18744974dae0daf05911 to your computer and use it in GitHub Desktop.
Connection to a SOCKET SERVER via HTTP proxy
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