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.
- data/.travis.yml +8 -2
- data/CHANGES.md +6 -0
- data/README.md +5 -10
- data/http.gemspec +5 -3
- data/lib/http.rb +19 -1
- data/lib/http/chainable.rb +36 -12
- data/lib/http/client.rb +37 -74
- data/lib/http/compat/curb.rb +3 -2
- data/lib/http/options.rb +109 -0
- data/lib/http/request.rb +42 -0
- data/lib/http/response.rb +73 -7
- data/lib/http/uri_backport.rb +131 -0
- data/lib/http/version.rb +1 -1
- data/spec/http/compat/curb_spec.rb +1 -1
- data/spec/http/options/callbacks_spec.rb +62 -0
- data/spec/http/options/form_spec.rb +18 -0
- data/spec/http/options/headers_spec.rb +29 -0
- data/spec/http/options/merge_spec.rb +37 -0
- data/spec/http/options/new_spec.rb +39 -0
- data/spec/http/options/response_spec.rb +24 -0
- data/spec/http/options_spec.rb +26 -0
- data/spec/http/response_spec.rb +69 -0
- data/spec/http_spec.rb +24 -2
- data/spec/spec_helper.rb +1 -2
- data/spec/support/{mock_server.rb → example_server.rb} +11 -14
- metadata +80 -85
- data/lib/http/parameters.rb +0 -17
- data/parser/common.rl +0 -40
- data/parser/http.rl +0 -220
- data/parser/multipart.rl +0 -41
data/.travis.yml
CHANGED
data/CHANGES.md
CHANGED
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
|
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
|
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
|
---------
|
data/http.gemspec
CHANGED
@@ -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"
|
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 = "
|
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'
|
21
|
+
gem.add_development_dependency 'rspec'
|
20
22
|
gem.add_development_dependency 'json'
|
21
23
|
end
|
data/lib/http.rb
CHANGED
@@ -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/
|
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
|
data/lib/http/chainable.rb
CHANGED
@@ -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,
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
84
|
+
default_options.headers
|
80
85
|
end
|
81
86
|
|
82
87
|
def default_headers=(headers)
|
83
|
-
@
|
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
|
data/lib/http/client.rb
CHANGED
@@ -1,99 +1,62 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
1
3
|
module Http
|
2
|
-
#
|
4
|
+
# Clients make requests and receive responses
|
3
5
|
class Client
|
4
|
-
|
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
|
-
|
17
|
-
def head(options = {})
|
18
|
-
request :head, options
|
19
|
-
end
|
8
|
+
attr_reader :default_options
|
20
9
|
|
21
|
-
|
22
|
-
|
23
|
-
request :get, options
|
10
|
+
def initialize(default_options = {})
|
11
|
+
@default_options = Options.new(default_options)
|
24
12
|
end
|
25
13
|
|
26
|
-
#
|
27
|
-
def
|
28
|
-
|
29
|
-
|
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
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
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
|
-
|
47
|
-
def options(options = {})
|
48
|
-
request :options, options
|
49
|
-
end
|
24
|
+
request = Request.new method, uri, headers, body
|
50
25
|
|
51
|
-
|
52
|
-
|
53
|
-
|
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
|
-
|
57
|
-
def patch(options = {})
|
58
|
-
request :patch, options
|
30
|
+
format_response method, response, opts.response
|
59
31
|
end
|
60
32
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
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
|
-
|
77
|
-
|
78
|
-
|
39
|
+
Http::Response.new.tap do |res|
|
40
|
+
response.each_header do |header, value|
|
41
|
+
res[header] = value
|
42
|
+
end
|
79
43
|
|
80
|
-
|
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
|
-
|
87
|
-
response.body = net_http_response.body
|
47
|
+
end
|
88
48
|
|
89
|
-
|
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: #{
|
59
|
+
else raise ArgumentError, "invalid response type: #{option}"
|
97
60
|
end
|
98
61
|
end
|
99
62
|
end
|
data/lib/http/compat/curb.rb
CHANGED
@@ -29,8 +29,9 @@ module Curl
|
|
29
29
|
end
|
30
30
|
|
31
31
|
def perform
|
32
|
-
client
|
33
|
-
response
|
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
|
data/lib/http/options.rb
ADDED
@@ -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
|