来自 java.net.http.HttpClient 使用 Java 11 和 Spring 启动的调用的不可靠响应
Unreliable response from java.net.http.HttpClient call using Java 11 and Spring Boot
我正在 Spring Boot 中编写一个 back-end 应用程序,它从第三方调用另一个 API。
我遇到了这个特定调用的问题,它检索了一个包含不记名令牌的令牌 object,然后我将其用于其他端点。
当调用其他端点时,检索到的令牌有时有效,但大多数时候无效,导致未经授权的响应。
@RestController
public class CotizacionController {
Logger logger = LoggerFactory.getLogger(CotizacionController.class);
@Value("${service.credentials.tokenServer}")
private String tokenServer;
@Value("${service.credentials.grantType}")
private String grantType;
@Value("${service.credentials.username}")
private String username;
@Value("${service.credentials.password}")
private String password;
HttpClient client = HttpClient.newHttpClient();
@RequestMapping("/create")
public Object Create() throws IOException, InterruptedException {
HashMap<String, String> parameters = new HashMap<>();
parameters.put("grant_type", grantType);
parameters.put("username", username);
parameters.put("password", password);
String form = parameters.keySet().stream()
.map(key -> key + "="
+ URLEncoder.encode(parameters.get(key),
StandardCharsets.UTF_8))
.collect(Collectors.joining("&"));
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(tokenServer))
.header("Content-Type", "application/x-www-form-urlencoded")
.POST(BodyPublishers.ofString(form)).build();
HttpResponse<?> response = client.send(request, BodyHandlers.ofString());
TokenResponse result = new ObjectMapper().readValue(response
.body().toString(), TokenResponse.class);
return result;
}
}
这是一个示例令牌 object:
{
"access_token": "z-bu-Pde6M2dlPiaRzd5XpTrT7ohpFQZe157HHVLfdKJWsdmKCloK7AYGEw7SLCe28tjYAxo8MZOE_3W00HEa-bqgUvcrAKfxIubAq0UGXv7jLPWbRwWzhAUCDon3kdstUrJ_OKRN2y26W6qyDBGDqlP5NRSF4unH_pD_ShmpDlSxZdYUqD0da5Y2_uO6YRs5GuWA7XhI9sPa98SxuXN_dwiDJVif418xK646fUgWR8",
"token_type": "bearer",
"expires_in": "3599"
}
使用 postman 检索令牌工作得很好,所以这不可能是第三方的问题 API。我也在 .NET Core 3 中实现了这个相同的服务,它在那边也工作得很好。
最让我困惑的是实际的 HttpClient 调用有效,我确实得到了一个正确的 Json,它映射到我的 TokenResponse object 就好了。只是token值无效...有时。
我也尝试过使用 RestTemplate 和 WebClient Spring 库,但结果是一样的。调用有效,但检索到的令牌无效。
起初我以为我遇到了竞争条件,因为最初我有另一个 HttpClient 和另一个端点使用令牌调用的响应。所以我将其简化为仅令牌调用并手动将令牌值复制到邮递员请求中。没用。
然后我想也许我的 HttpClient 授权 header 格式不正确,但这是错误的,因为使用邮递员请求将令牌简单地复制到受保护的端点表明令牌不起作用。
以及我尝试过的其他事情:
- 将我在控制器中生成的表单字符串粘贴到 Postman 请求中以确保其有效。
- 检查 URLEncoder 没有弄乱任何表单值。
- 从令牌 object 复制令牌值以在另一个端点与 Postman 一起使用。
- 跳过 object 映射和 return 一个简单的字符串,并从 Postman 的响应中手动复制令牌值,以便我可以在另一个端点中使用它。
此时我很迷茫,唯一想到的是 HttpClient.send() 方法可能正在以可能影响内容的方式解析 body ?我对此表示怀疑,但我看不出还会发生什么。
解决方案与 cookie 有关!
令牌服务器响应正在发送 2 set-cookie
headers,这在 Postman 和 .NET Core 中被自动处理并设置为后续 HTTP 请求。
第 3 方 API 在负载平衡器后面并生成这些 session cookie。
我通过在 main
方法中使用以下代码实现 system-wide CookieHandler 解决了这个问题。
public static void main(String[] args) {
CookieHandler.setDefault(new CookieManager());
SpringApplication.run(Main.class, args);
}
然后像这样构建我的 HttpClient object:
...
HttpClient client = HttpClient.newBuilder().cookieHandler(CookieHandler.getDefault()).build();
...
这样,响应 set-cookie
和请求 cookie
headers 将自动处理并处理此 HttpClient 发出的所有调用。
默认情况下,CookieHandler 是使用 CookiePolicy.ACCEPT_ORIGINAL_SERVER
参数创建的。我的理解是,这使得 cookie 只有在同一主机设置和请求时才起作用。查看文档以获取有关 CookiePolicy
的更多选项
我正在 Spring Boot 中编写一个 back-end 应用程序,它从第三方调用另一个 API。
我遇到了这个特定调用的问题,它检索了一个包含不记名令牌的令牌 object,然后我将其用于其他端点。 当调用其他端点时,检索到的令牌有时有效,但大多数时候无效,导致未经授权的响应。
@RestController
public class CotizacionController {
Logger logger = LoggerFactory.getLogger(CotizacionController.class);
@Value("${service.credentials.tokenServer}")
private String tokenServer;
@Value("${service.credentials.grantType}")
private String grantType;
@Value("${service.credentials.username}")
private String username;
@Value("${service.credentials.password}")
private String password;
HttpClient client = HttpClient.newHttpClient();
@RequestMapping("/create")
public Object Create() throws IOException, InterruptedException {
HashMap<String, String> parameters = new HashMap<>();
parameters.put("grant_type", grantType);
parameters.put("username", username);
parameters.put("password", password);
String form = parameters.keySet().stream()
.map(key -> key + "="
+ URLEncoder.encode(parameters.get(key),
StandardCharsets.UTF_8))
.collect(Collectors.joining("&"));
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(tokenServer))
.header("Content-Type", "application/x-www-form-urlencoded")
.POST(BodyPublishers.ofString(form)).build();
HttpResponse<?> response = client.send(request, BodyHandlers.ofString());
TokenResponse result = new ObjectMapper().readValue(response
.body().toString(), TokenResponse.class);
return result;
}
}
这是一个示例令牌 object:
{
"access_token": "z-bu-Pde6M2dlPiaRzd5XpTrT7ohpFQZe157HHVLfdKJWsdmKCloK7AYGEw7SLCe28tjYAxo8MZOE_3W00HEa-bqgUvcrAKfxIubAq0UGXv7jLPWbRwWzhAUCDon3kdstUrJ_OKRN2y26W6qyDBGDqlP5NRSF4unH_pD_ShmpDlSxZdYUqD0da5Y2_uO6YRs5GuWA7XhI9sPa98SxuXN_dwiDJVif418xK646fUgWR8",
"token_type": "bearer",
"expires_in": "3599"
}
使用 postman 检索令牌工作得很好,所以这不可能是第三方的问题 API。我也在 .NET Core 3 中实现了这个相同的服务,它在那边也工作得很好。
最让我困惑的是实际的 HttpClient 调用有效,我确实得到了一个正确的 Json,它映射到我的 TokenResponse object 就好了。只是token值无效...有时。
我也尝试过使用 RestTemplate 和 WebClient Spring 库,但结果是一样的。调用有效,但检索到的令牌无效。
起初我以为我遇到了竞争条件,因为最初我有另一个 HttpClient 和另一个端点使用令牌调用的响应。所以我将其简化为仅令牌调用并手动将令牌值复制到邮递员请求中。没用。
然后我想也许我的 HttpClient 授权 header 格式不正确,但这是错误的,因为使用邮递员请求将令牌简单地复制到受保护的端点表明令牌不起作用。
以及我尝试过的其他事情:
- 将我在控制器中生成的表单字符串粘贴到 Postman 请求中以确保其有效。
- 检查 URLEncoder 没有弄乱任何表单值。
- 从令牌 object 复制令牌值以在另一个端点与 Postman 一起使用。
- 跳过 object 映射和 return 一个简单的字符串,并从 Postman 的响应中手动复制令牌值,以便我可以在另一个端点中使用它。
此时我很迷茫,唯一想到的是 HttpClient.send() 方法可能正在以可能影响内容的方式解析 body ?我对此表示怀疑,但我看不出还会发生什么。
解决方案与 cookie 有关!
令牌服务器响应正在发送 2 set-cookie
headers,这在 Postman 和 .NET Core 中被自动处理并设置为后续 HTTP 请求。
第 3 方 API 在负载平衡器后面并生成这些 session cookie。
我通过在 main
方法中使用以下代码实现 system-wide CookieHandler 解决了这个问题。
public static void main(String[] args) {
CookieHandler.setDefault(new CookieManager());
SpringApplication.run(Main.class, args);
}
然后像这样构建我的 HttpClient object:
...
HttpClient client = HttpClient.newBuilder().cookieHandler(CookieHandler.getDefault()).build();
...
这样,响应 set-cookie
和请求 cookie
headers 将自动处理并处理此 HttpClient 发出的所有调用。
默认情况下,CookieHandler 是使用 CookiePolicy.ACCEPT_ORIGINAL_SERVER
参数创建的。我的理解是,这使得 cookie 只有在同一主机设置和请求时才起作用。查看文档以获取有关 CookiePolicy