Commit 4072ded

mo khan <mo@mokhan.ca>
2025-10-08 16:42:46
feat: add support for streaming responses
1 parent 58d4e9b
lib/net/hippie/client.rb
@@ -26,13 +26,22 @@ module Net
 
       def execute(uri, request, limit: follow_redirects, &block)
         connection = connection_for(uri)
-        response = connection.run(request)
-        if limit.positive? && response.is_a?(Net::HTTPRedirection)
-          url = connection.build_url_for(response['location'])
-          request = request_for(Net::HTTP::Get, url)
-          execute(url, request, limit: limit - 1, &block)
+        if block_given?
+          if block.arity == 2
+            response = connection.run(request)
+            yield(request, response)
+          else
+            connection.run(request, &block)
+          end
         else
-          block_given? ? yield(request, response) : response
+          response = connection.run(request)
+          if limit.positive? && response.is_a?(Net::HTTPRedirection)
+            url = connection.build_url_for(response['location'])
+            request = request_for(Net::HTTP::Get, url)
+            execute(url, request, limit: limit - 1)
+          else
+            response
+          end
         end
       end
 
lib/net/hippie/connection.rb
@@ -15,8 +15,14 @@ module Net
         @http = http
       end
 
-      def run(request)
-        @http.request(request)
+      def run(request, &block)
+        if block_given?
+          @http.request(request) do |response|
+            yield response
+          end
+        else
+          @http.request(request)
+        end
       end
 
       def build_url_for(path)
lib/net/hippie/version.rb
@@ -2,6 +2,6 @@
 
 module Net
   module Hippie
-    VERSION = '1.3.0'
+    VERSION = '1.4.0'
   end
 end
test/net/client_test.rb
@@ -317,4 +317,42 @@ class ClientTest < Minitest::Test
       end
     end
   end
+
+  def test_post_with_streaming
+    chunks = ["chunk1", "chunk2", "chunk3"]
+    response_body = chunks.join
+
+    WebMock.stub_request(:post, "https://example.org/stream")
+      .to_return(status: 200, body: response_body)
+
+    uri = URI.parse('https://example.org/stream')
+    received_chunks = []
+
+    subject.post(uri, body: { test: true }) do |response|
+      response.read_body do |chunk|
+        received_chunks << chunk
+      end
+    end
+
+    assert_equal chunks.join, received_chunks.join
+  end
+
+  def test_get_with_streaming
+    chunks = ["data1\n", "data2\n", "data3\n"]
+    response_body = chunks.join
+
+    WebMock.stub_request(:get, "https://example.org/stream")
+      .to_return(status: 200, body: response_body)
+
+    uri = URI.parse('https://example.org/stream')
+    received_data = []
+
+    subject.get(uri) do |response|
+      response.read_body do |chunk|
+        received_data << chunk
+      end
+    end
+
+    assert_equal chunks.join, received_data.join
+  end
 end
CHANGELOG.md
@@ -8,6 +8,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ## [Unreleased]
 
+## [1.4.0] - 2025-10-08
+### Added
+- Streaming response support via block parameter
+- Backward compatible with existing block API (arity-based detection)
+
 ## [1.3.0] - 2025-04-30
 ### Changed
 - Ruby 2.3+ required
@@ -95,7 +100,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - with\_retry.
 - authorization header helpers
 
-[Unreleased]: https://github.com/xlgmokha/net-hippie/compare/v1.3.0...HEAD
+[Unreleased]: https://github.com/xlgmokha/net-hippie/compare/v1.4.0...HEAD
+[1.4.0]: https://github.com/xlgmokha/net-hippie/compare/v1.3.0...v1.4.0
 [1.3.0]: https://github.com/xlgmokha/net-hippie/compare/v1.2.0...v1.3.0
 [1.2.0]: https://github.com/xlgmokha/net-hippie/compare/v1.1.1...v1.2.0
 [1.1.1]: https://github.com/xlgmokha/net-hippie/compare/v1.1.0...v1.1.1
README.md
@@ -90,6 +90,40 @@ headers = { 'Authorization' => Net::Hippie.bearer_auth('token') }
 Net::Hippie.get('https://www.example.org', headers: headers)
 ```
 
+### Streaming Responses
+
+Net::Hippie supports streaming responses by passing a block that accepts the response object:
+
+```ruby
+Net::Hippie.post('https://api.example.com/stream', body: { prompt: 'Hello' }) do |response|
+  response.read_body do |chunk|
+    print chunk
+  end
+end
+```
+
+This is useful for Server-Sent Events (SSE) or other streaming APIs:
+
+```ruby
+client = Net::Hippie::Client.new
+client.post('https://api.openai.com/v1/chat/completions',
+  headers: {
+    'Authorization' => Net::Hippie.bearer_auth(ENV['OPENAI_API_KEY']),
+    'Content-Type' => 'application/json'
+  },
+  body: { model: 'gpt-4', messages: [{ role: 'user', content: 'Hi' }], stream: true }
+) do |response|
+  buffer = ""
+  response.read_body do |chunk|
+    buffer += chunk
+    while (line = buffer.slice!(/.*\n/))
+      next if line.strip.empty?
+      puts line if line.start_with?('data: ')
+    end
+  end
+end
+```
+
 ## Development
 
 After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/test` to run the tests.