During the last couple of days, I had the task to call some rest endpoints for a backend application. So far, so good, so easy… I thought. However, something looked strange, when I tried to get a JWT authorization token, that I needed to perform other rest calls.

The company that developed the rest endpoint just gave me a curl command and asked me to do the same in Java. The curl command looked like that:

curl -k "https://backend-server/login" \
    --include \
    -H "Accept: application/json" \
    -H "Content-Type: application/json" \
    --data '{ "username": "sampleuser", "password": "samplepassword" }'

I tried it out and received a valid token in the authorization header of the response.

HTTP/1.1 200 OK
Date: Tue, 20 Feb 2018 11:27:22 GMT
Server: Apache/2.4.18 (Ubuntu)
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
Strict-Transport-Security: max-age=31536000 ; includeSubDomains
X-Frame-Options: DENY
Authorization: WioqOja-qfr6d1JtazhL1rl12zOzR7zkyqkQXMqhePIm-rQ...
Set-Cookie: Authorization=eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJleHRlcm5hbF...7zkyqkQXMqhePIm-rQ; Path=/
Transfer-Encoding: chunked

{"status":"success"}

The backend application used spring and so I used the spring RestTemplate to call the authorization endpoint. The code for sending the request with springs RestTemplate looked like that:

public Optional requestAuthenticationToken(String username, String password) {
  HttpHeaders headers = new HttpHeaders();
  headers.setContentType(MediaType.APPLICATION_JSON);
  headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
  RestAuthenticationRequest authRequest = new RestAuthenticationRequest(username, password);
  HttpEntity entity = new HttpEntity<>(authRequest, headers);
  URI uri = null;
  try {
    uri = UriComponentsBuilder
        .fromUri(new URI("http://localhost:9898/login"))
        .build()
        .encode()
        .toUri();
    ResponseEntity responseEntity = restTemplate.exchange(uri,
        HttpMethod.GET,
        entity,
        RestAuthenticationResponse.class
      );
    RestAuthenticationResponse result = responseEntity.getBody();
    log.debug("received auth response: {}", result.getStatus());
    if (responseEntity.getStatusCode().is2xxSuccessful()
        && StringUtils.equalsIgnoreCase(result.getStatus(), "success")) {
      String authToken = responseEntity.getHeaders().getFirst(AUTH_HEADER);
      if (StringUtils.isEmpty(authToken)) {
        log.warn("did not receive an auth token (but got a success http return code).");
      } else {
        return Optional.of(authToken);
      }
    } else {
      log.warn("could not get valid response (response code {} : {})",
          responseEntity.getStatusCodeValue(), responseEntity.getBody());
    }
  } catch (RestClientException e) {
    if (uri != null) {
      log.error("could not make a rest call to {}. {}", uri.toString(), e.getMessage());
    }
  } catch (URISyntaxException e) {
    log.error("could not create a URI.", e.getMessage());
  }
  return Optional.empty();
}

private static class RestAuthenticationRequest {
  private String username;
  private String password;
  ...
}
private static class RestAuthenticationResponse {
  private String status;
  ...
}

Before you simply copy this code and use it in your application, let me just point it out, that it did not work!

From the beginning I thought, that this curl command looked a bit strange, but – as it actually worked – I could not really say why it felt so strange. Then I realized that I have never seen a GET request, that used the body to transport data. GET requests should pass data in form of request parameters (in the Url) or header information. It does not make any sense, to use the body for get requests. Springs RestTemplate did think the same and did not send the data along with in the request. I used a web proxy to investigate the request, that I send to the server and it looked like this

the GET request with spring resttemplate
the GET request with Spring RestTemplate

As you can see, the username and password were not included. Spring just did not send this data, because GET requests should have request parameters not body entities. I was not able to change the authorization service backend, as this was done by another company. I was just a consumer and needed to get this going. Finally I did not use the RestTemplate to call this service but used Apache HttpClient (HttpClient is used by springs RestTemplate and so it was automatically included in our project).

The way of Apache’s HttpClient:

public Optional requestAuthenticationToken(String username, String password)  {
  String json = String.format("{ \"username\": \"%s\", \"password\": \"%s\" }", username, password);
  HttpEntityEnclosingRequestBase httpEntity = new HttpEntityEnclosingRequestBase() {
    @Override
    public String getMethod() {
      return HttpMethod.GET.name();
    }
  };
  httpEntity.addHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON.getType());
  httpEntity.addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON.getType());
  BufferedReader bufferedReader = null;
  try (CloseableHttpClient client = HttpClientBuilder.create().build()) {
    URI authenticationUri = this.websiteConfiguration.getAuthenticationUri(Locale.GERMANY);
    httpEntity.setURI(authenticationUri);
    httpEntity.setEntity(new StringEntity(json, ContentType.APPLICATION_JSON));
    HttpResponse response = client.execute(httpEntity);
    bufferedReader = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
    String responseBody = bufferedReader.readLine();
    bufferedReader.close();
    log.info("auth response code {}", response.getStatusLine().getStatusCode());
    log.info("auth response body {}", responseBody);
    Header authHeader = response.getFirstHeader(HttpHeaders.AUTHORIZATION);
    if (authHeader != null) {
      String token = authHeader.getValue();
      log.info("auth token {}", token);
      return Optional.of(token);
    } else {
      log.warn("could not get auth token from {}", authenticationUri.toString());
    }
  } catch (URISyntaxException e) {
    log.warn("bad URI {}.", e.getMessage());
  } catch (IOException e) {
    log.warn("could not call rest endpoint.", e.getMessage());
  } 
  return Optional.empty();
}

This code should do the same as I the code that used Springs RestTemplate, but this time the request looked like that:

GET request with Apache HttpClient
GET request with Apache HttpClient

This time the data (in the body of the request) was send and I received an authorization token from the rest resource.

The solution can be found in lines 3 – 8 in the java code, where I override the org.apache.http.client.methods.HttpEntityEnclosingRequestBase class of the HttpClient framework. Now I can send the data in the body of a GET request (that was simply ignored by springs RestTemplate).

The Spring way

Before you throw spring out of your projects, here are some code snippets to teach the RestTemplate send some entities along with a GET request:

private RestTemplate authRestTemplate = new RestTemplate();

@PostConstruct
public void init() {
  this.authRestTemplate.setRequestFactory(new HttpComponentsClientHttpRequestWithBodyFactory());
}
  
private static final class HttpComponentsClientHttpRequestWithBodyFactory extends HttpComponentsClientHttpRequestFactory {
  @Override 
  protected HttpUriRequest createHttpUriRequest(HttpMethod httpMethod, URI uri) { 
    if (httpMethod == HttpMethod.GET) { 
      return new HttpGetRequestWithEntity(uri); 
    } 
    return super.createHttpUriRequest(httpMethod, uri); 
  }
}

private static final class HttpGetRequestWithEntity extends HttpEntityEnclosingRequestBase {
  public HttpGetRequestWithEntity(final URI uri) {
    super.setURI(uri);
  }
  
  @Override
  public String getMethod() {
    return HttpMethod.GET.name();
  }
}

All you need is to create a custom RequestFactory and apply it to the RestTemplate. The RequestFactory uses the HttpGetRequestWithEntity with every Get request.

 

By Meik Kaufmann

I am a certified oracle enterprise architect and like to share my knowledge and experience that I gain in the projects I am working on.

4 thoughts on “Get requests with a request body: Spring RestTemplate vs Apache HttpClient”
  1. This is fantastic, thank you so much for your help with this – I’ve been battling this same problem for hours (trying to make a call to a third-party API that I don’t have control over). This definitely helped – thanks for posting this! (I’m not interested in sending my name/email, but wanted to give you a thumbs up for your help).

  2. That’s amazing, thank you! I did encounter a problem though: query strings don’t seem to work properly with this solution. But this can be fixed by adding another constructor to the HttpGetRequestsWithEntity class:

    /**
    * @throws IllegalArgumentException if the uri is invalid.
    */
    public HttpGetRequestWithEntity(final String uri) {
    super();
    setURI(URI.create(uri));
    }

Leave a Reply

Your email address will not be published. Required fields are marked *