jeff 0.1.0 → 0.2.0

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