【问题标题】:Spring Boot: Connection timed out when trying to call a service from a serviceSpring Boot:尝试从服务调用服务时连接超时
【发布时间】:2021-04-05 14:56:18
【问题描述】:

我有 2 个微服务 + 一个在其中注册的 Eureka 服务器。 我做了我能想到的一切,但是当我尝试从管理服务调用登录服务时,我总是得到“连接超时”。

POST http://localhost:9903/login

{
    "username":"adm4",
    "password":"adm4adm4"
}

我尝试使用 Spring RestTemplate 和 WebClient 以及 Apache HttpClient。 一直以来,流程都到达了 post 方法,我得到了相同的结果。 我想这一定是一些配置问题。

我正在使用所有模块在本地主机上工作。

它真的让我发疯!

请指教。我很感激。

相关信息如下。如果您需要更多信息,请告诉我。

首先您可以看到服务已注册并启动:

接下来是代码:

经理(呼叫)服务: (我之前的所有尝试都在评论中留下了)

@PostMapping("/login")
    public void login(@RequestBody LoginRequest loginRequest) throws Exception {

        String url = getBaseUrl("bbsim-login-service") + "/api/auth/signin";

/*         CloseableHttpClient httpclient = HttpClients.createDefault();

        try {
            HttpPost httpPost = new HttpPost(getBaseUrl("bbsim-login-service") + "/api/auth/signin");

            List<NameValuePair> params = new ArrayList<>();
            params.add(new BasicNameValuePair("username", loginRequest.getUsername()));
            params.add(new BasicNameValuePair("password", loginRequest.getPassword()));
            httpPost.setEntity(new UrlEncodedFormEntity(params));

            CloseableHttpResponse response = httpclient.execute(httpPost);
            System.out.println(response.getStatusLine().getStatusCode()); 
        } finally {
            httpclient.close();
        }
 */
/*         HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory();
        // Connect timeout: time is in milliseconds
        clientHttpRequestFactory.setConnectTimeout(30000);
        // Read timeout: time is in milliseconds
        clientHttpRequestFactory.setReadTimeout(30000);

        RestTemplate restTemplate = new RestTemplate(clientHttpRequestFactory);

        HttpEntity<LoginRequest> request = new HttpEntity<>(loginRequest);
        JwtResponse res = restTemplate.postForObject(url, request, JwtResponse.class);
        System.out.println(res); 
*/

        localApiClient
                .post()
                .uri(url)
                .body(Mono.just(loginRequest), LoginRequest.class)
                .retrieve()
                .bodyToMono(JwtResponse.class)
                .block();

    }

    private String getBaseUrl(String serviceName) {

        Application application = eurekaClient.getApplication(serviceName);
        InstanceInfo instanceInfo = application.getInstances().get(0);
        String hostname = instanceInfo.getHostName();
        int port = instanceInfo.getPort();
        return "http://" + hostname + ":" + port;
    }

application.yml:

server.port: 9903

spring:
    application.name: bbsim-manager-service

eureka:
    client:
        serviceUrl:
            defaultZone: ${EUREKA_URI:http://localhost:8088/eureka}
            registryFetchIntervalSeconds: 1
            # register-with-eureka: true
            # fetch-registry: true
    instance:
        leaseRenewalIntervalInSeconds: 1

如果我理解得很好,请求根本没有到达登录服务。


登录(调用)服务:

@PostMapping("/signin")
public ResponseEntity<?> authenticateUser(@Valid @RequestBody LoginRequest loginRequest) {

    Authentication authentication = authenticationManager.authenticate(
            new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword()));

    SecurityContextHolder.getContext().setAuthentication(authentication);
    String jwt = jwtUtils.generateJwtToken(authentication);
    
    UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal();      
    List<String> roles = userDetails.getAuthorities().stream()
            .map(item -> item.getAuthority())
            .collect(Collectors.toList());

    return ResponseEntity.ok().body(new JwtResponse(jwt, 
                userDetails.getId(), 
                userDetails.getUsername(), 
                userDetails.getEmail(), 
            roles));
}

application.yml 文件:

server.port: 9902

spring:
    application:
        name: bbsim-login-service

eureka:
    client:
        serviceUrl:
            defaultZone: http://localhost:8088/eureka/
            registryFetchIntervalSeconds: 1
            instance:
                leaseRenewalIntervalInSeconds: 1

此外,我尝试了以下方法 - 给了我相同的结果:

curl -d "@data.json" -H "Content-Type: application/json" -X POST http://localhost:9903/login

data.json 包含正文内容。

【问题讨论】:

  • 您可以使用curl 访问您的登录服务吗?
  • @AbdelghaniRoussi 谢谢 Abdeighani。我试过 curl -d "@data.json" -H "Content-Type: application/json" -X POST localhost:9903/login 并收到:AnnotatedConnectException: Connection timed out: no further information: host.docker.internal/192.168.1.177 :9902","路径":"/login"
  • RestTemplateWebClient无关。那么,当您启动 docker 容器时,您是否暴露了 9902 端口?
  • @AbdelghaniRoussi 我必须说这是我的第一个春季项目。虽然我看到这里涉及到了docker(host.docker.internal),但是直接我并没有做任何与docker相关的事情。 yaml文件中的端口定义不是您问题的答案吗?如果没有 - 我该怎么办?
  • 这似乎是连接到在 docker 内运行的应用程序的问题。我认为您可能需要在 docker tutorialspoint.com/docker/docker_managing_ports.htm runnable.com/docker/binding-docker-ports 上进行端口转发

标签: spring spring-boot spring-mvc spring-security netflix-eureka


【解决方案1】:

这不是一个完整的答案,但我希望它可以帮助您解决问题。

我认为您的问题可能与您机器的不同 IP 地址的混合有关。

首先,我认为 Eureka 正在公开您的服务,如 host.docker.internal,如所示,通过不同的 docker 容器引用主机的逻辑名称,原因在 this SO question 中解释。

基本上,docker 软件似乎正在使用 host.docker.internalgateway.docker.internal 的条目更新您的 hosts 文件,而 Eureka 可能将该别名作为正在宣传的机器 IP 的别名。请参阅上述问题中接受的答案。

当你正常运行 Spring Boot 时,底层服务器(Tomcat、Jetty、Undertow)将监听0.0.0.0 地址中的连接,即在所有可用的网络接口中,包括localhost。这让我很困扰,因为如前所述,该服务应该可以通过机器中的所有 IP 访问。

无论如何,我认为您可以尝试几种方法来解决您的问题。

解决问题的最佳方法可能是将您的 Eureka 服务器和/或您的 Eureka 客户端的 hostname 配置为一个通用的。

例如,您可以将服务器和客户端配置为公开为localhost

为此,您需要在各自的配置文件中包含以下配置属性:

eureka:
  instance:
    hostname: localhost

【讨论】:

  • 感谢@jccampanero 抽出宝贵时间。您能否通过指定是否将每个步骤添加到上一步或应该根据将 Docker 桌面添加到主机文件的初步状态来编辑您的答案?我有我的假设(目前没有成功),但我希望得到您的明确指示。
  • 嗨@dushkin。是的当然。无论如何,这个想法是每个更改都必须单独应用,您不能混合不同的解决方案。对不起,我会尽力澄清答案。
  • 我编辑了答案。请独立尝试每个解决方案。我将从解决方案 3 开始。如果它不起作用,请使用解决方案 4。您尝试过吗?对于您的评论,他们不工作?请,无论如何,第一步应该是分析您的hosts 文件并查看它的外观,确认包含与 docker 相关的条目。
  • 是的@dushkin,保持原样。正如您在答案中指出的 SO 问题中看到的那样,它似乎由 docker 软件维护,尽管您从主机文件中删除了这些条目,但在重新启动系统后可能仍然存在。请保持原样,独立尝试每个解决方案
  • 伟大的@dushkin!我很高兴看到答案很有帮助。我修改了答案。不,正如我在答案中所说,可能还有其他选择,但这个选项肯定是一个很好的解决方案。我认为您的代码是正确的,并且问题是由 docker 网络问题引起的。请记住,该解决方案唯一可能的缺点是,当您将应用程序部署到其他环境时,您需要将主机名调整为合适的主机名。无论如何,这应该不是问题,因为您可以包含不同的弹簧配置文件和/或为此使用环境变量
【解决方案2】:

看起来您正在使用 Docker。您正在尝试连接到 localhost,但其他服务正在其他容器中运行,因此 localhost 将无法工作。请在您的 YAML 文件中尝试 0.0.0.0host.docker.internal 看看是否可行。

换句话说,您将需要编辑以下内容。

server.port: 9903
    
    spring:
        application.name: bbsim-manager-service
    
    eureka:
        client:
            serviceUrl:
                defaultZone: ${EUREKA_URI:http://host.docker.internal:8088/eureka}
                registryFetchIntervalSeconds: 1
                # register-with-eureka: true
                # fetch-registry: true
        instance:
            leaseRenewalIntervalInSeconds: 1

或更改 EUREKA_URI 环境变量以反映这一点。 YAML 也在您的服务中

    server.port: 9902
    
    spring:
        application:
            name: bbsim-login-service
    
    eureka:
        client:
            serviceUrl:
                defaultZone: ${EUREKA_URI:http://host.docker.internal:8088/eureka/}
                registryFetchIntervalSeconds: 1
                instance:
                    leaseRenewalIntervalInSeconds: 1

【讨论】:

  • 在 yaml 或 hosts 文件中?
  • 在 yaml 文件中,因为您的所有服务都在容器中运行。您也发布了主机文件吗?
  • 感谢您的宝贵时间。 1.“您也发布了主机文件”是什么意思? 2. yaml 在哪些项目上?尤里卡服务器,服务,所有这些?无论如何,在 yaml 文件上设置它的正确方法是什么? host.docker.internal 不是 yamls 上的“已知属性”。
  • 我对两个服务的 defaultZone 值进行了更改,将 localhost 替换为 host.docker.internal。调用服务在加载时抛出“任务主管超时”并且没有加载。被调用的服务在“host.docker.internal:8088/eureka/apps”的 GET 请求上引发 I/O 错误:连接超时。我不明白是否应该以及在哪里替换 EUREKA_URI?
  • 看起来你没有使用 Docker 正确地暴露你的端口。你能发布你的 Dockerfile。
猜你喜欢
  • 1970-01-01
  • 2012-11-01
  • 2021-07-11
  • 1970-01-01
  • 1970-01-01
  • 2019-02-01
  • 2023-04-07
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多