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.

@@ -1,5 +1,11 @@
1
1
  rvm:
2
2
  - 1.8.7
3
3
  - 1.9.2
4
- - rbx
5
- - jruby
4
+ - 1.9.3
5
+ - ree
6
+ - ruby-head
7
+ - jruby-18mode
8
+ - jruby-19mode
9
+ - jruby-head
10
+ - rbx-18mode
11
+ - rbx-19mode
data/CHANGES.md CHANGED
@@ -1,3 +1,9 @@
1
+ 0.2.0
2
+ -----
3
+ * Request and response objects
4
+ * Callback system
5
+ * Internal refactoring ensuring true chainability
6
+
1
7
  0.1.0
2
8
  -----
3
9
  * Testing against WEBrick
data/README.md CHANGED
@@ -2,16 +2,10 @@ Http
2
2
  ====
3
3
  [![Build Status](http://travis-ci.org/tarcieri/http.png)](http://travis-ci.org/tarcieri/http)
4
4
 
5
- Ruby has always been this extremely web-focused language, and yet despite the
6
- selection of HTTP libraries out there, I always find myself falling back on
7
- Net::HTTP, and Net::HTTP sucks.
8
-
9
- Ruby should be simple and elegant and beautiful. Net::HTTP is not. I've often
10
- found myself falling back on the Perlish horrors of open-uri just because I
11
- found Net::HTTP to be too much of a pain. This shouldn't be!
12
-
13
5
  HTTP should be simple and easy! It should be so straightforward it makes
14
- you happy with how delightful it is to use!
6
+ you happy every time you use it.
7
+
8
+ The Http library makes it easy to construct requests using a simple chaining system.
15
9
 
16
10
  Making Requests
17
11
  ---------------
@@ -98,7 +92,8 @@ Contributing to Http
98
92
 
99
93
  * Fork Http on github
100
94
  * Make your changes and send me a pull request
101
- * If I like them I'll merge them and give you commit access to my repository
95
+ * If I like them I'll merge them
96
+ * If I've accepted a patch, feel free to ask for a commit bit!
102
97
 
103
98
  Copyright
104
99
  ---------
@@ -2,10 +2,10 @@
2
2
  require File.expand_path('../lib/http/version', __FILE__)
3
3
 
4
4
  Gem::Specification.new do |gem|
5
- gem.authors = ["Tony Arcieri", "Carl Lerche"]
5
+ gem.authors = ["Tony Arcieri"]
6
6
  gem.email = ["tony.arcieri@gmail.com"]
7
7
  gem.description = "HTTP so awesome it will lure Catherine Zeta Jones into your unicorn petting zoo"
8
- gem.summary = "Made entirely of Ruby (and Ragel and C and Java)"
8
+ gem.summary = "HTTP should be easy"
9
9
  gem.homepage = "https://github.com/tarcieri/http"
10
10
 
11
11
  gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
@@ -15,7 +15,9 @@ Gem::Specification.new do |gem|
15
15
  gem.require_paths = ["lib"]
16
16
  gem.version = Http::VERSION
17
17
 
18
+ gem.add_dependency 'certified'
19
+
18
20
  gem.add_development_dependency 'rake'
19
- gem.add_development_dependency 'rspec', '>= 2.6.0'
21
+ gem.add_development_dependency 'rspec'
20
22
  gem.add_development_dependency 'json'
21
23
  end
@@ -1,15 +1,33 @@
1
1
  require 'http/version'
2
+
2
3
  require 'http/chainable'
3
4
  require 'http/client'
4
5
  require 'http/mime_type'
5
- require 'http/parameters'
6
+ require 'http/options'
7
+ require 'http/request'
6
8
  require 'http/response'
9
+ require 'http/uri_backport' if RUBY_VERSION < "1.9.0"
7
10
 
8
11
  # THIS IS ENTIRELY TEMPORARY, I ASSURE YOU
9
12
  require 'net/https'
10
13
  require 'uri'
14
+ require 'certified'
11
15
 
12
16
  # Http, it can be simple!
13
17
  module Http
14
18
  extend Chainable
19
+
20
+ # The method given was not understood
21
+ class UnsupportedMethodError < ArgumentError; end
22
+
23
+ # Valid HTTP methods
24
+ METHODS = [:get, :head, :post, :put, :delete, :trace, :options, :connect, :patch]
25
+
26
+ # Matches HTTP header names when in "Canonical-Http-Format"
27
+ CANONICAL_HEADER = /^[A-Z][a-z]*(-[A-Z][a-z]*)*$/
28
+
29
+ # Transform to canonical HTTP header capitalization
30
+ def self.canonicalize_header(header)
31
+ header.to_s.split('-').map(&:capitalize).join('-')
32
+ end
15
33
  end
@@ -2,7 +2,7 @@ module Http
2
2
  module Chainable
3
3
  # Request a get sans response body
4
4
  def head(uri, options = {})
5
- request :head, uri, {:response => :object}.merge(options)
5
+ request :head, uri, options
6
6
  end
7
7
 
8
8
  # Get a resource
@@ -47,20 +47,17 @@ module Http
47
47
 
48
48
  # Make an HTTP request with the given verb
49
49
  def request(verb, uri, options = {})
50
- options[:response] ||= :parsed_body
51
-
52
- if options[:headers]
53
- headers = default_headers.merge options[:headers]
54
- else
55
- headers = default_headers
56
- end
50
+ branch(options).request verb, uri
51
+ end
57
52
 
58
- Client.new(uri).request verb, options.merge(:headers => headers)
53
+ # Make a request invoking the given event callbacks
54
+ def on(event, &block)
55
+ branch default_options.with_callback(event, block)
59
56
  end
60
57
 
61
58
  # Make a request with the given headers
62
59
  def with_headers(headers)
63
- Parameters.new default_headers.merge(headers)
60
+ branch default_options.with_headers(headers)
64
61
  end
65
62
  alias_method :with, :with_headers
66
63
 
@@ -75,12 +72,39 @@ module Http
75
72
  end
76
73
  end
77
74
 
75
+ def default_options
76
+ @default_options ||= Options.new
77
+ end
78
+
79
+ def default_options=(opts)
80
+ @default_options = Options.new(opts)
81
+ end
82
+
78
83
  def default_headers
79
- @default_headers ||= {}
84
+ default_options.headers
80
85
  end
81
86
 
82
87
  def default_headers=(headers)
83
- @default_headers = headers
88
+ @default_options = default_options.dup do |opts|
89
+ opts.headers = headers
90
+ end
84
91
  end
92
+
93
+ def default_callbacks
94
+ default_options.callbacks
95
+ end
96
+
97
+ def default_callbacks=(callbacks)
98
+ @default_options = default_options.dup do |opts|
99
+ opts.callbacks = callbacks
100
+ end
101
+ end
102
+
103
+ private
104
+
105
+ def branch(options)
106
+ Client.new(options)
107
+ end
108
+
85
109
  end
86
110
  end
@@ -1,99 +1,62 @@
1
+ require 'uri'
2
+
1
3
  module Http
2
- # We all know what HTTP clients are, right?
4
+ # Clients make requests and receive responses
3
5
  class Client
4
- # I swear I'll document that nebulous options hash
5
- def initialize(uri, options = {})
6
- if uri.is_a? URI
7
- @uri = uri
8
- else
9
- # Why the FUCK can't Net::HTTP do this?
10
- @uri = URI(uri.to_s)
11
- end
12
-
13
- @options = {:response => :object}.merge(options)
14
- end
6
+ include Chainable
15
7
 
16
- # Request a get sans response body
17
- def head(options = {})
18
- request :head, options
19
- end
8
+ attr_reader :default_options
20
9
 
21
- # Get a resource
22
- def get(options = {})
23
- request :get, options
10
+ def initialize(default_options = {})
11
+ @default_options = Options.new(default_options)
24
12
  end
25
13
 
26
- # Post to a resource
27
- def post(options = {})
28
- request :post, options
29
- end
30
-
31
- # Put to a resource
32
- def put(options = {})
33
- request :put, options
34
- end
35
-
36
- # Delete a resource
37
- def delete(options = {})
38
- request :delete, options
39
- end
14
+ # Make an HTTP request
15
+ def request(method, uri, options = {})
16
+ opts = @default_options.merge(options)
17
+ headers = opts.headers
40
18
 
41
- # Echo the request back to the client
42
- def trace(options = {})
43
- request :trace, options
44
- end
19
+ if opts.form
20
+ body = URI.encode_www_form(opts.form)
21
+ headers['Content-Type'] ||= 'application/x-www-form-urlencoded'
22
+ end
45
23
 
46
- # Return the methods supported on the given URI
47
- def options(options = {})
48
- request :options, options
49
- end
24
+ request = Request.new method, uri, headers, body
50
25
 
51
- # Convert to a transparent TCP/IP tunnel
52
- def connect(options = {})
53
- request :connect, options
54
- end
26
+ opts.callbacks[:request].each { |c| c.call(request) }
27
+ response = perform request
28
+ opts.callbacks[:response].each { |c| c.call(response) }
55
29
 
56
- # Apply partial modifications to a resource
57
- def patch(options = {})
58
- request :patch, options
30
+ format_response method, response, opts.response
59
31
  end
60
32
 
61
- # Make an HTTP request
62
- def request(verb, options = {})
63
- # Red, green, refactor tomorrow :/
64
- options = @options.merge(options)
65
- raw_headers = options[:headers] || {}
66
-
67
- # Stringify keys :/
68
- headers = {}
69
- raw_headers.each { |k,v| headers[k.to_s] = v }
70
-
71
- http = Net::HTTP.new(@uri.host, @uri.port)
72
-
73
- # Why the FUCK can't Net::HTTP do this either?!
74
- http.use_ssl = true if @uri.is_a? URI::HTTPS
33
+ def perform(request)
34
+ uri = request.uri
35
+ http = Net::HTTP.new(uri.host, uri.port)
36
+ http.use_ssl = true if uri.is_a? URI::HTTPS
37
+ response = http.request request.to_net_http_request
75
38
 
76
- request_class = Net::HTTP.const_get(verb.to_s.capitalize)
77
- request = request_class.new(@uri.request_uri, headers)
78
- request.set_form_data(options[:form]) if options[:form]
39
+ Http::Response.new.tap do |res|
40
+ response.each_header do |header, value|
41
+ res[header] = value
42
+ end
79
43
 
80
- net_http_response = http.request(request)
81
-
82
- response = Http::Response.new
83
- net_http_response.each_header do |header, value|
84
- response[header] = value
44
+ res.status = Integer(response.code)
45
+ res.body = response.body
85
46
  end
86
- response.status = Integer(net_http_response.code) # WTF again Net::HTTP
87
- response.body = net_http_response.body
47
+ end
88
48
 
89
- case options[:response]
49
+ def format_response(method, response, option)
50
+ case option
51
+ when :auto, NilClass
52
+ method == :head ? response : response.parse_body
90
53
  when :object
91
54
  response
92
55
  when :parsed_body
93
56
  response.parse_body
94
57
  when :body
95
58
  response.body
96
- else raise ArgumentError, "invalid response type: #{options[:response]}"
59
+ else raise ArgumentError, "invalid response type: #{option}"
97
60
  end
98
61
  end
99
62
  end
@@ -29,8 +29,9 @@ module Curl
29
29
  end
30
30
 
31
31
  def perform
32
- client = Http::Client.new @url, :headers => @headers
33
- response = client.send @method
32
+ client = Http::Client.new
33
+ options = {:response => :object, :headers => @headers}
34
+ response = client.request @method, @url, options
34
35
  @response_code, @body_str = response.code, response.body
35
36
  true
36
37
  rescue SocketError => ex
@@ -0,0 +1,109 @@
1
+ module Http
2
+ class Options
3
+
4
+ # How to format the response [:object, :body, :parse_body]
5
+ attr_accessor :response
6
+
7
+ # Http headers to include in the request
8
+ attr_accessor :headers
9
+
10
+ # Form data to embed in the request
11
+ attr_accessor :form
12
+
13
+ # Before callbacks
14
+ attr_accessor :callbacks
15
+
16
+ protected :response=, :headers=, :form=, :callbacks=
17
+
18
+ def self.new(default = {})
19
+ return default if default.is_a?(Options)
20
+ super
21
+ end
22
+
23
+ def initialize(default = {})
24
+ @response = default[:response] || :auto
25
+ @headers = default[:headers] || {}
26
+ @form = default[:form] || nil
27
+ @callbacks = default[:callbacks] || {:request => [], :response => []}
28
+ end
29
+
30
+ def with_response(response)
31
+ unless [:auto, :object, :body, :parsed_body].include?(response)
32
+ argument_error! "invalid response type: #{response}"
33
+ end
34
+ dup do |opts|
35
+ opts.response = response
36
+ end
37
+ end
38
+
39
+ def with_headers(headers)
40
+ unless headers.respond_to?(:to_hash)
41
+ argument_error! "invalid headers: #{headers}"
42
+ end
43
+ dup do |opts|
44
+ opts.headers = self.headers.merge(headers.to_hash)
45
+ end
46
+ end
47
+
48
+ def with_form(form)
49
+ dup do |opts|
50
+ opts.form = form
51
+ end
52
+ end
53
+
54
+ def with_callback(event, callback)
55
+ unless callback.respond_to?(:call)
56
+ argument_error! "invalid callback: #{callback}"
57
+ end
58
+ unless callback.respond_to?(:arity) and callback.arity == 1
59
+ argument_error! "callback must accept only one argument"
60
+ end
61
+ unless [:request, :response].include?(event)
62
+ argument_error! "invalid callback event: #{event}"
63
+ end
64
+ dup do |opts|
65
+ opts.callbacks = callbacks.dup
66
+ opts.callbacks[event] = (callbacks[event].dup << callback)
67
+ end
68
+ end
69
+
70
+ def [](option)
71
+ send(option) rescue nil
72
+ end
73
+
74
+ def merge(other)
75
+ h1, h2 = to_hash, other.to_hash
76
+ merged = h1.merge(h2) do |k,v1,v2|
77
+ case k
78
+ when :headers
79
+ v1.merge(v2)
80
+ when :callbacks
81
+ v1.merge(v2){|event,l,r| (l+r).uniq}
82
+ else
83
+ v2
84
+ end
85
+ end
86
+ Options.new(merged)
87
+ end
88
+
89
+ def to_hash
90
+ {:response => response,
91
+ :headers => headers,
92
+ :form => form,
93
+ :callbacks => callbacks}
94
+ end
95
+
96
+ def dup
97
+ dupped = super
98
+ yield(dupped) if block_given?
99
+ dupped
100
+ end
101
+
102
+ private
103
+
104
+ def argument_error!(message)
105
+ raise ArgumentError, message, caller[1..-1]
106
+ end
107
+
108
+ end
109
+ end