OkHttp async call replaces thread name in log - java

I use okhttp client to make async calls and log the request and response...
public void sendRequest(String requestJson) {
LOG.info("Sending payload: {}", requestJson);
Request httpRequest = buildRequest(requestJson, url);
okHttpClient.newCall(httpRequest).enqueue(new Callback() {
#Override
public void onResponse(Call call, Response response) {
LOG.info("Received response: {}", response.body().string());
// Do things with response...
}
}
}
This works fine. However, I have a problem with my logging. The request log shows up fine where pattern layout %t is replaced with the value I need i.e. a device Id in this case. However, because of the async call, that thread name is replaced with OkHttp http://localhost:8080/.... It's like the below;
[11:54:29:025] INFO [Device_778445] TestDeviceClient - Sending payload:...
[11:54:30:011] INFO [OkHttp http://localhost:8080/...] TestDeviceClient - Received response: ...
Is this possible to retain the %t/thread name value in async okhttp requests?
What I am looking for in short:
[11:54:29:025] INFO [Device_778445] TestDeviceClient - Sending payload:...
[11:54:30:011] INFO [Device_778445] TestDeviceClient - Received response: ...
NOTE:
I can work around this by getting the deviceId in a method argument and setting it back in onResponse method as the tread name, but I am not sure if this is preferable or if there is a better way...
Workaround like this;
public void sendRequest(String requestJson, String deviceId) {
LOG.info("Sending payload: {}", requestJson);
Request httpRequest = buildRequest(requestJson, url);
okHttpClient.newCall(httpRequest).enqueue(new Callback() {
#Override
public void onResponse(Call call, Response response) {
Thread.currentThread.setName(deviceId);
LOG.info("Received response: {}", response.body().string());
// Do things with response...
}
}
}

You have a conceptual misunderstanding.
Using asynchronous processing means using some other thread(s) to do a job while the thread that is passing the task is not getting blocked.
So it's absolutely fine that the okhttp thread have its own name.
Changing it to something other is also conceptually wrong.
Your problem is that you are using a thread name as a identifier to detect user's actions. Thread name is not a place for that.
What you may do is to pass additional device id as a parameter, and to remove from the logger pattern thread name if it is not important for you.
LOG.info("{}: Received response: {}", deviceId, response.body().string());

Related

Vertx event bus reply handler not getting invoked for a specific call

I'm running a local instance of vertx. Router redirects my requests to a worker verticle which has the following handler:
protected Handler<Message<JsonObject>> handler1() {
return msg -> {
final RequestBody req = mapper.readValue(msg.body().encode(), RequestBody.class);
processRequest(req, msg);
}
}
The processRequest function accepts the request body, makes calls to two external services, aggregates the responses, and returns back to the client.
private processRequest(RequestBody req, Message<JsonObject> msg) {
CompletableFuture<String> service1Response = getService1ResponseAsync(req); // Make async call to service 1
String service2Response = getService2ResponseSync(req); // Make sync call to service 2
ResponseBody response = aggregateResult(service1Response.join(), service2Response); // Tag1
msg.reply(mapper.writeValueAsString(response));
}
private CompletableFuture<String> getService1ResponseAsync(RequestBody req) {
CompletableFuture<String> result = new CompletableFuture();
// Below handler call makes GET call to service 1 using apache HTTP client and returns the response
vertx.eventBus().request("serviceVerticleAddr1", mapper.writeValueAsString(req), new DeliveryOptions(), reply -> { // Tag2
if (reply.succeeded())
result.complete(reply.result().body().toString());
else
result.completeExceptionally(result.cause());
}
}
When I hit the above API, my request times out. The thread from worker pool assigned for the execution of my request gets blocked at Tag1 forever. On debugging a little further, I found that the reply handler for the call in Tag2 is not getting invoked.
The handlers in service verticle (serviceVerticleAddr1) [i.e. Tag2] returns proper response for other APIs making use of it, but for me it's getting blocked. Can someone please help me identify the cause? Is there some kind of deadlock being formed when the thread calling vertx.eventBus().request [Tag2] starts to wait for future completion at service1Response.join() [Tag1]?
I think is blocked because of that sender is not being notified by the consumer that message has been processed.
I would recommend you to check inside the handler block registered for the consumer of serviceVerticleAddr1 address and ensure that is replying (notifying) to the sender that the requested message has successfully handled (or not). Consumer might look like
vertx.eventBus().consumer("serviceVerticleAddr1", message -> {
try{
doSomething();
message.reply("done");
} catch(){
message.fail(0, "fails");
}
});
This way, sender's async handler would be notified that consumer could process requested message

How to get message body from RoutingContext?

I created a vert.x HTTP server and I want to receive messages from clients in json format.
This is my code:
public class HttpServerVerticle extends AbstractVerticle {
#Override
public void start(Future<Void> future) {
Router router = Router.router(vertx)
.exceptionHandler(ex -> log.error("Error: " + ex));
router.route().handler(BodyHandler.create());
router.route("/getData").handler(this::getRequestHandler);
HttpServerOptions serverOptions = new HttpServerOptions();
vertx.createHttpServer(serverOptions)
.requestHandler(router::accept)
.listen(9539, result -> {
if (result.succeeded()) {
future.complete();
} else {
future.fail(result.cause());
}
});
}
private void getRequestHandler(RoutingContext request) {
JsonObject data = request.getBodyAsJson(); // In this place I had exception
...
request.response().end("{\"reply\":\"ok\"}");
}
}
When my handler was fire and I get some message from the client I get this exception:
io.vertx.core.json.DecodeException: Failed to decode:Unexpected end-of-input: expected close marker for Object (start marker at [Source: (String)"{"ClientRequest":["ok"],"Type":"Test"[truncated 678 chars]; line: 1, column: 1])
How can I avoid this exception and all time get the full message?
You should specify the HTTP method
router.route("/getData").handler(this::getRequestHandler);
is expecting an HTTP GET, but what you need is an HTTP POST:
router.post("/getData")
.consumes("application/json")
.handler(this::getRequestHandler);
will accept json in the body. The consumes is optional and enforces the client to only send json.
I would also suggest to name the route "/data" instead of "/getData" to conform with the concept of RESTful APIs.
Its possible the client is not sending a valid json.
This is a valid exception to handle in that case.
For logging the request, you can simply use request.getBodyAsString() before doing getBodyAsJson()

How to wait for the HTTP request to get completed in java

I am writing Java code where i am downloading the file from a server and i have to copy the file in my local system when the file download is complete.
I am using the below code:-
OkHttpClient.Builder builder = new OkHttpClient.Builder();
OkHttpClient client = builder.readTimeout(600, TimeUnit.SECONDS).writeTimeout(600, TimeUnit.SECONDS)
.connectTimeout(600, TimeUnit.SECONDS).build();
Request downloadRequest = new Request.Builder().url(url + fileName).addHeader("cache-control", "no-cache")
.addHeader("Authorization", token).build();
try {
Response downloadResponse = client.newCall(downloadRequest).execute();
System.out.println(downloadResponse.message());
System.out.println("got response from blob " + downloadResponse.isSuccessful() + " " + fileName);
return downloadResponse;
} catch (IOException e1) {
e1.printStackTrace();
}
return null;
}
But the request is made asynchronously and before the request is completed then response is returned which is incomplete. Can anyone please help me how can i make a request and wait till the response is completed.
Any help is highly appreciated!
Looks like you're returning the response object (not the response body content).
try something like:
return downloadedResponse.body().string()
My experience with HttpClient is such that the headers return first. The content doesn't necessarily come across the wire unless/until you consume it.
To make a synchronous GET request we need to build a Request object based on a URL and make a Call. After its execution we get back an instance of Response:
#Test
public void whenGetRequest_thenCorrect() throws IOException {
Request request = new Request.Builder()
.url(BASE_URL + "/date")
.build();
Call call = client.newCall(request);
Response response = call.execute();
assertThat(response.code(), equalTo(200));
}
You are already using synchronous method calling.
client.newCall(downloadRequest).execute();
This is a synchronous way of requesting URL. If you want to do the aysynchronous call you need to use enqueue method of Call class.
call.enqueue(new Callback() {
public void onResponse(Call call, Response response)
throws IOException {
// ...
}
public void onFailure(Call call, IOException e) {
fail();
}
});
I think problem is somewhere else. Kindly give more details why you are suspecting the current one as an asynchronous call so that we can do RCA.

Sending SOAP request with MessageCallback and MessageExtractor

When sending a SOAP request through Spring's WebServiceTemplate, I would like to provide my payload and perform operation on both the request and the response of it. This is because I need some details from the headers of the request/response.
In the Spring documentation I have found it's possible to alter the request with a WebServiceMessageCallback and the response with a WebServiceMessageExtractor.
The problem I'm having is that the WebServiceTemplate seems to choose between providing a payload and providing MessageCallback/MessageExtractor.
With that, I mean there are the following methods available:
marshalSendAndReceive(Object requestPayload, WebServiceMessageCallback requestCallback)
sendAndReceive(WebServiceMessageCallback requestCallback, WebServiceMessageExtractor<T> responseExtractor)
sendAndReceive(WebServiceMessageCallback requestCallback, WebServiceMessageCallback responseCallback)
But nothing to provide all three. So providing the payload, a WebServiceMessageCallback for operations on the request and a WebServiceMessageCallback/WebServiceMessageExtractor for operations on the response.
In the documentation they do provide the following snippet:
public void marshalWithSoapActionHeader(final Source s) {
final Transformer transformer = transformerFactory.newTransformer();
webServiceTemplate.sendAndReceive(new WebServiceMessageCallback() {
public void doWithMessage(WebServiceMessage message) {
transformer.transform(s, message.getPayloadResult());
},
new WebServiceMessageExtractor() {
public Object extractData(WebServiceMessage message) throws IOException
// do your own transforms with message.getPayloadResult()
// or message.getPayloadSource()
}
});
}
But passing your payload into an innerclass just to pass in a payload doesn't seem like clean code.
It doesn't really seem logical that you can provide a payload and callback for tampering the request, but not the response. Or tampering the request and response without providing a payload. How would I go about if I'd like to send a payload and access both the request and response?

Redo previous request when the first fails with 401 Unauthorized

I'm building an Android application that will fetch data from a REST API.
To make the requests I'm using Retrofit together with Otto.
For all my requests I add a RequestInterceptor that will add a header (Authorization) to all my requests.
In the RequestInterceptor I'm calling a method to the my current access_token then I populate the header to the request.
RequestInterceptor requestInterceptor = new RequestInterceptor() {
#Override
public void intercept(RequestFacade request) {
Token token = TokenPreferences.getToken();
request.addHeader("Authorization", token.getTokenType() + " " + token.getAccessToken());
}
};
RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint("https://example.com")
.setRequestInterceptor(requestInterceptor)
.build();
...
This works fine until the access_token has expired, then the request will fail with HTTP status 401 Unauthorized.
When this happens, I want to make a new request to get a new access_token from my refresh_token I got and then do the first request again.
I'm not really sure how to make that work.
Try a com.squareup.okhttp.Authenticator. As far as I can tell, this is preferable to com.squareup.okhttp.Interceptor (which you'll find suggested elsewhere), because it will only kick in for unauthorized requests. Here's a basic example:
public class ApiAuthenticator implements Authenticator {
#Override
public Request authenticate(Proxy proxy, Response response) throws IOException {
for (Challenge challenge : response.challenges()) {
if (challenge.getScheme().equals("Bearer")) {
String authToken = // Refresh the token here
if (authToken != null) {
return response.request().newBuilder()
.header("Authorization", "Bearer " + authToken)
.build();
}
}
}
return null;
}
#Override
public Request authenticateProxy(Proxy proxy, Response response) throws IOException {
return null;
}
}
You can attach it to your client like this:
okHttpClient.setAuthenticator(new ApiAuthenticator());
Be aware that if you're using Retrofit to refresh your token and the token is invalid, you might get unexpected, hard-to-debug behavior for 403 codes, but the solution is just to use a try/catch block.
try {
token = oauthService.refreshAccessToken(args);
} catch (RetrofitError error) {
// Do something that leads to login
}
Retry mechanizm is not implemented in Retrofit. It will be in v2. (Retrying requests manually)
You should implement retrying by recursive call from onFailure() callback as Deepack suggested.
I am facing the same issue and I currently don't know how to retry my request after it failed due to a Unauthorized error.
Although #Yuriy Ashaev mentioned that the retry mechanism should be part of the v2.0 Retrofit version, this should be dedicated only to 5xx errors (see Request Object draft description here) and attempting to retry a request that failed for another reason will raise an exception.
As of now, you can still add your own ErrorHandler to your RestAdapter and catch the Unauthorized error to fetch a refresh token. Here is a way to achieve this:
class RestErrorHandler implements retrofit.ErrorHandler {
#Override
public Throwable handleError(RetrofitError cause) {
Response r = cause.getResponse();
if (r != null && r.getStatus() == 401) {
Token token = mTokenProvider.fetchToken();
if (token != null) {
addHeader(HEADER_AUTHORIZATION, token.getToken_type() + " " + token.getAccess_token());
}
// Todo: Relaunch previous request
return cause;
}
return cause;
}
}
However I don't see any way to retry the failed request from this point other than in all your failure() request callback which will be called when returning the RetrofitError from your ErrorHandler...
I would really appreciate if someone could point us how you could retry the failed request from the ErrorHandler as the response only point to f-the request url.

Categories

Resources