Does OkHttp3 support HTTP2 via a HTTP forward proxy? - java

I am using OkHttp3 in my Android app to make HTTP/1.x requests to my backend servers via a forward proxy, like so:
List<Protocol> protos = new ArrayList<>();
protos.add(Protocol.HTTP_2);
protos.add(Protocol.HTTP_1_1);
InetSocketAddress proxyAddr = new InetSocketAddress("proxy.example.com", 80);
Proxy proxy = new Proxy(Proxy.Type.HTTP, proxyAddr);
OkHttpClient cli = new OkHttpClient.Builder()
.proxy(proxy)
.protocols(protos)
.build();
String url = "http://www.example.com/";
Request req = new Request.Builder().url(url).build();
Response res = cli.newCall(req).execute();
I would like to upgrade to HTTP2. However, it seems to me that OkHttp3 can make HTTP2 requests only if we are not going via a HTTP proxy. So, the above code wouldn't work.
In other words, OkHttp3 supports the first 3 cases below but not the fourth. HTTP/2 below means h2 (HTTP/2 over TLS) not h2c (HTTP/2 over clear text).
a) client <-- HTTP/1.x --> upstream server
b) client <-- HTTP/1.x --> forward proxy <-- HTTP/x --> upstream server
c) client <-- HTTP/2 --> upstream server
d) client <-- HTTP/2 --> forward proxy <-- HTTP/x --> upstream server
Does anyone confirm or deny my understanding? Thanks.

OkHttp will do HTTP/2 over an HTTP proxy. You’ll need HTTPS on the server since OkHttp doesn’t implement plaintext HTTP/2.

Jesse, I tried retrieving https://www.google.com/ with Proxy.Type.HTTP via nghttp2's forward proxy nghttpx, which supports HTTP2 over TLS. Unfortunately, TLS handshaking did not happen and the forward proxy reported the following error.
... tls: handshake libssl error: error:1407609B:SSL routines:SSL23_GET_CLIENT_HELLO:https proxy request
From what I gather, this error means that okhttp3's proxy code is not doing TLS handshaking with the forward proxy.
This makes me think that HTTP2 over TLS via a forward proxy is kind of pointless, because the forward proxy won't be able to add any value to encrypted requests - the forward proxy is just a pass-through pipe. In fact, I think TLS via any forward proxy is pointless. End-to-end HTTP2 over TLS does make sense, but via a forward proxy doesn't.

Related

GSS_S_CONTINUE_NEEDED with apache http client

I'm trying to setup up an http client to authenticate with spnego (apache http client 4.5.2)
Here's the code that setups the client
System.setProperty("javax.security.auth.useSubjectCredsOnly", "false");
final CredentialsProvider credsProvider = new SystemDefaultCredentialsProvider();
credsProvider.setCredentials(new AuthScope(null, -1, null), new KerberosCredentials(null));
final HttpClientBuilder builder = HttpClientBuilder.create();
builder.setDefaultCredentialsProvider(credsProvider);
final ApacheHttpClient4Engine engine = new ApacheHttpClient4Engine(builder.build());
final Client client = new ResteasyClientBuilder().httpEngine(engine).build();
On the server, I have a JAX-RS service which is sitting behind a waffle.servlet.NegotiateSecurityFilter to allow for ntlm or spnego authentication.
When I send my HTTP request from my chrome rest client, it works, authentication is successfully completed using negotiate.
When I try and use the apache http client however, it fails with the message in the log:
2018-03-05 16:47:16,048 DEBUG o.a.h.impl.auth.GGSSchemeBase main Authentication already attempted
2018-03-05 16:47:16,048 DEBUG o.a.h.i.auth.HttpAuthenticator main Authentication failed
After some further investigation I've discovered that the waffle filter is encoutingering GSS_S_CONTINUE_NEEDED and responding with an additional challenge which the apache client is simply ignoring and giving up on.
I've tested my apache client setup against another service which uses spnego (provided by a 3rd party) and it works properly, although doesn't employ the continue token.
I'm not sure if my system is setup incorrectly, and that the continue token shouldn't be happeneing or if the apache http client is improperly configured (or doesn't support continue tokens at all)
There's a very old ticket here https://issues.apache.org/jira/browse/HTTPCLIENT-1107 indicating that something was added to handle additional challenges, but from reading the code in org.apache.http.impl.auth.GGSSchemeBase.parseChallenge(CharArrayBuffer, int, int) it looks as though only the first challange is ever accepted.

HTTP/2 issue with Jetty - invalid preface

I'm experimenting with HTTP/2. I found some examples in this repo. Here's the constructor (boilerplate code skipped):
public WebServer(String path, HttpServlet servlet, int port) {
Server server = new Server(new QueuedThreadPool());
ServletContextHandler context = new ServletContextHandler(server, "/", ServletContextHandler.SESSIONS);
context.addServlet(new ServletHolder(servlet), path);
ServerConnector connector = new ServerConnector(server, 1,1, new HTTP2ServerConnectionFactory(new HttpConfiguration()));
connector.setPort(port);
server.addConnector(connector);
}
Then I start() the server, run java http2 client from the mentioned above repo and it connects with no error. But then I try to do the same with curl:
# curl --http2 http://localhost:8080
invalid_preface
And the browser (Chrome) says:
GET http://localhost:8080/ net::ERR_INVALID_HTTP_RESPONSE
What's whong with the server?
There is nothing wrong with the server.
You have configured Jetty to listen to HTTP/2 only (no HTTP/1.1), and then you have asked curl and Chrome to make a HTTP/1.1 request, which Jetty replied with invalid_preface because it cannot understand it.
Bear in mind that browsers will only speak HTTP/2 over TLS (i.e. SSL), while curl is able to also speak clear-text HTTP/2.
This example shows you how to setup a server that supports both clear-text HTTP/1.1 and HTTP/2.
This example shows you how to setup a server that supports both clear-text and TLS HTTP/1.1 and HTTP/2.
See here for how to use curl with HTTP/2.

How to get NodeJS to proxy Client Certificates like Jetty Proxy

I am writing a NodeJS proxy that will replace a Java Jetty Proxy. I am using node-http-proxy. The only piece remaining is to have the original client certificate passed along to the proxied server.
From my understanding, the Java Servlet specification requires that a Servlet container pull the Client Certificate from an HTTPS request and store that as an attribute on the HttpServletRequest.
I am not sure how the Servlet Container handles the Attributes when proxying the request to a new server. I presume that it is attaching them somehow either as headers or by some other means.
Does anyone know how those attributes (specifically the javax.servlet.request.X509Certificate) are passed on a proxied HTTPS request? And two, how do I achieve the same functionality using NodeJS.
In the event that is helps someone else out... The issue turned out to be the node module I was using (node-http-proxy) wasn't reusing the HTTP server connection certificates. That is, when attempting to create a connection with the proxy server, it was using a default (generated) certificate.
To properly connect with the proxy server, I had to pass the ca, pfx, and passphrase to the proxy connector.
const ca = ...
const pfx = ...
const passphrase = ...
// proxy connection
server.web(req, res, { ca: ca, pfx: pfx, passphrase: passphrase }, function(err) {});
After doing so, the Proxy server was able to pull and validate the certificate.

HTTP/2 Response Header in Java

Is there already a way to get the HTTP/2 response header in Java? I've tried to search in some libraries like URLConnection, Undertow or even Jetty, but without success.
P.S.: I'm using JDK 1.7 on my Java Project.
Other thing, the response header of the HTTP/2 is like this?
GET / HTTP/1.1
Host: server.example.com
Connection: Upgrade, HTTP2-Settings
Upgrade: h2c
HTTP2-Settings: <base64url encoding of HTTP/2 SETTINGS payload>
Is there any website who use this version already?
My main goal is to know if a website use the HTTP/2 version or not, is there a way to know this without need to read the response header in a Java Project?
Thanks.
HTTP/2 websites typically use TLS, because browsers only support HTTP/2 over TLS.
The method you are trying to use is the HTTP/1.1 upgrade to HTTP/2 which very few sites - if any at all - support.
Your snippet of code represent a request, not a response.
If the upgrade is successful, the HTTP/2 server sends back a 101 response in HTTP/1.1 format and the response to the GET request in HTTP/2 format. This is specified in RFC 7540, section 3.2.
In order to achieve what you want, i.e. to know if a website supports HTTP/2, you have to try to connect using HTTP/2 + TLS. If the connection succeeds, you know HTTP/2 is supported. If it fails, it's not supported.
For Jetty (disclaimer, I'm the HTTP/2 module maintainer), you have to use JDK 8, and the code will look like this:
// Setup.
HTTP2Client http2Client = new HTTP2Client();
SslContextFactory sslContextFactory = new SslContextFactory();
HttpClient httpClient = new HttpClient(new HttpClientTransportOverHTTP2(http2Client), sslContextFactory);
httpClient.start();
// Does webtide.com support HTTP/2 ?
ContentResponse response = httpClient.GET("https://webtide.com/");
// No errors, yes it supports HTTP/2 !
If you get a response without errors, you are on HTTP/2, otherwise the server does not support HTTP/2.
Remember that for that code to work, you have to use JDK 8 and the proper ALPN boot jar in the bootclasspath, as specified here.

Setting the virtual host port in Apache HttpClient

I use Apache Commons HttpClient 3.1 as a kind of reverse-proxy. This proxy server runs in a servlet container in port 8081, and proxies some of the requests to port 8080 on the same server. As the legacy server on port 8080 builds some absolute urls using the HTTP Host header, I want to explicitly set that header.
It is not possible to set the Host-header as you set other headers, as HttpClient automatically overrides the value you set. The only way I've found to change the Host-header is to set the virtual host:
HttpClient = ...
HttpMethod = ...
HostParams hostParams = new HostParams();
hostParams.setVirtualHost("localhost:8081");
hostConfiguration.setParams(hostParams);
hostConfiguration.setHost("localhost", 8080);
client.executeMethod(hostConfiguration, method);
But this doesn't work as it should because HttpClient seems to add the port it connects to, to the Host:
11:07:05.011 [qtp1813719644-21] DEBUG httpclient.wire.header - >> "Host: localhost:8081:8080[\r][\n]"
Is it any way I can fix this behaviour? If not, does Apache Httpclient 4.x behave differently?
As your problem is traversing a proxy (which in your case is a Servlet+HTTPClient), configure your client to use localhost:8080 as proxy and url as normal: http://localhost:8081/... :
hostConfiguration.setProxy("localhost", 8080);
hostConfiguration.setHost("localhost", 8081);

Categories

Resources