2

I've been struggling with the following issue:

I have a spring boot application which allows a user to post JSON content to an API endpoint. To use this endpoint, the user has to authenticate himself via basic authentication. Moreover, I use OkHttp (3.6.0) as an HTTP client.

Now if I post a large payload (> 4 MB) while being unauthorized, the OkHttp client fails with the following error:

java.net.SocketException: Connection reset by peer: socket write error

To reproduce the issue, I created a minimal client and server:

Server (Spring Boot Web App)

@SpringBootApplication
@RestController
public class App {

    @PostMapping
    public String create(@RequestBody Object obj) {
        System.out.println(obj);
        return "success";
    }

    public static void main(String[] args) {
        SpringApplication.run(App.class);
    }
}

Client (OkHttp 3.6.0)

public class Main {

    public static void main(String[] args) {
        OkHttpClient client = new OkHttpClient
            .Builder()
            .build();

        Request request = new Request.Builder()
            .url("http://localhost:8080")
            .header("Content-Type", "application/json")
            .post(RequestBody.create(MediaType.parse("application/json"), new File("src/main/java/content.json")))
//            .post(RequestBody.create(MediaType.parse("application/json"), new File("src/main/java/content-small.json")))
            .build();

        try {
            Response response = client.newCall(request).execute();
            System.out.println(response);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Instead of the previously mentioned exception ("java.net.SocketException: Connection reset by peer: socket write error"), I would expect the response to be a default error message with HTTP status code 401, e.g. {"timestamp":1508767498068,"status":401,"error":"Unauthorized","message":"Full authentication is required to access this resource","path":"/"}. This is the result I get when using cURL and Postman as clients.

When I'm using less payload (content-small.json; approx. 1KB) instead of the large payload (content.json; approx. 4881KB), I receive the expected response, i.e. Response{protocol=http/1.1, code=401, message=, url=http://localhost:8080/}.

The issue is actually embedded in a larger project with Eureka and Feign clients. Threfore, I would like to continue using OkHttp client and I need the expected behavior.

My problem analysis

Of course, I tried to solve this problem myself for quite some time now. The IOException occurs when the request body is written to the HTTP stream:

if (permitsRequestBody(request) && request.body() != null) {
    Sink requestBodyOut = httpStream.createRequestBody(request, request.body().contentLength());
    BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
    request.body().writeTo(bufferedRequestBody);
    bufferedRequestBody.close();
  }

My assumption is that the server closes the connection as soon as it receives the headers (as the request is unauthorized), but the client continues trying to write to the stream although it is already closed.

Update I've also implemented a simple client with Unirest which shows the same behavior. Implementation:

public class UnirestMain {

    public static void main(String[] args)
        throws IOException, UnirestException {
        HttpResponse response = Unirest
            .post("http://localhost:8080")
            .header("Content-Type", "aplication/json")
            .body(Files.readAllBytes(new File("src/main/java/content.json").toPath()))
//            .body(Files.readAllBytes(new File("src/main/java/content-small.json").toPath()))
            .asJson();

        System.out.println(response.getStatus());
        System.out.println(response.getStatusText());
        System.out.println(response.getBody());
    }
}

Expected output: {"path":"/","error":"Unauthorized","message":"Full authentication is required to access this resource","timestamp":1508769862951,"status":401}

Actual output: java.net.SocketException: Connection reset by peer: socket write error

Thylossus
  • 302
  • 1
  • 3
  • 11
  • Hey, did you figure this out? I'm dealing with the same thing using Retrofit2 with Okhttp3 client configuration. – MikeOscarEcho Oct 27 '17 at 15:27
  • Hi, unfortunately not. I implemented a workaround which prevents unauthorized requests in our environment by preemptively loading credentials for all remote services. Before each request we check the credential cache for credentials and add the `Authorization` header if credentials are available. We ensure that this credential cache is always up-to-date via Eureka. This solution works for us, but does not solve the initial problem stated in the question. – Thylossus Oct 28 '17 at 00:00

0 Answers0