slash 0.4.4

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.
@@ -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