为什么我的请求实体 InputStream 在 ContainerRequestFilter (Spring+Jersey) 中总是为空
Why my request entity InputStream is always empty in ContainerRequestFilter (Spring+Jersey)
我完全无法在 JaxRS ContainerRequestFilter 中获取请求 payload/form。
我的设置:
- JDK 8
- Spring启动 1.3.0.RELEASE
- Jersey 2.22.1(来自 SpringBoot)
这是我的 pom:(来自 Spring Initialzr)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jersey</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
这是我的申请 class:
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
这是我的 JerseyConfig:
@Configuration
public class JerseyConfig extends ResourceConfig {
public JerseyConfig() {
register(HelloController.class);
register(MyFilter.class);
}
}
这是我的 HelloController:
@Component
@Path("/hello")
public class HelloController {
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@POST
public String onHelloRequest(@FormParam("foo") String foo) {
System.out.println(foo);
return foo + "bar";
}
}
这是我遇到的问题的核心部分,ContainerRequestFilter:
public class MyFilter implements ContainerRequestFilter {
@Override
public void filter(ContainerRequestContext ctx) throws IOException {
System.err.println("I'm in the filter");
// Solution #1 doesn't work
if (ctx instanceof ContainerRequest) {
ContainerRequest request = (ContainerRequest) ctx;
request.bufferEntity();
Form f = request.readEntity(Form.class);
System.err.println(f.asMap().toString());
}
// Solution #2 doesn't work either
InputStream inputStream = ctx.getEntityStream();
StringBuilder textBuilder = new StringBuilder();
try (Reader reader = new BufferedReader(new InputStreamReader
(inputStream, Charset.forName(StandardCharsets.UTF_8.name())))) {
int c = 0;
while ((c = reader.read()) != -1) {
textBuilder.append((char) c);
}
}
System.err.println(textBuilder.toString());
}
}
如您所见,这是一个使用 Jersey 的非常精简的 Spring 引导示例。但是,看起来来自 ContainerRequestFilter 的 InputStream 已被使用。我用 Javax ServletFilter 试过了,我遇到了同样的问题。
是否有可能 Spring-web 在调用 Jersey 之前消耗 InputStream?
请帮助我。
编辑
为了测试它,我使用了 POSTMAN 并发送了:
POST http://localhost:8080/hello
Headers:
Content-type = application/x-www-form-urlencoded
有效负载:
foo=bar
我的回复是
巴巴
我的控制台输出是
我在过滤器中
{} // 解决方案 #1 <- 应该类似于 foo=bar
// 解决方案 #2 <- 空字符串
栏
EDIT2
即使没有任何自定义过滤器,我在控制台中也有该消息:
2015-11-21 20:14:05.438 WARN 4440 --- [nio-8080-exec-2] o.glassfish.jersey.servlet.WebComponent:对 URI http://localhost:8080/hello 的 servlet 请求包含表单请求 body 中的参数,但请求 body 已被访问请求参数的 servlet 或 servlet 过滤器使用。只有使用 @FormParam 的资源方法才能按预期工作。通过其他方式使用请求 body 的资源方法将无法按预期工作。
看起来肯定有其他东西在将 InputStream 传递给 Jersey 之前消耗了它,让每个 Jax-RS 过滤器|拦截器都没有状态。
这是"solution"
在 SpringBoot 中,如果您添加 starter-web 模块,它会添加 spring:webmvc,这似乎与 JaxRS 和 Servlet Filters 不兼容。如果您使用 SpringBoot 和 Jersey,请确保 webmvc jar 不在类路径中。
Spring 堆栈将消耗 Servlet 的 Request InputStream,并且管道中的每个向下 intercepter/filter 将一无所有。
这是一个有效的代码片段,它在 Servlet 过滤器中获取请求的有效负载
public MyFilter implements Filter
{
@Override
public final void doFilter(
final ServletRequest request,
final ServletResponse response,
final FilterChain chain)
throws IOException,
ServletException {
getRawFormPayload((HttpServletRequest) request);
chain.doFilter(request, response);
}
protected String getRawFormPayload(HttpServletRequest request) {
final int contentLength = request.getContentLength();
final StringBuilder payloadBuilder = new StringBuilder(contentLength);
try {
final InputStream inputStream = request.getInputStream();
final BufferedReader reader =
new BufferedReader(new InputStreamReader(inputStream, request.getCharacterEncoding()));
// Buffer should not be higher than the content-length of the request
reader.mark(contentLength + 1);
final char[] charBuffer = new char[contentLength + 1];
int bytesRead = -1;
while ((bytesRead = reader.read(charBuffer)) > 0) {
payloadBuilder.append(charBuffer, 0, bytesRead);
}
// Reset the buffer so the next Filter/Interceptor have unaltered InputStream
reader.reset();
} catch (final IOException e) {
this.LOG.error(e.getMessage(), e);
}
return payloadBuilder.toString();
}
}
我完全无法在 JaxRS ContainerRequestFilter 中获取请求 payload/form。
我的设置:
- JDK 8
- Spring启动 1.3.0.RELEASE
- Jersey 2.22.1(来自 SpringBoot)
这是我的 pom:(来自 Spring Initialzr)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jersey</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
这是我的申请 class:
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
这是我的 JerseyConfig:
@Configuration
public class JerseyConfig extends ResourceConfig {
public JerseyConfig() {
register(HelloController.class);
register(MyFilter.class);
}
}
这是我的 HelloController:
@Component
@Path("/hello")
public class HelloController {
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@POST
public String onHelloRequest(@FormParam("foo") String foo) {
System.out.println(foo);
return foo + "bar";
}
}
这是我遇到的问题的核心部分,ContainerRequestFilter:
public class MyFilter implements ContainerRequestFilter {
@Override
public void filter(ContainerRequestContext ctx) throws IOException {
System.err.println("I'm in the filter");
// Solution #1 doesn't work
if (ctx instanceof ContainerRequest) {
ContainerRequest request = (ContainerRequest) ctx;
request.bufferEntity();
Form f = request.readEntity(Form.class);
System.err.println(f.asMap().toString());
}
// Solution #2 doesn't work either
InputStream inputStream = ctx.getEntityStream();
StringBuilder textBuilder = new StringBuilder();
try (Reader reader = new BufferedReader(new InputStreamReader
(inputStream, Charset.forName(StandardCharsets.UTF_8.name())))) {
int c = 0;
while ((c = reader.read()) != -1) {
textBuilder.append((char) c);
}
}
System.err.println(textBuilder.toString());
}
}
如您所见,这是一个使用 Jersey 的非常精简的 Spring 引导示例。但是,看起来来自 ContainerRequestFilter 的 InputStream 已被使用。我用 Javax ServletFilter 试过了,我遇到了同样的问题。
是否有可能 Spring-web 在调用 Jersey 之前消耗 InputStream?
请帮助我。
编辑
为了测试它,我使用了 POSTMAN 并发送了:
POST http://localhost:8080/hello
Headers:
Content-type = application/x-www-form-urlencoded
有效负载:
foo=bar
我的回复是
巴巴
我的控制台输出是
我在过滤器中
{} // 解决方案 #1 <- 应该类似于 foo=bar
// 解决方案 #2 <- 空字符串
栏
EDIT2
即使没有任何自定义过滤器,我在控制台中也有该消息:
2015-11-21 20:14:05.438 WARN 4440 --- [nio-8080-exec-2] o.glassfish.jersey.servlet.WebComponent:对 URI http://localhost:8080/hello 的 servlet 请求包含表单请求 body 中的参数,但请求 body 已被访问请求参数的 servlet 或 servlet 过滤器使用。只有使用 @FormParam 的资源方法才能按预期工作。通过其他方式使用请求 body 的资源方法将无法按预期工作。
看起来肯定有其他东西在将 InputStream 传递给 Jersey 之前消耗了它,让每个 Jax-RS 过滤器|拦截器都没有状态。
这是"solution"
在 SpringBoot 中,如果您添加 starter-web 模块,它会添加 spring:webmvc,这似乎与 JaxRS 和 Servlet Filters 不兼容。如果您使用 SpringBoot 和 Jersey,请确保 webmvc jar 不在类路径中。
Spring 堆栈将消耗 Servlet 的 Request InputStream,并且管道中的每个向下 intercepter/filter 将一无所有。
这是一个有效的代码片段,它在 Servlet 过滤器中获取请求的有效负载
public MyFilter implements Filter
{
@Override
public final void doFilter(
final ServletRequest request,
final ServletResponse response,
final FilterChain chain)
throws IOException,
ServletException {
getRawFormPayload((HttpServletRequest) request);
chain.doFilter(request, response);
}
protected String getRawFormPayload(HttpServletRequest request) {
final int contentLength = request.getContentLength();
final StringBuilder payloadBuilder = new StringBuilder(contentLength);
try {
final InputStream inputStream = request.getInputStream();
final BufferedReader reader =
new BufferedReader(new InputStreamReader(inputStream, request.getCharacterEncoding()));
// Buffer should not be higher than the content-length of the request
reader.mark(contentLength + 1);
final char[] charBuffer = new char[contentLength + 1];
int bytesRead = -1;
while ((bytesRead = reader.read(charBuffer)) > 0) {
payloadBuilder.append(charBuffer, 0, bytesRead);
}
// Reset the buffer so the next Filter/Interceptor have unaltered InputStream
reader.reset();
} catch (final IOException e) {
this.LOG.error(e.getMessage(), e);
}
return payloadBuilder.toString();
}
}