Commit 602cf16
Changed files (2)
lib
net
hippie
src
lib/net/hippie/rust_connection.rb
@@ -4,13 +4,40 @@ require_relative 'rust_backend'
module Net
module Hippie
- # Rust-powered connection that mimics the Ruby Connection interface
+ # 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
@@ -18,13 +45,21 @@ module Net
def run(request)
url = build_url_for(request.path)
- headers = {} # Simplified for now
+ 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)
- RustBackend::ResponseAdapter.new(rust_response)
+ 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)
@@ -52,6 +87,66 @@ module Net
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/
src/lib.rs
@@ -1,4 +1,4 @@
-use magnus::{define_module, function, method, Error, Module, Object, Value, class};
+use magnus::{define_module, function, method, Error, Module, Object, Value, class, RHash, TryConvert};
use magnus::value::ReprValue;
use reqwest::{Client, Method, Response};
use std::collections::HashMap;
@@ -59,7 +59,7 @@ impl RustClient {
&self,
method_str: String,
url: String,
- _headers: Value, // Simplified - ignore headers for now
+ headers: Value,
body: String,
) -> Result<RustResponse, Error> {
let method = match method_str.to_uppercase().as_str() {
@@ -74,6 +74,15 @@ impl RustClient {
self.runtime.block_on(async {
let mut request_builder = self.client.request(method, &url);
+ // Add headers if provided
+ if let Ok(headers_hash) = RHash::from_value(headers) {
+ for (key, value) in headers_hash {
+ if let (Ok(key_str), Ok(value_str)) = (String::try_convert(key), String::try_convert(value)) {
+ request_builder = request_builder.header(&key_str, &value_str);
+ }
+ }
+ }
+
// Add body if not empty
if !body.is_empty() {
request_builder = request_builder.body(body);