Spring Boot REST endpoint connection not being released - rest

I have created Spring boot (2.1.4.RELEASE) REST endpoint to GET some data from the server. When I call this endpoint from the browser, I see the JSON in the browser window but I notice that the the spinner in fav icon is going on for 60 seconds. When i look at the network tab, I never see the response section for the request. After 60 seconds, it says that it failed. When I walk through the code in debugger, I see that data is being returned from the controller and when I 'play' the rest of the stack everything completes (thread that is being assigned to serve the request) I am kind of puzzled what's causing this behavior.
#GetMapping(path="/recipes")
public ResponseEntity<Collection<HpManifest>> getRecipes() {
ResponseEntity<Collection<HpManifest>> response = hpService.getRecipes();
return response;
}
public ResponseEntity<Collection<HpManifest>> getRecipes() {
logger.info("Retrieving recipes from");
UriComponentsBuilder builder =
UriComponentsBuilder.fromHttpUrl(endpointManifests)
.queryParam("type", HpManifestType.RECIPE.getType());
logger.info("REST endpoint: " + builder.toUriString());
ResponseEntity<Collection<HpManifest>> recipes = restTemplate.exchange(
builder.toUriString(),
HttpMethod.GET, null, new ParameterizedTypeReference<Collection<HpManifest>>() {});
logger.info("recipes are:");
recipes.getBody().forEach(r -> logger.info(r.toString()));
return recipes;
}

I ran into a similar issue just the other day. In my case it turned out to be that recipes (returned from the restTemplate.exchange method) contained a Transfer-Encoding: chunked in the headers and then when you return recipes, your spring framework is probably also including a Content-Length header. The combination of these two headers in a response to a browser can cause issues because the browser thinks it's getting chunked data back, but in reality it is not. I suggest making a new ResponseEntity from your recipes variable along the lines of:
return ResponseEntity.status(recipes.getStatusCode()).body(response.getBody());
Alternatively you could maybe force your spring framework to return chunked data, but I think that is not the right way to go.

Related

Spring Cloud Gateway Routing Based On Content of the Request Body

I need to create a reverse proxy that takes incoming request and based on the content of the request body, route the request to specific URI.
This is for a routing micro service that acts like a reverse proxy and does routing based on some information from each request body. This means for each request I need to parse the request body and get the "username" field and then make a JDBC connection to fetch additional information from the database. Based on that information in database, it would finally redirect the request to the correct URI.
From what I have now, I have 2 blocking methods. The first one is the parsing for the request body, the other one is the JDBC connection to the database. I understand that I should not put any blocking calls inside the gateway filter. I just don't know what I should do in this case. I could have both operations running async but in the end I still need the information from database to do routing.
#Bean
public RouteLocator apiLocator(RouteLocatorBuilder builder, XmlMapper xmlMapper) {
return builder.routes()
.route(r -> r
.path("/test")
.and()
.readBody(String.class, s -> true) // Read the request body, data will be cached as cachedRequestBodyObject
.filters(f -> f.filter(new GatewayFilter() {
#Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
try {
// The following method is blocking and should not be put here
xmlMapper.readValue((String) exchange.getAttribute("cachedRequestBodyObject"), Map.class);
} catch (Exception e) {
//TODO
}
return chain.filter(exchange);
}
}))
.uri("http://localhost:8080"))
.build();
}
The above example only includes the blocking parsing as my request body is XML based. My IDE is warning me of having a blocking call there which I really appreciate.
Any help is greatly appreciated. Thank you everyone!
After some research, Mono.fromCallable seems to be a good fit. I then asked the same question directly under the github repo, it turns out that using a servlet app may be better. For anyone who is interested to see what I came up with, please take a look here https://github.com/spring-cloud/spring-cloud-gateway/issues/1229

Apache Camel - Getting a list of files from FTP as a result of a GET request

As the title suggests I'm trying to get a list of files from an FTP directory to send as a response of a GET request.
I have current rest route implementation:
rest().get("/files")
.produces(MediaType.APPLICATION_JSON_VALUE)
.route()
.routeId("restRouteId")
.to("direct:getAllFiles");
On the other side of the direct route I have the following routes:
from("direct:getAllFiles")
.routeId("filesDirectId")
.to("controlbus:route" +
"?action=start" +
"&routeId=ftpRoute");
from([ftpurl])
.noAutoStartup()
.routeId("ftpRoute")
.aggregate(constant(true), new FileAggregationStrategy())
.completionFromBatchConsumer()
.process(filesProcessor)
.to("controlbus:route" +
"?action=stop" +
"&routeId=" + BESTANDEN_ROUTE_ID);
The issue at hand is that with this method the request does not wait for the complete process to finish, it almost instantly returns an empty response with StatusCode 200.
I've tried multiple solutions but they all fail in either of two ways: either the request gets a response even though the route hasn't finished yet OR the route gets stuck waiting for inflight exchanges at some point and waits for the 5 minute timeout to continue.
Thanks in advance for your advice and/or help!
Note: I'm working in a Spring Boot application (2.0.5) and Apache Camel (2.22.1).
I think the problem here is that your two routes are not connected. You are using the control bus to start the second route but it doesn't return the value back to the first route - it just completes, as you've noted.
What I think you need (I've not tested it) is something like:
from("direct:getAllFiles")
.routeId("filesDirectId")
.pollEnrich( [ftpurl], new FileAggregationStrategy() )
.process( filesProcessor );
as this will synchronously consume your ftp consumer, and do the post processing and return the values to your rest route.
With the help of #Screwtape's answer i managed to get it working for my specific issue. A few adjustments were needed, here is a list of what you need:
Add the option "sendEmptyMessageWhenIdle=true" to the ftp url
In the AggregationStrategy add an if (exchange == null) clause
In the clause set a property "finished" to true
Wrap the pollEnrich with a loopDoWhile that checks the finished property
In its entirety it looks something like:
from("direct:ftp")
.routeId("ftpRoute")
.loopDoWhile(!finished)
.pollEnrich("ftpurl...&sendEmptyMessageWhenIdle=true", new FileAggregationStrategy())
.choice()
.when(finished)
.process(filesProcessor)
.end()
.end();
In the AggregationStrategy the aggregate method looks something like:
#Override
public Exchange aggregate(Exchange currentExchange, Exchange newExchange) {
if (currentExchange == null)
return init(newExchange);
else {
if (newExchange == null) {
currentExchange.setProperty("finished", true);
return currentExchange;
}
return update(currentExchange, newExchange);
}
}

gwt getModuleBaseURL on same server creates an java.lang.UnsatisfiedLinkError

I have a single GWT web-application integrated with Spring MVC. I have a working Controller which works perfectly and is unit tested to accept POSTed JSON data and returns JSON data.
From within the same application, to avoid any SOP cross-site domain issues, I am making a call with a RequestBuilder to POST the same json data, and I expect JSON data back.
I created a basic java class that should make a call, but I have a few issues. This running web-app is running in hosted mode in Jetty in Eclipse. I have done a ton of research on how GWT should make a call to an existing web-service with a simple HTP request.
The first issue from my unit test is that:
String baseUrl = GWT.getModuleBaseURL();
is not working and I get:
java.lang.UnsatisfiedLinkError: com.google.gwt.core.client.impl.Impl.getModuleBaseURL()Ljava/lang/String;
I think I know what the correct URL should be, so when I hard-code the url correctly, and execute this code:
String url = getRootUrl() + "rest/pendingInvoices/searchAndCount";
System.out.println("PendingInvoiceDataSource: getData: url=" + url);
// Send request to server and catch any errors.
RequestBuilder builder = new RequestBuilder(RequestBuilder.POST, url);
builder.setHeader("Accept", "application/json");
builder.setHeader("Content-type", "application/json");
// builder.setRequestData(requestData);
try
{
System.out.println("PendingInvoiceDataSource: SEND REQUEST: getData: requestData=" + requestData);
Request request = builder.sendRequest(requestData, new RequestCallback()
{
public void onError(Request request, Throwable exception)
{
System.out.println("Couldn't retrieve JSON");
}
#Override
public void onResponseReceived(Request request, Response response)
{
if (200 == response.getStatusCode())
{
// updateTable(JsonUtils.safeEval(response.getText()));
System.out.println("data=" + response.getText());
}
else
{
System.out.println("Couldn't retrieve JSON (" + response.getStatusText() + ")");
}
}
});
}
catch (RequestException e)
{
System.out.println("Couldn't retrieve JSON");
}
I get this error on he sendRequest:
java.lang.UnsatisfiedLinkError: com.google.gwt.xhr.client.XMLHttpRequest.create()Lcom/google/gwt/xhr/client/XMLHttpRequest;
at com.google.gwt.xhr.client.XMLHttpRequest.create(Native Method)
at com.google.gwt.http.client.RequestBuilder.doSend(RequestBuilder.java:383)
at com.google.gwt.http.client.RequestBuilder.sendRequest(RequestBuilder.java:261)
I think this might be a quick fix, or maybe something small I have forgotten, so I'll try some more testing and see what I can find.
Everything client in GWT is only meant to run on the client-side: compiled to JS or in DevMode.
Only shared, server and vm classes can be used on the server-side.
If you want to get your server URL, use the appropriate methods from the HttpServletRequest (or whatever it is in Spring MVC as it seems from how you tagged the question that's what you're using).
If you want to make HTTP requests from your server, use an HttpURLConnection, or OkHttp, Apache Http Components or similar libraries, or even Spring's own HTTP client API.
Actually, it was only the Unit Test that was having a problem. Once I actually tried to run the deployed code, it all worked. I'll still try h GWTTestCase as suggested in order to get the unit test working.
But everything worked correctly when I ran the deployed code.
I also changed: String baseUrl = GWT.getModuleBaseURL();
which gave me:
http://localhost:8888/MyProject
To: String baseUrl = GWT.getHostPageBaseURL();
which gave me:
http://localhost:8888/
and that all worked.

POST to ASP.NET WebAPI using Fiddler2

I have a class that models exactly the entity I have in the database. I have a stored procedure that takes in parameters for a new row and returns all the settings in the table which in turn populates my repository. I am able to see the results of GET, PUT and DELETE in the List of type Setting that is in memory. I am noticing first that even when I close Visual Studio and reopen and run the project, sometimes, the List is still in the state it was before. It is not repopulating from the database so I'm not sure why that is first of all... Secondly, I can't seem to get POST to work from Fiddler unlike the other HTTP verbs. I DO see the values from Fiddler show up in the code below but I get the error: Invalid URI: The format of the URI could not be determined. I get the same error if I pass an ID or not.
Here is what I put into Fiddler:
POST localhost:54852/api/settings
Request Headers
User-Agent: Fiddler
Content-type: application/x-www-form-urlencoded
Host: localhost:54852
Content-Length: 149
Request Body
ID=0&Category=Dried%20Goods&Sub_Category=Other&UnitSize=99&UnitOfMeasureID=999&Facings=true&Quantity=true&EverydayPrice=999.99&PromotionPrice=111.11
PostSetting function within my SettingsController
public HttpResponseMessage PostSetting(Setting item)
{
item = repository.Add(item);
var response = new HttpResponseMessage<Setting>(item) { StatusCode = HttpStatusCode.Created };
string uri = Url.Route("DefaultApi", new { id = item.ID });
response.Headers.Location = new Uri(uri);
return response;
}
Should I create a new procedure that gets the MAXID from the database and use that as the NEW ID in the line above where a new ID is created?
You need to create a JSON representation of the Setting class or item that you are wanting to test with use Fiddler (now a Telerik product) and use the Composer tab.
Next you will want to perform a POST to the following URL:
http://[your base url]/api/settings
and pass the JSON formatted setting class.
You can see an example of this here: ASP.NET Web API - Scott Hanselman
Here is a short video showing how to achieve it easily
get and post to webapi from fiddler

gwt: making remote calls fails - sop?

I am writing some interface for users to collect data and send to a server. I went for GWT for various reasons.
Now, when I try to call my server:
String url = "http://127.0.0.1:3000/data/collection.xml";
RequestBuilder builder = new RequestBuilder(RequestBuilder.POST, URL.encode(url));
Request request = builder.sendRequest(data, new RequestCallback() {
public void onResponseReceived(Request request, Response response) {
if (200 == response.getStatusCode()) {
result.setText("SUCCESS!");
} else {
result.setText("ERROR with code: " + response.getStatusCode());
My server gets the request (a POST with some data) but I get ERROR with code: 0 (!) all the time. I guess this has to do with SOP. I read lots about this SOP but I am even more confused now. I tried to follow this tutorial but that's using a different approach (I managed to adapt it to issue GET calls only, but return objects are always null).
Can anyone point me into the right direction? thanks
You can not call any service from another server because of SOP. What you can do, you can use your original server as proxy to other servers.. I would recommend you to read this tutorial.