Module: TempmailSdk::Http

Defined in:
lib/tempmail_sdk/http.rb

Overview

共享 HTTP 客户端 所有 provider 通过此模块发起 HTTP 请求,自动应用全局配置(代理、超时、SSL 等) 基于标准库 Net::HTTP 实现,无第三方依赖

Defined Under Namespace

Classes: Response

Constant Summary collapse

CONN_RESET_ERRORS =

复用持久连接时,陈旧连接被对端关闭会抛出的错误类型。 原实现每次新建连接,故对这些错误重建连接重试一次,语义等价且幂等。

[EOFError, Errno::ECONNRESET, Errno::EPIPE,
Errno::ECONNABORTED, IOError].freeze

Class Method Summary collapse

Class Method Details

.acquire_entry(uri, config) ⇒ Object

获取(或初始化)目标主机的池条目;配置变更时整体重建连接池



138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/tempmail_sdk/http.rb', line 138

def acquire_entry(uri, config)
  key = pool_key(uri)
  @pool_mutex.synchronize do
    version = Config.config_version
    if version != @pool_config_version
      @pool.each_value { |e| safe_finish(e[:http]) }
      @pool.clear
      @pool_config_version = version
    end
    @pool[key] ||= { http: nil, mutex: Mutex.new }
  end
end

.build_http(uri, config, timeout) ⇒ Object

构造 Net::HTTP 实例(应用代理、TLS、超时)



193
194
195
196
197
198
199
200
201
202
203
204
205
# File 'lib/tempmail_sdk/http.rb', line 193

def build_http(uri, config, timeout)
  http = if config.proxy && !config.proxy.empty?
           p = URI.parse(config.proxy)
           Net::HTTP.new(uri.host, uri.port, p.host, p.port, p.user, p.password)
         else
           Net::HTTP.new(uri.host, uri.port)
         end
  http.use_ssl = (uri.scheme == "https")
  http.verify_mode = OpenSSL::SSL::VERIFY_NONE if config.insecure && http.use_ssl?
  http.open_timeout = timeout
  http.read_timeout = timeout
  http
end

.build_request(method, uri, config, headers, json, body) ⇒ Object

构造请求对象并写入请求头/请求体



208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
# File 'lib/tempmail_sdk/http.rb', line 208

def build_request(method, uri, config, headers, json, body)
  klass = method == :post ? Net::HTTP::Post : Net::HTTP::Get
  req = klass.new(uri.request_uri)

  merged = merge_spoof_headers(headers)
  config.headers&.each { |k, v| req[k] = v }
  merged.each { |k, v| req[k] = v }

  if json
    req.body = JSON.generate(json)
    req["Content-Type"] ||= "application/json"
  elsif body
    req.body = body
  end
  req
end

.get(url, headers: nil, timeout: nil) ⇒ Response

带全局配置的 GET 请求(自动注入伪造来源 IP 请求头)

Parameters:

  • url (String)
  • headers (Hash, nil) (defaults to: nil)
  • timeout (Integer, nil) (defaults to: nil)

Returns:



112
113
114
# File 'lib/tempmail_sdk/http.rb', line 112

def get(url, headers: nil, timeout: nil)
  request(:get, url, headers: headers, timeout: timeout)
end

.merge_spoof_headers(headers) ⇒ Object

将伪造来源 IP 请求头合并到用户指定的 headers 中(每次请求生成新的随机 IP)



101
102
103
104
105
# File 'lib/tempmail_sdk/http.rb', line 101

def merge_spoof_headers(headers)
  merged = spoof_ip_headers
  merged.merge!(headers) if headers
  merged
end

.perform_with_reuse(entry, uri, config, timeout, req) ⇒ Object

使用/建立持久连接执行请求;陈旧连接被对端关闭时重建一次



152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/tempmail_sdk/http.rb', line 152

def perform_with_reuse(entry, uri, config, timeout, req)
  http = entry[:http]
  if http.nil? || !http.started?
    http = start_http(uri, config, timeout)
    entry[:http] = http
  else
    http.open_timeout = timeout
    http.read_timeout = timeout
  end

  begin
    Response.new(http.request(req))
  rescue *CONN_RESET_ERRORS
    safe_finish(http)
    http = start_http(uri, config, timeout)
    entry[:http] = http
    Response.new(http.request(req))
  end
end

.pool_key(uri) ⇒ Object

池键:区分协议/主机/端口即可(配置变更整体清池)



173
174
175
# File 'lib/tempmail_sdk/http.rb', line 173

def pool_key(uri)
  "#{uri.scheme}://#{uri.host}:#{uri.port}"
end

.post(url, headers: nil, timeout: nil, json: nil, body: nil) ⇒ Object

带全局配置的 POST 请求

Parameters:

  • json (Object, nil) (defaults to: nil)

    作为 JSON 序列化的请求体

  • body (String, nil) (defaults to: nil)

    原始请求体



119
120
121
# File 'lib/tempmail_sdk/http.rb', line 119

def post(url, headers: nil, timeout: nil, json: nil, body: nil)
  request(:post, url, headers: headers, timeout: timeout, json: json, body: body)
end

.random_ipv4Object

生成随机 IPv4 地址(每段 1-254),用于伪造来源 IP 请求头



86
87
88
# File 'lib/tempmail_sdk/http.rb', line 86

def random_ipv4
  Array.new(4) { rand(1..254) }.join(".")
end

.request(method, url, headers: nil, timeout: nil, json: nil, body: nil) ⇒ Object

通用请求执行(复用持久连接池)



124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/tempmail_sdk/http.rb', line 124

def request(method, url, headers: nil, timeout: nil, json: nil, body: nil)
  config = Config.get_config
  uri = URI.parse(url)
  effective_timeout = timeout || config.timeout || 15
  req = build_request(method, uri, config, headers, json, body)

  entry = acquire_entry(uri, config)
  # 同一连接同一时刻仅允许一个请求,保证 socket 读写不交错
  entry[:mutex].synchronize do
    perform_with_reuse(entry, uri, config, effective_timeout, req)
  end
end

.safe_finish(http) ⇒ Object

安全关闭连接,忽略关闭期异常



186
187
188
189
190
# File 'lib/tempmail_sdk/http.rb', line 186

def safe_finish(http)
  http&.finish if http&.started?
rescue StandardError
  nil
end

.spoof_ip_headersObject

生成一组伪造来源 IP 的请求头,所有头使用同一个随机 IP



91
92
93
94
95
96
97
98
# File 'lib/tempmail_sdk/http.rb', line 91

def spoof_ip_headers
  ip = random_ipv4
  {
    "X-Forwarded-For" => ip,
    "X-Real-IP" => ip,
    "X-Originating-IP" => ip
  }
end

.start_http(uri, config, timeout) ⇒ Object

建立并启动持久连接(启用 keep-alive)



178
179
180
181
182
183
# File 'lib/tempmail_sdk/http.rb', line 178

def start_http(uri, config, timeout)
  http = build_http(uri, config, timeout)
  http.keep_alive_timeout = 30
  http.start
  http
end