Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I get a request body from HttpServerExchange?

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!

like image 276
FDB Avatar asked May 02 '18 17:05

FDB


3 Answers

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.

like image 178
LuisFMorales Avatar answered Oct 29 '22 06:10

LuisFMorales


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());
like image 3
Balloon Wen Avatar answered Oct 29 '22 07:10

Balloon Wen


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。

like image 1
lsk569937453 Avatar answered Oct 29 '22 07:10

lsk569937453