Gateway Filter: connection prematurely closed - spring

With connecting the DataBuffer-flux to the filter chain ..
#Component
public class GlobalPayloadFilter implements GlobalFilter {
#Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
HttpMethod method = exchange.getRequest().getMethod();
if (method != HttpMethod.POST) {
return chain.filter(exchange);
}
return exchange.getRequest().getBody().doOnNext(buffer -> {
/* access buffer */
}).then(chain.filter(exchange));
}
}
.. the request will time out. The log then states, that ..
The connection observed an error
reactor.netty.http.client.PrematureCloseException: Connection prematurely closed BEFORE response
It looks like this type of connection leads to a blocking thread.
Does anyone may help?

Related

How to create spring cloud gateway filter with synchronous http request

I'm creating a GateWayFilter to authenticate a request with a ticket.
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//If you want to build a "pre" filter you need to manipulate the
//request before calling chain.filter
ServerHttpRequest serverHttpRequest = exchange.getRequest();
ServerHttpRequest.Builder builder = serverHttpRequest.mutate();
//use builder to manipulate the request
List<String> tickets = serverHttpRequest.getQueryParams().get(TICKET);
if (CollectionUtils.isEmpty(tickets)) {
return onError(exchange);
}
String ticket = tickets.get(0);
// todo use MONO?
// todo add cache
final ApiCaller apiCaller;
try {
apiCaller = HttpRequestUtil.getObject(
"http://localhost:9001/authapi/authentication/ticket/" + ticket, ApiCaller.class);
} catch (IOException e) {
log.warn("failed authenticate ticket: {}", ticket, e);
return onError(exchange);
}
if (apiCaller == null || StringUtils.isBlank(apiCaller.getLipCode())) {
log.info("not valid ticket: {}", ticket);
return onError(exchange);
}
ApiCallerUtil.addApiCaller(builder, apiCaller);
return chain.filter(exchange.mutate().request(builder.build()).build());
}
the problem is I use a blocking http request in the method,
Question 1: Is there any disadvantage of using the blocking HTTP request?
Question 2:How can I chang to reactor http request to get the ApiCaller Information?

Spring Webflux: Read request-body in ServerWebExchangeMatcher

I have to create an application with just one endpoint where users can log in and do their other operational stuff. Users should be able to log in with a request body like:
{
"login": {
"token": "12345"
}
}
So every request with a body like this should be allowed via .permitAll() by the WebFilterChain.
The chain is configured like this:
#Bean
public SecurityWebFilterChain chain(ServerHttpSecurity http) {
return http.authorizeExchange(exchanges -> {
exchanges.matchers(new LoginRequestMatcher())
.permitAll()
.pathMatchers("api/v1*")
.hasRole("USER");
})
.csrf()
.disable()
.build();
}
The LoginRequestMatcher analyzes the body of the request and returns a Match if the pattern is ok. So far, this works quite fine.
My problem is that later in the Controller, the request body can not be accessed any more, so I'm currently trying to put it into a cache or the context to be able to access it later on.
Here is my current implementation of the LoginRequestMatcher:
public class LoginRequestMatcher implements ServerWebExchangeMatcher {
private static final Pattern loginRequestPattern = Pattern.compile("...loginPattern");
#Override
public Mono<MatchResult> matches(final ServerWebExchange exchange) {
return exchange.getRequest().getBody()
.map(dataBuffer -> {
byte[] byteArray=new byte[dataBuffer.readableByteCount()];
dataBuffer.read(byteArray);
DataBufferUtils.release(dataBuffer);
return byteArray;
}).defaultIfEmpty(new byte[0])
.map(bytes -> {
ServerHttpRequestDecorator decorator = initDecorator(exchange, bytes);
exchange.mutate().request(decorator).build();
Mono.just(decorator).contextWrite(ctx -> ctx.put("decorator",decorator))
return decorator;
}
)
// Rest of the code checks for Regex in the body and works fine.
.next();
Now, when I try to get the cached request-body after the RequestMatcher is processed (f.e. in a WebFilter), the context does not contain an object with the key "decorator".
#Configuration
#ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
public class HttpRequestBodyCachingFilter implements WebFilter {
#Override
public Mono<Void> filter(final ServerWebExchange exchange, final WebFilterChain chain) {
final HttpMethod method = exchange.getRequest().getMethod();
// Nothing to cache for GET and DELETE
if (method==null || HttpMethod.GET==method || HttpMethod.DELETE==method) {
return chain.filter(exchange);
}
// Get decorator from context
return Mono.just(exchange)
//ctx.get("decorator") throws Exception here, because the key does not exist.
.flatMap(s -> Mono.deferContextual(ctx -> ctx.get("decorator")))
.map(decorator ->
(ServerHttpRequestDecorator) decorator)
.flatMap(decorator -> chain.filter(exchange.mutate().request(decorator).build()));
}
}
How can I make the body readable after accessing it in inside a ServerWebExchangeMatcher?

Volley doesn't cache post request

I fetch data from server with following method:
public void process(final String url){
this.url=url;
GsonRequest<T> request=new GsonRequest<>(url, responseType, requestData, new Response.Listener<T>() {
#Override
public void onResponse(T response) {
//handle response here
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
//handle error here
}
});
request.setRetryPolicy(new DefaultRetryPolicy(
0,
DefaultRetryPolicy.DEFAULT_MAX_RETRIES,
DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
request.setShouldCache(true);
queue.add(request);
}
Some of the requests fetch signficant amount of data.I'd like Volley to cache data in some cases.However when I do one of these heavy request and then turn network off and then repeat my request Volley throws
java.net.UnknownHostException: Unable to resolve host "...": No address associated with hostname.
Is there something I can do to make Volley cache responses?
The problem was in server code - it didn't allow caching

Closing connection in GET request using Jersey Client 2.22.1

I am using Jersey client for REST calls from Java code:
<dependency>
<groupId>org.glassfish.jersey.core</groupId>
<artifactId>jersey-client</artifactId>
<version>2.22.1</version>
</dependency>
In my GET request,
javax.ws.rs.client.Invocation.Builder builder = ClientBuilder.newClient().target(url).request();
builder.get().readEntity(String.class);
the client will be closed automatically after calling readEntity(String.class).
If I use,
builder.get(String.class);
I get the same output.
Is the connection closed automatically or do I need to close it manually in this case?
Short answer
Consider the following code:
Client client = ClientBuilder.newClient();
String result = client.target(url).request().get(String.class);
Under the hood, Jersey invokes Response#readEntity(Class<T>) if the request has succeeded and the connection will be closed for you. So the connection doesn't need to be closed manually in this situation.
Now consider the following code:
Client client = ClientBuilder.newClient();
Response response = client.target(url).request().get();
For this situation, you need to invoke Response#close() to close the connection. Or invoke Response#readEntity(Class<T>) to make Jersey close the connection for you.
Long answer
As stated in the documentation, if you don't read the entity, then you need to close the response manually by invoking Response#close().
For more details, have a look at Jersey's documentation about how to close connections:
5.7. Closing connections
The underlying connections are opened for each request and closed
after the response is received and entity is processed (entity is
read). See the following example:
final WebTarget target = ... some web target
Response response = target.path("resource").request().get();
System.out.println("Connection is still open.");
System.out.println("string response: " + response.readEntity(String.class));
System.out.println("Now the connection is closed.");
If you don't read the entity, then you need to close the response
manually by response.close().
Also if the entity is read into an
InputStream (by response.readEntity(InputStream.class)), the
connection stays open until you finish reading from the InputStream.
In that case, the InputStream or the Response should be closed
manually at the end of reading from InputStream.
Additionally, have a look at JerseyInvocation source. The most important parts are quoted below.
In the translate(ClientResponse, RequestScope, Class<T>) method you'll see that response.readEntity(Class<T>) is invoked.
JerseyInvocation.Builder#get(Class<T>)
Invoke HTTP GET method for the current request synchronously.
#Override
public <T> T get(final Class<T> responseType)
throws ProcessingException, WebApplicationException {
return method("GET", responseType);
}
JerseyInvocation.Builder#method(String, Class<T>)
Invoke an arbitrary method for the current request synchronously.
#Override
public <T> T method(final String name, final Class<T> responseType)
throws ProcessingException, WebApplicationException {
// responseType null check omitted for brevity
requestContext.setMethod(name);
return new JerseyInvocation(this).invoke(responseType);
}
JerseyInvocation#invoke(Class<T>)
Synchronously invoke the request and receive a response of the specified type back.
#Override
public <T> T invoke(final Class<T> responseType)
throws ProcessingException, WebApplicationException {
// responseType null check omitted for brevity
final ClientRuntime runtime = request().getClientRuntime();
final RequestScope requestScope = runtime.getRequestScope();
return requestScope.runInScope(new Producer<T>() {
#Override
public T call() throws ProcessingException {
try {
return translate(runtime.invoke(requestForCall(requestContext)),
requestScope, responseType);
} catch (final ProcessingException ex) {
// Exception handling omitted for brevity
}
}
});
}
JerseyInvocation#translate(ClientResponse, RequestScope, Class<T>)
If the request suceeded, the response entity is read as an instance of specified Java type using Response#readEntity(Class<T>):
private <T> T translate(final ClientResponse response, final RequestScope scope,
final Class<T> responseType) throws ProcessingException {
if (responseType == Response.class) {
return responseType.cast(new InboundJaxrsResponse(response, scope));
}
if (response.getStatusInfo().getFamily() == Response.Status.Family.SUCCESSFUL) {
try {
return response.readEntity(responseType);
} catch (final ProcessingException ex) {
// Exception handling omitted for brevity
}
} else {
throw convertToException(new InboundJaxrsResponse(response, scope));
}
}

Asynchronous variation of the service activator EIP?

We have the following Camel route in our application:
from(webServiceUri).routeId("webServiceRoute")
.unmarshal(jaxb)
.process(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
final Message in = exchange.getIn();
final DataRequest body = in.getBody(DataRequest.class);
final DataRequest.Items items = body.getItems();
itemValidator.validate(items.getItem());
getContext().createProducerTemplate().sendBody(importUri, body);
DataResponse response = new DataResponse();
response.setReturnCode(ReturnCode.SUCCESS);
in.setBody(response);
}
})
.marshal(jaxb);
We want the "webServiceRoute" to return the response user as soon as the processor has validated the data and forwarded the message to the "importUri". But right now it seems like the response is not returned to the caller until the "importUri" exchange is completed. So my question is what is the "correct" way to asynchronously forward the received request to another queue? There will not be any reply from the "importUri" exchange (i.e. it should be InOnly).
You can replace .sendBody(importUri, body) by .asyncSendBody(importUri, body).
Nevertheless I find your route looks strange to me, why do you use a processor to forward your message. I would write something like:
DataResponse successResponse = new DataResponse();
response.setReturnCode(ReturnCode.SUCCESS);
from(webServiceUri).routeId("webServiceRoute")
.unmarshal(jaxb)
.bean(WebServiceRouteHelper.class,"validate")
.to(importUri)
.setBody(constant(sucessResponse))
.marshal(jaxb);
class WebServiceRouteHelper {
public DataRequest validate(DataRequest dataRequest) throws Exception {
final DataRequest.Items items = body.getItems();
itemValidator.validate(items.getItem());
return dataRequest;
}
}

Resources