10

I created an Undertow server and a Handler to Log requests. I'm having problems to retrieve the request body of HttpServerExchange.

At LoggingHandler class, I'm getting the body without problems. But at TestEndpoint the body is coming empty.

If I remove the line that retrieve the request body at LoggingHandler, the body gets populated at TestEndpoint.

Does anyone know a way to do this?

My Server class:

package com.undertow.server;

import com.undertow.server.endpoints.TestEndpoint;

import org.jboss.resteasy.plugins.server.undertow.UndertowJaxrsServer;
import org.jboss.resteasy.spi.ResteasyDeployment;

import io.undertow.Undertow;
import io.undertow.Undertow.Builder;
import io.undertow.server.HandlerWrapper;
import io.undertow.server.HttpHandler;
import io.undertow.server.handlers.BlockingHandler;
import io.undertow.servlet.api.DeploymentInfo;

public class UndertowServer {

    private UndertowJaxrsServer server;

    public UndertowServer() {
        this.server = new UndertowJaxrsServer();
    }

    public void start() {
        Builder builder = Undertow.builder().addHttpListener(8000, "0.0.0.0");
        this.server.start(builder);
        this.configureEndpoints();
    }

    private void configureEndpoints() {
        ResteasyDeployment deployment = new ResteasyDeployment();
        deployment.getActualResourceClasses().add(TestEndpoint.class);

        DeploymentInfo deploymentInfo = this.server.undertowDeployment(deployment) //
                .setClassLoader(ClassLoader.getSystemClassLoader()).setContextPath("/gateway/") //
                .setDeploymentName("gateway.war");

        deploymentInfo.addInitialHandlerChainWrapper(new HandlerWrapper() {
            @Override
            public HttpHandler wrap(HttpHandler handler) {
                return new BlockingHandler(new LoggingHandler(handler));
            }
        });

        this.server.deploy(deploymentInfo);
    }

    public static void main(String[] args) {
        new UndertowServer().start();
    }

}

My LoggingHandler class:

package com.undertow.server;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.stream.Collectors;

import org.apache.log4j.Logger;

import io.undertow.server.HttpHandler;
import io.undertow.server.HttpServerExchange;

public class LoggingHandler implements HttpHandler {

    private static Logger LOGGER = Logger.getLogger(LoggingHandler.class);

    private final HttpHandler next;

    public LoggingHandler(final HttpHandler next) {
        this.next = next;
    }

    @Override
    public void handleRequest(HttpServerExchange exchange) throws Exception {
        LOGGER.info(toString(exchange.getInputStream()).trim());
        this.next.handleRequest(exchange);
    }

    private String toString(InputStream is) throws IOException {
        try (BufferedReader br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) {
            return br.lines().collect(Collectors.joining(System.lineSeparator()));
        }
    }

}

My TestEndpoint class:

package com.undertow.server.endpoints;

import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.container.AsyncResponse;
import javax.ws.rs.container.Suspended;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import org.apache.log4j.Logger;

@Path("/person")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public class TestEndpoint {

    private static Logger LOGGER = Logger.getLogger(TestEndpoint.class);

    @POST
    @Path("/add")
    public void listar(@Suspended AsyncResponse response, String body) {
        LOGGER.info(body.trim());
        response.resume(Response.ok().build());
    }

}

Thanks a lot!

Talendar
  • 1,841
  • 14
  • 23
FDB
  • 113
  • 1
  • 6
  • So you mean `String body` in `TestEndoint` `listar` method its `null` or `empty`? @FDB – Vishrant May 02 '18 at 18:29
  • @Vishrant empty string – FDB May 02 '18 at 19:27
  • I am not sure, but is there any request forwarder in `HttpHandler`? if so try that. Check if there are other overloaded methods for `handleRequest` where you can add your header. – Vishrant May 02 '18 at 20:24
  • Try to debug and see where exactly the request body is set. – Vishrant May 02 '18 at 20:25
  • 1
    I shared my test project here.... https://github.com/fabiodelabruna/undertow-server-test – FDB May 02 '18 at 23:47
  • Well, it seems that your implementation problem is with your method that converts your inputStream into a String. Once you close the BufferedStream it closes your inputStream that is located inside your exchange, so that is why when you remove your logger call it works. – LuisFMorales Oct 05 '18 at 05:51

3 Answers3

4

As I said in my comment below, it seems that your implementation problem is with your method that converts your inputStream into a String. Once you close the BufferedReader it closes your inputStream that is located inside your exchange. Take a look at this question: Should BufferedReader and InputStreamReader be closed explicitly?

A simple solution should be to not expicitly close the BufferedStream (or avoid the try with resource block):

private String toString(InputStream is) throws IOException {
    BufferedReader br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));
    return br.lines().collect(Collectors.joining(System.lineSeparator()));
}

The one that is expected to close the stream is the element that created it, so in this case you can leverage it to be safely closed to undertow.

LuisFMorales
  • 186
  • 9
3

The InputStream is stored in exchange, which is shared by handlers. Once you read in one handler, you cannot re-read it in next handler.
Instead, you can put what you've read in as an attachment that stores in the exchange. Thus, you can get it directly, you don't need to re-read it again, which is inefficient.

    public static final AttachmentKey<Object> REQUEST_BODY = AttachmentKey.create(Object.class);
    exchange.putAttachment(REQUEST_BODY, toString(exchange.getInputStream()).trim());
Balloon Wen
  • 61
  • 1
  • 2
1

First you should know that the inputStream should not be read repeatable in java.For example, the ByteArrayInputStream can not be read repeatable.we all know that it is easy to implement the repeatable inputStream,but the jdk do not implement.I guess that follow the unified standard of InputStream.

/** 
 * Reads the next byte of data from the input stream. The value byte is 
 * returned as an <code>int</code> in the range <code>0</code> to 
 * <code>255</code>. If no byte is available because the end of the stream 
 * has been reached, the value <code>-1</code> is returned. This method 
 * blocks until input data is available, the end of the stream is detected, 
 * or an exception is thrown. 
 * 
 * <p> A subclass must provide an implementation of this method. 
 * 
 * @return     the next byte of data, or <code>-1</code> if the end of the 
 *             stream is reached. 
 * @exception  IOException  if an I/O error occurs. 
 */  
public abstract int read() throws IOException;  

The method to solve the problem is read the stream first and cache the data,write the input stream to the HttpServerExchange after execing the handleRequest method.But the HttpServerExchange do not have the setInputStream method ,so you have to implement that by the reflect。