Short Note: when Spring Boot and Privoxy don't like each other

I figured out after full day of work a very peculiar behavior in Spring Boot and wanted to write this one down since it was a pretty annoying one.

Let’s say you are:

  • using Spring Boot and need to make HTTP client connections (using RestTemplate or RestClient, etc) to some server

  • you innocently want to use out-of-the-box Marshaller like Jackson for example (because why not)

  • you must utilize HTTP proxy between your app and the target server

    • proxy causing issues is Privoxy, but… hey who knows if some others are (not) impacted
  • that HTTP proxy must (for some very important enterprisey reason) inspect HTTPS connections

You might notice that all clients fail in this scenario with an early EOF: Jetty, Apache HTTP Client, native JDK client… all of them.

// run with 
// -Dhttps.proxyHost=my.very.nice.proxy.local
// -Dhttps.proxyPort=my.innocent.proxy

ResponseEntity<LoginResponse> response = restClient.post()
  .uri("https://myserver.com")
  .body(new LoginRequest("username", "password"))
  .retrieve()
  .toEntity(LoginResponse.class);

// Caused by: org.springframework.web.client.ResourceAccessException: 
// I/O error on POST request for "https://myserver.com": 
// HttpConnectionOverHTTP@6dc73294::SslEndPoint@7c52e37e[{...}]
// Caused by: java.io.EOFException: HttpConnectionOverHTTP@6dc73294::SslEndPoint@7c52e37e[{...}]

After some time I figured out it was the marshaller that was flushing the connection. This is never a problem, mind you - but if HTTPS inspection is being used, the request is sent and this triggers Privoxy to flip and not try to send the response back and just… EOFing you as well :)

The fix is straight-forward: avoid flush() done by the marshaller and just send the body transformed into string yourself:

// run with 
// -Dhttps.proxyHost=my.very.nice.proxy.local
// -Dhttps.proxyPort=my.innocent.proxy

ResponseEntity<LoginResponse> response = restClient.post()
  .uri("https://myserver.com")
  .body(objectMapper.writeValueAsString(new LoginRequest("username", "password")))
  .retrieve()
  .toEntity(LoginResponse.class);

Last words

Is this now a bug in Privoxy or in Spring? I think it’s in Spring since cURL doesn’t make this assumption that flush is safe. All the clients, used directly, also don’t have this issue - the proxy terminates connection correctly after sending the response. If you just turn off the marshaller in Spring behavior is correct.