http 0.1.0 → 0.2.0

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

Potentially problematic release.


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

@@ -0,0 +1,42 @@
1
+ module Http
2
+ class Request
3
+ # Method is given as a lowercase symbol e.g. :get, :post
4
+ attr_reader :method
5
+
6
+ # "Request URI" as per RFC 2616
7
+ # http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html
8
+ attr_reader :uri
9
+ attr_reader :headers, :body, :version
10
+
11
+ # :nodoc:
12
+ def initialize(method, uri, headers = {}, body = nil, version = "1.1")
13
+ @method = method.to_s.downcase.to_sym
14
+ raise UnsupportedMethodError, "unknown method: #{method}" unless METHODS.include? @method
15
+
16
+ @uri = uri.is_a?(URI) ? uri : URI(uri.to_s)
17
+
18
+ @headers = {}
19
+ headers.each do |name, value|
20
+ name = name.to_s
21
+ key = name[CANONICAL_HEADER]
22
+ key ||= Http.canonicalize_header(name)
23
+ @headers[key] = value
24
+ end
25
+
26
+ @body, @version = body, version
27
+ end
28
+
29
+ # Obtain the given header
30
+ def [](header)
31
+ @headers[Http.canonicalize_header(header)]
32
+ end
33
+
34
+ # Create a Net::HTTP request from this request
35
+ def to_net_http_request
36
+ request_class = Net::HTTP.const_get(@method.to_s.capitalize)
37
+ request = request_class.new(@uri.request_uri, @headers)
38
+ request.body = @body
39
+ request
40
+ end
41
+ end
42
+ end
@@ -1,5 +1,62 @@
1
1
  module Http
2
2
  class Response
3
+ STATUS_CODES = {
4
+ 100 => 'Continue',
5
+ 101 => 'Switching Protocols',
6
+ 102 => 'Processing',
7
+ 200 => 'OK',
8
+ 201 => 'Created',
9
+ 202 => 'Accepted',
10
+ 203 => 'Non-Authoritative Information',
11
+ 204 => 'No Content',
12
+ 205 => 'Reset Content',
13
+ 206 => 'Partial Content',
14
+ 207 => 'Multi-Status',
15
+ 226 => 'IM Used',
16
+ 300 => 'Multiple Choices',
17
+ 301 => 'Moved Permanently',
18
+ 302 => 'Found',
19
+ 303 => 'See Other',
20
+ 304 => 'Not Modified',
21
+ 305 => 'Use Proxy',
22
+ 306 => 'Reserved',
23
+ 307 => 'Temporary Redirect',
24
+ 400 => 'Bad Request',
25
+ 401 => 'Unauthorized',
26
+ 402 => 'Payment Required',
27
+ 403 => 'Forbidden',
28
+ 404 => 'Not Found',
29
+ 405 => 'Method Not Allowed',
30
+ 406 => 'Not Acceptable',
31
+ 407 => 'Proxy Authentication Required',
32
+ 408 => 'Request Timeout',
33
+ 409 => 'Conflict',
34
+ 410 => 'Gone',
35
+ 411 => 'Length Required',
36
+ 412 => 'Precondition Failed',
37
+ 413 => 'Request Entity Too Large',
38
+ 414 => 'Request-URI Too Long',
39
+ 415 => 'Unsupported Media Type',
40
+ 416 => 'Requested Range Not Satisfiable',
41
+ 417 => 'Expectation Failed',
42
+ 418 => "I'm a Teapot",
43
+ 422 => 'Unprocessable Entity',
44
+ 423 => 'Locked',
45
+ 424 => 'Failed Dependency',
46
+ 426 => 'Upgrade Required',
47
+ 500 => 'Internal Server Error',
48
+ 501 => 'Not Implemented',
49
+ 502 => 'Bad Gateway',
50
+ 503 => 'Service Unavailable',
51
+ 504 => 'Gateway Timeout',
52
+ 505 => 'HTTP Version Not Supported',
53
+ 506 => 'Variant Also Negotiates',
54
+ 507 => 'Insufficient Storage',
55
+ 510 => 'Not Extended'
56
+ }
57
+
58
+ SYMBOL_TO_STATUS_CODE = Hash[STATUS_CODES.map { |code, msg| [msg.downcase.gsub(/\s|-/, '_').to_sym, code] }]
59
+
3
60
  attr_accessor :status
4
61
  attr_accessor :headers
5
62
  attr_accessor :body
@@ -15,9 +72,13 @@ module Http
15
72
  @headers = {}
16
73
  end
17
74
 
18
- # Set a header value
19
- def []=(header, value)
20
- key = header.to_s.downcase
75
+ # Set a header
76
+ def []=(name, value)
77
+ # If we have a canonical header, we're done
78
+ key = name[CANONICAL_HEADER]
79
+
80
+ # Convert to canonical capitalization
81
+ key ||= Http.canonicalize_header(name)
21
82
 
22
83
  # Check if the header has already been set and group
23
84
  old_value = @headers[key]
@@ -29,18 +90,23 @@ module Http
29
90
  end
30
91
 
31
92
  # Get a header value
32
- def [](header)
33
- @headers[header.to_s.downcase]
93
+ def [](name)
94
+ @headers[name] || @headers[Http.canonicalize_header(name)]
34
95
  end
35
96
 
36
97
  # Parse the response body according to its content type
37
98
  def parse_body
38
- if @headers['content-type']
39
- mime_type = MimeType[@headers['content-type'].split(/;\s*/).first]
99
+ if @headers['Content-Type']
100
+ mime_type = MimeType[@headers['Content-Type'].split(/;\s*/).first]
40
101
  return mime_type.parse(@body) if mime_type
41
102
  end
42
103
 
43
104
  @body
44
105
  end
106
+
107
+ # Returns an Array ala Rack: `[status, headers, body]`
108
+ def to_a
109
+ [status, headers, parse_body]
110
+ end
45
111
  end
46
112
  end
@@ -0,0 +1,131 @@
1
+ # Taken from Ruby 1.9's uri/common.rb
2
+ # By Akira Yamada <akira@ruby-lang.org>
3
+ # License:
4
+ # You can redistribute it and/or modify it under the same term as Ruby.
5
+
6
+ require 'uri'
7
+
8
+ # Backport Ruby 1.9's form encoding/decoding functionality
9
+ module URI
10
+ TBLENCWWWCOMP_ = {} # :nodoc:
11
+ 256.times do |i|
12
+ TBLENCWWWCOMP_[i.chr] = '%%%02X' % i
13
+ end
14
+ TBLENCWWWCOMP_[' '] = '+'
15
+ TBLENCWWWCOMP_.freeze
16
+ TBLDECWWWCOMP_ = {} # :nodoc:
17
+ 256.times do |i|
18
+ h, l = i>>4, i&15
19
+ TBLDECWWWCOMP_['%%%X%X' % [h, l]] = i.chr
20
+ TBLDECWWWCOMP_['%%%x%X' % [h, l]] = i.chr
21
+ TBLDECWWWCOMP_['%%%X%x' % [h, l]] = i.chr
22
+ TBLDECWWWCOMP_['%%%x%x' % [h, l]] = i.chr
23
+ end
24
+ TBLDECWWWCOMP_['+'] = ' '
25
+ TBLDECWWWCOMP_.freeze
26
+
27
+ # Encode given +str+ to URL-encoded form data.
28
+ #
29
+ # This method doesn't convert *, -, ., 0-9, A-Z, _, a-z, but does convert SP
30
+ # (ASCII space) to + and converts others to %XX.
31
+ #
32
+ # This is an implementation of
33
+ # http://www.w3.org/TR/html5/association-of-controls-and-forms.html#url-encoded-form-data
34
+ #
35
+ # See URI.decode_www_form_component, URI.encode_www_form
36
+ def self.encode_www_form_component(str)
37
+ str.to_s.gsub(/[^*\-.0-9A-Z_a-z]/) { |chr| TBLENCWWWCOMP_[chr] }
38
+ end
39
+
40
+ # Decode given +str+ of URL-encoded form data.
41
+ #
42
+ # This decods + to SP.
43
+ #
44
+ # See URI.encode_www_form_component, URI.decode_www_form
45
+ def self.decode_www_form_component(str)
46
+ raise ArgumentError, "invalid %-encoding (#{str})" unless /\A[^%]*(?:%\h\h[^%]*)*\z/ =~ str
47
+ str.gsub(/\+|%\h\h/) { |chr| TBLDECWWWCOMP_[chr] }
48
+ end
49
+
50
+ # Generate URL-encoded form data from given +enum+.
51
+ #
52
+ # This generates application/x-www-form-urlencoded data defined in HTML5
53
+ # from given an Enumerable object.
54
+ #
55
+ # This internally uses URI.encode_www_form_component(str).
56
+ #
57
+ # This method doesn't convert the encoding of given items, so convert them
58
+ # before call this method if you want to send data as other than original
59
+ # encoding or mixed encoding data. (Strings which are encoded in an HTML5
60
+ # ASCII incompatible encoding are converted to UTF-8.)
61
+ #
62
+ # This method doesn't handle files. When you send a file, use
63
+ # multipart/form-data.
64
+ #
65
+ # This is an implementation of
66
+ # http://www.w3.org/TR/html5/forms.html#url-encoded-form-data
67
+ #
68
+ # URI.encode_www_form([["q", "ruby"], ["lang", "en"]])
69
+ # #=> "q=ruby&lang=en"
70
+ # URI.encode_www_form("q" => "ruby", "lang" => "en")
71
+ # #=> "q=ruby&lang=en"
72
+ # URI.encode_www_form("q" => ["ruby", "perl"], "lang" => "en")
73
+ # #=> "q=ruby&q=perl&lang=en"
74
+ # URI.encode_www_form([["q", "ruby"], ["q", "perl"], ["lang", "en"]])
75
+ # #=> "q=ruby&q=perl&lang=en"
76
+ #
77
+ # See URI.encode_www_form_component, URI.decode_www_form
78
+ def self.encode_www_form(enum)
79
+ enum.map do |k,v|
80
+ if v.nil?
81
+ encode_www_form_component(k)
82
+ elsif v.respond_to?(:to_ary)
83
+ v.to_ary.map do |w|
84
+ str = encode_www_form_component(k)
85
+ unless w.nil?
86
+ str << '='
87
+ str << encode_www_form_component(w)
88
+ end
89
+ end.join('&')
90
+ else
91
+ str = encode_www_form_component(k)
92
+ str << '='
93
+ str << encode_www_form_component(v)
94
+ end
95
+ end.join('&')
96
+ end
97
+
98
+ WFKV_ = '(?:[^%#=;&]*(?:%\h\h[^%#=;&]*)*)' # :nodoc:
99
+
100
+ # Decode URL-encoded form data from given +str+.
101
+ #
102
+ # This decodes application/x-www-form-urlencoded data
103
+ # and returns array of key-value array.
104
+ # This internally uses URI.decode_www_form_component.
105
+ #
106
+ # _charset_ hack is not supported now because the mapping from given charset
107
+ # to Ruby's encoding is not clear yet.
108
+ # see also http://www.w3.org/TR/html5/syntax.html#character-encodings-0
109
+ #
110
+ # This refers http://www.w3.org/TR/html5/forms.html#url-encoded-form-data
111
+ #
112
+ # ary = URI.decode_www_form("a=1&a=2&b=3")
113
+ # p ary #=> [['a', '1'], ['a', '2'], ['b', '3']]
114
+ # p ary.assoc('a').last #=> '1'
115
+ # p ary.assoc('b').last #=> '3'
116
+ # p ary.rassoc('a').last #=> '2'
117
+ # p Hash[ary] # => {"a"=>"2", "b"=>"3"}
118
+ #
119
+ # See URI.decode_www_form_component, URI.encode_www_form
120
+ def self.decode_www_form(str)
121
+ return [] if str.empty?
122
+ unless /\A#{WFKV_}=#{WFKV_}(?:[;&]#{WFKV_}=#{WFKV_})*\z/o =~ str
123
+ raise ArgumentError, "invalid data of application/x-www-form-urlencoded (#{str})"
124
+ end
125
+ ary = []
126
+ $&.scan(/([^=;&]+)=([^;&]*)/) do
127
+ ary << [decode_www_form_component($1), decode_www_form_component($2)]
128
+ end
129
+ ary
130
+ end
131
+ end
@@ -1,3 +1,3 @@
1
1
  module Http
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -3,7 +3,7 @@ require 'spec_helper'
3
3
  require 'http/compat/curb'
4
4
 
5
5
  describe Curl do
6
- let(:test_endpoint) { "http://127.0.0.1:#{TEST_SERVER_PORT}/" }
6
+ let(:test_endpoint) { "http://127.0.0.1:#{ExampleService::PORT}/" }
7
7
 
8
8
  describe Curl::Easy do
9
9
  it "gets resources" do
@@ -0,0 +1,62 @@
1
+ require 'spec_helper'
2
+
3
+ describe Http::Options, "callbacks" do
4
+
5
+ let(:opts){ Http::Options.new }
6
+ let(:callback){ Proc.new{|r| nil } }
7
+
8
+ it 'recognizes invalid events' do
9
+ lambda{
10
+ opts.with_callback(:notacallback, callback)
11
+ }.should raise_error(ArgumentError, /notacallback/)
12
+ end
13
+
14
+ it 'recognizes invalid callbacks' do
15
+ lambda{
16
+ opts.with_callback(:request, Object.new)
17
+ }.should raise_error(ArgumentError, /invalid callback/)
18
+ lambda{
19
+ opts.with_callback(:request, Proc.new{|a,b| nil})
20
+ }.should raise_error(ArgumentError, /only one argument/)
21
+ end
22
+
23
+ describe "request" do
24
+
25
+ it 'defaults to []' do
26
+ opts.callbacks[:request].should eq([])
27
+ end
28
+
29
+ it 'may be specified with with_callback(:request, ...)' do
30
+
31
+ opts2 = opts.with_callback(:request, callback)
32
+ opts.callbacks[:request].should eq([])
33
+ opts2.callbacks[:request].should eq([callback])
34
+
35
+ opts3 = opts2.with_callback(:request, callback)
36
+ opts2.callbacks[:request].should eq([callback])
37
+ opts3.callbacks[:request].should eq([callback, callback])
38
+ end
39
+
40
+ end
41
+
42
+ describe "response" do
43
+
44
+ it 'defaults to []' do
45
+ opts.callbacks[:response].should eq([])
46
+ end
47
+
48
+ it 'may be specified with with_callback(:response, ...)' do
49
+
50
+ opts2 = opts.with_callback(:response, callback)
51
+ opts.callbacks[:response].should eq([])
52
+ opts2.callbacks[:response].should eq([callback])
53
+
54
+ opts3 = opts2.with_callback(:response, callback)
55
+ opts2.callbacks[:response].should eq([callback])
56
+ opts3.callbacks[:response].should eq([callback, callback])
57
+ end
58
+
59
+ end
60
+
61
+ end
62
+
@@ -0,0 +1,18 @@
1
+ require 'spec_helper'
2
+
3
+ describe Http::Options, "form" do
4
+
5
+ let(:opts){ Http::Options.new }
6
+
7
+ it 'defaults to nil' do
8
+ opts.form.should be_nil
9
+ end
10
+
11
+ it 'may be specified with with_form_data' do
12
+ opts2 = opts.with_form(:foo => 42)
13
+ opts.form.should be_nil
14
+ opts2.form.should eq(:foo => 42)
15
+ end
16
+
17
+ end
18
+
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+
3
+ describe Http::Options, "headers" do
4
+
5
+ let(:opts){ Http::Options.new }
6
+
7
+ it 'defaults to {}' do
8
+ opts.headers.should eq({})
9
+ end
10
+
11
+ it 'may be specified with with_headers' do
12
+ opts2 = opts.with_headers("accept" => "json")
13
+ opts.headers.should eq({})
14
+ opts2.headers.should eq("accept" => "json")
15
+ end
16
+
17
+ it 'accepts any object that respond to :to_hash' do
18
+ x = Struct.new(:to_hash).new("accept" => "json")
19
+ opts.with_headers(x).headers["accept"].should eq("json")
20
+ end
21
+
22
+ it 'recognizes invalid headers' do
23
+ lambda{
24
+ opts.with_headers(self)
25
+ }.should raise_error(ArgumentError)
26
+ end
27
+
28
+ end
29
+
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+
3
+ describe Http::Options, "merge" do
4
+
5
+ let(:opts){ Http::Options.new }
6
+
7
+ it 'supports a Hash' do
8
+ old_response = opts.response
9
+ opts.merge(:response => :body).response.should eq(:body)
10
+ opts.response.should eq(old_response)
11
+ end
12
+
13
+ it 'supports another Options' do
14
+ merged = opts.merge(Http::Options.new(:response => :body))
15
+ merged.response.should eq(:body)
16
+ end
17
+
18
+ it 'merges as excepted in complex cases' do
19
+ foo = Http::Options.new(
20
+ :response => :body,
21
+ :form => {:foo => 'foo'},
22
+ :headers => {:accept => "json", :foo => 'foo'},
23
+ :callbacks => {:request => ["common"], :response => ["foo"]})
24
+ bar = Http::Options.new(
25
+ :response => :parsed_body,
26
+ :form => {:bar => 'bar'},
27
+ :headers => {:accept => "xml", :bar => 'bar'},
28
+ :callbacks => {:request => ["common"], :response => ["bar"]})
29
+ foo.merge(bar).to_hash.should eq(
30
+ :response => :parsed_body,
31
+ :form => {:bar => 'bar'},
32
+ :headers => {:accept => "xml", :foo => "foo", :bar => 'bar'},
33
+ :callbacks => {:request => ["common"], :response => ["foo", "bar"]}
34
+ )
35
+ end
36
+
37
+ end