right_support 1.4.1 → 2.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.
- data/README.rdoc +94 -68
- data/lib/right_support.rb +1 -1
- data/lib/right_support/{config.rb → data.rb} +4 -6
- data/lib/right_support/data/uuid.rb +175 -0
- data/lib/right_support/log.rb +0 -1
- data/lib/right_support/log/system_logger.rb +1 -1
- data/lib/right_support/net.rb +1 -1
- data/lib/right_support/net/http_client.rb +118 -32
- data/lib/right_support/net/{balancing.rb → lb.rb} +4 -4
- data/lib/right_support/net/{balancing → lb}/health_check.rb +1 -1
- data/lib/right_support/net/{balancing → lb}/round_robin.rb +1 -1
- data/lib/right_support/net/{balancing/sticky_policy.rb → lb/sticky.rb} +2 -3
- data/lib/right_support/net/request_balancer.rb +1 -1
- data/lib/right_support/rack.rb +2 -1
- data/lib/right_support/rack/{custom_logger.rb → log_setter.rb} +21 -33
- data/lib/right_support/rack/request_logger.rb +47 -21
- data/lib/right_support/{config/recursive_true_class.rb → rack/request_tracker.rb} +33 -33
- data/lib/right_support/ruby/object_extensions.rb +0 -22
- data/lib/right_support/stats/helpers.rb +36 -65
- data/lib/right_support/validation/ssh.rb +2 -2
- data/right_support.gemspec +3 -3
- metadata +15 -16
- data/lib/right_support/config/feature_set.rb +0 -95
- data/lib/right_support/config/yaml_config.rb +0 -62
- data/lib/right_support/log/tag_logger.rb +0 -72
data/README.rdoc
CHANGED
@@ -9,57 +9,76 @@ SystemLogger is a rewrite of the seattle.rb SyslogLogger class that features the
|
|
9
9
|
* Inherits from standard Ruby Logger class for guaranteed compatibility
|
10
10
|
* Can be configured with options about how to handle newlines, ANSI escape codes, etc
|
11
11
|
|
12
|
-
|
13
|
-
|
12
|
+
We also provide Log::Mixin, a powerful tool that lets you sprinkle a logger
|
13
|
+
into any object or class, and supports per-class or per-instance custom
|
14
|
+
loggers!
|
14
15
|
|
15
|
-
|
16
|
-
|
16
|
+
class Jet < Aircraft
|
17
|
+
include RightSupport::Log.Mixin
|
18
|
+
end
|
17
19
|
|
18
|
-
|
19
|
-
|
20
|
-
|
20
|
+
#most jets log to the syslog
|
21
|
+
Jet.logger = RightSupport::Log::SystemLogger.new('black box',
|
22
|
+
:facility=>'local0')
|
21
23
|
|
22
|
-
|
24
|
+
#but MY jet
|
25
|
+
@my_jet.logger = Logger.new(StringIO.new)
|
23
26
|
|
24
|
-
|
25
|
-
use RightSupport::Rack::CustomLogger, Logger::INFO, my_logger
|
27
|
+
==== Logging for Rack Applications
|
26
28
|
|
27
|
-
|
29
|
+
RightSupport also provides Rack middleware that allows a Rack app to use any log sink it wishes. The
|
30
|
+
stock Rack logger middleware is inflexible and gives the end user no control over which logger is used
|
31
|
+
or where the log entries go. Example of usage on Sinatra based application:
|
32
|
+
|
33
|
+
class MyApp < Sinatra::Base
|
34
|
+
...
|
35
|
+
# Specify which logger will be used as default logger
|
36
|
+
LOGGER =\
|
37
|
+
if ENV['RACK_ENV'].downcase == 'development'
|
38
|
+
Logger.new(STDOUT)
|
39
|
+
else
|
40
|
+
RightSupport::Log::SystemLogger.new("MyApp", {:facility => "local0"})
|
41
|
+
end
|
42
|
+
|
43
|
+
# use the RequestLogger via LogSetter to log requests for application
|
44
|
+
use RightSupport::Rack::LogSetter, {:logger => LOGGER}
|
45
|
+
use RightSupport::Rack::RequestLogger
|
46
|
+
|
47
|
+
# set logger and mix Log::Mixin
|
48
|
+
RightSupport::Log::Mixin.default_logger = LOGGER
|
49
|
+
include RightSupport::Log::Mixin
|
50
|
+
...
|
51
|
+
end
|
28
52
|
|
29
|
-
|
30
|
-
ActiveSupport gem cannot be loaded. It also has some RightScale-specific
|
31
|
-
methods:
|
32
|
-
* String#snake_case
|
33
|
-
* String#to_const
|
34
|
-
* String#to_const_path
|
53
|
+
After that all rack requests to MyApp will be logged, and since we mixed Log::Mixin we can use:
|
35
54
|
|
36
|
-
|
55
|
+
logger.info "Hello world\nThis will appear on separate lines\nand without \e[33;0mbeautiful colors"
|
37
56
|
|
38
|
-
===
|
57
|
+
=== Networking Stuff
|
39
58
|
|
40
|
-
|
41
|
-
web app's models before saving, check for preconditions in your controllers,
|
42
|
-
and so forth.
|
59
|
+
==== HTTP Client
|
43
60
|
|
44
|
-
|
45
|
-
|
61
|
+
We provide a very thin wrapper around the rest-client gem that enables simple but
|
62
|
+
robust rest requests with a timeout, headers, etc.
|
46
63
|
|
47
|
-
|
48
|
-
|
64
|
+
HTTPClient is interface-compatible with the RestClient module, but allows an
|
65
|
+
optional timeout to be specified as an extra parameter.
|
49
66
|
|
50
|
-
|
51
|
-
|
52
|
-
end
|
53
|
-
end
|
67
|
+
# Create a wrapper object
|
68
|
+
@client = RightSupport::Net::HTTPClient.new
|
54
69
|
|
55
|
-
|
56
|
-
|
57
|
-
but does not pollute the dispatch table of your application classes.
|
70
|
+
# Default timeout is 5 seconds
|
71
|
+
@client.get('http://localhost')
|
58
72
|
|
59
|
-
|
60
|
-
|
73
|
+
# Make sure the HTTPClient request fails after 1 second so we can report an error
|
74
|
+
# and move on!
|
75
|
+
@client.get('http://localhost', {:headers => {'X-Hello'=>'hi!'}, :timeout => 1)}
|
61
76
|
|
62
|
-
|
77
|
+
# HTTPClient transforms String or Hash :query into query string, for example;
|
78
|
+
@client.get('http://localhost/moo', {:query=>{:a=>{:b=>:c}}} )
|
79
|
+
# the url that would be requested is http://localhost/moo?a[b]=c
|
80
|
+
|
81
|
+
==== Client-Side Load Balancer
|
63
82
|
|
64
83
|
RequestBalancer randomly chooses endpoints for a network request, which lets
|
65
84
|
you perform easy client-side load balancing:
|
@@ -71,26 +90,23 @@ you perform easy client-side load balancing:
|
|
71
90
|
REST.get(url)
|
72
91
|
end
|
73
92
|
|
74
|
-
The balancer will keep trying requests until one of them succeeds without
|
75
|
-
any exceptions. (NB: a nil return value counts as success!!)
|
93
|
+
The balancer will keep trying requests until one of them succeeds without
|
94
|
+
throwing any exceptions. (NB: a nil return value counts as success!!)
|
76
95
|
|
77
|
-
|
96
|
+
==== HTTP Request Tracking for Rack
|
78
97
|
|
79
|
-
|
80
|
-
|
98
|
+
Correlate data flows across your entire architecture with the RequestTracker
|
99
|
+
middleware, which uses custom HTTP headers to "tag" every Web request with
|
100
|
+
a UUID, and works with RequestLogger to log "begin" and "end" lines containing
|
101
|
+
the UUID.
|
81
102
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
# Create a wrapper object
|
86
|
-
@client = RightSupport::Net::HTTPClient.new
|
87
|
-
|
88
|
-
# Default timeout is 5 seconds
|
89
|
-
@client.get('http://localhost')
|
103
|
+
If your app consumes the UUID and passes it on to HTTP services that it
|
104
|
+
invokes, the same request UUID will appear in all logs and you can grep for
|
105
|
+
the "big picture" instead of wasting time paging between logs.
|
90
106
|
|
91
|
-
|
92
|
-
|
93
|
-
|
107
|
+
To use this functionality you need:
|
108
|
+
|
109
|
+
use RightSupport::Rack::RequestTracker
|
94
110
|
|
95
111
|
=== Statistics Gathering
|
96
112
|
|
@@ -107,27 +123,37 @@ Profile your compute-heavy and network activities using a Stats::Activity counte
|
|
107
123
|
|
108
124
|
puts "Only %.1f lines/sec? You are a slow typist!" % [stats.avg_rate]
|
109
125
|
|
110
|
-
===
|
126
|
+
=== Input Validation
|
111
127
|
|
112
|
-
|
113
|
-
|
114
|
-
|
128
|
+
Validation contains several format-checkers that can be used to validate your
|
129
|
+
web app's models before saving, check for preconditions in your controllers,
|
130
|
+
and so forth.
|
115
131
|
|
116
|
-
|
117
|
-
|
118
|
-
speak:
|
119
|
-
klingonese: false
|
120
|
-
belarusian: true
|
132
|
+
You can use it as a mixin by including the appropriate child module of
|
133
|
+
RightSupport::Validation.
|
121
134
|
|
122
|
-
|
135
|
+
class AwesomenessGenerator < ActiveRecord::Base
|
136
|
+
include RightSupport::Validation::OpenSSL
|
123
137
|
|
124
|
-
|
125
|
-
|
138
|
+
before_save do |record|
|
139
|
+
errors[:foo] = 'Hey, that's not a key!' unless pem_public_key?(record.foo)
|
140
|
+
end
|
126
141
|
end
|
127
142
|
|
128
|
-
|
143
|
+
But you really don't want to do that, do you? Instead, you want to call the module
|
144
|
+
methods of RightSupport::Validation, which contains all of the same mixin methods,
|
145
|
+
but does not pollute the dispatch table of your application classes.
|
129
146
|
|
130
|
-
|
147
|
+
the_key = STDIN.read
|
148
|
+
raise ArgumentError unless RightSupport::Validation.ssh_public_key?(the_key)
|
131
149
|
|
132
|
-
|
133
|
-
|
150
|
+
=== String Manipulation
|
151
|
+
|
152
|
+
StringExtensions contains String#camelize, which is only defined if the
|
153
|
+
ActiveSupport gem cannot be loaded. It also has some RightScale-specific
|
154
|
+
methods:
|
155
|
+
* String#snake_case
|
156
|
+
* String#to_const
|
157
|
+
* String#to_const_path
|
158
|
+
|
159
|
+
Net::StringEncoder applies and removes URL-escape, base64 and other encodings.
|
data/lib/right_support.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
#
|
2
|
-
# Copyright (c)
|
2
|
+
# Copyright (c) 2011 RightScale Inc
|
3
3
|
#
|
4
4
|
# Permission is hereby granted, free of charge, to any person obtaining
|
5
5
|
# a copy of this software and associated documentation files (the
|
@@ -22,13 +22,11 @@
|
|
22
22
|
|
23
23
|
module RightSupport
|
24
24
|
#
|
25
|
-
# A namespace for
|
25
|
+
# A namespace for data-processing tools.
|
26
26
|
#
|
27
|
-
module
|
27
|
+
module Data
|
28
28
|
|
29
29
|
end
|
30
30
|
end
|
31
31
|
|
32
|
-
require 'right_support/
|
33
|
-
require 'right_support/config/yaml_config'
|
34
|
-
require 'right_support/config/recursive_true_class'
|
32
|
+
require 'right_support/data/uuid'
|
@@ -0,0 +1,175 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2011 RightScale Inc
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
|
23
|
+
require 'set'
|
24
|
+
|
25
|
+
module RightSupport::Data
|
26
|
+
module UUID
|
27
|
+
# Exception that indicates a given implementation is unavailable.
|
28
|
+
class Unavailable < Exception; end
|
29
|
+
|
30
|
+
# The base class for all UUID implementations. Subclasses must override
|
31
|
+
# #initialize such that they raise an exception if the implementation is
|
32
|
+
# not available, and #generate such that it returns a String containing
|
33
|
+
# a UUID.
|
34
|
+
class Implementation
|
35
|
+
@subclasses = Set.new
|
36
|
+
|
37
|
+
def self.inherited(klass)
|
38
|
+
@subclasses << klass
|
39
|
+
end
|
40
|
+
|
41
|
+
# Determine which UUID implementations are available in this Ruby VM.
|
42
|
+
#
|
43
|
+
# === Return
|
44
|
+
# available(Array):: the available implementation classes
|
45
|
+
def self.available
|
46
|
+
avail = []
|
47
|
+
|
48
|
+
@subclasses.each do |eng|
|
49
|
+
begin
|
50
|
+
eng.new #if it initializes, it's available!
|
51
|
+
avail << eng
|
52
|
+
rescue Exception => e
|
53
|
+
#must not be available!
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Determine the default UUID implementation in this Ruby VM.
|
59
|
+
#
|
60
|
+
# === Return
|
61
|
+
# available(Array):: the available implementation classes
|
62
|
+
def self.default
|
63
|
+
#completely arbitrary
|
64
|
+
available.first
|
65
|
+
end
|
66
|
+
|
67
|
+
def generate
|
68
|
+
raise Unavailable, "This implementation is currently not available"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# An implementation that uses the SimpleUUID gem.
|
73
|
+
class SimpleUUID < Implementation
|
74
|
+
def initialize
|
75
|
+
require 'simple_uuid'
|
76
|
+
generate
|
77
|
+
end
|
78
|
+
|
79
|
+
def generate
|
80
|
+
::SimpleUUID::UUID.new.to_guid
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# An implementation that uses UUIDTools v1.
|
85
|
+
class UUIDTools1 < Implementation
|
86
|
+
def initialize
|
87
|
+
require 'uuidtools'
|
88
|
+
generate
|
89
|
+
end
|
90
|
+
|
91
|
+
def generate
|
92
|
+
::UUID.timestamp_create.to_s
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# An implementation that uses UUIDTools v2.
|
97
|
+
class UUIDTools2 < Implementation
|
98
|
+
def initialize
|
99
|
+
require 'uuidtools'
|
100
|
+
generate
|
101
|
+
end
|
102
|
+
|
103
|
+
def generate
|
104
|
+
::UUIDTools::UUID.timestamp_create.to_s
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# An implementation that uses the "uuid" gem.
|
109
|
+
class UUIDGem < Implementation
|
110
|
+
def initialize
|
111
|
+
require 'uuid'
|
112
|
+
generate
|
113
|
+
end
|
114
|
+
|
115
|
+
def generate
|
116
|
+
::UUID.new.to_s
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
module_function
|
121
|
+
|
122
|
+
def generate
|
123
|
+
impl = implementation
|
124
|
+
raise Unavailable, "No implementation is available" unless impl
|
125
|
+
impl.generate
|
126
|
+
end
|
127
|
+
|
128
|
+
# Return the implementation that will be used to generate UUIDs.
|
129
|
+
#
|
130
|
+
# === Return
|
131
|
+
# The implementation object.
|
132
|
+
#
|
133
|
+
# === Raise
|
134
|
+
# Unavailable:: if no suitable implementation is available
|
135
|
+
#
|
136
|
+
def implementation
|
137
|
+
return @implementation if @implementation
|
138
|
+
|
139
|
+
if (defl = Implementation.default)
|
140
|
+
self.implementation = defl
|
141
|
+
else
|
142
|
+
raise Unavailable, "No implementation is available"
|
143
|
+
end
|
144
|
+
|
145
|
+
@implementation
|
146
|
+
end
|
147
|
+
|
148
|
+
# Set the implementation that will be used to generate UUIDs.
|
149
|
+
#
|
150
|
+
# === Parameters
|
151
|
+
# klass(inherits-from Implementation):: the implementation class
|
152
|
+
#
|
153
|
+
# === Return
|
154
|
+
# The class that was passed as a parameter
|
155
|
+
#
|
156
|
+
# === Raise
|
157
|
+
# Unavailable:: if the specified implementation is not available
|
158
|
+
# ArgumentError:: if klass is not a subclass of Implementation, or is not available
|
159
|
+
#
|
160
|
+
def implementation=(klass)
|
161
|
+
if klass.is_a?(Implementation)
|
162
|
+
@implementation = klass
|
163
|
+
elsif klass.is_a?(Class) && klass.ancestors.include?(Implementation)
|
164
|
+
@implementation = klass.new
|
165
|
+
elsif klass.nil?
|
166
|
+
@implementation = nil
|
167
|
+
else
|
168
|
+
raise ArgumentError, "Expected Implementation instance or subclass, got #{klass}"
|
169
|
+
end
|
170
|
+
rescue Exception => e
|
171
|
+
raise Unavailable, "#{klass} is not available due to a #{e.class}: #{e.message}"
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
end
|
data/lib/right_support/log.rb
CHANGED
@@ -31,7 +31,6 @@ end
|
|
31
31
|
|
32
32
|
require 'right_support/log/system_logger'
|
33
33
|
require 'right_support/log/filter_logger'
|
34
|
-
require 'right_support/log/tag_logger'
|
35
34
|
require 'right_support/log/null_logger'
|
36
35
|
require 'right_support/log/multiplexer'
|
37
36
|
require 'right_support/log/exception_logger'
|
@@ -23,7 +23,7 @@
|
|
23
23
|
require 'logger'
|
24
24
|
|
25
25
|
module RightSupport::Log
|
26
|
-
|
26
|
+
if require_succeeds?('syslog')
|
27
27
|
# A logger that forwards log entries to the Unix syslog facility, but complies
|
28
28
|
# with the interface of the Ruby Logger object and faithfully translates log
|
29
29
|
# severities and other concepts. Provides optional cleanup/filtering in order
|
data/lib/right_support/net.rb
CHANGED
@@ -32,7 +32,7 @@ end
|
|
32
32
|
require 'right_support/net/address_helper'
|
33
33
|
require 'right_support/net/http_client'
|
34
34
|
require 'right_support/net/string_encoder'
|
35
|
-
require 'right_support/net/
|
35
|
+
require 'right_support/net/lb'
|
36
36
|
require 'right_support/net/request_balancer'
|
37
37
|
require 'right_support/net/ssl'
|
38
38
|
require 'right_support/net/dns'
|
@@ -1,12 +1,12 @@
|
|
1
1
|
module RightSupport::Net
|
2
|
-
|
2
|
+
if require_succeeds?('right_http_connection')
|
3
3
|
#nothing, nothing at all! just need to make sure
|
4
4
|
#that RightHttpConnection gets loaded before
|
5
5
|
#rest-client, so the Net::HTTP monkey patches
|
6
6
|
#take effect.
|
7
7
|
end
|
8
8
|
|
9
|
-
|
9
|
+
if require_succeeds?('restclient')
|
10
10
|
HAS_REST_CLIENT = true
|
11
11
|
end
|
12
12
|
|
@@ -24,31 +24,74 @@ module RightSupport::Net
|
|
24
24
|
#
|
25
25
|
#
|
26
26
|
# HTTPClient is a thin wrapper around the RestClient::Request class, with a few minor changes to its
|
27
|
-
# interface:
|
28
|
-
# * initializer accepts some default request options that can be overridden
|
29
|
-
#
|
30
|
-
#
|
31
|
-
#
|
27
|
+
# interface, namely:
|
28
|
+
# * initializer accepts some default request options that can be overridden
|
29
|
+
# per-request
|
30
|
+
# * it has discrete methods for get/put/post/delete, instead of a single
|
31
|
+
# "request" method
|
32
|
+
# * it supports explicit :query and :payload options for query-string and
|
33
|
+
# request-body, and understands the Rails convention for encoding a
|
34
|
+
# nested Hash into request parameters.
|
35
|
+
#
|
36
|
+
# == Request Parameters
|
37
|
+
# You can include a query-string with your request by passing the :query
|
38
|
+
# option to any of the request methods. You can pass a Hash, which will
|
39
|
+
# be translated to a URL-encoded query string using the Rails convention
|
40
|
+
# for nesting. Or, you can pass a String which will be appended verbatim
|
41
|
+
# to the URL. (In this case, don't forget to CGI.escape your string!)
|
42
|
+
#
|
43
|
+
# To include a form with your request, pass the :payload option to any
|
44
|
+
# request method. You can pass a Hash, which will be translated to an
|
45
|
+
# x-www-form-urlencoded request body using the Rails convention for
|
46
|
+
# nesting. Or, you can pass a String which will be appended verbatim
|
47
|
+
# to the URL. You can even use a binary String combined with a
|
48
|
+
# suitable request-content-type header to pass binary data in the
|
49
|
+
# payload. (In this case, be very careful about String encoding under
|
50
|
+
# Ruby 1.9!)
|
51
|
+
#
|
52
|
+
# == Usage Examples
|
53
|
+
#
|
54
|
+
# # Create an instance ot HTTPClient with some default request options
|
32
55
|
# @client = HTTPClient.new()
|
33
56
|
#
|
34
|
-
# # GET
|
57
|
+
# # Simple GET
|
35
58
|
# xml = @client.get 'http://example.com/resource'
|
36
|
-
# # and, with timeout of 5 seconds...
|
37
|
-
# jpg = @client.get 'http://example.com/resource', {:accept => 'image/jpg', :timeout => 5}
|
38
|
-
#
|
39
|
-
# # authentication and SSL
|
40
|
-
# @client.get 'https://user:password@example.com/private/resource'
|
41
59
|
#
|
42
|
-
# #
|
43
|
-
# @client.
|
60
|
+
# # And, with timeout of 5 seconds...
|
61
|
+
# jpg = @client.get 'http://example.com/resource',
|
62
|
+
# {:accept => 'image/jpg', :timeout => 5}
|
44
63
|
#
|
45
|
-
# #
|
46
|
-
# @client.
|
64
|
+
# # Doing some client authentication and SSL.
|
65
|
+
# @client.get 'https://user:password@example.com/private/resource'
|
66
|
+
#
|
67
|
+
# # The :query option will be encoded as a URL query-string using Rails
|
68
|
+
# # nesting convention (e.g. "a[b]=c" for this case).
|
69
|
+
# @client.get 'http://example.com', :query=>{:a=>{:b=>'c'}}
|
70
|
+
#
|
71
|
+
# # The :payload option specifies the request body. You can specify a raw
|
72
|
+
# # payload:
|
73
|
+
# @client.post 'http://example.com/resource', :payload=>'hi hi hi lol lol'
|
74
|
+
#
|
75
|
+
# # Or, you can specify a Hash payload which will be translated to a
|
76
|
+
# # x-www-form-urlencoded request body using the Rails nesting convention.
|
77
|
+
# # (e.g. "a[b]=c" for this case)
|
78
|
+
# @client.post 'http://example.com/resource', :payload=>{:d=>{:e=>'f'}}
|
79
|
+
#
|
80
|
+
# # You can specify query and/or payload for any HTTP verb, even if it
|
81
|
+
# # defies convention (be careful!)
|
82
|
+
# @client.post 'http://example.com/resource',
|
83
|
+
# :query => {:query_string_param=>'hi'}
|
84
|
+
# :payload => {:form_param=>'hi'}, :timeout => 10
|
47
85
|
#
|
48
86
|
# # POST and PUT with raw payloads
|
49
|
-
# @client.post 'http://example.com/resource',
|
50
|
-
#
|
51
|
-
#
|
87
|
+
# @client.post 'http://example.com/resource',
|
88
|
+
# {:payload => 'the post body',
|
89
|
+
# :headers => {:content_type => 'text/plain'}}
|
90
|
+
# @client.post 'http://example.com/resource.xml',
|
91
|
+
# {:payload => xml_doc}
|
92
|
+
# @client.put 'http://example.com/resource.pdf',
|
93
|
+
# {:payload => File.read('my.pdf'),
|
94
|
+
# :headers => {:content_type => 'application/pdf'}}
|
52
95
|
#
|
53
96
|
# # DELETE
|
54
97
|
# @client.delete 'http://example.com/resource'
|
@@ -58,18 +101,20 @@ module RightSupport::Net
|
|
58
101
|
# res.code # => 200
|
59
102
|
# res.headers[:content_type] # => 'image/jpg'
|
60
103
|
class HTTPClient
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
104
|
+
# The default options for every request; can be overridden by options
|
105
|
+
# passed to #initialize or to the individual request methods (#get,
|
106
|
+
# #post, and so forth).
|
107
|
+
DEFAULT_OPTIONS = {
|
108
|
+
:timeout => 5,
|
109
|
+
:open_timeout => 2,
|
110
|
+
:headers => {}
|
111
|
+
}
|
112
|
+
|
65
113
|
def initialize(defaults = {})
|
66
|
-
@defaults = defaults
|
67
|
-
@defaults[:timeout] ||= DEFAULT_TIMEOUT
|
68
|
-
@defaults[:open_timeout] ||= DEFAULT_OPEN_TIMEOUT
|
69
|
-
@defaults[:headers] ||= {}
|
114
|
+
@defaults = DEFAULT_OPTIONS.merge(defaults)
|
70
115
|
end
|
71
116
|
|
72
|
-
def get(*args)
|
117
|
+
def get(*args)
|
73
118
|
request(:get, *args)
|
74
119
|
end
|
75
120
|
|
@@ -94,7 +139,8 @@ module RightSupport::Net
|
|
94
139
|
# === Options
|
95
140
|
# This method can accept any of the options that RestClient::Request can accept, since
|
96
141
|
# all options are proxied through after merging in defaults, etc. Interesting options:
|
97
|
-
# * :
|
142
|
+
# * :query - hash containing a query string (GET parameters) as a Hash or String
|
143
|
+
# * :payload - hash containing the request body (POST or PUT parameters) as a Hash or String
|
98
144
|
# * :headers - hash containing additional HTTP request headers
|
99
145
|
# * :cookies - will replace possible cookies in the :headers
|
100
146
|
# * :user and :password - for basic auth, will be replaced by a user/password available in the url
|
@@ -108,13 +154,53 @@ module RightSupport::Net
|
|
108
154
|
#
|
109
155
|
def request(type, url, options={}, &block)
|
110
156
|
options = @defaults.merge(options)
|
157
|
+
|
158
|
+
# Handle query-string option which is implemented by us, not by rest-client.
|
159
|
+
# (rest-client version of this, :headers={:params=>...}) but it
|
160
|
+
# is broken in many ways and not suitable for use!)
|
161
|
+
if query = options.delete(:query)
|
162
|
+
url = process_query_string(url, query)
|
163
|
+
end
|
164
|
+
|
111
165
|
options.merge!(:method => type, :url => url)
|
112
166
|
|
113
167
|
request_internal(options, &block)
|
114
168
|
end
|
115
169
|
|
116
|
-
|
117
|
-
|
170
|
+
private
|
171
|
+
|
172
|
+
# Process a query-string option and append it to the URL as a properly
|
173
|
+
# encoded query string. The input must be either a String or Hash.
|
174
|
+
#
|
175
|
+
# === Parameters
|
176
|
+
# url(String):: the URL to request, including any query-string parameters
|
177
|
+
# query(Hash|String):: the URL params, that will be added to URL, Hash or String
|
178
|
+
#
|
179
|
+
# === Return
|
180
|
+
# Returns url with concated with parameters.
|
181
|
+
def process_query_string(url='', query={})
|
182
|
+
url_params = ''
|
183
|
+
|
184
|
+
if query.kind_of?(String)
|
185
|
+
url_params = query.gsub(/^\?/, '')
|
186
|
+
elsif query.kind_of?(Hash)
|
187
|
+
if require_succeeds?('addressable/uri')
|
188
|
+
uri = Addressable::URI.new
|
189
|
+
uri.query_values = query
|
190
|
+
url_params = uri.query
|
191
|
+
else
|
192
|
+
url_params = query.collect { |k, v| "#{CGI.escape(k.to_s)}=#{CGI.escape(v.to_s)}" }.join('&')
|
193
|
+
end
|
194
|
+
else
|
195
|
+
raise ArgumentError.new("Parameter query should be String or Hash")
|
196
|
+
end
|
197
|
+
unless (url+url_params)[/\?/]
|
198
|
+
url_params = '?' + url_params unless url_params.empty?
|
199
|
+
end
|
200
|
+
|
201
|
+
url + url_params
|
202
|
+
end
|
203
|
+
|
118
204
|
# Wrapper around RestClient::Request.execute -- see class documentation for details.
|
119
205
|
def request_internal(options, &block)
|
120
206
|
if HAS_REST_CLIENT
|