rest 2.7.2 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -13
- data/Gemfile.lock +16 -18
- data/README.md +2 -0
- data/lib/rest.rb +3 -1
- data/lib/rest/client.rb +12 -6
- data/lib/rest/version.rb +1 -1
- data/lib/rest/wrappers/base_wrapper.rb +7 -9
- data/lib/rest/wrappers/excon_wrapper.rb +4 -4
- data/lib/rest/wrappers/internal_client/internal/abstract_response.rb +108 -0
- data/lib/rest/wrappers/internal_client/internal/exceptions.rb +194 -0
- data/lib/rest/wrappers/internal_client/internal/mimes.rb +54 -0
- data/lib/rest/wrappers/internal_client/internal/net_http_ext.rb +55 -0
- data/lib/rest/wrappers/internal_client/internal/payload.rb +237 -0
- data/lib/rest/wrappers/internal_client/internal/raw_response.rb +36 -0
- data/lib/rest/wrappers/internal_client/internal/request.rb +322 -0
- data/lib/rest/wrappers/internal_client/internal/resource.rb +173 -0
- data/lib/rest/wrappers/internal_client/internal/response.rb +28 -0
- data/lib/rest/wrappers/internal_client/internal_client.rb +177 -0
- data/lib/rest/wrappers/internal_client_wrapper.rb +127 -0
- data/rest.gemspec +4 -2
- data/test/test_base.rb +1 -1
- data/test/test_performance.rb +1 -1
- data/test/test_rest.rb +6 -4
- metadata +47 -36
@@ -0,0 +1,173 @@
|
|
1
|
+
module Rest
|
2
|
+
module InternalClient
|
3
|
+
# A class that can be instantiated for access to a RESTful resource,
|
4
|
+
# including authentication.
|
5
|
+
#
|
6
|
+
# Example:
|
7
|
+
#
|
8
|
+
# resource = InternalClient::Resource.new('http://some/resource')
|
9
|
+
# jpg = resource.get(:accept => 'image/jpg')
|
10
|
+
#
|
11
|
+
# With HTTP basic authentication:
|
12
|
+
#
|
13
|
+
# resource = InternalClient::Resource.new('http://protected/resource', :user => 'user', :password => 'password')
|
14
|
+
# resource.delete
|
15
|
+
#
|
16
|
+
# With a timeout (seconds):
|
17
|
+
#
|
18
|
+
# InternalClient::Resource.new('http://slow', :timeout => 10)
|
19
|
+
#
|
20
|
+
# With an open timeout (seconds):
|
21
|
+
#
|
22
|
+
# InternalClient::Resource.new('http://behindfirewall', :open_timeout => 10)
|
23
|
+
#
|
24
|
+
# You can also use resources to share common headers. For headers keys,
|
25
|
+
# symbols are converted to strings. Example:
|
26
|
+
#
|
27
|
+
# resource = InternalClient::Resource.new('http://some/resource', :headers => { :client_version => 1 })
|
28
|
+
#
|
29
|
+
# This header will be transported as X-Client-Version (notice the X prefix,
|
30
|
+
# capitalization and hyphens)
|
31
|
+
#
|
32
|
+
# Use the [] syntax to allocate subresources:
|
33
|
+
#
|
34
|
+
# site = InternalClient::Resource.new('http://example.com', :user => 'adam', :password => 'mypasswd')
|
35
|
+
# site['posts/1/comments'].post 'Good article.', :content_type => 'text/plain'
|
36
|
+
#
|
37
|
+
class Resource
|
38
|
+
attr_reader :url, :options, :block
|
39
|
+
|
40
|
+
def initialize(url, options={}, backwards_compatibility=nil, &block)
|
41
|
+
@url = url
|
42
|
+
@block = block
|
43
|
+
if options.class == Hash
|
44
|
+
@options = options
|
45
|
+
else # compatibility with previous versions
|
46
|
+
@options = {:user => options, :password => backwards_compatibility}
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def get(additional_headers={}, &block)
|
51
|
+
headers = (options[:headers] || {}).merge(additional_headers)
|
52
|
+
Request.execute(options.merge(
|
53
|
+
:method => :get,
|
54
|
+
:url => url,
|
55
|
+
:headers => headers), &(block || @block))
|
56
|
+
end
|
57
|
+
|
58
|
+
def head(additional_headers={}, &block)
|
59
|
+
headers = (options[:headers] || {}).merge(additional_headers)
|
60
|
+
Request.execute(options.merge(
|
61
|
+
:method => :head,
|
62
|
+
:url => url,
|
63
|
+
:headers => headers), &(block || @block))
|
64
|
+
end
|
65
|
+
|
66
|
+
def post(payload, additional_headers={}, &block)
|
67
|
+
headers = (options[:headers] || {}).merge(additional_headers)
|
68
|
+
Request.execute(options.merge(
|
69
|
+
:method => :post,
|
70
|
+
:url => url,
|
71
|
+
:payload => payload,
|
72
|
+
:headers => headers), &(block || @block))
|
73
|
+
end
|
74
|
+
|
75
|
+
def put(payload, additional_headers={}, &block)
|
76
|
+
headers = (options[:headers] || {}).merge(additional_headers)
|
77
|
+
Request.execute(options.merge(
|
78
|
+
:method => :put,
|
79
|
+
:url => url,
|
80
|
+
:payload => payload,
|
81
|
+
:headers => headers), &(block || @block))
|
82
|
+
end
|
83
|
+
|
84
|
+
def patch(payload, additional_headers={}, &block)
|
85
|
+
headers = (options[:headers] || {}).merge(additional_headers)
|
86
|
+
Request.execute(options.merge(
|
87
|
+
:method => :patch,
|
88
|
+
:url => url,
|
89
|
+
:payload => payload,
|
90
|
+
:headers => headers), &(block || @block))
|
91
|
+
end
|
92
|
+
|
93
|
+
def delete(additional_headers={}, &block)
|
94
|
+
headers = (options[:headers] || {}).merge(additional_headers)
|
95
|
+
Request.execute(options.merge(
|
96
|
+
:method => :delete,
|
97
|
+
:url => url,
|
98
|
+
:headers => headers), &(block || @block))
|
99
|
+
end
|
100
|
+
|
101
|
+
def to_s
|
102
|
+
url
|
103
|
+
end
|
104
|
+
|
105
|
+
def user
|
106
|
+
options[:user]
|
107
|
+
end
|
108
|
+
|
109
|
+
def password
|
110
|
+
options[:password]
|
111
|
+
end
|
112
|
+
|
113
|
+
def headers
|
114
|
+
options[:headers] || {}
|
115
|
+
end
|
116
|
+
|
117
|
+
def timeout
|
118
|
+
options[:timeout]
|
119
|
+
end
|
120
|
+
|
121
|
+
def open_timeout
|
122
|
+
options[:open_timeout]
|
123
|
+
end
|
124
|
+
|
125
|
+
# Construct a subresource, preserving authentication.
|
126
|
+
#
|
127
|
+
# Example:
|
128
|
+
#
|
129
|
+
# site = InternalClient::Resource.new('http://example.com', 'adam', 'mypasswd')
|
130
|
+
# site['posts/1/comments'].post 'Good article.', :content_type => 'text/plain'
|
131
|
+
#
|
132
|
+
# This is especially useful if you wish to define your site in one place and
|
133
|
+
# call it in multiple locations:
|
134
|
+
#
|
135
|
+
# def orders
|
136
|
+
# InternalClient::Resource.new('http://example.com/orders', 'admin', 'mypasswd')
|
137
|
+
# end
|
138
|
+
#
|
139
|
+
# orders.get # GET http://example.com/orders
|
140
|
+
# orders['1'].get # GET http://example.com/orders/1
|
141
|
+
# orders['1/items'].delete # DELETE http://example.com/orders/1/items
|
142
|
+
#
|
143
|
+
# Nest resources as far as you want:
|
144
|
+
#
|
145
|
+
# site = InternalClient::Resource.new('http://example.com')
|
146
|
+
# posts = site['posts']
|
147
|
+
# first_post = posts['1']
|
148
|
+
# comments = first_post['comments']
|
149
|
+
# comments.post 'Hello', :content_type => 'text/plain'
|
150
|
+
#
|
151
|
+
def [](suburl, &new_block)
|
152
|
+
case
|
153
|
+
when block_given? then
|
154
|
+
self.class.new(concat_urls(url, suburl), options, &new_block)
|
155
|
+
when block then
|
156
|
+
self.class.new(concat_urls(url, suburl), options, &block)
|
157
|
+
else
|
158
|
+
self.class.new(concat_urls(url, suburl), options)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def concat_urls(url, suburl) # :nodoc:
|
163
|
+
url = url.to_s
|
164
|
+
suburl = suburl.to_s
|
165
|
+
if url.slice(-1, 1) == '/' or suburl.slice(0, 1) == '/'
|
166
|
+
url + suburl
|
167
|
+
else
|
168
|
+
"#{url}/#{suburl}"
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Rest
|
2
|
+
module InternalClient
|
3
|
+
|
4
|
+
# A Response from InternalClient, you can access the response body, the code or the headers.
|
5
|
+
#
|
6
|
+
module Response
|
7
|
+
|
8
|
+
include AbstractResponse
|
9
|
+
|
10
|
+
attr_accessor :args, :net_http_res
|
11
|
+
|
12
|
+
attr_writer :body
|
13
|
+
|
14
|
+
def body
|
15
|
+
self
|
16
|
+
end
|
17
|
+
|
18
|
+
def Response.create body, net_http_res, args
|
19
|
+
result = body || ''
|
20
|
+
result.extend Response
|
21
|
+
result.net_http_res = net_http_res
|
22
|
+
result.args = args
|
23
|
+
result
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,177 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'zlib'
|
3
|
+
require 'stringio'
|
4
|
+
|
5
|
+
begin
|
6
|
+
require 'net/https'
|
7
|
+
rescue LoadError => e
|
8
|
+
raise e unless RUBY_PLATFORM =~ /linux/
|
9
|
+
raise LoadError, "no such file to load -- net/https. Try running apt-get install libopenssl-ruby"
|
10
|
+
end
|
11
|
+
|
12
|
+
require File.dirname(__FILE__) + '/internal/exceptions'
|
13
|
+
require File.dirname(__FILE__) + '/internal/request'
|
14
|
+
require File.dirname(__FILE__) + '/internal/abstract_response'
|
15
|
+
require File.dirname(__FILE__) + '/internal/response'
|
16
|
+
require File.dirname(__FILE__) + '/internal/raw_response'
|
17
|
+
require File.dirname(__FILE__) + '/internal/resource'
|
18
|
+
require File.dirname(__FILE__) + '/internal/payload'
|
19
|
+
require File.dirname(__FILE__) + '/internal/net_http_ext'
|
20
|
+
require File.dirname(__FILE__) + '/internal/mimes'
|
21
|
+
|
22
|
+
# This module's static methods are the entry point for using the REST client.
|
23
|
+
#
|
24
|
+
# # GET
|
25
|
+
# xml = InternalClient.get 'http://example.com/resource'
|
26
|
+
# jpg = InternalClient.get 'http://example.com/resource', :accept => 'image/jpg'
|
27
|
+
#
|
28
|
+
# # authentication and SSL
|
29
|
+
# InternalClient.get 'https://user:password@example.com/private/resource'
|
30
|
+
#
|
31
|
+
# # POST or PUT with a hash sends parameters as a urlencoded form body
|
32
|
+
# InternalClient.post 'http://example.com/resource', :param1 => 'one'
|
33
|
+
#
|
34
|
+
# # nest hash parameters
|
35
|
+
# InternalClient.post 'http://example.com/resource', :nested => { :param1 => 'one' }
|
36
|
+
#
|
37
|
+
# # POST and PUT with raw payloads
|
38
|
+
# InternalClient.post 'http://example.com/resource', 'the post body', :content_type => 'text/plain'
|
39
|
+
# InternalClient.post 'http://example.com/resource.xml', xml_doc
|
40
|
+
# InternalClient.put 'http://example.com/resource.pdf', File.read('my.pdf'), :content_type => 'application/pdf'
|
41
|
+
#
|
42
|
+
# # DELETE
|
43
|
+
# InternalClient.delete 'http://example.com/resource'
|
44
|
+
#
|
45
|
+
# # retreive the response http code and headers
|
46
|
+
# res = InternalClient.get 'http://example.com/some.jpg'
|
47
|
+
# res.code # => 200
|
48
|
+
# res.headers[:content_type] # => 'image/jpg'
|
49
|
+
#
|
50
|
+
# # HEAD
|
51
|
+
# InternalClient.head('http://example.com').headers
|
52
|
+
#
|
53
|
+
# To use with a proxy, just set InternalClient.proxy to the proper http proxy:
|
54
|
+
#
|
55
|
+
# InternalClient.proxy = "http://proxy.example.com/"
|
56
|
+
#
|
57
|
+
# Or inherit the proxy from the environment:
|
58
|
+
#
|
59
|
+
# InternalClient.proxy = ENV['http_proxy']
|
60
|
+
#
|
61
|
+
# For live tests of InternalClient, try using http://rest-test.heroku.com, which echoes back information about the rest call:
|
62
|
+
#
|
63
|
+
# >> InternalClient.put 'http://rest-test.heroku.com/resource', :foo => 'baz'
|
64
|
+
# => "PUT http://rest-test.heroku.com/resource with a 7 byte payload, content type application/x-www-form-urlencoded {\"foo\"=>\"baz\"}"
|
65
|
+
#
|
66
|
+
module Rest
|
67
|
+
module InternalClient
|
68
|
+
|
69
|
+
def self.get(url, headers={}, &block)
|
70
|
+
Request.execute(:method => :get, :url => url, :headers => headers, &block)
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.post(url, payload, headers={}, &block)
|
74
|
+
Request.execute(:method => :post, :url => url, :payload => payload, :headers => headers, &block)
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.patch(url, payload, headers={}, &block)
|
78
|
+
Request.execute(:method => :patch, :url => url, :payload => payload, :headers => headers, &block)
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.put(url, payload, headers={}, &block)
|
82
|
+
Request.execute(:method => :put, :url => url, :payload => payload, :headers => headers, &block)
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.delete(url, headers={}, &block)
|
86
|
+
Request.execute(:method => :delete, :url => url, :headers => headers, &block)
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.head(url, headers={}, &block)
|
90
|
+
Request.execute(:method => :head, :url => url, :headers => headers, &block)
|
91
|
+
end
|
92
|
+
|
93
|
+
def self.options(url, headers={}, &block)
|
94
|
+
Request.execute(:method => :options, :url => url, :headers => headers, &block)
|
95
|
+
end
|
96
|
+
|
97
|
+
class << self
|
98
|
+
attr_accessor :proxy
|
99
|
+
end
|
100
|
+
|
101
|
+
# Setup the log for InternalClient calls.
|
102
|
+
# Value should be a logger but can can be stdout, stderr, or a filename.
|
103
|
+
# You can also configure logging by the environment variable RESTCLIENT_LOG.
|
104
|
+
def self.log= log
|
105
|
+
@@log = create_log log
|
106
|
+
end
|
107
|
+
|
108
|
+
def self.version
|
109
|
+
version_path = File.dirname(__FILE__) + "/../VERSION"
|
110
|
+
return File.read(version_path).chomp if File.file?(version_path)
|
111
|
+
"0.0.0"
|
112
|
+
end
|
113
|
+
|
114
|
+
# Create a log that respond to << like a logger
|
115
|
+
# param can be 'stdout', 'stderr', a string (then we will log to that file) or a logger (then we return it)
|
116
|
+
def self.create_log param
|
117
|
+
if param
|
118
|
+
if param.is_a? String
|
119
|
+
if param == 'stdout'
|
120
|
+
stdout_logger = Class.new do
|
121
|
+
def << obj
|
122
|
+
STDOUT.puts obj
|
123
|
+
end
|
124
|
+
end
|
125
|
+
stdout_logger.new
|
126
|
+
elsif param == 'stderr'
|
127
|
+
stderr_logger = Class.new do
|
128
|
+
def << obj
|
129
|
+
STDERR.puts obj
|
130
|
+
end
|
131
|
+
end
|
132
|
+
stderr_logger.new
|
133
|
+
else
|
134
|
+
file_logger = Class.new do
|
135
|
+
attr_writer :target_file
|
136
|
+
|
137
|
+
def << obj
|
138
|
+
File.open(@target_file, 'a') { |f| f.puts obj }
|
139
|
+
end
|
140
|
+
end
|
141
|
+
logger = file_logger.new
|
142
|
+
logger.target_file = param
|
143
|
+
logger
|
144
|
+
end
|
145
|
+
else
|
146
|
+
param
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
@@env_log = create_log ENV['RESTCLIENT_LOG']
|
152
|
+
|
153
|
+
@@log = nil
|
154
|
+
|
155
|
+
def self.log # :nodoc:
|
156
|
+
@@env_log || @@log
|
157
|
+
end
|
158
|
+
|
159
|
+
@@before_execution_procs = []
|
160
|
+
|
161
|
+
# Add a Proc to be called before each request in executed.
|
162
|
+
# The proc parameters will be the http request and the request params.
|
163
|
+
def self.add_before_execution_proc &proc
|
164
|
+
@@before_execution_procs << proc
|
165
|
+
end
|
166
|
+
|
167
|
+
# Reset the procs to be called before each request is executed.
|
168
|
+
def self.reset_before_execution_procs
|
169
|
+
@@before_execution_procs = []
|
170
|
+
end
|
171
|
+
|
172
|
+
def self.before_execution_procs # :nodoc:
|
173
|
+
@@before_execution_procs
|
174
|
+
end
|
175
|
+
|
176
|
+
end
|
177
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
require_relative 'internal_client/internal_client'
|
2
|
+
|
3
|
+
module Rest
|
4
|
+
|
5
|
+
module Wrappers
|
6
|
+
class InternalClientExceptionWrapper < HttpError
|
7
|
+
attr_reader :ex
|
8
|
+
|
9
|
+
def initialize(ex)
|
10
|
+
super(ex.response, ex.http_code)
|
11
|
+
@ex = ex
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class InternalClientResponseWrapper < BaseResponseWrapper
|
16
|
+
def initialize(response)
|
17
|
+
@response = response
|
18
|
+
end
|
19
|
+
|
20
|
+
def code
|
21
|
+
@response.code
|
22
|
+
end
|
23
|
+
|
24
|
+
def body
|
25
|
+
@response.body
|
26
|
+
end
|
27
|
+
|
28
|
+
def headers_orig
|
29
|
+
@response.headers
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
class InternalClientWrapper < BaseWrapper
|
35
|
+
|
36
|
+
def default_headers
|
37
|
+
{}
|
38
|
+
end
|
39
|
+
|
40
|
+
def get(url, req_hash={})
|
41
|
+
response = nil
|
42
|
+
begin
|
43
|
+
req_hash[:method] = :get
|
44
|
+
req_hash[:url] = url
|
45
|
+
req_hash[:headers] ||= default_headers
|
46
|
+
req_hash[:headers][:params] = req_hash[:params] if req_hash[:params]
|
47
|
+
#p req_hash
|
48
|
+
r2 = Rest::InternalClient::Request.execute(req_hash)
|
49
|
+
response = InternalClientResponseWrapper.new(r2)
|
50
|
+
rescue Rest::InternalClient::Exception => ex
|
51
|
+
#p ex
|
52
|
+
#if ex.http_code == 404
|
53
|
+
# return InternalClientResponseWrapper.new(ex.response)
|
54
|
+
#end
|
55
|
+
raise InternalClientExceptionWrapper.new(ex)
|
56
|
+
end
|
57
|
+
response
|
58
|
+
end
|
59
|
+
|
60
|
+
def post(url, req_hash={})
|
61
|
+
response = nil
|
62
|
+
begin
|
63
|
+
req_hash[:method] = :post
|
64
|
+
req_hash[:url] = url
|
65
|
+
b = req_hash[:body]
|
66
|
+
if b
|
67
|
+
if b.is_a?(Hash)
|
68
|
+
b = b.to_json
|
69
|
+
end
|
70
|
+
req_hash[:payload] = b
|
71
|
+
end
|
72
|
+
r2 = Rest::InternalClient::Request.execute(req_hash)
|
73
|
+
response = InternalClientResponseWrapper.new(r2)
|
74
|
+
rescue Rest::InternalClient::Exception => ex
|
75
|
+
raise InternalClientExceptionWrapper.new(ex)
|
76
|
+
end
|
77
|
+
response
|
78
|
+
end
|
79
|
+
|
80
|
+
def put(url, req_hash={})
|
81
|
+
response = nil
|
82
|
+
begin
|
83
|
+
req_hash[:method] = :put
|
84
|
+
req_hash[:url] = url
|
85
|
+
req_hash[:payload] = req_hash[:body] if req_hash[:body]
|
86
|
+
r2 = Rest::InternalClient::Request.execute(req_hash)
|
87
|
+
response = InternalClientResponseWrapper.new(r2)
|
88
|
+
rescue Rest::InternalClient::Exception => ex
|
89
|
+
raise InternalClientExceptionWrapper.new(ex)
|
90
|
+
end
|
91
|
+
response
|
92
|
+
end
|
93
|
+
|
94
|
+
def patch(url, req_hash={})
|
95
|
+
response = nil
|
96
|
+
begin
|
97
|
+
req_hash[:method] = :patch
|
98
|
+
req_hash[:url] = url
|
99
|
+
req_hash[:payload] = req_hash[:body] if req_hash[:body]
|
100
|
+
r2 = Rest::InternalClient::Request.execute(req_hash)
|
101
|
+
response = InternalClientResponseWrapper.new(r2)
|
102
|
+
rescue Rest::InternalClient::Exception => ex
|
103
|
+
raise InternalClientExceptionWrapper.new(ex)
|
104
|
+
end
|
105
|
+
response
|
106
|
+
end
|
107
|
+
|
108
|
+
|
109
|
+
def delete(url, req_hash={})
|
110
|
+
response = nil
|
111
|
+
begin
|
112
|
+
req_hash[:method] = :delete
|
113
|
+
req_hash[:url] = url
|
114
|
+
req_hash[:payload] = req_hash[:body] if req_hash[:body]
|
115
|
+
r2 = Rest::InternalClient::Request.execute(req_hash)
|
116
|
+
response = InternalClientResponseWrapper.new(r2)
|
117
|
+
# todo: make generic exception
|
118
|
+
rescue Rest::InternalClient::Exception => ex
|
119
|
+
raise InternalClientExceptionWrapper.new(ex)
|
120
|
+
end
|
121
|
+
response
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|