Content negotiation in SpringBoot subverts your cognition

Posted by techrat on Sun, 27 Feb 2022 10:41:04 +0100

Hello, everyone. I'm a passer-by. This is Chapter 32 of spring MVC series.

This article will introduce the content negotiation in spring MVC. Some friends may have heard of it, but those who haven't heard of it may feel strange. Anyway, let me tell you a little first. This article is a very important knowledge point. Don't make mistakes. If you insist on reading it, you will get a lot of harvest. There is a pdf version at the end, which you need to get by yourself.

catalogue

  • 1. Preparatory knowledge
  • 2. Let's do a test first
    • 2.1 test scenario 1
    • 2.2. Conclusion 1: the return value is affected by the server
    • 2.3 test scenario 2
    • 2.4. Conclusion 2: the return value is affected by the client Accept header
    • 2.5 summary
  • 3. Why?
    • 3.1. This is determined through consultation
    • 3.2. It brings two problems
  • 4. How can the client tell the server what type of content it can accept?
    • 4.1. Two common methods
    • 4.2. It brings two more problems
  • 5. What is a media type (MimeType or MediaType)?
    • 5.1 interpretation
    • 5.2 MimeType format
    • 5.3 common MimeType examples
    • 5.4 application of MimeType in http request
    • 5.5. Special parameter q: specify MimeType priority
  • 6. What is the http request header Accept like?
    • 6.1. Accept function
    • 6.2. Accept format
  • 7. MediaType utility class in Spring
    • 7.1 common constants
    • 7.2 common methods
    • 7.3 sorting rules
  • 8. Media types that the server can respond to
    • 8.1 the server has three ways to specify the media type of response
    • 8.2. Method 1: @ RequestMapping annotation produces attribute
    • 8.3. Mode 2: response Setheader ("content type", "media type");
    • 8.4. Method 3: the internal mechanism of spring MVC automatically determines the list of media types that can respond
    • 8.5. Mode 3 source code interpretation
  • 9. Summary
  • 10. Case code git address
    • 10.1. git address
    • 10.2 description of case code structure in this paper
  • 11. Spring MVC series directory
  • 12. More series
  • 13. Latest information

1. Preparatory knowledge

Interface test sharp tool HTTP Client

2. Let's do a test first

Think about what the following spring MVC interface will output?

@RequestMapping(value = "/cn/test1")
@ResponseBody
public List<String> test1() {
    List<String> result = Arrays.asList(
        "Lau Andy",
        "Xue You Zhang",
        "Guo Fucheng",
        "dawn");
    return result;
}

The code is very simple. The response body annotation is marked on the method, and the following json format data will be output.

Access this interface in the browser, and the effect is as follows

2.1 test scenario 1

Please add the following content to the maven configuration of the project, and then try what will be output

<!-- add to jackson to configure -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>2.11.4</version>
</dependency>

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.11.4</version>
</dependency>

<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-xml</artifactId>
    <version>2.11.4</version>
</dependency>

Visit again in the browser. The effect is as follows

I pour, what is this? It's a little strange. Right click the current page of the browser to view the source code. As follows, how does it become xml???

2.2. Conclusion 1: the return value is affected by the server

From the above, we can see that we just adjusted the configuration of maven on the server side. At this time, the return result of the interface has changed from json format to xml format.

Here comes the first conclusion: the format of the return value of the interface is affected by the server.

2.3 test scenario 2

We use Http Client in idea to access the above interface. The effect is as follows. The returned result is still data in xml format.

###
GET http://localhost:8080/chat22/cn/test1

Let's adjust the calling code, add a line of code, add an Accept: application/json in the request, change it to the following, and then look at the effect again, as shown in the figure below. How does the result become json this time.

###
GET http://localhost:8080/chat22/cn/test1
Accept: application/json

The only difference between these two requests is that the code of Accept: application/json is added for the second time, and then the result becomes json, indicating that the response result has been affected by this header.

Let's look back at the request of the browser. What is the Accept in its request header? As shown in the figure below, I extracted the content. The following code looks strange. What is this? We will elaborate later.

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9

2.4. Conclusion 2: the return value is affected by the client Accept header

In scenario 2, we just adjusted the Accept in the http request header, and the response results are different.

Here comes the second conclusion: the return value is affected by the client Accept header.

2.5 summary

As can be seen from the above, the format of the response result is affected by the server and the client, and is determined by both.

3. Why?

3.1. This is determined through consultation

The server and the requester negotiate to decide what format of content to return.

When the client sends a request, it can tell the server that it wants the other party to return a list of data formats, and the server interface also has its own list of response formats that it can support. The final return result will find a type that can be supported by both sides according to the two type lists. If it cannot find the appropriate one, an error will be reported.

For example, the server can respond to the data in json and xml format, and when the browser sends the request, it tells the server that I can receive the data in html and json format, and then it will eventually return the data in json format, which can be supported by both.

Another example: the server can respond to data in json and html format. When the client sends an http request, it says it wants to accept data in xml format. At this time, the server is unable to return data in xml format, and an error will be reported in the end.

If you still don't understand, a more popular explanation:

  • Xiao Ming asks Xiao Wang to introduce his girlfriend. Xiao Ming says that those who can meet these requirements can [rich, beautiful and humorous]. Xiao Wang collects the resources around him and finds that there are no rich and beautiful ones, but there are humorous ones. Then he introduces the humorous ones to Xiao Ming. If Xiao Wang does not meet these conditions, he can't introduce his girlfriend to Xiao Ming.
  • Xiao Ming asks Xiao Wang to introduce his girlfriend. Xiao Ming says that those who can meet these needs can be [rich, beautiful and humorous]. If they can all be met, give priority to the rich, then beautiful and then humorous. Xiao Wang collects the resources around him and finds that there are all rich, beautiful and humorous, and then according to Xiao Ming's needs, Introduce the rich one with the highest priority to him. Xiao Ming is happy, ha ha.

3.2. It brings two problems

  • How can the client tell the server what type of content it can accept?
  • How does the interface developed on the server specify the type of response?

4. How can the client tell the server what type of content it can accept?

4.1. Two common methods

  • Method 1: use Accept in the http request header to specify the type that the client can receive (also known as media type)
  • Method 2: customized method For example, the suffix of the request address, test1 xml,test1.json, which specifies the class content type by suffix For example, a parameter, such as format, can be added to the request to specify the content type that can be received

Both of these two methods are implemented in spring MVC. The first method is enabled by default in spring MVC, and the support of these two methods is enabled by default in spring boot. This paper mainly explains the first method, and the second method will be introduced in detail in the subsequent spring boot series.

4.2. It brings two more problems

  • Question 1: what is the type of media
  • Question 2: what is the http request header Accept like?

5. What is a media type (MimeType or MediaType)?

5.1 interpretation

Simply understand, media type is the format used to represent content, such as the format that can be used to represent the content of http request body and response body.

English Title: MineType or MediaType

5.2 MimeType format

  • Format: type/subtype; Parameter 1 = value 1; Parameter 2 = value 2; Parameter n = value n
  • Type: indicates the primary type
  • subtype: indicates the type of sub column
  • Types and parameters are separated by semicolons
  • There can be many parameters, and multiple parameters are separated by English semicolons

5.3 common MimeType examples

MimeType

explain

application/json

Represents json formatted data

application/json; charset=UTF-8

Represents json format data, followed by an encoding parameter

text/plain

Represents plain text content

text/html

Represents content in html format

text/html;charset=utf-8

Represents html, utf-8 encoding

application/json; q=1

It represents json format data and has a q parameter. This parameter is special and indicates priority

5.4 application of MimeType in http request

(1) Request header content type: used to specify the format of the content in the request body.

For example, content type: application / json is used to tell the server that the content of the client request body is in json format, so that the server can parse the content in the request body in json format

(2) Request header Accept: used to tell the server the media type that the client can receive.

Accpet consists of multiple mimetypes separated by English commas. For example: Accept:text/html,text/xml,application/json. This tells the server that the client can receive data in three formats, and the server can choose a format to respond according to its own ability

(3) Response header content type: used to inform the client of the format of the class content in the response body.

For example: content type: text / html, which means that the content of the response is in html format. At this time, the browser will display the content in html; The browser will make different display effects according to different formats

(4) Detailed explanation of content type in Http

Content type in Http is a very important thing. Friends who don't know it suggest going here to learn about it first: http://itsoku.com/article/199

5.5. Special parameter q: specify MimeType priority

When there are multiple media types together, the q parameter can be added to the media to specify the priority of the media type. The q value ranges from 0.0 to 1.0 (1.0 has the highest priority)

For example, the Http request header Accept can specify multiple media types, so the q parameter can be added to the media type to specify the priority of the media type, and the server preferentially selects the format with high media type for response.

For example: Accept: text/html;q=0.8,text/xml;q=0.6,application/json;q=0.9, which tells the server that the client wants to return the content of these three types. If the server supports these three types, the type with high Q value will be returned first.

Let's go back to the case at the beginning and look at the Accept value in the request header of this case in the browser

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9

6. What is the http request header Accept like?

6.1. Accept function

It is used to specify the media type that the client can receive and to tell the server what format data the client wants the server to return

6.2. Accept format

  • Media type 1, media type 2, media type 3
  • Multiple media are separated by commas
  • In media types, you can use the q parameter to mark their priority. The range of q value is from 0.0 to 1.0 (1.0 has the highest priority)
  • The article is a little long. Thank you for your reading. If you have any harvest, please share it. Passers-by thank you here.

Let's go on.

7. MediaType utility class in Spring

spring provides a tool class org. To facilitate the operation of media types springframework. http. MediaType. MediaType provides many common MediaType constants and common methods.

7.1 common constants

public static final String APPLICATION_JSON_VALUE = "application/json"; //json
public static final String TEXT_PLAIN_VALUE = "text/plain"; //text
public static final String TEXT_HTML_VALUE = "text/html"; //html
public static final String APPLICATION_XML_VALUE = "application/xml"; //xml
public static final String IMAGE_GIF_VALUE = "image/gif"; //gif picture
public static final String IMAGE_JPEG_VALUE = "image/jpeg"; //jpeg image
public static final String APPLICATION_PDF_VALUE = "application/pdf";//pdf format
public static final String APPLICATION_FORM_URLENCODED_VALUE = "application/x-www-form-urlencoded"; //Format of content submitted by ordinary form
public static final String MULTIPART_FORM_DATA_VALUE = "multipart/form-data";//Content format of upload file form submission

7.2 common methods

method

explain

static MediaType parseMediaType(String mediaType)

Parse text to MediaType

static List<MediaType> parseMediaTypes(@Nullable String mediaTypes)

Parse text into a MediaType list

static String toString(Collection<MediaType> mediaTypes)

Resolves the MediaType list to a string

static void sortBySpecificityAndQuality(List<MediaType> mediaTypes)

Sort multiple mediatypes, and the internal will be sorted according to the q parameter

boolean includes(@Nullable MediaType other)

Judge whether the current MediaType contains other specified in the parameter. For example, the current one is: * / *, which is a wildcard type, so it can match all types

7.3 sorting rules

Within the SpringMVC, the list of media types that can be supported by the client Accept and the list of media types that can be supported by the server-side interface are processed internally. After that, a list of media types supported by both sides is obtained, then the MediaType#sortBySpecificityAndQuality method is used to sort up the ascending order. Finally, the highest priority is selected to return.

Let's analyze why the first case returns data in xml format

Accept sent by browser:

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9

Media types supported by the server interface:

application/xml;charset=UTF-8, text/xml;charset=UTF-8, application/*+xml;charset=UTF-8, application/json, application/*+json

The two take the intersection, and finally get the media types supported by both sides:

application/xhtml+xml, application/xml;charset=UTF-8;q=0.9, application/xml;q=0.9, application/xml;charset=UTF-8;q=0.8, text/xml;charset=UTF-8;q=0.8, application/*+xml;charset=UTF-8;q=0.8, application/json;q=0.8, application/*+json;q=0.8

Then we call the MediaType#sortBySpecificityAndQuality method to sort it and get it.

Tip: sorting rules: type is in front, wildcard * is in the back, and Q is in front. If q is not specified, it means that Q is 1

application/xhtml+xml, application/xml;charset=UTF-8;q=0.9, application/xml;q=0.9, application/xml;charset=UTF-8;q=0.8, text/xml;charset=UTF-8;q=0.8, application/json;q=0.8, application/*+xml;charset=UTF-8;q=0.8, application/*+json;q=0.8

Then take the first as the final return type:

Content-Type: application/xhtml+xml;charset=UTF-8

As shown in the following figure, it is indeed consistent with the results in the browser

8. Media types that the server can respond to

8.1 the server has three ways to specify the media type of response

  • Method 1: @ RequestMapping annotation produces attribute
  • Mode 2: response Setheader ("content type", "media type");
  • Method 3: if neither of the above two methods is specified, the internal mechanism of spring MVC will automatically determine the list of media types that can respond

8.2. Method 1: @ RequestMapping annotation produces attribute

(1) Explain

@The RequestMapping annotation has a produces attribute, which is used to specify the media type that the current interface can respond to and can also be understood as the media type that the interface can handle. Other annotations @ PostMapping/@GettMapping/@PutMapping/@DeleteMapping/@PatchMapping/@PatchMapping also have this attribute, which has the same function; Here, the @ RequestMapping annotation has a produces attribute to illustrate.

(2) Case

For example, if the interface is required to only return data in json format, it can be written like this

@RequestMapping(value = "/cn/test1", produces = {"application/json"})
@ResponseBody
public List<String> testProduct() {
    List<String> result = Arrays.asList(
            "Lau Andy",
            "Xue You Zhang",
            "Guo Fucheng",
            "dawn");
    return result;
}

Test scenario 1: the browser directly accesses and returns json format data

Test scenario 2: the header Accept is specified as applicaiton/xml, and 406 occurs, which cannot be processed by the server. That is because the client only wants the server to return data in application/xml format, while the server interface can only return data in application/json format. Before the request reaches the interface, it is intercepted by spring MVC and rejected

8.3. Mode 2: response Setheader ("content type", "media type");

This method directly ignores the requirements of your client. No matter what the client's Accept is, the server will directly return the specified type. For example, the following code will only return data in xml format regardless of the client's Accept value.

@RequestMapping(value = "/cn/contenttype")
public void testContentType(HttpServletResponse response) throws IOException {
    //Specifies the type of result for the response
    response.setHeader("Content-Type", "application/xml");
    response.getWriter().write(
            "<List>" +
                    "<item>Lau Andy</item>" +
                    "<item>Xue You Zhang</item>" +
                    "<item>Guo Fucheng</item>" +
                    "<item>dawn</item>" +
                    "</List>");
    response.getWriter().flush();
}

8.4. Method 3: the internal mechanism of spring MVC automatically determines the list of media types that can respond

The following code is used to negotiate the final returned media type by combining the Accpet in the request header within spring MVC.

@RequestMapping(value = "/cn/auto")
@ResponseBody
public List<String> testAuto(HttpServletResponse response) throws IOException {
    List<String> result = Arrays.asList(
            "Lau Andy",
            "Xue You Zhang",
            "Guo Fucheng",
            "dawn");
    return result;
}

For example, what you Accept is application/xml, which means that the client wants to return data in xml format.

Aceept passes application/json, which means that if the client wants to return data in json format, the returned data is in json format.

This code raises a question: what types of media can this code respond to? Have you thought about this problem

The method or class is annotated with @ ResponseBody annotation. Usually, the return value of this interface will be returned by org. In spring MVC springframework. web. servlet. mvc. method. annotation. Requestresponsebodymethodprocessor #handlereturnvalue is used to process.

This method will find all message converters (org.springframework.http.converter.HttpMessageConverter) in the current spring MVC container. There is a getsupportedmediateypes method in the message converter.

List<MediaType> getSupportedMediaTypes();

This method will return the media types that the current converter can support, indicating that the converter can convert the content into data in these media type formats, and then respond to the client. For example, the return value of the above interface is a List, and then throw it to the HttpMessageConverter of xml, which will be converted into data in xml format and output to the client.

Spring MVC will call the getsupportedmediateypes method of these httpmessageconverters to get a list of media types, which is the corresponding media types of the current interface.

Then, combined with the Accept in the http header, we can get a media type that can be accepted by both sides.

Then sort.

Then take the best one, usually the first one after sorting, as the media type of the final response. This media type will correspond to an HttpMessageConverter, and then use the HttpMessageConverter to convert the return value of the interface into data in the specified media type format, such as xml format, json format, etc., and then output it to the response body.

8.5. Mode 3 source code interpretation

Mode 3 is the most commonly used mode, followed by mode 1. Mode 2 is less used. Here we will interpret the source code of mode 3 in order to deepen our understanding.

The negotiation of contents will be involved in mode 3, and the process is roughly as follows

  • step1: get the list of media types that can be received by the client: obtained by parsing the request header Accpet
  • Step 2: get the list of media types that the server can respond to: traverse the getSupportedMediaTypes method of all httpmessageconverters to get a list of media types
  • Step 3: get a list of media types approved by both parties according to the list of media types supported by both parties
  • Step 4: sort the list of media types supported by both parties in step
  • Step 5: select an appropriate media type as the response
  • step6: according to the return value of the interface and the MediaType obtained by step5, match to the appropriate HttpMessageConverter, then call the write method of HttpMessageConverter, and internally convert the content to the specified format output.

The code of this process is in the following method. You can set a breakpoint and then request the / cn/auto interface in mode 3 to enter this method.

org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor#writeWithMessageConverters(T, org.springframework.core.MethodParameter, org.springframework.http.server.ServletServerHttpRequest, org.springframework.http.server.ServletServerHttpResponse)

Step 1: get the list of media types supported by the client

Get the list of media types that the client can receive: obtained by parsing the request header

Step 2: get the list of media types that the server can respond to

The corresponding codes are as follows

List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType);

Next, enter the getProducibleMediaTypes method

protected List<MediaType> getProducibleMediaTypes(
    HttpServletRequest request, Class<?> valueClass, @Nullable Type targetType) {
 //This value is taken from the products attribute of @ RquestMapping. If there is any, it is taken directly
    Set<MediaType> mediaTypes =
        (Set<MediaType>) request.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
    if (!CollectionUtils.isEmpty(mediaTypes)) {
        return new ArrayList<>(mediaTypes);
    }
    //Traverse HttpMessageConverter and call its canWrite method to determine whether it can process the return value of the current interface method. For example, the current interface is list < string >
    //If it can be processed, call its getSupportedMediaTypes method to get the media type list
    List<MediaType> result = new ArrayList<>();
    for (HttpMessageConverter<?> converter : this.messageConverters) {
        if (converter instanceof GenericHttpMessageConverter && targetType != null) {
            if (((GenericHttpMessageConverter<?>) converter).canWrite(targetType, valueClass, null)) {
                result.addAll(converter.getSupportedMediaTypes(valueClass));
            }
        }
        else if (converter.canWrite(valueClass, null)) {
            result.addAll(converter.getSupportedMediaTypes(valueClass));
        }
    }
    //If the above media type is empty, * / * media type will be returned; otherwise, the list of found media types will be returned
    return (result.isEmpty() ? Collections.singletonList(MediaType.ALL) : result);
}

Let's take a screenshot of HttpMessageConverter. Here are 8

HttpMessageConverter

Supported mediatypes

Supported interface return value types

explain

StringHttpMessageConverter

text/plain,*/*

String

Return plain text

ByteArrayHttpMessageConverter

application/octet-stream

byte[]

Return byte stream

FormHttpMessageConverter

application/x-www-form-urlencodedmultipart/form-datamultipart/mixed

MultiValueMap<String, ?>

Output the content in the content format submitted by the form

ResourceHttpMessageConverter

*/*

org.springframework.core.io.Resource

Resource is used to represent various resources, which can be used to download files

MappingJackson2HttpMessageConverter

application/jsonapplication/*+json

Any type that can be converted to json format by jackson tool

This is the response json uses

MappingJackson2XmlHttpMessageConverter

application/xmltext/xmlapplication/*+xml

Any type that can be converted to xml format by jackson xml tool

This is what is used to respond to xml

The last two converters in the above list are in the following packages, so after adding these configurations, spring MVC has the ability to handle json and xml. Here is also the answer to the question at the beginning of this article.

After the getProducibleMediaTypes method is executed, the list of media types that the server can respond to is obtained

Step 3: get a list of media types approved by both parties according to the list of media types supported by both parties

Step 4: sort the list of media types supported by both parties in step

Step 5: select an appropriate media type as the response

As follows, the sorted list will be traversed, and then traversed to take the first specific media type, mediatype Is isconcrete () used to climb south a specific type? The specific type is a type that does not contain wildcard * inside

step6: match to the appropriate HttpMessageConverter and convert the result to the specified format for output

The code is as follows: according to the return value of the interface and the MediaType obtained by step5, it is matched to the appropriate HttpMessageConverter, then the HttpMessageConverter write method is invoked, and the contents are converted to the specified format output.

if (selectedMediaType != null) {
    selectedMediaType = selectedMediaType.removeQualityValue();
    for (HttpMessageConverter<?> converter : this.messageConverters) {
        GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ?
                                                        (GenericHttpMessageConverter<?>) converter : null);
        if (genericConverter != null ?
            ((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) :
            converter.canWrite(valueType, selectedMediaType)) {
            body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
                                               (Class<? extends HttpMessageConverter<?>>) converter.getClass(),
                                               inputMessage, outputMessage);
            if (body != null) {
                Object theBody = body;
                LogFormatUtils.traceDebug(logger, traceOn ->
                                          "Writing [" + LogFormatUtils.formatValue(theBody, !traceOn) + "]");
                addContentDispositionHeader(inputMessage, outputMessage);
                if (genericConverter != null) {
                    genericConverter.write(body, targetType, selectedMediaType, outputMessage);
                }
                else {
                    ((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);
                }
            }
            else {
                if (logger.isDebugEnabled()) {
                    logger.debug("Nothing to write: null body");
                }
            }
            return;
        }
    }
}

9. Summary

The content of this article is a very, very important knowledge point. I suggest you read it twice, knock it + debug, test it, and it's easier to master it; After mastering these, we can make better use of spring MVC and spring boot.

10. Case code git address

10.1. git address

https://gitee.com/javacode2018/springmvc-series

10.2 description of case code structure in this paper