excon 0.0.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of excon might be problematic. Click here for more details.

data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
data/README.rdoc ADDED
@@ -0,0 +1,28 @@
1
+ = excon
2
+
3
+ Http(s) EXtended CONnections
4
+
5
+ == Copyright
6
+
7
+ (The MIT License)
8
+
9
+ Copyright (c) 2009 {geemus (Wesley Beary)}[http://github.com/geemus]
10
+
11
+ Permission is hereby granted, free of charge, to any person obtaining
12
+ a copy of this software and associated documentation files (the
13
+ "Software"), to deal in the Software without restriction, including
14
+ without limitation the rights to use, copy, modify, merge, publish,
15
+ distribute, sublicense, and/or sell copies of the Software, and to
16
+ permit persons to whom the Software is furnished to do so, subject to
17
+ the following conditions:
18
+
19
+ The above copyright notice and this permission notice shall be
20
+ included in all copies or substantial portions of the Software.
21
+
22
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,55 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "excon"
8
+ gem.summary = %Q{EXtended http(s) CONnections}
9
+ gem.description = %Q{speed, persistence, http(s)}
10
+ gem.email = "wbeary@engineyard.com"
11
+ gem.homepage = "http://github.com/geemus/excon"
12
+ gem.authors = ["Wesley Beary"]
13
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
14
+ end
15
+ rescue LoadError
16
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
17
+ end
18
+
19
+ require 'rake/testtask'
20
+ Rake::TestTask.new(:test) do |test|
21
+ test.libs << 'lib' << 'test'
22
+ test.pattern = 'test/**/*_test.rb'
23
+ test.verbose = true
24
+ end
25
+
26
+ begin
27
+ require 'rcov/rcovtask'
28
+ Rcov::RcovTask.new do |test|
29
+ test.libs << 'test'
30
+ test.pattern = 'test/**/*_test.rb'
31
+ test.verbose = true
32
+ end
33
+ rescue LoadError
34
+ task :rcov do
35
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
36
+ end
37
+ end
38
+
39
+ task :test => :check_dependencies
40
+
41
+ task :default => :test
42
+
43
+ require 'rake/rdoctask'
44
+ Rake::RDocTask.new do |rdoc|
45
+ if File.exist?('VERSION')
46
+ version = File.read('VERSION')
47
+ else
48
+ version = ""
49
+ end
50
+
51
+ rdoc.rdoc_dir = 'rdoc'
52
+ rdoc.title = "excon #{version}"
53
+ rdoc.rdoc_files.include('README*')
54
+ rdoc.rdoc_files.include('lib/**/*.rb')
55
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
@@ -0,0 +1,39 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'lib/excon')
2
+
3
+ require 'benchmark'
4
+ require 'net/http'
5
+ require 'open-uri'
6
+
7
+ COUNT = 100
8
+ data = "Content-Length: 100"
9
+ Benchmark.bmbm(25) do |bench|
10
+ bench.report('excon') do
11
+ COUNT.times do
12
+ Excon.new('http://www.google.com').request(:method => 'GET', :path => '/')
13
+ end
14
+ end
15
+ bench.report('excon (persistent)') do
16
+ excon = Excon.new('http://www.google.com')
17
+ COUNT.times do
18
+ excon.request(:method => 'GET', :path => '/')
19
+ end
20
+ end
21
+ bench.report('net/http') do
22
+ COUNT.times do
23
+ # Net::HTTP.get('www.google.com', '/')
24
+ Net::HTTP.start('www.google.com') {|http| http.get('/') }
25
+ end
26
+ end
27
+ bench.report('net/http (persistent)') do
28
+ Net::HTTP.start('www.google.com', 80) do |http|
29
+ COUNT.times do
30
+ http.get('/')
31
+ end
32
+ end
33
+ end
34
+ bench.report('open-uri') do
35
+ COUNT.times do
36
+ open('http://www.google.com/').read
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,29 @@
1
+ require 'benchmark'
2
+
3
+ COUNT = 1_000_000
4
+ data = "Content-Length: 100"
5
+ Benchmark.bmbm(25) do |bench|
6
+ bench.report('regex') do
7
+ COUNT.times do
8
+ header = data.match(/(.*):\s(.*)/)
9
+ "#{header[1]}: #{header[2]}"
10
+ end
11
+ end
12
+ bench.report('split') do
13
+ COUNT.times do
14
+ header = data.split(': ')
15
+ "#{header[0]}: #{header[1]}"
16
+ end
17
+ end
18
+ end
19
+
20
+
21
+
22
+ # Rehearsal ------------------------------------------------------------
23
+ # regex 4.270000 0.010000 4.280000 ( 4.294186)
24
+ # split 3.870000 0.000000 3.870000 ( 3.885645)
25
+ # --------------------------------------------------- total: 8.150000sec
26
+ #
27
+ # user system total real
28
+ # regex 4.260000 0.010000 4.270000 ( 4.284764)
29
+ # split 3.860000 0.010000 3.870000 ( 3.882795)
@@ -0,0 +1,69 @@
1
+ require 'benchmark'
2
+
3
+ COUNT = 1_000_000
4
+ data = "Content-Length: 100\r\n"
5
+ Benchmark.bmbm(25) do |bench|
6
+ bench.report('chomp') do
7
+ COUNT.times do
8
+ data = "Content-Length: 100\r\n"
9
+ data.chomp
10
+ end
11
+ end
12
+ bench.report('chomp!') do
13
+ COUNT.times do
14
+ data = "Content-Length: 100\r\n"
15
+ data.chomp!
16
+ end
17
+ end
18
+ bench.report('chop') do
19
+ COUNT.times do
20
+ data = "Content-Length: 100\r\n"
21
+ data.chop
22
+ end
23
+ end
24
+ bench.report('chop!') do
25
+ COUNT.times do
26
+ data = "Content-Length: 100\r\n"
27
+ data.chop!
28
+ end
29
+ end
30
+ bench.report('strip') do
31
+ COUNT.times do
32
+ data = "Content-Length: 100\r\n"
33
+ data.strip
34
+ end
35
+ end
36
+ bench.report('strip!') do
37
+ COUNT.times do
38
+ data = "Content-Length: 100\r\n"
39
+ data.strip!
40
+ end
41
+ end
42
+ bench.report('index') do
43
+ COUNT.times do
44
+ data = "Content-Length: 100\r\n"
45
+ data[0..-3]
46
+ end
47
+ end
48
+ end
49
+
50
+
51
+
52
+ # Rehearsal ------------------------------------------------------------
53
+ # chomp 0.640000 0.000000 0.640000 ( 0.644043)
54
+ # chomp! 0.530000 0.000000 0.530000 ( 0.531415)
55
+ # chop 0.620000 0.000000 0.620000 ( 0.624321)
56
+ # chop! 0.500000 0.000000 0.500000 ( 0.509146)
57
+ # strip 0.640000 0.000000 0.640000 ( 0.638785)
58
+ # strip! 0.530000 0.000000 0.530000 ( 0.532196)
59
+ # index 0.740000 0.000000 0.740000 ( 0.745742)
60
+ # --------------------------------------------------- total: 4.200000sec
61
+ #
62
+ # user system total real
63
+ # chomp 0.640000 0.010000 0.650000 ( 0.647287)
64
+ # chomp! 0.530000 0.000000 0.530000 ( 0.532868)
65
+ # chop 0.630000 0.000000 0.630000 ( 0.628236)
66
+ # chop! 0.520000 0.000000 0.520000 ( 0.522950)
67
+ # strip 0.640000 0.000000 0.640000 ( 0.646328)
68
+ # strip! 0.520000 0.000000 0.520000 ( 0.532715)
69
+ # index 0.740000 0.010000 0.750000 ( 0.771277)
data/lib/excon.rb ADDED
@@ -0,0 +1,21 @@
1
+ __DIR__ = File.dirname(__FILE__)
2
+
3
+ $LOAD_PATH.unshift __DIR__ unless
4
+ $LOAD_PATH.include?(__DIR__) ||
5
+ $LOAD_PATH.include?(File.expand_path(__DIR__))
6
+
7
+ require 'openssl'
8
+ require 'socket'
9
+ require 'uri'
10
+
11
+ require 'excon/connection'
12
+ require 'excon/errors'
13
+ require 'excon/response'
14
+
15
+ module Excon
16
+
17
+ def self.new(url)
18
+ Excon::Connection.new(url)
19
+ end
20
+
21
+ end
@@ -0,0 +1,111 @@
1
+ module Excon
2
+ class Connection
3
+
4
+ unless defined?(:CHUNK_SIZE)
5
+ CHUNK_SIZE = 1048576 # 1 megabyte
6
+ end
7
+
8
+ def initialize(url)
9
+ @uri = URI.parse(url)
10
+ end
11
+
12
+ def request(params)
13
+ begin
14
+ params[:path] ||= ''
15
+ unless params[:path][0..0] == '/'
16
+ params[:path] = "/#{params[:path]}"
17
+ end
18
+ if params[:query] && !params[:query].empty?
19
+ params[:path] << "?#{params[:query]}"
20
+ end
21
+ request = "#{params[:method]} #{params[:path]} HTTP/1.1\r\n"
22
+ params[:headers] ||= {}
23
+ params[:headers]['Host'] = params[:host] || @uri.host
24
+ if params[:body] && !params[:headers]['Content-Length']
25
+ params[:headers]['Content-Length'] = params[:body].length
26
+ end
27
+ for key, value in params[:headers]
28
+ request << "#{key}: #{value}\r\n"
29
+ end
30
+ request << "\r\n"
31
+ connection.write(request)
32
+
33
+ if params[:body]
34
+ if params[:body].is_a?(String)
35
+ connection.write(params[:body])
36
+ else
37
+ while chunk = params[:body].read(CHUNK_SIZE)
38
+ connection.write(chunk)
39
+ end
40
+ end
41
+ end
42
+
43
+ response = Excon::Response.new
44
+ response.status = connection.readline[9..11].to_i
45
+ while true
46
+ data = connection.readline.chop!
47
+ unless data.empty?
48
+ header = data.split(': ')
49
+ response.headers[header[0]] = header[1]
50
+ else
51
+ break
52
+ end
53
+ end
54
+
55
+ unless params[:method] == 'HEAD'
56
+ unless params[:block]
57
+ response.body = ''
58
+ params[:block] = lambda { |chunk| response.body << chunk }
59
+ end
60
+
61
+ if response.headers['Content-Length']
62
+ remaining = response.headers['Content-Length'].to_i
63
+ while remaining > 0
64
+ params[:block].call(connection.read([CHUNK_SIZE, remaining].min))
65
+ remaining -= CHUNK_SIZE
66
+ end
67
+ elsif response.headers['Transfer-Encoding'] == 'chunked'
68
+ while true
69
+ chunk_size = connection.readline.chomp!.to_i(16)
70
+ chunk = connection.read(chunk_size + 2).chop! # 2 == "/r/n".length
71
+ if chunk_size > 0
72
+ params[:block].call(chunk)
73
+ else
74
+ break
75
+ end
76
+ end
77
+ elsif response.headers['Connection'] == 'close'
78
+ params[:block].call(connection.read)
79
+ @connection = nil
80
+ end
81
+ end
82
+ rescue => connection_error
83
+ @connection = nil
84
+ raise(connection_error)
85
+ end
86
+
87
+ if params[:expects] && ![*params[:expects]].include?(response.status)
88
+ raise(Excon::Errors.status_error(params[:expects], response.status, response))
89
+ else
90
+ response
91
+ end
92
+ end
93
+
94
+ private
95
+
96
+ def connection
97
+ if !@connection || @connection.closed?
98
+ @connection = TCPSocket.open(@uri.host, @uri.port)
99
+ if @uri.scheme == 'https'
100
+ @ssl_context = OpenSSL::SSL::SSLContext.new
101
+ @ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE
102
+ @connection = OpenSSL::SSL::SSLSocket.new(@connection, @ssl_context)
103
+ @connection.sync_close = true
104
+ @connection.connect
105
+ end
106
+ end
107
+ @connection
108
+ end
109
+
110
+ end
111
+ end
@@ -0,0 +1,92 @@
1
+ module Excon
2
+
3
+ module Errors
4
+ class Continue < StandardError; end # 100
5
+ class SwitchingProtocols < StandardError; end # 101
6
+ class OK < StandardError; end # 200
7
+ class Created < StandardError; end # 201
8
+ class Accepted < StandardError; end # 202
9
+ class NonAuthoritativeInformation < StandardError; end # 203
10
+ class NoContent < StandardError; end # 204
11
+ class ResetContent < StandardError; end # 205
12
+ class PartialContent < StandardError; end # 206
13
+ class MultipleChoices < StandardError; end # 300
14
+ class MovedPermanently < StandardError; end # 301
15
+ class Found < StandardError; end # 302
16
+ class SeeOther < StandardError; end # 303
17
+ class NotModified < StandardError; end # 304
18
+ class UseProxy < StandardError; end # 305
19
+ class TemporaryRedirect < StandardError; end # 307
20
+ class BadRequest < StandardError; end # 400
21
+ class Unauthorized < StandardError; end # 401
22
+ class PaymentRequired < StandardError; end # 402
23
+ class Forbidden < StandardError; end # 403
24
+ class NotFound < StandardError; end # 404
25
+ class MethodNotAllowed < StandardError; end # 405
26
+ class NotAcceptable < StandardError; end # 406
27
+ class ProxyAuthenticationRequired < StandardError; end # 407
28
+ class RequestTimeout < StandardError; end # 408
29
+ class Conflict < StandardError; end # 409
30
+ class Gone < StandardError; end # 410
31
+ class LengthRequired < StandardError; end # 411
32
+ class PreconditionFailed < StandardError; end # 412
33
+ class RequestEntityTooLarge < StandardError; end # 412
34
+ class RequestURITooLong < StandardError; end # 414
35
+ class UnsupportedMediaType < StandardError; end # 415
36
+ class RequestedRangeNotSatisfiable < StandardError; end # 416
37
+ class ExpectationFailed < StandardError; end # 417
38
+ class InternalServerError < StandardError; end # 500
39
+ class NotImplemented < StandardError; end # 501
40
+ class BadGateway < StandardError; end # 502
41
+ class ServiceUnavailable < StandardError; end # 503
42
+ class GatewayTimeout < StandardError; end # 504
43
+
44
+ # Messages for nicer exceptions, from rfc2616
45
+ def self.status_error(expected, actual, response)
46
+ @errors ||= {
47
+ 100 => [Excon::Errors::Continue, 'Continue'],
48
+ 101 => [Excon::Errors::SwitchingProtocols, 'Switching Protocols'],
49
+ 200 => [Excon::Errors::OK, 'OK'],
50
+ 201 => [Excon::Errors::Created, 'Created'],
51
+ 202 => [Excon::Errors::Accepted, 'Accepted'],
52
+ 203 => [Excon::Errors::NonAuthoritativeInformation, 'Non-Authoritative Information'],
53
+ 204 => [Excon::Errors::NoContent, 'No Content'],
54
+ 205 => [Excon::Errors::ResetContent, 'Reset Content'],
55
+ 206 => [Excon::Errors::PartialContent, 'Partial Content'],
56
+ 300 => [Excon::Errors::MultipleChoices, 'Multiple Choices'],
57
+ 301 => [Excon::Errors::MovedPermanently, 'Moved Permanently'],
58
+ 302 => [Excon::Errors::Found, 'Found'],
59
+ 303 => [Excon::Errors::SeeOther, 'See Other'],
60
+ 304 => [Excon::Errors::NotModified, 'Not Modified'],
61
+ 305 => [Excon::Errors::UseProxy, 'Use Proxy'],
62
+ 307 => [Excon::Errors::TemporaryRedirect, 'Temporary Redirect'],
63
+ 400 => [Excon::Errors::BadRequest, 'Bad Request'],
64
+ 401 => [Excon::Errors::Unauthorized, 'Unauthorized'],
65
+ 402 => [Excon::Errors::PaymentRequired, 'Payment Required'],
66
+ 403 => [Excon::Errors::Forbidden, 'Forbidden'],
67
+ 404 => [Excon::Errors::NotFound, 'Not Found'],
68
+ 405 => [Excon::Errors::MethodNotAllowed, 'Method Not Allowed'],
69
+ 406 => [Excon::Errors::NotAcceptable, 'Not Acceptable'],
70
+ 407 => [Excon::Errors::ProxyAuthenticationRequired, 'Proxy Authentication Required'],
71
+ 408 => [Excon::Errors::RequestTimeout, 'Request Timeout'],
72
+ 409 => [Excon::Errors::Conflict, 'Conflict'],
73
+ 410 => [Excon::Errors::Gone, 'Gone'],
74
+ 411 => [Excon::Errors::LengthRequired, 'Length Required'],
75
+ 412 => [Excon::Errors::PreconditionFailed, 'Precondition Failed'],
76
+ 413 => [Excon::Errors::RequestEntityTooLarge, 'Request Entity Too Large'],
77
+ 414 => [Excon::Errors::RequestURITooLong, 'Request-URI Too Long'],
78
+ 415 => [Excon::Errors::UnsupportedMediaType, 'Unsupported Media Type'],
79
+ 416 => [Excon::Errors::RequestedRangeNotSatisfiable, 'Request Range Not Satisfiable'],
80
+ 417 => [Excon::Errors::ExpectationFailed, 'Expectation Failed'],
81
+ 500 => [Excon::Errors::InternalServerError, 'InternalServerError'],
82
+ 501 => [Excon::Errors::NotImplemented, 'Not Implemented'],
83
+ 502 => [Excon::Errors::BadGateway, 'Bad Gateway'],
84
+ 503 => [Excon::Errors::ServiceUnavailable, 'Service Unavailable'],
85
+ 504 => [Excon::Errors::GatewayTimeout, 'Gateway Timeout']
86
+ }
87
+ error = @errors[actual]
88
+ error[0].new("Expected(#{expected.inspect}) <=> Actual(#{actual} #{error[1]}): #{response.body}")
89
+ end
90
+
91
+ end
92
+ end
@@ -0,0 +1,12 @@
1
+ module Excon
2
+ class Response
3
+
4
+ attr_accessor :body, :headers, :status
5
+
6
+ def initialize
7
+ @body = ''
8
+ @headers = {}
9
+ end
10
+
11
+ end
12
+ end
data/test/test.rb ADDED
@@ -0,0 +1,15 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'lib/excon')
2
+
3
+ x = Excon.new('http://www.google.com')
4
+
5
+ 10.times do
6
+ p x.request(
7
+ :method => 'GET',
8
+ :path => '/'
9
+ )
10
+ end
11
+
12
+ # require 'open-uri'
13
+ # 10.times do
14
+ # p open('http://www.google.com').read
15
+ # end
metadata ADDED
@@ -0,0 +1,67 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: excon
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Wesley Beary
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-10-31 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: speed, persistence, http(s)
17
+ email: wbeary@engineyard.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README.rdoc
24
+ files:
25
+ - .document
26
+ - .gitignore
27
+ - README.rdoc
28
+ - Rakefile
29
+ - VERSION
30
+ - benchmarks/excon_vs.rb
31
+ - benchmarks/headers_split_vs_match.rb
32
+ - benchmarks/strip_newline.rb
33
+ - lib/excon.rb
34
+ - lib/excon/connection.rb
35
+ - lib/excon/errors.rb
36
+ - lib/excon/response.rb
37
+ - test/test.rb
38
+ has_rdoc: true
39
+ homepage: http://github.com/geemus/excon
40
+ licenses: []
41
+
42
+ post_install_message:
43
+ rdoc_options:
44
+ - --charset=UTF-8
45
+ require_paths:
46
+ - lib
47
+ required_ruby_version: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ version: "0"
52
+ version:
53
+ required_rubygems_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: "0"
58
+ version:
59
+ requirements: []
60
+
61
+ rubyforge_project:
62
+ rubygems_version: 1.3.5
63
+ signing_key:
64
+ specification_version: 3
65
+ summary: EXtended http(s) CONnections
66
+ test_files:
67
+ - test/test.rb