微信小程序请求接口为例深入讲解HTTP协议网络编程基础包含GETPOST请求实现响应处理状态码含义与常见错误排查实战技巧
这篇文章我写了好久,因为我见过太多人学网络编程时一头雾水。HTTP听起来很吓人,但其实就像你跟服务员点菜一样简单。
一、先别急着写代码,让我们搞清楚HTTP到底是什么
你可能在浏览器地址栏输入过 www.baidu.com,也可能在App里见过数据加载。这些背后有一个看不见的协议在干活,它就是 HTTP(HyperText Transfer Protocol,超文本传输协议)。
用一个生活场景来理解:
你去餐厅吃饭,这个流程是:
- 你(客户端)对服务员说:”我要一份宫保鸡丁” → 这就是 HTTP请求
- 服务员把需求告诉厨房 → 厨房准备菜
- 服务员把菜端给你 → 这就是 HTTP响应
- 服务员还会告诉你:”菜上齐了”(200)或者”不好意思厨房没食材了”(404/500)→ 这就是 状态码
HTTP协议本质上就是 客户端和服务器之间约定好的一种沟通方式。它规定了:
- 请求该怎么发
- 响应该怎么回
- 出错了怎么说
在微信小程序里,我们发起网络请求用的就是 wx.request(),但别急,先理解原理,代码自然就会写了。
二、URL的 Anatomy(解剖)—— 请求前你必须要懂的事
任何HTTP请求都需要一个目的地,这个目的地就是 URL(统一资源定位符)。
https://api.example.com/users/123?name=张三&age=25
拆开来你看到的是:
| 组成部分 | 说明 | 例子 |
|---|---|---|
| 协议 | 用哪种方式传输 | https:// |
| 域名/IP | 服务器在哪里 | api.example.com |
| 端口 | 服务的”门牌号”(通常省略) | :443(HTTPS默认) |
| 路径 | 具体要访问哪个资源 | /users/123 |
| 查询参数 | 附加信息,用 ? 开头 |
?name=张三&age=25 |
| Fragment | 锚点,浏览器用,服务器不处理 | #section1 |
💡 在小程序里,你经常看到的请求地址长这样:
> https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=xxx&secret=xxx > ``` > 这就是在问微信服务器"给我一个 token",参数写在 `?` 后面。 --- ## 三、HTTP请求的三大件:方法、头、体 每次发起HTTP请求,都包含三个部分: ### 3.1 请求方法(Method)—— 你想对服务器做什么 最常见的有四种: | 方法 | 通俗理解 | 安全性 | 幂等性 | |------|---------|--------|--------| | **GET** | "帮我查一下这个" | ✅ 安全(只读) | ✅ 多次相同请求结果一样 | | **POST** | "我要新增/修改数据" | ❌ 不安全(可修改) | ❌ 幂等性不保证 | | **PUT** | "完全替换这个资源" | ❌ 不安全 | ✅ 幂等 | | **DELETE** | "删除这个资源" | ❌ 不安全 | ✅ 幂等 | **GET vs POST 到底有什么区别?** 很多初学者搞混,我用一个比喻: > - **GET** 就像你在图书馆问图书管理员:"有没有《JavaScript权威指南》这本书?" —— 你只是查询,不改变任何东西 > - **POST** 就像你在图书馆办借书手续:"我要借这本书" —— 你改变了图书馆的状态(书被借走了) ### 3.2 请求头(Headers)—— 附加信息 请求头就像是请求的"备注栏",告诉服务器一些额外信息: ```http Content-Type: application/json # 告诉服务器:我的数据是JSON格式 Authorization: Bearer eyJhbGciOiJIUzI1Ni... # 告诉服务器:这是我的身份令牌 Accept: application/json # 告诉服务器:我想要JSON格式的数据 User-Agent: Mozilla/5.0... # 告诉服务器:这是什么客户端发的请求
在小程序里,你经常需要设置 Content-Type,否则服务器可能不知道你的数据是什么格式。
3.3 请求体(Body)—— 发送的数据
- GET请求 没有请求体(数据放在URL参数里)
- POST请求 有请求体(数据放在Body里)
POST的数据格式常见的有:
| 格式 | Content-Type | 例子 |
|---|---|---|
| JSON | application/json |
{"name":"张三","age":25} |
| Form | application/x-www-form-urlencoded |
name=张三&age=25 |
| 表单+文件 | multipart/form-data |
上传文件时用 |
四、GET请求 —— 最简单的网络请求
4.1 理解GET请求的本质
GET请求是最常用、最简单的请求方法。它的特点:
- 参数放在 URL末尾,用
?连接 - 没有请求体
- 可以被缓存、收藏、出现在历史记录里
一个典型的GET请求:
GET https://api.example.com/users?page=1&size=10 HTTP/1.1
Host: api.example.com
4.2 小程序中的GET请求实现
微信小程序用 wx.request() 发起网络请求,默认就是GET请求(不传 method 参数时):
// 最简单的GET请求
wx.request({
url: 'https://api.example.com/users',
data: {
page: 1,
size: 10
},
success: function(res) {
console.log('请求成功:', res.data)
},
fail: function(err) {
console.log('请求失败:', err)
}
})
实际上,上面的写法会把 data 里的参数拼到URL后面,等同于:
GET https://api.example.com/users?page=1&size=10 HTTP/1.1
4.3 带查询参数的完整例子
假设你有一个用户列表接口,支持按姓名搜索和分页:
// 搜索用户列表
wx.request({
url: 'https://api.example.com/users/search',
data: {
keyword: '张三',
page: 1,
pageSize: 20
},
success: function(res) {
if (res.statusCode === 200) {
console.log('搜索结果:', res.data.users)
console.log('总数:', res.data.total)
}
}
})
生成的请求URL是:
GET https://api.example.com/users/search?keyword=张三&page=1&pageSize=20
4.4 GET请求的注意事项
- 参数长度限制:URL有长度限制(浏览器一般2KB,服务器可能更短),所以数据量大的查询不适合用GET
- 敏感数据不要放URL:密码、token等不要放在URL参数里,容易被日志记录
- 中文需要编码:小程序框架会自动处理,但如果你手动拼URL,要用
encodeURIComponent()
// 手动拼接URL时的中文编码
const name = '张三'
const url = 'https://api.example.com/users?name=' + encodeURIComponent(name)
五、POST请求 —— 当你需要修改数据时
5.1 POST请求的本质
POST请求用于:
- 创建新资源
- 更新已有资源
- 发送敏感数据(数据在Body里,不在URL里)
一个典型的POST请求:
POST /api/users HTTP/1.1
Host: api.example.com
Content-Type: application/json
{"name": "张三", "age": 25}
5.2 小程序中的POST请求实现
// 创建新用户
wx.request({
url: 'https://api.example.com/users',
method: 'POST',
header: {
'Content-Type': 'application/json'
},
data: {
name: '张三',
age: 25,
email: 'zhangsan@example.com'
},
success: function(res) {
console.log('创建成功:', res.data)
// res.data 可能是 { id: 123, name: '张三', ... }
},
fail: function(err) {
console.error('创建失败:', err)
}
})
5.3 带文件上传的POST请求
上传头像是一个常见的POST场景,需要 multipart/form-data 格式:
// 上传头像
wx.chooseImage({
count: 1,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
success: function(res) {
const tempFilePath = res.tempFilePaths[0]
wx.uploadFile({
url: 'https://api.example.com/avatar/upload',
filePath: tempFilePath,
name: 'avatar', // 文件字段名
header: {
'Authorization': 'Bearer ' + wx.getStorageSync('token')
},
success: function(res) {
const data = JSON.parse(res.data)
console.log('上传成功,新头像URL:', data.avatarUrl)
},
fail: function(err) {
console.error('上传失败:', err)
}
})
}
})
5.4 POST表单数据格式
有些老接口要求用 application/x-www-form-urlencoded 格式:
// POST表单格式
wx.request({
url: 'https://api.example.com/login',
method: 'POST',
header: {
'Content-Type': 'application/x-www-form-urlencoded'
},
data: {
username: 'zhangsan',
password: '123456'
},
success: function(res) {
console.log('登录结果:', res.data)
if (res.data.success) {
wx.setStorageSync('token', res.data.token)
}
}
})
六、响应处理 —— 服务器返回了什么?
6.1 响应的结构
服务器返回的响应也分三部分:
HTTP/1.1 200 OK
Content-Type: application/json
Set-Cookie: sessionId=abc123
{"code": 0, "message": "success", "data": {"id": 1, "name": "张三"}}
| 组成部分 | 说明 |
|---|---|
| 状态行 | 协议版本 + 状态码 + 状态描述 |
| 响应头 | 附加信息(Content-Type、Cookie等) |
| 响应体 | 实际数据 |
6.2 小程序中响应的数据结构
wx.request 的 success 回调里,res 对象包含:
wx.request({
url: 'https://api.example.com/users/1',
success: function(res) {
console.log('完整响应:', res)
// res 包含:
// res.statusCode // HTTP状态码,如 200
// res.header // 响应头
// res.data // 响应体(服务器返回的数据)
}
})
6.3 处理不同的数据格式
JSON响应(最常见):
wx.request({
url: 'https://api.example.com/users/1',
success: function(res) {
// res.data 已经是对象了,因为小程序自动解析JSON
console.log('用户名称:', res.data.name)
console.log('用户邮箱:', res.data.email)
}
})
字符串响应:
wx.request({
url: 'https://api.example.com/health',
success: function(res) {
// 服务器返回纯文本
console.log('健康检查:', res.data)
}
})
二进制响应(图片/文件):
wx.request({
url: 'https://api.example.com/download/file',
responseType: 'arraybuffer', // 指定响应类型
success: function(res) {
// 把buffer转成base64或直接保存
const fs = wx.getFileSystemManager()
const filePath = wx.env.USER_DATA_PATH + '/downloaded.pdf'
fs.writeFileSync(filePath, res.data, 'binary')
console.log('文件已保存到:', filePath)
}
})
七、状态码 —— 服务器的”表情”
HTTP状态码是服务器返回的一个三位数字,告诉你请求的结果。
7.1 状态码分类速查表
| 范围 | 含义 | 通俗理解 |
|---|---|---|
| 1xx | 信息性 | “正在处理中” |
| 2xx | 成功 | “搞定啦!” |
| 3xx | 重定向 | “换个地方找” |
| 4xx | 客户端错误 | “你搞错了” |
| 5xx | 服务器错误 | “我搞砸了” |
7.2 最常用的状态码详解
2xx 系列(成功)
| 状态码 | 含义 | 场景 |
|---|---|---|
| 200 | OK - 成功 | 最常见的成功响应 |
| 201 | Created - 已创建 | POST请求创建资源成功 |
| 204 | No Content - 无内容 | 删除成功,没有返回数据 |
// 删除资源
wx.request({
url: 'https://api.example.com/users/123',
method: 'DELETE',
success: function(res) {
if (res.statusCode === 204) {
console.log('删除成功,服务器没有返回数据')
}
}
})
4xx 系列(客户端错误)
| 状态码 | 含义 | 排查方向 |
|---|---|---|
| 400 | Bad Request | 请求参数错误,检查传参 |
| 401 | Unauthorized | 未登录或token过期 |
| 403 | Forbidden | 没有权限访问 |
| 404 | Not Found | 资源不存在,检查URL |
| 405 | Method Not Allowed | 用错了请求方法 |
| 429 | Too Many Requests | 请求太频繁,被限流了 |
// 处理401未授权
wx.request({
url: 'https://api.example.com/users/me',
success: function(res) {
if (res.statusCode === 401) {
// 跳转到登录页
wx.redirectTo({
url: '/pages/login/login'
})
wx.showToast({
title: '请先登录',
icon: 'none'
})
}
}
})
5xx 系列(服务器错误)
| 状态码 | 含义 | 排查方向 |
|---|---|---|
| 500 | Internal Server Error | 服务器内部错误,联系后端 |
| 502 | Bad Gateway | 网关错误,服务器配置问题 |
| 503 | Service Unavailable | 服务器维护中 |
| 504 | Gateway Timeout | 服务器超时 |
// 处理500服务器错误
wx.request({
url: 'https://api.example.com/orders',
success: function(res) {
if (res.statusCode === 500) {
wx.showToast({
title: '服务器开小差了,请稍后再试',
icon: 'none',
duration: 2000
})
}
}
})
7.3 自定义业务状态码
很多后端会在响应体里加上自己的业务状态码:
// 后端返回的响应结构
{
"code": 200, // 业务状态码(0或200表示成功)
"message": "成功", // 提示信息
"data": { // 实际数据
"id": 123,
"name": "张三"
}
}
对应的处理逻辑:
wx.request({
url: 'https://api.example.com/users/123',
success: function(res) {
// 先判断HTTP状态码
if (res.statusCode !== 200) {
console.error('HTTP错误:', res.statusCode)
return
}
// 再判断业务状态码
const result = res.data
if (result.code !== 0 && result.code !== 200) {
// 业务失败
wx.showToast({
title: result.message || '请求失败',
icon: 'none'
})
return
}
// 业务成功
console.log('用户信息:', result.data)
}
})
八、常见错误排查实战 —— 遇到问题怎么办?
8.1 配置问题:找不到服务器
现象: fail 回调被调用,报错信息里包含 errCode: -1 或 errMsg: "request:fail..."
排查步骤:
// 第一步:检查baseURL是否正确
const BASE_URL = 'https://api.example.com'
// 第二步:在开发者工具里打开调试面板
// Network标签可以看到所有请求
wx.request({
url: BASE_URL + '/users',
success: function(res) {
console.log('成功', res)
},
fail: function(err) {
console.error('失败', err)
// 看err里的errMsg,常见错误:
// "request:fail url not in domain list"
// → 说明域名没在小程序后台配置
}
})
解决方案:
- 去微信公众平台 → 开发管理 → 开发设置 → 服务器域名
- 在
request合法域名里添加你的服务器域名 - 注意: 必须是
https://开头的域名,且已备案
8.2 网络问题:请求超时
现象: errMsg: "request:fail timeout"
// 设置超时时间(默认60秒)
wx.request({
url: 'https://api.example.com/slow-endpoint',
timeout: 10000, // 10秒超时
success: function(res) {
console.log('成功', res.data)
},
fail: function(err) {
if (err.errMsg.indexOf('timeout') !== -1) {
wx.showToast({
title: '网络太慢了,请重试',
icon: 'none'
})
}
}
})
排查建议:
- 检查网络连接是否正常
- 联系后端确认接口响应时间
- 对于慢接口,给用户加 loading 提示
8.3 跨域问题:小程序里不存在!
很多人会问:”微信小程序有跨域问题吗?”
答案是:没有。跨域限制是浏览器安全策略,小程序运行在腾讯服务器里,不走浏览器CORS机制。但你仍然需要配置服务器域名白名单。
8.4 参数传递问题:数据丢了或错了
GET请求参数丢失:
// 错误写法:data里没有参数,URL里也没有
wx.request({
url: 'https://api.example.com/users',
data: {}, // 空的
success: function(res) {
// 服务器收到的是 GET /users,没有搜索条件
}
})
// 正确写法
wx.request({
url: 'https://api.example.com/users',
data: {
keyword: '搜索词'
},
success: function(res) {
// 实际请求: GET /users?keyword=搜索词
}
})
POST请求数据格式错误:
// 错误:服务器期望JSON,你传了form格式
wx.request({
url: 'https://api.example.com/users',
method: 'POST',
// 没有设置Content-Type,默认是application/x-www-form-urlencoded
data: {
name: '张三'
},
success: function(res) {
// 服务器可能解析失败,返回400
}
})
// 正确:显式设置Content-Type为json
wx.request({
url: 'https://api.example.com/users',
method: 'POST',
header: {
'Content-Type': 'application/json'
},
data: {
name: '张三'
},
success: function(res) {
console.log('创建成功', res.data)
}
})
8.5 Token认证问题:401 Unauthorized
// 带Token的请求
wx.request({
url: 'https://api.example.com/users/me',
header: {
'Authorization': 'Bearer ' + wx.getStorageSync('token')
},
success: function(res) {
if (res.statusCode === 401) {
// Token过期或无效
wx.removeStorageSync('token')
wx.reLaunch({
url: '/pages/login/login'
})
}
}
})
封装一个自动重试过期Token的函数:
// token管理类
class TokenManager {
static async refreshToken() {
const refreshToken = wx.getStorageSync('refreshToken')
if (!refreshToken) {
throw new Error('无refreshToken')
}
return new Promise((resolve, reject) => {
wx.request({
url: 'https://api.example.com/auth/refresh',
method: 'POST',
data: { refreshToken },
success(res) {
if (res.data.code === 0) {
wx.setStorageSync('token', res.data.data.accessToken)
resolve(res.data.data.accessToken)
} else {
reject(new Error('refresh failed'))
}
},
fail(err) {
reject(err)
}
})
})
}
}
// 请求拦截器
function request(options) {
return new Promise((resolve, reject) => {
wx.request({
...options,
header: {
...options.header,
'Authorization': 'Bearer ' + wx.getStorageSync('token')
},
success(res) {
if (res.statusCode === 401) {
// Token过期,尝试刷新
TokenManager.refreshToken()
.then(newToken => {
// 用新token重试
options.header['Authorization'] = 'Bearer ' + newToken
request(options).then(resolve).catch(reject)
})
.catch(() => {
wx.reLaunch({ url: '/pages/login/login' })
reject(new Error('登录已过期'))
})
} else {
resolve(res)
}
},
fail(err) {
reject(err)
}
})
})
}
8.6 数据解析问题
// 有时候服务器返回的data不是标准JSON
wx.request({
url: 'https://api.example.com/data',
success: function(res) {
console.log('原始数据:', res.data)
console.log('数据类型:', typeof res.data)
// 如果是字符串,需要手动解析
if (typeof res.data === 'string') {
try {
const data = JSON.parse(res.data)
console.log('解析后:', data)
} catch (e) {
console.error('JSON解析失败:', e)
}
}
}
})
如何判断服务器返回了什么格式?
- 看
res.header['Content-Type'] application/json→ JSON对象text/html→ HTML字符串application/octet-stream→ 二进制数据
九、实战项目:封装一个通用的请求工具
实际项目中,我们不会每次都写 wx.request,而是封装一个工具类:
// utils/request.js
const BASE_URL = 'https://api.example.com'
// 请求队列,用于并发控制
let pendingRequests = 0
class Request {
// 显示loading
static showLoading(title = '加载中...') {
wx.showLoading({ title, mask: true })
}
// 隐藏loading
static hideLoading() {
wx.hideLoading()
}
// 显示错误提示
static showError(msg) {
wx.showToast({ title: msg, icon: 'none', duration: 2000 })
}
// 核心请求方法
static request(options) {
// 默认配置
const config = {
baseUrl: BASE_URL,
method: 'GET',
header: {
'Content-Type': 'application/json'
},
showLoading: false,
showErrorTip: true,
...options
}
// 自动添加token
const token = wx.getStorageSync('token')
if (token) {
config.header['Authorization'] = 'Bearer ' + token
}
// 构建完整URL
let url = config.baseUrl
if (config.url.indexOf('http') !== 0) {
url += config.url
}
// 显示loading
if (config.showLoading) {
this.showLoading()
}
return new Promise((resolve, reject) => {
wx.request({
url,
method: config.method,
data: config.data,
header: config.header,
timeout: config.timeout || 30000,
success: (res) => {
if (config.showLoading) {
this.hideLoading()
}
// 检查HTTP状态码
if (res.statusCode >= 200 && res.statusCode < 300) {
// 检查业务状态码
if (res.data.code === 0 || res.data.code === 200) {
resolve(res.data)
} else {
// 业务错误
if (config.showErrorTip) {
this.showError(res.data.message || '操作失败')
}
reject(res.data)
}
} else if (res.statusCode === 401) {
// 未授权
this.showError('请先登录')
wx.reLaunch({ url: '/pages/login/login' })
reject(new Error('未授权'))
} else if (res.statusCode === 403) {
this.showError('没有权限操作')
reject(new Error('无权限'))
} else if (res.statusCode === 404) {
this.showError('请求的资源不存在')
reject(new Error('不存在'))
} else if (res.statusCode >= 500) {
this.showError('服务器错误,请稍后重试')
reject(new Error('服务器错误'))
} else {
this.showError('请求失败:' + res.statusCode)
reject(new Error('请求失败'))
}
},
fail: (err) => {
if (config.showLoading) {
this.hideLoading()
}
let errorMsg = '网络请求失败'
if (err.errMsg.indexOf('timeout') !== -1) {
errorMsg = '请求超时,请检查网络'
} else if (err.errMsg.indexOf('fail') !== -1) {
errorMsg = '网络连接失败'
}
if (config.showErrorTip) {
this.showError(errorMsg)
}
reject(err)
}
})
})
}
// 便捷方法
static get(url, data = {}, options = {}) {
return this.request({
url,
method: 'GET',
data,
...options
})
}
static post(url, data = {}, options = {}) {
return this.request({
url,
method: 'POST',
data,
...options
})
}
static put(url, data = {}, options = {}) {
return this.request({
url,
method: 'PUT',
data,
...options
})
}
static delete(url, options = {}) {
return this.request({
url,
method: 'DELETE',
...options
})
}
}
export default Request
使用示例:
// pages/user/user.js
import Request from '../../utils/request'
Page({
data: {
user: null,
loading: false
},
onLoad() {
this.loadUserInfo()
},
async loadUserInfo() {
this.setData({ loading: true })
try {
const res = await Request.get('/users/me', {}, {
showLoading: true
})
this.setData({
user: res.data
})
} catch (err) {
console.error('加载用户信息失败:', err)
} finally {
this.setData({ loading: false })
}
},
async updateUserName() {
try {
const res = await Request.post('/users/name', {
name: '新名字'
})
wx.showToast({ title: '修改成功' })
this.loadUserInfo()
} catch (err) {
console.error('修改失败:', err)
}
}
})
十、调试技巧 —— 如何快速定位问题
10.1 微信开发者工具调试
- 打开调试面板:开发者工具底部有
调试器面板 - Network标签:可以看到所有网络请求
- 查看请求详情:点击某个请求,可以看到请求头、响应头、请求参数、响应数据
10.2 打印关键信息
// 调试时打印完整请求信息
wx.request({
url: 'https://api.example.com/users',
success: function(res) {
console.log('=== 请求成功 ===')
console.log('状态码:', res.statusCode)
console.log('响应头:', res.header)
console.log('响应数据:', res.data)
console.log('完整响应:', JSON.stringify(res, null, 2))
},
fail: function(err) {
console.error('=== 请求失败 ===')
console.error('错误信息:', err.errMsg)
console.error('完整错误:', err)
}
})
10.3 常见问题速查表
| 问题 | 可能原因 | 解决方法 |
|---|---|---|
url not in domain list |
域名未配置 | 微信公众平台添加域名 |
request:fail timeout |
网络慢或服务器慢 | 增加timeout,检查网络 |
request:fail |
服务器挂了 | 联系后端确认 |
| 401 | Token过期 | 刷新token或重新登录 |
| 403 | 权限不足 | 检查用户权限 |
| 404 | 接口路径错误 | 检查URL和参数 |
| 500 | 服务器内部错误 | 联系后端查日志 |
| 数据解析失败 | Content-Type不对 | 检查header设置 |
十一、给初学者的一句话总结
HTTP协议就像你和服务器之间的”暗号”:
你发出请求(GET查数据,POST传数据),服务器收到后返回响应(状态码告诉你结果,Body里有数据)。状态码是服务器的表情——2xx是笑脸,4xx是”你搞错了”,5xx是”我搞砸了”。
记住:
- GET查数据,POST改数据
- 看状态码判断请求结果
- 401要重新登录,500找后端
- 域名要配置,参数要编码
- 封装工具类,事半功倍
每次遇到问题,先打开调试面板看Network,大部分问题都能从那里找到答案。
这篇文章我花了不少时间整理,希望能帮你把HTTP网络编程的基础打扎实。如果还有疑问,随时来问,我们一起排查。
