Skip to content
Browse files

net-http-pipeline 0.0

  • Loading branch information...
0 parents commit a545e5676b40f9acbefea34b508ddeac42363037 @drbrain committed
Showing with 428 additions and 0 deletions.
  1. +23 −0 .autotest
  2. +4 −0 .gitignore
  3. +6 −0 History.txt
  4. +8 −0 Manifest.txt
  5. +59 −0 README.txt
  6. +13 −0 Rakefile
  7. +103 −0 lib/net/http/pipeline.rb
  8. +12 −0 sample/two_get.rb
  9. +200 −0 test/test_net_http_pipeline.rb
23 .autotest
@@ -0,0 +1,23 @@
+# -*- ruby -*-
+
+require 'autotest/restart'
+
+# Autotest.add_hook :initialize do |at|
+# at.extra_files << "../some/external/dependency.rb"
+#
+# at.libs << ":../some/external"
+#
+# at.add_exception 'vendor'
+#
+# at.add_mapping(/dependency.rb/) do |f, _|
+# at.files_matching(/test_.*rb$/)
+# end
+#
+# %w(TestA TestB).each do |klass|
+# at.extra_class_map[klass] = "test/test_misc.rb"
+# end
+# end
+
+# Autotest.add_hook :run_command do |at|
+# system "rake build"
+# end
4 .gitignore
@@ -0,0 +1,4 @@
+/TAGS
+/pkg
+/doc
+*.swp
6 History.txt
@@ -0,0 +1,6 @@
+=== 0.0 / 2010-12-07
+
+* 1 major enhancement
+
+ * Birthday!
+
8 Manifest.txt
@@ -0,0 +1,8 @@
+.autotest
+History.txt
+Manifest.txt
+README.txt
+Rakefile
+lib/net/http/pipeline.rb
+sample/two_get.rb
+test/test_net_http_pipeline.rb
59 README.txt
@@ -0,0 +1,59 @@
+= net-http-pipeline
+
+* http://seattlerb.rubyforge.org/net-http-pipeline
+* http://github.com/drbrain/net-http-pipeline
+
+== DESCRIPTION:
+
+An HTTP/1.1 pipelining implementation atop Net::HTTP. This is an experimental
+proof of concept.
+
+== FEATURES/PROBLEMS:
+
+* Provides HTTP/1.1 pipelining
+* Does not implement request wrangling per RFC 2616 8.1.2.2
+* Does not handle errors
+
+== SYNOPSIS:
+
+ require 'net/http/pipeline'
+
+ Net::HTTP.start 'localhost' do |http|
+ req1 = Net::HTTP::Get.new '/'
+ req2 = Net::HTTP::Get.new '/'
+
+ http.pipeline req1, req2 do |res|
+ puts res.code
+ puts res.body[0..60].inspect
+ puts
+ end
+ end
+
+== INSTALL:
+
+ gem install net-http-pipeline
+
+== LICENSE:
+
+(The MIT License)
+
+Copyright (c) 2010 Eric Hodel
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
13 Rakefile
@@ -0,0 +1,13 @@
+# -*- ruby -*-
+
+require 'rubygems'
+require 'hoe'
+
+Hoe.plugin :git
+Hoe.plugin :minitest
+
+Hoe.spec 'net-http-pipeline' do
+ developer 'Eric Hodel', 'drbrain@segment7.net'
+end
+
+# vim: syntax=Ruby
103 lib/net/http/pipeline.rb
@@ -0,0 +1,103 @@
+require 'net/http'
+
+##
+# An HTTP/1.1 pipelining implementation atop Net::HTTP. Currently this is not
+# compliant with RFC 2616 8.1.2.2.
+#
+# Pipeline allows pou to create a bunch of requests then pipeline them to an
+# HTTP/1.1 server.
+#
+# = Example
+#
+# require 'net/http/pipeline'
+#
+# Net::HTTP.start 'localhost' do |http|
+# req1 = Net::HTTP::Get.new '/'
+# req2 = Net::HTTP::Get.new '/'
+#
+# http.pipeline req1, req2 do |res|
+# puts res.code
+# puts res.body[0..60].inspect
+# puts
+# end
+# end
+
+module Net::HTTP::Pipeline
+
+ VERSION = '0.0'
+
+ ##
+ # Pipeline error class
+
+ class Error < RuntimeError
+ end
+
+ ##
+ # Pipelines +requests+ to the HTTP server yielding responses if a block is
+ # given. Returns all responses recieved.
+ #
+ # Raises an exception if the connection is not pipelining-capable or if the
+ # HTTP session has not been started.
+
+ def pipeline *requests
+ raise Error, 'pipelining requires HTTP/1.1 or newer' unless
+ @curr_http_version >= '1.1'
+ raise Error, 'Net::HTTP not started' unless started?
+
+ requests.each do |req|
+ begin_transport req
+ req.exec @socket, @curr_http_version, edit_path(req.path)
+ end
+
+ responses = []
+
+ requests.each do |req|
+ begin
+ res = Net::HTTPResponse.read_new @socket
+ end while res.kind_of? Net::HTTPContinue
+
+ res.reading_body @socket, req.response_body_permitted? do
+ responses << res
+ yield res if block_given?
+ end
+
+ pipeline_end_transport res
+ end
+
+ responses
+ end
+
+ ##
+ # Updates the HTTP version and ensures the connection has keep-alive.
+
+ def pipeline_end_transport res
+ @curr_http_version = res.http_version
+
+ if @socket.closed? then
+ D 'Conn socket closed on pipeline'
+ elsif pipeline_keep_alive? res then
+ D 'Conn pipeline keep-alive'
+ else
+ D 'Conn close on pipeline'
+ @socket.close
+ end
+ end
+
+ ##
+ # Checks for an connection close header
+
+ def pipeline_keep_alive? res
+ not res.connection_close?
+ end
+
+end
+
+class Net::HTTP
+
+ ##
+ # Adds pipeline support to Net::HTTP
+
+ include Pipeline
+
+end
+
12 sample/two_get.rb
@@ -0,0 +1,12 @@
+require 'net/http/pipeline'
+
+Net::HTTP.start 'localhost' do |http|
+ req1 = Net::HTTP::Get.new '/'
+ req2 = Net::HTTP::Get.new '/'
+
+ http.pipeline req1, req2 do |res|
+ puts res.code
+ puts res.body[0..60].inspect
+ puts
+ end
+end
200 test/test_net_http_pipeline.rb
@@ -0,0 +1,200 @@
+require "test/unit"
+require 'net/http/pipeline'
+require 'stringio'
+
+class TestNetHttpPipeline < Test::Unit::TestCase
+
+ ##
+ # Net::BufferedIO stub
+
+ class Buffer
+ attr_accessor :read_io, :write_io
+ def initialize
+ @read_io = StringIO.new
+ @write_io = StringIO.new
+ @closed = false
+ end
+
+ def close
+ @closed = true
+ end
+
+ def closed?
+ @closed
+ end
+
+ def finish
+ @write_io.rewind
+ end
+
+ def read bytes, dest = '', ignored
+ @read_io.read bytes, dest
+
+ dest
+ end
+
+ def readline
+ @read_io.readline.chomp "\r\n"
+ end
+
+ def readuntil terminator, ignored
+ @read_io.gets terminator
+ end
+
+ def start
+ @read_io.rewind
+ end
+
+ def write data
+ @write_io.write data
+ end
+ end
+
+ include Net::HTTP::Pipeline
+
+ attr_writer :started
+
+ def setup
+ @curr_http_version = '1.1'
+ @started = true
+ end
+
+ def D(*) end
+
+ def begin_transport req
+ end
+
+ def edit_path path
+ path
+ end
+
+ def http_response body, *extra_header
+ http_response = []
+ http_response << 'HTTP/1.1 200 OK'
+ http_response << "Content-Length: #{body.bytesize}"
+ http_response.push(*extra_header)
+ http_response.push nil, nil # Array chomps on #join
+
+ http_response.join("\r\n") << body
+ end
+
+ def response
+ r = Net::HTTPResponse.allocate
+ def r.http_version() Net::HTTP::HTTPVersion end
+ def r.read_body() true end
+
+ r.instance_variable_set :@header, {}
+ def r.header() @header end
+ r
+ end
+
+ def started?() @started end
+
+ def test_pipeline
+ @socket = Buffer.new
+
+ @socket.read_io.write http_response('Worked 1!')
+ @socket.read_io.write http_response('Worked 2!')
+
+ req1 = Net::HTTP::Get.new '/'
+ req2 = Net::HTTP::Get.new '/'
+
+ @socket.start
+
+ responses = pipeline req1, req2
+
+ @socket.finish
+
+ expected = <<-EXPECTED
+GET / HTTP/1.1\r
+Accept: */*\r
+User-Agent: Ruby\r
+\r
+GET / HTTP/1.1\r
+Accept: */*\r
+User-Agent: Ruby\r
+\r
+ EXPECTED
+
+ assert_equal expected, @socket.write_io.read
+ refute @socket.closed?
+
+ assert_equal 'Worked 1!', responses.first.body
+ assert_equal 'Worked 2!', responses.last.body
+ end
+
+ def test_pipeline_connection_close
+ @socket = Buffer.new
+
+ @socket.read_io.write http_response('Worked 1!', 'Connection: close')
+
+ req1 = Net::HTTP::Get.new '/'
+
+ @socket.start
+
+ responses = pipeline req1
+
+ @socket.finish
+
+ assert @socket.closed?
+ end
+
+ def test_pipeline_http_1_0
+ @curr_http_version = '1.0'
+
+ e = assert_raises Net::HTTP::Pipeline::Error do
+ pipeline
+ end
+
+ assert_equal 'pipelining requires HTTP/1.1 or newer', e.message
+ end
+
+ def test_pipeline_not_started
+ @started = false
+
+ e = assert_raises Net::HTTP::Pipeline::Error do
+ pipeline
+ end
+
+ assert_equal 'Net::HTTP not started', e.message
+ end
+
+ def test_pipeline_end_transport
+ @curr_http_version = nil
+
+ res = response
+
+ @socket = StringIO.new
+
+ pipeline_end_transport res
+
+ refute @socket.closed?
+ assert_equal '1.1', @curr_http_version
+ end
+
+ def test_pipeline_end_transport_no_keep_alive
+ @curr_http_version = nil
+
+ res = response
+ res.header['connection'] = ['close']
+
+ @socket = StringIO.new
+
+ pipeline_end_transport res
+
+ assert @socket.closed?
+ assert_equal '1.1', @curr_http_version
+ end
+
+ def test_pipeline_keep_alive_eh
+ res = response
+
+ assert pipeline_keep_alive? res
+
+ res.header['connection'] = ['close']
+
+ refute pipeline_keep_alive? res
+ end
+
+end
+

0 comments on commit a545e56

Please sign in to comment.
Something went wrong with that request. Please try again.