Module: IceJade::Transport
- Included in:
- Getter::Client, Poster::Client
- Defined in:
- lib/ice_jade/transport.rb
Overview
HTTP 传输层共享逻辑
Poster::Client 与 Getter::Client 共用的底层能力: base_url 拼接、默认头合并、Net::HTTP 执行、超时指数退避重试、 状态码/JSON 解析/Response 包装。
设计为 instance 方法模块,由两个 Client 类 include 后复用, 各自只关心自己负责的 HTTP 动词与参数构造,传输细节由 Transport 统一。
Constant Summary collapse
- HTTP_STATUS_TEXT =
内置 HTTP 状态码文本(兼容各 Ruby 版本)
{ 100 => 'Continue', 101 => 'Switching Protocols', 200 => 'OK', 201 => 'Created', 202 => 'Accepted', 204 => 'No Content', 301 => 'Moved Permanently', 302 => 'Found', 304 => 'Not Modified', 400 => 'Bad Request', 401 => 'Unauthorized', 403 => 'Forbidden', 404 => 'Not Found', 405 => 'Method Not Allowed', 429 => 'Too Many Requests', 500 => 'Internal Server Error', 502 => 'Bad Gateway', 503 => 'Service Unavailable' }.freeze
Instance Attribute Summary collapse
-
#base_url ⇒ Object
readonly
Returns the value of attribute base_url.
-
#default_headers ⇒ Object
readonly
Returns the value of attribute default_headers.
-
#max_retries ⇒ Object
readonly
Returns the value of attribute max_retries.
-
#open_timeout ⇒ Object
readonly
Returns the value of attribute open_timeout.
-
#timeout ⇒ Object
readonly
Returns the value of attribute timeout.
Instance Method Summary collapse
-
#adapt_response(response) ⇒ Hash
将 Net::HTTP 响应适配为 IceJade::Response 所需的 Hash.
-
#apply_headers(req, headers) ⇒ Object
将头 Hash 写入 Net::HTTP 请求对象.
-
#build_multipart(params) ⇒ Object
multipart 边界生成与 MIME 推断 —— POST 专用,Getter 不使用, 但为保持 Transport 的自包含性一并放在这里,供 Poster::Client 复用。.
-
#build_uri(url) ⇒ URI::Generic
拼接 base_url 与相对路径,或直接解析完整 URL.
-
#execute(uri, request) ⇒ IceJade::Response
执行 Net::HTTP 请求,超时指数退避重试,异常包装为 Response.
-
#init_transport(base_url:, headers:, timeout:, open_timeout:, max_retries:) ⇒ Object
公共初始化参数解析,供两个 Client 的 #initialize 调用.
-
#merge_headers(base, extra) ⇒ Object
默认头与额外头合并(额外同名覆盖).
- #mime_type(filename) ⇒ Object
-
#normalize_headers(h) ⇒ Object
头键值统一字符串化.
Instance Attribute Details
#base_url ⇒ Object (readonly)
Returns the value of attribute base_url.
35 36 37 |
# File 'lib/ice_jade/transport.rb', line 35 def base_url @base_url end |
#default_headers ⇒ Object (readonly)
Returns the value of attribute default_headers.
35 36 37 |
# File 'lib/ice_jade/transport.rb', line 35 def default_headers @default_headers end |
#max_retries ⇒ Object (readonly)
Returns the value of attribute max_retries.
35 36 37 |
# File 'lib/ice_jade/transport.rb', line 35 def max_retries @max_retries end |
#open_timeout ⇒ Object (readonly)
Returns the value of attribute open_timeout.
35 36 37 |
# File 'lib/ice_jade/transport.rb', line 35 def open_timeout @open_timeout end |
#timeout ⇒ Object (readonly)
Returns the value of attribute timeout.
35 36 37 |
# File 'lib/ice_jade/transport.rb', line 35 def timeout @timeout end |
Instance Method Details
#adapt_response(response) ⇒ Hash
将 Net::HTTP 响应适配为 IceJade::Response 所需的 Hash
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 |
# File 'lib/ice_jade/transport.rb', line 113 def adapt_response(response) body = response.body.to_s code = response.code.to_i data = begin JSON.parse(body) rescue JSON::ParserError body end { 'ok' => code >= 200 && code < 300, 'code' => code, 'message' => HTTP_STATUS_TEXT[code] || 'Unknown', 'data' => data } end |
#apply_headers(req, headers) ⇒ Object
将头 Hash 写入 Net::HTTP 请求对象
50 51 52 53 54 |
# File 'lib/ice_jade/transport.rb', line 50 def apply_headers(req, headers) headers.each do |k, v| req[k.to_s] = v.to_s end end |
#build_multipart(params) ⇒ Object
multipart 边界生成与 MIME 推断 —— POST 专用,Getter 不使用, 但为保持 Transport 的自包含性一并放在这里,供 Poster::Client 复用。
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 |
# File 'lib/ice_jade/transport.rb', line 133 def build_multipart(params) boundary = "----IceJadeTransportBoundary#{SecureRandom.hex(16)}" body = +"".b params.each do |key, value| if value.is_a?(String) && File.exist?(value) filename = File.basename(value) file_data = File.binread(value) mime = mime_type(filename) body << "--#{boundary}\r\n" body << %(Content-Disposition: form-data; name="#{key}"; filename="#{filename}") << "\r\n" body << "Content-Type: #{mime}" << "\r\n\r\n" body << file_data.b body << "\r\n" else body << "--#{boundary}\r\n" body << %(Content-Disposition: form-data; name="#{key}") << "\r\n\r\n" body << value.to_s body << "\r\n" end end body << "--#{boundary}--\r\n" [body, boundary] end |
#build_uri(url) ⇒ URI::Generic
拼接 base_url 与相对路径,或直接解析完整 URL
41 42 43 44 45 46 47 |
# File 'lib/ice_jade/transport.rb', line 41 def build_uri(url) if base_url && !url.to_s.start_with?('http') URI.parse("#{base_url}#{url}") else URI.parse(url.to_s) end end |
#execute(uri, request) ⇒ IceJade::Response
执行 Net::HTTP 请求,超时指数退避重试,异常包装为 Response
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
# File 'lib/ice_jade/transport.rb', line 71 def execute(uri, request) http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = uri.scheme == 'https' http.open_timeout = open_timeout http.read_timeout = timeout retries = 0 begin response = http.request(request) Response.new(adapt_response(response)) rescue Net::OpenTimeout, Net::ReadTimeout => e retries += 1 if retries <= max_retries sleep(2**retries) retry end Response.new({ 'ok' => false, 'code' => 0, 'message' => "Timeout after #{max_retries} retries: #{e.}", 'data' => nil }) rescue StandardError => e Response.new({ 'ok' => false, 'code' => 0, 'message' => e., 'data' => nil }) ensure http.finish if http.started? end end |
#init_transport(base_url:, headers:, timeout:, open_timeout:, max_retries:) ⇒ Object
公共初始化参数解析,供两个 Client 的 #initialize 调用
27 28 29 30 31 32 33 |
# File 'lib/ice_jade/transport.rb', line 27 def init_transport(base_url:, headers:, timeout:, open_timeout:, max_retries:) @base_url = base_url ? base_url.to_s.chomp('/') : nil @default_headers = normalize_headers(headers) @timeout = timeout @open_timeout = open_timeout @max_retries = max_retries end |
#merge_headers(base, extra) ⇒ Object
默认头与额外头合并(额外同名覆盖)
62 63 64 |
# File 'lib/ice_jade/transport.rb', line 62 def merge_headers(base, extra) base.merge(extra) end |
#mime_type(filename) ⇒ Object
159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 |
# File 'lib/ice_jade/transport.rb', line 159 def mime_type(filename) ext = File.extname(filename).downcase case ext when '.png' then 'image/png' when '.jpg', '.jpeg' then 'image/jpeg' when '.gif' then 'image/gif' when '.webp' then 'image/webp' when '.bmp' then 'image/bmp' when '.pdf' then 'application/pdf' when '.txt' then 'text/plain' when '.json' then 'application/json' when '.zip' then 'application/zip' when '.doc' then 'application/msword' when '.docx' then 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' when '.xls' then 'application/vnd.ms-excel' when '.xlsx' then 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' else 'application/octet-stream' end end |
#normalize_headers(h) ⇒ Object
头键值统一字符串化
57 58 59 |
# File 'lib/ice_jade/transport.rb', line 57 def normalize_headers(h) h.transform_keys(&:to_s).transform_values(&:to_s) end |