如何从HttpServerExchange获取请求正文?

10

我创建了一个Undertow服务器和一个处理程序来记录请求。 我有问题获取HttpServerExchange的请求体。

LoggingHandler类中,我可以毫无问题地获取请求体。 但是在TestEndpoint中,请求体为空。

如果我删除在LoggingHandler中检索请求体的行,则请求体将在TestEndpoint中填充。

有人知道如何解决这个问题吗?

我的服务器类:

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();
    }

}

我的LoggingHandler类:

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()));
        }
    }

}

我的TestEndpoint类:

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());
    }

}

非常感谢!


那么你的意思是 TestEndpoint 中的 listar 方法中的 String bodynull 还是空的?@FDB - Vishrant
@Vishrant 空字符串 - FDB
我不确定,但是在 HttpHandler 中是否有任何请求转发器?如果有,请尝试使用它。检查是否有其他重载的 handleRequest 方法,您可以在其中添加您的标头。 - Vishrant
尝试进行调试,查看请求正文在哪里设置。 - Vishrant
1
我在这里分享了我的测试项目... https://github.com/fabiodelabruna/undertow-server-test - FDB
嗯,看起来你的实现问题在于将inputStream转换为String的方法。一旦你关闭了BufferedStream,它就会关闭你在exchange中的inputStream,这就是为什么当你删除logger调用时它能正常工作的原因。 - LuisFMorales
3个回答

4
正如我在下面的评论中所说,你的实现问题似乎出在将inputStream转换为String的方法上。一旦你关闭了BufferedReader,它就会关闭你在exchange中的inputStream。可以参考这个问题:应该显式地关闭BufferedReader和InputStreamReader吗? 一个简单的解决方案是不要显式关闭BufferedStream(或避免try with resource块):
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()));
}

预期要关闭流的元素是创建它的元素,因此在这种情况下,您可以利用它来安全地关闭到Undertow。

3

流(InputStream)存储在exchange中,由处理程序共享。一旦您在一个处理程序中读取了它,就不能在下一个处理程序中重新读取它。
相反,您可以将已读取的内容作为附件存储在exchange中。这样,您可以直接获取它,无需再次读取,这是低效的。

    public static final AttachmentKey<Object> REQUEST_BODY = AttachmentKey.create(Object.class);
    exchange.putAttachment(REQUEST_BODY, toString(exchange.getInputStream()).trim());

1

首先,您应该知道在Java中,inputStream不应该被重复读取。例如,ByteArrayInputStream不能被重复读取。我们都知道实现可重复的inputStream很容易,但是JDK没有实现。我猜测这是为了遵循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;  

解决问题的方法是先读取流并缓存数据,执行handleRequest方法后将输入流写入HttpServerExchange。但是HttpServerExchange没有setInputStream方法,所以您需要通过反射来实现它。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接