Sean Cribbs avatar Sean Cribbs committed 574bb92

Add Excon-based HTTP backend.

Comments (0)

Files changed (5)

riak-client/Gemfile

 gem 'rack', '>=1.0'
 gem 'rake'
 gem 'bundler'
+gem 'excon', "~>0.2.4"
 
 if defined? JRUBY_VERSION
   gem 'json'

riak-client/lib/riak/client.rb

     autoload :HTTPBackend,    "riak/client/http_backend"
     autoload :NetHTTPBackend, "riak/client/net_http_backend"
     autoload :CurbBackend,    "riak/client/curb_backend"
+    autoload :ExconBackend,   "riak/client/excon_backend"
 
     # When using integer client IDs, the exclusive upper-bound of valid values.
     MAX_CLIENT_ID = 4294967296

riak-client/lib/riak/client/excon_backend.rb

+# Copyright 2010 Sean Cribbs, Sonian Inc., and Basho Technologies, Inc.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License");
+#    you may not use this file except in compliance with the License.
+#    You may obtain a copy of the License at
+#
+#        http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS,
+#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#    See the License for the specific language governing permissions and
+#    limitations under the License.
+require 'riak'
+
+module Riak
+  class Client
+    # An HTTP backend for Riak::Client that uses Wesley Geary's Excon
+    # HTTP library. Comforms to the Riak::Client::HTTPBackend
+    # interface.
+    class ExconBackend < HTTPBackend
+      def self.configured?
+        begin
+          require 'excon'
+          true
+        rescue LoadError
+          false
+        end
+      end
+
+      private
+      def perform(method, uri, headers, expect, data=nil, &block)
+        params = {:headers => RequestHeaders.new(headers)}
+        params[:body] = data if [:put,:post].include?(method)
+        # params[:idempotent] = (method != :post)
+        response = Excon.send(method, uri.to_s, params, &block)
+        if valid_response?(expect, response.status)
+          result = {:headers => response_headers.initialize_http_header(response.headers), :code => response.status}
+          if return_body?(method, response.status, block_given?)
+            result[:body] = response.body
+          end
+          result
+        else
+          raise FailedRequest.new(method, expect, response.status, response.headers, response.body)
+        end
+      end
+
+      # Excon uses for..in syntax to emit headers, but we still want
+      # to split them on 8KB boundaries.
+      class RequestHeaders < Riak::Util::Headers
+        alias each each_capitalized
+
+        def initialize(hash)
+          initialize_http_header(hash)
+        end
+      end
+    end
+  end
+end

riak-client/spec/riak/excon_backend_spec.rb

+# Copyright 2010 Sean Cribbs, Sonian Inc., and Basho Technologies, Inc.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License");
+#    you may not use this file except in compliance with the License.
+#    You may obtain a copy of the License at
+#
+#        http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS,
+#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#    See the License for the specific language governing permissions and
+#    limitations under the License.
+require File.expand_path("../spec_helper", File.dirname(__FILE__))
+
+begin
+  require 'excon'
+rescue LoadError
+  warn "Skipping ExconBackend specs, excon library not found."
+else
+  $mock_server = DrbMockServer
+  $mock_server.maybe_start
+
+  describe Riak::Client::ExconBackend do
+    def setup_http_mock(method, uri, options={})
+      method = method.to_s.upcase
+      uri = URI.parse(uri)
+      path = uri.path || "/"
+      query = uri.query || ""
+      status = options[:status] ? Array(options[:status]).first.to_i : 200
+      body = options[:body] || []
+      headers = options[:headers] || {}
+      headers['Content-Type'] ||= "text/plain"
+      @_mock_set = [status, headers, method, path, query, body]
+      $mock_server.expect(*@_mock_set)
+    end
+
+    before :each do
+      @client = Riak::Client.new(:port => $mock_server.port, :http_backend => :Excon) # Point to our mock
+      @backend = @client.http
+      @_mock_set = false
+    end
+
+    after :each do
+      if @_mock_set
+        $mock_server.satisfied.should be_true("Expected #{@_mock_set.inspect}, failed")
+      end
+    end
+
+    it_should_behave_like "HTTP backend"
+
+    it "should split long headers into 8KB chunks" do
+      # TODO: This doesn't actually inspect the emitted headers. How
+      # can it?
+      setup_http_mock(:put, @backend.path("/riak/","foo").to_s, :body => "ok")
+      lambda do
+        @backend.put(200, "/riak/", "foo", "body",{"Long-Header" => (["12345678"*10]*100).join(", ") })
+      end.should_not raise_error
+    end
+
+    it "should support IO objects as the request body" do
+      file = File.open(File.expand_path("../../fixtures/cat.jpg", __FILE__), 'rb')
+      lambda do
+        setup_http_mock(:put, @backend.path("/riak/","foo").to_s, :body => "ok")
+        @backend.put(200, "/riak/", "foo", file, {})
+        $mock_server.satisfied.should be_true
+      end.should_not raise_error
+      file.rewind # Have to rewind the file or we hang
+      lambda do
+        setup_http_mock(:post, @backend.path("/riak/","foo").to_s, :body => "ok")
+        @backend.post(200, "/riak/", "foo", file, {})
+        $mock_server.satisfied.should be_true
+      end.should_not raise_error
+    end
+  end
+end

riak-client/spec/support/drb_mock_server.rb

 
 module DrbMockServer
   extend self
-  def start_server
-    server = MockServer.new
-    DRb.start_service(DRBURI, server)
-    Signal.trap("HUP") { server.stop; exit }
-    DRb.thread.join
-  end
 
   def start_client
-    child_pid = fork do
-      start_server
+    # JRuby doesn't support fork
+    if defined? JRUBY_VERSION
+      @server = MockServer.new
+      at_exit { @server.stop }
+    else
+      child_pid = Process.fork do
+        start_server
+      end
+      sleep 1
+      at_exit { Process.kill("HUP", child_pid); Process.wait2 }
+      DRb.start_service
+      @server = DRbObject.new_with_uri(DRBURI)
     end
-    sleep 1
-    at_exit { Process.kill("HUP", child_pid); Process.wait2 }
-    DRb.start_service
-    @server = DRbObject.new_with_uri(DRBURI)
     true
   end
 
   def method_missing(meth, *args, &block)
     @server.send(meth, *args, &block)
   end
+
+  private
+  def start_server
+    server = MockServer.new
+    DRb.start_service(DRBURI, server)
+    Signal.trap("HUP") { server.stop; exit }
+    DRb.thread.join
+  end
 end
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.