slash 0.4.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Igor Gunko
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,11 @@
1
+ == slash
2
+ @resource = Slash::Resource.new('http://api.something.com',
3
+ Slash::Formats.xml(:codec => Slash::Formats::PeanutsXML.new(Response)),
4
+ rewrite_options(options).update(:key => API_KEY))
5
+
6
+ cats = @resource['cats']
7
+ cats.get
8
+ cats.create(:name => 'Tom')
9
+ cats[123].update(:name => 'Tim')
10
+ cats[123].destroy
11
+
@@ -0,0 +1,29 @@
1
+ $KCODE = 'u'
2
+
3
+ require 'rubygems'
4
+ require 'rake'
5
+ require 'rake/clean'
6
+ require 'rake/gempackagetask'
7
+ require 'rake/rdoctask'
8
+ require 'spec/rake/spectask'
9
+
10
+ Rake::GemPackageTask.new(Gem::Specification.load('slash.gemspec')) do |p|
11
+ p.need_tar = true
12
+ p.need_zip = true
13
+ end
14
+
15
+ Rake::RDocTask.new do |rdoc|
16
+ files =['README.rdoc', 'MIT-LICENSE', 'lib/**/*.rb']
17
+ rdoc.rdoc_files.add(files)
18
+ rdoc.main = "README.rdoc" # page to start on
19
+ rdoc.title = "Slash Documentation"
20
+ rdoc.rdoc_dir = 'doc/rdoc' # rdoc output folder
21
+ rdoc.options << '--line-numbers'
22
+ end
23
+
24
+ desc 'Run specs'
25
+ task :test => :spec
26
+
27
+ Spec::Rake::SpecTask.new do |t|
28
+ t.spec_files = FileList['spec/**/*.rb']
29
+ end
@@ -0,0 +1,11 @@
1
+ require 'extlib'
2
+ require 'slash/formats'
3
+ require 'slash/resource'
4
+
5
+ module Slash
6
+ USER_AGENT = 'Slash 0.4.4 (http://github.com/omg/slash)'
7
+
8
+ class << self
9
+ attr_accessor :logger
10
+ end
11
+ end
@@ -0,0 +1,122 @@
1
+ require 'forwardable'
2
+ require 'addressable/uri'
3
+ require 'slash/exceptions'
4
+
5
+
6
+ module Slash
7
+ class Queue
8
+ end
9
+
10
+ class Connection
11
+ extend Forwardable
12
+
13
+ attr_accessor :timeout, :proxy
14
+
15
+ # Execute a GET request.
16
+ # Used to get (find) resources.
17
+ def get(uri, options = {}, &block)
18
+ request(:get, uri, options, &block)
19
+ end
20
+
21
+ # Execute a DELETE request (see HTTP protocol documentation if unfamiliar).
22
+ # Used to delete resources.
23
+ def delete(uri, options = {}, &block)
24
+ request(:delete, uri, options, &block)
25
+ end
26
+
27
+ # Execute a PUT request.
28
+ # Used to update resources.
29
+ def put(uri, options = {}, &block)
30
+ request(:put, uri, options, &block)
31
+ end
32
+
33
+ # Execute a POST request.
34
+ # Used to create new resources.
35
+ def post(uri, options = {}, &block)
36
+ request(:post, uri, options, &block)
37
+ end
38
+
39
+ # Execute a HEAD request.
40
+ # Used to obtain meta-information about resources, such as whether they exist and their size (via response headers).
41
+ def head(uri, options = {}, &block)
42
+ request(:head, uri, options, &block)
43
+ end
44
+
45
+ private
46
+ def prepare_request(uri, options)
47
+ case options[:auth]
48
+ when nil, :basic
49
+ user, password = uri.normalized_user, uri.normalized_password
50
+ options.headers['Authorization'] = 'Basic ' + ["#{user}:#{ password}"].pack('m').delete("\r\n") if user || password
51
+ else
52
+ raise ArgumentError, 'unsupported auth'
53
+ end
54
+ end
55
+
56
+ # Handles response and error codes from remote service.
57
+ def handle_response(response)
58
+ response.exception = case response.code.to_i
59
+ when 301,302
60
+ Redirection.new(response)
61
+ when 200...400
62
+ nil
63
+ when 400
64
+ BadRequest.new(response)
65
+ when 401
66
+ UnauthorizedAccess.new(response)
67
+ when 403
68
+ ForbiddenAccess.new(response)
69
+ when 404
70
+ ResourceNotFound.new(response)
71
+ when 405
72
+ MethodNotAllowed.new(response)
73
+ when 409
74
+ ResourceConflict.new(response)
75
+ when 410
76
+ ResourceGone.new(response)
77
+ when 422
78
+ ResourceInvalid.new(response)
79
+ when 401...500
80
+ ClientError.new(response)
81
+ when 500...600
82
+ ServerError.new(response)
83
+ else
84
+ ConnectionError.new(response, "Unknown response code: #{response.code}")
85
+ end
86
+ response
87
+ end
88
+
89
+ def check_and_raise(response)
90
+ raise response.exception if response.exception
91
+ response
92
+ end
93
+
94
+ def logger #:nodoc:
95
+ Slash.logger
96
+ end
97
+
98
+ class << self
99
+ attr_writer :default
100
+
101
+ def default(&block)
102
+ @default = block if block_given?
103
+ @default
104
+ end
105
+
106
+ def create_default
107
+ if @default.respond_to?(:new)
108
+ return @default.new
109
+ else
110
+ return @default.call
111
+ end
112
+ end
113
+ end
114
+
115
+ self.default { NetHttpConnection.new }
116
+ end
117
+
118
+ autoload :NetHttpConnection, 'slash/nethttp'
119
+
120
+ autoload :TyphoeusConnection, 'slash/typhoeus'
121
+ autoload :TyphoeusQueue, 'slash/typhoeus'
122
+ end
@@ -0,0 +1,66 @@
1
+ module Slash
2
+ class ConnectionError < StandardError # :nodoc:
3
+ attr_reader :response
4
+
5
+ def initialize(response, message = nil)
6
+ @response = response
7
+ @message = message
8
+ end
9
+
10
+ def to_s
11
+ "Failed with #{response.code} #{response.message if response.respond_to?(:message)}"
12
+ end
13
+ end
14
+
15
+ # Raised when a Timeout::Error occurs.
16
+ class TimeoutError < ConnectionError
17
+ def initialize(message)
18
+ @message = message
19
+ end
20
+ def to_s; @message ;end
21
+ end
22
+
23
+ # Raised when a OpenSSL::SSL::SSLError occurs.
24
+ class SSLError < ConnectionError
25
+ def initialize(message)
26
+ @message = message
27
+ end
28
+ def to_s; @message ;end
29
+ end
30
+
31
+ # 3xx Redirection
32
+ class Redirection < ConnectionError # :nodoc:
33
+ def to_s; response.headers['Location'] ? "#{super} => #{response.headers['Location']}" : super; end
34
+ end
35
+
36
+ # 4xx Client Error
37
+ class ClientError < ConnectionError; end # :nodoc:
38
+
39
+ # 400 Bad Request
40
+ class BadRequest < ClientError; end # :nodoc
41
+
42
+ # 401 Unauthorized
43
+ class UnauthorizedAccess < ClientError; end # :nodoc
44
+
45
+ # 403 Forbidden
46
+ class ForbiddenAccess < ClientError; end # :nodoc
47
+
48
+ # 404 Not Found
49
+ class ResourceNotFound < ClientError; end # :nodoc:
50
+
51
+ # 409 Conflict
52
+ class ResourceConflict < ClientError; end # :nodoc:
53
+
54
+ # 410 Gone
55
+ class ResourceGone < ClientError; end # :nodoc:
56
+
57
+ # 5xx Server Error
58
+ class ServerError < ConnectionError; end # :nodoc:
59
+
60
+ # 405 Method Not Allowed
61
+ class MethodNotAllowed < ClientError # :nodoc:
62
+ def allowed_methods
63
+ @response['Allow'].split(',').map { |verb| verb.strip.downcase.to_sym }
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,67 @@
1
+ module Slash
2
+ module Formats
3
+ autoload :JSON, 'slash/json'
4
+ autoload :PeanutsXML, 'slash/peanuts'
5
+
6
+ def self.xml(options = {})
7
+ Format.new({:mime => 'application/xml'}.update(options))
8
+ end
9
+
10
+ def self.json(options = {})
11
+ options = {:mime => 'application/json'}.update(options)
12
+ options[:codec] ||= JSON
13
+ Format.new(options)
14
+ end
15
+
16
+ class Format
17
+ attr_reader :mime, :codec
18
+
19
+ def initialize(options)
20
+ @codec = options[:codec]
21
+ @mime = options[:mime] || (@codec.respond_to?(:mime) ? @codec.mime : nil)
22
+ end
23
+
24
+ def prepare_request(options)
25
+ headers = options[:headers]
26
+ headers['Accept'] = mime if mime
27
+ data = options.delete(:data)
28
+ if data
29
+ options[:body] = codec.encode(data)
30
+ headers['Content-Type'] = mime if mime
31
+ end
32
+ options
33
+ end
34
+
35
+ def interpret_response(response)
36
+ bs = response.body_stream
37
+ bs && codec.decode(bs)
38
+ end
39
+ end
40
+
41
+ class WithSuffix < Format
42
+ attr_reader :suffix
43
+
44
+ def initialize(mime, suffix, codec)
45
+ super(mime, codec)
46
+ @suffix = suffix
47
+ end
48
+
49
+ def prepare_request(options, &block)
50
+ options[:path] += suffix if suffix
51
+ super
52
+ end
53
+
54
+ def self.xml(options = {})
55
+ WithSuffix.new(options.fetch(:mime, 'application/xml'),
56
+ options.fetch(:suffix, '.xml'),
57
+ options.fetch(:codec))
58
+ end
59
+
60
+ def self.json(options = {})
61
+ WithSuffix.new(options.fetch(:mime, 'application/json'),
62
+ options.fetch(:suffix, '.json'),
63
+ options.fetch(:codec) { JSON })
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,16 @@
1
+ require 'json'
2
+ require 'slash/formats'
3
+
4
+ module Slash
5
+ module Formats
6
+ class JSON
7
+ def encode(data)
8
+ ::JSON.generate(data)
9
+ end
10
+
11
+ def decode(data)
12
+ ::JSON.parse(data.read)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,153 @@
1
+ require 'net/https'
2
+ require 'date'
3
+ require 'time'
4
+ require 'benchmark'
5
+ require 'stringio'
6
+ require 'slash/connection'
7
+
8
+
9
+ module Slash
10
+ # Class to handle connections to remote web services.
11
+ # This class is used by ActiveResource::Base to interface with REST
12
+ # services.
13
+ class NetHttpConnection < Connection
14
+ @@request_types = {
15
+ :get => Net::HTTP::Get,
16
+ :post => Net::HTTP::Post,
17
+ :put => Net::HTTP::Put,
18
+ :delete => Net::HTTP::Delete
19
+ }
20
+
21
+ def self.request_types
22
+ @@request_types
23
+ end
24
+
25
+ attr_reader :proxy, :ssl_options
26
+
27
+ # Set the proxy for remote service.
28
+ def proxy=(proxy)
29
+ @http = nil
30
+ @proxy = proxy.is_a?(URI) ? proxy : URI.parse(proxy)
31
+ end
32
+
33
+ # Set the number of seconds after which HTTP requests to the remote service should time out.
34
+ def timeout=(timeout)
35
+ @timeout = timeout
36
+ configure_http(@http) if @http
37
+ end
38
+
39
+ # Hash of options applied to Net::HTTP instance when +site+ protocol is 'https'.
40
+ def ssl_options=(opts={})
41
+ @ssl_options = opts
42
+ configure_http(@http) if @http
43
+ end
44
+
45
+ def request(method, uri, options = {})
46
+ raise ArgumentError, 'this connection does not support async mode' if options[:async]
47
+
48
+ options = options.dup
49
+ prepare_request(uri, options)
50
+
51
+ rqtype = @@request_types[method] || raise(ArgumentError, "Unsupported method #{method}")
52
+ params = options[:params]
53
+ if !params.blank?
54
+ if [:post, :put].include?(method)
55
+ form_data = params
56
+ else
57
+ uri = uri.dup
58
+ uri.query_values = (uri.query_values(:notation => :flat) || {}).to_mash.update(params)
59
+ end
60
+ end
61
+ rq = rqtype.new(uri.query.blank? ? uri.path : "#{uri.path}?#{uri.query}", options[:headers])
62
+ rq.form_data = form_data if form_data
63
+ rq.body = options[:body] if options[:body]
64
+
65
+ resp = http_request(uri, rq)
66
+ if block_given?
67
+ yield resp
68
+ else
69
+ check_and_raise(resp)
70
+ end
71
+ end
72
+
73
+ private
74
+ # Makes request to remote service.
75
+ def http_request(uri, rq)
76
+ logger.debug "#{rq.method.to_s.upcase} #{uri}" if logger
77
+ result = nil
78
+ ms = 1000 * Benchmark.realtime { result = http(uri).request(rq) }
79
+ logger.debug "--> %d %s (%d %.0fms)" % [result.code, result.message, result.body ? result.body.length : 0, ms] if logger
80
+ augment_response(result)
81
+ rescue Timeout::Error => e
82
+ raise TimeoutError.new(e.message)
83
+ rescue OpenSSL::SSL::SSLError => e
84
+ raise SSLError.new(e.message)
85
+ end
86
+
87
+ def augment_response(response)
88
+ class << response
89
+ attr_accessor :exception
90
+ alias headers to_hash
91
+ def body_stream
92
+ body && StringIO.new(body)
93
+ end
94
+ def success?
95
+ exception.nil?
96
+ end
97
+ end
98
+ handle_response(response)
99
+ end
100
+
101
+ # Creates new Net::HTTP instance for communication with
102
+ # remote service and resources.
103
+ def http(uri)
104
+ if !@http || @host != uri.normalized_host || @port != uri.inferred_port || @scheme != uri.normalized_scheme
105
+ @host, @port, @scheme = uri.normalized_host, uri.inferred_port, uri.normalized_scheme
106
+ @http = configure_http(new_http)
107
+ end
108
+ @http
109
+ end
110
+
111
+ def new_http
112
+ if @proxy
113
+ Net::HTTP.new(@host, @port, @proxy.host, @proxy.port, @proxy.user, @proxy.password)
114
+ else
115
+ Net::HTTP.new(@host, @port)
116
+ end
117
+ end
118
+
119
+ def configure_http(http)
120
+ http = apply_ssl_options(http)
121
+
122
+ # Net::HTTP timeouts default to 60 seconds.
123
+ if @timeout
124
+ http.open_timeout = http.read_timeout = @timeout
125
+ end
126
+
127
+ http
128
+ end
129
+
130
+ def apply_ssl_options(http)
131
+ return http unless @scheme == 'https'
132
+
133
+ http.use_ssl = true
134
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
135
+ return http unless defined?(@ssl_options)
136
+
137
+ http.ca_path = @ssl_options[:ca_path] if @ssl_options[:ca_path]
138
+ http.ca_file = @ssl_options[:ca_file] if @ssl_options[:ca_file]
139
+
140
+ http.cert = @ssl_options[:cert] if @ssl_options[:cert]
141
+ http.key = @ssl_options[:key] if @ssl_options[:key]
142
+
143
+ http.cert_store = @ssl_options[:cert_store] if @ssl_options[:cert_store]
144
+ http.ssl_timeout = @ssl_options[:ssl_timeout] if @ssl_options[:ssl_timeout]
145
+
146
+ http.verify_mode = @ssl_options[:verify_mode] if @ssl_options[:verify_mode]
147
+ http.verify_callback = @ssl_options[:verify_callback] if @ssl_options[:verify_callback]
148
+ http.verify_depth = @ssl_options[:verify_depth] if @ssl_options[:verify_depth]
149
+
150
+ http
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,24 @@
1
+ require 'peanuts'
2
+ require 'slash/formats'
3
+
4
+ module Slash
5
+ module Formats
6
+ class PeanutsXML
7
+ attr_reader :response_type
8
+ attr_accessor :to_xml_options, :from_xml_options
9
+
10
+ def initialize(response_type, from_xml_options = {}, to_xml_options = {})
11
+ @response_type = response_type
12
+ @to_xml_options, @from_xml_options = to_xml_options, from_xml_options
13
+ end
14
+
15
+ def encode(data)
16
+ data.to_xml(:string, to_xml_options)
17
+ end
18
+
19
+ def decode(data)
20
+ response_type.from_xml(data, from_xml_options)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,195 @@
1
+ require 'forwardable'
2
+ require 'addressable/uri'
3
+ require 'slash/connection'
4
+ require 'slash/formats'
5
+
6
+
7
+ module Slash
8
+ class Resource
9
+ class Response
10
+ extend Forwardable
11
+
12
+ def initialize(result, response, exception)
13
+ @result, @response, @exception = result, response, exception
14
+ end
15
+
16
+ attr_reader :result, :response, :exception
17
+
18
+ def_delegators :response, :code, :headers
19
+
20
+ def result!
21
+ raise exception if exception
22
+ result
23
+ end
24
+
25
+ def success?
26
+ exception.nil?
27
+ end
28
+ end
29
+
30
+ extend Forwardable
31
+
32
+ attr_accessor :connection, :uri, :params, :headers, :user, :password, :timeout, :proxy
33
+
34
+ def_delegator :uri, :path
35
+ def_delegator :uri, :query_values, :query
36
+
37
+ def user_agent
38
+ headers['User-Agent']
39
+ end
40
+
41
+ def user_agent=(value)
42
+ headers['User-Agent'] = value
43
+ end
44
+
45
+ def self.new!(*args, &block)
46
+ r = allocate
47
+ r.send(:initialize!, *args, &block)
48
+ r
49
+ end
50
+
51
+ def initialize(connection, uri, options = {})
52
+ @connection = connection
53
+ @uri = Addressable::URI.parse(uri)
54
+ query = options[:query]
55
+ unless query.blank?
56
+ @uri = @uri.dup
57
+ uq = @uri.query_values
58
+ @uri.query_values = uq ? uq.merge(query) : query
59
+ end
60
+ @params, @headers = (options[:params] || {}).to_mash, options[:headers] || {}
61
+ self.user_agent ||= options[:user_agent] || Slash::USER_AGENT
62
+ end
63
+
64
+ def initialize!(from, options)
65
+ @connection = from.connection
66
+ options = _merge(from, options)
67
+ @uri, @params, @headers = options[:uri], options[:params], options[:headers]
68
+ end
69
+ private :initialize!
70
+
71
+ def slash(options = {})
72
+ self.class.new!(self, options)
73
+ end
74
+
75
+ def [](path)
76
+ slash(:path => path)
77
+ end
78
+
79
+ # Execute a GET request.
80
+ # Used to get (find) resources.
81
+ def get(options = {}, &block)
82
+ request(options.merge(:method => :get), &block)
83
+ end
84
+
85
+ # Execute a DELETE request (see HTTP protocol documentation if unfamiliar).
86
+ # Used to delete resources.
87
+ def delete(options = {}, &block)
88
+ request(options.merge(:method => :delete), &block)
89
+ end
90
+
91
+ # Execute a PUT request.
92
+ # Used to update resources.
93
+ def put(options = {}, &block)
94
+ request(options.merge(:method => :put), &block)
95
+ end
96
+
97
+ # Execute a POST request.
98
+ # Used to create new resources.
99
+ def post(options = {}, &block)
100
+ request(options.merge(:method => :post), &block)
101
+ end
102
+
103
+ # Execute a HEAD request.
104
+ # Used to obtain meta-information about resources, such as whether they exist and their size (via response headers).
105
+ def head(options = {}, &block)
106
+ request(options.merge(:method => :head), &block)
107
+ end
108
+
109
+ def request(options)
110
+ rq = prepare_request(merge(options))
111
+ connection.request(rq.delete(:method), rq.delete(:uri), rq) do |response|
112
+ resp = handle_response(response)
113
+ block_given? ? yield(resp) : resp
114
+ end
115
+ end
116
+
117
+ private
118
+ def prepare_request(options)
119
+ options[:body] = options.delete(:data).to_s
120
+ options
121
+ end
122
+
123
+ def handle_response(response)
124
+ begin
125
+ exception = response.exception
126
+ rescue => e
127
+ exception = e
128
+ end
129
+ Response.new(prepare_result(response), response, exception)
130
+ end
131
+
132
+ def prepare_result(response)
133
+ response.body
134
+ end
135
+
136
+ def merge(options, &block)
137
+ _merge(self, options, &block)
138
+ end
139
+
140
+ def _merge(from, options)
141
+ options = options.dup
142
+ path, query, params, headers = options[:path], options[:query], options[:params], options[:headers]
143
+
144
+ u = options[:uri] = from.uri.dup
145
+
146
+ uq = u.query_values(:notation => :flat)
147
+ uq = uq ? (query ? uq.to_mash.merge(query) : uq) : query
148
+ if path
149
+ upath = u.path
150
+ u.path = upath + '/' unless upath =~ /\/\z/
151
+ u.join!(path)
152
+ end
153
+ if uq
154
+ u.query_values = uq
155
+ else
156
+ u.query = nil
157
+ end
158
+
159
+ p = options[:params] = from.params.dup
160
+ p.merge!(params) unless params.blank?
161
+
162
+ h = options[:headers] = from.headers.dup
163
+ h.merge!(headers) unless headers.blank?
164
+
165
+ options
166
+ end
167
+ end
168
+
169
+ class SimpleResource < Resource
170
+ attr_accessor :format
171
+
172
+ def initialize(uri, options = {})
173
+ super(options[:connection] || create_connection, uri, options)
174
+ self.format = options[:format]
175
+ end
176
+
177
+ private
178
+ def initialize!(from, options)
179
+ super
180
+ self.format = from.format
181
+ end
182
+
183
+ def create_connection
184
+ Connection.create_default
185
+ end
186
+
187
+ def prepare_request(options)
188
+ format ? format.prepare_request(options) : super
189
+ end
190
+
191
+ def prepare_result(response)
192
+ response.success? && format ? format.interpret_response(response) : super
193
+ end
194
+ end
195
+ end
@@ -0,0 +1,86 @@
1
+ require 'typhoeus'
2
+ require 'forwardable'
3
+ require 'stringio'
4
+ require 'slash/connection'
5
+
6
+
7
+ module Slash
8
+ class TyphoeusQueue < Queue
9
+ extend Forwardable
10
+
11
+ def initialize(hydra_or_options = nil)
12
+ case hydra_or_options
13
+ when nil
14
+ @hydra = Typhoeus::Hydra.new
15
+ @hydra.disable_memoization
16
+ when Hash
17
+ @hydra = Typhoeus::Hydra.new(hydra_or_options)
18
+ @hydra.disable_memoization
19
+ else
20
+ @hydra = hydra_or_options
21
+ end
22
+ end
23
+
24
+ attr_accessor :hydra
25
+
26
+ def_delegator :hydra, :queue, :submit
27
+ def_delegator :hydra, :run
28
+ end
29
+
30
+ class TyphoeusConnection < Connection
31
+ def initialize(options = {})
32
+ @queue = options[:queue] || TyphoeusQueue.new
33
+ end
34
+
35
+ attr_accessor :queue
36
+
37
+ def request(method, uri, options = {})
38
+ options = options.dup
39
+ prepare_request(uri, options)
40
+
41
+ params, headers = options[:params], options[:headers]
42
+ rq = Typhoeus::Request.new(uri.to_s,
43
+ :method => method,
44
+ :headers => headers,
45
+ :params => !params.blank? ? params.inject({}) {|h, x| h[x[0].to_s] = x[1] || ''; h } : nil,
46
+ :body => options[:body],
47
+ :timeout => options[:timeout] || timeout,
48
+ :user_agent => headers['User-Agent']
49
+ )
50
+ ret = nil
51
+ rq.on_complete do |response|
52
+ if logger
53
+ logger.debug "%s %s --> %d (%d %.0fs)" % [rq.method.to_s.upcase, rq.url,
54
+ response.code, response.body ? response.body.length : 0, response.time]
55
+ end
56
+ ret = response = augment_response(response)
57
+ ret = yield response if block_given?
58
+ response
59
+ end
60
+ async = options[:async]
61
+ queue = [true, false, nil].include?(async) ? self.queue : async
62
+ queue.submit(rq)
63
+ if async
64
+ queue
65
+ else
66
+ queue.run
67
+ block_given? ? ret : check_and_raise(rq.handled_response)
68
+ end
69
+ end
70
+
71
+ private
72
+ def augment_response(response)
73
+ class << response
74
+ attr_accessor :exception
75
+ def body_stream
76
+ body && StringIO.new(body)
77
+ end
78
+ def success?
79
+ exception.nil?
80
+ end
81
+ end
82
+
83
+ handle_response(response)
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,15 @@
1
+ # To change this template, choose Tools | Templates
2
+ # and open the template in the editor.
3
+
4
+ require 'resource'
5
+
6
+ describe Slash::Resource do
7
+ before(:each) do
8
+
9
+ end
10
+
11
+ it "should desc" do
12
+ # TODO
13
+ end
14
+ end
15
+
metadata ADDED
@@ -0,0 +1,159 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: slash
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 4
8
+ - 4
9
+ version: 0.4.4
10
+ platform: ruby
11
+ authors:
12
+ - Igor Gunko
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-03-10 00:00:00 +02:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: extlib
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ~>
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 0
29
+ - 9
30
+ - 14
31
+ version: 0.9.14
32
+ type: :runtime
33
+ version_requirements: *id001
34
+ - !ruby/object:Gem::Dependency
35
+ name: addressable
36
+ prerelease: false
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ~>
40
+ - !ruby/object:Gem::Version
41
+ segments:
42
+ - 2
43
+ - 1
44
+ - 1
45
+ version: 2.1.1
46
+ type: :runtime
47
+ version_requirements: *id002
48
+ - !ruby/object:Gem::Dependency
49
+ name: rspec
50
+ prerelease: false
51
+ requirement: &id003 !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ~>
54
+ - !ruby/object:Gem::Version
55
+ segments:
56
+ - 1
57
+ - 2
58
+ - 8
59
+ version: 1.2.8
60
+ type: :development
61
+ version_requirements: *id003
62
+ - !ruby/object:Gem::Dependency
63
+ name: typhoeus
64
+ prerelease: false
65
+ requirement: &id004 !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ~>
68
+ - !ruby/object:Gem::Version
69
+ segments:
70
+ - 0
71
+ - 1
72
+ - 22
73
+ version: 0.1.22
74
+ type: :development
75
+ version_requirements: *id004
76
+ - !ruby/object:Gem::Dependency
77
+ name: peanuts
78
+ prerelease: false
79
+ requirement: &id005 !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ~>
82
+ - !ruby/object:Gem::Version
83
+ segments:
84
+ - 2
85
+ - 1
86
+ - 1
87
+ version: 2.1.1
88
+ type: :development
89
+ version_requirements: *id005
90
+ - !ruby/object:Gem::Dependency
91
+ name: json
92
+ prerelease: false
93
+ requirement: &id006 !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ~>
96
+ - !ruby/object:Gem::Version
97
+ segments:
98
+ - 1
99
+ - 2
100
+ - 2
101
+ version: 1.2.2
102
+ type: :development
103
+ version_requirements: *id006
104
+ description: " REST-client\n"
105
+ email: tekmon@gmail.com
106
+ executables: []
107
+
108
+ extensions: []
109
+
110
+ extra_rdoc_files:
111
+ - README.rdoc
112
+ - MIT-LICENSE
113
+ files:
114
+ - README.rdoc
115
+ - MIT-LICENSE
116
+ - Rakefile
117
+ - lib/slash.rb
118
+ - lib/slash/exceptions.rb
119
+ - lib/slash/connection.rb
120
+ - lib/slash/nethttp.rb
121
+ - lib/slash/typhoeus.rb
122
+ - lib/slash/resource.rb
123
+ - lib/slash/formats.rb
124
+ - lib/slash/json.rb
125
+ - lib/slash/peanuts.rb
126
+ has_rdoc: true
127
+ homepage: http://github.com/omg/slash
128
+ licenses: []
129
+
130
+ post_install_message:
131
+ rdoc_options:
132
+ - --line-numbers
133
+ - --main
134
+ - README.rdoc
135
+ require_paths:
136
+ - lib
137
+ required_ruby_version: !ruby/object:Gem::Requirement
138
+ requirements:
139
+ - - ">="
140
+ - !ruby/object:Gem::Version
141
+ segments:
142
+ - 0
143
+ version: "0"
144
+ required_rubygems_version: !ruby/object:Gem::Requirement
145
+ requirements:
146
+ - - ">="
147
+ - !ruby/object:Gem::Version
148
+ segments:
149
+ - 0
150
+ version: "0"
151
+ requirements: []
152
+
153
+ rubyforge_project:
154
+ rubygems_version: 1.3.6
155
+ signing_key:
156
+ specification_version: 2
157
+ summary: REST-client
158
+ test_files:
159
+ - spec/resource_spec.rb