您当前的位置: 首页 > 慢生活 > 程序人生 网站首页程序人生
flutter-构建布局 - 教程、使用更原生的http客户端、解决请求状态码406问题
发布时间:2026-07-09 23:18:53编辑:雪饮阅读()
-
构建布局的官方链接是这个
https://docs.flutter.dev/ui/layout/tutorial
这里写的其实已经非常清楚详细,也专业。
那我这里则是继续上篇没有完成的数据响应回来的渲染。
那么上篇我看到服务端响应状态码是406

这个406状态码意思是服务端响应的内容,客户端无法解析,实际上我这里看到响应体是什么也没有。
经过我的折腾,我发现这样可以解决问题。
先看完整代码search_result_table.dart:
import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:http/io_client.dart';
class SearchResultTable extends StatefulWidget {
const SearchResultTable({super.key});
@override
State<SearchResultTable> createState() => _SearchResultTableState();
}
class _SearchResultTableState extends State<SearchResultTable> {
final TextEditingController _apiController = TextEditingController(
text: "https://search-server.xoczl.net/Search/Social/Query",
);
final TextEditingController _keywordController = TextEditingController(
text: "搭讪",
);
final TextEditingController authorizationController = TextEditingController(
text:
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoi5ri45a6iLTEiLCJpZCI6IjI0MDAwMDAwMDAyIiwiaXAiOiI1Mi44OC4xNjkuMjUzIiwidHlwZSI6MywicGFja2FnZV9pZCI6MSwibGV2ZWwiOjEsImlzcyI6IkdVRVNUIiwic3ViIjoic29jaWFsLXNlcnZlciIsImV4cCI6MTc4NDIxMjU3MCwibmJmIjoxNzgzNjA3NzcwLCJpYXQiOjE3ODM2MDc3NzAsImp0aSI6ImUwMTdmOWI2MjlmNDRjMmU5YTMxMDlkOWZkNWYzMDEyIn0.J1cuFSnZ0Xi4eE0sA-aF_c3LcN_YttNqzipvETBKPl0",
);
@override
void dispose() {
_apiController.dispose();
_keywordController.dispose();
authorizationController.dispose();
super.dispose();
}
Widget _buildGridCell({
required Widget child,
bool rightBorder = false,
bool bottomBorder = false,
}) {
return Container(
decoration: BoxDecoration(
border: Border(
right: rightBorder ? const BorderSide() : BorderSide.none,
bottom: bottomBorder ? const BorderSide() : BorderSide.none,
),
),
child: child,
);
}
Widget _buildHeaderCell(String text) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Text(text, style: const TextStyle(fontWeight: FontWeight.bold)),
);
}
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(border: Border.all()),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
Expanded(
child: TextField(
controller: _apiController,
decoration: const InputDecoration(
hintText: '请输入api地址',
border: OutlineInputBorder(),
),
),
),
const SizedBox(width: 8),
Expanded(
child: TextField(
controller: _keywordController,
decoration: const InputDecoration(
hintText: '请输入关键词',
border: OutlineInputBorder(),
),
),
),
const SizedBox(width: 8),
Expanded(
child: TextField(
controller: authorizationController,
decoration: const InputDecoration(
hintText: '请输入authorization',
border: OutlineInputBorder(),
),
),
),
],
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
//搜索
ElevatedButton(
onPressed: () async {
if (_apiController.text.isEmpty) {
ScaffoldMessenger.of(
context,
).showSnackBar(const SnackBar(content: Text('请输入api地址')));
return;
}
if (_keywordController.text.isEmpty) {
ScaffoldMessenger.of(
context,
).showSnackBar(const SnackBar(content: Text('请输入关键词')));
return;
}
/*
发起http的post请求
请求头需要包含authorization
请求头content-type的值为application/json
请求的json对象格式为:
var jsonObject=[{"name":"social.post","limit":100,"page":1,"or":[{"field":"title","op":"$like","value":"%kasumi"},{"field":"tags","op":"$in","value":["kasumi"]}],"and":[{"field":"categories_id","op":"$nin","value":["137","257","347","264","764","931","97","101","365","534","115","361","335","389","336","425","367","456","930","85","2","382","78","252","154","1108","917","416","344","1107","337","74","99","281","93","230","454","166","175","397","452","414","285","928","766","1009","288","113","322","49","1106","426","81","758","332","104","88","364","1011","333","57","240","695","86","396","677","41","265","959","102","56","358","221","301","929","242","187","224","789","340","349","55","231","402","105","169","461","346","50","182","210","676","48","292","95","413","131","334","35","348","310","455","43","246","77","401","330","293","355","84","161","23","312","1012","359","771","87","124","163","395","1214","123","927","926","388","39","934","202","274","453","214","286","932","375","405","212","404","38","7","46","40","363","268","1110","75","350","262","218","394","92","37","247","128","675","933"]},{"field":"status","op":"$in","value":[1,9]}],"sort":[{"field":"create_time","order":"DESC"}]}];
这个json对象中仅仅用输入的关键词替换这个对象中的kasumi
*/
final keyword = _keywordController.text;
final apiUrl = _apiController.text;
final auth = authorizationController.text;
final requestBody = [
{
"name": "social.post",
"limit": 100,
"page": 1,
"or": [
{
"field": "title",
"op": "\$like",
"value": "%$keyword",
},
{
"field": "tags",
"op": "\$in",
"value": [keyword],
},
],
"and": [
{
"field": "categories_id",
"op": "\$nin",
"value": [
"137",
"257",
"347",
"264",
"764",
"931",
"97",
"101",
"365",
"534",
"115",
"361",
"335",
"389",
"336",
"425",
"367",
"456",
"930",
"85",
"2",
"382",
"78",
"252",
"154",
"1108",
"917",
"416",
"344",
"1107",
"337",
"74",
"99",
"281",
"93",
"230",
"454",
"166",
"175",
"397",
"452",
"414",
"285",
"928",
"766",
"1009",
"288",
"113",
"322",
"49",
"1106",
"426",
"81",
"758",
"332",
"104",
"88",
"364",
"1011",
"333",
"57",
"240",
"695",
"86",
"396",
"677",
"41",
"265",
"959",
"102",
"56",
"358",
"221",
"301",
"929",
"242",
"187",
"224",
"789",
"340",
"349",
"55",
"231",
"402",
"105",
"169",
"461",
"346",
"50",
"182",
"210",
"676",
"48",
"292",
"95",
"413",
"131",
"334",
"35",
"348",
"310",
"455",
"43",
"246",
"77",
"401",
"330",
"293",
"355",
"84",
"161",
"23",
"312",
"1012",
"359",
"771",
"87",
"124",
"163",
"395",
"1214",
"123",
"927",
"926",
"388",
"39",
"934",
"202",
"274",
"453",
"214",
"286",
"932",
"375",
"405",
"212",
"404",
"38",
"7",
"46",
"40",
"363",
"268",
"1110",
"75",
"350",
"262",
"218",
"394",
"92",
"37",
"247",
"128",
"675",
"933",
],
},
{
"field": "status",
"op": "\$in",
"value": [1, 9],
},
],
"sort": [
{"field": "create_time", "order": "DESC"},
],
},
];
try {
final ioClient = HttpClient()
..badCertificateCallback =
(X509Certificate cert, String host, int port) =>
true;
final client = IOClient(ioClient);
final response = await client.post(
Uri.parse(apiUrl),
headers: {
'authorization': auth,
'accept': 'application/json',
'Content-Type': 'application/json',
'origin': 'https://xn--10jong-2o7io6t.30gnao.space',
'referer': 'https://xn--10jong-2o7io6t.30gnao.space/',
'X-Bundle-Info': '1, com.xingba.web.online',
},
body: jsonEncode(requestBody),
);
client.close();
if (response.statusCode == 200) {
debugPrint('请求成功, 响应: ${response.body}');
} else {
debugPrint(
'请求失败, 状态码: ${response.statusCode}, 响应: ${response.body}',
);
}
} catch (e) {
debugPrint('请求异常: $e');
}
debugPrint(
'搜索按钮被点击, api: ${_apiController.text}, 关键词: ${_keywordController.text}',
);
},
child: const Text('搜索'),
),
const SizedBox(width: 8),
ElevatedButton(
onPressed: () {
debugPrint('屏蔽关键词管理按钮被点击');
},
child: const Text('屏蔽关键词管理'),
),
],
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Row(
children: [
const Text(
'搜索结果',
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
),
],
),
),
const Divider(),
Row(
children: [
Expanded(
flex: 2,
child: _buildGridCell(
child: _buildHeaderCell('标题'),
rightBorder: true,
bottomBorder: true,
),
),
Expanded(
flex: 2,
child: _buildGridCell(
child: _buildHeaderCell('图片'),
rightBorder: true,
bottomBorder: true,
),
),
Expanded(
child: _buildGridCell(
child: _buildHeaderCell('操作'),
bottomBorder: true,
),
),
],
),
Row(
children: [
Expanded(
flex: 2,
child: _buildGridCell(
child: const Padding(
padding: EdgeInsets.all(8.0),
child: Text('标题1'),
),
rightBorder: true,
bottomBorder: true,
),
),
Expanded(
flex: 2,
child: _buildGridCell(
child: const Padding(
padding: EdgeInsets.all(8.0),
child: Text('图片1.jpg'),
),
rightBorder: true,
bottomBorder: true,
),
),
Expanded(
child: _buildGridCell(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: ElevatedButton(
style: ElevatedButton.styleFrom(
minimumSize: Size.zero,
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 8,
),
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
onPressed: () {
debugPrint('详情按钮被点击');
},
child: const Text('详情'),
),
),
bottomBorder: true,
),
),
],
),
Row(
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('共10条记录,共2页'),
const SizedBox(width: 16),
ElevatedButton(
style: ElevatedButton.styleFrom(
minimumSize: Size.zero,
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 8,
),
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
onPressed: () {
debugPrint('下一页按钮被点击');
},
child: const Text('下一页'),
),
const SizedBox(width: 8),
ElevatedButton(
style: ElevatedButton.styleFrom(
minimumSize: Size.zero,
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 8,
),
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
onPressed: () {
debugPrint('上一页按钮被点击');
},
child: const Text('上一页'),
),
],
),
),
),
],
),
],
),
);
}
}
接下来我来说明下核心的地方:
try {
final ioClient = HttpClient()
..badCertificateCallback =
(X509Certificate cert, String host, int port) =>
true;
final client = IOClient(ioClient);
final response = await client.post(
Uri.parse(apiUrl),
headers: {
'authorization': auth,
'accept': 'application/json',
'Content-Type': 'application/json',
'origin': 'https://xn--10jong-2o7io6t.30gnao.space',
'referer': 'https://xn--10jong-2o7io6t.30gnao.space/',
'X-Bundle-Info': '1, com.xingba.web.online',
},
body: jsonEncode(requestBody),
);
client.close();
if (response.statusCode == 200) {
debugPrint('请求成功, 响应: ${response.body}');
} else {
debugPrint(
'请求失败, 状态码: ${response.statusCode}, 响应: ${response.body}',
);
}
} catch (e) {
debugPrint('请求异常: $e');
}
这里其实是我不断测试对方接口,我一开始以为是请求头与证书校验问题,那么我这里的操作主要取消证书校验的是:
final ioClient = HttpClient()
..badCertificateCallback =
(X509Certificate cert, String host, int port) =>
true;
final client = IOClient(ioClient);
这里就不能用原本的标准http客户端,而是更底层一点的才可以定义证书校验。
尤其是:
final ioClient = HttpClient()
..badCertificateCallback =
(X509Certificate cert, String host, int port) =>
true;
你会以为是两行代码,其实这里”..”运算符叫做级联运算符,它的作用是在同一个对象上 连续调用多个操作 ,而不需要重复写对象名。
完全等价于 这样写:
final temp = HttpClient();
temp.badCertificateCallback = (X509Certificate cert, String host, int port) => true;
final ioClient = temp;
那么这里的:
(X509Certificate cert, String host, int port) =>
true;
这个又是什么意思呢?
这个语法呢?后面一个箭头?箭头后面又一个true?
这是 Dart 的 简写函数 语法,当函数体只有一个表达式时,可以用 => 代替 { return ...; } 。
这段代码等价写法:
(X509Certificate cert, String host, int port) {
return true;
}
语法规则:
// 箭头函数(只能写一行表达式)
(参数) => 表达式;
// 普通函数(可以写多行语句)
(参数) {
语句;
return 值;
}
在这段代码中的含义
..badCertificateCallback = (X509Certificate cert, String host, int port) => true;
意思是:给 badCertificateCallback 赋值一个回调函数,这个函数接收 3 个参数(证书、主机名、端口), 无论什么证书都返回 true ,即"信任所有证书"。
关键字词:flutter,布局,教程,http,客户端,原生,状态码,406