UnityWebRequest如何发送Json数据格式的Post请求,为什么使用UnityWebRequest.Post会报错?


在前后端的交互中,我们很经常将 Json 格式的数据装入请求体,向后端发送 Post 请求。

Unity 提供了 UnityWebRequest 这个和网络有关的类,我们可以借助它实现数据的请求和响应的接收。想了解详情可参考 Unity 官方文档中相关的 API 介绍:UnityWebRequest

那么下面就来为大家演示一下如何利用 UnityWebRequest 发送 Json 数据格式的 Post 请求。后端的部分我是用基于 Java 的 SpringBoot 框架搭建的程序,对应的接口用了 @RequestBody 注解来接收前端传来的 Json 请求参数。这里就不具体演示后端怎么写了,本篇博客还是把重点放在 Unity 前端的实现方法上,总的来说有两种实现方式,但不论是哪一种,思路都是一样的,只是代码的实现上有些不同。


🔍 方法一 :new UnityWebRequest

如果在百度上搜,可能大部分会搜到这种实现方式,我先附上代码,然后再具体解释:

private void Start() {
	string url="xxx";
	string json="一个Json格式的数据,这里大家替换成自己想要测试的Json数据";
	StartCoroutine(I_RequestByJsonBodyPost(url,json));
}
private static IEnumerator I_RequestByJsonBodyPost(string url, string json) 
{        
     UnityWebRequest www = new UnityWebRequest(url, UnityWebRequest.kHttpVerbPOST);
     DownloadHandler downloadHandler = new DownloadHandlerBuffer();
     www.downloadHandler = downloadHandler;       								
     www.SetRequestHeader("Content-Type", "application/json;charset=utf-8");        
     byte[] bodyRaw = Encoding.UTF8.GetBytes(json);
     www.uploadHandler = new UploadHandlerRaw(bodyRaw);
     yield return www.SendWebRequest();
     Debug.Log(www.downloadHandler.text);
 }

(注:Encoding 位于 System.Text 命名空间下,使用前要先 using System.Text)

这里假设大家已经得到了 Json 数据。而在实际开发中,基本上是要把一个对象转化成 Json 字符串。Unity 中也有许多Json 与对象互相转化的解决方案,如 Newstonsoft.json , LitJson, JsonUtility 等。

重点讲解:
1️⃣ UnityWebRequest 结合协程一起使用是比较常见的。SendWebRequest() 这个方法就是开始与远程服务器通信,然后会暂停协程,等到完成通信或系统错误后才会继续执行 yield return 后面的代码。

2️⃣ UnityWebRequest.kHttpVerbPOST 是 UnityWebRequest 类的一个静态常量,值为 “POST”

3️⃣ 为了保证前端发送的是 Json 格式的数据,让后端能够正常地解析,必须要把请求头中的 Content-Type 设为 “application/json” ,因此要有这段代码:

www.SetRequestHeader("Content-Type", "application/json;charset=utf-8");        

注: 为了保证字符不乱码,我改成了"application/json;charset=utf-8"

4️⃣ 为了能够传送数据给后端, UnityWebRequest 提供了 uploadHandler 这个字段用于管理请求体的数据。我们这里使用的是原生(Raw)的 Json 数据,因此要使用 UploadHandlerRaw 类(继承自 UploadHandler 类),利用 System.Text.Encoding.UTF8.GetBytes 将 Json 字符串转化成字节流传入 UploadHandlerRaw 类的构造函数

5️⃣ 为了能够接收后端的响应, UnityWebRequest 提供了 downloadHandler 这个字段用于下载后端返回的响应,我们可以用 downloadHandler 的 text 字段得到响应的字符串信息。

⭐ 总结一下,大致的思路是:

① 实例化 UnityWebRequest,设置为 Post 请求
② 设置 Content-Type
③ 配置 uploadHandler 和 downloadHandler


🔍 方法二 :UnityWebRequest.Post

要想发送 Post 请求,我们可以用 UnityWebRequest.Post 这个 API。
在这里插入图片描述
可以看到这个 API 有 9 个重载,Json 格式的数据可以是个字符串,那么这里选用上图中的第一个重载。

public static UnityWebRequest Post(string uri, string postData);

我们先来看看这个 API 的官方介绍:
在这里插入图片描述
这个 API 内部的实现方法其实大部分和我们刚刚介绍的方法一是类似的,可以说它帮我们封装了一些东西。这里有几个重点我先翻译下:

1️⃣ 这个方法设置的 Content-Type 默认是 application/x-www-form-urlencoded,因此要想发送 Json 数据,我们也要手动把 Content-Type 设成 application/json;charset=utf-8
在这里插入图片描述

2️⃣ 这个方法已经为我们实例化了一个 downloadHandler
在这里插入图片描述

3️⃣ 这个方法也为我们实例化了一个 uploadHandler,并且它能将数据转化为字节流,不过注意一下第一句话,数据会先被 escaped ,这里先卖个关子,待会儿会详细介绍这个 escaped,也是这一部分的重点(里面有坑)。
在这里插入图片描述

❓ UnityWebRequest.Post 会使后端报出数据读取格式错误?

那么既然为我们在内部实现了 uploadHandler 和 downloadHandler 的逻辑,是不是使用起来就更简单了呢?
来看下面这段代码:

private static IEnumerator I_RequestByJsonBodyPost(string url, string json) 
{        
     UnityWebRequest www = UnityWebRequest.Post(url, json);    								
     www.SetRequestHeader("Content-Type", "application/json;charset=utf-8");        
     yield return www.SendWebRequest();
     Debug.Log(www.downloadHandler.text);
 }

这时候,查看后端的日志,发现报了这个错:
在这里插入图片描述
报错信息大致是后端无法读取前端传递的数据格式,而且可以发现,为什么后端收到的数据中会有个 “%”,前端传的不是 Json 格式的数据吗?哪来的 % 呢?
不过看到这个 %,我大致可以猜到是进行了某种字符编码。现在我们再回到刚刚留下来的 escaped 问题。回看官方 API 文档,重点关注这句话:
在这里插入图片描述
可以看到我们传进去的字符串会被 URLEncoded。那么这个 escaped 的过程,就是对 postData 进行了 URLEncode。因为这个 API 默认把 Content-Type 当作 application/x-www-form-urlencoded,而 URLEncode 是为这种 Content-Type 准备的,我们可以看看 URLEncode 的相关解释:

URL编码(URL encoding),也称作百分号编码(Percent-encoding), 是特定上下文的统一资源定位符 (URL)的编码机制。适用于统一资源标识符(URI)的编码,也用于为"application/x-www-form-urlencoded" MIME准备数据, 因为它用于通过HTTP的请求操作(request)提交HTML表单数据。

因此这个方法在内部将我们的 Json 数据进行了 URL 编码。我们可以用 URL 编码转换工具试一试:(网址:https://www.gjk.cn/urlencode
在这里插入图片描述
URL 编码后:
在这里插入图片描述
这也解释了为什么后端会收到一个%。但因为后端对应的接口设置了只能解析 Json 格式的数据,它明显不认这个 URL 编码,所以报错了。

我们也可以看看 Unity 内部是如何实现这个 Post 方法的(github链接
在这里插入图片描述
可以看到在把 postData 转换为 byte 数组前,它多了一句 URL 编码的转换。

那么如何解决这个问题呢?
我们只能手动创建一个 uploadHandler,覆盖掉原来的 uploadHandler。

❗ 正确的做法

private static IEnumerator I_RequestByJsonBodyPost(string url, string json) 
{        
     UnityWebRequest www = UnityWebRequest.Post(url, json);    								
     www.SetRequestHeader("Content-Type", "application/json;charset=utf-8");        
     byte[] bodyRaw = Encoding.UTF8.GetBytes(json);
     www.uploadHandler = new UploadHandlerRaw(bodyRaw);
     yield return www.SendWebRequest();
     Debug.Log(www.downloadHandler.text);
 }

后记:
本篇介绍的方法一在网上能够搜到很多类似的介绍。但是当时我也有点纳闷,UnityWebRequest.Post 看起来似乎更加简洁,为什么很少的文章是介绍用 UnityWebRequest.Post 来发送 Json 数据呢?经过尝试我得到了本篇文章里提及的后端的报错。而我之前也一直没注意到 UnityWebRequest.Post(string uri, string postData) 这个方法在内部居然会先进行 URL 编码,也在网上搜了好久,虽然最后总算搜到了相关的资料,但是感觉目前网上对这个问题的解答不是很多。于是就想写这篇博客做一个总结,希望能够帮到更多的小伙伴。 🌹