基于自定义注解实现重复读取请求头拦截器

需求分析

目前的需求是,由于服务端不是简单的控制台而是计算引擎,因此暴露的接口在接收客户端参数的时候进行签名校验

客户端将签名参数设置在 RequestHeader 中传递,将接口入参设置在 RequestBody 中

朴素的做法是直接读取数据流中的数据,但是 ServletRequest 的 getInputStream 和 getReader 是不可重复调用的,读完数据就没了,因此需要实现可重复读取请求头

基本思路

基本的思路是在请求处理之前通过 HandlerInterceptor 拦截,将 HttpServletRequest 中的数据获取到并缓存在内存中,再通过框架提供的配置接口将数据 Set 回去

因此总结分为两个部分处理:

  • 拦截请求
  • 缓存请求数据并重新设置回去

代码实现

缓存请求并重新设置回请求对象

1
2
3
4
5
6
7
8
9
10
11

/**
* Provides a convenient implementation of the HttpServletRequest interface that can be subclassed by developers wishing to adapt the request to a Servlet.
*
* <p>
* This class implements the Wrapper or Decorator pattern. Methods default to calling through to the wrapped request object.
*
* @see javax.servlet.http.HttpServletRequest
* @since Servlet 2.3
*/
public class HttpServletRequestWrapper extends ServletRequestWrapper implements HttpServletRequest {}

我们需要继承 MVC 提供的一个基础 Warpper 类,这个类提供了最基础的基于 HttpServletRequest 的实现,我们可以在这上面进行自己业务的特殊需求实现(缓存请求数据)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public class HttpRequestWrapper extends HttpServletRequestWrapper {

private byte[] m_byteRequestBody;
private final HttpServletRequest m_pRequest;

public HttpRequestWrapper(HttpServletRequest pRequest) throws IOException {
super(pRequest);
this.m_pRequest = pRequest;
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
//缓存数据,其实这里调用getInputStream()的时候request的数据就已经没了
IOUtils.copy(m_pRequest.getInputStream(), byteStream);
this.m_byteRequestBody = byteStream.toByteArray();
}

public byte[] getRequestBody() {
return this.m_byteRequestBody;
}

@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream byteStream = new ByteArrayInputStream(m_byteRequestBody);
return new ServletInputStream() {
@Override
public boolean isFinished() {
return false;
}

@Override
public boolean isReady() {
return true;
}

@Override
public void setReadListener(ReadListener listener) {
throw new UnsupportedOperationException("Async not supported");
}

@Override
public int read() {
return byteStream.read();
}
};
}

@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(this.getInputStream()));
}
}

核心在于我们重写了 getInputStream 方法,将缓存的数据byteStream返回到上游调用方

拦截器

之后我们就可以根据我们的业务代码编写对应的签名拦截器

由于签名校验是公共逻辑,我们可以自定义注解,通过拦截器拦截注解来处理,只对打了对应自定义注解的接口进行签名校验

自定义注解:

1
2
3
4
5
6
7

@Documented
@Target(ElementType.METHOD)
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface SignatureCheck {
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Component
public class SignInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
//拦截对应注解
if (handler instanceof HandlerMethod) {
HandlerMethod pHandlerMethod = (HandlerMethod) handler;
SignCheck pSignCheck = pHandlerMethod.getMethodAnnotation(SignCheck.class);
if (pSignCheck != null) {
//对应校验签名的业务代码
}
}
return true;
}
}

基于自定义注解实现重复读取请求头拦截器
http://example.com/2024/11/06/基于自定义注解实现重复读取请求头拦截器/
作者
Noctis64
发布于
2024年11月6日
许可协议