I want to request to a url using okhttp in another thread (like IO thread) and get Response in the Android main thread, But I don't know how to create an Observable.
It's easier and safer to use Observable.defer() instead of Observable.create():
final OkHttpClient client = new OkHttpClient();
Observable.defer(new Func0<Observable<Response>>() {
#Override public Observable<Response> call() {
try {
Response response = client.newCall(new Request.Builder().url("your url").build()).execute();
return Observable.just(response);
} catch (IOException e) {
return Observable.error(e);
}
}
});
That way unsubscription and backpressure are handled for you. Here's a great post by Dan Lew about create() and defer().
If you wished to go the Observable.create() route then it should look more like in this library with isUnsubscribed() calls sprinkled everywhere. And I believe this still doesn't handle backpressure.
I realise this post is a bit old, but there's a new and more convenient way of doing this now
Observable.fromCallable {
client.newCall(Request.Builder().url("your url").build()).execute()
}
More info: https://artemzin.com/blog/rxjava-defer-execution-of-function-via-fromcallable/
I came late to the discussion but, if for some reason the code need to stream the response body, then defer or fromCallable won't do it. Instead one can employ the using operator.
Single.using(() -> okHttpClient.newCall(okRequest).execute(), // 1
response -> { // 2
...
return Single.just((Consumer<OutputStream>) fileOutput -> {
try (InputStream upstreamResponseStream = response.body().byteStream();
OutputStream fileOutput = responseBodyOutput) {
ByteStreams.copy(upstreamResponseStream, output);
}
});
},
Response::close, // 3
false) // 4
.subscribeOn(Schedulers.io()) // 5
.subscribe(copier -> copier.accept(...), // 6
throwable -> ...); // 7
The first lambda executes the response after upon subscription.
The second lambda creates the observable type, here with Single.just(...)
The third lambda disposes the response. With defer one could have used the try-with-resources style.
Set the eager toggle to false to make the disposer called after the terminal event, i.e. after the subscription consumer has been executed.
Of course make the thing happen on another threadpool
Here's the lambda that will consume the response body. Without eager set to false, the code will raise an IOException with reason 'closed' because the response will be already closed before entering this lambda.
The onError lambda should handle exceptions, especially the IOException that cannot be anymore caught with the using operator as it was possible with a try/catch with defer.
Okhttp3 with RxSingle background API call.
Disposable disposables = Single.fromCallable(() -> {
Log.e(TAG, "clearData: Thread[" + Thread.currentThread().getName() + "]");
OkHttpClient client = Util.getHttpClient();
Request request = new Request.Builder()
.addHeader("Authorization", "Bearer " + Util.getUserToken())
.url(BuildConfig.BASE_URL + ApiConstants.DELETE_FEEDS)
.build();
Response response = client.newCall(request).execute();
if(response.isSuccessful()) {
...
return ; // Any type
} else {
return ; // Any type
}
}).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe((result) -> {
Log.d(TAG, "api() completed");
});
compositeDisposable.add(disposables);
Related
I'm writing a web client for an API.
Every request should be accompanied with an access_token that may overdue.
What i want is to catch cases where request is failed due to overdue token, refresh it and retry the request. The issue is that I'm receiving the token as a Mono via the same webClient.
What I actually want is something like this:
private AtomicReference<Token> token
...
public Mono<ApiResponse> callApi() {
return Mono.justOrEmpty(token.get())
.switchIfEmpty(
Mono.defer(() -> auth()
.doOnNext(token::set)))
.flatMap(token -> performRequest(token))
.doOnError(e -> {
var newToken = auth().block() // I obviously can't block here
token.set(newToken);
})
.retry(5);
}
private Mono<Token> auth() {
// api call here that returns token
}
So what's the correct reactive way to update token and then retry request with it?
===UPD===
I managed to handle it, however, it looks a bit wanky.
Probably you have a better solution?
private AtomicReference<TokenHolder> tokenHolder = new AtomicReference<>();
private AtomicBoolean lastQueryFailed = new AtomicBoolean();
public Mono<ApiResponse> getApiResponse() {
return Mono.defer(this::getToken)
.flatMap(this::requestApi)
.doOnError((e) -> {
log.error(e)
lastQueryFailed.set(true);
})
.retryWhen(Retry.backoff(
3,
Duration.ofSeconds(2)
));
}
private Mono<Token> getToken() {
if (tokenHolder.get() == null) {
return auth()
.doOnNext(tokenHolder::set);
}
if (!lastQueryFailed.get()) {
return Mono.just(tokenHolder.get());
}
return auth()
.doOnNext(tokenHolder::set);
}
private Mono<Token> auth() {
// api call here that returns token
}
Mono.onErrorResume seems to be the operator you are looking for: It allows you to switch to a different Mono (in your case one based on auth()) when an error occurs:
public Mono<ApiResponse> getApiResponse() {
return Mono.justOrEmpty(token.get())
.switchIfEmpty(Mono.defer(() -> auth()
.doOnNext(token::set)))
.flatMap(this::requestApi)
.onErrorResume(SecurityException.class, error -> auth()
.doOnNext(token::set)
.flatMap(this::requestApi))
.retryWhen(Retry.backoff(3, Duration.ofSeconds(2)));
}
Note that I assumed you get a SecurityException if the token is overdue. You can change it to a different class, or even a Predicate<Throwable> to catch the overdue token exception. It is recommended to catch this specific error instead of all errors, else it will also refresh the token on other errors, like when the service is unreachable.
I want to use Java Http Client: java.net.http.HttpClient in asynchronous way.
When I receive http response (any http code) or timeout I want to execute some custom Java code. I struggle to complete the body of error handling.
CompletableFuture<HttpResponse<String>> response =
httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString())
.thenApplyAsync(body -> {
System.out.println("Request finished");
return body;
})
.exceptionallyAsync(exception -> {
// WHAT TO RETURN HERE ?
});
The method: exceptionallyAsync returns: CompletableFuture<T>, but I do not know how to complete this method.
Can you please help me to finish this method? I cannot find example in GitHub or Google.
You can use
.exceptionally(this::handlError)
If this is your response handler with a method like:
private Void handlError(Throwable ex) {
System.out.println(ex.toString());
return null;
}
I'm writing a kind of a wrapper around my request handlers to make them stream HTTP response. What I've got now is
Handler response wrapper:
#Component
public class ResponseBodyEmitterProcessor {
public ResponseBodyEmitter process(Supplier<?> supplier) {
ResponseBodyEmitter emitter = new ResponseBodyEmitter();
Executors.newSingleThreadExecutor()
.execute(() -> {
CompletableFuture<?> future = CompletableFuture.supplyAsync(supplier)
.thenAccept(result -> {
try {
emitter.send(result, MediaType.APPLICATION_JSON_UTF8);
emitter.complete();
} catch (IOException e) {
emitter.completeWithError(e);
}
});
while (!future.isDone()) {
try {
emitter.send("", MediaType.APPLICATION_JSON_UTF8);
Thread.sleep(500);
} catch (IOException | InterruptedException e) {
emitter.completeWithError(e);
}
}
});
return emitter;
}
}
Controller:
#RestController
#RequestMapping("/something")
public class MyController extends AbstractController {
#GetMapping(value = "/anything")
public ResponseEntity<ResponseBodyEmitter> getAnything() {
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_JSON_UTF8)
.body(process(() -> {
//long operation
}));
}
What I'm doing is just send empty string every half a second to keep a request alive. It's required for some tool to not shut it down by timeout. The problem here that I don't see any Content-Type header in a response. There's nothing at all, despite I return ResponseEntity from my controller method as it's said in this thread:
https://github.com/spring-projects/spring-framework/issues/18518
Looks like only TEXT_HTML media type is acceptable for streaming. Isn't there a way to stream json at all? I even manually mapped my dtos to json string using objectMapper, but still no luck. Tried with APPLICATION_JSON and APPLICATION_STREAM_JSON - doesn't work. Tried in different browsers - the same result.
I also manually set Content-Type header for ResponseEntity in the controller. Now there's the header in a response, but I'm not sure, if my data is actually streamed. In Chrome I can only see the result of my operation, not intermediate chars that I'm sending (changed them to "a" for test).
I checked the timing of request processing for two options:
Without emitter (just usual controller handler)
With emitter
As I understand Waiting status means: "Waiting for the first byte to appear". Seems like with emitter the first byte appears much earlier - this looks like what I need. Can I consider it as a proof that my solution works?
Maybe there's another way to do it in Spring? What I need is just to notify the browser that a request is still being processed by sending some useless data to it until the actual operation is done - then return the result.
Any help would be really appreciated.
Looking at the source of ResponseBodyEmitter#send it seems that the specified MediaType should have been set in the AbstractHttpMessageConverter#addDefaultHeaders method but only when no other contentType header is already present.
protected void addDefaultHeaders(HttpHeaders headers, T t, MediaType contentType) throws IOException{
if (headers.getContentType() == null) {
MediaType contentTypeToUse = contentType;
// ...
if (contentTypeToUse != null) {
headers.setContentType(contentTypeToUse);
}
}
// ...
}
I would suggest to set a break point there and have a look why the header is not applied. Maybe the #RestController sets a default header.
As a workaround you could try the set the contentType header via an annotation in the MVC controller.
E.g.
#RequestMapping(value = "/something", produces = MediaType.APPLICATION_JSON_UTF8)
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.
How to end chained requests in Rx Vert.X ?
HttpClient client = Vertx.vertx().createHttpClient();
HttpClientRequest request = client.request(HttpMethod.POST,
"someURL")
.putHeader("content-type", "application/x-www-form-urlencoded")
.putHeader("content-length", Integer.toString(jsonData.length())).write(jsonData);
request.toObservable().
//flatmap HttpClientResponse -> Observable<Buffer>
flatMap(httpClientResponse -> { //something
return httpClientResponse.toObservable();
}).
map(buffer -> {return buffer.toString()}).
//flatmap data -> Observable<HttpClientResponse>
flatMap(postData -> client.request(HttpMethod.POST,
someURL")
.putHeader("content-type", "application/x-www-form-urlencoded")
.putHeader("content-length", Integer.toString(postData.length())).write(postData).toObservable()).
//flatmap HttpClientResponse -> Observable<Buffer>
flatMap(httpClientResponse -> {
return httpClientResponse.toObservable();
})......//other operators
request.end();
Notice that I have .end() for the top request. How do I end request that is inside of the .flatmap ? Do I even need to end it ?
There are multiple ways to ensure to call request.end(). But I would dig into documentation of Vert.x or just open source code if there is one, to see if it does call end() for you. Otherwise one could be
final HttpClientRequest request = ...
request.toObservable()
.doOnUnsubscribe(new Action0() {
#Override
public void call() {
request.end();
}
});
I think you can do something like the following code.
The main idea is that you don't directly use the HttpClientRequest as obtained by the Vertx client. Instead you create another flowable that will invoke end() as soon as the first subscription is received.
Here, for instance, you can obtain the request through a pair custom methods: in this case request1() and request2(). They both use doOnSubscribe() to trigger the end() you need. Read its description on the ReactiveX page.
This examle uses vertx and reactivex, I hope you could use this set up.
import io.reactivex.Flowable;
import io.vertx.core.http.HttpMethod;
import io.vertx.reactivex.core.Vertx;
import io.vertx.reactivex.core.buffer.Buffer;
import io.vertx.reactivex.core.http.HttpClient;
import io.vertx.reactivex.core.http.HttpClientRequest;
import io.vertx.reactivex.core.http.HttpClientResponse;
import org.junit.Test;
public class StackOverflow {
#Test public void test(){
Buffer jsonData = Buffer.buffer("..."); // the json data.
HttpClient client = Vertx.vertx().createHttpClient(); // the vertx client.
request1(client)
.flatMap(httpClientResponse -> httpClientResponse.toFlowable())
.map(buffer -> buffer.toString())
.flatMap(postData -> request2(client, postData) )
.forEach( httpResponse -> {
// do something with returned data);
});
}
private Flowable<HttpClientResponse> request1(HttpClient client) {
HttpClientRequest request = client.request(HttpMethod.POST,"someURL");
return request
.toFlowable()
.doOnSubscribe( subscription -> request.end() );
}
private Flowable<HttpClientResponse> request2(HttpClient client, String postData) {
HttpClientRequest request = client.request(HttpMethod.POST,"someURL");
// do something with postData
return request
.toFlowable()
.doOnSubscribe( subscription -> request.end() );
}
}