Spring webflux webfilter removes the body from the request - spring-webflux

I have a filter created where I access the body of the payload and do some logic on it (for now let's say I log the body). In the last step, I return Mono but when the request proceeds through the controller to services it throws a bad request error that the body is missing.
Code for the filter:
public Mono<Void> filter(ServerWebExchange serverWebExchange, WebFilterChain webFilterChain) {
HttpHeaders headers = serverWebExchange.getRequest().getHeaders();
String domain = headers.getFirst("domain");
return serverWebExchange.getRequest().getBody()
.single()
.flatMap(body -> Mono.just(parseBody(body)))
.flatMap(s -> webFilterChain.filter(serverWebExchange));
}
private String parseBody(DataBuffer fbody) {
System.out.println("parsing body");
ByteArrayOutputStream baos = null;
try {
baos = new ByteArrayOutputStream();
Channels.newChannel(baos).write(fbody.asByteBuffer().asReadOnlyBuffer());
} catch (IOException e) {
e.printStackTrace();
}
return baos.toString(StandardCharsets.UTF_8);
}
The error: org.springframework.web.server.ServerWebInputException: 400 BAD_REQUEST "Request body is missing"
What can cause this behavior?

The reason is that you can only read the body once (https://github.com/spring-cloud/spring-cloud-gateway/issues/1587)
You are reading it here: serverWebExchange.getRequest().getBody() and therefore it is omitted in the request.
A solution is to cache the body, you can use for instance the ReadBodyRoutePredicateFactory (https://github.com/spring-cloud/spring-cloud-gateway/issues/1307).
Make sure the RemoveCachedBodyFilter is enabled so the body is released so you do not have memory leaks.

Related

How do I close a resource after responding using JAX-RS's CompletionStage async processing?

JAX-RS AsyncResponse's CompletionCallback and ConnectionCallback allow for the cleanup of resources after a response is completed or dropped.
#GET
public void get(#Suspend AsyncResponse response) {
executor.submit(() -> {
CloseableHttpResponse httpResponse = getHttpResponse();
response.register(closeOnCompleteOrDisconnect(httpResponse));
response.resume(Response.ok().entity(httpResponse.getEntity().getContent()).build());
// We couldn't close the httpResponse now because the entity content hasn't been read.
// it can't be closed till the message body writer finishes streaming the response.
// hence the closeOnCompleteOrDisconnect callback.
});
}
I'd like to convert this code to CompletableFuture, but don't know how to close the httpResponse after the message body has been written.
#GET
public CompletableFuture<Response> getAsync() {
return CompletableFuture.supplyAsync(() -> {
CloseableHttpResponse httpResponse = getHttpResponse();
Response.ok().entity(httpResponse.getEntity().getContent()).build();
// still can't close the httpResponse, and now we've no AsyncResponse to register a callback on.
}, executor);
}
https://download.oracle.com/otn-pub/jcp/jaxrs-2_1-final-eval-spec/jaxrs-2_1-final-spec.pdf?AuthParam=1605738947_546c3c96f63881e7441d4b95f55d00e7

OKHttp Authenticator custom http code other than 401 and 407

I have oauth token implemented on server side but upon Invalid token or Token expirey i am getting 200 http status code but in response body i have
{"code":"4XX", "data":{"some":"object"}
When i try to read string in interceptor i get okhttp dispatcher java.lang.illegalstateexception closed because response.body().string() must be called only once.
Also i read from here Refreshing OAuth token using Retrofit without modifying all calls that we can use OkHttp Authenticator class but it works only with 401/407 i havent triedn as i will not get this. Is there any way we can customize Authenticator and proceed our logic inside it.
Thank you
If it possible, try to talk with your server side about response codes. Communication is also a very important skill.
If it inpossible, you can modify response codes manually with reflection, it enables okHttp authentication logic.
public OkHttpClient getOkHttpClient() {
return new OkHttpClient.Builder()
.authenticator((route, response) -> {
System.out.println("it working");
return null;
})
.addNetworkInterceptor(new UnauthorizedCaseParserInterceptor())
.build();
}
public class UnauthorizedCaseParserInterceptor implements Interceptor {
#Override
public Response intercept(#NonNull Chain chain) throws IOException {
Request request = chain.request();
Response response = chain.proceed(request);
if (isUnauthorizedResponse(response)) {
try {
Field codeField = response.getClass().getDeclaredField("code");
codeField.setAccessible(true);
codeField.set(response, HttpURLConnection.HTTP_UNAUTHORIZED);
} catch (Exception e) {
return response;
}
}
return response;
}
private boolean isUnauthorizedResponse(Response response) {
//parse response...
}
}
Please use this solution only as a last resort.

Apache HttpClient - Default protocol

I am using Apache HttpClient to send a POST requests. How can I determine which PROTOCOL my Apache HttpClient instance is using for sending "https://" requests. I use following code block to send my POST requests.
public void sendPostURL(String url, HashMap<String, String>params, String user, String pass) {
HttpClient client = new HttpClient();
String urlContent = "";
PostMethod method = new PostMethod("https://...");
// Prepare connection information
client.getParams().setParameter("http.useragent", "MyApp");
if ( (user != null) &&(pass != null) ) {
client.getParams().setAuthenticationPreemptive(true);
client.getState().setCredentials(AuthScope.ANY, (new UsernamePasswordCredentials(user, pass)));
}
// Prepare parameters
for (Map.Entry<String, String> entry : params.entrySet()) {
method.addParameter(entry.getKey(), ((entry.getValue() != null) ? entry.getValue().toString() : ""));
}
try{
// HTTP execution
int returnCode = client.executeMethod(method);
} catch (Exception e) {
// Exception
e.printStackTrace();
} finally {
method.releaseConnection();
}
}
Please guide me on how can I get the PROTOCOL that HttpClient is using to send the request. Also how can I override the PROTOCOL used. Hoping for a solution. Thanks in advance.
The protocol is HTTPS, is it not ?

Maximum threads issue

To begin with, I checked the discussions regarding this issue and couldn't find an answer to my problem and that's why I'm opening this question.
I've set up a web service using restlet 2.0.15.The implementation is only for the server. The connections to the server are made through a webpage, and therefore I didn't use ClientResource.
Most of the answers to the exhaustion of the thread pool problem suggested the inclusion of
#exhaust + #release
The process of web service can be described as a single function.Receive GET requests from the webpage, query the database, frame the results in XML and return the final representation. I used a Filter to override the beforeHandle and afterHandle.
The code for component creation code:
Component component = new Component();
component.getServers().add(Protocol.HTTP, 8188);
component.getContext().getParameters().add("maxThreads", "512");
component.getContext().getParameters().add("minThreads", "100");
component.getContext().getParameters().add("lowThreads", "145");
component.getContext().getParameters().add("maxQueued", "100");
component.getContext().getParameters().add("maxTotalConnections", "100");
component.getContext().getParameters().add("maxIoIdleTimeMs", "100");
component.getDefaultHost().attach("/orcamento2013", new ServerApp());
component.start();
The parameters are the result of a discussion present in this forum and modification by my part in an attempt to maximize efficiency.
Coming to the Application, the code is as follows:
#Override
public synchronized Restlet createInboundRoot() {
// Create a router Restlet that routes each call to a
// new instance of HelloWorldResource.
Router router = new Router(getContext());
// Defines only one route
router.attach("/{taxes}", ServerImpl.class);
//router.attach("/acores/{taxes}", ServerImplAcores.class);
System.out.println(router.getRoutes().size());
OriginFilter originFilter = new OriginFilter(getContext());
originFilter.setNext(router);
return originFilter;
}
I used an example Filter found in a discussion here, too. The implementation is as follows:
public OriginFilter(Context context) {
super(context);
}
#Override
protected int beforeHandle(Request request, Response response) {
if (Method.OPTIONS.equals(request.getMethod())) {
Form requestHeaders = (Form) request.getAttributes().get("org.restlet.http.headers");
String origin = requestHeaders.getFirstValue("Origin", true);
Form responseHeaders = (Form) response.getAttributes().get("org.restlet.http.headers");
if (responseHeaders == null) {
responseHeaders = new Form();
response.getAttributes().put("org.restlet.http.headers", responseHeaders);
responseHeaders.add("Access-Control-Allow-Origin", origin);
responseHeaders.add("Access-Control-Allow-Methods", "GET,POST,DELETE");
responseHeaders.add("Access-Control-Allow-Headers", "Content-Type");
responseHeaders.add("Access-Control-Allow-Credentials", "true");
response.setEntity(new EmptyRepresentation());
return SKIP;
}
}
return super.beforeHandle(request, response);
}
#Override
protected void afterHandle(Request request, Response response) {
if (!Method.OPTIONS.equals(request.getMethod())) {
Form requestHeaders = (Form) request.getAttributes().get("org.restlet.http.headers");
String origin = requestHeaders.getFirstValue("Origin", true);
Form responseHeaders = (Form) response.getAttributes().get("org.restlet.http.headers");
if (responseHeaders == null) {
responseHeaders = new Form();
response.getAttributes().put("org.restlet.http.headers", responseHeaders);
responseHeaders.add("Access-Control-Allow-Origin", origin);
responseHeaders.add("Access-Control-Allow-Methods", "GET,POST,DELETE"); //
responseHeaders.add("Access-Control-Allow-Headers", "Content-Type");
responseHeaders.add("Access-Control-Allow-Credentials", "true");
}
}
super.afterHandle(request, response);
Representation requestRepresentation = request.getEntity();
if (requestRepresentation != null) {
try {
requestRepresentation.exhaust();
} catch (IOException e) {
// handle exception
}
requestRepresentation.release();
}
Representation responseRepresentation = response.getEntity();
if(responseRepresentation != null) {
try {
responseRepresentation.exhaust();
} catch (IOException ex) {
Logger.getLogger(OriginFilter.class.getName()).log(Level.SEVERE, null, ex);
} finally {
}
}
}
The responseRepresentation does not have a #release method because it crashes the processes giving the warning WARNING: A response with a 200 (Ok) status should have an entity (...)
The code of the ServerResource implementation is the following:
public class ServerImpl extends ServerResource {
String itemName;
#Override
protected void doInit() throws ResourceException {
this.itemName = (String) getRequest().getAttributes().get("taxes");
}
#Get("xml")
public Representation makeItWork() throws SAXException, IOException {
DomRepresentation representation = new DomRepresentation(MediaType.TEXT_XML);
DAL dal = new DAL();
String ip = getRequest().getCurrent().getClientInfo().getAddress();
System.out.println(itemName);
double tax = Double.parseDouble(itemName);
Document myXML = Auxiliar.getMyXML(tax, dal, ip);
myXML.normalizeDocument();
representation.setDocument(myXML);
return representation;
}
#Override
protected void doRelease() throws ResourceException {
super.doRelease();
}
}
I've tried the solutions provided in other threads but none of them seem to work. Firstly, it does not seem that the thread pool is augmented with the parameters set as the warnings state that the thread pool available is 10. As mentioned before, the increase of the maxThreads value only seems to postpone the result.
Example: INFO: Worker service tasks: 0 queued, 10 active, 17 completed, 27 scheduled.
There could be some error concerning the Restlet version, but I downloaded the stable version to verify this was not the issue.The Web Service is having around 5000 requests per day, which is not much.Note: the insertion of the #release method either in the ServerResource or OriginFilter returns error and the referred warning ("WARNING: A response with a 200 (Ok) status should have an entity (...)")
Please guide.
Thanks!
By reading this site the problem residing in the server-side that I described was resolved by upgrading the Restlet distribution to the 2.1 version.
You will need to alter some code. You should consult the respective migration guide.

Java Mail on Glassfish AS

I'm facing problems to send e-mails with Java Mail on Glashfish 3.1.1.
The server doesn't throw any Exception, just send an empty message with only header to recipient.
Running Java Mail without the Glashfish, everything works fine.
public void sendHtmlMessage(String subject, String html){
// Creates a email Session and
// and Authenticator attached to it
Session session = getMailSession();
try{
MimeMessage message = createMessage(session, subject, MailSender.MIME_Type.TEXT_HTML, html); // creates message
transportSMTPMessage(session, message); // transport message
} catch(AddressException e){ log("Internet address is invalid!"); e.printStackTrace(); }
catch(NoSuchProviderException e){ log("Host is invalid!"); e.printStackTrace(); }
catch(MessagingException e){ log("Message is invalid!"); e.printStackTrace(); }
catch(Exception e){ log("Gereric Exception!"); e.printStackTrace(); }
}
// Helper to obtain Mail Session
private Session getMailSession(){
Properties props = new Properties();
return Session.getInstance(props,
new Authenticator() {
#Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(userName, password);
}
});
}
// Helper to create MimeMessage
private MimeMessage createMessage(Session session, String subject, MailSender.MIME_Type mime, String content)
throws AddressException, MessagingException{
// Some code to create the message...
message.saveChanges();
return message;
}
// Helper to transpot the message thru the SMTP protocol
private void transportSMTPMessage(Session session, Message message)
throws NoSuchProviderException, MessagingException{
// Creates the transport connection and
// Send the message
Transport transport = session.getTransport("smtp");
transport.connect(host, userName, password);
transport.sendMessage(message, message.getAllRecipients());
transport.close();
}
I think everything's fine with the code, I just don't understand, why it doesn't work on Glassfish?
I'd be grateful if someone help me.
Thanks in advance.
Call session.setDebug(true) in your application after creating the Session. Then look at the server log file to see if the JavaMail debug output has any clues about what's going wrong. I'm assuming you're not getting any exceptions.