Hurl 请求语法详解:从方法、URL、请求头到 Body 的完整编写指南
【免费下载链接】hurlHurl, run and test HTTP requests with plain text.项目地址: https://gitcode.com/GitHub_Trending/hu/hurl
本文基于 Hurl 官方文档 request,系统讲解 Hurl 文件中 HTTP 请求的完整定义方式:方法与 URL、请求头、[Options]、[Query]、[Form]、[Multipart]、[Cookies]、[BasicAuth]各配置段,以及 JSON、XML、GraphQL、多行字符串、Base64、Hex、文件等多种 Body 类型。读完后,你可以结合仓库中 Request AST 定义 与 分段解析器 的源码证据,既会写 Hurl 请求,也理解每一段语法在解析层是如何落地的。
1. 请求的整体解剖
在 Hurl 中,一个请求(Request)描述一个 HTTP 请求:由必填的方法和 URL 开始,随后是可选的请求头;之后可以用[Options]、[Query]、[Form]、[Multipart]、[Cookies]、[BasicAuth]等配置段来进一步定制请求;最后是一个可选的 Body,且 Body 必须是请求配置的最后一部分。
一个典型示例:
GET https://example.org/api/dogs?id=4567 User-Agent: My User Agent Content-Type: application/json [BasicAuth] alice: secret请求的结构可以概括为三部分,从源码结构看,这与 Hurl 核心解析后的RequestAST 节点一一对应:
| 组成部分 | 是否必填 | 说明 |
|---|---|---|
| 方法 + URL | 必填 | 如PUT https://sample.net |
| 请求头 | 可选 | 紧跟在方法/URL 之后,无段落标记 |
| 配置段 | 可选,且无序 | [Options]、[Query]、[Form]、[BasicAuth]、[Cookies]等可任意混排 |
| Body | 可选 | 必须是请求的最后一部分,同样无显式标记 |
关键规则有两条:
- 请求头直接跟在方法与 URL 之后,没有段名。这一设计让 Hurl 文件"看起来"和真实 HTTP 报文一致;而 Query、Form、Cookies 等其他参数则通过命名段定义。
- 配置段之间没有顺序要求,可以任意混排,两种写法等价:
GET https://example.org/api/dogs User-Agent: My User Agent [Query] id: 4567 order: newest [BasicAuth] alice: secretGET https://example.org/api/dogs User-Agent: My User Agent [BasicAuth] alice: secret [Query] id: 4567 order: newestBody 则像请求头一样没有显式标记,靠"位置"识别——它必须出现在所有请求头与配置段之后:
POST https://example.org/api/dogs?id=4567 User-Agent: My User Agent { "name": "Ralphy" }在 核心 AST 中,Request结构体的字段顺序正是这一语法的直接体现:method、url、headers、sections(各配置段)、body。其中url的类型是Template,说明 URL 本身支持{{variable}}模板变量。而"配置段无序"这一语法特性在实现上体现为:SectionValue 枚举按段类型(QueryParams、FormParams、MultipartFormData、Cookies、BasicAuth、Options等)区分内容,运行时通过 Request 的访问器方法(querystring_params()、form_params()、multipart_form_data()、cookies()、basic_auth()、options())按类型查找,而不是按位置查找——这正是配置段可以任意混排的底层原因。
2. 方法与 URL
2.1 方法
HTTP 请求方法是必填项,通常是GET、HEAD、POST、PUT、DELETE、CONNECT、OPTIONS、TRACE、PATCH之一。
也可以使用其他自定义方法(如
QUERY),但约束是只能使用大写字母。
2.2 URL
URL 是必填项,且可以包含 query 参数,尽管更推荐用[Query]段来组织。两种写法等价:
# URL 中直接带 query 参数的请求 GET https://forum/questions/?search=Install%20Linux&order=newest # 使用 query 参数段的等价请求 GET https://example.org/forum/questions/ [Query] search: Install Linux order: newest
[Query]段中的参数值不做 URL 编码(如Install Linux中的空格)。
当 URL 与[Query]段同时存在 query 参数时,最终发出的请求会同时携带两组参数,而不是互相覆盖。
3. 请求头(Headers)
请求头是可选的 HTTP 请求头列表。每个请求头由名称、一个:和值组成:
GET https://example.org/news User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:70.0) Gecko/20100101 Firefox/70.0 Accept: */* Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate, br Connection: keep-alive请求头直接跟在 URL 之后,没有段名,这一点与 query 参数、form 参数或 cookies 不同。
一个容易踩坑的细节:请求头值不以双引号起始。如果值以双引号开头,这些引号会成为值的一部分:
PATCH https://example.org/file.txt If-Match: "e0023aa4e"此处If-Match请求头发送的值是"e0023aa4e"(首尾均含双引号)——这正是 ETag 场景下的正确用法。
4. [Options] 段:单请求级选项
[Options]段用于只对当前请求应用选项。像--location(见 manual)、--verbose(见 manual)、--insecure(见 manual)等选项本来可以在命令行传递并作用于 Hurl 文件的所有请求,而[Options]段让某个请求单独开启选项,不影响其他请求。文档给出的完整选项清单如下(每项均为可选):
GET https://example.org # 一个 Options 段,每个选项都可选,且只作用于该请求…… [Options] aws-sigv4: aws:amz:sts # 生成 AWS SigV4 Authorization 头 cacert: /etc/cert.pem # 自定义证书文件 cert: /etc/client-cert.pem # 客户端认证证书 key: /etc/client-cert.key # 客户端认证证书密钥 compressed: true # 请求压缩响应 connect-timeout: 20s # 连接超时 delay: 3s # 该请求的延迟(即 sleep) fail-with-body: true # 即使存在断言错误也输出 HTTP 响应 http3: true # 使用 HTTP/3 协议版本 insecure: true # 允许不安全的 SSL 连接与传输 ipv6: true # 使用 IPv6 地址 limit-rate: 32000 # 限制该请求速度(字节/秒) location: true # 跟随该请求的重定向 max-redirs: 10 # 最大重定向次数 max-time: 30s # 请求/响应的最大耗时 no-header: Accept # 从请求中移除的响应头名称 no-jsonpath-coercion: true # 禁用该请求的 JSONPath 结果类型强制转换 output: out.html # 将响应转储到该文件 path-as-is: true # 不处理 URL 路径中的 /../ 或 /./ 序列 retry: 10 # HTTP/断言错误时的重试次数 retry-interval: 500ms # 重试间隔 skip: false # 跳过该请求 unix-socket: sock # 使用 Unix socket 传输 user: bob:secret # 使用 basic 认证 proxy: my.proxy:8012 # 定义代理(host:port,host 可以是 IP 地址) variable: country=Italy # 定义变量 country variable: planet=Earth # 定义变量 planet variables-file: vars.env # 从 properties 文件定义变量 verbose: true # 允许详细输出 very-verbose: true # 允许更详细的输出在
[Options]段中定义的变量(包括从variables-file加载的值)对后续 entry 也生效。这是一个例外:所有其他选项都只作用于当前请求。
这一例外规则在选项枚举实现中也有对应:OptionKind 中VariablesFile、Variable等变体携带Template值,与其余仅带布尔/数值参数的选项区分开。
5. [Query] 段:query 参数
可选的 query 参数列表。每个参数由字段名、:和值组成,段以[Query]开头。与 URL 中的 query 参数不同,[Query]段中的每个值不做 URL 编码:
GET https://example.org/news User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:70.0) Gecko/20100101 Firefox/70.0 [Query] order: newest search: {{custom-search}} count: 100参数值可以是{{变量}}模板(见 模板机制)。如果 URL 中已存在参数,最终请求会同时携带两组参数(合并,不覆盖)。
6. [Form] 段:表单参数
[Form]段用于发送类似 HTML 表单的数据。段内是可选的键值列表,每个键后跟:和值;键值对会编码为以&分隔、键值间用=连接的元组,发送在请求 body 中。请求的 Content-Type 为application/x-www-form-urlencoded:
POST https://example.org/contact [Form] default: false token: {{token}} email: john.doe@rookie.org number: 33611223344[Form]段可以看作对 Body 段的语法糖(form 段中的值不做 URL 编码)。也可以用单行字符串 body达到同样效果:
# 使用 form 参数段的 POST 请求: POST https://example.org/test [Form] name: John Doe key1: value1 # 等价的 POST 请求(使用 body 段): POST https://example.org/test Content-Type: application/x-www-form-urlencoded `name=John%20Doe&key1=value1`注意冲突规则:当 body 段与 form 参数段同时存在时,只有 body 段会被采用(参见 Request::form_params 的取法与 Body 枚举 的优先级)。仓库中 form_params 集成测试 与 post 集成测试 覆盖了这一编码行为。
7. [Multipart] 段:多部分表单数据
[Multipart]段用于发送键/值与文件内容(即multipart/form-data格式)。段以[Multipart]开头:
POST https://example.org/upload [Multipart] field1: value1 field2: file,example.txt; # 也可以显式指定文件内容类型: field3: file,example.zip; application/zip文件相对输入 Hurl 文件所在目录解析,且不能包含隐式父目录(..)。可以使用--file-root选项(见 manual)指定所有文件节点的根目录。
内容类型可以显式指定,也可以根据文件扩展名推断:
| 扩展名 | 推断的 Content-Type |
|---|---|
.gif | image/gif |
.jpg | image/jpeg |
.jpeg | image/jpeg |
.png | image/png |
.svg | image/svg+xml |
.txt | text/plain |
.htm | text/html |
.html | text/html |
.pdf | application/pdf |
.xml | application/xml |
默认内容类型为application/octet-stream。
作为[Multipart]段的替代方案,也可以用[多行字符串 body]手工构造 multipart 报文:
POST https://example.org/upload Content-Type: multipart/form-data; boundary="boundary" ``` --boundary Content-Disposition: form-data; name="key1" value1 --boundary Content-Disposition: form-data; name="upload1"; filename="data.txt" Content-Type: text/plain Hello World! --boundary Content-Disposition: form-data; name="upload2"; filename="data.html" Content-Type: text/html <div>Hello <b>World</b>!</div> --boundary-- ```使用多行字符串 body 发送 multipart 表单时,文件内容必须内联写在 Hurl 文件中。
在 AST 层,multipart 参数由 MultipartParam 表达,每个参数要么是普通键值,要么是文件引用。
8. [Cookies] 段:会话 Cookie
可选的本请求 Cookie 列表。每个 Cookie 由名称、:和值组成,段以[Cookies]开头。Cookie 按请求发送,不会加入 Cookie 存储会话;而响应头中设置的 Cookie(如Set-Cookie: theme=light)则会写入存储会话。
GET https://example.org/index.html [Cookies] theme: light sessionToken: abc123[Cookies]段可以看作对应请求头的语法糖:
# 使用 cookies 段的 GET 请求: GET https://example.org/index.html [Cookies] theme: light sessionToken: abc123 # 等价的 GET 请求(使用请求头): GET https://example.org/index.html Cookie: theme=light; sessionToken=abc1239. [BasicAuth] 段:基本认证
[BasicAuth]段用于执行 basic 认证。用户名后跟:和密码,段以[BasicAuth]开头。用户名和密码不做 base64 编码(Hurl 内部完成编码):
# 使用登录 bob 和密码 secret 进行基本认证 GET https://example.org/protected [BasicAuth] bob: secret用户名和密码两侧的空白会被 trim。如果你确实想在密码中使用空格(!!),可以使用 Hurl Unicode 字面量 \u{20}。
它与手工构造Authorization请求头等价(后者更繁琐,需要自己算 base64):
# Authorization 头的值可用 `echo -n 'bob:secret' | base64` 计算 GET https://example.org/protected Authorization: Basic Ym9iOnNlY3JldA==[BasicAuth]提供的是逐请求认证。如果希望为 Hurl 文件中所有请求添加基本认证,使用命令行的-u/--user选项(见 manual)。仓库中 basic_authentication 集成测试 覆盖了该认证方式及其错误场景。
10. Body 段:请求体
Body 是可选的 HTTP 请求体,且必须是请求配置的最后一部分。Hurl 提供多种 Body 形态,按数据特征选择:
- 请求体是 JSON 或 XML 字符串时,可以直接原样写入,无需任何包装;
- 其他文本类型使用[多行字符串 body](以
```开始和结束); - 需要精确控制字节时使用 Base64、Hex 或文件引用。
即使是
GET也可以设置 body,尽管这并非常见实践。
10.1 JSON body
JSON 请求体用于将字面量 JSON 作为请求体,此时application/json内容类型会被自动设置:
# 用 JSON body 创建一个新的狗条目 POST https://example.org/api/dogs { "id": 0, "name": "Frieda", "picture": "images/scottish-terrier.jpeg", "age": 3, "breed": "Scottish Terrier", "location": "Lisco, Alabama" }JSON body 支持用变量模板化:
# 用 JSON body 创建一个新的猫条目 POST https://example.org/api/cats { "id": 42, "lives": {{ lives_count }}, "name": "{{ name }}" }从实现看,JSON body 是多行字符串 body 的简写形式,等价于带json标识符的写法:
POST https://example.org/api/dogs ```json { "id": 0, "name": "Frieda", "picture": "images/scottish-terrier.jpeg", "age": 3, "breed": "Scottish Terrier", "location": "Lisco, Alabama" } ```如果不希望模板在 JSON body 中被求值,可以使用带raw标识符的多行字符串 body:
# {{name}} 不是变量 POST https://example.org/api/cats Content-Type: application/json ```raw { "id": 42, "name": "{{ name }}" } ```10.2 XML body
XML 请求体用于将字面量 XML 作为请求体。例如发送 SOAP 报文:
# 用 XML body 发送 SOAP 请求 POST https://example.org/InStock Content-Type: application/soap+xml; charset=utf-8 Content-Length: 299 SOAPAction: "http://www.w3.org/2003/05/soap-envelope" <?xml version="1.0" encoding="UTF-8"?> <soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:m="http://example.net"> <soap:Header></soap:Header> <soap:Body> <m:GetStockPrice> <m:StockName>GOOG</m:StockName> </m:GetStockPrice> </soap:Body> </soap:Envelope>XML body 等价于带xml标识符的多行字符串 body:
POST https://example.org/InStock Content-Type: application/soap+xml; charset=utf-8 Content-Length: 299 SOAPAction: "http://www.w3.org/2003/05/soap-envelope" ```xml <?xml version="1.0" encoding="UTF-8"?> <soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:m="http://example.net"> <soap:Header></soap:Header> <soap:Body> <m:GetStockPrice> <m:StockName>GOOG</m:StockName> </m:GetStockPrice> </soap:Body> </soap:Envelope> ```与 JSON body 不同,XML body 的简写语法不能使用变量。如果需要在 XML body 中使用变量,请使用带变量的普通多行字符串 body。
10.3 GraphQL query
GraphQL 查询使用带graphql标识符的多行字符串 body:
POST https://example.org/starwars/graphql ```graphql { human(id: "1000") { name height(unit: FOOT) } } ```GraphQL 查询 body 可以使用 GraphQL 变量(variables块):
POST https://example.org/starwars/graphql ```graphql query Hero($episode: Episode, $withFriends: Boolean!) { hero(episode: $episode) { name friends @include(if: $withFriends) { name } } } variables { "episode": "JEDI", "withFriends": false } ```GraphQL 查询与所有多行字符串 body 一样,还可以使用 Hurl 变量:
POST https://example.org/starwars/graphql ```graphql { human(id: "{{human_id}}") { name height(unit: FOOT) } } ```Hurl 变量与 GraphQL 变量可以在同一个 body 中混用。
在 AST 实现 中,GraphQl结构体同时持有value(查询模板)与variables(可选的变量块),印证了这两类变量并存的设计。
10.4 多行字符串 body
对于既不是 JSON 也不是 XML 的文本 body,使用多行字符串,以```开始和结束:
POST https://example.org/models ``` Year,Make,Model,Description,Price 1997,Ford,E350,"ac, abs, moon",3000.00 1999,Chevy,"Venture ""Extended Edition""","",4900.00 1999,Chevy,"Venture ""Extended Edition, Very Large""",,5000.00 1996,Jeep,Grand Cherokee,"MUST SELL! air, moon roof, loaded",4799.00 ```多行字符串的标准用法:
``` line1 line2 line3 ```其求值结果是"line1\nline2\nline3\n"(注意末尾换行)。
多行字符串 body 支持用变量模板化:
POST https://example.org/models [Options] variable: var1=lemon variable: var2=yellow ``` Fruit,Color {{var1}},{{var2}} ```注意:转义不会被处理(即不支持 Hurl Unicode 字面量)——\n是连续两个字符(\后跟n),不是单个换行符。
多行字符串 body 可以使用语言标识符(如json、xml、graphql或raw)。不同的标识符会额外发送对应的Content-Type请求头,且实际发出的字节可能与原始多行文本不同(例如 JSON 会被规范化):
POST https://example.org/api/dogs ```json { "id": 0, "name": "Frieda" } ```raw标识符的多行字符串 body 不评估模板:
# {{name}} 不是变量 POST https://example.org/api/cats Content-Type: application/json ```raw { "id": 42, "lives": {{ lives_count }}, "name": "{{ name }}" } ```从源码结构看,这五种形态对应 MultilineStringKind 枚举的Text、Json、Xml、Raw、GraphQl变体,lang() 方法 给出标识符到名称的映射(无标识符、json、xml、raw、graphql),与上文语法一一对应。
10.5 单行字符串 body
对于不含换行符的文本 body,可以使用单行字符串,以反引号`开始和结束:
POST https://example.org/helloworld `Hello world!`10.6 Base64 body
Base64 body 用于将二进制数据设置为请求体。Base64 body 以base64,开头、以;结尾。支持 MIME Base64 编码(换行与空白可以出现在任何位置,解码时忽略),=填充字符可以补齐:
POST https://example.org # body 前的注释 base64,TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIG FkaXBpc2NpbmcgZWxpdC4gSW4gbWFsZXN1YWRhLCBuaXNsIHZlbCBkaWN0dW0g aGVuZHJlcml0LCBlc3QganVzdG8gYmliZW5kdW0gbWV0dXMsIG5lYyBydXR0dW 0gdG9ydG9yIG1hc3NhIGlkIG1ldHVzLiA=;10.7 Hex body
Hex body 用于将二进制数据设置为请求体。Hex body 以hex,开头、以;结尾:
PUT https://example.org # 发送一个 UTF-8 编码的 café hex,636166c3a90a;10.8 文件 body
要把本地文件的二进制内容作为请求体,可以使用文件 body。文件 body 以file,开头、以;结尾:
POST https://example.org # body 前的注释 file,data.bin;文件相对输入 Hurl 文件所在目录解析,且不能包含隐式父目录(..)。可以使用--file-root选项(见 manual)指定所有文件节点的根目录。
从源码结构看,这几类字节级 body 在 AST 中统一收敛于 Bytes 枚举(Json、File、Hex等变体),并经由 visit 访问器 统一遍历——hurlfmt格式化器与 runner 都基于这套 AST 工作,因此你写的每一行 Hurl 语法都有明确的解析与再序列化实现。
11. 实战速查:组合使用各部分
把上面的部件组合起来,一个"满配"请求的完整形态如下,可作为编写自己的 Hurl 文件时的检查清单:
POST https://example.org/api/dogs User-Agent: My User Agent # 1) 请求头:紧跟方法与 URL [Options] # 2) 配置段:无序、可任意混排 retry: 3 retry-interval: 500ms variable: dog=Frieda [Query] # query 参数(不 URL 编码) page: 1 [BasicAuth] # basic 认证(明文用户名:密码) alice: secret [Cookies] # 逐请求 Cookie(不写入 Cookie 存储) theme: light { # 3) Body:必须是最后一部分 "name": "{{ dog }}" }编写时的四条硬性约束(均有 request 文档与 Request 解析 实现依据):
- 方法与 URL 必填,方法只能是大写字母;
- 请求头必须紧跟 URL 之后,且没有段名标记;
- 各配置段(
[Options]、[Query]、[Form]、[Multipart]、[Cookies]、[BasicAuth])无序,可任意混排,但[Options]中定义的variable/variables-file例外地会延续到后续 entry; - Body 必须是请求的最后一部分;当 body 段与
[Form]段同时存在时,只有 body 段生效。
更多语法细节可参考仓库中的 Hurl 语法文件、Hurl 文件规范与 manual,以及 integration/hurl/tests_ok/ 下按功能组织的真实请求示例(如 multipart、post、jsonpath),它们展示了上述每种请求写法在真实运行中的形态。
【免费下载链接】hurlHurl, run and test HTTP requests with plain text.项目地址: https://gitcode.com/GitHub_Trending/hu/hurl
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考