夜雨聆风学习资料网

ARTICLE · 1000936

第21篇_源码加更 03|Builder、方法转换和状态码文本

第21篇_源码加更 03|Builder、方法转换和状态码文本

适合谁收藏

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

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

先给结论

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

读图重点

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

先看文件职责

序号
文件
职责
行数
1
FB_HttpMessageBuilder.st
请求与响应构造器
244
2
F_HttpMethodToString.st
方法枚举转文本
35
3
F_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_HttpMessageBuilderVAR_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 : BOOLVAR_IN_OUT    stRequest : ST_HttpRequest;    // 结构化协议或测试数据。END_VARVAR_OUTPUT    sMessage : STRING(GVL_Http.cnMaxMessageSize);    // 诊断或协议文本字段。END_VARVAR    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_IFsMethod := F_HttpMethodToString(    eMethod := stRequest.eMethod    );IF LEN(stRequest.sTarget) = 0 THEN    sTarget := GVL_Http.cnDefaultPath;ELSE    sTarget := stRequest.sTarget;END_IFuiBodyLen := 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_IFIF LEN(stRequest.sAdditionalHeader) > 0 THEN    sMessage := CONCAT(sMessage, stRequest.sAdditionalHeader);    sMessage := CONCAT(sMessage, '$R$N');END_IFIF LEN(stRequest.sContentType) > 0 THEN    sMessage := CONCAT(sMessage, 'Content-Type: ');    sMessage := CONCAT(sMessage, stRequest.sContentType);    sMessage := CONCAT(sMessage, '$R$N');END_IFIF uiBodyLen > 0 THEN    sMessage := CONCAT(sMessage, 'Content-Length: ');    sMessage := CONCAT(sMessage, sContentLen);    sMessage := CONCAT(sMessage, '$R$N');END_IFsMessage := CONCAT(sMessage, '$R$N');IF uiBodyLen > 0 THEN    sMessage := CONCAT(sMessage, stRequest.sBody);END_IFuiCandidate := TO_UINT(LEN(sMessage));IF uiCandidate >= GVL_Http.cnMaxMessageSize THEN    M_SetError(        eNewError := E_HttpError.iBufferTooSmall,        sMessage  := 'request message exceeds buffer'        );    RETURN;END_IFudiMessageLength := 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 : BOOLVAR_IN_OUT    stResponse : ST_HttpResponse;    // 结构化协议或测试数据。END_VARVAR_OUTPUT    sMessage : STRING(GVL_Http.cnMaxMessageSize);    // 诊断或协议文本字段。END_VARVAR    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_IFIF LEN(stResponse.sReason) > 0 THEN    sReason := stResponse.sReason;ELSE    sReason := F_HttpStatusReason(        uiStatusCode := stResponse.uiStatusCode        );END_IFIF LEN(stResponse.sContentType) > 0 THEN    sContentType := stResponse.sContentType;ELSE    sContentType := GVL_Http.cnDefaultContentType;END_IFuiBodyLen := 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_IFsMessage := 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_IFsMessage := CONCAT(sMessage, '$R$N');IF uiBodyLen > 0 THEN    sMessage := CONCAT(sMessage, stResponse.sBody);END_IFuiCandidate := TO_UINT(LEN(sMessage));IF uiCandidate >= GVL_Http.cnMaxMessageSize THEN    M_SetError(        eNewError := E_HttpError.iBufferTooSmall,        sMessage  := 'response message exceeds buffer'        );    RETURN;END_IFudiMessageLength := 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_SetErrorVAR_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篇

相关学习资料