读完这篇,你能做什么
能读取请求响应示例,解析 JSON,并对四类错误输出明确且脱敏的诊断。
学习理由、准备要求与知识点
为什么学它
模型调用首先是远程接口调用,必须区分网络、HTTP、解析与业务四层失败。
先读这些内容
知识点清单
- API 契约
- HTTP 方法与地址
- 请求头与请求体
- 状态码和响应正文
- JSON 基本类型
- 序列化与反序列化
- 认证凭证与业务参数
- 超时和错误分层
运行前先确认完整算法使用 Java 17 标准库;JDK 源码基线为 OpenJDK 17 GA。示例是内存中的机制模型,外部服务接入需要另外配置和验证。
01先弄懂它是什么
程序要把学习问题发给另一个程序,双方先得约好地址、请求方法、身份信息、正文格式和错误含义,这就是 API 约定。HTTP 负责传递请求和响应,JSON 是正文可以使用的一种数据格式,它们各有用途。网络连上了,只说明通信具备条件,还要检查服务端是否真正完成了业务处理。
Java 对象不能原样变成网络传输内容,要先序列化成约定格式。对方收到字节后,再按字符编码解码、解析 JSON,并映射成 DTO。JSON 只有对象、数组、字符串、数字、布尔值和 null 等基本结构;一个字段叫 id,不会自动保证唯一,数字能解析出来,也不代表金额或数量范围合法,这些都要由业务代码检查。
HTTP状态码、响应头和正文要一起阅读。认证失败、限流、服务端错误与连接超时属于不同层级;返回200但正文含业务拒绝也可能是接口自己的约定。令牌放在哪个头、允许哪些字段、是否分页,都应来自明确契约,不能靠客户端猜测。
文中这几个词,先认一认
- 序列化
- 把程序里的对象转换成可传输或保存的形式。本篇使用 JSON 文本,再编码为 UTF-8 字节。
- 消息边界
- 一条完整业务消息从哪里开始、在哪里结束。网络一次传来的数据块,可能只是其中一部分。
- 契约
- 通信双方约好的字段、类型、状态和错误含义,决定怎样正确发送与处理数据。
02看看它怎么工作
一次响应可能分多次到达,不能对每块字节直接调用JSON解析器并认为它是完整文档。甚至一个中文字符的UTF-8编码也可能跨块,字节解码器应保留未完成字节。本篇算法从已经正确解码的字符块开始,只演示连接JSON对象的边界识别,不处理字节解码。
边界识别维护括号栈、是否位于字符串内、前一个字符是否转义。在字符串内部,花括号只是内容;在字符串外,开括号入栈,匹配的闭括号出栈。最外层对象闭合后才能输出完整文本。这个状态跨块保留,因此分块点落在引号或反斜线之后也不改变最终结果。
找到边界仍不等于JSON合法。文本{bad}具有成对花括号,却不符合对象语法;算法会先把它作为一条候选消息交给真正的JSON库,再由JSON库拒绝。把划分消息边界(分帧)、解析JSON语法、转换数据结构和校验业务规则分开,才能知道错误出在哪一步,也方便逐层测试。
正在加载流程图…
查看流程图源码(Mermaid)
flowchart LR
B["网络字节块"] --> D["增量 UTF-8 解码"]
D --> F["状态机识别帧"]
F --> J["JSON 语法解析"]
J --> T["DTO 和业务校验"]
T --> O["成功或分层错误"]按顺序看,每一步做了什么
- 按API文档构造方法、地址、头和JSON请求正文。
- 序列化为UTF-8字节,配置连接及整体请求边界。
- 增量解码收到的字节,再识别完整消息边界。
- 把完整JSON交给成熟解析器并映射DTO。
- 结合状态码、字段校验和业务规则决定成功或错误。
03对照实现代码
OpenJDK 17 GA,jdk-17+35 · HttpRequest.BodyPublishers.ofString
下方为原创 Java 教学片段,调用已核验的 JDK API 说明局部机制;不是该类原样源码,也不实现神经网络。完整程序见算法模块。
String json = "{\"question\":\"什么是索引\"}";
// ofString 默认以 UTF-8 编码;它不负责验证字符串是否是合法 JSON。
HttpRequest.BodyPublisher body = HttpRequest.BodyPublishers.ofString(json);
// 请求构建、认证、发送与响应解析是另外的调用步骤。ofString(String)的真实实现转到指定UTF-8的重载,再创建字符串请求体发布器。它处理正文编码和发布,不负责理解question字段。
BodyPublisher存在不说明请求已经发送。构造请求还需URI、方法和头,再由HttpClient执行;构建时异常和网络执行异常应该分层处理。
Content-Type说明媒体类型,不会使任意字符串自动变成JSON。正文应来自可靠序列化器,手工拼接用户输入容易漏掉引号、反斜线和控制字符转义。
本篇Java桥梁是HTTP客户端入口和字符分帧状态机。它没有实现TLS、HTTP协议栈或完整JSON解析器,真实服务集成仍应使用成熟库和明确接口契约。
代码先调用谁,再调用谁
- DTO交给JSON序列化器
BodyPublishers.ofString编码正文HttpRequest构造请求元数据HttpClient发送并接收响应- 解码、解析及业务校验
04动手写一个例子
跨字符块的JSON对象分帧
输入已正确解码的字符块列表,其中顶层只允许连续JSON对象。输出每个完整对象文本;检测括号不匹配或截断,但不验证完整JSON语法。保存Main.java后以Java17编译和运行自测。
- 输入 1
字符块{"a": 与 1}{"b":2}- 输出 1
两个帧:{"a":1}、{"b":2}
- 输入 2
空白字符块- 输出 2
空列表
栈存储当前尚未闭合的对象或数组起始符,因此遇到闭合符时可以同时检查类型和嵌套顺序。仅计算花括号数量会漏掉数组与对象交叉闭合。
字符串状态优先于括号判断,转义状态优先于结束引号判断。遇到反斜线后,下一个字符作为转义内容处理,然后清除标记,不把其中的引号视为字符串结束。
每个字符只进入当前帧一次。外层栈清空时立即输出并重置缓冲区;所有块读完仍有未闭合状态则报截断。没有消息结束信号前,短暂空读不能被当成截断。
为什么这样写是对的处理任意字符前缀后,栈和字符串标记完整描述当前未结束帧的结构状态;已输出列表只包含完整闭合的顶层对象,字符顺序保持不变。
C为全部字符数,d为最大嵌套深度。保留全部输出文本需要O(C),若逐帧交付则工作内存为最大帧长度加O(d);真实实现还应限制最大帧与嵌套深度。
保存为 Main.java,运行 javac --release 17 -encoding UTF-8 Main.java,再运行 java Main。程序包含固定输入自测,断言失败时会抛出错误。
展开收起代码 · Main.java · 完整可运行程序
长行可在代码区域内左右滚动,按 Tab 可把焦点移入代码区。
import java.util.*;
public class Main {
static List<String> frames(List<String> chunks){
List<String> out=new ArrayList<>(); Deque<Character> stack=new ArrayDeque<>();
StringBuilder b=new StringBuilder(); boolean in=false,escape=false;
for(String chunk:chunks)for(char c:chunk.toCharArray()){
if(stack.isEmpty()){
if(Character.isWhitespace(c))continue;
if(c!='{')throw new IllegalArgumentException("object expected");
}
b.append(c);
if(in){
if(escape)escape=false; // 转义后的引号不能结束字符串。
else if(c==92)escape=true; // 92 是反斜线字符。
else if(c=='"')in=false;
continue;
}
if(c=='"'){in=true;continue;}
if(c=='{'||c=='[')stack.push(c);
else if(c=='}'||c==']'){
if(stack.isEmpty()||stack.pop()!=(c=='}'?'{':'['))throw new IllegalArgumentException("mismatch");
if(stack.isEmpty()){out.add(b.toString());b.setLength(0);}
}
}
if(in||!stack.isEmpty())throw new IllegalArgumentException("truncated");
return out;
}
static void check(List<String> expected,List<String> actual){if(!expected.equals(actual))throw new AssertionError(actual);}
static void rejects(String text){try{frames(List.of(text));throw new AssertionError();}catch(IllegalArgumentException expected){}}
public static void main(String[] args){
check(List.of("{\"a\":1}","{\"b\":2}"),frames(List.of("{\"a\":","1}{\"b\":2}")));
check(List.of("{\"x\":\"}\"}"),frames(List.of("{\"x\":\"}","\"}")));// 字符串中的括号。
check(List.of("{\"x\":\"a\\\"b\"}"),frames(List.of("{\"x\":\"a\\","\"b\"}")));
check(List.of(),frames(List.of(" ","\n")));
rejects("{\"a\":[}"); rejects("{\"a\":1");
check(List.of("{bad}"),frames(List.of("{bad}")));// 分帧器不冒充语法解析器。
System.out.println("PASS 7 tests");
}
}这些特殊情况也要试一试
- 字符串内花括号不影响结构栈。
- 反斜线和转义引号跨块时必须保持状态。
- 顶层数组或标量不在本题契约内,不能误称支持全部JSON流。
进阶选读 · 后续再读:受资源约束的流式协议解析
场景攻击者发送无限长字符串或极深嵌套,客户端缓冲区持续增长。
做法在解码和分帧两层分别设置字节、字符、嵌套与整体时间限制;取消时释放缓冲与下游任务,并将半成品标记为不完整。
复杂度扫描仍为O(C),受控工作空间O(B+d),B为最大允许帧长;达到限制时明确失败,不能继续无限累积。
好处与代价缓冲越大越能容纳合法大结果,但内存占用和拒绝服务风险越高;分页与分段DTO常比单个巨大JSON更可控。
05项目里怎么用
HTTP重试必须考虑方法与业务语义。超时可能发生在服务端已经写入之后,重新发送创建请求会重复行动;应使用服务端支持的幂等键或查询结果,而不是看到超时就循环发送。认证失败通常需要修正身份,参数错误需要修正输入,退避无法自动解决这两类问题。
日志保留请求标识、耗时、状态与安全截断后的错误摘要,不直接记录认证头或整份敏感正文。客户端错误页可能是HTML而不是JSON,应先检查媒体类型与状态再决定如何解析,避免把真实网关错误掩盖成一个含糊的JSON解析异常。
可以先用内存里的响应样本测试客户端:正常内容、缺字段、多字段、错误状态、内容截断和慢响应都各准备一份。它能证明客户端如何处理这些已知情况,但没有验证真实网络、凭证和远端权限。正式集成还要检查实际发出和收到的内容,并保留不含敏感信息的请求关联标识。
容易踩的坑
- 把一个网络块等同于一个JSON文档。
- 将括号匹配成功当作完整JSON合法性证明。
- 手工拼接用户输入,遗漏字符串转义。
- 超时后一律重试写请求,忽略结果未知。
几种做法,怎么选
表格有四列,窄屏可左右滑动查看代价和适用条件。
| 方案 | 优势 | 代价与限制 | 适用条件 |
|---|---|---|---|
| 整份响应缓冲 | 解析简单,容易得到完整DTO | 大响应占内存且展示较晚 | 有明确响应上限的接口 |
| 逐帧解析 | 可早处理并限制单帧内存 | 需维护边界和取消状态 | 流式事件与长任务 |
| 成熟序列化库 | 正确处理类型与转义 | 仍需业务验证和配置 | 正式API输入输出 |
06检验是否读懂了
先自己想、自己写,再展开答案核对。场景题是假设的练习情境;遇到还没学过的内容,可以做完基础后再回来。
先用自己的话解释
01JSON与HTTP是什么关系?
展开参考答案与推导
HTTP是通信协议,JSON是正文表示形式之一;HTTP可以传文本、图片等,JSON也能存到文件。
02状态码成功为什么还要校验正文?
展开参考答案与推导
传输层或接口层成功不保证字段符合业务规则,正文也可能使用约定的业务错误结构,必须按契约解释。
03分帧器为何接受{bad}?
展开参考答案与推导
它只找对象边界与检查结构闭合,不检查键名引号和冒号语法;该文本随后必须由JSON解析器拒绝。
换个情况,还会处理吗
01中文字符偶尔乱码,应先改模型提示吗?
展开参考答案与推导
先检查字节到字符的增量解码和编码一致性;网络块可能切断UTF-8字符,逐块独立解码会造成替换字符。
02服务端返回HTML错误页怎么办?
展开参考答案与推导
保留状态、媒体类型和脱敏摘要,归类为网关或服务错误,不强行映射业务DTO;再结合请求标识排查。
03创建操作超时,能直接重发吗?
展开参考答案与推导
先查幂等机制与操作结果。超时不说明服务端未执行;缺少安全重试条件时应保留结果未知并核对。
写代码试一试
01添加嵌套数组测试
- 输入 / 条件
{"a":[{"b":1}]}- 输出 / 预期
一个完整帧
提示 两种起始括号入同一栈。
展开参考答案与推导
每个闭合符匹配栈顶类型,最外层对象退出后输出;不能在内部对象闭合时提前输出。
02怎样限制超长输入?
- 输入 / 条件
最大帧10字符,输入超过10- 输出 / 预期
拒绝并停止累积
提示 在追加字符时检查。
展开参考答案与推导
对单帧缓冲计数,超过上限立即失败;还应限制字符解码前的字节量,避免先分配巨大字符串再检查。
03为什么最后一块结束才检查截断?
- 输入 / 条件
第一块{"a":,第二块1}- 输出 / 预期
完整帧
提示 块结束不等于消息结束。
展开参考答案与推导
中途保留状态等待后续字符;只有确认输入流结束仍有未闭合结构时才报告截断。
读完之后,独立完成
课后作业:自己做一遍
本篇共 10 道作业,先完成 8 道基础与综合任务,再按前置知识挑战进阶题。其中包含 1 道思维拓展,试着换个条件,看看原来的办法是否还管用。不附参考答案。
能读取请求响应示例,解析 JSON,并对四类错误输出明确且脱敏的诊断。
本篇重点加练
接口契约与错误层次决定客户端如何理解失败,需要区分观察证据和推测。
- API 契约
- 超时和错误分层
重点内容会多练几次。先说清楚怎么做,再试试空数据、出错或条件改变时会发生什么。
先自己做,卡住时记下问题,再回来查。问答题写清“为什么、什么时候适用”;动手题留下输入、输出和运行步骤,方便下次重做。
基础必做
- 作业 01原理问答
为查询笔记和新增笔记各写一份接口契约,说明方法、地址、参数位置和重复请求行为,不把URL名称当作完整契约。
- 提交内容
- 一份独立书面回答或标注图,包含自造例子、概念边界及判断依据。
- 怎样算完成
- 逐项回应题目要求,区分事实、假设与个人判断。
- 能用自己的例子解释适用条件,记录尚未弄懂的点。
练到的知识点API 契约 · HTTP 方法与地址
- 作业 02代码推演
给出状态码200但正文是业务错误、状态码401、连接超时三条记录,逐项标记请求头、响应头、正文和缺失的证据。
- 提交内容
- 手工推演记录,写明输入约定、中间状态、最终判断;可运行时附程序对照记录。
- 怎样算完成
- 推演包含中间过程,数据形状、单位、顺序或状态前后一致。
- 主动检查至少一个边界或异常条件,能解释手算与运行差异。
练到的知识点请求头与请求体 · 状态码和响应正文
- 作业 03原理问答
为“没有收到响应”写一份故障分类表,列出客户端能够直接观察的事实与仍需服务器记录才能确认的事项。
- 提交内容
- 概念或合同说明、关系图与自造案例,明确事实、假设和适用条件。
- 怎样算完成
- 关键概念有明确边界,能用案例检验声明。
- 未获证据支持的假设单独标注。
练到的知识点API 契约 · 超时和错误分层
综合应用
- 作业 04面试场景
将字符串"12"、数值12、
null、空数组和缺失字段送入自定JSON契约,推演序列化与反序列化后应用应如何判断类型。- 提交内容
- 场景分析与决策记录,包含信息缺口、约束、备选方案和验证计划。
- 怎样算完成
- 方案回应给定约束,列出至少一种替代方案及代价。
- 每项关键结论说明所需证据,不能核实的部分明确标注。
练到的知识点JSON 基本类型 · 序列化与反序列化
- 作业 05编程实践
使用代码中的预制响应或本地文本样本,模拟认证缺失、参数非法、处理超时和服务异常。编写小型解析与分类脚本,凭证仅用占位值,提交响应正文、协议状态与错误类别;不要求实现HTTP服务器。
- 提交内容
- 可复现的最小程序或实验材料、自写测试、环境说明与运行记录;题目允许纸面实验时提交完整案例表。
- 怎样算完成
- 提交物按说明可以重现;测试覆盖正常、边界与失败场景。
- 运行证据与断言能核验声明的行为,注明模拟与真实执行范围。
练到的知识点认证凭证与业务参数 · 超时和错误分层
- 作业 06排错实验
制作Content-Type不匹配与截断JSON的两类预制响应,使用小脚本核验正文能否解析,再结合协议记录分析失败可能位于哪一层;区分实际观察与尚需服务器证据的判断。
- 提交内容
- 最小故障输入、复现步骤、观测记录、定位依据及回归用例。
- 怎样算完成
- 其他人可按记录复现故障,并区分现象与已验证原因。
- 定位过程有观测依据,回归用例可以再次暴露原问题。
练到的知识点HTTP 方法与地址 · 请求头与请求体 · 序列化与反序列化
- 作业 07编程实践
为本地预制的接口v1、v2记录编写同一组客户端验收测试:自行指定两版的字段类型、错误码与超时语义,让部分合同保持、部分改变;检查哪些使用方式仍兼容,不要求实现服务器。
- 提交内容
- 可复现的小程序或题目允许的离线实验、自写测试、输入和运行记录。
- 怎样算完成
- 输入、环境和运行过程可重现,测试覆盖声明的重点条件。
- 验证目标由合同确定,模拟结果不冒充真实服务证据。
练到的知识点API 契约 · 超时和错误分层
- 作业 08面试场景
同一查询接口在浏览器能成功、脚本超时。制定逐层比对清单,限定只比较方法、地址、头、参数与时限,不把网络现象直接归因为业务代码错误。
- 提交内容
- 场景评审记录,包含约束、备选方案、未决信息和验证计划。
- 怎样算完成
- 方案回应题目约束,取舍能对应到可核验的事实。
- 给出失败或条件变化时的检查方式。
练到的知识点API 契约 · 超时和错误分层
进阶挑战 · 掌握前置后选做
- 作业 09设计评审
在掌握基础后设计一个统一错误响应格式,保留协议状态与业务错误的区分,并评审它是否能支撑前端重试和问题定位。
- 提交内容
- 设计评审文档或对照实验报告,包含前置条件、方案取舍、验收证据与尚未验证的范围。
- 怎样算完成
- 比较使用一致条件与事先声明的标准,记录失败与未知结果。
- 说明方案的适用前提、未覆盖风险及后续验证方法。
练到的知识点API 契约 · 状态码和响应正文 · 超时和错误分层
思维拓展
先写出你的猜想,再用例子或实验验证。答案可以不同,但要说清依据、适用情况,以及还有什么没弄懂。
- 作业 10思维拓展 · 跨域迁移
将协议错误与业务错误分层的思想迁移到本地命令行工具,设计退出码、标准输出与业务结果之间的合同,并给出可区分的样本。
- 提交内容
- 原场景与目标场景的对应表、可迁移的机制、不能迁移的前提及验证设计。
- 怎样算完成
- 逐项核对两种场景的前提,不只比较相似术语。
- 至少一个不能直接迁移的边界有具体检验方法。
练到的知识点API 契约 · 超时和错误分层
完成后,再检验一次
给每题标记“独立完成 / 查阅后完成 / 尚未完成”。查过资料或没做出的题,记下卡在哪里。隔一天换一组输入再试,能解释结果和出错原因,再把这个知识点标为掌握。
继续阅读
先看能帮你完成当前练习的部分。例子跑通后,再读更深入的解释。
- 必读教程中文
MDN:HTTP 概述 (新标签页)↗
阅读请求、响应、方法与状态码的组成,先画出一次 API 交互再排查认证和网络问题。
- 实践教程中文
MDN:使用 JSON (新标签页)↗
先做 JSON 对象、数组和类型的阅读练习,分清数据文本与语言中的对象表示。
- 深入参考文档中文
Python 3.12:JSON 编解码 (新标签页)↗
查阅 loads、dumps 和
JSONDecodeError,用来实现数据交换并区分解析与业务校验。 - 深入参考文档中文
Python 3.12:urllib.request (新标签页)↗
查阅 Request、urlopen、超时参数和响应处理;只在理解具体接口契约后再进行真实网络调用。
- 实践实践指南中文
FastAPI 接口入门:把 Python 数据接成可调用的服务 (新标签页)↗
先读创建数据模型和声明请求参数。小练习:做一个只返回学习记录的本地接口,发送正常数据、缺少必填字段和错误类型三种请求,记录响应差别;暂不接真实模型。 文档公开,示例标有 Python 3.10+。中文页包含人类引导的 AI 翻译,疑义可对照英文。
- 深入参考文档英文
OpenJDK 17 HttpRequest 源码 (新标签页)↗
按指定版本定位
HttpRequest.BodyPublishers.ofString;对照源码分析理解实际调用边界。