Spring MVC處理響應的 header

咱們常常須要在HttpResponse中設置一些headers,咱們使用Spring MVC框架的時候咱們如何給Response設置Header呢?javascript

So easy, 看下面的代碼:html

@RequestMapping(value = "/rulelist", method = RequestMethod.GET)

@ResponseBody
public String getRuleList(HttpServletRequest request,
        HttpServletResponse response) {
    response.addHeader("test", "test");
    return service.getRuleList();
}

 

經過驗證,咱們能夠看到test項已經被成功添加到response的頭部信息java

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

 

接下來,咱們但願修改Content-Type,從而統一服務器端和客戶端的內容編碼。咱們繼續修改代碼,json

@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請求的流程有一個總體的瞭解。app

下圖清晰地向你們展現了Spring MVC處理HTTP請求的流程,(圖片來自網絡)框架

 具體流程以下:this

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 = {} 進行設置才能夠。

相關文章
相關標籤/搜索