问题描述:
在Android中使用OkHttp向服务端post一个json时,服务端一直返回无法获取到参数。
具体表现:
前两天参加一个移动互联网的比赛的时候,在代码中使用OkHttp向局域网内的服务器发送一个post请求时,服务器一直返回无法获取到参数。Android端中请求的代码如下:
OkHttpClient client = new OkHttpClient();
RequestBody body = new FormBody.Builder()
.add("a", "a")
.add("b", "1")
.build();
Request request = new Request.Builder()
.url("http://...")
.post(body)
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
...
}
@Override
public void onResponse(Call call, Response response) throws IOException {
...
}
});
由于技术比较菜,所以一直以来OkHttp发送json的时候是用FormBody来做的,而且在之前的与服务器的交互中都没有出现过类似的问题。所以在比赛的时候突然遇到,一直以为是服务端的原因。后来比赛完后,我的指导老师帮我分析时提到了:Content-type这个请求头的问题,我才心里有点清楚大概是什么原因。
通过查看FormBody的源码的时候发现,在其第一行就很明显的写明了Content-type的值:
public final class FormBody extends RequestBody {
private static final MediaType CONTENT_TYPE = MediaType.get("application/x-www-form-urlencoded");
private final List<String> encodedNames;
private final List<String> encodedValues;
FormBody(List<String> encodedNames, List<String> encodedValues) {
this.encodedNames = Util.immutableList(encodedNames);
this.encodedValues = Util.immutableList(encodedValues);
}
...
}
那么"application/x-www-form-urlencoded"和"application/json"具体在服务端会出现什么问题呢?这里我用php来展示一下。
首先是"application/x-www-form-urlencoded":
即Android端用FormBody的形式来传值,那么在php端如果需要获取对应的值,则需要这样子写:
if (isset($_POST['a'])) {
$a = $_POST['a'];
} else {
$a = "not found";
}
echo $a;
可以看到,这里直接用$_POST[键名]的形式就可以获取到由OkHttp发送来的值了。
如果是"application/json":
在Android端的代码如下:
OkHttpClient client = new OkHttpClient();
JSONObject object = new JSONObject();
try {
object.put("a", "a");
object.put("b", "b");
} catch (JSONException e) {
e.printStackTrace();
}
RequestBody body = RequestBody.create(MediaType.parse("application/json"), object.toString());
Request request = new Request.Builder()
.url("https://...")
.post(body)
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
...
}
@Override
public void onResponse(Call call, Response response) throws IOException {
...
}
});
可以看到,这里的RequestBody使用了create的方式来创建,其中特意指明了Content-type为"application/json"的形式,然后对应所要传的值则用toString()的方法将json对象转换成json字符串的形式了。
那么如果是这样子的传值方式,在php端又如何获取到对应的值呢?其实原理很简单,只需要拿到请求体,然后将其转换成json对象即可。
$rawData = file_get_contents("php://input");
$a = "not found";
if (json_decode($rawData)->a){
$a = json_decode($rawData)->a;
}
echo $a;
总结一下:
其实到这里,我大概就明白为什么我在比赛的时候利用FormBody的方法来请求,但是却被告知无法获取到参数的原因了。因为比赛的时候服务端是接收的json字符串,也就是Content-type为application/json这样的。而我使用FormBody的时候,Content-type是application/x-www-form-urlencoded。不过到这里我其实还有点疑惑,那就是为什么我以前做过的许多与服务端的对接,都是用的FormBody的形式,但是能正常请求呢?我想大概是我接触的服务端做得比较完善,兼顾了不同的Content-type的情况?
不过这次采坑也让我明白了我该去学习学习Http协议了,不然以后还有更多的坑等着我。。