1. Introduction
1.绪论
In this tutorial, we’ll learn how to read the body from the HttpServletRequest multiple times using Spring.
在本教程中,我们将学习如何使用Spring从HttpServletRequest多次读取body。
HttpServletRequest is an interface which exposes getInputStream() method to read the body. By default, the data from this InputStream can be read only once.
HttpServletRequest是一个接口,它暴露了getInputStream()方法来读取主体。默认情况下,这个InputStream的数据只能被读取一次。
2. Maven Dependencies
2.Maven的依赖性
The first thing we’ll need is the appropriate spring-webmvc and javax.servlet dependencies:
我们首先需要的是适当的spring-webmvc和javax.servlet依赖项。
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.0.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
</dependency>
Also, since we’re using the application/json content-type, the jackson-databind dependency is required:
另外,由于我们使用的是application/json 内容类型,所以需要jackson-databind依赖。
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.10.0</version>
</dependency>
Spring uses this library to convert to and from JSON.
Spring使用这个库来进行JSON的转换和输出。
3. Spring’s ContentCachingRequestWrapper
3.Spring的ContentCachingRequestWrapper
Spring provides a ContentCachingRequestWrapper class. This class provides a method, getContentAsByteArray() to read the body multiple times.
Spring提供了一个ContentCachingRequestWrapper类。这个类提供了一个方法,getContentAsByteArray() 来多次读取正文。
This class has a limitation, though: We can’t read the body multiple times using the getInputStream() and getReader() methods.
不过,这个类有一个限制。 我们不能使用getInputStream()和getReader()方法多次读取正文。
This class caches the request body by consuming the InputStream. If we read the InputStream in one of the filters, then other subsequent filters in the filter chain can’t read it anymore. Because of this limitation, this class is not suitable in all situations.
这个类通过消耗InputStream来缓存请求体。如果我们在其中一个过滤器中读取InputStream,那么过滤器链中的其他后续过滤器就不能再读取它。由于这个限制,这个类并不适合在所有情况下使用。
To overcome this limitation, let’s now take a look at a more general-purpose solution.
为了克服这一限制,我们现在来看看一个更通用的解决方案。
4. Extending HttpServletRequest
4.扩展HttpServletRequest
Let’s create a new class – CachedBodyHttpServletRequest – which extends HttpServletRequestWrapper. This way, we don’t need to override all the abstract methods of the HttpServletRequest interface.
让我们创建一个新类–CachedBodyHttpServletRequest–,它扩展了HttpServletRequestWrapper。这样,我们就不需要覆盖HttpServletRequest接口的所有抽象方法了。
HttpServletRequestWrapper class has two abstract methods getInputStream() and getReader(). We’ll override both of these methods and create a new constructor.
HttpServletRequestWrapper类有两个抽象方法getInputStream()和getReader()。我们将覆盖这两个方法并创建一个新的构造函数。
4.1. The Constructor
4.1.构造函数
First, let’s create a constructor. Inside it, we’ll read the body from the actual InputStream and store it in a byte[] object:
首先,让我们创建一个构造函数。在它里面,我们将从实际的InputStream中读取主体,并将其存储在一个byte[]对象中。
public class CachedBodyHttpServletRequest extends HttpServletRequestWrapper {
private byte[] cachedBody;
public CachedBodyHttpServletRequest(HttpServletRequest request) throws IOException {
super(request);
InputStream requestInputStream = request.getInputStream();
this.cachedBody = StreamUtils.copyToByteArray(requestInputStream);
}
}
As a result, we’ll be able to read the body multiple times.
因此,我们将能够多次阅读正文。
4.2. getInputStream()
4.2.getInputStream()
Next, let’s override the getInputStream() method. We’ll use this method to read the raw body and convert it into an object.
接下来,让我们覆盖getInputStream()方法。我们将使用这个方法来读取原始体并将其转换为一个对象。
In this method, we’ll create and return a new object of CachedBodyServletInputStream class (an implementation of ServletInputStream):
在这个方法中,我们将创建并返回一个CachedBodyServletInputStreamclass(ServletInputStream的一个实现)的新对象。
@Override
public ServletInputStream getInputStream() throws IOException {
return new CachedBodyServletInputStream(this.cachedBody);
}
4.3. getReader()
4.3.getReader()
Then, we’ll override the getReader() method. This method returns a BufferedReader object:
然后,我们将覆盖getReader()方法。这个方法返回一个BufferedReader对象。
@Override
public BufferedReader getReader() throws IOException {
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(this.cachedBody);
return new BufferedReader(new InputStreamReader(byteArrayInputStream));
}
5. Implementation of ServletInputStream
5.实现ServletInputStream
Let’s create a class – CachedBodyServletInputStream – which will implement ServletInputStream. In this class, we’ll create a new constructor as well as override the isFinished(), isReady() and read() methods.
让我们创建一个类 – CachedBodyServletInputStream – 它将实现ServletInputStream。在这个类中,我们将创建一个新的构造函数,并覆盖isFinished()、isReady()和read()方法。
5.1. The Constructor
5.1.构造函数
First, let’s create a new constructor that takes a byte array.
首先,让我们创建一个新的构造函数,接受一个字节数组。
Inside it, we’ll create a new ByteArrayInputStream instance using that byte array. After that, we’ll assign it to the global variable cachedBodyInputStream:
在里面,我们将使用该字节数组创建一个新的ByteArrayInputStream实例。之后,我们将把它分配给全局变量cachedBodyInputStream:。
public class CachedBodyServletInputStream extends ServletInputStream {
private InputStream cachedBodyInputStream;
public CachedBodyServletInputStream(byte[] cachedBody) {
this.cachedBodyInputStream = new ByteArrayInputStream(cachedBody);
}
}
5.2. read()
5.2.read()
Then, we’ll override the read() method. In this method, we’ll call ByteArrayInputStream#read:
然后,我们将重写read() 方法。在这个方法中,我们将调用ByteArrayInputStream#read:。
@Override
public int read() throws IOException {
return cachedBodyInputStream.read();
}
5.3. isFinished()
5.3.isFinished()
Then, we’ll override the isFinished() method. This method indicates whether InputStream has more data to read or not. It returns true when zero bytes available to read:
然后,我们将覆盖isFinished()方法。这个方法表明InputStream是否有更多的数据可以读取。当有零字节可读时,它返回true。
@Override
public boolean isFinished() {
return cachedBody.available() == 0;
}
5.4. isReady()
5.4.isReady()
Similarly, we’ll override the isReady() method. This method indicates whether InputStream is ready for reading or not.
同样地,我们将覆盖isReady()方法。这个方法表明InputStream是否准备好进行读取。
Since we’ve already copied InputStream in a byte array, we’ll return true to indicate that it’s always available:
因为我们已经将InputStream复制到一个字节数组中,我们将返回true以表示它总是可用的。
@Override
public boolean isReady() {
return true;
}
6. The Filter
6.过滤器
Finally, let’s create a new filter to make use of the CachedBodyHttpServletRequest class. Here we’ll extend Spring’s OncePerRequestFilter class. This class has an abstract method doFilterInternal().
最后,让我们创建一个新的过滤器来利用CachedBodyHttpServletRequest类。在这里我们将扩展Spring的OncePerRequestFilter类。这个类有一个抽象的方法doFilterInternal()。
In this method, we’ll create an object of the CachedBodyHttpServletRequest class from the actual request object:
在这个方法中,我们将从实际的请求对象中创建一个CachedBodyHttpServletRequest类的对象。
CachedBodyHttpServletRequest cachedBodyHttpServletRequest =
new CachedBodyHttpServletRequest(request);
Then we’ll pass this new request wrapper object to the filter chain. So, all the subsequent calls to the getInputStream() method will invoke the overridden method:
然后我们将把这个新的请求包装对象传递给过滤器链。因此,所有后续对getInputStream()方法的调用都会调用这个重载方法。
filterChain.doFilter(cachedContentHttpServletRequest, response);
7. Conclusion
7.结论
In this tutorial, we quickly walked through the ContentCachingRequestWrapper class. We also saw its limitations.
在本教程中,我们快速浏览了ContentCachingRequestWrapper类。我们也看到了它的局限性。
Then, we created a new implementation of the HttpServletRequestWrapper class. We overrode the getInputStream() method to return an object of ServletInputStream class.
然后,我们创建了一个新的HttpServletRequestWrapper类的实现。我们重写了getInputStream()方法,以返回ServletInputStream类的一个对象。
Finally, we created a new filter to pass the request wrapper object to the filter chain. So, we were able to read the request multiple times.
最后,我们创建了一个新的过滤器,将请求包装器对象传递给过滤器链。因此,我们能够多次读取该请求。
The full source code of the examples can be found over on GitHub.
示例的完整源代码可以在GitHub上找到over。