Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Connection reset exception in a Spring Web MVC controller method is ignored #34264

Open
forketyfork opened this issue Jan 15, 2025 · 6 comments · May be fixed by #34269
Open

Connection reset exception in a Spring Web MVC controller method is ignored #34264

forketyfork opened this issue Jan 15, 2025 · 6 comments · May be fixed by #34269
Labels
status: waiting-for-triage An issue we've not yet triaged or decided on

Comments

@forketyfork
Copy link

forketyfork commented Jan 15, 2025

Consider a Spring Web MVC controller method that calls some downstream http service:

@PostMapping("/hello")
public String hello() {
    return new RestTemplate().postForObject("http://localhost:8888", "hello", String.class);
}

Starting from Spring Boot 3.4.0 (Spring Web 6.2.0), if calling the downstream service produces a "Connection reset" error, it is silently ignored. This endpoint would return 200 OK in such case.

Before Spring Web 6.2.0, calling this endpoint http://localhost:8080/hello would result in HTTP error code 500, with the following logs:

2025-01-15T12:47:57.658+01:00 DEBUG 15980 --- [demo] [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet        : POST "/hello", parameters={}
2025-01-15T12:47:57.658+01:00 DEBUG 15980 --- [demo] [nio-8080-exec-2] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to com.example.demo.DemoApplication#hello()
2025-01-15T12:47:57.660+01:00 DEBUG 15980 --- [demo] [nio-8080-exec-2] o.s.web.client.RestTemplate              : HTTP POST http://localhost:8888
2025-01-15T12:47:57.660+01:00 DEBUG 15980 --- [demo] [nio-8080-exec-2] o.s.web.client.RestTemplate              : Accept=[text/plain, application/json, application/*+json, */*]
2025-01-15T12:47:57.660+01:00 DEBUG 15980 --- [demo] [nio-8080-exec-2] o.s.web.client.RestTemplate              : Writing [hello] with org.springframework.http.converter.StringHttpMessageConverter
2025-01-15T12:47:57.662+01:00 DEBUG 15980 --- [demo] [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet        : Failed to complete request: org.springframework.web.client.ResourceAccessException: I/O error on POST request for "http://localhost:8888": Connection reset
2025-01-15T12:47:57.662+01:00 ERROR 15980 --- [demo] [nio-8080-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.springframework.web.client.ResourceAccessException: I/O error on POST request for "http://localhost:8888": Connection reset] with root cause

java.net.SocketException: Connection reset
	at java.base/sun.nio.ch.NioSocketImpl.implRead(NioSocketImpl.java:318) ~[na:na]
        ...
2025-01-15T12:47:57.663+01:00 DEBUG 15980 --- [demo] [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet        : "ERROR" dispatch for POST "/error", parameters={}
2025-01-15T12:47:57.664+01:00 DEBUG 15980 --- [demo] [nio-8080-exec-2] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#error(HttpServletRequest)
2025-01-15T12:47:57.664+01:00 DEBUG 15980 --- [demo] [nio-8080-exec-2] o.s.w.s.m.m.a.HttpEntityMethodProcessor  : Using 'application/json', given [*/*] and supported [application/json, application/*+json]
2025-01-15T12:47:57.664+01:00 DEBUG 15980 --- [demo] [nio-8080-exec-2] o.s.w.s.m.m.a.HttpEntityMethodProcessor  : Writing [{timestamp=Wed Jan 15 12:47:57 CET 2025, status=500, error=Internal Server Error, path=/hello}]
2025-01-15T12:47:57.665+01:00 DEBUG 15980 --- [demo] [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet        : Exiting from "ERROR" dispatch, status 500

After Spring Web 6.2.0 (Spring Boot 3.4.0), the logs look as follows, note the line "Looks like the client has gone away" — it's coming from DisconnectedClientHelper that assumes for some reason that the "Connection reset" exception comes from the client. Note that the exception is completely ignored, and the endpoint returns 200 OK:

2025-01-15T12:42:28.211+01:00 DEBUG 15887 --- [demo] [nio-8080-exec-7] o.s.web.servlet.DispatcherServlet        : POST "/hello", parameters={}
2025-01-15T12:42:28.213+01:00 DEBUG 15887 --- [demo] [nio-8080-exec-7] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to com.example.demo.DemoApplication#hello()
2025-01-15T12:42:28.214+01:00 DEBUG 15887 --- [demo] [nio-8080-exec-7] o.s.web.client.RestTemplate              : HTTP POST http://localhost:8888
2025-01-15T12:42:28.215+01:00 DEBUG 15887 --- [demo] [nio-8080-exec-7] o.s.web.client.RestTemplate              : Accept=[text/plain, application/json, application/*+json, */*]
2025-01-15T12:42:28.215+01:00 DEBUG 15887 --- [demo] [nio-8080-exec-7] o.s.web.client.RestTemplate              : Writing [hello] with org.springframework.http.converter.StringHttpMessageConverter
2025-01-15T12:42:28.216+01:00 DEBUG 15887 --- [demo] [nio-8080-exec-7] o.s.w.s.handler.DisconnectedClient       : Looks like the client has gone away: org.springframework.web.client.ResourceAccessException: I/O error on POST request for "http://localhost:8888": Connection reset (For a full stack trace, set the log category 'org.apache.commons.logging.LogAdapter$Slf4jLocationAwareLog@4d9f6662' to TRACE level.)
2025-01-15T12:42:28.216+01:00 DEBUG 15887 --- [demo] [nio-8080-exec-7] o.s.web.servlet.DispatcherServlet        : Completed 200 OK

A simple reproducer follows.

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.client.RestTemplate;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

@SpringBootApplication
@Controller
public class DemoApplication {

    @PostMapping("/hello")
    public String hello() {
        return new RestTemplate().postForObject("http://localhost:8888", "hello", String.class);
    }
    
    public static void main(String[] args) throws Exception {
        new Thread(() -> {
            try (ServerSocket socket = new ServerSocket(8888)) {
                while (true) {
                    try (Socket clientSocket = socket.accept()) {
                        // simulate TCP connection reset
                        clientSocket.getInputStream().read();
                        clientSocket.setSoLinger(true, 0);
                    }
                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }).start();

        Thread.sleep(2000);

        SpringApplication.run(DemoApplication.class, args);
    }

}
@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Jan 15, 2025
@forketyfork forketyfork changed the title Connection reset exception in a spring-mvc controller method is ignored Connection reset exception in a Spring Web MVC controller method is ignored Jan 15, 2025
@quaff
Copy link
Contributor

quaff commented Jan 16, 2025

It's a regression introduced by 203fa75.

quaff added a commit to quaff/spring-framework that referenced this issue Jan 16, 2025
quaff added a commit to quaff/spring-framework that referenced this issue Jan 16, 2025
quaff added a commit to quaff/spring-framework that referenced this issue Jan 16, 2025
@forketyfork
Copy link
Author

@quaff thanks for the quick fix. But wouldn't it only fix the issue for the case when RestTemplate is used? Any other HTTP client might produce a completely different exception.

quaff added a commit to quaff/spring-framework that referenced this issue Jan 16, 2025
@quaff
Copy link
Contributor

quaff commented Jan 16, 2025

@quaff thanks for the quick fix. But wouldn't it only fix the issue for the case when RestTemplate is used? Any other HTTP client might produce a completely different exception.

It should works for all http client provided by Spring Framework, like RestTemplate, RestClient, and WebClient.

@forketyfork
Copy link
Author

forketyfork commented Jan 16, 2025

True, but what if I use any other client, e.g., Java HTTP client directly?

@forketyfork
Copy link
Author

The DisconnectedClientHelper docs say:

The utility methods help to log a single line message at DEBUG level, and a full stacktrace at TRACE level.

So the point here is to reduce the logging, and this heuristic makes sense in most of the cases. And even for false positive cases, not logging the exception might not be that critical. But the controller method also returns 200 OK in such case, and I think that's the real problem, as it breaks the expected behavior.

quaff added a commit to quaff/spring-framework that referenced this issue Jan 16, 2025
@quaff
Copy link
Contributor

quaff commented Jan 16, 2025

The DisconnectedClientHelper docs say:

The utility methods help to log a single line message at DEBUG level, and a full stacktrace at TRACE level.

So the point here is to reduce the logging, and this heuristic makes sense in most of the cases. And even for false positive cases, not logging the exception might not be that critical. But the controller method also returns 200 OK in such case, and I think that's the real problem, as it breaks the expected behavior.

I agree with you, I appended another commit to fix it, let's wait for team member's response.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: waiting-for-triage An issue we've not yet triaged or decided on
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants