Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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", E.g. "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);
like image 953
Nikhil Talreja Avatar asked Nov 05 '14 02:11

Nikhil Talreja


2 Answers

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);
                }
            }
        }
    }
like image 77
Adrian Duta Avatar answered Sep 18 '22 05:09

Adrian Duta


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>
like image 29
Ashley Waldron Avatar answered Sep 22 '22 05:09

Ashley Waldron