写在前面
不得不说,开发环境下,微信小程序要是在发起网络请求的话,遇到的坑也是有的。尽管,微信提供了十分便捷的API 供开发者调用。今天在小程序中写了一个表单,需要提交到服务器上,于是发起了一个 POST 请求,结果报错了。
关于网络请求
微信小程序提供了许多使用网络相关的 API,同时文档中也列出了一些(一大堆)注意事项常见问题什么的。比如:
- 每个微信小程序需要事先设置通讯域名,小程序只可以跟指定的域名与进行网络通信
- 域名只支持 https (wx.request、wx.uploadFile、wx.downloadFile) 和 wss (wx.connectSocket) 协议
- 域名不能使用 IP 地址(小程序的局域网 IP 除外)或 localhost
- 网络请求的 referer header 不可设置
- wx.request、wx.uploadFile、wx.downloadFile 的最大并发限制是 10 个
- 小程序进入后台运行后,如果 5s 内网络请求没有结束,会回调错误信息 fail interrupted;在回到前台之前,网络请求接口调用都会无法调用
更多可以查看 相关说明
这些条条框框实在不友好,尤其是开发环境中,向本地服务器发起网络请求时一般都是 localhost。好在文档的末尾,有提到怎样跳过域名校验。
在微信开发者工具中,可以临时开启 开发环境不校验请求域名、TLS版本及HTTPS证书 选项,跳过服务器域名的校验。此时,在微信开发者工具中及手机开启调试模式时,不会进行服务器域名的校验。
所以事情就变得很简单了,关闭域名校验后,直接在程序中调用 API 就行了。
关于 wx.request
相关参数
属性 | 类型 | 默认值 | 说明 |
---|---|---|---|
url | string | 开发者服务器接口地址 | |
data | string/object/ArrayBuffer | 请求的参数 | |
header | Object | 设置请求的 header | |
timeout | number | 超时时间,单位为毫秒 | |
method | string | GET | HTTP 请求方法 |
dataType | string | JSON | 返回的数据格式 |
responseType | string | text | 响应的数据类型 |
success | function | 接口调用成功的回调函数 | |
fail | function | 接口调用失败的回调函数 | |
complete | function | 接口调用结束的回调函数(调用成功、失败都会执行) |
更多关于可以参考文档
代码示例
这里的 url 是我自己的 Django 本地服务器地址,已经启动。于是我们可以仿照文档给出的示例,发起一个 POST 请求可以这样写:
wx.request({
url: 'http://127.0.0.1:8000/blog/wxapi/',
data: {
x: 'haha',
y: 'ahah'
},
method:'POST', // 默认 GET
header: {
'content-type': 'application/x-www-form-urlencoded' // 默认值 application/json
},
success:res=> {
console.log('---request success---')
console.log(res.data)
},
fail:res=> {
console.log(res.data)
console.log('---request fail---')
},
complete: res => {
console.log(res.data)
console.log('---request complete---')
}
})
403 Forbidden
然而,这样请求得到的响应码会是 403,显然这是客户端的错误。仔细看提示会发现,这是由于该 POST 请求没有带cookie或者说 cookie 为 null。而 Django 对 POST 请求都会进行 CSRF cookie 验证,防止CSRF(跨站请求伪造)恶意攻击。这个 CSRF cookie 就是 Django 模板里的 form 表单下的 {% csrf_token %}。这也是微信小程序开发与 web开发的一个区别所在。小程序的运行环境是在微信而不是浏览器,尽管我们可以在 header 中设置一个 Set-Cookie
,也是无济于事,因为我们拿到的并不是 {% csrf_token %} 这样的令牌。所以 Django 服务器会认为这是一个恶意请求,从而返回 403。
解决办法
要想解决说简单也简单,说难也有些复杂。我们已经明确知道这是一个关于跨域请求的问题。仔细观察 Django 项目中的 setting.py
文件,不难发现其中有这样一段配置:
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
这是项目中最基本的中间件,其中 django.middleware.csrf.CsrfViewMiddleware
就和 csrf 验证有关。所以我们不妨将其注释,然后在小程序中重新发起请求。观察控制台输出的 res
部分如下:
data:
data: "ok"
x: "haha new"
y: "ahah new"
errMsg: "request:ok"
header:
Content-Length: "48"
Content-Type: "application/json"
Date: "Sun, 09 Feb 2020 14:44:18 GMT"
Server: "WSGIServer/0.2 CPython/3.5.3"
X-Frame-Options: "SAMEORIGIN"
statusCode: 200
备注:服务器端代码views.py
# 微信小程序测试api
def wxapi(request):
if request.method=='GET':
context= {
"data": "ok"
}
return JsonResponse(context)
if request.method=='POST':
x = request.POST['x']
y = request.POST['y']
print(x + "---" + y)
context = {
"data": "ok",
"x":x+" new",
"y":y+" new"
}
return JsonResponse(context)
return HttpResponse("--ok--")
但是注释该配置是十分危险的下下之策,这里只是开发环境中,为了学习微信小程序,无伤大雅。当然,不这样做的话,我们就需要去实现如何跨域请求拿到 csrf_token 。好在已经有大佬们为我们造好了轮子jango-cors-headers
,我们只需要安装配置它就可以了。这里就不详细讲解了。