Hurl 文件格式基础:字符编码、注释与字符串转义完全指南
2026/9/13 2:38:24 网站建设 项目流程

Hurl 文件格式基础:字符编码、注释与字符串转义完全指南

【免费下载链接】hurlHurl, run and test HTTP requests with plain text.项目地址: https://gitcode.com/GitHub_Trending/hu/hurl

Hurl 是一个以纯文本文件驱动 HTTP 请求发送与测试的命令行工具,而.hurl文件正是这套工作流的"源代码"。本文围绕 docs/hurl-file.md 展开,系统讲解 Hurl 文件的字符编码约定、文件扩展名、注释语法,以及字符串中特殊字符的转义规则(包括\u{n}Unicode 转义与\#转义),并结合仓库源码(packages/hurl_core/src/parser/string.rs、packages/hurl_core/src/input.rs)与正式语法定义 docs/spec/grammar/hurl.grammar 讲解其底层解析机制。读完本文,你将能写出编码正确、注释清晰、转义无误、可直接被 Hurl 解析执行的 Hurl 文件。

字符编码:UTF-8 与 BOM 的处理策略

Hurl 文件应当使用UTF-8 编码,并且文件开头不应带字节序标记(Byte Order Mark,BOM)

这是 Hurl 官方文档的明确约定(见 docs/hurl-file.md 的 Character Encoding 一节),原因很直观:

  • Hurl 文件的请求头、请求体、断言等内容都可能是多语言文本,UTF-8 是唯一被官方约定支持的编码;
  • 文件开头的 BOM(EF BB BF三个字节)不属于任何语法元素,虽然 Hurl 不会把它当作错误,但为了与其他工具链(如hurlfmt格式化、CI 集成)协同工作,推荐去掉。

源码层的 BOM 处理

从实现层面看,Hurl 在读取输入文件时会主动剥离 BOM,而不是报错。这一点可以在 packages/hurl_core/src/input.rs 中找到证据:

fn string_from_utf8(buffer: Vec<u8>) -> Result<String, io::Error> { let mut buffer = buffer; strip_bom(&mut buffer); String::from_utf8(buffer).map_err(|e| io::Error::new(ErrorKind::InvalidData, e)) } /// Remove BOM from the input bytes fn strip_bom(bytes: &mut Vec<u8>) { if bytes.starts_with(&[0xefu8, 0xbb, 0xbf]) { bytes.drain(0..3); } }

也就是说,EF BB BF会被静默移除,随后剩余的字节流再交给String::from_utf8解码。文件一旦包含非法 UTF-8 序列(而非 BOM),则会被视为无效输入并报错,例如string_from_utf8(vec![0xef])会返回 "incomplete utf-8 byte sequence from index 0"(见同文件的单元测试)。

仓库的集成测试 integration/hurl/tests_ok_not_linted/bom.hurl 专门验证了这一行为:该测试文件以 BOM 开头,仍能正常运行,并通过bytes startsWith hex,efbbbf;断言确认把文件内容 POST 到/mirror后得到的原始字节确实带有 BOM——说明 Hurl 在解析时容忍了 BOM 的存在(相关断言与 HTML 渲染示例见 integration/hurl/tests_ok_not_linted/bom.html)。

实操建议:如果你的 Hurl 文件由 Windows 记事本或某些 IDE 以"UTF-8 with BOM"保存,Hurl 依然可以解析;但为了可移植性与整洁性,建议用hurlfmt或编辑器"另存为无 BOM 的 UTF-8"。

文件扩展名:.hurl

Hurl 文件的扩展名为.hurl。Hurl 通过命令行参数接收该文件作为输入,例如:

hurl my_test.hurl

同时.hurl扩展名也被各类编辑器插件与 CI 配置用于语法高亮识别:

  • Vim 的语法与文件类型检测配置见 contrib/vim/syntax/hurl.vim 与 contrib/vim/ftdetect/hurl.vim;
  • Sublime Text 语法定义见 contrib/sublime-text/Hurl.sublime-syntax;
  • Emacs 模式见 contrib/emacs/hurl-mode.el。

仓库内的文档、测试与示例均以.hurl命名(例如集成测试目录 integration/hurl/tests_ok 下的大量用例)。

注释:以#开头,直到行尾

注释以#开头,持续到该行结束。因为 Hurl 文件本身就是一份"HTTP 工作流文档",所以官方强烈建议多写注释、把文件写得更具描述性。

# A very simple Hurl file # with tasty comments... GET https://www.sample.net x-app: MY_APP # Add a dummy header HTTP 302 # Check that we have a redirection [Asserts] header "Location" exists header "Location" contains "login" # Check that we are redirected to the login page

这个示例同时展示了注释的三种典型位置:

  1. 整行注释:文件开头的# A very simple Hurl file
  2. 请求行/响应行后的行尾注释x-app: MY_APP # Add a dummy headerHTTP 302 # Check that we have a redirection
  3. 断言行后的行尾注释header "Location" contains "login" # ...

语法层面的注释定义

在 Hurl 的正式语法 docs/spec/grammar/hurl.grammar 中,注释与行终止被统一定义为:

lt: sp* comment? [\n]? comment: "#" ~[\n]*

也就是说,lt(line terminator)允许在行首有若干空格、可选一个注释、再跟一个可选的换行符。注释由#加上任意非换行字符组成。这就是为什么注释可以出现在几乎所有"一行结束"的位置:请求行、响应行、头部、断言行等,因为解析器在每个lt处都会尝试消费注释。

从解析器实现上看,packages/hurl_core/src/parser/string.rs 的unquoted_template在解析未加引号的字符串(如 URL 或请求头值)时,遇到#会立即停止并结束当前 token,把余下部分留给注释处理——这正是"#之后是注释"这一规则在代码层的体现。

字符串中的特殊字符与转义

Hurl 字符串支持两类特殊字符表示:

  1. 标准的转义序列

    • \"双引号
    • \\反斜杠
    • \b退格(backspace)
    • \f换页(form feed)
    • \n换行(line feed)
    • \r回车(carriage return)
    • \t水平制表符(horizontal tab)
  2. 任意 Unicode 标量值:写成\u{n},其中n1 到 8 位的十六进制数字

官方文档给出了一个等价断言的示例:

GET https://example.org/api HTTP 200 # The following assert are equivalent: [Asserts] jsonpath "$.slideshow.title" == "A beautiful ✈!" jsonpath "$.slideshow.title" == "A beautiful \u{2708}!"

这里的(U+2708,飞机符号)直接用字面量书写与用\u{2708}转义书写完全等价。

源码中的转义实现

packages/hurl_core/src/parser/string.rs 的escape_charunicode函数完整实现了上述规则:

pub fn escape_char(reader: &mut Reader) -> ParseResult<char> { try_literal("\\", reader)?; let start = reader.cursor(); match reader.read() { Some('#') => Ok('#'), Some('"') => Ok('"'), Some('`') => Ok('`'), Some('\\') => Ok('\\'), Some('/') => Ok('/'), Some('b') => Ok('\x08'), Some('n') => Ok('\n'), Some('f') => Ok('\x0c'), Some('r') => Ok('\r'), Some('t') => Ok('\t'), Some('u') => unicode(reader), _ => Err(ParseError::new(start.pos, false, ParseErrorKind::EscapeChar)), } } pub(crate) fn unicode(reader: &mut Reader) -> ParseResult<char> { literal("{", reader)?; let v = hex_value(reader)?; let c = match std::char::from_u32(v) { None => return Err(ParseError::new(reader.cursor().pos, false, ParseErrorKind::Unicode)), Some(c) => c, }; literal("}", reader)?; Ok(c) }

几点值得注意的实现细节:

  • unicode解析{后的十六进制数字并调用std::char::from_u32,这保证了\u{n}n必须是合法的 Unicode 标量值,超出范围的数值会直接报ParseErrorKind::Unicode错误;
  • hex_value允许一个或多个十六进制数字,对应语法中unicode-char: "{" hexdigit+ "}"的定义(见 docs/spec/grammar/hurl.grammar 的 Strings 一节);
  • 带引号字符串(quoted-string)里,转义允许\"\\\b\f\n\r\t\u{...};而键名(key-string)与未加引号的值(value-string)则额外允许\#(以及键名中的\:),具体规则见语法中quoted-string-escaped-charkey-string-escaped-charvalue-string-escaped-char三条产生式。

单元测试也验证了这些行为,例如escape_char的测试断言\n转义为换行、\u{0a}\u{E9}分别转义为换行与é,见 packages/hurl_core/src/parser/string.rs。

转义#:让#不再是注释

由于#在行内是注释的起始符,当你的真实数据(例如请求头值)中需要包含字面意义上的#字符时,必须用\#将其转义,以与注释区分。官方文档给出的例子:

GET https://example.org/api x-token: BEEF \#STEAK # Some comment HTTP 200

这里发送的请求头是x-token: BEEF #STEAK——前一个\#是转义后的字面#,而后一个#则正常开启注释Some comment

转义#的实现与适用范围

在语法层面,docs/spec/grammar/hurl.grammar 中:

value-string-escaped-char: "\\" ("#" | "\\" | "\b" | "\f" | "\n" | "\r" | "\t" | "\u" unicode-char) key-string-escaped-char: "\\" ("#" | ":" | "\\" | "\b" | "\f" | "\n" | "\r" | "\t" | "\u" unicode-char)

可以看到:

  • 未加引号的值(如请求头值、URL、表单值)中允许\#转义;
  • 键名中同样允许\#,并且额外允许\:转义冒号(用于在键名中包含字面:);
  • 对应的解析器实现分别在 packages/hurl_core/src/parser/string.rs 的escape_charSome('#') => Ok('#'))与 packages/hurl_core/src/parser/key_string.rs 的key_string_escaped_char(支持\#\:等)中。

单元测试test_unquoted_template_with_encoded_hash(packages/hurl_core/src/parser/string.rs)直接验证了\u{23}会被解析为字面#,而test_unquoted_template_with_hash则确认裸#会终止字符串解析。

注意一个边界:在带引号的字符串(quoted-string,如断言中的"...")内部,#本来就不具备注释语义,因此无需转义;\#转义主要针对未加引号的场景(请求行、请求头、键值等)。这一点从语法中quoted-string-escaped-char不包含#分支、而value-string-escaped-char包含#分支可以看出。

小结:一份"规范"的 Hurl 文件长什么样

综合以上约定,一个规范、可读、无歧义的 Hurl 文件应该满足:

规则要求违反后果
字符编码UTF-8,无 BOM(有 BOM 会被容忍并剥离)非法 UTF-8 序列将报解析错误
文件扩展名.hurl不影响解析,但影响编辑器高亮与 CI 识别
注释#开始,到行尾结束;可在任意行尾追加无(属于书写规范)
字面#在未加引号的键/值中使用\#转义#之后的内容被当作注释而丢失
字面:在键名中使用\:转义键名在冒号处被截断
特殊字符\"\\\b\f\n\r\t非法转义序列(如\l)报EscapeChar错误
Unicode使用\u{n}(1–8 位十六进制)非法标量值报Unicode错误

把 Hurl 文件当作"可执行的、带注释的 HTTP 文档"来写,是充分发挥 Hurl 价值的第一步。相关的整体文件结构介绍可以继续阅读 docs/hurl-file.md 的姊妹篇 docs/grammar.md,或通过仓库中 integration/hurl/tests_ok 下的大量真实用例进一步验证各类写法。

【免费下载链接】hurlHurl, run and test HTTP requests with plain text.项目地址: https://gitcode.com/GitHub_Trending/hu/hurl

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

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

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

立即咨询