简体   繁体   中英

Spring REST template accept headers

During some load testing of one of our REST services, we start seeing these kind of logs for Spring's REST template when the load increases:

Under a concurrent load and after 3-4 hours, the Accept header of the http request becomes

DEBUG: org.springframework.web.client.RestTemplate - Setting request Accept header to [text/plain, application/json, application/*+json, text/plain, text/plain, text/plain, text/plain, text/plain, text/plain, text/plain, text/plain, text/plain, text/plain, text/plain, text/plain, text/plain, text/plain, text/plain, text/plain, text/plain, text/plain, text/plain, text/plain, text/plain, text/plain, text/plain, text/plain, text/plain, text/plain, text/plain,<and so on>, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, */*, <and so on>]

Eventually all calls to this service using RestTemplate start failing with 400 Error (Bad Request)

The REST service being called accepts a String as input and has the following signature

@RequestMapping(value = "/findRecordById", method = {RequestMethod.POST, RequestMethod.GET })
@ResponseBody
public String findRecordById(@RequestBody String id) {//method body}

We are sending POST type of requests to this service with request content of the form "someId", Eg "123"

Under light load, there are no issues in calling the service.

Whats puzzling is the text/plain, */* that keep getting added to the list of accept headers for the REST template. Why does this happen?

The REST template bean declaration is like this:

<bean id="restTemplate" class="org.springframework.web.client.RestTemplate">
        <constructor-arg>
            <bean class="org.springframework.http.client.HttpComponentsClientHttpRequestFactory">
                <property name="readTimeout">
                    <value>90000</value>
                 </property>
                <property name="httpClient" ref="restHttpClient" />
            </bean>
        </constructor-arg>
    </bean>

    <bean id="restHttpClient"  class="org.apache.http.impl.client.DefaultHttpClient">
          <constructor-arg> 
            <bean class="org.apache.http.impl.conn.PoolingClientConnectionManager">
                <property name="defaultMaxPerRoute">
                    <value>100000</value>
                 </property>
                <property name="maxTotal">
                    <value>100000</value>
                 </property>                 

            </bean>
          </constructor-arg>
    </bean>

How the request is being created:

String postParams = "\"" + id + "\"";

String postResp = restTemplate.postForObject("findRecordById",postParams, String.class);

In case anybody comes here because of the repeated text/plain Accept header problem that the poster had, I experienced the same thing and here's what was happening: We had our usual bean definition for the rest template in servlet-context.xml where we specified a message converter for application/json like so (this is for spring-beans 4.0):

<beans:bean id="myRestTemplate" class="com.mypackage.MyClass">
        <beans:property name="requestFactoryNonSSL" ref="restTemplateNonSSLRequestFactory"/>
        <beans:property name="requestFactorySSL" ref="restTemplateNonSSLRequestFactory"/>
        <beans:property name="messageConverters">
            <beans:list>
                <beans:bean class="org.springframework.http.converter.StringHttpMessageConverter">
                    <beans:property name="supportedMediaTypes">
                        <beans:list>
                            <beans:value>application/json;charset=UTF-8</beans:value>
                        </beans:list>
                    </beans:property>
                </beans:bean>           
            </beans:list>
        </beans:property>
    </beans:bean>

However in in the source code we also were explicitly adding a StringHttpMessageConverter using:

restTemplate.getMessageConverters().add(new StringHttpMessageConverter());

However this messageConverter list was just getting a new instance of StringHttpMessageConverter added to it on every request. For each request Spring goes through that message converter list and adds the corresponding Accept header (text/plain). After so many requests this causes the header length to grow so large that it will get rejected by the server container you're calling. The simplest way to fix this was to just specify the text/plain as a supportedMediaTypes in the servlet-context.xml and remove the above line in the code. If you can't do this you need to put a check in the code to make sure that StringHttpMessageConverter isn't repeatedly getting added to the restTemplate instance.

Here's the servlet-context.xml with the text/plain supportedMediaType added:

<beans:bean id="myRestTemplate" class="com.mypackage.MyClass">
        <beans:property name="requestFactoryNonSSL" ref="restTemplateNonSSLRequestFactory"/>
        <beans:property name="requestFactorySSL" ref="restTemplateNonSSLRequestFactory"/>
        <beans:property name="messageConverters">
            <beans:list>
                <beans:bean class="org.springframework.http.converter.StringHttpMessageConverter">
                    <beans:property name="supportedMediaTypes">
                        <beans:list>
                            <beans:value>application/json;charset=UTF-8</beans:value>
                            <beans:value>text/plain</beans:value>
                        </beans:list>
                    </beans:property>
                </beans:bean>           
            </beans:list>
        </beans:property>
    </beans:bean>

Could you please try this:

restTemplate.getMessageConverters().add(new StringHttpMessageConverter());

String postParams = "\"" + id + "\"";

String postResp = restTemplate.postForObject("findRecordById",postParams, String.class);

text/plain is added because you try to read a String and the RestTemplate, found StringHttpMessageConverter as converter for your request, and the supported media type for StringHttpMessageConverter is text/plain.

As you can see in this method of RestTemplate.

public void doWithRequest(ClientHttpRequest request) throws IOException {
            if (responseType != null) {
                List<MediaType> allSupportedMediaTypes = new ArrayList<MediaType>();
                for (HttpMessageConverter<?> messageConverter : getMessageConverters()) {
                    if (messageConverter.canRead(responseType, null)) {
                        List<MediaType> supportedMediaTypes = messageConverter.getSupportedMediaTypes();
                        for (MediaType supportedMediaType : supportedMediaTypes) {
                            if (supportedMediaType.getCharSet() != null) {
                                supportedMediaType =
                                        new MediaType(supportedMediaType.getType(), supportedMediaType.getSubtype());
                            }
                            allSupportedMediaTypes.add(supportedMediaType);
                        }
                    }
                }
                if (!allSupportedMediaTypes.isEmpty()) {
                    MediaType.sortBySpecificity(allSupportedMediaTypes);
                    if (logger.isDebugEnabled()) {
                        logger.debug("Setting request Accept header to " + allSupportedMediaTypes);
                    }
                    request.getHeaders().setAccept(allSupportedMediaTypes);
                }
            }
        }
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM