Making multiple asynchronous HTTP 2.0 requests with okhttp3 - java

I am trying to use okhttp (3.4.1) to implement a HTTP 2.0 based client. Part of my requirement is to implement multiple asynchronous HTTP requests to different URLs with a callback to handle the response at a later time.
However, with my current implementation, I see that I cannot get all my asynchronous requests to use the same TCP connection unless I make a blocking HTTP request from my main thread at the beginning.
I understand that the enque() method used for asynchronous calls engages the dispatcher which seems to spawn a new thread for each of the requests.
Here is my code snippet:
OkHttpClient client = new OkHttpClient();
My async Get Request method looks as follows:
public void AsyncGet(String url) throws Exception {
Request request = new Request.Builder().url(url).build();
Call call = client.newCall(request);
call.enqueue(new Callback() {
#Override
public void onFailure(Call call, IOException e) {
e.printStackTrace();
}
#Override
public void onResponse(Call call, Response response) throws IOException {
if (!response.isSuccessful()) {
throw new IOException("Unexpected code " + response);
}
new Thread() {
#Override
public void run() {
/* Some code */
}
}.start();
}
});
}
My synchronous Get Request is as follows:
public Response SyncGet(String url) throws Exception {
Request request = new Request.Builder().url(url).build();
Call call = client.newCall(request);
Response response = call.execute();
if (!response.isSuccessful()) {
throw new IOException("Unexpected code " + response);
}
return response;
}
Making a call sequence like the following triggers 2 TCP connections to the server.
AsyncGet(Url);
AsyncGet(Url2);
However, a call sequence like the following makes use of the same TCP connection.
SyncGet(Url);
AsyncGet(Url);
AsyncGet(Url2);
I have not debugged this but, it looks like OkHttp forces us to make a blocking HTTP request on the main thread first to possibly obtain the TCP connection context and then share that with other threads? Or, am I missing something?

You can call async to and set sector to each call later on based on the sector you can distinguish the response of call. Try this code; I hope it will help you!
Create a separate class for api call:
public class RestApi {
protected RestApiResponse serverResponse;
protected Context c;
public RestApi(RestApiResponse serverResponse, Context c) {
this.serverResponse = serverResponse;
this.c = c;
}
public void postDataWithoutAuth(String url,String sector) {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url("http://publicobject.com/helloworld.txt")
.build();
client.newCall(request).enqueue(new Callback() {
#Override
public void onFailure(Call call, IOException e) {
Log.e("response", call.request().body().toString());
serverResponse.onErrorLoaded(call.request().body().toString(), sector);
}
#Override
public void onResponse(Call call, Response response) throws IOException {
serverResponse.onResponseLoaded(response, sector);
}
});
}
}
Then create an interface for callback
public interface RestApiResponse {
public void onResponseLoaded(Response response, String sector);
public void onErrorLoaded(String response, String sector);
}
and access it from your Activity like this
RestApi apicall = new RestApi(this, getBaseContext());
apicall.postDataWithoutAuthUrlEncoded("ur url","your sector");

Related

Android use OkHttpClient on main thread

In my main activity, i am opening a new activity like this:
someActivityResultLauncher.launch(myIntent);
In my secondary activity i am invoking a REST api with like this:
OkHttpClient client = new OkHttpClient();
RequestBody body = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), someRequest.toString());
Request request = new Request.Builder().url(myURL).post(body).build();
client.newCall(request).enqueue(new Callback() {
#Override
public void onFailure(#NonNull Call call, #NonNull IOException e) {
System.out.println(e);
}
#Override
public void onResponse(#NonNull Call call, #NonNull Response response) throws IOException {
System.out.println(response.body().string());
}
});
After that i close the secondary activity returning an object with data i retrieved from the REST api to the main activity.
The problem is that the HTTP call executes on the seperate thread and the main thread is executed before the HTTP call, which means the secondary activity returns the object before it is filled with data.
I tried execute() in order to wait for the HTTP response before returning to the main activity, but this generates an android.os.NetworkOnMainThreadException.
How can i force it to wait for a response before returning to the main activity?
You can use java.util.concurrent.CountDownLatch:
CountDownLatch latch = new CountDownLatch(1);
OkHttpClient client = new OkHttpClient();
RequestBody body = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), someRequest.toString());
Request request = new Request.Builder().url(myURL).post(body).build();
client.newCall(request).enqueue(new Callback() {
#Override
public void onFailure(#NonNull Call call, #NonNull IOException e) {
System.out.println(e);
//Add line here
latch.countDown();
}
#Override
public void onResponse(#NonNull Call call, #NonNull Response response) throws IOException {
System.out.println(response.body().string());
//Add line here
latch.countDown();
}
});
try {
//Await method will stop main thread until calling Latch.countDown();
latch.await(10, //Write here timeout you want
TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
//Do your stuff with UI here

How to mock rest-easy's asynchronous HTTP request?

Is there an offical way to mock a rest-easy asynchronous HTTP request?
The sample code:
#GET
#Path("test")
public void test(#Suspended final AsyncResponse response) {
Thread t = new Thread() {
#Override
public void run()
{
try {
Response jaxrs = Response.ok("basic").type(MediaType.APPLICATION_JSON).build();
response.resume(jaxrs);
}
catch (Exception e)
{
e.printStackTrace();
}
}
};
t.start();
}
I offen mock rest-easy's request this way:
#Test
public void test() throws Exception {
/**
* mock
*/
Dispatcher dispatcher = MockDispatcherFactory.createDispatcher();
dispatcher.getRegistry().addSingletonResource(action);
MockHttpRequest request = MockHttpRequest.get("/hello/test");
request.addFormHeader("X-FORWARDED-FOR", "122.122.122.122");
MockHttpResponse response = new MockHttpResponse();
/**
* call
*/
dispatcher.invoke(request, response);
/**
* verify
*/
System.out.println("receive content:"+response.getContentAsString());
}
BUT it dosn't work. I got a BadRequestException during the unit test.
What's the right way to mock a rest-easy asynchronous HTTP request?
By reading rest-easy's source code, I finally find a way to work around with asynchronous HTTP request :
#Test
public void test() throws Exception {
/**
* mock
*/
Dispatcher dispatcher = MockDispatcherFactory.createDispatcher();
dispatcher.getRegistry().addSingletonResource(action);
MockHttpRequest request = MockHttpRequest.get("/hello/test");
request.addFormHeader("X-FORWARDED-FOR", "122.122.122.122");
MockHttpResponse response = new MockHttpResponse();
// Add following two lines !!!
SynchronousExecutionContext synchronousExecutionContext = new SynchronousExecutionContext((SynchronousDispatcher)dispatcher, request, response );
request.setAsynchronousContext(synchronousExecutionContext);
/**
* call
*/
dispatcher.invoke(request, response);
/**
* verify
*/
System.out.println("receive content:"+response.getContentAsString());
}
The output of response is correct !!!

Return value from inner class in a method

I am building a Login system for an Android app. I am using OkHttp to connect to my server and get a JSON response.
I have defined a class with the login return data (right now just a true/false response based on whether the user exists in the database), and then written the code to connect to the server, as shown below:
class UserLogin {
boolean status;
public void setStatus(boolean status) {
this.status = status;
}
public boolean getStatus() {
return status;
}
}
public class ClientServerInterface {
OkHttpClient client = new OkHttpClient();
boolean login(Request request) {
final Gson gson = new Gson();
client.newCall(request).enqueue(new Callback() {
UserLogin login;
#Override
public void onFailure(Call call, IOException e) {
}
#Override
public void onResponse(Call call, Response response) throws IOException {
login = gson.fromJson(response.body().charStream(), UserLogin.class);
login.setStatus(login.status);
}
});
// need to return the boolean response (status) here
}
}
The code which passes the Request variable to the login method works perfectly. I want login to return a boolean response so that I can pass that to other methods in other classes.
However, because the UserLogin object is defined in the callback I can't access it in the parent method. I have made a getStatus method but not sure how to use it properly to get the status in the main login method.
The code which passes the Request variable to the login method works
perfectly. I want login to return a boolean response so that I can
pass that to other methods in other classes.
you can't. enqueue executes the code in Async way. You don't know when the callback is invoked. What you could do is to add the Callback as parameter to your login method. E.g.
boolean login(Request request, final Callback callback) {
and either pass it to enqueue,
client.newCall(request).enqueue(callback);
or call the callback manually. E.g.
#Override
public void onResponse(Call call, Response response) throws IOException {
if (callback != null) {
callback.onResponse(call, response);
}
}
in both cases the caller of login will receive the callback on the provided object and, accordingly to the content it receives, can decide wha actions undertake
You can do this using a SynchronousQueue:
final SynchronousQueue<Boolean> queue = new SynchronousQueue<>();
client.newCall(request).enqueue(new Callback() {
UserLogin login;
#Override
public void onFailure(Call call, IOException e) {
queue.put(false);
}
#Override
public void onResponse(Call call, Response response) throws IOException {
queue.put(true);
}
});
return queue.take();
Add loginStatus variable to class like below and one more to indicate login operation completion.
public class ClientServerInterface {
OkHttpClient client = new OkHttpClient();
private boolean loginStatus = false;
private boolean isLoginOperationDone = false;
boolean login(Request request) {
final Gson gson = new Gson();
client.newCall(request).enqueue(new Callback() {
UserLogin login;
#Override
public void onFailure(Call call, IOException e) {
}
#Override
public void onResponse(Call call, Response response) throws IOException {
login = gson.fromJson(response.body().charStream(), UserLogin.class);
loginStatus = login.setStatus(login.status);
isLoginOperationDone = true;
}
});
// need to return the boolean response (status) here
while( !isLoginOperationDone )
{
//not to do anything.
}
return loginStatus;
}
}
Note that this might be a little hacky but will do solve your problem.
The way to go is with AsyncTask. Override the doInBackground method to perform the http requests and get the result by overriding the onPostExecute method.
Read more here: http://developer.android.com/reference/android/os/AsyncTask.html
An even better way to go is to run a background Service for all your API calls.

Restlet Client :: how to add filters?

I suffering of a lack of documentation on the use of Restlet at the client side.
I am getting a resource on server via a ClientResource:
new ClientResource(url).get();
But the server can return an ETag header. To handle this I want to save the ETag when returned and send it back to the server when using the same url.
Currently I am doing it like this:
ClientResource clientResource = new ClientResource(url);
addEtag(url, clientResource); // add the cached ETag to the query if any
clientResource.get();
saveEtag(url, clientResource); // cache the ETag if any
I would like to do this using the Restlet framework. I am searching for days wihtout understanding the missing link.
I can extend an application, overwrite the createOutboundRoot() method and return a filter:
public class RestLetClient extends Application {
private Client client;
// instantiation of the client and other things here
#Override
public Restlet createOutboundRoot() {
return new Filter(getContext(), client){
#Override
protected int beforeHandle(Request request, Response response) {
addEtag(request);
return super.doHandle(request, response);
}
#Override
protected void afterHandle(Request request, Response response) {
saveEtag(request, reponse);
return super.afterHandle(request, response);
}
};
}
}
BUT how can I use this filtering around the Restlet client from my business code?
EDIT
The best I could get to work until now is this:
Request request = new Request(Method.GET, uri);
//the filter created in original post
filter.handle(request).getEntity();
This works but it is not integrated in the framework. What I am achieving to do is at the client side what is only documented for the server side. On the server you would do:
public class ServerApplication extends Application {
#Override
public Restlet createInboundRoot() {
Router router = new Router(getContext());
router.attach(GET_URL, GetResource.class);
return router;
}
}
and then start the server. The application will the be triggered on the reception of a GET request on the url.
What is the equivalent on the client side? How can I trigger a Client Application? If I have an Application running at the client side I can nicely add filters where they belong in a REST application
EDIT 2
When trying to run my client within an Application I get the error: The filter org.restlet.engine.application.RangeFilter#f372a7a was executed without a next Restlet attached to it.
Here is how I am getting the error. I have a class extending Application that is called from a JUnit test:
public class RestLetClient extends Application {
private final Client client;
Logger logger = LoggerFactory.getLogger(getClass());
public RestLetClient() {
this.client = new Client(Protocol.HTTP);
}
public Representation get(final String uri) throws Exception {
Request request = new Request(Method.GET, uri);
Response response = handle(request);
return response.getEntity();
}
#Override
public Restlet createOutboundRoot() {
return new Filter(getContext(), client) {
#Override
protected int beforeHandle(Request request, Response response) {
addEtagFilter(request);
return super.beforeHandle(request, response);
}
#Override
protected void afterHandle(Request request, Response response) {
saveEtagFilter(request, response);
super.afterHandle(request, response);
}
};
}
private void saveEtagFilter(Request request, Response response) {
logger.debug("saving etag");
}
private void addEtagFilter(Request request) {
logger.debug("adding etag");
}
}
and the unit with a single test method:
public class RestLetClientTest {
public static final String URL = "http://localhost:8123/resource";
private RestLetClient instance;
private Server server;
#Before
public void setUp() throws Exception {
server = new Server(Protocol.HTTP, 8123, new TestApplication());
server.start();
instance = new RestLetClient();
instance.start();
}
#After
public void tearDown() throws Exception {
instance.stop();
}
#Test
public void testGet() throws Exception {
Representation representation = instance.get(URL);
System.out.println(representation.getText());
}
private class TestApplication extends Application {
#Override
public Restlet createInboundRoot() {
return new Router().attach(RestLetClientTest.URL, GetResource.class);
}
}
private class GetResource extends ServerResource {
#Get
public Representation getResource() {
return new StringRepresentation("hello world");
}
}
}
What am I doing wrong?
I had a much nicer answer from a colleague. I post it here for the documentation.
The solution is to use a ClientResource, a Filter and a Client.
The Filter becomes the next() of the ClientResource and the Client the next() of the Filter.
public class ETagFilter extends Filter {
#Override
protected int beforeHandle(Request request, Response response) {
addEtag(request);
return super.beforeHandle(request, response);
}
#Override
protected void afterHandle(Request request, Response response) {
saveEtag(request, reponse);
super.afterHandle(request, response);
}
}
public class RestLetClient extends Application {
public Representation get(final String uri) throws Exception {
Client client = new Client(Protocol.HTTP);
ETagFilter eTagFilter = new ETagFilter();
clientResource = new ClientResource(uri);
clientResource.setNext(eTagFilter);
eTagFilter.setNext(client);
return clientResource.get(halMediaType);
}
}
For info. In my OP I was trying to transform code meant for server side into client side. That approach was wrong. My colleague pointed that the approach is much more like the use Apache HttpClient for similar needs
To have a client working you need to take the Application out of the picture since it is Server oriented according to the javadoc.
What you need is a Component, a ClientRouter and a custom ClientRoute.
Component manage connectors. A Restlet Client is a Connector.
ClientRouter dispatches to client connectors.
ClientRoute extends Filter allowing to add filters around your client handeling.
My solution:
The Component
public class RestLetComponent extends Component {
public RestLetComponent(Client client) {
getClients().add(client);
}
}
The ClientRouter
public class RestLetClientRouter extends ClientRouter {
public RestLetClientRouter(final Client client) {
super(new RestLetComponent(client));
ClientRoute clientRoute = new RestLetClientRoute(this, client);
//forcing to use only our custom route
getRoutes().clear();
getRoutes().add(clientRoute);
}
public Representation get(final String uri) throws Exception {
Request request = new Request(Method.GET, uri);
Response response = handle(request);
return response.getEntity();
}
}
And the custom ClientRoute that will add the filters
public class RestLetClientRoute extends ClientRoute {
Logger logger = LoggerFactory.getLogger(getClass());
public RestLetClientRoute(Router router, Client client) {
super(router, client);
}
//the filters
#Override
protected int beforeHandle(Request request, Response response) {
addEtagFilter(request);
return super.beforeHandle(request, response);
}
#Override
protected int doHandle(Request request, Response response) {
logger.debug("handling request: " + request.getMethod() + " - " + request.getResourceRef());
return super.doHandle(request, response);
}
#Override
protected void afterHandle(Request request, Response response) {
saveEtagFilter(request, response);
super.afterHandle(request, response);
}
private void saveEtagFilter(Request request, Response response) {
logger.debug("saving etag");
}
private void addEtagFilter(Request request) {
logger.debug("adding etag");
}
}
And last but not least, I apologize to the Restlet authors, the documentation is there. I was reading the Restlet in Action book but the answer is in the very well documented javadoc.

Handling redirect Response from a Restful or JAX-RS web service

A typical restful call from GWT (using RestyGWT) looks like this:
LoginService.Util.getService().login("FACEBOOK", new MethodCallback<Void>() {
#Override
public void onSuccess(Method method, Void response) {
}
#Override
public void onFailure(Method method, Throwable exception) {
}
});
Although the call might respond onSuccess, the redirect response is not processed. Which the server side redirect looks like this:
return Response.seeOther(new URI(url)).build();
What is the proper way of handling redirect from request either made through RequestBuilder or RestyGWT?
Check this tutorial, RequestBuilder, And try this,
// Send request to server and catch any errors.
RequestBuilder builder = new RequestBuilder(RequestBuilder.GET, url);
try {
Request request = builder.sendRequest(null, new RequestCallback() {
public void onError(Request request, Throwable exception) {
displayError("Couldn't retrieve JSON");
}
public void onResponseReceived(Request request, Response response) {
if (200 == response.getStatusCode()) {
updateTable(JsonUtils.safeEval(response.getText()));
} else {
displayError("Couldn't retrieve JSON (" + response.getStatusText() + ")");
}
}
});
} catch (RequestException e) {
displayError("Couldn't retrieve JSON");
}
Try this,
response.sendRedirect("http://10.62.229.57:8080/birt/");
I use for redirect response in Jax-rs web service.

Categories

Resources