笔者报错时的运行环境:
安卓:
OkHttp 4.11.0
Android Studio Flamingo | 2022.2.1
Android SDK 33
Gradle 8.0.1
JDK 17
后端:
Spring Cloud Alibaba:2022.0.0.0-RC2
Spring Cloud:2022.0.0
Spring Boot:3.0.2
Nacos 2.2.3
Maven 3.8.3
JDK 17.0.7
IntelliJ IDEA 2022.3.1 (Ultimate Edition)
问题描述
最近笔者在安卓中使用 OkHttp 向后端 Spring Cloud 发送请求的时候,整个过程发生了如下报错。
-
后端 Spring MVC 控制器代码:
@PostMapping("/XXX") @ResponseBody @CrossOrigin public XXX xxx(HttpServletRequest request, HttpServletResponse response, @RequestBody XXX xxx) { // ... }
Spring MVC 报错信息:
202X-XX-XX XX:XX:XX.XXX [WARN] [http-nio-XXX-exec-X] org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver.logException(AbstractHandlerExceptionResolver.java:207) Resolved [org.springframework.web.HttpMediaTypeNotSupportedException: Content-Type 'application/x-www-form-urlencoded;charset=UTF-8' is not supported]
-
安卓 OkHttp 代码:
OkHttpClient client = new OkHttpClient(); RequestBody formBody = new FormBody.Builder() .add("XXX", XXX) .build(); Request request = new Request.Builder() .url("http://XXXXX:XXXX/XXX") .post(formBody) .build(); try (Response response = client.newCall(request).execute()) { if (!response.isSuccessful()) { Log.e(TAG, "请求失败。返回码:" + response); this.runOnUiThread(() -> { Toast.makeText(this, "请求失败", Toast.LENGTH_SHORT).show(); }); return; } // ... 请求发送成功... } catch (IOException exception) { Log.e(TAG, "服务器连接失败"); Log.e(TAG, ExceptionUtil.getStackTraceString(exception)); this.runOnUiThread(() -> { Toast.makeText(this, "服务器连接失败", Toast.LENGTH_SHORT).show(); }); }
OkHttp 报错信息:
Response{protocol=http/1.1, code=415, message=Unsupported Media Type, url=http://XXX.XXX.XXX.XXX:XXXXX/XXX/XXX}
问题原因
这是因为上面使用 OkHttp 发送 POST 请求时,使用的是 FormBody.Builder()
构造的请求体,这样发送的 HTTP 报文的 Content-Type
就会被设置成 application/x-www-form-urlencoded;charset=UTF-8
。
而后端 Spring MVC 在接收端使用的是 @RequestBody
注解,这样就会只认可值为 application/json
的 Content-Type
。因此,Spring Cloud 微服务拒绝了这个请求。
可以验证这一点,使用 Wireshark 抓包时,OkHttp 发送的 HTTP 报文如下。
而 Spring Cloud 微服务拒绝了这个请求,返回码为 415,且说明只接受值为 application/json
的 Content-Type
。
解决办法
这种问题是客户端与服务端发送数据没有协调好导致的,因此可以选择修改它们其中的任意一方来完成适配。
这里以修改安卓 OkHttp 代码为例,改使用 RequestBody.create
来创建请求体即可。
OkHttpClient client = new OkHttpClient();
var jsonType = MediaType.parse("application/json; charset=utf-8");
String content = JsonUtil.pojo2Json(XXX);
RequestBody requestBody = RequestBody.create(jsonType, content);
Request request = new Request.Builder()
.url("http://XXXXX:XXXX/XXX")
.post(requestBody)
.build();
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) {
Log.e(TAG, "请求失败。返回码:" + response);
this.runOnUiThread(() -> {
Toast.makeText(this, "请求失败", Toast.LENGTH_SHORT).show();
});
return;
}
// ... 请求发送成功...
} catch (IOException exception) {
Log.e(TAG, "服务器连接失败");
Log.e(TAG, ExceptionUtil.getStackTraceString(exception));
this.runOnUiThread(() -> {
Toast.makeText(this, "服务器连接失败", Toast.LENGTH_SHORT).show();
});
}