Spring Boot中无法拦截和操作HttpServletResponse

Posted

技术标签:

【中文标题】Spring Boot中无法拦截和操作HttpServletResponse【英文标题】:Unable to intercept and manipulate HttpServletResponse in Spring Boot 【发布时间】:2017-02-19 03:48:56 【问题描述】:

我需要Base64 解码我的 Spring Boot 服务接收到的每个 JSON 请求负载。在使用 HTTP POST 方法发布之前,JSON 有效负载将在客户端进行 Base64 编码。此外,在呈现给调用客户端应用程序之前,我还需要对 JSON 响应进行Base64 编码。

我需要通过使用处理程序拦截器来减少样板代码。 我已经通过使用拦截器实现了操作的请求/传入分支,但还没有为响应分支实现这一点。 我已经在下面发布了代码 sn-ps。拦截响应和base64编码的代码在拦截器类的postHandle方法中。

我在这里做错了什么?

拦截器类:

public class Base64ResponseEncodingInterceptor implements HandlerInterceptor    
    private static final String DECODED_REQUEST_ATTRIB = "decodedRequest";
    private static final String POST = "POST";


    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView arg3) throws Exception 
          try 
              if (POST.equalsIgnoreCase(request.getMethod())) 
                    CharResponseWrapper res = new CharResponseWrapper(response);
                    res.getWriter();
                    byte[] encoded = Base64.encodeBase64(res.toString().getBytes());
                    byte[] encoded = Base64.encodeBase64(response.getHeader(ENCODED_RESPONSE_ATTRIB).getBytes());
                    response.getWriter().write(new String(encoded));
              
           catch (Exception e) 
              throw new Exception(e.getMessage());
          
    


    // preHandle and afterCompletion methods
    // Omitted 

上面使用的CharResponseWrapper 类:

public class CharResponseWrapper extends HttpServletResponseWrapper 

    protected CharArrayWriter charWriter;

    protected PrintWriter writer;

    protected boolean getOutputStreamCalled;

    protected boolean getWriterCalled;


    public CharResponseWrapper(HttpServletResponse response) 
        super(response);
        charWriter = new CharArrayWriter();
    


    @Override
    public ServletOutputStream getOutputStream() throws IOException 
        if (getWriterCalled) 
            throw new IllegalStateException("getWriter already called");
        
        getOutputStreamCalled = true;
        return super.getOutputStream();
    


    @Override
    public PrintWriter getWriter() throws IOException 
        if (writer != null) 
            return writer;
        
        if (getOutputStreamCalled) 
            throw new IllegalStateException("getOutputStream already called");
        
        getWriterCalled = true;
        writer = new PrintWriter(charWriter);
        return writer;
    


    @Override
    public String toString() 
        String s = null;
        if (writer != null) 
            s = charWriter.toString();
        
        return s;
    

Interceptor注册的JavaConfig类:

@Configuration
@EnableJpaRepositories(repositoryBaseClass = BaseRepositoryBean.class, basePackages = "")
@EntityScan(basePackages =  "com.companyname", "com.companyname.productname")
public class RestConfig extends WebMvcConfigurerAdapter 


      @Override
      public void addInterceptors(InterceptorRegistry registry) 
        registry.addInterceptor(new Base64ResponseEncodingInterceptor());
      


使用拦截器的控制器类(此处仅显示工作请求支路):

@Autowired
HttpServletRequest request;

String decodedRequest = null;

@ModelAttribute("decodedRequest")
public void getDecodedParam()
    decodedRequest = (String) request.getAttribute("decodedRequest");
 

postHandle 方法中的代码不起作用。要么是HttpServletResponse,要么是null,或者我收到一条异常消息:

getOutputStream 已经调用

更新:解决直接在 ResponseBodyAdvice 中读取响应的解决方案 在控制器类中,我添加了以下内容:

@RestController
@RequestMapping("/api/ipmanager")
public class IPProfileRestController extends AbstractRestController 

    @Autowired
    HttpServletResponse response;

   String encodedResponse = null;

   @ModelAttribute("encodedResponse")
    public void getEncodedResponse()
       response.setHeader("encodedResponse", StringUtils.EMPTY);
     

    @RequestMapping(value = "/time", method =  RequestMethod.POST , produces =  MediaType.APPLICATION_JSON_VALUE, MediaType.TEXT_PLAIN_VALUE , consumes = 
            MediaType.APPLICATION_JSON_VALUE )
    public @ResponseBody String saveAccessClientTime(@RequestBody String ecodedRequest) 

        // Some code here

        String controllerResponse = prettyJson(iPProfileResponse);
        response.setHeader("encodedResponse", controllerResponse);
        return controllerResponse;
    

我在 ResponseBodyAdvice

中有以下内容
@ControllerAdvice
public class Base64EncodedResponseBodyAdvice implements ResponseBodyAdvice<Object> 

    @Override
    public boolean supports(MethodParameter returnType, 
                            Class<? extends HttpMessageConverter<?>> converterType) 
        return true;
    

    @Override
    public Object beforeBodyWrite(Object body,
                                  MethodParameter returnType,
                                  MediaType selectedContentType,
                                  Class<? extends HttpMessageConverter<?>> converterType,
                                  ServerHttpRequest request,
                                  ServerHttpResponse response) 

        String body1 = StringUtils.EMPTY;
        // Encode the response and return

        List<String> listOfHeaderValues = response.getHeaders().get("encodedResponse");

        body1=new String(Base64.encodeBase64(listOfHeaderValues.get(0).getBytes()));

        return body1;
    


【问题讨论】:

【参考方案1】:

正如Spring MVC documentation 所说:

HandlerInterceptorpostHandle 方法并不总是理想的 适合与@ResponseBodyResponseEntity 方法一起使用。 在这样的 案例HttpMessageConverter 写入并提交响应 在调用 postHandle 之前,这使得无法更改 响应,例如添加标题。相反,应用程序可以 实现ResponseBodyAdvice 并将其声明为 @ControllerAdvicebean 或者直接在上面配置 RequestMappingHandlerAdapter.

话虽如此:

我在这里做错了什么?

由于响应已经提交,您无法更改它。为了更改响应,您应该注册一个 ResponseBodyAdvice&lt;T&gt; 并将您的响应编码逻辑放在那里:

@ControllerAdvice
public class Base64EncodedResponseBodyAdvice implements ResponseBodyAdvice<Object> 
    @Override
    public boolean supports(MethodParameter returnType, 
                            Class<? extends HttpMessageConverter<?>> converterType) 
        return true;
    

    @Override
    public Object beforeBodyWrite(Object body,
                                  MethodParameter returnType,
                                  MediaType selectedContentType,
                                  Class<? extends HttpMessageConverter<?>> converterType,
                                  ServerHttpRequest request,
                                  ServerHttpResponse response) 

        // Encode the response and return
    

【讨论】:

谢谢你,阿里。如果我使用 @ControllerAdvice 进行注释,我是否需要在 JavaConfig 类中注册它,就像请求段的拦截器一样?我将尽快对此进行测试并返回结果。谢谢。 不不需要注册,只要可以扫描就可以了 你好,阿里,我已经把它插入了,它会工作的。我能够通过发送一个在邮递员中打印出来的字符串对象作为响应来覆盖预期的 http 响应。我现在面临的挑战是将我的响应负载作为字符串访问的最佳方式,这样我就可以在将其呈现给客户端之前进行 base64 编码。 response.getBody() 只返回一个 OutputStream。您是否有任何用于从 ServerHttpResponse 检索有效负载正文的 sn-p? ***.com/questions/216894/… 我无法使用上面的链接从 http 响应中检索数据,但我通过在响应标头中设置一个键并在控制器中将响应数据分配给它来解决问题。然后在 ResponseBodyAdvice 中读取该值并对其进行 Base64 编码。我将使用解决方案更新我上面的问题,并将您的答案标记为正确,但我仍然对能够读取数据的 Http 响应而不是将其存储在响应标头中感兴趣。非常感谢你,阿里【参考方案2】:

如果您的方法返回ResponseEntity&lt;T&gt;,则将此代码写入您的HandlerInterceptorpostHandle() 方法中:

例如。 response.addHeader("jwt_token", "kajdlakjd");

它会起作用的!

【讨论】:

以上是关于Spring Boot中无法拦截和操作HttpServletResponse的主要内容,如果未能解决你的问题,请参考以下文章

无法为 Spring Boot 2 应用程序中的拦截器添加配置 WebMvcConfigurer

spring-boot 拦截器不拦截

spring boot中注册拦截器

Spring Boot实现一个监听用户请求的拦截器

Spring Boot中使用过滤器和拦截器

spring-boot添加自定义拦截器