rest 2.7.2 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|