right_support 1.4.1 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|