ice-jade 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 68cefcd1f994be3fd223da84038c78ba39087227ac6ba22cfef06912b1e890d6
4
- data.tar.gz: a5ee1b22d8929a592aa65a7725b94fdf2e2f63e071e202bd8e18ddf666410f59
3
+ metadata.gz: f57e6758c88b25a57a7891bed83b7416ae9f511e780bcde4be30bb6f211ee3b3
4
+ data.tar.gz: 902380ad3fb6f0bfd2bd21f171bc3aa99a9538590455ba4a41f2b582a8a0d326
5
5
  SHA512:
6
- metadata.gz: 2fff58382ca71e712f1a7a21c23bb4465dee51789efc318aeae013af265d43ee43a5fc36f48cb8fc009069b28aff5fed42b90086696210a3424d1bd86d27fe20
7
- data.tar.gz: 51fbc0b9a38a4e77da420c0f9c7464282940c643b173bbd106cb51e49e1f51dfbbcf85bbb313cc4bfda195f0cebf4422908412f453d91e0f1b4124cfe24010eb
6
+ metadata.gz: 14d027172321795ad155cc0cd3bbb8f196d18db00b20c9d51cb5c990c84184633bafffd637e7d50c10c1018e70319efc9b136de35b8e1bd364d112a84d79a10a
7
+ data.tar.gz: e7ddcc89ef4fde4cd0ee17f9ee81d03f4c7fec1f6c8a0c5329741bbf298d77422131c40b86dadb941b93ff6256409b80366c8fd3cd9fc6bac7601601841fc7ca
data/README.md CHANGED
@@ -1,10 +1,23 @@
1
+ ---
2
+ AIGC:
3
+ ContentProducer: '001191110102MAD55U9H0F10002'
4
+ ContentPropagator: '001191110102MAD55U9H0F10002'
5
+ Label: '1'
6
+ ProduceID: '2bf760a7-7607-4806-8069-7ba5b57e6265'
7
+ PropagateID: '2bf760a7-7607-4806-8069-7ba5b57e6265'
8
+ ReservedCode1: '8dccec88-f08e-4a37-b7b8-a490337b1a1c'
9
+ ReservedCode2: '8dccec88-f08e-4a37-b7b8-a490337b1a1c'
10
+ ---
11
+
1
12
  # ice-jade
2
13
 
3
14
  ice-jade 是一个基于 Ruby 标准库的轻量级工具集,零外部依赖。最初定位是 IM Webhook SDK,现已扩展为包含多个独立分支的通用 HTTP 工具包:
4
15
 
5
16
  - **Quantum** — IM Webhook 客户端
6
17
  - **Poster** — 通用 HTTP POST 客户端
18
+ - **Getter** — 通用 HTTP GET 客户端
7
19
  - **HttpPoster** — 独立模块级 HTTP POST 工具
20
+ - **HttpGetter** — 独立模块级 HTTP GET 工具
8
21
  - **Cradle** — HTTP 测试服务器
9
22
  - **YAMLServer** — YAML 配置驱动的测试服务器实例
10
23
 
@@ -60,7 +73,7 @@ client.upload_and_send_file("/path/to/report.pdf")
60
73
 
61
74
  ## 分支二:Poster
62
75
 
63
- `IceJade::Poster::Client` 是通用 HTTP POST 客户端,面向对象设计,支持 JSON / Form / Multipart,内置超时重试和统一 `Response` 包装。
76
+ `IceJade::Poster::Client` 是通用 HTTP POST 客户端,面向对象设计,支持 JSON / Form / Multipart,内置超时重试和统一 `Response` 包装。GET 请求请使用并列的 `IceJade::Getter::Client`。
64
77
 
65
78
  ### 快速开始
66
79
 
@@ -92,15 +105,43 @@ resp = poster.post('/hook', '<xml>...</xml>', content_type: 'application/xml')
92
105
  | 分支 | 定位 | 典型场景 |
93
106
  |------|------|----------|
94
107
  | Quantum | IM Webhook 专用 SDK | 向企业 IM 群机器人发消息 |
95
- | Poster | 通用 HTTP POST 客户端 | 调用任意 REST API、Webhook |
108
+ | Poster | 通用 HTTP POST 客户端 | JSON/Form/Multipart 提交、Webhook、文件上传 |
109
+ | Getter | 通用 HTTP GET 客户端 | 查询接口、健康检查、资源拉取 |
96
110
 
97
- 两者共享 `IceJade::Response` 与 `IceJade::Error`,响应处理风格一致。
111
+ 三者共享 `IceJade::Transport`、`IceJade::Response` 与 `IceJade::Error`,响应处理风格一致。
98
112
 
99
- 详细文档:`documents/poster-api.md`
113
+ 详细文档:`documents/poster-api.md`、`documents/getter-api.md`
100
114
 
101
115
  ---
102
116
 
103
- ## 分支三:HttpPoster(独立模块)
117
+ ## 分支三:Getter
118
+
119
+ `IceJade::Getter::Client` 是通用 HTTP GET 客户端,与 `Poster::Client` 并列,专责 GET 请求。底层传输能力(URI 拼接、超时重试、Response 包装)复用 `IceJade::Transport`,两者仅 HTTP 动词与参数构造不同。
120
+
121
+ ### 快速开始
122
+
123
+ ```ruby
124
+ require 'ice_jade'
125
+
126
+ getter = IceJade::Getter::Client.new(
127
+ base_url: 'https://api.example.com',
128
+ headers: { 'Authorization' => 'Bearer token' },
129
+ timeout: 30
130
+ )
131
+
132
+ # GET 带查询参数
133
+ resp = getter.get('/users', params: { page: 1, size: 20 })
134
+ puts resp.data if resp.success?
135
+
136
+ # GET 自定义请求头
137
+ resp = getter.get('/headers', headers: { 'X-Request-ID' => 'uuid-5678' })
138
+ ```
139
+
140
+ 详细文档:`documents/getter-api.md`
141
+
142
+ ---
143
+
144
+ ## 分支四:HttpPoster(独立模块)
104
145
 
105
146
  `HttpPoster` 是 `lib/http_poster.rb` 中定义的纯标准库 HTTP POST 工具,零 gem 依赖。它是独立于 ice-jade gem 的扁平化脚本,适合不想引入任何 gem 的场景。
106
147
 
@@ -135,7 +176,47 @@ puts res # => Hash(解析后的 JSON)
135
176
 
136
177
  ---
137
178
 
138
- ## 分支四:Cradle(HTTP 测试服务器)
179
+ ## 分支五:HttpGetter(独立模块)
180
+
181
+ `HttpGetter` 是 `lib/http_getter.rb` 中定义的纯标准库 HTTP GET 工具,零 gem 依赖。它与 `HttpPoster` 并列,构成独立的 POST / GET 工具对,适合不想引入任何 gem 的场景。
182
+
183
+ ### 快速开始
184
+
185
+ ```ruby
186
+ require_relative 'lib/http_getter'
187
+
188
+ # GET JSON(模块静态方法)
189
+ res = HttpGetter.get_json(
190
+ 'https://api.example.com/users',
191
+ { page: 1, size: 20 },
192
+ { 'Authorization' => 'Bearer token' },
193
+ { timeout: 30 }
194
+ )
195
+ puts res # => Hash(解析后的 JSON)
196
+
197
+ # GET 纯文本(不做 JSON 解析)
198
+ text = HttpGetter.get_text('https://example.com/robots.txt')
199
+ puts text # => 原始字符串
200
+ ```
201
+
202
+ ### 与 Getter 的对比
203
+
204
+ | 维度 | HttpGetter | IceJade::Getter::Client |
205
+ |------|------------|-------------------------|
206
+ | 调用方式 | 模块静态方法 | 先 `new` 实例化再调用 |
207
+ | URL 处理 | 只能传完整 URL | 支持 `base_url` + 相对路径 |
208
+ | 参数风格 | 位置参数 `(url,p,h,opts)` | 关键字参数 `(params:)` |
209
+ | 成功返回 | Hash / String | `IceJade::Response` |
210
+ | 错误处理 | 抛 `TimeoutError` / `HttpError` | 包装为 `Response`(不抛异常) |
211
+ | 原始文本 | `get_text` 直接返回 String | 统一 Response(`data` 自动解析) |
212
+ | 所属体系 | 独立脚本(无依赖) | ice-jade gem 分支 |
213
+ | 典型场景 | 快速脚本、教学、零依赖 | 正式项目、与 Quantum 混用 |
214
+
215
+ 详细文档:`documents/http-getter-api.md`
216
+
217
+ ---
218
+
219
+ ## 分支六:Cradle(HTTP 测试服务器)
139
220
 
140
221
  Cradle 是轻量级 HTTP 测试服务器,基于 Ruby 标准库 `socket` + `thread`,零外部依赖。用于本地测试 Poster、Quantum 或其他 HTTP 客户端。
141
222
 
@@ -167,7 +248,7 @@ server.start
167
248
 
168
249
  ---
169
250
 
170
- ## 分支五:YAMLServer(YAML 配置驱动的测试实例)
251
+ ## 分支七:YAMLServer(YAML 配置驱动的测试实例)
171
252
 
172
253
  YAMLServer 通过载入 YAML 配置文件启动定制化的 HTTP 测试实例,无需写 Ruby 代码注册路由,适合频繁切换测试场景。
173
254
 
@@ -222,17 +303,21 @@ Cradle 和 YAMLServer 都可以通过扩展代码添加新功能,变成满足
222
303
  ```
223
304
  lib/
224
305
  ice_jade.rb # 主入口
225
- http_poster.rb # 独立模块(零依赖)
306
+ http_poster.rb # 独立 POST 模块(零依赖)
307
+ http_getter.rb # 独立 GET 模块(零依赖)
226
308
  ice_jade/
227
309
  version.rb
228
310
  error.rb
229
311
  response.rb
230
312
  client_base.rb
313
+ transport.rb # Poster/Getter 共享传输层
231
314
  quantum/
232
315
  client.rb
233
316
  message.rb
234
317
  poster/
235
- client.rb
318
+ client.rb # POST 客户端
319
+ getter/
320
+ client.rb # GET 客户端
236
321
  cradle/
237
322
  server.rb # 原生服务器
238
323
  yaml_server.rb # YAML 配置层
@@ -247,13 +332,17 @@ config/cradle/
247
332
  examples/
248
333
  quantum_usage.rb # Quantum 用法示例
249
334
  poster_usage.rb # Poster + YAMLServer 联测
335
+ getter_usage.rb # Getter + YAMLServer 联测
250
336
  comparison_poster.rb # HttpPoster vs Poster 对比
337
+ comparison_getter.rb # HttpGetter vs Getter 对比
251
338
  cradle_usage.rb # Cradle 服务器用法
252
339
 
253
340
  documents/
254
341
  im-quantum-api.md # Quantum API 文档
255
342
  poster-api.md # Poster API 文档
343
+ getter-api.md # Getter API 文档
256
344
  http-poster-api.md # HttpPoster 文档
345
+ http-getter-api.md # HttpGetter 文档
257
346
  cradle-api.md # Cradle 文档
258
347
  yaml-server-api.md # YAMLServer 文档
259
348
  cradle-extension.md # 扩展指南
@@ -268,8 +357,10 @@ documents/
268
357
  ```
269
358
  ice-jade/
270
359
  ├── Quantum # IM Webhook 客户端
271
- ├── Poster # 通用 HTTP POST 客户端
272
- ├── HttpPoster # 独立模块(零 gem 依赖)
360
+ ├── Poster # 通用 HTTP POST 客户端
361
+ ├── Getter # 通用 HTTP GET 客户端 ┤ 共享 Transport
362
+ ├── HttpPoster # 独立 POST 模块(零 gem 依赖) ┐
363
+ ├── HttpGetter # 独立 GET 模块(零 gem 依赖) ┘ 并列独立工具对
273
364
  ├── Cradle # HTTP 测试服务器
274
365
  └── YAMLServer # YAML 配置层(基于 Cradle)
275
366
  ```
@@ -322,3 +413,5 @@ end
322
413
 
323
414
  - Quantum:文件上传 30MB,消息频率 20条/分钟
324
415
  - Cradle:不支持 WebSocket、HTTPS、持久连接
416
+
417
+ > AI生成
@@ -0,0 +1,41 @@
1
+ # Cradle 实例配置:Poster 测试专用
2
+ #
3
+ # 启动命令:
4
+ # ruby bin/cradle-instance config/cradle/poster_test.yml
5
+ #
6
+ # 本配置为 IceJade::Poster 测试提供精确匹配的响应格式,
7
+ # 使 examples/poster_usage.rb 和 examples/comparison_poster.rb 能够本地闭环通过。
8
+
9
+ port: 8765
10
+ host: 127.0.0.1
11
+
12
+ routes:
13
+ # POST /echo —— 回显 JSON 请求体
14
+ - method: POST
15
+ path: /echo
16
+ handler: echo_json
17
+
18
+ # POST /form —— 回显 form-urlencoded 数据
19
+ - method: POST
20
+ path: /form
21
+ handler: echo_form
22
+
23
+ # POST /upload —— 回显 multipart 文件上传
24
+ - method: POST
25
+ path: /upload
26
+ handler: echo_upload
27
+
28
+ # POST /error —— 模拟 500 错误
29
+ - method: POST
30
+ path: /error
31
+ handler: static
32
+ status: 500
33
+ headers:
34
+ Content-Type: application/json
35
+ body: '{"error":"Internal Server Error"}'
36
+
37
+ # POST /delay/:seconds —— 延迟响应
38
+ - method: POST
39
+ path: /delay/:seconds
40
+ handler: delay
41
+ delay: 1
@@ -0,0 +1,193 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # =============================================================================
5
+ # HttpGetter vs IceJade::Getter 同步对比示例
6
+ # =============================================================================
7
+ #
8
+ # 本文件把两种调用方式并排展示,均面向本地 Cradle YAML 实例,
9
+ # 通过 YAML 配置启动,无需外部网络,直接本地闭环验证。
10
+ #
11
+ # 左侧:HttpGetter —— 独立模块,静态方法,异常驱动
12
+ # 右侧:IceJade::Getter —— 面向对象,实例化复用,Response 驱动
13
+ #
14
+ # =============================================================================
15
+
16
+ require_relative '../lib/http_getter'
17
+ require_relative '../lib/ice_jade'
18
+
19
+ puts "=" * 70
20
+ puts "HttpGetter (独立模块) vs IceJade::Getter (ice-jade gem 分支)"
21
+ puts "目标: 本地 Cradle YAML 实例"
22
+ puts "=" * 70
23
+
24
+ # =============================================================================
25
+ # 启动 Cradle YAML 实例
26
+ # =============================================================================
27
+ config_path = File.join(__dir__, '../config/cradle/poster_test.yml')
28
+ instance = IceJade::Cradle::YAMLServer.new(config_path)
29
+ thread = Thread.new { instance.start }
30
+ sleep 0.5
31
+
32
+ base_url = "http://#{instance.config['host']}:#{instance.config['port']}"
33
+
34
+ # IceJade::Getter 实例(复用配置)
35
+ getter = IceJade::Getter::Client.new(
36
+ base_url: base_url,
37
+ headers: { 'X-Custom-Header' => 'ice-jade' },
38
+ timeout: 30
39
+ )
40
+
41
+ begin
42
+
43
+ # ==========================================================================
44
+ # 场景 1:GET 查询参数回显
45
+ # ==========================================================================
46
+ puts "\n【场景 1】GET → /echo (查询参数)"
47
+ puts "-" * 70
48
+
49
+ # --- HttpGetter ---
50
+ begin
51
+ res = HttpGetter.get_json(
52
+ "#{base_url}/echo",
53
+ { page: 1, size: 20 },
54
+ { 'X-Custom-Header' => 'ice-jade' },
55
+ { timeout: 30 }
56
+ )
57
+ puts "[HttpGetter] Query echoed: #{res.is_a?(Hash) ? res['query'] : res}"
58
+ rescue HttpGetter::HttpError => e
59
+ puts "[HttpGetter] 失败: #{e.message}"
60
+ end
61
+
62
+ # --- IceJade::Getter ---
63
+ resp = getter.get('/echo', params: { page: 1, size: 20 })
64
+ puts "[IceJade::Getter] Query echoed: #{resp.data.is_a?(Hash) ? resp.data['query'] : 'N/A'}"
65
+ puts " success?: #{resp.success?}, code: #{resp.code}"
66
+
67
+ # ==========================================================================
68
+ # 场景 2:GET 自定义请求头回显
69
+ # ==========================================================================
70
+ puts "\n【场景 2】GET → /headers (自定义头)"
71
+ puts "-" * 70
72
+
73
+ # --- HttpGetter ---
74
+ begin
75
+ res = HttpGetter.get_json(
76
+ "#{base_url}/headers",
77
+ {},
78
+ { 'X-Custom-Header' => 'ice-jade', 'X-Request-ID' => 'uuid-5678' },
79
+ { timeout: 30 }
80
+ )
81
+ echoed = res.is_a?(Hash) ? res['headers'] : {}
82
+ puts "[HttpGetter] X-Custom-Header: #{echoed['X-Custom-Header']}"
83
+ puts "[HttpGetter] X-Request-Id: #{echoed['X-Request-Id']}"
84
+ rescue HttpGetter::HttpError => e
85
+ puts "[HttpGetter] 失败: #{e.message}"
86
+ end
87
+
88
+ # --- IceJade::Getter ---
89
+ resp = getter.get('/headers', headers: { 'X-Request-ID' => 'uuid-5678' })
90
+ echoed = resp.data.is_a?(Hash) ? resp.data['headers'] : {}
91
+ puts "[IceJade::Getter] X-Custom-Header: #{echoed['X-Custom-Header']}"
92
+ puts "[IceJade::Getter] X-Request-Id: #{echoed['X-Request-Id']}"
93
+ puts " success?: #{resp.success?}, code: #{resp.code}"
94
+
95
+ # ==========================================================================
96
+ # 场景 3:GET 纯文本(get_text 返回原始字符串,不做 JSON 解析)
97
+ # ==========================================================================
98
+ puts "\n【场景 3】GET 纯文本 → /echo (get_text)"
99
+ puts "-" * 70
100
+
101
+ # --- HttpGetter ---
102
+ begin
103
+ res = HttpGetter.get_text(
104
+ "#{base_url}/echo",
105
+ { ping: 'pong' },
106
+ {},
107
+ { timeout: 30 }
108
+ )
109
+ puts "[HttpGetter] 原始字符串: #{res.class} (#{res.to_s[0, 60]}...)"
110
+ rescue HttpGetter::HttpError => e
111
+ puts "[HttpGetter] 失败: #{e.message}"
112
+ end
113
+
114
+ # --- IceJade::Getter ---
115
+ # Getter 没有 raw 选项,统一返回 Response,data 已解析
116
+ resp = getter.get('/echo', params: { ping: 'pong' })
117
+ puts "[IceJade::Getter] 解析后 data: #{resp.data.class}"
118
+ puts " success?: #{resp.success?}, code: #{resp.code}"
119
+
120
+ # ==========================================================================
121
+ # 场景 4:错误处理(HTTP 404)
122
+ # ==========================================================================
123
+ puts "\n【场景 4】错误处理 → /status/404"
124
+ puts "-" * 70
125
+
126
+ # --- HttpGetter ---
127
+ begin
128
+ HttpGetter.get_json("#{base_url}/status/404", {}, {}, { timeout: 30 })
129
+ rescue HttpGetter::HttpError => e
130
+ puts "[HttpGetter] 捕获异常: #{e.message}"
131
+ end
132
+
133
+ # --- IceJade::Getter ---
134
+ resp = getter.get('/status/404')
135
+ puts "[IceJade::Getter] 不抛异常,直接检查响应:"
136
+ puts " success?: #{resp.success?}, code: #{resp.code}, message: #{resp.message}"
137
+
138
+ # ==========================================================================
139
+ # 场景 5:通用 get(url 已含 query,params 追加)
140
+ # ==========================================================================
141
+ puts "\n【场景 5】通用 get → /echo?foo=1 (params 追加)"
142
+ puts "-" * 70
143
+
144
+ # --- HttpGetter ---
145
+ begin
146
+ res = HttpGetter.get(
147
+ "#{base_url}/echo?foo=1",
148
+ { bar: '2' },
149
+ { 'X-Request-ID' => 'uuid-1234' },
150
+ { timeout: 30 }
151
+ )
152
+ puts "[HttpGetter] Query echoed: #{res.is_a?(Hash) ? res['query'] : res}"
153
+ rescue HttpGetter::HttpError => e
154
+ puts "[HttpGetter] 失败: #{e.message}"
155
+ end
156
+
157
+ # --- IceJade::Getter ---
158
+ resp = getter.get('/echo?foo=1', params: { bar: '2' },
159
+ headers: { 'X-Request-ID' => 'uuid-1234' })
160
+ puts "[IceJade::Getter] Query echoed: #{resp.data.is_a?(Hash) ? resp.data['query'] : 'N/A'}"
161
+ puts " success?: #{resp.success?}, code: #{resp.code}"
162
+
163
+ # ==========================================================================
164
+ # 结构差异速查表
165
+ # ==========================================================================
166
+ puts "\n" + "=" * 70
167
+ puts "结构差异速查表"
168
+ puts "=" * 70
169
+ puts <<~DIFF
170
+
171
+ ┌──────────────────┬──────────────────────────┬──────────────────────────┐
172
+ │ 维度 │ HttpGetter │ IceJade::Getter │
173
+ ├──────────────────┼──────────────────────────┼──────────────────────────┤
174
+ │ 调用方式 │ 模块静态方法 │ 先 new 实例化再调用 │
175
+ │ URL 处理 │ 只能传完整 URL │ 支持 base_url + 相对路径 │
176
+ │ 参数风格 │ 位置参数 (url,p,h,opts) │ 关键字参数 (params:) │
177
+ │ 配置复用 │ 每次调用都传 headers │ 初始化一次,多次复用 │
178
+ │ 成功返回 │ Hash / String │ IceJade::Response │
179
+ │ 错误处理 │ 抛 TimeoutError/HttpError│ 包装为 Response │
180
+ │ 判断结果 │ begin/rescue │ resp.success? │
181
+ │ 原始文本 │ get_text 直接返回 String │ 统一 Response(data 解析)│
182
+ │ 所属体系 │ 独立脚本 │ ice-jade gem 分支 │
183
+ │ 适合场景 │ 快速脚本、教学、零依赖 │ 正式项目、与 Quantum 混用│
184
+ └──────────────────┴──────────────────────────┴──────────────────────────┘
185
+
186
+ DIFF
187
+ puts "示例运行完毕。"
188
+
189
+ ensure
190
+ instance.stop
191
+ thread.join
192
+ puts "\nCradle YAML 实例已关闭。"
193
+ end
@@ -0,0 +1,195 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # =============================================================================
5
+ # HttpPoster vs IceJade::Poster 同步对比示例
6
+ # =============================================================================
7
+ #
8
+ # 本文件把两种调用方式并排展示,均面向本地 Cradle YAML 实例,
9
+ # 通过 YAML 配置启动,无需外部网络,直接本地闭环验证。
10
+ #
11
+ # 左侧:HttpPoster —— 独立模块,静态方法,异常驱动
12
+ # 右侧:IceJade::Poster —— 面向对象,实例化复用,Response 驱动
13
+ #
14
+ # =============================================================================
15
+
16
+ require_relative '../lib/http_poster'
17
+ require_relative '../lib/ice_jade'
18
+ require 'tmpdir'
19
+
20
+ puts "=" * 70
21
+ puts "HttpPoster (独立模块) vs IceJade::Poster (ice-jade gem 分支)"
22
+ puts "目标: 本地 Cradle YAML 实例"
23
+ puts "=" * 70
24
+
25
+ # =============================================================================
26
+ # 启动 Cradle YAML 实例
27
+ # =============================================================================
28
+ config_path = File.join(__dir__, '../config/cradle/poster_test.yml')
29
+ instance = IceJade::Cradle::YAMLServer.new(config_path)
30
+ thread = Thread.new { instance.start }
31
+ sleep 0.5
32
+
33
+ base_url = "http://#{instance.config['host']}:#{instance.config['port']}"
34
+
35
+ # IceJade::Poster 实例(复用配置)
36
+ poster = IceJade::Poster::Client.new(
37
+ base_url: base_url,
38
+ headers: { 'X-Custom-Header' => 'ice-jade' },
39
+ timeout: 30
40
+ )
41
+
42
+ begin
43
+
44
+ # ==========================================================================
45
+ # 场景 1:POST JSON
46
+ # ==========================================================================
47
+ puts "\n【场景 1】POST JSON → /echo"
48
+ puts "-" * 70
49
+
50
+ # --- HttpPoster ---
51
+ begin
52
+ res = HttpPoster.post_json(
53
+ "#{base_url}/echo",
54
+ { name: 'Alice', role: 'admin' },
55
+ { 'X-Custom-Header' => 'ice-jade' },
56
+ { timeout: 30 }
57
+ )
58
+ puts "[HttpPoster] Echo body: #{res.is_a?(Hash) ? res['body'] : res}"
59
+ rescue HttpPoster::HttpError => e
60
+ puts "[HttpPoster] 失败: #{e.message}"
61
+ end
62
+
63
+ # --- IceJade::Poster ---
64
+ resp = poster.post_json('/echo', { name: 'Alice', role: 'admin' })
65
+ puts "[IceJade::Poster] Echo body: #{resp.data.is_a?(Hash) ? resp.data['body'] : resp.data}"
66
+ puts " success?: #{resp.success?}, code: #{resp.code}"
67
+
68
+ # ==========================================================================
69
+ # 场景 2:POST Form
70
+ # ==========================================================================
71
+ puts "\n【场景 2】POST Form → /form"
72
+ puts "-" * 70
73
+
74
+ # --- HttpPoster ---
75
+ begin
76
+ res = HttpPoster.post_form(
77
+ "#{base_url}/form",
78
+ { username: 'admin', password: 'secret' },
79
+ {},
80
+ { timeout: 30 }
81
+ )
82
+ puts "[HttpPoster] Form echoed: #{res.is_a?(Hash) ? res['form'] : res}"
83
+ rescue HttpPoster::HttpError => e
84
+ puts "[HttpPoster] 失败: #{e.message}"
85
+ end
86
+
87
+ # --- IceJade::Poster ---
88
+ resp = poster.post_form('/form', { username: 'admin', password: 'secret' })
89
+ puts "[IceJade::Poster] Form echoed: #{resp.data.is_a?(Hash) ? resp.data['form'] : 'N/A'}"
90
+ puts " success?: #{resp.success?}, code: #{resp.code}"
91
+
92
+ # ==========================================================================
93
+ # 场景 3:POST Multipart(文件上传)
94
+ # ==========================================================================
95
+ puts "\n【场景 3】POST Multipart → /upload"
96
+ puts "-" * 70
97
+
98
+ tmp_path = File.join(Dir.tmpdir, 'ice_jade_demo.txt')
99
+ File.write(tmp_path, "Hello from IceJade!\n")
100
+
101
+ # --- HttpPoster ---
102
+ begin
103
+ res = HttpPoster.post_multipart(
104
+ "#{base_url}/upload",
105
+ { file: tmp_path, description: 'demo' },
106
+ {},
107
+ { timeout: 30 }
108
+ )
109
+ puts "[HttpPoster] 文件名回显: #{res.is_a?(Hash) ? res['files']&.keys : res}"
110
+ rescue HttpPoster::HttpError => e
111
+ puts "[HttpPoster] 失败: #{e.message}"
112
+ end
113
+
114
+ # --- IceJade::Poster ---
115
+ resp = poster.post_multipart('/upload', { file: tmp_path, description: 'demo' })
116
+ puts "[IceJade::Poster] 文件名回显: #{resp.data.is_a?(Hash) ? resp.data['files']&.keys : 'N/A'}"
117
+ puts " success?: #{resp.success?}, code: #{resp.code}"
118
+
119
+ File.delete(tmp_path)
120
+
121
+ # ==========================================================================
122
+ # 场景 4:错误处理(HTTP 500)
123
+ # ==========================================================================
124
+ puts "\n【场景 4】错误处理 → /error"
125
+ puts "-" * 70
126
+
127
+ # --- HttpPoster ---
128
+ begin
129
+ HttpPoster.post_json("#{base_url}/error", {})
130
+ rescue HttpPoster::HttpError => e
131
+ puts "[HttpPoster] 捕获异常: #{e.message}"
132
+ end
133
+
134
+ # --- IceJade::Poster ---
135
+ resp = poster.post_json('/error', {})
136
+ puts "[IceJade::Poster] 不抛异常,直接检查响应:"
137
+ puts " success?: #{resp.success?}, code: #{resp.code}, message: #{resp.message}"
138
+
139
+ # ==========================================================================
140
+ # 场景 5:通用 post(自定义 Content-Type)
141
+ # ==========================================================================
142
+ puts "\n【场景 5】通用 post → /echo (application/xml)"
143
+ puts "-" * 70
144
+
145
+ # --- HttpPoster ---
146
+ begin
147
+ res = HttpPoster.post(
148
+ "#{base_url}/echo",
149
+ '<xml><body>hello</body></xml>',
150
+ { 'Content-Type' => 'application/xml',
151
+ 'X-Request-ID' => 'uuid-1234' },
152
+ { timeout: 30 }
153
+ )
154
+ puts "[HttpPoster] Content-Type 回显: #{res.is_a?(Hash) ? res['headers']&.dig('Content-Type') : res}"
155
+ rescue HttpPoster::HttpError => e
156
+ puts "[HttpPoster] 失败: #{e.message}"
157
+ end
158
+
159
+ # --- IceJade::Poster ---
160
+ resp = poster.post('/echo', '<xml><body>hello</body></xml>',
161
+ headers: { 'X-Request-ID' => 'uuid-1234' },
162
+ content_type: 'application/xml')
163
+ puts "[IceJade::Poster] Content-Type 回显: #{resp.data.is_a?(Hash) ? resp.data['headers']&.dig('Content-Type') : 'N/A'}"
164
+ puts " success?: #{resp.success?}, code: #{resp.code}"
165
+
166
+ # ==========================================================================
167
+ # 结构差异速查表
168
+ # ==========================================================================
169
+ puts "\n" + "=" * 70
170
+ puts "结构差异速查表"
171
+ puts "=" * 70
172
+ puts <<~DIFF
173
+
174
+ ┌──────────────────┬──────────────────────────┬──────────────────────────┐
175
+ │ 维度 │ HttpPoster │ IceJade::Poster │
176
+ ├──────────────────┼──────────────────────────┼──────────────────────────┤
177
+ │ 调用方式 │ 模块静态方法 │ 先 new 实例化再调用 │
178
+ │ URL 处理 │ 只能传完整 URL │ 支持 base_url + 相对路径 │
179
+ │ 参数风格 │ 位置参数 (url,p,h,opts) │ 关键字参数 (headers:) │
180
+ │ 配置复用 │ 每次调用都传 headers │ 初始化一次,多次复用 │
181
+ │ 成功返回 │ Hash / String │ IceJade::Response │
182
+ │ 错误处理 │ 抛 TimeoutError/HttpError│ 包装为 Response │
183
+ │ 判断结果 │ begin/rescue │ resp.success? │
184
+ │ 所属体系 │ 独立脚本 │ ice-jade gem 分支 │
185
+ │ 适合场景 │ 快速脚本、教学、零依赖 │ 正式项目、与 Quantum 混用│
186
+ └──────────────────┴──────────────────────────┴──────────────────────────┘
187
+
188
+ DIFF
189
+ puts "示例运行完毕。"
190
+
191
+ ensure
192
+ instance.stop
193
+ thread.join
194
+ puts "\nCradle YAML 实例已关闭。"
195
+ end