Protocol Upgrade Mechanism in okHttp Interceptors - java

I need to upgrade my request from being served by HTTP1.1 to HTTP/2.
This is so far what I have tried.
public class H2cUpgradeRequestInterceptor implements Interceptor {
private static final Log logger = LogFactory.getLog(H2cUpgradeRequestInterceptor.class);
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Request upgradeRequest = request.newBuilder().addHeader("Connection", "Upgrade, HTTP2-Settings")
.addHeader("Upgrade", "h2c").addHeader("HTTP2-Settings", "AAMAAABkAARAAAAAAAIAAAAA").build();
Response upgradeResponse = chain.proceed(upgradeRequest);
if (upgradeResponse != null && upgradeResponse.code() == HttpStatus.SC_SWITCHING_PROTOCOLS) {
logger.debug("Switching Protocols success"); // Success. Got 101 in reply.
}
Response response = chain.proceed(request);
if (response.protocol() == Protocol.HTTP_2) { // This is returning HTTP1.1 as protocol
logger.debug("Used upgraded h2c protocol");
}
return response;
}
}
My server has h2c enabled. I am also receiving 101 Switching Protocols in my first chain.proceed() call. But the next proceed() always gives me HTTP1.1 as protocol even if switching protocols is successful. Is it right right way of achieving protocol upgrade? And, if yes, how can I make sure it uses HTTP2 after switching protocol is successful?

Closing this since, as per the docs
I need not send the request again.
If the Upgrade header field is received in a GET request and the
server decides to switch protocols, it first responds with a 101
(Switching Protocols) message in HTTP/1.1 and then immediately follows
that with the new protocol's equivalent of a response to a GET on the
target resource. This allows a connection to be upgraded to protocols
with the same semantics as HTTP without the latency cost of an
additional round trip.
Following back here

Related

How to follow-through on HTTP 303 status code when using HttpClient in Java 11 and later?

When using the java.net.http.HttpClient classes in Java 11 and later, how does one tell the client to follow through an HTTP 303 to get to the redirected page?
Here is an example. Wikipedia provides a REST URL for getting the summary of a random page of their content. That URL redirects to the URL of the randomly-chosen page. When running this code, I see the 303 when calling HttpResponse#toString. But I do not know how to tell the client class to follow along to the new URL.
HttpClient client = HttpClient.newHttpClient();
HttpRequest request =
HttpRequest
.newBuilder()
.uri( URI.create( "https://en.wikipedia.org/api/rest_v1/page/random/summary" ) )
.build();
try
{
HttpResponse < String > response = client.send( request , HttpResponse.BodyHandlers.ofString() );
System.out.println( "response = " + response ); // ⬅️ We can see the `303` status code.
String body = response.body();
System.out.println( "body = " + body );
}
catch ( IOException e )
{
e.printStackTrace();
}
catch ( InterruptedException e )
{
e.printStackTrace();
}
When run:
response = (GET https://en.wikipedia.org/api/rest_v1/page/random/summary) 303
body =
Problem
You're using HttpClient#newHttpClient(). The documentation of that method states:
Returns a new HttpClient with default settings.
Equivalent to newBuilder().build().
The default settings include: the "GET" request method, a preference of HTTP/2, a redirection policy of NEVER [emphasis added], the default proxy selector, and the default SSL context.
As emphasized, you are creating an HttpClient with a redirection policy of NEVER.
Solution
There are at least two solutions to your problem.
Automatically Follow Redirects
If you want to automatically follow redirects then you need to use HttpClient#newBuilder() (instead of #newHttpClient()) which allows you to configure the to-be-built client. Specifically, you need to call HttpClient.Builder#followRedirects(HttpClient.Redirect) with an appropriate redirect policy before building the client. For example:
HttpClient client =
HttpClient.newBuilder()
.followRedirects(HttpClient.Redirect.NORMAL) // follow redirects
.build();
The different redirect policies are specified by the HttpClient.Redirect enum:
Defines the automatic redirection policy.
The automatic redirection policy is checked whenever a 3XX response code is received. If redirection does not happen automatically, then the response, containing the 3XX response code, is returned, where it can be handled manually.
There are three constants: ALWAYS, NEVER, and NORMAL. The meaning of the first two is obvious from their names. The last one, NORMAL, behaves just like ALWAYS except it won't redirect from https URLs to http URLs.
Manually Follow Redirects
As noted in the documentation of HttpClient.Redirect you could instead manually follow a redirect. I'm not well versed in HTTP and how to properly handle all responses so I won't give an example here. But I believe, at a minimum, this requires you:
Check the status code of the response.
If the code indicates a redirect, grab the new URI from the response headers.
If the new URI is relative then resolve it against the request URI.
Send a new request.
Repeat 1-4 as needed.
Obviously configuring the HttpClient to automatically follow redirects is much easier (and less error-prone), but this approach would give you more control.
Please find below code where i was calling another api from my REST APi in java.
To note I am using java version 17. This will solve error code 303.
#GetMapping(value = "url/api/url")
private String methodName() throws IOException, InterruptedException {
var url = "api/url/"; // remote api url which you want to call
System.out.println(url);
var request = HttpRequest.newBuilder().GET().uri(URI.create(url)).setHeader("access-token-key", "accessTokenValue").build();
System.out.println(request);
var client = HttpClient.newBuilder().followRedirects(HttpClient.Redirect.NORMAL).build();
System.out.println(client);
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response);
System.out.println(response.body());
return response.body();
}

How to route error messages rippling up from an http outbound gateway on http status code?

I have a gateway with a custom error channel configured to receive error messages coming from an http outbound gateway deeper in the flow.
#MessagingGateway(errorChannel="invocationFailureChannel")
public interface Invocator {
#Gateway(requestChannel="invocationChannel")
public Boolean invokeService(Message<String> invocation);
}
When the outbound gateway fails to call it's designated endpoint, i end up with a MessageHandlingException on the error channel. It's got a cause which tells me what went wrong, but i want to route the message based on the status code rather than the exception object i'm using now to sort of be a substitute for the status (all exceptions subclassing IOException are non 500):
public ErrorType determineErrorType(Throwable cause) {
return (cause.getCause() instanceof IOException ?
ErrorType.DOWNSTREAM_UNAVAILABILITY :
ErrorType.INVALID_DATA);
}
It would be much cleaner if i actually routed on the status code though, coz in the future there might be other flows for other status codes. Plus i'm not even sure the condition i have in place covers all possible exceptions.
Thanks for the help.
EDIT:
Hey i tried to send a request to a non existent url and inspect the exception payload, but the cause is a ResourceAccessException/UnknownHostException rather than an HttpStatusCodeException.
Now there's a lot of intermediate components between the gateway and the eventual http outbound gateway, but that won't matter right? Here's the actual gateway configuration:
.handle(Http.outboundGateway(spelParser.parseExpression("headers." + HeaderKeys.TARGET_ENDPOINT))
.extractPayload(true)
.httpMethod(HttpMethod.POST)
.expectedResponseType(String.class)
.requestFactory(requestFactory())
.get()
, httpOutboundEndpointSpec())
.wireTap("outboundLoggerChannel")
.handle(new GenericHandler<String>() {
#Override
public Object handle(String reply, Map<String, Object> headers) {
System.out.println("Logging response");
return true;
}
})
When the outbound gateway returns any status other than 200, the exception ignores the wiretap after the gateway call and directly jumps up to the error channel configured on the #Gateway right? I don't really know what i'm doing wrong.
You need to show your error flow but, the MessagingException.cause() is most likely a HttpStatusCodeException (client - 4xx or server - 5xx) with a statusCode property.

Using JAX-RS (RESTEasy) as middleware: brokering a client's request to another server

I'm building a server using JAX-RS (RESTEasy) that will provide a REST interface to clients. The server will act as a broker between the client and another server. The other server, a 3rd-party server (JasperReports), also has a REST interface. I'd like to use JAX-RS to have my broker talk to that server. (My broker server adds authentication and other services.) So, there you have the three parties: client, broker-server, reports-server.
I see the workflow this way. The broker-server implements JAX-RS (server) to get the client's requests, repackage them, and pass them along to the reports-server, using JAX-RS (client). When the broker-server has obtained a report, I'd like to relay that back to the client. But, so far, I believe that's where things break down.
Here's some code:
// Server gets a request and passes it to its (internal) client, handler.
#GET
#Path("/jobs")
public Response fetchAllScheduledJobs() {
ReportScheduleHandler handler = new ReportScheduleHandler();
Response response = handler.fetchAllScheduledJobs();
return response;
}
Here is the handler sending that off to the reports-server...
public Response fetchAllScheduledJobs() {
Client client = ClientBuilder.newClient();
client.register(getBasicAuthentication());
Response response =
client.target(getReportsBaseUri())
.request()
.accept("application/json")
.get();
client.close();
return response;
}
So, in my (misguided) thinking, I'm thinking that the broker-server just returns the response back to the client, and all is well. But, as I said above, the client is getting nothing back. I'm using a REST developer's client ("Postman"), and here are the headers I'm getting back:
Cache-Control →private
Content-Length →0
Content-Type →application/json
Date →Mon, 14 Jul 2014 16:05:46 GMT
Expires →Wed, 31 Dec 1969 19:00:00 EST
P3P →CP="ALL"
Server →Apache-Coyote/1.1
Transfer-Encoding →chunked
(Copied and pasted, it looks just like that. I have no idea why Postman shows these arrows!)
Any idea what I'm missing here? Does the broker need to somehow unpack the Response it receives from its internal client and repackage that before returning it to the original client? Please ask any questions you need for clarification. Thanks!
Edit
Wait! Could it be that my Response has an input stream and that I need to read that and write out and output stream to the client -- or something like that?
You're closing your client, therefore not unwrapping the Response in an open client context. Unwrap your response, close the client, and return your unwrapped object.
edit:
Sorry, not your client. I believe in the Response object you've got a close() method.
Pretty much like this:
Client client = ClientFactory.newClient();
WebTarget target = client.target("http://foo.com/resource");
Response response = target.request().get();
String value = response.readEntity(String.class);
response.close(); // You should close connections!
return value;

HTTP Response Code: expected time between informational code (1xx) and non-informational code

I am using a HttpURLConnection instance in order to connect to a URL.
I am then calling the getResponseCode method in order to determine the connection state.
I am polling the connection with this method, until the returned response-code is NOT 1xx:
HttpURLConnection con = (HttpURLConnection)new URL(ref).openConnection();
int responseType = con.getResponseCode()/100;
while (responseType == 1)
{
Thread.sleep(10);
responseType = con.getResponseCode()/100;
}
switch (responseType)
{
...
}
What is the recommended time to sleep inside the loop? (here set to 10ms).
Is there any time period (min, max, average) defined in the HTTP standard?
The status code "100 continue" is usually sent by the server after it got the request header of the POST request, to signal the client that it can follow up with the POST body. This way the server can reject requests early based on the request header (e.g. authorization required) without wasting resources on the client. The final response will then be sent after the server received the request body from the client, which might be immediately or hours later, depending on how fast the clients sends the request body.

In a JAX-WS web service, how do I force a socket close after each Soap call is returned?

I am using com.sun.httpserver.HttpServer and javax.xml.ws.Endpoint to publish a JAX-WS web service, which was generated by running wsimport on an existing WSDL and implementing the genereated service interface. All this was part of JDK 1.6 (JAX-WS RI 2.1.6). My web service, running as a Java program without an additional Web container is supposed to simulate an existing SOAP service that was implemented using Apache Axis, running on Tomcat. Existing clients are likewise implemented using Apache Axis.
The problem I am having is that Soap operation calls from the clients to my JAX-WS service hang for a while, and then end with socket time-out on the client's side. This occurs even though the JAX-WS service is returning the SOAP response right away.
Inspecting the packets with tcpdump and wireshark, I noticed that with the existing Axis web service, after the SOAP response is sent from the server to the client, the server sends a "FIN ACK" packet, to which the clients responds with "FIN ACK". This concludes all packets pertinent to the SOAP operation. On the other hand, when running with the JAX-WS service, the server does not send a "FIN ACK" after the SOAP response is sent to the client. And the client seems to continue reading the socket input stream for it.
This leads me to believe that the JAX-WS web service stack is somehow keeping the socket open, even after the response to a SOAP call has been sent. And it appears that the client is expecting the socket to close at the end of a SOAP operation. Unfortunately, I cannot modify the client to behave differently.
Is there a way to configure the Endpoint or the HttpServer instances that I am using to publish the JAX-WS service to always close a socket after each SOAP operation?
I tried setting the system property http.keepAlive to false, but this did not seem to make any difference.
Thanks in advance for your help.
I found a way around this problem, but it's not very elegant. Essentially I get the HttpHandler object from the HttpContext after it's been created by the Endpoint.publish operation. And I call its handle() method from another HttpHandler class I wrote, which follows it up by sending a HttpHeader with "Connection" set to "close". Something like this:
...
HttpServer server = HttpServer.create(myInetSocketAddress, 5);
HttpContext context = server.createContext(mySoapPath);
Endpoint endpoint = Endpoint.create(mySoapImpl);
endpoint.publish(context);
MyHandler handler = new MyHandler(context.getHandler());
server.removeContext(mySoapPath);
server.createContext(mySoapPath, handler);
server.start();
...
private class MyHandler implements HttpHandler {
private HttpHandler h;
public MyHandler(HttpHandler in) {
h = in;
}
public void handle(HttpExchange t) throws IOException {
h.handle(t);
t.getResponseHeaders().set("Connection", "close");
t.sendResponseHeaders(200, 0);
t.close();
}
}
It works for my current needs, but it just seems like there's got to be a better way. Please do post if you have another solution. Thanks!
When I had this problem in Java 8 all requests from JAX-WS RI 2.2.9 had header: Connection: keep-alive
To force closing TCP connection after each request I had to change this header to Connection: close
I had done it by:
SoapService service = new SoapService(url);
SoapPort port = service.getSomePort();
Map<String, List<String>> requestHeaders = new HashMap<>();
requestHeaders.put("Connection", Collections.singletonList("close"));
BindingProvider bindingProvider = (BindingProvider)port;
bindingProvider.getRequestContext().put(MessageContext.HTTP_REQUEST_HEADERS, requestHeaders);
where:
public static final String HTTP_REQUEST_HEADERS = "javax.xml.ws.http.request.headers";

Categories