1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
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
158
159
160
161
162
163
164
165
166
|
# frozen_string_literal: true
module Net
module Hippie
# Rust-powered HTTP connection with debug logging support.
#
# RustConnection provides a high-performance HTTP client using Rust's reqwest library
# while maintaining full compatibility with the Ruby backend interface, including
# comprehensive debug logging that matches Net::HTTP's set_debug_output behavior.
#
# == Debug Logging
#
# When a logger is provided, RustConnection outputs detailed HTTP transaction logs
# in the same format as Net::HTTP:
#
# logger = File.open('http_debug.log', 'w')
# connection = RustConnection.new('https', 'api.example.com', 443, logger: logger)
#
# # Output format:
# # -> "GET https://api.example.com/users HTTP/1.1"
# # -> "accept: application/json"
# # -> "user-agent: net/hippie 2.0.0"
# # -> ""
# # <- "HTTP/1.1 200"
# # <- "content-type: application/json"
# # <- ""
# # <- "{\"users\":[...]}"
#
# @since 2.0.0
# @see RubyConnection The Ruby/net-http implementation
# @see Connection The backend abstraction layer
class RustConnection
def initialize(scheme, host, port, options = {})
@scheme = scheme
@host = host
@port = port
@options = options
@logger = options[:logger]
# Create the Rust client (simplified version for now)
@rust_client = Net::Hippie::RustClient.new
end
def run(request)
url = build_url_for(request.path)
headers = extract_headers(request)
body = request.body || ''
method = extract_method(request)
# Debug logging (mimics Net::HTTP's set_debug_output behavior)
log_request(method, url, headers, body) if @logger
begin
rust_response = @rust_client.public_send(method.downcase, url, headers, body)
response = RustBackend::ResponseAdapter.new(rust_response)
# Debug log response
log_response(response) if @logger
response
rescue => e
# Map Rust errors to Ruby equivalents
raise map_rust_error(e)
end
end
def build_url_for(path)
return path if path.start_with?('http')
port_suffix = (@port == 80 && @scheme == 'http') || (@port == 443 && @scheme == 'https') ? '' : ":#{@port}"
"#{@scheme}://#{@host}#{port_suffix}#{path}"
end
private
def extract_headers(request)
headers = {}
request.each_header do |key, value|
headers[key] = value
end
headers
end
def extract_method(request)
request.class.name.split('::').last.sub('HTTP', '').downcase
end
# Logs HTTP request details in Net::HTTP debug format.
#
# Outputs request line, headers, and body using the same format as
# Net::HTTP's set_debug_output for consistent debugging experience.
#
# @param method [String] HTTP method (GET, POST, etc.)
# @param url [String] Complete request URL
# @param headers [Hash] Request headers
# @param body [String] Request body content
# @since 2.0.0
def log_request(method, url, headers, body)
# Format similar to Net::HTTP's debug output
@logger << "-> \"#{method.upcase} #{url} HTTP/1.1\"\n"
# Log headers
headers.each do |key, value|
@logger << "-> \"#{key.downcase}: #{value}\"\n"
end
@logger << "-> \"\"\n" # Empty line
# Log body if present
if body && !body.empty?
@logger << "-> \"#{body}\"\n"
end
@logger.flush if @logger.respond_to?(:flush)
end
# Logs HTTP response details in Net::HTTP debug format.
#
# Outputs response status, headers, and body (truncated if large) using
# the same format as Net::HTTP's set_debug_output.
#
# @param response [RustBackend::ResponseAdapter] HTTP response object
# @since 2.0.0
def log_response(response)
# Format similar to Net::HTTP's debug output
@logger << "<- \"HTTP/1.1 #{response.code}\"\n"
# Log some common response headers if available
%w[content-type content-length location server date].each do |header|
value = response[header]
if value
@logger << "<- \"#{header}: #{value}\"\n"
end
end
@logger << "<- \"\"\n" # Empty line
# Log response body (truncated if too long)
body = response.body
if body && !body.empty?
display_body = body.length > 1000 ? "#{body[0...1000]}...[truncated]" : body
@logger << "<- \"#{display_body}\"\n"
end
@logger.flush if @logger.respond_to?(:flush)
end
def map_rust_error(error)
case error.message
when /Net::ReadTimeout/
Net::ReadTimeout.new
when /Net::OpenTimeout/
Net::OpenTimeout.new
when /Errno::ECONNREFUSED/
Errno::ECONNREFUSED.new
when /Errno::ECONNRESET/
Errno::ECONNRESET.new
when /timeout/i
Net::ReadTimeout.new
else
error
end
end
end
end
end
|