androidhttpproxyhttpclient

HTTP CONNECT method with Android


I want to extend an existing Android app which sets up a http connection to a remote device which it sends commands to and receives values from.

The feature consists of a tunneled connection via a custom proxy server that has been set up. I have the http header format given which should make the proxy server create and provide a tunnel for my app.

CONNECT <some id>.<target ip>:80 HTTP/1.1
Host: <proxy ip>:443
Authorization: basic <base64 encoded auth string>

# Here beginns the payload for the target device. It could be whatever.
GET / HTTP/1.1
Host: 127.0.0.1:80

The app uses the Apache HttpClient library to handle it's connections, and I would like to integrate with that. This is not mandatory, however.
The authorization is standard conform basic auth.

I have trouble implementing this because it is not clear to me how the HttpClient is intended to be used for such behaviour. There is no CONNECT method in the library, only GET, POST and so on. I figured this would then be managed by the proxy settings of the HttpClient instance.
The problem here is that the request line is not standard, since the CONNECT line contains an id which the custom proxy then would parse and interpret.

I now would like to know if there is any intended method to implement this using the Apache HttpClient and what it would look like with this sample data given, or if I have to implement my own method for this. And if so, which interface (there are a few that would sound reasonable to inherit from) it should implement.

Any explanation, snippet or pointer would be appreciated.

UPDATE: I now have a small snippet set up, without Android. Just plain Java and Apache HttpClient. I still think the Host mismatch in the request is a problem, since I can't manage to establish a connection.

final HttpClient httpClient = new DefaultHttpClient();

// Set proxy
HttpHost proxy = new HttpHost (deviceId + "." + "proxy ip", 443, "https");
httpClient.getParams().setParameter (ConnRoutePNames.DEFAULT_PROXY, proxy);

final HttpGet httpGet = new HttpGet("http://" + "target device ip");
httpGet.addHeader ("Authorization", "Basic" + 
    Base64.encodeBase64String((username + ":" + password).getBytes()));
// Trying to overvrite the host in the header containing the device Id
httpGet.setHeader("Host", "proxy ip");

System.out.println("Sending request..");
try {
    final HttpResponse httpResponse = httpClient.execute (httpGet);
    final InputStream inputStream = httpResponse.getEntity ().getContent ();
    final InputStreamReader inputStreamReader = 
        new InputStreamReader(inputStream, "ISO-8859-1");

    final BufferedReader bufferedReader = new BufferedReader(inputStreamReader);

    final StringBuilder stringBuilder = new StringBuilder ();
    String bufferedStrChunk = null;

    while ((bufferedStrChunk = bufferedReader.readLine ()) != null) {
        stringBuilder.append (bufferedStrChunk);
    }

    System.out.println("Received String: " + stringBuilder.toString());
}
catch (final ClientProtocolException exception) {
    System.out.println("ClientProtocolException");
    exception.printStackTrace();
}
catch (final IOException exception) {
    System.out.println("IOException");
    exception.
}

This looks fairly good to me in the way of "it could actually work". Anyways, I receive the following log and trace:

Sending request..
2015/03/03 13:16:16:199 CET [DEBUG] ClientParamsStack - 'http.route.default-proxy': https://"device id"."proxy ip":443
2015/03/03 13:16:16:207 CET [DEBUG] SingleClientConnManager - Get connection for route HttpRoute[{}->https://"device id"."proxy ip":443->http://"target device ip"]
2015/03/03 13:16:16:549 CET [DEBUG] ClientParamsStack - 'http.tcp.nodelay': true
2015/03/03 13:16:16:549 CET [DEBUG] ClientParamsStack - 'http.socket.buffer-size': 8192
2015/03/03 13:16:16:563 CET [DEBUG] DefaultClientConnection - Connection shut down
2015/03/03 13:16:16:563 CET [DEBUG] SingleClientConnManager - Releasing connection org.apache.http.impl.conn.SingleClientConnManager$ConnAdapter@bc6a08
Exception in thread "main" java.lang.IllegalStateException: Unable to establish route.
planned = HttpRoute[{}->https://"device id"."proxy ip":443->http://"target device ip"]
current = HttpRoute[{s}->https://"device id"."proxy ip":443->http://"target device ip"]
    at org.apache.http.impl.client.DefaultRequestDirector.establishRoute(DefaultRequestDirector.java:672)
    at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:385)
    at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:641)
    at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:576)
    at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:554)
    at run.main(run.java:71)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:134)

Any ideas what goes wrong?

UPDATE: As stated here, this might be due to a bug when redirecting. The fact that the target does redirect tells me, that I do not have reached the correct target, implying that the Host parameter may have not been overwritten.


Solution

  • In fact, this can't be done with the HttpClient at all, I tried at the wrong level. It works when done with a TcpSocket (or a SSLSocket). The custom CONNECT header can simply be assembled and sent like that:

    final Socket tcpSocket = SSLSocketFactory.getDefault().createSocket("host ip", 443);
    String connect = "custom CONNECT header";
    
    tcpSocket.getOutputStream().write((connect).getBytes());
    tcpSocket.getOutputStream().flush();
    

    The response from the server can then be read with a BufferedReader or whatever.