Spring Boot: efficiently get data from REST API - java

I have a Spring Boot application that (among other things) gets some data from a third party JSON API (secured with OAuth), processes the result and presents it to the user. The application receives approx. 1 request each second.
Unfortunately this process is very slow at the moment (and in many cases even ends with a 503 error) and I am looking for some idea to improve the implementation. (by the way: the third party API itself does not seem to be the bottleneck as a instance of my app running on my local machine using the exact same API response very fast at the same time that the deploy instance takes very long).
For the API call I use the Apache HTTP library - or more specifically the Async HTTP Client:
this.httpClientAsync = HttpAsyncClients.custom()
.setDefaultCredentialsProvider(credsProvider) //for forward proxy
.build();
And the actual call to the API is this:
updateToken(); //get or update OAuth Token
HttpGet httpget = new HttpGet(URL);
httpget.addHeader("Authorization", "Bearer " + accessToken);
Future<HttpResponse> f = this.httpClientAsync.execute(httpget, callback);
Do you have any suggestion on how to improve the implementation?
To be honest, I don't even have an idea where the bottleneck is at the moment. Any idea on how to find out about that?
Thanks for your hints!
One more thing/update:
the Spring Controller looks something like this:
#RequestMapping(value = "/api/v1/api_data")
public DeferredResult<ResponseEntity<Map>> getAPIData() throws IOException, InterruptedException {
DeferredResult<ResponseEntity<Map>> res = new DeferredResult<>();
triggerAPICall(new FutureCallback() {
public void completed(Object o) {
(...)
res.setResult(...);
}
(...)
}
return res;
}
Furthermore, I was originally not using the async version of the HTTP client, but the blocking version. This then even slowed down the rest of the application...

Related

Assign a different proxy to thread - Java

Hi Guys I am making a bot which can use a an other REST API to create a user but API only supports one user at a time and I dont want to change it. So I am using Multithreading to call the API multiple times, but I want to use proxies in it. So like different proxies for different threads, and all the threads use HttpClient and I tried its #proxy method and gave it the ip and port of the proxy but when I tried to call the API it returned a null response and I tried without the proxy and it did return a valid response.
So is there any other way to assign an proxy to a thread
My Code
HttpClient client = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_1_1)
.followRedirects(HttpClient.Redirect.NORMAL)
//THIS LINE BELOW IS HOW I USED PROXIES
.proxy(ProxySelector.of(new InetSocketAddress("proxy.example.com", 80)))
.connectTimeout(Duration.ofSeconds(30))
.build();
HttpRequest discordAccNoCaptcha = HttpRequest.newBuilder()
.uri(URI.create("https://myapiserver.com/api/v9/auth/register"))
.timeout(Duration.ofMinutes(2))
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
String response = client.send(discordAccNoCaptcha, HttpResponse.BodyHandlers.ofString()).body(); /* This is null when using ProxySelector and valid response when using no ProxySelector */

How can I make a PATCH request (with SSL) in Java?

I need my Java application to make a PATCH request to a web server using SSL.
I have tried the following:
public String patchForm(FormDataMultiPart f, Map<String,String> headers) {
Entity<FormDataMultiPart> entity = Entity.entity(f, f.getMediaType());
webTarget.property(HttpUrlConnectorProvider.SET_METHOD_WORKAROUND, true);
Builder request = webTarget.request();
if (headers != null){
for (String key : headers.keySet()){
request = request.header(key, headers.get(key));
}
}
Response result = request.method("PATCH", entity);
return result.readEntity(String.class);
}
Where Entity is javax.ws.rs.client.Entity and webTarget is a javax.ws.rs.client.WebTarget.
However, when I make the request, the server interprets it as a POST request and gives me the wrong response.
What could be the cause of the problem? Is there any way to fix it?
Thank you in advance.
For a Patch you need to set which url you are hitting to be secure.
For example
Your webTarget will have
webTarget.path("https://secureUrlWichWillForceSsl.com");
if it starts with http:// it will not be secure and no SSL.
After a lot of trial and error, doing and undoing, adding _HttpMethod=PATCH to the query string and then removing it because it turned out not to be necessary, especially adding a missing break to a switch close which was causing the patchForm function to not be called at all (yes, that was a stupid oversight which cost me hours of work), and then fixing the errors which kept cropping up, I have finally arrived at something which works.
The code which finally worked was the following:
public String patchForm(FormDataMultiPart f, Map<String,String> headers) {
Entity<FormDataMultiPart> entity = Entity.entity(f, f.getMediaType());
webTarget.register(MultiPartFeature.class);
webTarget.property(HttpUrlConnectorProvider.SET_METHOD_WORKAROUND, true);
Builder request = webTarget.request();
headers.put("X-HTTP-Method-Override", "PATCH");
if (headers != null){
for (String key : headers.keySet()){
request = request.header(key, headers.get(key));
}
}
Response result = request.patch(entity);
return result.readEntity(String.class);
}
Note that the Builder class has a built-in patch method. I was using an old library which did not have it. And yet, the method workaround is still necessary because otherwise I get an exception since the PATCH method is non-standard.
Also note that, when submitting multipart forms, it is important to register the Multipart feature. I have read that it is possible to register it either on the client by overriding the configureClient method (when writing a custom client) or on the web target. Doing it on the web target suited my needs better, but you can do it in whatever way works best for you.

How to increase Spring WebClient concurrent requests limit?

I have 2 microservices. First one is simply echo service which returns after 3 seconds delay. Second one calls first over spring webclient. When I simulate 501 concurrent users to request second microservice, first 500 users get response in 3 seconds and the last one gets response in 6 seconds. Means spring webclient supports only 500 concurrent connections by default. Corresponding value is configurable in apache client. But I can't find how to configure it in spring webclient.
Similar situation with WebSocket, it supports only 256 concurrent connections. How to configure it?
Tests are done with jMeter. If I test first microservice directly - there is no limit.
public TestController(WebClient.Builder webClientBuilder) {
this.webClient = webClientBuilder.baseUrl("http://localhost:8081").build();
}
#GetMapping("/test1")
public Mono<String> test1() {
return webClient
.get().uri("/test/HTTP")
.retrieve().bodyToMono(String.class);
}
Thanks Mark, solution from you link works.
Either this:
HttpClient httpClient = HttpClient.create(ConnectionProvider
.elastic("myConnectionProvider"));
this.webClient = webClientBuilder.clientConnector(new ReactorClientHttpConnector(httpClient))
.baseUrl("http://localhost:8081").build();
Or that:
HttpClient httpClient = HttpClient.create(ConnectionProvider
.fixed("myConnectionProvider", 1000));
this.webClient = webClientBuilder.clientConnector(new ReactorClientHttpConnector(httpClient))
.baseUrl("http://localhost:8081").build();

Deserialize Google Web Kit Response (Java) and Java Servlets

I have a monolithic legacy application that I need to read and submit data to. It's using Google Web Kit and Java Servlets.
I have access to the source code, but I'm new to both Servlets and GWT.
I'm trying to encapsulate a rest client in my project that can communicate with GET/POST rest calls to the legacy server.
I've been able to send a POST request using Postman, and then used Reactive Spring 5.0 framework to sending that request.
When I try to deserialize the response, I'm running into a ton of errors.
How would I deserialize this payload?
7|0|7|http://localhost:8080/testproject/
|29F4EA1240F157649C12466F01F46F60|
com.test.client.GreetingService|greetServer|java.lang.String|
myInput1|myInput2|1|2|3|4|2|5|5|6|7|
I've searched all day, and followed a few blogs like these:
https://docs.google.com/document/d/1eG0YocsYYbNAtivkLtcaiEE5IOF5u4LUol8-LL0TIKU/edit#
https://blog.gdssecurity.com/labs/2009/10/8/gwt-rpc-in-a-nutshell.html
I'm not sure code wise how I can serialize them into my own object for my new service.
static WebClient webClient = WebClient.create();
public static void main(String[] args) {
Mono<String> body = Mono.just("7|0|7|http://localhost:8080/testproject/|29F4EA1240F157649C12466F01F46F60|com.test.client.GreetingService|greetServer|java.lang.String|myInput1|myInput2|1|2|3|4|2|5|5|6|7|");
Mono<String> response = webClient.post()
.uri("http://localhost:8080/testproject/")
.header("Content-Type", "text/x-gwt-rpc;charset=UTF-8")
.header("X-GWT-Module-Base", "http://localhost:8080/testproject/")
.header("X-GWT-Permutation", "29F4EA1240F157649C12466F01F46F60")
.cookie("JSESSIONID", "2BCEBF12GE2C3A0335F5012812A73638")
.body(body, String.class)
.retrieve()
.bodyToMono(String.class);
String unBlocked = response.block();
System.out.println(unBlocked);
//OK[1,1,["java.lang.Integer/3438228391"],0,2]
try {
ServerSerializationStreamReader streamReader = new ServerSerializationStreamReader(
Thread.currentThread().getContextClassLoader(), null);
streamReader.prepareToRead(unBlocked);
System.out.println(streamReader.readObject());
} catch ( Exception e) {
e.printStackTrace();
}
}
Error:
com.google.gwt.user.client.rpc.IncompatibleRemoteServiceException: This application is out of date, please click the refresh button on your browser. ( Malformed or old RPC message received - expecting version between 5 and 7 )
I've tried every version of GWT because of the malformed RCP message.
Also, I tried to stick it into a string, which I'm sure is failing on its own.
You are trying to decode the server response with the code meant decode the client request. At present they use a different format for historical reasons - note how the response starts with "//OK", but the request has the version/flags/stringcount "7|0|7" beginning.
In at least a small part this is because when a client calls a server, it needs to describe what version it is speaking and where the server should find the file listing the set of classes that the client expects are allowed to be serialized. When the server responds, since the client already told it about the typed that can be serialized, it doesn't need to tell the client the same thing again.
Reading the com.google.gwt.user.client.rpc.impl.ClientSerializationStreamReader class and its docs will show the response format and how it can be decoded into objects. There is presently no server-side code that I'm aware of that is intended to do this job, but could probably be written with fairly little difficulty, just some persistence.

Accessing OneNote API from Java

I am interested in writing a Java application that can access my OneNote notebooks via the OneNote API. I am not sure how to gain access to that API from within Java. Can anybody point me to an example of how to get started here? I use Eclipse as my development environment.
This as straightforward process.
The 3 steps would be:
1) create a OneNote application on the OneNote developper's page. More info here https://dev.onedrive.com/app-registration.htm. This is a one time action.
2) your java application should then provide an authentification mechanism and a tolken-refresh mechanism.
See this post for more info on the authentification mechanism part : Getting a OneNote token with Java. This post is about the OAuth 2.0 flow 'Authorization code grant flow'. More info here https://msdn.microsoft.com/en-us/library/hh243647.aspx#flows
3) your java application calls adhoc API Rest methods to retreive the needed informations.
Example to retrieve all your notebooks (using OkHttp for Http requests):
private final static String NOTEBOOKS_ENDPOINT = "https://www.onenote.com/api/v1.0/me/notes/notebooks";
public Notebooks readAllNoteBooks() {
try {
if (client == null)
client = new OkHttpClient();
Request request = createOneNoteRequest(a_valid_tolken, NOTEBOOKS_ENDPOINT);
Response response = client.newCall(request).execute();
JsonObject content = UrlHelper.parseResponse(response);
System.out.println(content);
return Notebooks.build(content.get("value"));
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
public static Request createOneNoteRequest(String mAccessToken, String url) {
Request.Builder reqBuilder = new Request.Builder();
reqBuilder.url(url);
reqBuilder.header("Authorization", "Bearer " + mAccessToken);
return reqBuilder.build();
}
NoteBooks and NoteBook are 2 tiny classes matching the key attributes from the OneNote objects.
Microsoft has provided REST apis for accessing One note functionalities like creating and accessing notes. See OneNote Rest API reference.
singrass,
In addition to the above replies, the Android OneNote API sample may also help you. There is no OneNote application class that you can create (unless you want to create your own). You simply call the API through the HttpClient. If you are unfamiliar on how to call REST APIs in Java in general, this thread may help you.
-- James

Categories