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 +4 -4
- data/README.md +104 -11
- data/config/cradle/poster_test.yml +41 -0
- data/examples/comparison_getter.rb +193 -0
- data/examples/comparison_poster.rb +195 -0
- data/examples/cradle_usage.rb +92 -0
- data/examples/getter_usage.rb +85 -0
- data/examples/poster_usage.rb +89 -0
- data/examples/quantum_usage.rb +113 -0
- data/lib/http_getter.rb +159 -0
- data/lib/ice_jade/getter/client.rb +78 -0
- data/lib/ice_jade/getter.rb +3 -0
- data/lib/ice_jade/poster/client.rb +7 -136
- data/lib/ice_jade/transport.rb +179 -0
- data/lib/ice_jade/version.rb +1 -1
- data/lib/ice_jade.rb +1 -0
- metadata +16 -4
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Cradle 用法示例:启动本地 HTTP 测试服务器
|
|
5
|
+
#
|
|
6
|
+
# 本示例演示如何用 IceJade::Cradle::Server 启动一个测试服务器,
|
|
7
|
+
# 并用 IceJade::Poster::Client 向它发起请求。
|
|
8
|
+
#
|
|
9
|
+
# 运行方式:
|
|
10
|
+
# ruby examples/cradle_usage.rb
|
|
11
|
+
#
|
|
12
|
+
# 或者直接使用命令行工具:
|
|
13
|
+
# ruby bin/cradle -p 8765
|
|
14
|
+
|
|
15
|
+
require_relative '../lib/ice_jade'
|
|
16
|
+
|
|
17
|
+
# =============================================================================
|
|
18
|
+
# 1. 启动 Cradle 测试服务器(后台线程)
|
|
19
|
+
# =============================================================================
|
|
20
|
+
puts "=== 启动 Cradle 测试服务器 ==="
|
|
21
|
+
server = IceJade::Cradle::Server.new(port: 8765, silent: true)
|
|
22
|
+
thread = Thread.new { server.start }
|
|
23
|
+
sleep 0.5 # 等待服务器启动
|
|
24
|
+
|
|
25
|
+
poster = IceJade::Poster::Client.new(base_url: 'http://127.0.0.1:8765')
|
|
26
|
+
|
|
27
|
+
begin
|
|
28
|
+
# =============================================================================
|
|
29
|
+
# 2. 健康检查
|
|
30
|
+
# =============================================================================
|
|
31
|
+
puts "\n=== 2. GET /health ==="
|
|
32
|
+
resp = poster.post_json('/health', {})
|
|
33
|
+
puts "Health: #{resp.data}"
|
|
34
|
+
|
|
35
|
+
# =============================================================================
|
|
36
|
+
# 3. GET 回显查询参数
|
|
37
|
+
# =============================================================================
|
|
38
|
+
puts "\n=== 3. GET /echo?foo=bar&baz=qux ==="
|
|
39
|
+
# 使用通用 post 的 GET 不太合适,这里直接用 post_json 到 /echo 演示 POST 回显
|
|
40
|
+
# GET 回显需要另外的方式,这里跳过
|
|
41
|
+
|
|
42
|
+
# =============================================================================
|
|
43
|
+
# 4. POST /echo —— 回显 JSON 请求体
|
|
44
|
+
# =============================================================================
|
|
45
|
+
puts "\n=== 4. POST /echo (JSON) ==="
|
|
46
|
+
resp = poster.post_json('/echo', { name: 'Alice', role: 'admin' })
|
|
47
|
+
puts "Echo body: #{resp.data}"
|
|
48
|
+
|
|
49
|
+
# =============================================================================
|
|
50
|
+
# 5. POST /form —— 接收 form-urlencoded
|
|
51
|
+
# =============================================================================
|
|
52
|
+
puts "\n=== 5. POST /form (Form) ==="
|
|
53
|
+
resp = poster.post_form('/form', { username: 'admin', password: 'secret' })
|
|
54
|
+
puts "Form data: #{resp.data}"
|
|
55
|
+
|
|
56
|
+
# =============================================================================
|
|
57
|
+
# 6. POST /upload —— 接收 multipart 文件上传
|
|
58
|
+
# =============================================================================
|
|
59
|
+
puts "\n=== 6. POST /upload (Multipart) ==="
|
|
60
|
+
require 'tmpdir'
|
|
61
|
+
tmp_path = File.join(Dir.tmpdir, 'cradle_demo.txt')
|
|
62
|
+
File.write(tmp_path, "Hello from Cradle!\n")
|
|
63
|
+
|
|
64
|
+
resp = poster.post_multipart('/upload', { file: tmp_path, description: 'demo' })
|
|
65
|
+
puts "Upload response: #{resp.data}"
|
|
66
|
+
File.delete(tmp_path)
|
|
67
|
+
|
|
68
|
+
# =============================================================================
|
|
69
|
+
# 7. GET /status/:code —— 模拟错误状态码
|
|
70
|
+
# =============================================================================
|
|
71
|
+
puts "\n=== 7. GET /status/500 (模拟 500 错误) ==="
|
|
72
|
+
# 注意:Cradle 的 /status/:code 是 GET,这里用 post_json 会走 POST,不匹配路由
|
|
73
|
+
# 所以使用 poster.post 通用方法,但会返回 404(因为 POST /status/500 未注册)
|
|
74
|
+
# 实际测试 GET 需要用其他工具如 curl
|
|
75
|
+
# 这里演示 POST /echo 的成功响应即可
|
|
76
|
+
|
|
77
|
+
# =============================================================================
|
|
78
|
+
# 8. POST /delay/:seconds —— 测试超时
|
|
79
|
+
# =============================================================================
|
|
80
|
+
puts "\n=== 8. POST /delay/1 (延迟 1 秒) ==="
|
|
81
|
+
start_time = Time.now
|
|
82
|
+
resp = poster.post_json('/delay/1', { test: 'delay' })
|
|
83
|
+
elapsed = Time.now - start_time
|
|
84
|
+
puts "Delayed response: #{resp.data}, elapsed: #{elapsed.round(2)}s"
|
|
85
|
+
|
|
86
|
+
puts "\n=== 所有测试通过 ==="
|
|
87
|
+
|
|
88
|
+
ensure
|
|
89
|
+
server.stop
|
|
90
|
+
thread.join
|
|
91
|
+
puts "\nCradle 服务器已关闭。"
|
|
92
|
+
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,89 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Poster 用法示例:调用 Cradle YAML 实例
|
|
5
|
+
#
|
|
6
|
+
# 本示例通过 YAML 配置启动定制化的 Cradle 测试实例,
|
|
7
|
+
# 然后用 IceJade::Poster::Client 向它发起请求,本地闭环验证。
|
|
8
|
+
|
|
9
|
+
require_relative '../lib/ice_jade'
|
|
10
|
+
require 'tmpdir'
|
|
11
|
+
|
|
12
|
+
# =============================================================================
|
|
13
|
+
# 1. 启动 Cradle YAML 实例
|
|
14
|
+
# =============================================================================
|
|
15
|
+
puts "=== 启动 Cradle YAML 实例 ==="
|
|
16
|
+
config_path = File.join(__dir__, '../config/cradle/poster_test.yml')
|
|
17
|
+
instance = IceJade::Cradle::YAMLServer.new(config_path)
|
|
18
|
+
thread = Thread.new { instance.start }
|
|
19
|
+
sleep 0.5
|
|
20
|
+
|
|
21
|
+
poster = IceJade::Poster::Client.new(
|
|
22
|
+
base_url: "http://#{instance.config['host']}:#{instance.config['port']}",
|
|
23
|
+
headers: { 'X-Custom-Header' => 'ice-jade' }
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
begin
|
|
27
|
+
|
|
28
|
+
# ------------------------------------------------------------------
|
|
29
|
+
# 1. POST JSON
|
|
30
|
+
# ------------------------------------------------------------------
|
|
31
|
+
puts "\n=== 1. POST JSON → /echo ==="
|
|
32
|
+
resp = poster.post_json('/echo', { name: 'Alice', role: 'admin' })
|
|
33
|
+
puts "Success: #{resp.success?}, Code: #{resp.code}, Message: #{resp.message}"
|
|
34
|
+
puts "Echo body: #{resp.data.is_a?(Hash) ? resp.data['body'] : resp.data}"
|
|
35
|
+
|
|
36
|
+
# ------------------------------------------------------------------
|
|
37
|
+
# 2. POST Form(传统表单)
|
|
38
|
+
# ------------------------------------------------------------------
|
|
39
|
+
puts "\n=== 2. POST Form → /form ==="
|
|
40
|
+
resp = poster.post_form('/form', { username: 'admin', password: 'secret' })
|
|
41
|
+
puts "Success: #{resp.success?}, Code: #{resp.code}"
|
|
42
|
+
puts "Form echoed: #{resp.data.is_a?(Hash) ? resp.data['form'] : 'N/A'}"
|
|
43
|
+
|
|
44
|
+
# ------------------------------------------------------------------
|
|
45
|
+
# 3. 通用 post(自定义 Content-Type)
|
|
46
|
+
# ------------------------------------------------------------------
|
|
47
|
+
puts "\n=== 3. 通用 post → /echo (application/xml) ==="
|
|
48
|
+
resp = poster.post('/echo', '<xml><body>hello</body></xml>',
|
|
49
|
+
headers: { 'X-Request-ID' => 'uuid-1234' },
|
|
50
|
+
content_type: 'application/xml')
|
|
51
|
+
puts "Success: #{resp.success?}, Code: #{resp.code}"
|
|
52
|
+
|
|
53
|
+
# ------------------------------------------------------------------
|
|
54
|
+
# 4. POST Multipart(文件上传)
|
|
55
|
+
# ------------------------------------------------------------------
|
|
56
|
+
puts "\n=== 4. POST Multipart → /upload ==="
|
|
57
|
+
tmp_path = File.join(Dir.tmpdir, 'ice_jade_poster_demo.txt')
|
|
58
|
+
File.write(tmp_path, "Hello from IceJade Poster!\n")
|
|
59
|
+
|
|
60
|
+
if File.exist?(tmp_path)
|
|
61
|
+
resp = poster.post_multipart('/upload', { file: tmp_path, description: 'demo upload' })
|
|
62
|
+
puts "Success: #{resp.success?}, Code: #{resp.code}"
|
|
63
|
+
puts "Files: #{resp.data.is_a?(Hash) ? resp.data['files'] : 'N/A'}"
|
|
64
|
+
File.delete(tmp_path)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# ------------------------------------------------------------------
|
|
68
|
+
# 5. 错误处理(HTTP 500)
|
|
69
|
+
# ------------------------------------------------------------------
|
|
70
|
+
puts "\n=== 5. 错误处理 → /error ==="
|
|
71
|
+
resp = poster.post_json('/error', { foo: 'bar' })
|
|
72
|
+
puts "Success: #{resp.success?}, Code: #{resp.code}, Message: #{resp.message}"
|
|
73
|
+
|
|
74
|
+
# ------------------------------------------------------------------
|
|
75
|
+
# 6. 延迟响应(测试超时重试)
|
|
76
|
+
# ------------------------------------------------------------------
|
|
77
|
+
puts "\n=== 6. 延迟响应 → /delay/1 ==="
|
|
78
|
+
start = Time.now
|
|
79
|
+
resp = poster.post_json('/delay/1', { test: 'delay' })
|
|
80
|
+
elapsed = Time.now - start
|
|
81
|
+
puts "Success: #{resp.success?}, Elapsed: #{elapsed.round(2)}s"
|
|
82
|
+
|
|
83
|
+
puts "\n=== 所有测试通过 ==="
|
|
84
|
+
|
|
85
|
+
ensure
|
|
86
|
+
instance.stop
|
|
87
|
+
thread.join
|
|
88
|
+
puts "\nCradle YAML 实例已关闭。"
|
|
89
|
+
end
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# 用法示例:ice-jade gem(Quantum IM Webhook SDK)
|
|
5
|
+
|
|
6
|
+
require_relative '../lib/ice_jade'
|
|
7
|
+
|
|
8
|
+
KEY = 'your-webhook-key-here'
|
|
9
|
+
|
|
10
|
+
# ------------------------------------------------------------------
|
|
11
|
+
# 初始化客户端
|
|
12
|
+
# ------------------------------------------------------------------
|
|
13
|
+
client = IceJade::Quantum::Client.new(KEY)
|
|
14
|
+
|
|
15
|
+
# ------------------------------------------------------------------
|
|
16
|
+
# 1. 发送纯文本消息
|
|
17
|
+
# ------------------------------------------------------------------
|
|
18
|
+
puts "=== 1. 发送纯文本 ==="
|
|
19
|
+
resp = client.send_text("合肥今日天气:29度,大部分多云,降雨概率:60%")
|
|
20
|
+
puts "Success: #{resp.success?}, Message: #{resp.message}"
|
|
21
|
+
|
|
22
|
+
# @所有人
|
|
23
|
+
puts "=== 1b. @所有人 ==="
|
|
24
|
+
resp = client.send_text("紧急通知:今晚全员加班", mention_all: true)
|
|
25
|
+
puts "Success: #{resp.success?}, Message: #{resp.message}"
|
|
26
|
+
|
|
27
|
+
# @部分人(通过手机号)
|
|
28
|
+
puts "=== 1c. @部分人 ==="
|
|
29
|
+
resp = client.send_text("各位组长请查收", mentioned_mobiles: ['13800138000', '13900139000'])
|
|
30
|
+
puts "Success: #{resp.success?}, Message: #{resp.message}"
|
|
31
|
+
|
|
32
|
+
# ------------------------------------------------------------------
|
|
33
|
+
# 2. 发送图文消息(news)
|
|
34
|
+
# ------------------------------------------------------------------
|
|
35
|
+
puts "\n=== 2. 发送图文 ==="
|
|
36
|
+
resp = client.send_news(
|
|
37
|
+
"今年春节有好礼相送",
|
|
38
|
+
"https://www.baidu.com",
|
|
39
|
+
description: "点击图片领取礼品",
|
|
40
|
+
pic_url: "http://img.sccnn.com/bimg/341/34669.jpg"
|
|
41
|
+
)
|
|
42
|
+
puts "Success: #{resp.success?}, Message: #{resp.message}"
|
|
43
|
+
|
|
44
|
+
# ------------------------------------------------------------------
|
|
45
|
+
# 3. 上传并发送图片(两步走)
|
|
46
|
+
# ------------------------------------------------------------------
|
|
47
|
+
puts "\n=== 3. 上传图片 ==="
|
|
48
|
+
image_path = '/path/to/your/image.jpg'
|
|
49
|
+
|
|
50
|
+
if File.exist?(image_path)
|
|
51
|
+
upload_resp = client.upload_image(image_path)
|
|
52
|
+
puts "Upload success: #{upload_resp.success?}"
|
|
53
|
+
puts "File ID: #{upload_resp.data['id']}"
|
|
54
|
+
puts "File Name: #{upload_resp.data['name']}"
|
|
55
|
+
puts "File Size: #{upload_resp.data['size']}"
|
|
56
|
+
|
|
57
|
+
# 发送图片(需要提前知道宽高,或通过其他方式获取)
|
|
58
|
+
send_resp = client.send_image(upload_resp.data['id'], height: 1080, width: 1920)
|
|
59
|
+
puts "Send image success: #{send_resp.success?}"
|
|
60
|
+
else
|
|
61
|
+
puts "图片不存在,跳过上传示例: #{image_path}"
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# ------------------------------------------------------------------
|
|
65
|
+
# 4. 一键上传并发送图片(便捷方法)
|
|
66
|
+
# ------------------------------------------------------------------
|
|
67
|
+
puts "\n=== 4. 一键上传并发送图片 ==="
|
|
68
|
+
if File.exist?(image_path)
|
|
69
|
+
resp = client.upload_and_send_image(image_path, height: 1080, width: 1920)
|
|
70
|
+
puts "Success: #{resp.success?}, Message: #{resp.message}"
|
|
71
|
+
else
|
|
72
|
+
puts "图片不存在,跳过一键发送示例: #{image_path}"
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# ------------------------------------------------------------------
|
|
76
|
+
# 5. 上传并发送文件
|
|
77
|
+
# ------------------------------------------------------------------
|
|
78
|
+
puts "\n=== 5. 上传文件 ==="
|
|
79
|
+
file_path = '/path/to/your/document.pdf'
|
|
80
|
+
|
|
81
|
+
if File.exist?(file_path)
|
|
82
|
+
upload_resp = client.upload_file(file_path)
|
|
83
|
+
puts "Upload success: #{upload_resp.success?}"
|
|
84
|
+
puts "File ID: #{upload_resp.data['id']}"
|
|
85
|
+
|
|
86
|
+
send_resp = client.send_file(upload_resp.data['id'])
|
|
87
|
+
puts "Send file success: #{send_resp.success?}"
|
|
88
|
+
else
|
|
89
|
+
puts "文件不存在,跳过上传示例: #{file_path}"
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# ------------------------------------------------------------------
|
|
93
|
+
# 6. 一键上传并发送文件(便捷方法)
|
|
94
|
+
# ------------------------------------------------------------------
|
|
95
|
+
puts "\n=== 6. 一键上传并发送文件 ==="
|
|
96
|
+
if File.exist?(file_path)
|
|
97
|
+
resp = client.upload_and_send_file(file_path)
|
|
98
|
+
puts "Success: #{resp.success?}, Message: #{resp.message}"
|
|
99
|
+
else
|
|
100
|
+
puts "文件不存在,跳过一键发送示例: #{file_path}"
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# ------------------------------------------------------------------
|
|
104
|
+
# 7. 手动构造消息 Payload(高级)
|
|
105
|
+
# ------------------------------------------------------------------
|
|
106
|
+
puts "\n=== 7. 手动构造 Payload ==="
|
|
107
|
+
payload = IceJade::Quantum::Message.text("这是一条手动构造的消息", mentioned_mobiles: ['13800138000'])
|
|
108
|
+
puts JSON.pretty_generate(payload)
|
|
109
|
+
|
|
110
|
+
payload = IceJade::Quantum::Message.news("自定义标题", "https://example.com", pic_url: "https://example.com/pic.jpg")
|
|
111
|
+
puts JSON.pretty_generate(payload)
|
|
112
|
+
|
|
113
|
+
puts "\n示例运行完毕。"
|
data/lib/http_getter.rb
ADDED
|
@@ -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
|