• Spring MVC处理响应的 header


    我们经常需要在HttpResponse中设置一些headers,我们使用Spring MVC框架的时候我们如何给Response设置Header呢?

    So easy, 看下面的代码:

    @RequestMapping(value = "/rulelist", method = RequestMethod.GET)
    
    @ResponseBody
    public String getRuleList(HttpServletRequest request,
            HttpServletResponse response) {
        response.addHeader("test", "test");
        return service.getRuleList();
    }

    通过验证,我们可以看到test项已经被成功添加到response的头部信息

    Content-Length: 2 kilobytes
    Content-Type:   text/plain;charset=ISO-8859-1
    Server: Apache-Coyote/1.1
    test: test

    接下来,我们希望修改Content-Type,从而统一服务器端和客户端的内容编码。我们继续修改代码,

    @RequestMapping(value = "/rulelist", method = RequestMethod.GET)
    @ResponseBody
    public String getRuleList(HttpServletRequest request,
            HttpServletResponse response) {
        response.addHeader("Content-Type", "application/json;charset=UTF-8");
        return service.getRuleList();
    }

    接下来,我们验证一下结果:

    Content-Length: 2 kilobytes
    Content-Type:   text/plain;charset=ISO-8859-1
    Server: Apache-Coyote/1.1

    和我们预想的并一样,response的content-type header没有被设置成"application/json;charset=UTF-8",很令人困惑。

    那么,接下来让我们来探索下Spring MVC内部是如何处理这一过程的。首先我们先要对Spring MVC框架处理Http请求的流程有一个整体的了解。

    下图清晰地向大家展示了Spring MVC处理HTTP请求的流程,(图片来自网络)

     具体流程如下:

    1. DispatcherServlet接收到Request请求

    2. HandlerMapping选择一个合适的Handler处理Request请求

    3-4. 选择合适的HandlerAdapter,调用用户编写的Controller处理业务逻辑。(HandlerAdapter主要是帮助Spring MVC支持多种类型的Controller)

    5. Controller将返回结果放置到Model中并且返回view名称给Handler Adapter

    6. DispatcherServlet选择合适的ViewResolver来生成View对象

    7-8. View对象利用Model中的数据进行渲染并返回数据

    相信大家对于上面的处理流程并不陌生,上面的流程图向我们展示了SpringMVC生成ModelAndView并返回response的大体流程。

    下面我们来看看我们上面代码片段的处理流程是如何进行的?

    从上面的流程图我们可以看到,content-type header是单独被处理的,具体过程可以参考下面的源码(AbstractMessageConverterMethodProcessor):

    protected <T> void writeWithMessageConverters(T returnValue, MethodParameter returnType,
            ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
            throws IOException, HttpMediaTypeNotAcceptableException {
     
        Class<?> returnValueClass = getReturnValueType(returnValue, returnType);
        HttpServletRequest servletRequest = inputMessage.getServletRequest();
        List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(servletRequest); //适合的兼容media types类型实际上,我们可以使用produces = {}来指定我们需要的mediatype
        List<MediaType> producibleMediaTypes = getProducibleMediaTypes(servletRequest, returnValueClass);
     
        Set<MediaType> compatibleMediaTypes = new LinkedHashSet<MediaType>();
        for (MediaType requestedType : requestedMediaTypes) {
            for (MediaType producibleType : producibleMediaTypes) {
                if (requestedType.isCompatibleWith(producibleType)) {
                    compatibleMediaTypes.add(getMostSpecificMediaType(requestedType, producibleType));
                }
            }
        }
        if (compatibleMediaTypes.isEmpty()) {
            if (returnValue != null) {
                throw new HttpMediaTypeNotAcceptableException(producibleMediaTypes);
            }
            return;
        }
     
        List<MediaType> mediaTypes = new ArrayList<MediaType>(compatibleMediaTypes);
        MediaType.sortBySpecificityAndQuality(mediaTypes);
     
        MediaType selectedMediaType = null;   //选择最匹配的mediaType
        for (MediaType mediaType : mediaTypes) {
            if (mediaType.isConcrete()) {
                selectedMediaType = mediaType;
                break;
            }
            else if (mediaType.equals(MediaType.ALL) || mediaType.equals(MEDIA_TYPE_APPLICATION)) {
                selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
                break;
            }
        }
     
        if (selectedMediaType != null) {
            selectedMediaType = selectedMediaType.removeQualityValue();
            for (HttpMessageConverter<?> messageConverter : this.messageConverters) {         //遍历messageConvertors, 寻找可以处理相应返回类型和mediatype的HttpMessageConvertor
                if (messageConverter.canWrite(returnValueClass, selectedMediaType)) {
                    returnValue = this.adviceChain.invoke(returnValue, returnType, selectedMediaType,
                            (Class<HttpMessageConverter<?>>) messageConverter.getClass(), inputMessage, outputMessage);
                    if (returnValue != null) {         //这里将会填充mediatype到header,并将httpmessage发送给请求者
                        ((HttpMessageConverter<T>) messageConverter).write(returnValue, selectedMediaType, outputMessage);
                        if (logger.isDebugEnabled()) {
                            logger.debug("Written [" + returnValue + "] as "" + selectedMediaType + "" using [" +
                                    messageConverter + "]");
                        }
                    }
                    return;
                }
            }
        }
     
        if (returnValue != null) {
            throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes);
        }
    }

    接下来,将选择好的mediatype写入到HttpOutputMessage中

    public final void write(final T t, MediaType contentType, HttpOutputMessage outputMessage)
            throws IOException, HttpMessageNotWritableException {
     
        final HttpHeaders headers = outputMessage.getHeaders();     //设置contenttype到HttpOutputMessage
        if (headers.getContentType() == null) {
            MediaType contentTypeToUse = contentType;
            if (contentType == null || contentType.isWildcardType() || contentType.isWildcardSubtype()) {
                contentTypeToUse = getDefaultContentType(t);
            }
            if (contentTypeToUse != null) {
                headers.setContentType(contentTypeToUse);
            }
        }
        if (headers.getContentLength() == -1) {
            Long contentLength = getContentLength(t, headers.getContentType());
            if (contentLength != null) {
                headers.setContentLength(contentLength);
            }
        }
          /* 省略了不相干代码 */
    }

    最终的Headers设置在ServletServerHttpResponse类中完成,

    private void writeHeaders() {
        if (!this.headersWritten) {
            for (Map.Entry<String, List<String>> entry : this.headers.entrySet()) {
                String headerName = entry.getKey();
                for (String headerValue : entry.getValue()) {         //将复合类中之前设置的header(content-type)内容补充到servletResponse
                    this.servletResponse.addHeader(headerName, headerValue);
                }
            }
            // HttpServletResponse exposes some headers as properties: we should include those if not already present
            if (this.servletResponse.getContentType() == null && this.headers.getContentType() != null) {
                this.servletResponse.setContentType(this.headers.getContentType().toString());
            }
            if (this.servletResponse.getCharacterEncoding() == null && this.headers.getContentType() != null &&
                    this.headers.getContentType().getCharSet() != null) {
                this.servletResponse.setCharacterEncoding(this.headers.getContentType().getCharSet().name());
            }
            this.headersWritten = true;
        }
    }

    从上述的代码中,我们可以看到在RequestResponseBodyMethodProcessor这个ReturnValueHandler中,media-type被单独的逻辑进行处理,因此直接在ServletResponse中设置content-type header并不能正常生效。

    需要在@RequestMapping中添加produces = {} 进行设置才可以。

    参考:https://www.cnblogs.com/kaiblog/p/7565231.html

    我们经常需要在HttpResponse中设置一些headers,我们使用Spring MVC框架的时候我们如何给Response设置Header呢?

    Sooooooooooooo easy, 看下面的代码:

    1
    2
    3
    4
    5
    6
    7
    @RequestMapping(value = "/rulelist", method = RequestMethod.GET)
    @ResponseBody
    public String getRuleList(HttpServletRequest request,
            HttpServletResponse response) {
        response.addHeader("test""test");
        return service.getRuleList();
    }

    通过验证,我们可以看到test项已经被成功添加到response的头部信息

    1
    2
    3
    4
    Content-Length: 2 kilobytes
    Content-Type:   text/plain;charset=ISO-8859-1
    Server: Apache-Coyote/1.1
    test: test

    接下来,我们希望修改Content-Type,从而统一服务器端和客户端的内容编码。我们继续修改代码,

    1
    2
    3
    4
    5
    6
    7
    @RequestMapping(value = "/rulelist", method = RequestMethod.GET)
    @ResponseBody
    public String getRuleList(HttpServletRequest request,
            HttpServletResponse response) {
        response.addHeader("Content-Type""application/json;charset=UTF-8");
        return service.getRuleList();
    }

    接下来,我们验证一下结果:

    1
    2
    3
    Content-Length: 2 kilobytes
    Content-Type:   text/plain;charset=ISO-8859-1
    Server: Apache-Coyote/1.1

    和我们预想的并一样,response的content-type header没有被设置成"application/json;charset=UTF-8",很令人困惑。

    那么,接下来让我们来探索下Spring MVC内部是如何处理这一过程的。首先我们先要对Spring MVC框架处理Http请求的流程有一个整体的了解。

    下图清晰地向大家展示了Spring MVC处理HTTP请求的流程,(图片来自网络)

     

      

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

    具体流程如下:

    1. DispatcherServlet接收到Request请求

    2. HandlerMapping选择一个合适的Handler处理Request请求

    3-4. 选择合适的HandlerAdapter,调用用户编写的Controller处理业务逻辑。(HandlerAdapter主要是帮助Spring MVC支持多种类型的Controller)

    5. Controller将返回结果放置到Model中并且返回view名称给Handler Adapter

    6. DispatcherServlet选择合适的ViewResolver来生成View对象

    7-8. View对象利用Model中的数据进行渲染并返回数据

    相信大家对于上面的处理流程并不陌生,上面的流程图向我们展示了SpringMVC生成ModelAndView并返回response的大体流程。

    下面我们来看看我们上面代码片段的处理流程是如何进行的?

    从上面的流程图我们可以看到,content-type header是单独被处理的,具体过程可以参考下面的源码(AbstractMessageConverterMethodProcessor):

    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
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    protected <T> void writeWithMessageConverters(T returnValue, MethodParameter returnType,
            ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
            throws IOException, HttpMediaTypeNotAcceptableException {
     
        Class<?> returnValueClass = getReturnValueType(returnValue, returnType);
        HttpServletRequest servletRequest = inputMessage.getServletRequest();
        List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(servletRequest); //适合的兼容media types类型实际上,我们可以使用produces = {}来指定我们需要的mediatype
        List<MediaType> producibleMediaTypes = getProducibleMediaTypes(servletRequest, returnValueClass);
     
        Set<MediaType> compatibleMediaTypes = new LinkedHashSet<MediaType>();
        for (MediaType requestedType : requestedMediaTypes) {
            for (MediaType producibleType : producibleMediaTypes) {
                if (requestedType.isCompatibleWith(producibleType)) {
                    compatibleMediaTypes.add(getMostSpecificMediaType(requestedType, producibleType));
                }
            }
        }
        if (compatibleMediaTypes.isEmpty()) {
            if (returnValue != null) {
                throw new HttpMediaTypeNotAcceptableException(producibleMediaTypes);
            }
            return;
        }
     
        List<MediaType> mediaTypes = new ArrayList<MediaType>(compatibleMediaTypes);
        MediaType.sortBySpecificityAndQuality(mediaTypes);
     
        MediaType selectedMediaType = null;   //选择最匹配的mediaType
        for (MediaType mediaType : mediaTypes) {
            if (mediaType.isConcrete()) {
                selectedMediaType = mediaType;
                break;
            }
            else if (mediaType.equals(MediaType.ALL) || mediaType.equals(MEDIA_TYPE_APPLICATION)) {
                selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
                break;
            }
        }
     
        if (selectedMediaType != null) {
            selectedMediaType = selectedMediaType.removeQualityValue();
            for (HttpMessageConverter<?> messageConverter : this.messageConverters) {         //遍历messageConvertors, 寻找可以处理相应返回类型和mediatype的HttpMessageConvertor
                if (messageConverter.canWrite(returnValueClass, selectedMediaType)) {
                    returnValue = this.adviceChain.invoke(returnValue, returnType, selectedMediaType,
                            (Class<HttpMessageConverter<?>>) messageConverter.getClass(), inputMessage, outputMessage);
                    if (returnValue != null) {         //这里将会填充mediatype到header,并将httpmessage发送给请求者
                        ((HttpMessageConverter<T>) messageConverter).write(returnValue, selectedMediaType, outputMessage);
                        if (logger.isDebugEnabled()) {
                            logger.debug("Written [" + returnValue + "] as "" + selectedMediaType + "" using [" +
                                    messageConverter + "]");
                        }
                    }
                    return;
                }
            }
        }
     
        if (returnValue != null) {
            throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes);
        }
    }

    接下来,将选择好的mediatype写入到HttpOutputMessage中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    public final void write(final T t, MediaType contentType, HttpOutputMessage outputMessage)
            throws IOException, HttpMessageNotWritableException {
     
        final HttpHeaders headers = outputMessage.getHeaders();     //设置contenttype到HttpOutputMessage
        if (headers.getContentType() == null) {
            MediaType contentTypeToUse = contentType;
            if (contentType == null || contentType.isWildcardType() || contentType.isWildcardSubtype()) {
                contentTypeToUse = getDefaultContentType(t);
            }
            if (contentTypeToUse != null) {
                headers.setContentType(contentTypeToUse);
            }
        }
        if (headers.getContentLength() == -1) {
            Long contentLength = getContentLength(t, headers.getContentType());
            if (contentLength != null) {
                headers.setContentLength(contentLength);
            }
        }
          /* 省略了不相干代码 */
    }

    最终的Headers设置在ServletServerHttpResponse类中完成,

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    private void writeHeaders() {
        if (!this.headersWritten) {
            for (Map.Entry<String, List<String>> entry : this.headers.entrySet()) {
                String headerName = entry.getKey();
                for (String headerValue : entry.getValue()) {         //将复合类中之前设置的header(content-type)内容补充到servletResponse
                    this.servletResponse.addHeader(headerName, headerValue);
                }
            }
            // HttpServletResponse exposes some headers as properties: we should include those if not already present
            if (this.servletResponse.getContentType() == null && this.headers.getContentType() != null) {
                this.servletResponse.setContentType(this.headers.getContentType().toString());
            }
            if (this.servletResponse.getCharacterEncoding() == null && this.headers.getContentType() != null &&
                    this.headers.getContentType().getCharSet() != null) {
                this.servletResponse.setCharacterEncoding(this.headers.getContentType().getCharSet().name());
            }
            this.headersWritten = true;
        }
    }

    从上述的代码中,我们可以看到在RequestResponseBodyMethodProcessor这个ReturnValueHandler中,media-type被单独的逻辑进行处理,因此直接在ServletResponse中设置content-type header并不能正常生效。

    需要在@RequestMapping中添加produces = {} 进行设置才可以。

  • 相关阅读:
    centos6 下erlang安装
    待研究
    关键字拦截查询
    获取CNVD的cookie
    adb pull 文件夹到电脑
    Linux中查看端口占用情况
    Running Tensorflow on AMD GPU
    验证码识别相关文章
    conda和pip相关操作
    windows安装pycrypto报错
  • 原文地址:https://www.cnblogs.com/unknows/p/8724942.html
Copyright © 2020-2023  润新知