Can Elasticsearch stream the SearchResponse? - java

I have a rest application that can export some report data from Elasticsearch. It is easy to do with the Java API:
SearchResponse response = getClient()
.prepareSearch("my_index_name")
.setQuery(QueryBuilders.someQuery())
.addAggregation(AggregationBuilders.someAggregation())
.get();
The problem starts with the big responses. Using this code snippet, the response is read to build the SearchResponse object in memory. In my case, the response does not fits in memory.
Paging cannot help because we often need to return the full data and Aggregations do not support paging yet.
I know that I can use the Elasticsearch REST API to read the response as stream, but manually build the request it is cumbersome. I really want something like this:
// my dream API
InputStream response = getClient()
.prepareSearch("my_index_name")
.setQuery(QueryBuilders.someQuery())
.addAggregation(AggregationBuilders.someAggregation())
.getStream();
So, can the Elasticsearch Java API stream the SearchResponse?

A proposal for streaming results does exist but it doesn't seem to have picked up steam so far and was closed (for now).
There's a way to do it with XContentBuilder but that still requires the whole response to be in memory before being sent.
It might not be what you want, but that's the closest thing that I know which could fulfill your need. Worth giving it a try.

I believe there is no way to obtain an InputStream from the Java API (but I might be wrong). I also think there is no way to directly obtain an InputStream in Jest (a REST-based Elasticsearch Java API).
You mention that it is cumbersome to create the search request to the _search endpoint yourself: if you're referring to building the actual json query, I just would like to point out that once you have a SearchSourceBuilder, you can call toString() on it to get a fully working json representation of your query.
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.query(this.getQuery())
.from(this.getFrom())
.size(this.getSize())
.fetchSource(this.getSource(), null);
this.getSort().forEach(sourceBuilder::sort);
sourceBuilder.toString() // the json representation

Related

Sending a json string as query parameter via spring web client

I am trying to use the code below to send across a json string as a query param using Spring WebClient.
var client = WebClient.builder()
.baseUrl(baseurl)
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.build()
client.post().uri(uriBuilder -> uriBuilder.path("/api/SomeRequest")
.queryParam("response", responseJsonStr).build()
).retrieve().bodyToMono(ReponseObj.class).block()
I am seeing a error
java.lang.IllegalArgumentException: Not enough variables available to expand questionKey.
From the searching I have done, I can tell the issue is around the the Json String I am supplying as a query parameter(It has '{' and '}'). I think this might have to do with URI encoding. But looking at the details around encoding , I am not sure what the best way to approach this is.
NOTE: I can't make changes to the server that is expecting the Json string as a query param(it later decodes the json after extracting the query param).
Looks like you know that it's a really bad idea to send a json string as query param and not as post body, so I will not go into it any furhter.
You were right. You have to encode the query parameter on the client side to make it work. You can simply encode the param like this:
.queryParam("response", URLEncoder.encode(responseJsonStr, StandardCharsets.UTF_8))
Just make sure that you use the same character set as the server side.

Sending GET or POST request with Groovy

I want to use HttpURLConnection class in Groovy to send GET and POST (with Jsonbody) request to an api. But what can I tell you. With HttpURLConnection it is soo difficult. I do not know how to use it. There is not even a send method. It looks like when you call getResponseCode() this method sends a request. Then you have to use InputStream and for POST you even have to use OutputStream. Oh my god. For what ?? I am used to handy libraries like Jersey Client. But this HttpURLConnection is just a nightmare. I have to use it because I have jenkinsfile and in my pipeline I need to upload something. And that is possible with calling an REST Api. What do you think about HttpURLConnection ? Does someone know a good website with a GET Request and a POST Request with a body.
I think this is what you are trying to do https://www.baeldung.com/httpurlconnection-post
Making it Groovy should be pretty trivial.
If you are doing an HTTP request and you want to use vanilla Java or Groovy, then I would recommend using HttpClient and HttpRequest.Builder; it's somewhat fluid. And you supply a BodyHandler object, which can be used to get the content however you like it (String, JSON Object, whatever).

Sending Java/Kotlin objects to RSocket route

I've set up #MessageMapping endpoint using Spring's RSocket support and tested it with RSocketRequester. This flow is working perfectly since Spring handles bunch of low-level stuff for us.
Now, I'd like to have some sort of UI to show whatever data I'm working with and do any UI updates on that data through mentioned #MessageMapping endpoint. The problem is that I have no idea how to send full Java/Kotlin object (as JSON) to my endpoint using either rsocket-java or rsocket-kotlin libraries. Since I have to send both data and metadata and metadata is encoded as ByteBuf I had to encode JSON to ByteBuf, but it comes to endpoint all messed up and it cannot be deserialized.
I've also tried sending raw string value and it also comes to endpoint all messed up (bunch of non-UTF chars). I've tried both APPLICATION_JSON and TEXT_PLAIN for data mime type, but it didn't work for either.
Also, I guess whatever I'm doing wrong is not strictly related to Java/Kotlin libraries, but can be applied to any other existing RSocket library (Go, Rust, JS, etc.)
If you are using an rsocket-kotlin client (not building on spring-boot) then you can copy the code from https://github.com/rsocket/rsocket-cli/blob/master/src/main/kotlin/io/rsocket/cli/Main.kt
It builds metadata for the route with
this.route != null -> {
CompositeMetadata(RoutingMetadata(route!!)).toPacket().readBytes()
}
I guess I've figured it out.
I've scratched that binary encoded metadata and borrowed what I found on the internet for RSocket in JavaScript and it worked :)
So, the solution is to use routing metadata mime type (I was using composite since example I found was using it) and the payload then can be created like this:
val json = "{}"
val route = "myawesomeroute"
DefaultPayload.create(<json>, "${route.length.toChar()}$route")

Specify raw query string when using WebTarget

I'm working with a large existing project, which utilises javax.ws.rs.client.WebTarget to talk to RESTful API. The API is developed by a third party and I have no control over it. I need to make a request in the following format:
https://end.point.url/endpoint/id?data
Unfortunately, I can't figure out how to specify such request using WebTarget. I tried using path("endpoint/id?data"), but this gets converted into endpoint/id%3Fdata and I get 404 back. I tried using queryParam specifying empty value, which gets me endpoint/id?data= - which results in error required parameter data missing.
What other option is there? Replacing WebTarget with something else isn't feasible, as it is all over the large project.
First, related question: Url encoding issue with Jersey Client
After quite a bit of research, it seems that the only way to do it is to specify the entire uri when the WebTarget is created, like so:
Client client = ClientBuilder.newClient().register(authFeature);
WebTarget webTarget = client.target("https://end.point.url/endpoint/id?data")

REST call in Java

I have a few questions about a specific REST call I'm making in JAVA. I'm quite the novice, so I've cobbled this together from several sources. The call itself looks like this:
String src = AaRestCall.subTrackingNum(trackingNum);
The Rest call class looks like this:
public class AaRestCall {
public static String subTrackingNum (Sting trackingNum) throws IOException {
URL url = new URL("https://.../rest/" + trackingNum);
String query = "{'TRACKINGNUM': trackingNum}";
//make connection
URLConnection urlc = url.openConnection();
//use post mode
urlc.setDoOutput(true);
urlc.setAllowUserInteraction(false);
//send query
PrintStream ps = new PrintStream(urlc.getOutputStream());
ps.print(query);
ps.close();
//get result
BufferedReader br = new BufferedReader(new InputStreamReader(urlc
.getInputStream()));
StringBuilder sb = new StringBuilder();
String line = null;
while ((line=br.readLine())!=null) {
sb.append(line);
}
br.close();
return sb.toString();
}
}
Now, I have a few questions on top of the what is wrong with this in general.
1) If this rest call is returning a JSON object, is that going to get screwed up by going to a String?
2) What's the best way to parse out the JSON that is returning?
3) I'm not really certain how to format the query field. I assume that's supposed to be documented in the REST API?
Thanks in advance.
REST is a pattern applied on top of HTTP. From your questions, it seems to me that you first need to understand how HTTP (and chatty socket protocols in general) works and what the Java API offers for deal with it.
You can use whatever Json library out there to parse the HTTP response body (provided it's a 200 OK, that you need to check for, and also watch out for HTTP redirects!), but it's not how things are usually built.
If the service exposes a real RESTful interface (opposed to a simpler HTTP+JSON) you'll need to use four HTTP verbs, and URLConnection doesn't let you do so. Plus, you'll likely want to add headers for authentication, or maybe cookies (which in fact are just HTTP headers, but are still worth to be considered separately). So my suggestion is building the client-side part of the service with the HttpClient from Apache commons, or maybe some JAX-RS library with client support (for example Apache CXF). In that way you'll have full control of the communication while also getting nicer abstractions to work with, instead of consuming the InputStream provided by your URLConnection and manually serializing/deserializing parameters/responses.
Regarding the bit about how to format the query field, again you first need to grasp the basics of HTTP. Anyway, the definite answer depends on the remote service implementation, but you'll face four options:
The query string in the service URL
A form-encoded body of your HTTP request
A multipart body of your HTTP request (similar to the former, but the different MIME type is enough to give some headache) - this is often used in HTTP+JSON services that also have a website, and the same URL can be used for uploading a form that contains a file input
A service-defined (for example application/json, or application/xml) encoding for your HTTP body (again, it's really the same as the previous two points, but the different MIME encoding means that you'll have to use a different API)
Oh my. There are a couple of areas where you can improve on this code. I'm not even going to point out the errors since I'd like you to replace the HTTP calls with a HTTP client library. I'm also unaware of the spec required by your API so getting you to use the POST or GET methods properly at this level of abstraction will take more work.
1) If this rest call is returning a JSON object, is that going to get
screwed up by going to a String?
No, but marshalling that json into an obect is your job. A library like google gson can help.
2) What's the best way to parse out the JSON that is returning?
I like to use gson like I mentioned above, but you can use another marshal/unmarhal library.
3) I'm not really certain how to format the query field. I assume
that's supposed to be documented in the REST API?
Yes. Take a look at the documentation and come up with java objects that mirror the json structure. You can then parse them with the following code.
gson.fromJson(json, MyStructure.class);
Http client
Please take a look at writing your HTTP client using a library like apache HTTP client which will make your job much easier.
Testing
Since you seem to be new to this, I'd also suggest you take a look at a tool like Postman which can help you test your API calls if you suspect that the code you've written is faulty.
I think that you should use a REST client library instead of writing your own, unless it is for educational purposes - then by all means go nuts!
The REST service will respond to your call with a HTTP response, the payload may and may not be formatted as a JSON string. If it is, I suggest that you use a JSON parsing library to convert that String into a Java representation.
And yes, you will have to resort to the particular REST API:s documentation for details.
P.S. The java URL class is broken, use URI instead.

Categories

Resources