jeff 0.1.0 → 0.2.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.
@@ -1,7 +1,4 @@
1
1
  rvm:
2
- - 1.8.7
3
2
  - 1.9.3
4
- - jruby-18mode
5
3
  - jruby-19mode
6
- - rbx-18mode
7
4
  - rbx-19mode
@@ -0,0 +1,5 @@
1
+ guard 'rspec', :cli => '-fd' do
2
+ watch(%r{^spec/.+_spec\.rb$})
3
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
4
+ watch('spec/spec_helper.rb') { 'spec' }
5
+ end
data/README.md CHANGED
@@ -1,25 +1,71 @@
1
1
  # Jeff
2
2
 
3
- Jeff is a minimum-viable client for [Amazon Web Services (AWS) APIs][aws] that
4
- support [Signature Version 2][sign].
3
+ **Jeff** is a light-weight module that mixes in client behaviour for [Amazon
4
+ Web Services (AWS)][aws]. It wraps the HTTP adapter [Excon][excon] and
5
+ implements [Signature Version 2][sign].
6
+
7
+ ![jeff][jeff]
5
8
 
6
9
  ## Usage
7
10
 
11
+ Here's a hypothetical client.
12
+
13
+ ```ruby
14
+ class Client
15
+ include Jeff
16
+ end
17
+ ```
18
+
19
+ Customise default headers and parameters.
20
+
8
21
  ```ruby
9
- client = Jeff.new 'http://some-aws-url.com/'
10
- client << params
11
- client << data
22
+ class Client
23
+ headers 'User-Agent' => 'Client'
24
+ params 'Service' => 'SomeService',
25
+ 'Tag' => Proc.new { tag }
12
26
 
13
- client.request
27
+ attr_accessor :tag
28
+ end
14
29
  ```
15
30
 
16
- Stream responses.
31
+ Set an AWS endpoint and credentials.
17
32
 
18
33
  ```ruby
19
- client << ->(chunk, remaining, total) { puts chunk }
34
+ client = Client.new.tap do |config|
35
+ config.endpoint = 'http://example.com/path'
36
+ config.key = 'key'
37
+ config.secret = 'secret'
38
+ end
39
+ ```
40
+ You should now be able to access the endpoint.
20
41
 
21
- client.request
42
+ ```ruby
43
+ client.post query: {},
44
+ body: 'data'
22
45
  ```
23
46
 
24
- [aws]: http://aws.amazon.com/
25
- [sign]: http://docs.amazonwebservices.com/general/latest/gr/signature-version-2.html
47
+ Make a chunked request.
48
+
49
+ ```ruby
50
+ file = File.open 'data'
51
+ chunker = -> { file.read Excon::CHUNK_SIZE).to_s }
52
+
53
+ client.post query: {},
54
+ request_block: chunker
55
+ ```
56
+
57
+ Stream a response.
58
+
59
+ ```ruby
60
+ streamer = ->(chunk, remaining, total) { puts chunk }
61
+
62
+ client.get query: {},
63
+ response_block: streamer
64
+ ```
65
+
66
+ HTTP connections are persistent.
67
+
68
+ [aws]: http://aws.amazon.com/
69
+ [excon]: https://github.com/geemus/excon
70
+ [sign]: http://docs.amazonwebservices.com/general/latest/gr/signature-version-2.html
71
+ [jeff]: http://f.cl.ly/items/0a3R3J0k1R2f423k1q2l/jeff.jpg
@@ -5,8 +5,8 @@ require File.expand_path('../lib/jeff/version.rb', __FILE__)
5
5
  Gem::Specification.new do |gem|
6
6
  gem.authors = ['Hakan Ensari']
7
7
  gem.email = ['hakan.ensari@papercavalier.com']
8
- gem.description = %q{A minimum-viable Amazon Web Services (AWS) client}
9
- gem.summary = %q{An AWS client}
8
+ gem.description = %q{Minimum-viable Amazon Web Services (AWS) client}
9
+ gem.summary = %q{AWS client}
10
10
  gem.homepage = 'https://github.com/hakanensari/jeff'
11
11
 
12
12
  gem.files = `git ls-files`.split($\)
@@ -16,7 +16,9 @@ Gem::Specification.new do |gem|
16
16
  gem.require_paths = ['lib']
17
17
  gem.version = Jeff::VERSION
18
18
 
19
- gem.add_dependency 'excon', '~> 0.14'
20
- gem.add_development_dependency 'rake', '~> 0.9'
21
- gem.add_development_dependency 'rspec', '~> 2.10'
19
+ gem.add_dependency 'excon', '~> 0.14.2'
20
+ gem.add_development_dependency 'guard-rspec'
21
+ gem.add_development_dependency 'pry'
22
+ gem.add_development_dependency 'rake'
23
+ gem.add_development_dependency 'rspec'
22
24
  end
@@ -1,19 +1,168 @@
1
1
  require 'base64'
2
- require 'forwardable'
3
2
  require 'time'
4
3
 
5
4
  require 'excon'
6
5
 
7
6
  require 'jeff/version'
8
- require 'jeff/query_builder'
9
- require 'jeff/user_agent'
10
- require 'jeff/client'
11
- require 'jeff/signature'
12
7
 
13
8
  module Jeff
14
- class << self
15
- extend Forwardable
9
+ MissingEndpoint = Class.new ArgumentError
10
+ MissingKey = Class.new ArgumentError
11
+ MissingSecret = Class.new ArgumentError
16
12
 
17
- def_delegator Client, :new
13
+ SHA256 = OpenSSL::Digest::SHA256.new
14
+ UNRESERVED = /([^\w.~-]+)/
15
+
16
+ def self.included(base)
17
+ base.extend ClassMethods
18
+ end
19
+
20
+ # Returns an Excon::Connection.
21
+ def connection
22
+ @connection ||= Excon.new endpoint, :headers => default_headers
23
+ end
24
+
25
+ # Returns the Hash default request parameters.
26
+ def default_params
27
+ self.class.params.reduce({}) do |a, (k, v)|
28
+ a.update k => (v.is_a?(Proc) ? instance_eval(&v) : v)
29
+ end
30
+ end
31
+
32
+ # Returns the Hash default headers.
33
+ def default_headers
34
+ self.class.headers
35
+ end
36
+
37
+ # Gets the String AWS endpoint.
38
+ #
39
+ # Raises a MissingEndpoint error if endpoint is missing.
40
+ def endpoint
41
+ @endpoint or raise MissingEndpoint
42
+ end
43
+
44
+ # Sets the String AWS endpoint.
45
+ attr_writer :endpoint
46
+
47
+ # Gets the String AWS access key id.
48
+ #
49
+ # Raises a MissingKey error if key is missing.
50
+ def key
51
+ @key or raise MissingKey
52
+ end
53
+
54
+ # Sets the String AWS access key id.
55
+ attr_writer :key
56
+
57
+ # Gets the String AWS secret key.
58
+ #
59
+ # Raises a MissingSecret error if secret is missing.
60
+ def secret
61
+ @secret or raise MissingSecret
62
+ end
63
+
64
+ # Sets the String AWS secret key.
65
+ attr_writer :secret
66
+
67
+ # Generate HTTP request verb methods that sign queries and then delegate
68
+ # request to Excon.
69
+ Excon::HTTP_VERBS. each do |method|
70
+ eval <<-DEF
71
+ def #{method}(opts = {}, &block)
72
+ opts.update method: :#{method}
73
+ request opts, &block
74
+ end
75
+ DEF
76
+ end
77
+
78
+ # Internal: Builds a sorted query.
79
+ #
80
+ # hsh - A hash of parameters specific to request.
81
+ #
82
+ # Returns a query String.
83
+ def build_query(hsh)
84
+ default_params
85
+ .merge(hsh)
86
+ .map { |k, v| "#{k}=#{ escape v }" }
87
+ .sort
88
+ .join '&'
89
+ end
90
+
91
+ # Internal: Signs a message.
92
+ #
93
+ # message - A String to sign.
94
+ #
95
+ # Returns a String signature.
96
+ def sign(message)
97
+ digest = OpenSSL::HMAC.digest SHA256, secret, message
98
+ Base64.encode64(digest).chomp
99
+ end
100
+
101
+ private
102
+
103
+ def request(opts, &block)
104
+ query = build_query opts[:query] || {}
105
+ string_to_sign = [
106
+ opts[:method],
107
+ host,
108
+ path,
109
+ query
110
+ ].join "\n"
111
+ signature = sign string_to_sign
112
+ opts[:query] = [query, "Signature=#{escape signature}"].join '&'
113
+
114
+ connection.request opts, &block
115
+ end
116
+
117
+ def escape(val)
118
+ val.to_s.gsub(UNRESERVED) do
119
+ '%' + $1.unpack('H2' * $1.bytesize).join('%').upcase
120
+ end
121
+ end
122
+
123
+ def host
124
+ @host ||= url.host
125
+ end
126
+
127
+ def path
128
+ @path ||= url.path
129
+ end
130
+
131
+ def url
132
+ @url ||= URI endpoint
133
+ end
134
+
135
+ module ClassMethods
136
+ # Amazon recommends that libraries identify themselves via a User Agent.
137
+ USER_AGENT = "Jeff/#{VERSION} (Language=Ruby; #{`hostname`.chomp})"
138
+
139
+ # Gets/Updates the default headers.
140
+ #
141
+ # hsh - A Hash of headers.
142
+ #
143
+ # Returns the Hash headers.
144
+ def headers(hsh = nil)
145
+ @headers ||= { 'User-Agent' => USER_AGENT }
146
+ @headers.update hsh if hsh
147
+
148
+ @headers
149
+ end
150
+
151
+ # Gets/Updates the default request parameters.
152
+ #
153
+ # hsh - A Hash of parameters (default: nil).
154
+ #
155
+ # Returns the Hash parameters.
156
+ def params(hsh = nil)
157
+ @params ||= {
158
+ 'AWSAccessKeyId' => Proc.new { key },
159
+ 'SignatureVersion' => '2',
160
+ 'SignatureMethod' => 'HmacSHA256',
161
+ 'Timestamp' => Proc.new { Time.now.utc.iso8601 }
162
+ }
163
+ @params.update hsh if hsh
164
+
165
+ @params
166
+ end
18
167
  end
19
168
  end
@@ -1,3 +1,3 @@
1
1
  module Jeff
2
- VERSION = '0.1.0'
2
+ VERSION = '0.2.0'
3
3
  end
@@ -1,9 +1,166 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Jeff do
4
- describe '.new' do
5
- it 'delegates to Client' do
6
- Jeff.new('http://foo').should be_a Jeff::Client
4
+ let(:klass) { Class.new { include Jeff } }
5
+ let(:client) { klass.new }
6
+
7
+ describe '.headers' do
8
+ subject { klass.headers }
9
+
10
+ it { should have_key 'User-Agent' }
11
+
12
+ it 'should be configurable' do
13
+ klass.instance_eval do
14
+ headers 'Foo' => 'bar'
15
+ end
16
+
17
+ should have_key 'Foo'
18
+ end
19
+ end
20
+
21
+ describe '.params' do
22
+ subject { klass.params }
23
+
24
+ it { should have_key 'AWSAccessKeyId' }
25
+
26
+ it { should have_key 'SignatureMethod' }
27
+
28
+ it { should have_key 'SignatureVersion' }
29
+
30
+ it { should have_key 'Timestamp' }
31
+
32
+ it 'should be configurable' do
33
+ klass.instance_eval do
34
+ params 'Foo' => 'bar'
35
+ end
36
+
37
+ should have_key 'Foo'
38
+ end
39
+ end
40
+
41
+ describe '#endpoint' do
42
+ it 'should require a value' do
43
+ expect { client.endpoint }.to raise_error Jeff::MissingEndpoint
44
+ end
45
+ end
46
+
47
+ describe '#key' do
48
+ it 'should require a value' do
49
+ expect { client.key }.to raise_error Jeff::MissingKey
50
+ end
51
+ end
52
+
53
+ describe '#secret' do
54
+ it 'should require a value' do
55
+ expect { client.secret }.to raise_error Jeff::MissingSecret
56
+ end
57
+ end
58
+
59
+ context 'given a key' do
60
+ before do
61
+ client.key = 'key'
62
+ end
63
+
64
+ describe '#default_params' do
65
+ subject { client.default_params }
66
+
67
+ it 'should include the key' do
68
+ subject['AWSAccessKeyId'].should eql client.key
69
+ end
70
+
71
+ it 'should generate a timestamp' do
72
+ subject['Timestamp'].should be_a String
73
+ end
74
+ end
75
+
76
+ describe '#build_query' do
77
+ subject { client.build_query 'Foo' => 1, 'AA' => 1 }
78
+
79
+ it 'should include default parameters' do
80
+ should match /Timestamp/
81
+ end
82
+
83
+ it 'should include request-specific parameters' do
84
+ should match /Foo/
85
+ end
86
+
87
+ it 'should sort parameters' do
88
+ should match /^AA/
89
+ end
90
+ end
91
+ end
92
+
93
+ context 'given a key and a secret' do
94
+ before do
95
+ client.key = 'key'
96
+ client.secret = 'secret'
97
+ end
98
+
99
+ describe '#sign' do
100
+ subject { client.sign 'foo' }
101
+
102
+ it { should be_a String }
103
+ end
104
+ end
105
+
106
+ context 'given an endpoint' do
107
+ before do
108
+ client.endpoint = 'http://slowapi.com/delay/0'
109
+ end
110
+
111
+ describe "#connection" do
112
+ subject { client.connection }
113
+ let(:headers) { subject.connection[:headers] }
114
+
115
+ it { should be_an Excon::Connection }
116
+
117
+ it 'should set default headers' do
118
+ headers.should eq klass.headers
119
+ end
120
+
121
+ it 'should cache itself' do
122
+ subject.should be client.connection
123
+ end
124
+ end
125
+ end
126
+
127
+ context 'given an endpoint, key, and secret' do
128
+ before do
129
+ client.endpoint = 'http://slowapi.com/delay/0'
130
+ client.key = 'key'
131
+ client.secret = 'secret'
132
+ end
133
+
134
+ Excon::HTTP_VERBS.each do |method|
135
+ describe "##{method}" do
136
+ subject { client.send method, mock: true }
137
+
138
+ it "should make a #{method.upcase} request" do
139
+ Excon.stub({ method: method.to_sym }, { body: method })
140
+ subject.body.should eql method
141
+ end
142
+
143
+ it 'should include default headers' do
144
+ Excon.stub({ method: method.to_sym }) do |params|
145
+ { body: params[:headers] }
146
+ end
147
+ subject.body.should have_key 'User-Agent'
148
+ end
149
+
150
+ it 'should include parameters' do
151
+ Excon.stub({ method: method.to_sym }) do |params|
152
+ { body: params[:query] }
153
+ end
154
+ subject.body.should match client.build_query({})
155
+ end
156
+
157
+ it 'should append a signature' do
158
+ Excon.stub({ method: method.to_sym }) do |params|
159
+ { body: params[:query] }
160
+ end
161
+ subject.body.should match /Signature=[^&]+$/
162
+ end
163
+ end
7
164
  end
8
165
  end
9
166
  end
@@ -1,7 +1,4 @@
1
+ require 'pry'
1
2
  require 'rspec'
2
- begin
3
- require 'pry'
4
- rescue LoadError
5
- end
6
3
 
7
4
  require 'jeff'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jeff
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-07-03 00:00:00.000000000 Z
12
+ date: 2012-07-04 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: excon
@@ -18,7 +18,7 @@ dependencies:
18
18
  requirements:
19
19
  - - ~>
20
20
  - !ruby/object:Gem::Version
21
- version: '0.14'
21
+ version: 0.14.2
22
22
  type: :runtime
23
23
  prerelease: false
24
24
  version_requirements: !ruby/object:Gem::Requirement
@@ -26,40 +26,72 @@ dependencies:
26
26
  requirements:
27
27
  - - ~>
28
28
  - !ruby/object:Gem::Version
29
- version: '0.14'
29
+ version: 0.14.2
30
+ - !ruby/object:Gem::Dependency
31
+ name: guard-rspec
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: pry
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
30
62
  - !ruby/object:Gem::Dependency
31
63
  name: rake
32
64
  requirement: !ruby/object:Gem::Requirement
33
65
  none: false
34
66
  requirements:
35
- - - ~>
67
+ - - ! '>='
36
68
  - !ruby/object:Gem::Version
37
- version: '0.9'
69
+ version: '0'
38
70
  type: :development
39
71
  prerelease: false
40
72
  version_requirements: !ruby/object:Gem::Requirement
41
73
  none: false
42
74
  requirements:
43
- - - ~>
75
+ - - ! '>='
44
76
  - !ruby/object:Gem::Version
45
- version: '0.9'
77
+ version: '0'
46
78
  - !ruby/object:Gem::Dependency
47
79
  name: rspec
48
80
  requirement: !ruby/object:Gem::Requirement
49
81
  none: false
50
82
  requirements:
51
- - - ~>
83
+ - - ! '>='
52
84
  - !ruby/object:Gem::Version
53
- version: '2.10'
85
+ version: '0'
54
86
  type: :development
55
87
  prerelease: false
56
88
  version_requirements: !ruby/object:Gem::Requirement
57
89
  none: false
58
90
  requirements:
59
- - - ~>
91
+ - - ! '>='
60
92
  - !ruby/object:Gem::Version
61
- version: '2.10'
62
- description: A minimum-viable Amazon Web Services (AWS) client
93
+ version: '0'
94
+ description: Minimum-viable Amazon Web Services (AWS) client
63
95
  email:
64
96
  - hakan.ensari@papercavalier.com
65
97
  executables: []
@@ -69,18 +101,13 @@ files:
69
101
  - .gitignore
70
102
  - .travis.yml
71
103
  - Gemfile
104
+ - Guardfile
72
105
  - LICENSE
73
106
  - README.md
74
107
  - Rakefile
75
108
  - jeff.gemspec
76
109
  - lib/jeff.rb
77
- - lib/jeff/client.rb
78
- - lib/jeff/query_builder.rb
79
- - lib/jeff/signature.rb
80
- - lib/jeff/user_agent.rb
81
110
  - lib/jeff/version.rb
82
- - spec/jeff/client_spec.rb
83
- - spec/jeff/user_agent_spec.rb
84
111
  - spec/jeff_spec.rb
85
112
  - spec/spec_helper.rb
86
113
  homepage: https://github.com/hakanensari/jeff
@@ -95,26 +122,19 @@ required_ruby_version: !ruby/object:Gem::Requirement
95
122
  - - ! '>='
96
123
  - !ruby/object:Gem::Version
97
124
  version: '0'
98
- segments:
99
- - 0
100
- hash: 2544026845906331060
101
125
  required_rubygems_version: !ruby/object:Gem::Requirement
102
126
  none: false
103
127
  requirements:
104
128
  - - ! '>='
105
129
  - !ruby/object:Gem::Version
106
130
  version: '0'
107
- segments:
108
- - 0
109
- hash: 2544026845906331060
110
131
  requirements: []
111
132
  rubyforge_project:
112
133
  rubygems_version: 1.8.23
113
134
  signing_key:
114
135
  specification_version: 3
115
- summary: An AWS client
136
+ summary: AWS client
116
137
  test_files:
117
- - spec/jeff/client_spec.rb
118
- - spec/jeff/user_agent_spec.rb
119
138
  - spec/jeff_spec.rb
120
139
  - spec/spec_helper.rb
140
+ has_rdoc:
@@ -1,126 +0,0 @@
1
- module Jeff
2
- # A minimum-viable Amazon Web Services (AWS) client.
3
- class Client
4
- include UserAgent
5
-
6
- # Internal: Returns the String request body.
7
- attr :body
8
-
9
- # Internal: Returns the Proc chunked request body.
10
- attr :chunker
11
-
12
- # Gets/Sets the String AWS access key id.
13
- attr_accessor :key
14
-
15
- # Gets/Sets the String AWS secret key.
16
- attr_accessor :secret
17
-
18
- # Creates a new client.
19
- #
20
- # endpoint - A String AWS endpoint.
21
- #
22
- # Examples
23
- #
24
- # client = Jeff.new 'http://ecs.amazonaws.com/onca/xml'
25
- #
26
- def initialize(endpoint)
27
- @endpoint = URI endpoint
28
- @connection = Excon.new endpoint.to_s, :headers => {
29
- 'User-Agent' => USER_AGENT
30
- }
31
-
32
- reset_request_attributes
33
- end
34
-
35
- # Updates the request attributes.
36
- #
37
- # data - A Hash of parameters or a String request body or a Proc that will
38
- # deliver chunks of data.
39
- #
40
- # Returns self.
41
- #
42
- # Examples
43
- #
44
- # @client << {
45
- # 'AssociateTag' => 'tag',
46
- # 'Service' => 'AWSECommerceService',
47
- # 'Version' => '2011-08-01'
48
- # }
49
- #
50
- def <<(data)
51
- case data
52
- when Hash
53
- @params.update data
54
- when String
55
- @body ||= '' << data
56
- when Proc
57
- @chunker = data
58
- end
59
-
60
- self
61
- end
62
-
63
- # Configures the client.
64
- #
65
- # Yields self.
66
- #
67
- # Examples
68
- #
69
- # client.configure do |c|
70
- # c.key = 'key'
71
- # c.secret = 'secret'
72
- # end
73
- #
74
- def configure
75
- yield self
76
- end
77
-
78
- # Returns the Hash request parameters, including required defaults.
79
- def params
80
- {
81
- 'AWSAccessKeyId' => @key,
82
- 'SignatureVersion' => '2',
83
- 'SignatureMethod' => 'HmacSHA256',
84
- 'Timestamp' => Time.now.utc.iso8601
85
- }.merge @params
86
- end
87
-
88
- # Makes an HTTP request.
89
- #
90
- # Returns an Excon::Response.
91
- def request(opts = {}, &blk)
92
- opts.update :body => @body,
93
- :method => action,
94
- :query => query,
95
- :request_block => @chunker
96
-
97
- begin
98
- @connection.request opts, &blk
99
- ensure
100
- reset_request_attributes
101
- end
102
- end
103
-
104
- # Returns the String URL.
105
- def url
106
- [@endpoint, query].join '?'
107
- end
108
-
109
- private
110
-
111
- def action
112
- @body || @chunker ? :post : :get
113
- end
114
-
115
- def query
116
- @query_builder ||= QueryBuilder.new @endpoint, @secret
117
- @query_builder.build action, params
118
- end
119
-
120
- def reset_request_attributes
121
- @body = nil
122
- @chunker = nil
123
- @params = {}
124
- end
125
- end
126
- end
@@ -1,42 +0,0 @@
1
- module Jeff
2
- class QueryBuilder
3
- UNRESERVED = /([^\w.~-]+)/
4
-
5
- def initialize(endpoint, secret)
6
- @endpoint = endpoint
7
- @secret = secret
8
- end
9
-
10
- def build(mth, params)
11
- @mth = mth.to_s.upcase
12
- @query = stringify params
13
-
14
- "#{@query}&Signature=#{escape signature}"
15
- end
16
-
17
- private
18
-
19
- def escape(val)
20
- val.to_s.gsub(UNRESERVED) do
21
- '%' + $1.unpack('H2' * $1.bytesize).join('%').upcase
22
- end
23
- end
24
-
25
- def signature
26
- Signature.new @secret, string_to_sign
27
- end
28
-
29
- def string_to_sign
30
- [
31
- @mth,
32
- @endpoint.host,
33
- @endpoint.path,
34
- @query
35
- ].join "\n"
36
- end
37
-
38
- def stringify(hsh)
39
- hsh.map { |k, v| "#{k}=#{ escape v }" }.sort.join '&'
40
- end
41
- end
42
- end
@@ -1,18 +0,0 @@
1
- module Jeff
2
- class Signature
3
- SHA256 = OpenSSL::Digest::SHA256.new
4
-
5
- def initialize(secret, message)
6
- @secret = secret
7
- @message = message
8
- end
9
-
10
- def digest
11
- OpenSSL::HMAC.digest SHA256, @secret, @message
12
- end
13
-
14
- def to_s
15
- Base64.encode64(digest).chomp
16
- end
17
- end
18
- end
@@ -1,11 +0,0 @@
1
- module Jeff
2
- module UserAgent
3
- USER_AGENT = begin
4
- hostname = `hostname`.chomp
5
- engine = defined?(RUBY_ENGINE) ? RUBY_ENGINE : 'ruby'
6
- language = [engine, RUBY_VERSION, "p#{RUBY_PATCHLEVEL}"].join ' '
7
-
8
- "Jeff/#{VERSION} (Language=#{language}; Host=#{hostname})"
9
- end
10
- end
11
- end
@@ -1,87 +0,0 @@
1
- require 'spec_helper'
2
-
3
- module Jeff
4
- describe Client do
5
- before do
6
- @endpoint = 'http://slowapi.com/delay/0'
7
-
8
- @client = Client.new @endpoint
9
- @client.configure do |config|
10
- config.key = 'key'
11
- config.secret = 'secret'
12
- end
13
- end
14
-
15
- describe '#<<' do
16
- it 'updates the request parameters' do
17
- @client << { 'Foo' => 1 }
18
- @client.params.should include 'Foo'
19
- end
20
-
21
- it 'updates the request body' do
22
- @client << 'foo'
23
- @client.body.should eql 'foo'
24
- end
25
-
26
- it 'updates the request chunked body' do
27
- chunker = lambda {}
28
- @client << chunker
29
- @client.chunker.should eql chunker
30
- end
31
- end
32
-
33
- describe '#params' do
34
- subject { @client.params }
35
-
36
- it 'includes a key' do
37
- should include 'AWSAccessKeyId'
38
- end
39
-
40
- it 'includes a signature version' do
41
- should include 'SignatureVersion'
42
- end
43
-
44
- it 'includes a signature method' do
45
- should include 'SignatureMethod'
46
- end
47
-
48
- it 'includes a timestamp' do
49
- should include 'Timestamp'
50
- end
51
- end
52
-
53
- describe '#url' do
54
- subject { @client.url }
55
-
56
- it 'includes the endpoint' do
57
- should include @endpoint
58
- end
59
-
60
- it 'sorts the parameters' do
61
- @client << { 'Z' => 1, 'A' => 1 }
62
- should match /\?A=[^&]+/
63
- end
64
-
65
- it 'is signed' do
66
- should match /Signature=[^&]+$/
67
- end
68
- end
69
-
70
- describe '#request' do
71
- context 'given no body or chunker' do
72
- it 'makes a GET request' do
73
- Excon.stub({ :method => :get }, { :body => 'get' })
74
- @client.request(:mock => true).body.should eql 'get'
75
- end
76
- end
77
-
78
- context 'given a body' do
79
- it 'makes a POST request' do
80
- Excon.stub({ :method => :post }, { :body => 'post' })
81
- @client << 'foo'
82
- @client.request(:mock => true).body.should eql 'post'
83
- end
84
- end
85
- end
86
- end
87
- end
@@ -1,19 +0,0 @@
1
- require 'spec_helper'
2
-
3
- module Jeff
4
- describe UserAgent do
5
- subject { UserAgent::USER_AGENT }
6
-
7
- it 'describes the library' do
8
- should match /Jeff\/[\d\w.]+\s/
9
- end
10
-
11
- it 'describes the interpreter' do
12
- should match /Language=(?:j?ruby|rbx)/
13
- end
14
-
15
- it 'describes the host' do
16
- should match /Host=[\w\d]+/
17
- end
18
- end
19
- end