How do I post code that seems to fix a bug in
Apache SOAP 2.2 with respect to using SSL through a proxy server?
While I don't purport to be an Apache SOAP
programming expert, I'll share the code here in the hopes that it will make it
into the next iteration. Of course, I'm sure it can be made nicer since I
was just trying to hack out a fix to keep me moving forward. There's no
doubt a more elegant way... For now, I'm just using my modified JAR, but
I'd much prefer to have the "blessed version" in the next release.
Thanks,
David
+++
In org.apache.soap.util.net.HTTPUtils (note that
I've just branch the code -- again -- based on whether a proxy server was
specified or not, so that if a proxy was specified, then it is passed along to
the SSLUtils code)
private static Socket buildSocket(URL url,
int
targetPort,
String httpProxyHost, int httpProxyPort)
throws Exception {
Socket s = null;
String host = null;
int port;
String httpProxyHost, int httpProxyPort)
throws Exception {
Socket s = null;
String host = null;
int port;
if (httpProxyHost ==
null) {
host = url.getHost();
port = targetPort;
} else {
host = httpProxyHost;
port = httpProxyPort;
}
host = url.getHost();
port = targetPort;
} else {
host = httpProxyHost;
port = httpProxyPort;
}
if
(url.getProtocol().equalsIgnoreCase("HTTPS"))
{
if ( httpProxyHost == null )
{
// Using reflection to avoid compile time dependencies
Class SSLUtilsClass =
Class.forName("org.apache.soap.util.net.SSLUtils");
Class[] paramTypes = new Class[] {String.class, int.class};
Method buildSSLSocket = SSLUtilsClass.getMethod(
"buildSSLSocket", paramTypes);
Object[] params = new Object[] {host, new Integer(port)};
s = (Socket)buildSSLSocket.invoke(null, params);
}
else
{
// Using reflection to avoid compile time dependencies
Class SSLUtilsClass =
Class.forName("org.apache.soap.util.net.SSLUtils");
Class[] paramTypes = new Class[] {String.class, int.class, String.class, int.class};
Method buildSSLSocket = SSLUtilsClass.getMethod(
"buildSSLSocket", paramTypes);
Object[] params = new Object[] {host, new Integer(port), url.getHost(), new Integer(targetPort)};
s = (Socket)buildSSLSocket.invoke(null, params);
}
} else {
s = new Socket(host, port);
}
if ( httpProxyHost == null )
{
// Using reflection to avoid compile time dependencies
Class SSLUtilsClass =
Class.forName("org.apache.soap.util.net.SSLUtils");
Class[] paramTypes = new Class[] {String.class, int.class};
Method buildSSLSocket = SSLUtilsClass.getMethod(
"buildSSLSocket", paramTypes);
Object[] params = new Object[] {host, new Integer(port)};
s = (Socket)buildSSLSocket.invoke(null, params);
}
else
{
// Using reflection to avoid compile time dependencies
Class SSLUtilsClass =
Class.forName("org.apache.soap.util.net.SSLUtils");
Class[] paramTypes = new Class[] {String.class, int.class, String.class, int.class};
Method buildSSLSocket = SSLUtilsClass.getMethod(
"buildSSLSocket", paramTypes);
Object[] params = new Object[] {host, new Integer(port), url.getHost(), new Integer(targetPort)};
s = (Socket)buildSSLSocket.invoke(null, params);
}
} else {
s = new Socket(host, port);
}
return
s;
}
}
+++
The new method added to
org.apache.soap.util.net.SSLUtils differs because it it includes the parameters
to the target host/port as well as the proxy host/port. It creates a
normal socket, does the proxy tunnel handshake with a CONNECT statement, then
wraps that socket in an SSL socket and returns that. Note that
'doTunnelHandshake()' was taken nearly "as is" from the sample code provided by
Sun with JSSE 1.0.2.
/** This method builds an SSL socket through
a tunnel proxy, after auto-starting SSL */
public static Socket buildSSLSocket(String proxyHost, int proxyPort, String targetHost, int targetPort)
throws IOException, UnknownHostException
{
/*
* Let's setup the SSLContext first, as there's a lot of
* computations to be done. If the socket were created
* before the SSLContext, the server/proxy might timeout
* waiting for the client to actually send something.
*/
SSLSocketFactory factory =
(SSLSocketFactory)SSLSocketFactory.getDefault();
public static Socket buildSSLSocket(String proxyHost, int proxyPort, String targetHost, int targetPort)
throws IOException, UnknownHostException
{
/*
* Let's setup the SSLContext first, as there's a lot of
* computations to be done. If the socket were created
* before the SSLContext, the server/proxy might timeout
* waiting for the client to actually send something.
*/
SSLSocketFactory factory =
(SSLSocketFactory)SSLSocketFactory.getDefault();
Socket tunnel = new
Socket(proxyHost, proxyPort);
doTunnelHandshake(tunnel, targetHost, targetPort);
doTunnelHandshake(tunnel, targetHost, targetPort);
/*
* Ok, let's overlay the tunnel socket with SSL.
*/
SSLSocket sslSocket =
(SSLSocket)factory.createSocket(tunnel, proxyHost, proxyPort, true);
* Ok, let's overlay the tunnel socket with SSL.
*/
SSLSocket sslSocket =
(SSLSocket)factory.createSocket(tunnel, proxyHost, proxyPort, true);
/*
* Handshaking is started manually in this example because
* PrintWriter catches all IOExceptions (including
* SSLExceptions), sets an internal error flag, and then
* returns without rethrowing the exception.
*
* Unfortunately, this means any error messages are lost,
* which caused lots of confusion for others using this
* code. The only way to tell there was an error is to call
* PrintWriter.checkError().
*/
sslSocket.startHandshake();
return sslSocket;
}
/*
* Tell our tunnel where we want to CONNECT, and look for the
* right reply. Throw IOException if anything goes wrong.
*/
private static void doTunnelHandshake(Socket tunnel, String host, int port)
throws IOException
{
OutputStream out = tunnel.getOutputStream();
String msg = "CONNECT " + host + ":" + port + " HTTP/1.0\n"
+ "User-Agent: "
+ sun.net.www.protocol.http.HttpURLConnection.userAgent
+ "\r\n\r\n";
byte b[];
try {
/*
* We really do want ASCII7 -- the http protocol doesn't change
* with locale.
*/
b = msg.getBytes("ASCII7");
} catch (UnsupportedEncodingException ignored) {
/*
* If ASCII7 isn't there, something serious is wrong, but
* Paranoia Is Good (tm)
*/
b = msg.getBytes();
}
out.write(b);
out.flush();
* Tell our tunnel where we want to CONNECT, and look for the
* right reply. Throw IOException if anything goes wrong.
*/
private static void doTunnelHandshake(Socket tunnel, String host, int port)
throws IOException
{
OutputStream out = tunnel.getOutputStream();
String msg = "CONNECT " + host + ":" + port + " HTTP/1.0\n"
+ "User-Agent: "
+ sun.net.www.protocol.http.HttpURLConnection.userAgent
+ "\r\n\r\n";
byte b[];
try {
/*
* We really do want ASCII7 -- the http protocol doesn't change
* with locale.
*/
b = msg.getBytes("ASCII7");
} catch (UnsupportedEncodingException ignored) {
/*
* If ASCII7 isn't there, something serious is wrong, but
* Paranoia Is Good (tm)
*/
b = msg.getBytes();
}
out.write(b);
out.flush();
/*
* We need to store the reply so we can create a detailed
* error message to the user.
*/
byte reply[] = new byte[200];
int replyLen = 0;
int newlinesSeen = 0;
boolean headerDone = false; /* Done on first newline */
* We need to store the reply so we can create a detailed
* error message to the user.
*/
byte reply[] = new byte[200];
int replyLen = 0;
int newlinesSeen = 0;
boolean headerDone = false; /* Done on first newline */
InputStream in =
tunnel.getInputStream();
boolean error = false;
boolean error = false;
while (newlinesSeen < 2)
{
int i = in.read();
if (i < 0) {
throw new IOException("Unexpected EOF from proxy");
}
if (i == '\n') {
headerDone = true;
++newlinesSeen;
} else if (i != '\r') {
newlinesSeen = 0;
if (!headerDone && replyLen < reply.length) {
reply[replyLen++] = (byte) i;
}
}
}
/*
* Converting the byte array to a string is slightly wasteful
* in the case where the connection was successful, but it's
* insignificant compared to the network overhead.
*/
String replyStr;
try {
replyStr = new String(reply, 0, replyLen, "ASCII7");
} catch (UnsupportedEncodingException ignored) {
replyStr = new String(reply, 0, replyLen);
}
int i = in.read();
if (i < 0) {
throw new IOException("Unexpected EOF from proxy");
}
if (i == '\n') {
headerDone = true;
++newlinesSeen;
} else if (i != '\r') {
newlinesSeen = 0;
if (!headerDone && replyLen < reply.length) {
reply[replyLen++] = (byte) i;
}
}
}
/*
* Converting the byte array to a string is slightly wasteful
* in the case where the connection was successful, but it's
* insignificant compared to the network overhead.
*/
String replyStr;
try {
replyStr = new String(reply, 0, replyLen, "ASCII7");
} catch (UnsupportedEncodingException ignored) {
replyStr = new String(reply, 0, replyLen);
}
/* We asked for HTTP/1.0, so we should get
that back */
if (!replyStr.startsWith("HTTP/1.0 200")) {
throw new IOException("Unable to tunnel through "
+ "proxy returns \"" + replyStr + "\"");
}
if (!replyStr.startsWith("HTTP/1.0 200")) {
throw new IOException("Unable to tunnel through "
+ "proxy returns \"" + replyStr + "\"");
}
/* tunneling Handshake was successful!
*/
}
}