第21篇_源码加更 03|Builder、方法转换和状态码文本
2026/9/3 21:05:44 网站建设 项目流程

适合谁收藏

  • 准备复用本组源码并审查对象边界的工程师。
  • 需要把公开代码装配进目标 CodeSys 工程的人。
  • 准备重新完成编译、离线测试和真机验证的读者。

开源范围本篇完整公开 3 个 ST 文件。代码直接读取已验证工程,保留声明、实现、注释和缩进;没有伪代码,没有跨文件拼接,也没有省略号。

先给结论

构造器把结构体变成对端可识别的 HTTP/1.1 报文。方法文本、原因短语、长度和连接策略在这里统一收口。 本篇的通过标准不是“代码已经贴出”,而是每个文件的职责、调用位置、状态边界和验证入口都能对应起来,并且完整代码可逐字回查源文件。

读图重点

先找到FB_HttpMessageBuilder在本组中的位置,再沿图确认其余对象分别承担数据、状态、执行或诊断职责。图只给阅读顺序,最终判断必须回到下面的完整 ST 代码。

先看文件职责

序号文件职责行数
1FB_HttpMessageBuilder.st请求与响应构造器244
2F_HttpMethodToString.st方法枚举转文本35
3F_HttpStatusReason.st状态码原因短语43

阅读顺序不是按文件名机械展开。先看数据和状态,再看公开方法,最后顺着错误出口和调用对象检查边界。源码篇的目标是让读者能对照工程复现,不是用大段代码制造篇幅。

从协议约束到代码职责

协议约束

构造器把结构体变成对端可识别的 HTTP/1.1 报文。方法文本、原因短语、长度和连接策略在这里统一收口。 协议层只定义消息与状态成立的条件,工程层还必须把条件分配给确定对象,避免 Parser、Builder、Server、Client 和测试入口互相越权。

对象分工

  • 第 1 个对象FB_HttpMessageBuilder.st:承担“请求与响应构造器”。阅读时先确认输入与公开输出,再追状态、长度和错误出口,最后核对它被谁周期调用或被哪个对象消费。
  • 第 2 个对象F_HttpMethodToString.st:承担“方法枚举转文本”。阅读时先确认输入与公开输出,再追状态、长度和错误出口,最后核对它被谁周期调用或被哪个对象消费。
  • 第 3 个对象F_HttpStatusReason.st:承担“状态码原因短语”。阅读时先确认输入与公开输出,再追状态、长度和错误出口,最后核对它被谁周期调用或被哪个对象消费。

装配与验证

这组文件必须作为一个职责单元阅读和编译。先用确定输入验证“Host 是请求构造硬门槛”,再制造非法或容量边界验证“出站统一显式 Content-Length”,最后在真实通信或上层调用中确认“消息超限时拒绝发送”。如果单文件测试通过但装配后失败,应优先检查结构体、常量、状态枚举和周期调用关系,而不是立即重写核心算法。源码完整公开只证明读者拿到了同一事实源,目标运行时是否通过仍需重新编译和真机取证。

本篇从FB_HttpMessageBuilder.st开始审查:先确认“Host 是请求构造硬门槛”对应的类型、常量或公开输入,再沿 CASE 或方法调用追踪成功路径,随后逐个检查容量、超时和协议错误出口,最后在F_HttpStatusReason.st对应的结果或装配位置确认错误能被观测、清理并再次执行。这个顺序专门用来区分“算法正确但没有周期调用”“状态能完成但错误被覆盖”和“消息超限时拒绝发送尚未形成验证证据”三类问题。只有本篇源码、装配和验证使用同一组对象与同一组边界,完整开源才具有可复现意义。

本篇完整开源代码

下面按职责顺序给出本篇全部 ST 文件。每个代码块保留完整声明与实现;阅读时把状态、长度、错误出口和调用对象与上面的职责表逐项对照。

完整源码 1:FB_HttpMessageBuilder.st

职责:请求与响应构造器

/// 功能 : HTTP/1.1 请求与响应构造器。 /// 库依赖 : 暂无 {attribute 'hide_all_locals'} FUNCTION_BLOCK FB_HttpMessageBuilder VAR_OUTPUT bDone : BOOL; // 报文构造完成标志。 bError : BOOL; // 错误锁存标志。 diErrorID : DINT; // 诊断错误码,0表示无错误。 eError : E_HttpError; // 最近一次构造错误码。 sDiagMsg : STRING(255); // 最近一次构造诊断文本。 udiMessageLength : UDINT; // 构造后的报文字节长度。 END_VAR // === IMPLEMENTATION === /// ======================================================================= /// 名称 : FB_HttpMessageBuilder /// 功能 : 构造首版 HTTP/1.1 Client 请求与 Server 响应文本。 /// 库依赖 : 暂无 /// ======================================================================= /// 使用说明 : 1. 由调用者通过 bConnectionClose 选择 `keep-alive` 或 `close`。 /// : 2. 不生成 chunked 出站报文,统一使用 Content-Length 便于 PLC 侧诊断。 /// ======================================================================= // === METHOD M_BuildRequest === /// ======================================================================= /// 名称 : M_BuildRequest /// 功能 : 将 ST_HttpRequest 构造为 HTTP 请求文本。 /// 说明 : Host 必填,body 非空时自动填充 Content-Length。 /// ======================================================================= {attribute 'hide_all_locals'} METHOD PUBLIC M_BuildRequest : BOOL VAR_IN_OUT stRequest : ST_HttpRequest; // 结构化协议或测试数据。 END_VAR VAR_OUTPUT sMessage : STRING(GVL_Http.cnMaxMessageSize); // 诊断或协议文本字段。 END_VAR VAR sMethod : STRING(16); // 诊断或协议文本字段。 sTarget : STRING(GVL_Http.cnMaxTargetLen); // 诊断或协议文本字段。 sContentLen : STRING(16); // 诊断或协议文本字段。 uiBodyLen : UINT; // 计数、长度或状态数值。 uiCandidate : UINT; // 计数、长度或状态数值。 END_VAR // === IMPLEMENTATION === // 工程说明:本段集中处理状态、边界或诊断,避免跨周期残留。 // 边界说明:执行前后保持输出和错误码可被在线诊断追踪。 // 原因:请求构造统一写入 Host、Connection 和 Content-Length,避免 PLC 端生成歧义 HTTP/1.1 报文。 // 约束:首版不生成 chunked 出站请求,长连接只允许顺序复用,不允许 pipeline 并发请求。 M_Reset(); M_BuildRequest := FALSE; sMessage := ''; IF LEN(stRequest.sHost) = 0 THEN M_SetError( eNewError := E_HttpError.iMissingHost, sMessage := 'request host is empty' ); RETURN; END_IF sMethod := F_HttpMethodToString( eMethod := stRequest.eMethod ); IF LEN(stRequest.sTarget) = 0 THEN sTarget := GVL_Http.cnDefaultPath; ELSE sTarget := stRequest.sTarget; END_IF uiBodyLen := TO_UINT(LEN(stRequest.sBody)); sContentLen := UINT_TO_STRING(uiBodyLen); sMessage := CONCAT(sMethod, ' '); sMessage := CONCAT(sMessage, sTarget); sMessage := CONCAT(sMessage, ' HTTP/1.1$R$NHost: '); sMessage := CONCAT(sMessage, stRequest.sHost); sMessage := CONCAT(sMessage, '$R$NConnection: '); IF stRequest.bConnectionClose THEN sMessage := CONCAT(sMessage, 'close$R$N'); ELSE sMessage := CONCAT(sMessage, 'keep-alive$R$N'); END_IF IF LEN(stRequest.sAdditionalHeader) > 0 THEN sMessage := CONCAT(sMessage, stRequest.sAdditionalHeader); sMessage := CONCAT(sMessage, '$R$N'); END_IF IF LEN(stRequest.sContentType) > 0 THEN sMessage := CONCAT(sMessage, 'Content-Type: '); sMessage := CONCAT(sMessage, stRequest.sContentType); sMessage := CONCAT(sMessage, '$R$N'); END_IF IF uiBodyLen > 0 THEN sMessage := CONCAT(sMessage, 'Content-Length: '); sMessage := CONCAT(sMessage, sContentLen); sMessage := CONCAT(sMessage, '$R$N'); END_IF sMessage := CONCAT(sMessage, '$R$N'); IF uiBodyLen > 0 THEN sMessage := CONCAT(sMessage, stRequest.sBody); END_IF uiCandidate := TO_UINT(LEN(sMessage)); IF uiCandidate >= GVL_Http.cnMaxMessageSize THEN M_SetError( eNewError := E_HttpError.iBufferTooSmall, sMessage := 'request message exceeds buffer' ); RETURN; END_IF udiMessageLength := TO_UDINT(uiCandidate); bDone := TRUE; M_BuildRequest := TRUE; // === METHOD M_BuildResponse === /// ======================================================================= /// 名称 : M_BuildResponse /// 功能 : 将 ST_HttpResponse 构造为 HTTP 响应文本。 /// 说明 : sReason 为空时自动采用常用原因短语。 /// ======================================================================= {attribute 'hide_all_locals'} METHOD PUBLIC M_BuildResponse : BOOL VAR_IN_OUT stResponse : ST_HttpResponse; // 结构化协议或测试数据。 END_VAR VAR_OUTPUT sMessage : STRING(GVL_Http.cnMaxMessageSize); // 诊断或协议文本字段。 END_VAR VAR sReason : STRING(64); // 诊断或协议文本字段。 sStatus : STRING(16); // 诊断或协议文本字段。 sContentLen : STRING(16); // 诊断或协议文本字段。 sContentType : STRING(96); // 诊断或协议文本字段。 uiBodyLen : UINT; // 计数、长度或状态数值。 uiCandidate : UINT; // 计数、长度或状态数值。 END_VAR // === IMPLEMENTATION === // 工程说明:本段集中处理状态、边界或诊断,避免跨周期残留。 // 边界说明:执行前后保持输出和错误码可被在线诊断追踪。 // 原因:响应构造始终显式 Content-Length,使外部 client 可以用真实字节数验证 Server 行为。 // 风险:状态码为 0 或 body 超过上限时必须拒绝构造,否则 Server 会发送不可诊断的坏报文。 M_Reset(); M_BuildResponse := FALSE; sMessage := ''; IF stResponse.uiStatusCode = 0 THEN M_SetError( eNewError := E_HttpError.iInvalidArgument, sMessage := 'response status code is zero' ); RETURN; END_IF IF LEN(stResponse.sReason) > 0 THEN sReason := stResponse.sReason; ELSE sReason := F_HttpStatusReason( uiStatusCode := stResponse.uiStatusCode ); END_IF IF LEN(stResponse.sContentType) > 0 THEN sContentType := stResponse.sContentType; ELSE sContentType := GVL_Http.cnDefaultContentType; END_IF uiBodyLen := TO_UINT(LEN(stResponse.sBody)); sStatus := UINT_TO_STRING(stResponse.uiStatusCode); sContentLen := UINT_TO_STRING(uiBodyLen); sMessage := CONCAT('HTTP/1.1 ', sStatus); sMessage := CONCAT(sMessage, ' '); sMessage := CONCAT(sMessage, sReason); sMessage := CONCAT(sMessage, '$R$NConnection: '); IF stResponse.bConnectionClose THEN sMessage := CONCAT(sMessage, 'close$R$NContent-Type: '); ELSE sMessage := CONCAT(sMessage, 'keep-alive$R$NContent-Type: '); END_IF sMessage := CONCAT(sMessage, sContentType); sMessage := CONCAT(sMessage, '$R$NContent-Length: '); sMessage := CONCAT(sMessage, sContentLen); sMessage := CONCAT(sMessage, '$R$N'); IF LEN(stResponse.sAdditionalHeader) > 0 THEN sMessage := CONCAT(sMessage, stResponse.sAdditionalHeader); sMessage := CONCAT(sMessage, '$R$N'); END_IF sMessage := CONCAT(sMessage, '$R$N'); IF uiBodyLen > 0 THEN sMessage := CONCAT(sMessage, stResponse.sBody); END_IF uiCandidate := TO_UINT(LEN(sMessage)); IF uiCandidate >= GVL_Http.cnMaxMessageSize THEN M_SetError( eNewError := E_HttpError.iBufferTooSmall, sMessage := 'response message exceeds buffer' ); RETURN; END_IF udiMessageLength := TO_UDINT(uiCandidate); bDone := TRUE; M_BuildResponse := TRUE; // === METHOD M_Reset === /// ======================================================================= /// 名称 : M_Reset /// 功能 : 清空构造器诊断状态。 /// 说明 : 每个公开构造方法开头调用,保证输出语义一致。 /// ======================================================================= {attribute 'hide_all_locals'} METHOD PRIVATE M_Reset // === IMPLEMENTATION === // 工程说明:本段集中处理状态、边界或诊断,避免跨周期残留。 bDone := FALSE; bError := FALSE; eError := E_HttpError.iNoError; sDiagMsg := ''; udiMessageLength := 0; // === METHOD M_SetError === /// ======================================================================= /// 名称 : M_SetError /// 功能 : 锁存构造错误诊断。 /// 说明 : 内部错误出口统一调用,避免 bError/eError/sDiagMsg 不一致。 /// ======================================================================= {attribute 'hide_all_locals'} METHOD PRIVATE M_SetError VAR_INPUT eNewError : E_HttpError := E_HttpError.iNoError; // 枚举状态或错误码。 sMessage : STRING(255) := ''; // 诊断或协议文本字段。 END_VAR // === IMPLEMENTATION === // 工程说明:本段集中处理状态、边界或诊断,避免跨周期残留。 bDone := FALSE; bError := TRUE; eError := eNewError; sDiagMsg := sMessage;

完整源码 2:F_HttpMethodToString.st

职责:方法枚举转文本

/// 功能 : HTTP 方法枚举转字符串。 /// 库依赖 : 暂无 {attribute 'hide_all_locals'} FUNCTION F_HttpMethodToString : STRING(16) VAR_INPUT eMethod : E_HttpMethod := E_HttpMethod.iUnknown; // 枚举状态或错误码。 END_VAR // === IMPLEMENTATION === /// ======================================================================= /// 名称 : F_HttpMethodToString /// 功能 : 将 E_HttpMethod 转为 HTTP start-line 方法文本。 /// 库依赖 : 暂无 /// ======================================================================= /// 使用说明 : 1. 未知方法返回 GET,保证出站请求可控。 /// : 2. 入站原始方法仍由 ST_HttpRequest.sMethod 保留。 /// ======================================================================= // 工程说明:本段集中处理状态、边界或诊断,避免跨周期残留。 // 边界说明:执行前后保持输出和错误码可被在线诊断追踪。 CASE eMethod OF E_HttpMethod.iHead: F_HttpMethodToString := 'HEAD'; E_HttpMethod.iPost: F_HttpMethodToString := 'POST'; E_HttpMethod.iPut: F_HttpMethodToString := 'PUT'; E_HttpMethod.iDelete: F_HttpMethodToString := 'DELETE'; E_HttpMethod.iPatch: F_HttpMethodToString := 'PATCH'; E_HttpMethod.iOptions: F_HttpMethodToString := 'OPTIONS'; ELSE F_HttpMethodToString := 'GET'; END_CASE

完整源码 3:F_HttpStatusReason.st

职责:状态码原因短语

/// 功能 : HTTP 状态码默认原因短语。 /// 库依赖 : 暂无 {attribute 'hide_all_locals'} FUNCTION F_HttpStatusReason : STRING(64) VAR_INPUT uiStatusCode : UINT := 200; // 计数、长度或状态数值。 END_VAR // === IMPLEMENTATION === /// ======================================================================= /// 名称 : F_HttpStatusReason /// 功能 : 提供首版常用 HTTP 状态码原因短语。 /// 库依赖 : 暂无 /// ======================================================================= /// 使用说明 : 1. Builder 在 sReason 为空时使用本函数填充。 /// : 2. 未覆盖状态码返回 OK 以外不会用于错误判定,仅影响可读文本。 /// ======================================================================= // 工程说明:本段集中处理状态、边界或诊断,避免跨周期残留。 // 边界说明:执行前后保持输出和错误码可被在线诊断追踪。 CASE uiStatusCode OF 200: F_HttpStatusReason := 'OK'; 201: F_HttpStatusReason := 'Created'; 204: F_HttpStatusReason := 'No Content'; 400: F_HttpStatusReason := 'Bad Request'; 404: F_HttpStatusReason := 'Not Found'; 405: F_HttpStatusReason := 'Method Not Allowed'; 411: F_HttpStatusReason := 'Length Required'; 413: F_HttpStatusReason := 'Payload Too Large'; 500: F_HttpStatusReason := 'Internal Server Error'; 501: F_HttpStatusReason := 'Not Implemented'; ELSE F_HttpStatusReason := 'OK'; END_CASE

本篇阅读抓手

  • Host 是请求构造硬门槛。
  • 出站统一显式 Content-Length。
  • 消息超限时拒绝发送。

如何验证这组源码

  1. 先针对FB_HttpMessageBuilder.st的公开输入和错误出口建立确定性用例。
  2. 再把“Host 是请求构造硬门槛”转成至少一个正常场景和一个失败场景。
  3. 本篇 3 个文件必须一起编译,避免只验证单个函数而漏掉数据结构或调用边界。
  4. 真机复核时重点观察“消息超限时拒绝发送”,并保留对应状态、计数和原始报文。

这一篇你最该记住

  • Host 是请求构造硬门槛。
  • 出站统一显式 Content-Length。
  • 消息超限时拒绝发送。

系列导航

  • 系列:CodeSys HTTP 系列教程,第 21/28 篇。
  • 当前源码加更:第 3/8 篇。
  • 本篇完整源码文件数:3。
  • 上一篇:第20篇
  • 下一篇:第22篇

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询