ice-jade 0.6.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: 70f058625ea166e107bf454a437142ebb6a4cb0f3c49d88e484fe45be069d271
4
- data.tar.gz: 0ea0b70756e261510b7ac062f39f02411dd7a54301b7f3c86339c7f6380f57db
3
+ metadata.gz: f57e6758c88b25a57a7891bed83b7416ae9f511e780bcde4be30bb6f211ee3b3
4
+ data.tar.gz: 902380ad3fb6f0bfd2bd21f171bc3aa99a9538590455ba4a41f2b582a8a0d326
5
5
  SHA512:
6
- metadata.gz: aed37f531cce690f2ed41b80adcc5e45b8a7cab2dbb5ad1c4c2c111d4e477ae9c8e172a99f02b05a1ca13a4a150169e23c2f00109f49ec0dd640338e232eb6c1
7
- data.tar.gz: 72cc6594677d28a1ea49ef7f9fac87dd73ff8af30ce1af9dac8fa3c95740171aab185b3c0b5b1f2f418e87b938dd38900c1b27ceea5adfec022c2ba76a77c874
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,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,85 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Getter 用法示例:调用 Cradle YAML 实例
5
+ #
6
+ # 本示例通过 YAML 配置启动定制化的 Cradle 测试实例,
7
+ # 然后用 IceJade::Getter::Client 向它发起 GET 请求,本地闭环验证。
8
+ #
9
+ # Getter 与 Poster 并列:Poster 专责 POST,Getter 专责 GET,
10
+ # 两者复用同一套 IceJade::Transport 底层(URI 拼接、超时重试、Response 包装)。
11
+
12
+ require_relative '../lib/ice_jade'
13
+
14
+ # =============================================================================
15
+ # 1. 启动 Cradle YAML 实例
16
+ # =============================================================================
17
+ puts "=== 启动 Cradle YAML 实例 ==="
18
+ config_path = File.join(__dir__, '../config/cradle/poster_test.yml')
19
+ instance = IceJade::Cradle::YAMLServer.new(config_path)
20
+ thread = Thread.new { instance.start }
21
+ sleep 0.5
22
+
23
+ getter = IceJade::Getter::Client.new(
24
+ base_url: "http://#{instance.config['host']}:#{instance.config['port']}",
25
+ headers: { 'X-Custom-Header' => 'ice-jade' }
26
+ )
27
+
28
+ begin
29
+
30
+ # ------------------------------------------------------------------
31
+ # 1. GET 查询参数回显
32
+ # ------------------------------------------------------------------
33
+ puts "\n=== 1. GET → /echo (查询参数) ==="
34
+ resp = getter.get('/echo', params: { page: 1, size: 20 })
35
+ puts "Success: #{resp.success?}, Code: #{resp.code}, Message: #{resp.message}"
36
+ puts "Query echoed: #{resp.data.is_a?(Hash) ? resp.data['query'] : 'N/A'}"
37
+
38
+ # ------------------------------------------------------------------
39
+ # 2. GET 回显请求头(默认头 + 额外头合并校验)
40
+ # ------------------------------------------------------------------
41
+ puts "\n=== 2. GET → /headers (自定义头) ==="
42
+ resp = getter.get('/headers', headers: { 'X-Request-ID' => 'uuid-5678' })
43
+ puts "Success: #{resp.success?}, Code: #{resp.code}"
44
+ # 注意:Net::HTTP 会将头名规范化为 X-Request-Id(每段首字母大写)
45
+ echoed_headers = resp.data.is_a?(Hash) ? resp.data['headers'] : {}
46
+ puts "X-Custom-Header: #{echoed_headers['X-Custom-Header']}"
47
+ puts "X-Request-Id: #{echoed_headers['X-Request-Id']}"
48
+
49
+ # ------------------------------------------------------------------
50
+ # 3. GET 错误处理(HTTP 404)
51
+ # ------------------------------------------------------------------
52
+ puts "\n=== 3. GET 错误处理 → /status/404 ==="
53
+ resp = getter.get('/status/404')
54
+ puts "Success: #{resp.success?}, Code: #{resp.code}, Message: #{resp.message}"
55
+
56
+ # ------------------------------------------------------------------
57
+ # 4. GET 错误处理(HTTP 500)
58
+ # ------------------------------------------------------------------
59
+ puts "\n=== 4. GET 错误处理 → /status/500 ==="
60
+ resp = getter.get('/status/500')
61
+ puts "Success: #{resp.success?}, Code: #{resp.code}, Message: #{resp.message}"
62
+
63
+ # ------------------------------------------------------------------
64
+ # 5. GET 已含 query 的 URL(params 追加而非覆盖)
65
+ # ------------------------------------------------------------------
66
+ puts "\n=== 5. GET → /echo?foo=1 (params 追加) ==="
67
+ resp = getter.get('/echo?foo=1', params: { bar: '2' })
68
+ puts "Success: #{resp.success?}, Code: #{resp.code}"
69
+ puts "Query echoed: #{resp.data.is_a?(Hash) ? resp.data['query'] : 'N/A'}"
70
+
71
+ # ------------------------------------------------------------------
72
+ # 6. GET 健康检查
73
+ # ------------------------------------------------------------------
74
+ puts "\n=== 6. GET → /health ==="
75
+ resp = getter.get('/health')
76
+ puts "Success: #{resp.success?}, Code: #{resp.code}"
77
+ puts "Status: #{resp.data.is_a?(Hash) ? resp.data['status'] : 'N/A'}"
78
+
79
+ puts "\n=== 所有测试通过 ==="
80
+
81
+ ensure
82
+ instance.stop
83
+ thread.join
84
+ puts "\nCradle YAML 实例已关闭。"
85
+ end
@@ -0,0 +1,159 @@
1
+ # http_getter.rb
2
+ # Ruby 纯标准库 HTTP GET 工具模块
3
+ # 零外部依赖,支持查询参数 / JSON / 透传响应
4
+ #
5
+ # =============================================================================
6
+ # 说明:本模块是独立工具脚本,不依赖 ice-jade gem。
7
+ #
8
+ # 如果你正在使用 ice-jade 项目,推荐优先使用 IceJade::Getter::Client
9
+ # (位于 lib/ice_jade/getter/client.rb),原因:
10
+ #
11
+ # - 面向对象:初始化时配置 base_url/headers/timeout,后续复用
12
+ # - 统一响应:返回 IceJade::Response(ok, code, message, data, raw)
13
+ # - 异常友好:超时/HTTP 错误均包装为 Response,不抛异常
14
+ # - 与 Quantum 分支共享同一套响应体系
15
+ #
16
+ # HttpGetter 适合:
17
+ # - 不想引入任何 gem 依赖的独立脚本
18
+ # - 快速测试、一次性请求
19
+ # - 学习/教学用途,代码更扁平直观
20
+ #
21
+ # 对比示例见:examples/comparison_getter.rb
22
+ # =============================================================================
23
+
24
+ require 'net/http'
25
+ require 'json'
26
+ require 'uri'
27
+
28
+ module HttpGetter
29
+ class Error < StandardError; end
30
+ class TimeoutError < Error; end
31
+ class HttpError < Error; end
32
+
33
+ # ============ 核心 GET 方法 ============
34
+ #
35
+ # @param url [String] 完整 URL 或路径;若已含 query,params 会追加在其后
36
+ # @param params [Hash] 查询参数,自动用 URI.encode_www_form 编码到 query string
37
+ # @param headers [Hash] 自定义请求头(自动覆盖默认头)
38
+ # @param opts [Hash] 选项: :timeout, :open_timeout, :max_retries
39
+ #
40
+ # @return [Hash/String] 解析后的 JSON 或原始响应体
41
+ #
42
+ def self.get(url, params = {}, headers = {}, opts = {})
43
+ uri = URI.parse(url)
44
+ unless params.empty?
45
+ extra = URI.encode_www_form(params)
46
+ uri.query = uri.query ? "#{uri.query}&#{extra}" : extra
47
+ end
48
+
49
+ req = Net::HTTP::Get.new(uri)
50
+ req['User-Agent'] = headers['User-Agent'] || 'ruby-http-getter/1.0'
51
+ req['Accept'] = headers['Accept'] || 'application/json'
52
+
53
+ # 认证头(支持 Bearer Token)
54
+ if headers['Authorization'] || headers[:authorization]
55
+ req['Authorization'] = headers['Authorization'] || headers[:authorization]
56
+ end
57
+
58
+ # 追加其余自定义头
59
+ headers.each do |k, v|
60
+ next if %w[Accept Authorization User-Agent].include?(k.to_s)
61
+ req[k.to_s] = v.to_s
62
+ end
63
+
64
+ execute(uri, req, opts)
65
+ end
66
+
67
+ # ============ 快捷方法 ============
68
+
69
+ # GET JSON(API 调用最常用)
70
+ # 自动设置 Accept: application/json,期望服务端返回 JSON
71
+ def self.get_json(url, params = {}, headers = {}, opts = {})
72
+ headers = headers.merge('Accept' => 'application/json')
73
+ get(url, params, headers, opts)
74
+ end
75
+
76
+ # GET 纯文本(原始响应体,不做 JSON 解析)
77
+ # 自动设置 Accept: text/plain,返回原始 String
78
+ def self.get_text(url, params = {}, headers = {}, opts = {})
79
+ headers = headers.merge('Accept' => 'text/plain')
80
+ opts = opts.merge(__raw__: true)
81
+ get(url, params, headers, opts)
82
+ end
83
+
84
+ private
85
+
86
+ def self.execute(uri, request, opts = {})
87
+ timeout = opts[:timeout] || 60
88
+ open_timeout = opts[:open_timeout] || 10
89
+ max_retries = opts[:max_retries] || 2
90
+ raw = opts[:__raw__] == true
91
+
92
+ http = Net::HTTP.new(uri.host, uri.port)
93
+ http.use_ssl = uri.scheme == 'https'
94
+ http.open_timeout = open_timeout
95
+ http.read_timeout = timeout
96
+
97
+ retries = 0
98
+ begin
99
+ response = http.request(request)
100
+ parse(response, raw)
101
+ rescue Net::OpenTimeout, Net::ReadTimeout => e
102
+ retries += 1
103
+ if retries <= max_retries
104
+ sleep(2 ** retries)
105
+ retry
106
+ end
107
+ raise TimeoutError, "请求超时(已重试 #{max_retries} 次): #{e.message}"
108
+ ensure
109
+ http.finish if http.started?
110
+ end
111
+ end
112
+
113
+ def self.parse(response, raw = false)
114
+ body = response.body.to_s
115
+ code = response.code.to_i
116
+
117
+ if code >= 400
118
+ begin
119
+ err = JSON.parse(body)
120
+ msg = err.dig('error', 'message') || body
121
+ rescue
122
+ msg = body
123
+ end
124
+ raise HttpError, "HTTP #{code}: #{msg}"
125
+ end
126
+
127
+ return body if raw
128
+
129
+ JSON.parse(body) rescue body
130
+ end
131
+ end
132
+
133
+ # ============ 使用示例 ============
134
+ if __FILE__ == $0
135
+ # 示例 1: GET JSON(调用公开 API)
136
+ begin
137
+ res = HttpGetter.get_json(
138
+ 'https://httpbin.org/get',
139
+ { page: 1, size: 20 },
140
+ { 'Authorization' => 'Bearer your-token-here' },
141
+ { timeout: 30 }
142
+ )
143
+ puts res['args']
144
+ rescue HttpGetter::HttpError => e
145
+ puts "请求失败: #{e.message}"
146
+ end
147
+
148
+ # 示例 2: GET 纯文本
149
+ # res = HttpGetter.get_text('https://httpbin.org/robots.txt')
150
+ # puts res # => 原始字符串
151
+
152
+ # 示例 3: 通用 GET(自定义头,url 已含 query,params 追加)
153
+ # res = HttpGetter.get(
154
+ # 'https://httpbin.org/get?foo=1',
155
+ # { bar: '2' },
156
+ # { 'X-Request-ID' => 'uuid-5678' }
157
+ # )
158
+ # puts res['args'] # => {"foo"=>"1", "bar"=>"2"}
159
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../transport'
4
+
5
+ module IceJade
6
+ module Getter
7
+ # 通用 HTTP GET 客户端
8
+ #
9
+ # 与 IceJade::Poster::Client 并列,专用负责 GET 请求。
10
+ # 底层传输能力(URI 拼接、请求执行、响应适配、超时重试)复用 IceJade::Transport,
11
+ # 与 Poster 仅 HTTP 动词与参数构造不同:GET 幂等,无请求体,参数走 query string。
12
+ #
13
+ # 零外部依赖,内置超时控制、指数退避重试、统一 Response 包装,
14
+ # 与 Quantum 模块风格一致,错误不抛异常。
15
+ #
16
+ # @example 基础用法
17
+ # getter = IceJade::Getter::Client.new(
18
+ # base_url: 'https://api.example.com',
19
+ # headers: { 'Authorization' => 'Bearer token' },
20
+ # timeout: 30
21
+ # )
22
+ # resp = getter.get('/users', params: { page: 1, size: 20 })
23
+ # puts resp.data if resp.success?
24
+ #
25
+ class Client
26
+ include IceJade::Transport
27
+
28
+ # @param base_url [String, nil] 基础 URL,后续请求可传相对路径
29
+ # @param headers [Hash] 默认请求头(每次请求自动合并,同名会被覆盖)
30
+ # @param timeout [Integer] 读取超时(秒),默认 60
31
+ # @param open_timeout [Integer] 连接超时(秒),默认 10
32
+ # @param max_retries [Integer] 超时后最大重试次数,默认 2
33
+ def initialize(base_url: nil, headers: {}, timeout: 60, open_timeout: 10, max_retries: 2)
34
+ init_transport(base_url: base_url, headers: headers,
35
+ timeout: timeout, open_timeout: open_timeout,
36
+ max_retries: max_retries)
37
+ end
38
+
39
+ # ------------------------------------------------------------------
40
+ # 通用 GET
41
+ # ------------------------------------------------------------------
42
+
43
+ # 发送通用 GET 请求
44
+ #
45
+ # 与 Poster 的 post 系列方法对称:返回 IceJade::Response,错误不抛异常。
46
+ # GET 幂等,超时重试同样安全。
47
+ #
48
+ # @param url [String] 完整 URL 或相对路径(需先设置 base_url)
49
+ # @param params [Hash, nil] 查询参数,自动用 URI.encode_www_form 编码到 query string;
50
+ # 若 url 已含 query,则在其后追加
51
+ # @param headers [Hash] 额外请求头(与默认头合并,同名覆盖)
52
+ # @return [IceJade::Response]
53
+ #
54
+ # @example 带查询参数
55
+ # resp = getter.get('/users', params: { page: 1, size: 20 })
56
+ # puts resp.data if resp.success?
57
+ #
58
+ # @example 自定义请求头
59
+ # resp = getter.get('/headers', headers: { 'X-Request-ID' => 'uuid-5678' })
60
+ #
61
+ # @example 错误响应
62
+ # resp = getter.get('/status/404')
63
+ # resp.success? # => false
64
+ # resp.code # => 404
65
+ def get(url, params: nil, headers: {})
66
+ uri = build_uri(url)
67
+ if params && !params.empty?
68
+ extra = URI.encode_www_form(params)
69
+ uri.query = uri.query ? "#{uri.query}&#{extra}" : extra
70
+ end
71
+ merged_headers = merge_headers(default_headers, normalize_headers(headers))
72
+ req = Net::HTTP::Get.new(uri)
73
+ apply_headers(req, merged_headers)
74
+ execute(uri, req)
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'getter/client'
@@ -1,11 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'net/http'
4
- require 'json'
5
- require 'uri'
6
- require 'securerandom'
7
- require_relative '../response'
8
- require_relative '../error'
3
+ require_relative '../transport'
9
4
 
10
5
  module IceJade
11
6
  module Poster
@@ -13,6 +8,7 @@ module IceJade
13
8
  #
14
9
  # 零外部依赖,支持 JSON / Form / Multipart 三种常用提交方式,
15
10
  # 内置超时控制、指数退避重试、统一 Response 包装,与 Quantum 模块风格一致。
11
+ # 底层传输能力(URI 拼接、请求执行、响应适配、超时重试)复用 IceJade::Transport,
16
12
  #
17
13
  # @example 基础用法
18
14
  # poster = IceJade::Poster::Client.new(
@@ -24,7 +20,7 @@ module IceJade
24
20
  # puts resp.data if resp.success?
25
21
  #
26
22
  class Client
27
- attr_reader :base_url, :default_headers, :timeout, :open_timeout, :max_retries
23
+ include IceJade::Transport
28
24
 
29
25
  # @param base_url [String, nil] 基础 URL,后续请求可传相对路径
30
26
  # @param headers [Hash] 默认请求头(每次请求自动合并,同名会被覆盖)
@@ -32,11 +28,9 @@ module IceJade
32
28
  # @param open_timeout [Integer] 连接超时(秒),默认 10
33
29
  # @param max_retries [Integer] 超时后最大重试次数,默认 2
34
30
  def initialize(base_url: nil, headers: {}, timeout: 60, open_timeout: 10, max_retries: 2)
35
- @base_url = base_url ? base_url.to_s.chomp('/') : nil
36
- @default_headers = normalize_headers(headers)
37
- @timeout = timeout
38
- @open_timeout = open_timeout
39
- @max_retries = max_retries
31
+ init_transport(base_url: base_url, headers: headers,
32
+ timeout: timeout, open_timeout: open_timeout,
33
+ max_retries: max_retries)
40
34
  end
41
35
 
42
36
  # ------------------------------------------------------------------
@@ -101,14 +95,7 @@ module IceJade
101
95
 
102
96
  private
103
97
 
104
- def build_uri(url)
105
- if base_url && !url.to_s.start_with?('http')
106
- URI.parse("#{base_url}#{url}")
107
- else
108
- URI.parse(url.to_s)
109
- end
110
- end
111
-
98
+ # 构造 POST 请求对象(JSON / Form / 自定义 Content-Type 三种序列化分支)
112
99
  def build_request(uri, body, content_type, headers)
113
100
  req = Net::HTTP::Post.new(uri)
114
101
  apply_headers(req, headers)
@@ -127,122 +114,6 @@ module IceJade
127
114
 
128
115
  req
129
116
  end
130
-
131
- def apply_headers(req, headers)
132
- headers.each do |k, v|
133
- req[k.to_s] = v.to_s
134
- end
135
- end
136
-
137
- def normalize_headers(h)
138
- h.transform_keys(&:to_s).transform_values(&:to_s)
139
- end
140
-
141
- def merge_headers(base, extra)
142
- base.merge(extra)
143
- end
144
-
145
- def execute(uri, request)
146
- http = Net::HTTP.new(uri.host, uri.port)
147
- http.use_ssl = uri.scheme == 'https'
148
- http.open_timeout = open_timeout
149
- http.read_timeout = timeout
150
-
151
- retries = 0
152
- begin
153
- response = http.request(request)
154
- Response.new(adapt_response(response))
155
- rescue Net::OpenTimeout, Net::ReadTimeout => e
156
- retries += 1
157
- if retries <= max_retries
158
- sleep(2**retries)
159
- retry
160
- end
161
- Response.new({ 'ok' => false, 'code' => 0,
162
- 'message' => "Timeout after #{max_retries} retries: #{e.message}", 'data' => nil })
163
- rescue StandardError => e
164
- Response.new({ 'ok' => false, 'code' => 0, 'message' => e.message, 'data' => nil })
165
- ensure
166
- http.finish if http.started?
167
- end
168
- end
169
-
170
- # 内置 HTTP 状态码文本(兼容各 Ruby 版本)
171
- HTTP_STATUS_TEXT = {
172
- 100 => 'Continue', 101 => 'Switching Protocols',
173
- 200 => 'OK', 201 => 'Created', 202 => 'Accepted',
174
- 204 => 'No Content',
175
- 301 => 'Moved Permanently', 302 => 'Found', 304 => 'Not Modified',
176
- 400 => 'Bad Request', 401 => 'Unauthorized', 403 => 'Forbidden',
177
- 404 => 'Not Found', 405 => 'Method Not Allowed',
178
- 429 => 'Too Many Requests',
179
- 500 => 'Internal Server Error', 502 => 'Bad Gateway',
180
- 503 => 'Service Unavailable'
181
- }.freeze
182
-
183
- def adapt_response(response)
184
- body = response.body.to_s
185
- code = response.code.to_i
186
-
187
- data = begin
188
- JSON.parse(body)
189
- rescue JSON::ParserError
190
- body
191
- end
192
-
193
- {
194
- 'ok' => code >= 200 && code < 300,
195
- 'code' => code,
196
- 'message' => HTTP_STATUS_TEXT[code] || 'Unknown',
197
- 'data' => data
198
- }
199
- end
200
-
201
- def build_multipart(params)
202
- boundary = "----IceJadePosterBoundary#{SecureRandom.hex(16)}"
203
- body = +"".b
204
-
205
- params.each do |key, value|
206
- if value.is_a?(String) && File.exist?(value)
207
- filename = File.basename(value)
208
- file_data = File.binread(value)
209
- mime = mime_type(filename)
210
- body << "--#{boundary}\r\n"
211
- body << %(Content-Disposition: form-data; name="#{key}"; filename="#{filename}") << "\r\n"
212
- body << "Content-Type: #{mime}" << "\r\n\r\n"
213
- body << file_data.b
214
- body << "\r\n"
215
- else
216
- body << "--#{boundary}\r\n"
217
- body << %(Content-Disposition: form-data; name="#{key}") << "\r\n\r\n"
218
- body << value.to_s
219
- body << "\r\n"
220
- end
221
- end
222
-
223
- body << "--#{boundary}--\r\n"
224
- [body, boundary]
225
- end
226
-
227
- def mime_type(filename)
228
- ext = File.extname(filename).downcase
229
- case ext
230
- when '.png' then 'image/png'
231
- when '.jpg', '.jpeg' then 'image/jpeg'
232
- when '.gif' then 'image/gif'
233
- when '.webp' then 'image/webp'
234
- when '.bmp' then 'image/bmp'
235
- when '.pdf' then 'application/pdf'
236
- when '.txt' then 'text/plain'
237
- when '.json' then 'application/json'
238
- when '.zip' then 'application/zip'
239
- when '.doc' then 'application/msword'
240
- when '.docx' then 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
241
- when '.xls' then 'application/vnd.ms-excel'
242
- when '.xlsx' then 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
243
- else 'application/octet-stream'
244
- end
245
- end
246
117
  end
247
118
  end
248
119
  end
@@ -0,0 +1,179 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'net/http'
4
+ require 'json'
5
+ require 'uri'
6
+ require 'securerandom'
7
+ require_relative 'response'
8
+ require_relative 'error'
9
+
10
+ module IceJade
11
+ # HTTP 传输层共享逻辑
12
+ #
13
+ # Poster::Client 与 Getter::Client 共用的底层能力:
14
+ # base_url 拼接、默认头合并、Net::HTTP 执行、超时指数退避重试、
15
+ # 状态码/JSON 解析/Response 包装。
16
+ #
17
+ # 设计为 instance 方法模块,由两个 Client 类 include 后复用,
18
+ # 各自只关心自己负责的 HTTP 动词与参数构造,传输细节由 Transport 统一。
19
+ module Transport
20
+ # 公共初始化参数解析,供两个 Client 的 #initialize 调用
21
+ #
22
+ # @param base_url [String, nil] 基础 URL,后续请求可传相对路径
23
+ # @param headers [Hash] 默认请求头(每次请求自动合并,同名会被覆盖)
24
+ # @param timeout [Integer] 读取超时(秒)
25
+ # @param open_timeout [Integer] 连接超时(秒)
26
+ # @param max_retries [Integer] 超时后最大重试次数
27
+ def init_transport(base_url:, headers:, timeout:, open_timeout:, max_retries:)
28
+ @base_url = base_url ? base_url.to_s.chomp('/') : nil
29
+ @default_headers = normalize_headers(headers)
30
+ @timeout = timeout
31
+ @open_timeout = open_timeout
32
+ @max_retries = max_retries
33
+ end
34
+
35
+ attr_reader :base_url, :default_headers, :timeout, :open_timeout, :max_retries
36
+
37
+ # 拼接 base_url 与相对路径,或直接解析完整 URL
38
+ #
39
+ # @param url [String] 完整 URL 或相对路径(需先设置 base_url)
40
+ # @return [URI::Generic]
41
+ def build_uri(url)
42
+ if base_url && !url.to_s.start_with?('http')
43
+ URI.parse("#{base_url}#{url}")
44
+ else
45
+ URI.parse(url.to_s)
46
+ end
47
+ end
48
+
49
+ # 将头 Hash 写入 Net::HTTP 请求对象
50
+ def apply_headers(req, headers)
51
+ headers.each do |k, v|
52
+ req[k.to_s] = v.to_s
53
+ end
54
+ end
55
+
56
+ # 头键值统一字符串化
57
+ def normalize_headers(h)
58
+ h.transform_keys(&:to_s).transform_values(&:to_s)
59
+ end
60
+
61
+ # 默认头与额外头合并(额外同名覆盖)
62
+ def merge_headers(base, extra)
63
+ base.merge(extra)
64
+ end
65
+
66
+ # 执行 Net::HTTP 请求,超时指数退避重试,异常包装为 Response
67
+ #
68
+ # @param uri [URI::Generic]
69
+ # @param request [Net::HTTPGenericRequest]
70
+ # @return [IceJade::Response]
71
+ def execute(uri, request)
72
+ http = Net::HTTP.new(uri.host, uri.port)
73
+ http.use_ssl = uri.scheme == 'https'
74
+ http.open_timeout = open_timeout
75
+ http.read_timeout = timeout
76
+
77
+ retries = 0
78
+ begin
79
+ response = http.request(request)
80
+ Response.new(adapt_response(response))
81
+ rescue Net::OpenTimeout, Net::ReadTimeout => e
82
+ retries += 1
83
+ if retries <= max_retries
84
+ sleep(2**retries)
85
+ retry
86
+ end
87
+ Response.new({ 'ok' => false, 'code' => 0,
88
+ 'message' => "Timeout after #{max_retries} retries: #{e.message}", 'data' => nil })
89
+ rescue StandardError => e
90
+ Response.new({ 'ok' => false, 'code' => 0, 'message' => e.message, 'data' => nil })
91
+ ensure
92
+ http.finish if http.started?
93
+ end
94
+ end
95
+
96
+ # 内置 HTTP 状态码文本(兼容各 Ruby 版本)
97
+ HTTP_STATUS_TEXT = {
98
+ 100 => 'Continue', 101 => 'Switching Protocols',
99
+ 200 => 'OK', 201 => 'Created', 202 => 'Accepted',
100
+ 204 => 'No Content',
101
+ 301 => 'Moved Permanently', 302 => 'Found', 304 => 'Not Modified',
102
+ 400 => 'Bad Request', 401 => 'Unauthorized', 403 => 'Forbidden',
103
+ 404 => 'Not Found', 405 => 'Method Not Allowed',
104
+ 429 => 'Too Many Requests',
105
+ 500 => 'Internal Server Error', 502 => 'Bad Gateway',
106
+ 503 => 'Service Unavailable'
107
+ }.freeze
108
+
109
+ # 将 Net::HTTP 响应适配为 IceJade::Response 所需的 Hash
110
+ #
111
+ # @param response [Net::HTTPResponse]
112
+ # @return [Hash] 形如 { 'ok' => bool, 'code' => int, 'message' => str, 'data' => data }
113
+ def adapt_response(response)
114
+ body = response.body.to_s
115
+ code = response.code.to_i
116
+
117
+ data = begin
118
+ JSON.parse(body)
119
+ rescue JSON::ParserError
120
+ body
121
+ end
122
+
123
+ {
124
+ 'ok' => code >= 200 && code < 300,
125
+ 'code' => code,
126
+ 'message' => HTTP_STATUS_TEXT[code] || 'Unknown',
127
+ 'data' => data
128
+ }
129
+ end
130
+
131
+ # multipart 边界生成与 MIME 推断 —— POST 专用,Getter 不使用,
132
+ # 但为保持 Transport 的自包含性一并放在这里,供 Poster::Client 复用。
133
+ def build_multipart(params)
134
+ boundary = "----IceJadeTransportBoundary#{SecureRandom.hex(16)}"
135
+ body = +"".b
136
+
137
+ params.each do |key, value|
138
+ if value.is_a?(String) && File.exist?(value)
139
+ filename = File.basename(value)
140
+ file_data = File.binread(value)
141
+ mime = mime_type(filename)
142
+ body << "--#{boundary}\r\n"
143
+ body << %(Content-Disposition: form-data; name="#{key}"; filename="#{filename}") << "\r\n"
144
+ body << "Content-Type: #{mime}" << "\r\n\r\n"
145
+ body << file_data.b
146
+ body << "\r\n"
147
+ else
148
+ body << "--#{boundary}\r\n"
149
+ body << %(Content-Disposition: form-data; name="#{key}") << "\r\n\r\n"
150
+ body << value.to_s
151
+ body << "\r\n"
152
+ end
153
+ end
154
+
155
+ body << "--#{boundary}--\r\n"
156
+ [body, boundary]
157
+ end
158
+
159
+ def mime_type(filename)
160
+ ext = File.extname(filename).downcase
161
+ case ext
162
+ when '.png' then 'image/png'
163
+ when '.jpg', '.jpeg' then 'image/jpeg'
164
+ when '.gif' then 'image/gif'
165
+ when '.webp' then 'image/webp'
166
+ when '.bmp' then 'image/bmp'
167
+ when '.pdf' then 'application/pdf'
168
+ when '.txt' then 'text/plain'
169
+ when '.json' then 'application/json'
170
+ when '.zip' then 'application/zip'
171
+ when '.doc' then 'application/msword'
172
+ when '.docx' then 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
173
+ when '.xls' then 'application/vnd.ms-excel'
174
+ when '.xlsx' then 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
175
+ else 'application/octet-stream'
176
+ end
177
+ end
178
+ end
179
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module IceJade
4
- VERSION = '0.6.0'
4
+ VERSION = '0.7.0'
5
5
  end
data/lib/ice_jade.rb CHANGED
@@ -10,4 +10,5 @@ require_relative 'ice_jade/client_base'
10
10
  require_relative 'ice_jade/quantum/client'
11
11
  require_relative 'ice_jade/quantum/message'
12
12
  require_relative 'ice_jade/poster'
13
+ require_relative 'ice_jade/getter'
13
14
  require_relative 'ice_jade/cradle'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ice-jade
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Frampt
@@ -9,8 +9,9 @@ bindir: bin
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies: []
12
- description: A lightweight Ruby SDK featuring Quantum (IM robot messaging) and Poster
13
- (zero-dependency HTTP POST client with JSON/Form/Multipart support).
12
+ description: A lightweight Ruby SDK featuring Quantum (IM robot messaging), Getter
13
+ & Poster (zero-dependency HTTP GET & POST clients with JSON/Form/Multipart support
14
+ and sharing a common IceJade::Transport layer).
14
15
  email:
15
16
  - 18995691365@189.cn
16
17
  executables:
@@ -24,10 +25,13 @@ files:
24
25
  - bin/cradle
25
26
  - bin/cradle-instance
26
27
  - config/cradle/poster_test.yml
28
+ - examples/comparison_getter.rb
27
29
  - examples/comparison_poster.rb
28
30
  - examples/cradle_usage.rb
31
+ - examples/getter_usage.rb
29
32
  - examples/poster_usage.rb
30
33
  - examples/quantum_usage.rb
34
+ - lib/http_getter.rb
31
35
  - lib/http_poster.rb
32
36
  - lib/ice_jade.rb
33
37
  - lib/ice_jade/client_base.rb
@@ -35,11 +39,14 @@ files:
35
39
  - lib/ice_jade/cradle/server.rb
36
40
  - lib/ice_jade/cradle/yaml_server.rb
37
41
  - lib/ice_jade/error.rb
42
+ - lib/ice_jade/getter.rb
43
+ - lib/ice_jade/getter/client.rb
38
44
  - lib/ice_jade/poster.rb
39
45
  - lib/ice_jade/poster/client.rb
40
46
  - lib/ice_jade/quantum/client.rb
41
47
  - lib/ice_jade/quantum/message.rb
42
48
  - lib/ice_jade/response.rb
49
+ - lib/ice_jade/transport.rb
43
50
  - lib/ice_jade/version.rb
44
51
  homepage: https://www.srdcloud.cn/codehub/Frampt/ice-jade
45
52
  licenses:
@@ -61,5 +68,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
61
68
  requirements: []
62
69
  rubygems_version: 4.0.10
63
70
  specification_version: 4
64
- summary: IM webhook SDK & general-purpose HTTP POST client
71
+ summary: IM webhook SDK & general-purpose HTTP POST/GET client
65
72
  test_files: []