tuppari 0.1.1
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.
- data/.gitignore +6 -0
- data/Gemfile +8 -0
- data/README.md +94 -0
- data/lib/tuppari.rb +41 -0
- data/lib/tuppari/application.rb +16 -0
- data/lib/tuppari/auth.rb +107 -0
- data/lib/tuppari/authentication_error.rb +6 -0
- data/lib/tuppari/client.rb +290 -0
- data/lib/tuppari/client_error.rb +6 -0
- data/lib/tuppari/server_url.rb +19 -0
- data/lib/tuppari/version.rb +6 -0
- data/spec/README.md +6 -0
- data/spec/tuppari/auth_spec.rb +128 -0
- data/spec/tuppari_spec.rb +111 -0
- data/tuppari.gemspec +28 -0
- metadata +96 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
# Tuppari client for Ruby
|
2
|
+
|
3
|
+
## About Tuppari
|
4
|
+
|
5
|
+
[Tuppari](http://hakobera.github.com/tuppari/) is an experimental implementation of unbreakable message broadcasting system using WebSocket and Amazon Web Services by [@hakobera](https://github.com/hakobera).
|
6
|
+
|
7
|
+
https://github.com/hakobera/tuppari
|
8
|
+
|
9
|
+
https://github.com/hakobera/tuppari-servers
|
10
|
+
|
11
|
+
|
12
|
+
## Tuppari for Ruby developers
|
13
|
+
|
14
|
+
You can easily build a Tuppari application by just using Ruby API. Let's get started now!
|
15
|
+
|
16
|
+
### Gemfile
|
17
|
+
|
18
|
+
Prepare Gemfile as follows.
|
19
|
+
|
20
|
+
```ruby
|
21
|
+
source 'https://rubygems.org'
|
22
|
+
|
23
|
+
gem 'tuppari'
|
24
|
+
```
|
25
|
+
|
26
|
+
### Getting Started
|
27
|
+
|
28
|
+
And then invoke `bundle console` to try Tuppari.
|
29
|
+
|
30
|
+
```ruby
|
31
|
+
require 'tuppari'
|
32
|
+
|
33
|
+
Tuppari.create_account(YOUR_ID, PASSWORD)
|
34
|
+
|
35
|
+
tuppari = Tuppari.login(YOUR_ID, PASSWORD)
|
36
|
+
|
37
|
+
application = tuppari.create_application('first-tuppari')
|
38
|
+
|
39
|
+
open('tuppari.html', 'w') do |f|
|
40
|
+
html = <<"EOF"
|
41
|
+
<html>
|
42
|
+
<head>
|
43
|
+
<script src="http://cdn.tuppari.com/0.1.0/tuppari.min.js"></script>
|
44
|
+
<script>
|
45
|
+
var client, channel;
|
46
|
+
client = tuppari.createClient({applicationId: '#{application.id}'});
|
47
|
+
channel = client.subscribe('ch');
|
48
|
+
channel.bind('update', function (msg) {
|
49
|
+
console.log(msg);
|
50
|
+
});
|
51
|
+
</script>
|
52
|
+
</head>
|
53
|
+
<body>Check JavaScript console.</body>
|
54
|
+
</html>
|
55
|
+
EOF
|
56
|
+
f.write(html)
|
57
|
+
end
|
58
|
+
|
59
|
+
#
|
60
|
+
# [notice] Please open this 'tuppari.html' file in browser.
|
61
|
+
#
|
62
|
+
|
63
|
+
tuppari.application = application # or tuppari.load_application_by_name('first-tuppari')
|
64
|
+
|
65
|
+
tuppari.publish_message(
|
66
|
+
:channel => 'ch',
|
67
|
+
:event => 'update',
|
68
|
+
:message => "Hello, Tuppari! (#{Time.now})"
|
69
|
+
)
|
70
|
+
```
|
71
|
+
|
72
|
+
### Publishing messages without account authentication
|
73
|
+
|
74
|
+
Tuppari account authentication is not needed for publishing messages. And following values for the target application are required.
|
75
|
+
|
76
|
+
- application_id
|
77
|
+
- access_key_id (for the application)
|
78
|
+
- access_secret_key (for the application)
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
application = Tuppari::Application.new(
|
82
|
+
:id => 'xxxx',
|
83
|
+
:access_key_id => 'yyy',
|
84
|
+
:access_secret_key => 'zzz'
|
85
|
+
) # or application = tuppari.get_application('first-tuppari')
|
86
|
+
|
87
|
+
Tuppari.publish_message(
|
88
|
+
:application => application,
|
89
|
+
:channel => 'ch',
|
90
|
+
:event => 'update',
|
91
|
+
:message => "Hello, Tuppari! (#{Time.now})"
|
92
|
+
)
|
93
|
+
```
|
94
|
+
|
data/lib/tuppari.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'logger'
|
4
|
+
require 'json'
|
5
|
+
require 'rest_client'
|
6
|
+
require 'time'
|
7
|
+
require 'tuppari/auth'
|
8
|
+
require 'tuppari/application'
|
9
|
+
require 'tuppari/authentication_error'
|
10
|
+
require 'tuppari/client'
|
11
|
+
require 'tuppari/client_error'
|
12
|
+
require 'tuppari/server_url'
|
13
|
+
|
14
|
+
# Tuppari API client
|
15
|
+
#
|
16
|
+
# `Tuppari` is an experimental implementation of unbreakable message broadcasting system
|
17
|
+
# using WebSocket and Amazon Web Services.
|
18
|
+
#
|
19
|
+
# see also https://github.com/hakobera/tuppari-servers/wiki/API-Reference
|
20
|
+
#
|
21
|
+
module Tuppari
|
22
|
+
|
23
|
+
# Creates a new Tuppari::Client instance.
|
24
|
+
def self.new(params = {})
|
25
|
+
Tuppari::Client.new(params)
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.create_account(account_name, password)
|
29
|
+
new().create_account(account_name, password)
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.login(account_name, password)
|
33
|
+
new().login(account_name, password)
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.publish_message(params)
|
37
|
+
Tuppari.new().publish_message(params)
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
module Tuppari
|
4
|
+
class Application
|
5
|
+
|
6
|
+
attr_accessor :name, :id, :access_key_id, :access_secret_key
|
7
|
+
|
8
|
+
def initialize(params)
|
9
|
+
@name = params[:name]
|
10
|
+
@id = params[:id]
|
11
|
+
@access_key_id = params[:access_key_id]
|
12
|
+
@access_secret_key = params[:access_secret_key]
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
data/lib/tuppari/auth.rb
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'openssl'
|
4
|
+
require 'digest'
|
5
|
+
require 'json'
|
6
|
+
|
7
|
+
module Tuppari
|
8
|
+
module Auth
|
9
|
+
|
10
|
+
def self.create_canonical_uri(uri)
|
11
|
+
if %r{^http(s)*://}.match(uri)
|
12
|
+
uri.sub(%r{^http(s)*://[^/]+/}, '/')
|
13
|
+
elsif %r{^[^/]}.match(uri)
|
14
|
+
"/#{uri}"
|
15
|
+
else
|
16
|
+
uri
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.create_canonical_query_string(query_string)
|
21
|
+
if query_string.nil?
|
22
|
+
''
|
23
|
+
else
|
24
|
+
query_string.split("&").sort_by { |e| e.split("=").first }.join('&')
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.create_canonical_headers(headers)
|
29
|
+
if headers.nil?
|
30
|
+
''
|
31
|
+
else
|
32
|
+
downcase_keys(headers).to_a.sort_by { |k, v| k }.map { |k, v| "#{k}:#{v}" }.join("\n")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.create_signed_headers(headers)
|
37
|
+
if headers.nil?
|
38
|
+
''
|
39
|
+
else
|
40
|
+
downcase_keys(headers).to_a.sort_by { |k, v| k }.map { |k, v| k }.join(';')
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.create_body_hash(body_params)
|
45
|
+
sha256 = Digest::SHA256.new
|
46
|
+
digest = sha256.digest(JSON.unparse(body_params))
|
47
|
+
Digest.hexencode(digest)
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.create_canonical_request(method, uri, query_string, headers, body_params)
|
51
|
+
request = []
|
52
|
+
request << method
|
53
|
+
request << create_canonical_uri(uri)
|
54
|
+
request << create_canonical_query_string(query_string)
|
55
|
+
request << create_canonical_headers(headers)
|
56
|
+
request << create_signed_headers(headers)
|
57
|
+
request << create_body_hash(body_params)
|
58
|
+
request.join("\n")
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.create_string_to_sign(canonical_request, request_time)
|
62
|
+
result = []
|
63
|
+
result << "SHA256"
|
64
|
+
result << request_time.gmtime.strftime("%Y%m%dT%H%M%SZ")
|
65
|
+
sha256 = Digest::SHA256.new
|
66
|
+
digest = sha256.digest(canonical_request)
|
67
|
+
result << Digest.hexencode(digest)
|
68
|
+
result.join("\n")
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.create_signature(secret, string_to_sign, request_time, host)
|
72
|
+
signing_key = create_hmac_hash_value(create_hmac_hash_value("TUPPARI#{secret}", request_time.gmtime.strftime("%Y%m%dT%H%M%SZ")), host)
|
73
|
+
create_hmac_hash_value(signing_key, string_to_sign)
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.create_authorization_header(params)
|
77
|
+
id = params[:id]
|
78
|
+
secret = params[:secret]
|
79
|
+
method = params[:method] || 'POST'
|
80
|
+
uri = params[:uri]
|
81
|
+
host = params[:host] || 'api.tuppari.com'
|
82
|
+
query_string = params[:query_string] || ''
|
83
|
+
headers = params[:headers] || {}
|
84
|
+
body_params = params[:body_params] || {}
|
85
|
+
request_time = params[:request_time] || Time.now
|
86
|
+
|
87
|
+
signed_headers = create_signed_headers(headers)
|
88
|
+
canonical_request = create_canonical_request(method, uri, query_string, headers, body_params)
|
89
|
+
string_to_sign = create_string_to_sign(canonical_request, request_time)
|
90
|
+
signature = create_signature(secret, string_to_sign, request_time, host)
|
91
|
+
"HMAC-SHA256 Credential=#{id},SignedHeaders=#{signed_headers},Signature=#{signature}"
|
92
|
+
end
|
93
|
+
|
94
|
+
protected
|
95
|
+
|
96
|
+
def self.downcase_keys(headers)
|
97
|
+
headers.each_with_object({}) { |(k, v), hash|
|
98
|
+
hash[k.downcase] = v
|
99
|
+
}
|
100
|
+
end
|
101
|
+
|
102
|
+
def self.create_hmac_hash_value(key, data)
|
103
|
+
OpenSSL::HMAC.hexdigest('sha256', key, data)
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,290 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'logger'
|
4
|
+
require 'time'
|
5
|
+
require 'json'
|
6
|
+
require 'rest_client'
|
7
|
+
|
8
|
+
require 'tuppari'
|
9
|
+
|
10
|
+
module Tuppari
|
11
|
+
class Client
|
12
|
+
|
13
|
+
attr_accessor :account_name, :account_secret, :application, :log
|
14
|
+
|
15
|
+
def initialize(params = {})
|
16
|
+
@log = Logger.new(params[:log_output] || STDOUT)
|
17
|
+
@log.level = params[:log_level] || Logger::INFO
|
18
|
+
@account_name = params[:account_name]
|
19
|
+
@account_secret = params[:account_secret]
|
20
|
+
setup_application(params)
|
21
|
+
end
|
22
|
+
|
23
|
+
def load_application_by_name(application_name)
|
24
|
+
setup_application({:application_name => application_name})
|
25
|
+
self
|
26
|
+
end
|
27
|
+
|
28
|
+
def create_account(account_name, password)
|
29
|
+
body = JSON.unparse({"accountName" => account_name, "password" => password})
|
30
|
+
begin
|
31
|
+
RestClient.post(Tuppari::ServerURL::ACCOUNTS_REGISTER, body, :content_type => :json)
|
32
|
+
rescue RestClient::Exception => e
|
33
|
+
@log.warn("Failed to create a new Tuppari account (#{e}, #{e.http_body})")
|
34
|
+
raise e
|
35
|
+
rescue Exception => e
|
36
|
+
@log.warn("Failed to create a new Tuppari account (#{e})")
|
37
|
+
raise e
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def login(account_name, password)
|
42
|
+
body = JSON.unparse({:accountName => account_name, :password => password})
|
43
|
+
begin
|
44
|
+
body = RestClient.post(Tuppari::ServerURL::ACCOUNTS_AUTH, body, :content_type => :json)
|
45
|
+
credentials = JSON.parse(body)['credentials']
|
46
|
+
@account_name = credentials['id']
|
47
|
+
@account_secret = credentials['secret']
|
48
|
+
@log.debug {
|
49
|
+
"Authenticated tuppari user (account_name:#{@account_name}, account_secret:#{@account_secret})"
|
50
|
+
}
|
51
|
+
rescue RestClient::Exception => e
|
52
|
+
@log.warn("Failed to authenticate your Tuppari account (#{e}, #{e.http_body})")
|
53
|
+
raise e
|
54
|
+
rescue Exception => e
|
55
|
+
@log.warn("Failed to authenticate your Tuppari account (#{e})")
|
56
|
+
raise e
|
57
|
+
end
|
58
|
+
self
|
59
|
+
end
|
60
|
+
|
61
|
+
def is_authenticated?
|
62
|
+
!@account_name.nil? && !@account_secret.nil?
|
63
|
+
end
|
64
|
+
|
65
|
+
def create_application(application_name)
|
66
|
+
unless is_authenticated?
|
67
|
+
raise Tuppari::AuthenticationError.new("Call #authenticate(account_name, password) before.")
|
68
|
+
end
|
69
|
+
|
70
|
+
time = Time.now
|
71
|
+
headers = headers('CreateApplication', time)
|
72
|
+
body_params = {:applicationName => application_name.to_s}
|
73
|
+
auth_header = Tuppari::Auth.create_authorization_header(
|
74
|
+
:id => @account_name,
|
75
|
+
:secret => @account_secret,
|
76
|
+
:method => 'POST',
|
77
|
+
:uri => Tuppari::ServerURL::APPLICATIONS,
|
78
|
+
:host => 'api.tuppari.com',
|
79
|
+
:query_string => '',
|
80
|
+
:headers => headers,
|
81
|
+
:body_params => body_params,
|
82
|
+
:request_time => time
|
83
|
+
)
|
84
|
+
begin
|
85
|
+
body = RestClient.post(Tuppari::ServerURL::APPLICATIONS,
|
86
|
+
JSON.unparse(body_params),
|
87
|
+
headers.merge(:authorization => auth_header))
|
88
|
+
@log.debug {
|
89
|
+
"create_application response: #{body}"
|
90
|
+
}
|
91
|
+
@log.info {
|
92
|
+
"Tuppari application is created. (#{application_name})"
|
93
|
+
}
|
94
|
+
response = JSON.parse(body)
|
95
|
+
Tuppari::Application.new(
|
96
|
+
:name => response['applicationName'],
|
97
|
+
:id => response['applicationId'],
|
98
|
+
:access_key_id => response['accessKeyId'],
|
99
|
+
:access_secret_key => response['accessSecretKey']
|
100
|
+
)
|
101
|
+
rescue RestClient::Exception => e
|
102
|
+
@log.warn("Failed to create a new Tuppari application (#{e}, #{e.http_body})")
|
103
|
+
raise e
|
104
|
+
rescue Exception => e
|
105
|
+
@log.warn("Failed to create a new Tuppari application (#{e})")
|
106
|
+
raise e
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def delete_application(application_name)
|
111
|
+
unless is_authenticated?
|
112
|
+
raise Tuppari::AuthenticationError.new("Call #authenticate(account_name, password) before.")
|
113
|
+
end
|
114
|
+
|
115
|
+
time = Time.now
|
116
|
+
headers = headers('DeleteApplication', time)
|
117
|
+
body_params = {:applicationName => application_name}
|
118
|
+
auth_header = Tuppari::Auth.create_authorization_header(
|
119
|
+
:id => @account_name,
|
120
|
+
:secret => @account_secret,
|
121
|
+
:method => 'POST',
|
122
|
+
:uri => Tuppari::ServerURL::APPLICATIONS,
|
123
|
+
:host => 'api.tuppari.com',
|
124
|
+
:query_string => '',
|
125
|
+
:headers => headers,
|
126
|
+
:body_params => body_params,
|
127
|
+
:request_time => time
|
128
|
+
)
|
129
|
+
begin
|
130
|
+
RestClient.post(Tuppari::ServerURL::APPLICATIONS,
|
131
|
+
JSON.unparse(body_params),
|
132
|
+
headers.merge(:authorization => auth_header))
|
133
|
+
@log.info {
|
134
|
+
"Tuppari application is deleted. (#{application_name})"
|
135
|
+
}
|
136
|
+
rescue RestClient::Exception => e
|
137
|
+
@log.warn("Failed to delete a Tuppari application (#{e}, #{e.http_body})")
|
138
|
+
raise e
|
139
|
+
rescue Exception => e
|
140
|
+
@log.warn("Failed to delete a Tuppari application (#{e})")
|
141
|
+
raise e
|
142
|
+
end
|
143
|
+
nil
|
144
|
+
end
|
145
|
+
|
146
|
+
def get_application(application_name)
|
147
|
+
get_application_list().find { |app| app.name == application_name }
|
148
|
+
end
|
149
|
+
|
150
|
+
def get_application_list()
|
151
|
+
unless is_authenticated?
|
152
|
+
raise Tuppari::AuthenticationError.new("Call #authenticate(account_name, password) before.")
|
153
|
+
end
|
154
|
+
|
155
|
+
time = Time.now
|
156
|
+
headers = headers('ListApplication', time)
|
157
|
+
body_params = {}
|
158
|
+
auth_header = Tuppari::Auth.create_authorization_header(
|
159
|
+
:id => @account_name,
|
160
|
+
:secret => @account_secret,
|
161
|
+
:method => 'POST',
|
162
|
+
:uri => Tuppari::ServerURL::APPLICATIONS,
|
163
|
+
:host => 'api.tuppari.com',
|
164
|
+
:query_string => '',
|
165
|
+
:headers => headers,
|
166
|
+
:body_params => body_params,
|
167
|
+
:request_time => time
|
168
|
+
)
|
169
|
+
begin
|
170
|
+
body = RestClient.post(Tuppari::ServerURL::APPLICATIONS,
|
171
|
+
JSON.unparse(body_params),
|
172
|
+
headers.merge(:authorization => auth_header))
|
173
|
+
@log.debug {
|
174
|
+
"get_application_list response: #{response}"
|
175
|
+
}
|
176
|
+
JSON.parse(body).to_a.map { |name, app|
|
177
|
+
Tuppari::Application.new(
|
178
|
+
:name => app['name'],
|
179
|
+
:id => app['applicationId'],
|
180
|
+
:access_key_id => app['accessKeyId'],
|
181
|
+
:access_secret_key => app['accessSecretKey']
|
182
|
+
)
|
183
|
+
}
|
184
|
+
rescue RestClient::Exception => e
|
185
|
+
@log.warn("Failed to get the Tuppari application list (#{e}, #{e.http_body})")
|
186
|
+
raise e
|
187
|
+
rescue Exception => e
|
188
|
+
@log.warn("Failed to get the Tuppari application list (#{e})")
|
189
|
+
raise e
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def publish_message(params)
|
194
|
+
|
195
|
+
setup_application(params)
|
196
|
+
if @application.nil?
|
197
|
+
raise Tuppari::ClientError.new("Specify Tuppari application to publish messages.")
|
198
|
+
end
|
199
|
+
|
200
|
+
channel = params[:channel]
|
201
|
+
event = params[:event]
|
202
|
+
message = params[:message]
|
203
|
+
|
204
|
+
time = Time.now
|
205
|
+
headers = headers('PublishMessage', time)
|
206
|
+
body_params = {
|
207
|
+
:applicationId => @application.id,
|
208
|
+
:channel => channel,
|
209
|
+
:event => event,
|
210
|
+
:message => message
|
211
|
+
}
|
212
|
+
auth_header = Tuppari::Auth.create_authorization_header(
|
213
|
+
:id => @application.access_key_id,
|
214
|
+
:secret => @application.access_secret_key,
|
215
|
+
:method => 'POST',
|
216
|
+
:uri => Tuppari::ServerURL::MESSAGES,
|
217
|
+
:host => 'api.tuppari.com',
|
218
|
+
:query_string => '',
|
219
|
+
:headers => headers,
|
220
|
+
:body_params => body_params,
|
221
|
+
:request_time => time
|
222
|
+
)
|
223
|
+
begin
|
224
|
+
body = RestClient.post(Tuppari::ServerURL::MESSAGES,
|
225
|
+
JSON.unparse(body_params),
|
226
|
+
headers.merge(:authorization => auth_header))
|
227
|
+
@log.debug {
|
228
|
+
"Tuppari message has been sent. (#{@application.id},#{channel},#{event},#{message})"
|
229
|
+
}
|
230
|
+
JSON.parse(body)
|
231
|
+
rescue RestClient::Exception => e
|
232
|
+
@log.warn {
|
233
|
+
"Failed to publish a Tuppari message (#{e}, #{e.http_body}, application_id:#{@application.id}, " +
|
234
|
+
"channel:#{channel}, event:#{event}, message:#{message})"
|
235
|
+
}
|
236
|
+
raise e
|
237
|
+
rescue Exception => e
|
238
|
+
@log.warn {
|
239
|
+
"Failed to publish a Tuppari message (#{e}, application_id:#{@application.id}, " +
|
240
|
+
"channel:#{channel}, event:#{event}, message:#{message})"
|
241
|
+
}
|
242
|
+
raise e
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
def get_service_info()
|
247
|
+
begin
|
248
|
+
body = RestClient.get(Tuppari::ServerURL::INFO, 'Content-type' => 'application/json')
|
249
|
+
JSON.parse(body)
|
250
|
+
rescue RestClient::Exception => e
|
251
|
+
@log.warn("Failed to get the Tuppari service info (#{e}, #{e.http_body})")
|
252
|
+
raise e
|
253
|
+
rescue Exception => e
|
254
|
+
@log.warn("Failed to get the Tuppari service info (#{e})")
|
255
|
+
raise e
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
protected
|
260
|
+
|
261
|
+
def setup_application(params)
|
262
|
+
if !params[:application].nil?
|
263
|
+
app = params[:application]
|
264
|
+
if app.is_a? Tuppari::Application
|
265
|
+
@application = app
|
266
|
+
else
|
267
|
+
raise Tuppari::ClientError.new("application should be a Tuppari::Application object.")
|
268
|
+
end
|
269
|
+
elsif !params[:application_name].nil?
|
270
|
+
application_name = params[:application_name]
|
271
|
+
application = get_application_list().find { |app| app.name == application_name }
|
272
|
+
if application.nil?
|
273
|
+
raise Tuppari::ClientError.new("Application is not found (application_name: #{application_name})")
|
274
|
+
else
|
275
|
+
@application = application
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
def headers(operation, time)
|
281
|
+
{
|
282
|
+
'Host' => 'api.tuppari.com',
|
283
|
+
'Content-type' => 'application/json',
|
284
|
+
'X-Tuppari-Date' => time.gmtime.rfc2822,
|
285
|
+
'X-Tuppari-Operation' => operation
|
286
|
+
}
|
287
|
+
end
|
288
|
+
|
289
|
+
end
|
290
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
module Tuppari
|
4
|
+
module ServerURL
|
5
|
+
|
6
|
+
BASE_URL = 'https://api.tuppari.com'
|
7
|
+
|
8
|
+
ACCOUNTS_REGISTER = BASE_URL + '/accounts/register'
|
9
|
+
|
10
|
+
ACCOUNTS_AUTH = BASE_URL + '/accounts/auth'
|
11
|
+
|
12
|
+
APPLICATIONS = BASE_URL + '/applications'
|
13
|
+
|
14
|
+
MESSAGES = BASE_URL + '/messages'
|
15
|
+
|
16
|
+
INFO = BASE_URL + '/info'
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
data/spec/README.md
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'tuppari'
|
4
|
+
|
5
|
+
describe Tuppari::Auth do
|
6
|
+
|
7
|
+
it "should have correct #create_canonical_uri" do
|
8
|
+
Tuppari::Auth.create_canonical_uri('foo/bar').should eq('/foo/bar')
|
9
|
+
Tuppari::Auth.create_canonical_uri('/foo/bar').should eq('/foo/bar')
|
10
|
+
Tuppari::Auth.create_canonical_uri('http://www.google.com/foo/bar').should eq('/foo/bar')
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should have correct #create_canonical_query_string" do
|
14
|
+
Tuppari::Auth.create_canonical_query_string(nil).should eq('')
|
15
|
+
Tuppari::Auth.create_canonical_query_string('').should eq('')
|
16
|
+
Tuppari::Auth.create_canonical_query_string('a=b').should eq('a=b')
|
17
|
+
Tuppari::Auth.create_canonical_query_string('a=b&c=d').should eq('a=b&c=d')
|
18
|
+
Tuppari::Auth.create_canonical_query_string('c=d&a=b').should eq('a=b&c=d')
|
19
|
+
Tuppari::Auth.create_canonical_query_string('c=d&a=b&c=e').should eq('a=b&c=d&c=e')
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should have correct #create_canonical_headers" do
|
23
|
+
Tuppari::Auth.create_canonical_headers(nil).should eq('')
|
24
|
+
Tuppari::Auth.create_canonical_headers({}).should eq('')
|
25
|
+
|
26
|
+
headers = {
|
27
|
+
'X-Tuppari-Operation' => 'CreateApplication',
|
28
|
+
'Content-type' => 'application/json',
|
29
|
+
'Host' => 'api.tuppari.com'
|
30
|
+
}
|
31
|
+
Tuppari::Auth.create_canonical_headers(headers).should eq("content-type:application/json\nhost:api.tuppari.com\nx-tuppari-operation:CreateApplication")
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should have correct #create_signed_headers" do
|
35
|
+
Tuppari::Auth.create_signed_headers(nil).should eq('')
|
36
|
+
Tuppari::Auth.create_signed_headers({}).should eq('')
|
37
|
+
|
38
|
+
headers = {
|
39
|
+
'X-Tuppari-Operation' => 'CreateApplication',
|
40
|
+
'Content-type' => 'application/json',
|
41
|
+
'Host' => 'api.tuppari.com'
|
42
|
+
}
|
43
|
+
Tuppari::Auth.create_signed_headers(headers).should eq('content-type;host;x-tuppari-operation')
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'should have correct #create_body_hash' do
|
47
|
+
Tuppari::Auth.create_body_hash({'a' => 'b', 'c' => 'd'}).should eq('b85c7da93e8790518898c280e15e3f1af5d46bf4aaa4407690f0f0a3b0316478')
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'should have correct #create_canonical_request' do
|
51
|
+
headers = {
|
52
|
+
'X-Tuppari-Operation' => 'CreateApplication',
|
53
|
+
'Content-type' => 'application/json',
|
54
|
+
'Host' => 'api.tuppari.com'
|
55
|
+
}
|
56
|
+
body_params = {'applicationName' => 'example1'}
|
57
|
+
request = Tuppari::Auth.create_canonical_request('POST', '/test', 'b=v2&a=v1', headers, body_params)
|
58
|
+
|
59
|
+
request.should eq("POST\n" +
|
60
|
+
"/test\n" +
|
61
|
+
"a=v1&b=v2\n" +
|
62
|
+
"content-type:application/json\n" +
|
63
|
+
"host:api.tuppari.com\n" +
|
64
|
+
"x-tuppari-operation:CreateApplication\n" +
|
65
|
+
"content-type;host;x-tuppari-operation\n" +
|
66
|
+
"8f2d5fe4a93000d3546e578d265fc936806f6ef6dc6f7ee87715e1a5c514c168")
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'should have correct #create_string_to_sign' do
|
70
|
+
request = "POST\n" +
|
71
|
+
"/test\n" +
|
72
|
+
"a=v1&b=v2\n" +
|
73
|
+
"content-type:application/json\n" +
|
74
|
+
"host:api.tuppari.com\n" +
|
75
|
+
"x-tuppari-operation:CreateApplication\n" +
|
76
|
+
"content-type;host;x-tuppari-operation\n" +
|
77
|
+
"8f2d5fe4a93000d3546e578d265fc936806f6ef6dc6f7ee87715e1a5c514c168"
|
78
|
+
Tuppari::Auth.create_string_to_sign(request, Time.new(2011, 2, 3, 4, 5, 6)).should(
|
79
|
+
eq("SHA256\n" +"20110202T190506Z\n" +"152176000cc08c7d9d0558bc3a50368aa38619a695ad20f50bec1344429cb315"))
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'should have correct #create_signature' do
|
84
|
+
secret = 'secretKey1'
|
85
|
+
string_to_sign = "SHA256\n" +
|
86
|
+
"19700101T000000Z\n" +
|
87
|
+
"152176000cc08c7d9d0558bc3a50368aa38619a695ad20f50bec1344429cb315"
|
88
|
+
time = Time.new(1970, 1, 1, 0, 0, 0, "+00:00")
|
89
|
+
host = 'api.tuppari.com'
|
90
|
+
expected_signature = '4815ff1681a278e7c852902ea3604f17831a80a78dc0ff82f5142598a034509b'
|
91
|
+
(1...1000).each do |i|
|
92
|
+
signature = Tuppari::Auth.create_signature(secret, string_to_sign, time, host)
|
93
|
+
signature.should eq(expected_signature)
|
94
|
+
end
|
95
|
+
|
96
|
+
secret2 = 'secretKey2'
|
97
|
+
signature1 = Tuppari::Auth.create_signature(secret, string_to_sign, time, host)
|
98
|
+
signature2 = Tuppari::Auth.create_signature(secret2, string_to_sign, time, host)
|
99
|
+
signature2.should_not eq(signature1)
|
100
|
+
end
|
101
|
+
|
102
|
+
it 'should have correct #create_authorization_header' do
|
103
|
+
header = Tuppari::Auth.create_authorization_header(
|
104
|
+
:id => 'accessKeyId',
|
105
|
+
:secret => 'accessSecretKey',
|
106
|
+
:method => 'POST',
|
107
|
+
:host => 'api.tuppari.com',
|
108
|
+
:uri => '/test',
|
109
|
+
:query_string => 'a=v1&b=v2',
|
110
|
+
:headers => {
|
111
|
+
'Host' => 'api.tuppari.com',
|
112
|
+
'Content-Type' => 'application/json',
|
113
|
+
'X-Tuppari-Operation' => 'CreateApplication'
|
114
|
+
},
|
115
|
+
:body_params => {
|
116
|
+
'applicationName' => 'example1'
|
117
|
+
},
|
118
|
+
:request_time => Time.new(1970, 1, 1, 0, 0, 0, "+00:00")
|
119
|
+
)
|
120
|
+
header.should eq("HMAC-SHA256 Credential=accessKeyId,SignedHeaders=content-type;host;x-tuppari-operation,Signature=050f8711271747d4f63a3caa3ffb420e4cd5a0e9d9dda8ba7e4faad6794c40d0")
|
121
|
+
end
|
122
|
+
|
123
|
+
it 'should have correct #create_hmac_hash_value' do
|
124
|
+
result = Tuppari::Auth.create_hmac_hash_value('123', 'abc')
|
125
|
+
result.should eq('8f16771f9f8851b26f4d460fa17de93e2711c7e51337cb8a608a0f81e1c1b6ae')
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'tuppari'
|
4
|
+
|
5
|
+
require 'logger'
|
6
|
+
require 'ruby-debug'
|
7
|
+
|
8
|
+
describe Tuppari do
|
9
|
+
|
10
|
+
account_name = ENV['TUPPARI_ACCOUNT_NAME']
|
11
|
+
password = ENV['TUPPARI_PASSWORD']
|
12
|
+
|
13
|
+
=begin
|
14
|
+
|
15
|
+
it "should create a new account" do
|
16
|
+
tuppari = Tuppari.new()
|
17
|
+
response = tuppari.create_account("account_name", "password")
|
18
|
+
puts response
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should create a new account" do
|
22
|
+
tuppari = Tuppari.new(:account_name => account_name, :account_secret => account_secret)
|
23
|
+
response = tuppari.delete_application("application_name")
|
24
|
+
puts response
|
25
|
+
end
|
26
|
+
|
27
|
+
=end
|
28
|
+
|
29
|
+
it "should be created with account_name/account_secret" do
|
30
|
+
tuppari = Tuppari.new(
|
31
|
+
:account_name => account_name,
|
32
|
+
:account_secret => Tuppari.login(account_name, password).account_secret
|
33
|
+
)
|
34
|
+
tuppari.is_authenticated?.should eq(true)
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should login" do
|
38
|
+
tuppari = Tuppari.new()
|
39
|
+
tuppari.is_authenticated?.should eq(false)
|
40
|
+
tuppari.login(account_name, password)
|
41
|
+
tuppari.is_authenticated?.should eq(true)
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should raise Tuppari::AuthenticationError when creating a new applications without authentication" do
|
45
|
+
tuppari = Tuppari.new()
|
46
|
+
name = Time.now.tv_sec.to_s
|
47
|
+
begin
|
48
|
+
tuppari.create_application(name)
|
49
|
+
fail()
|
50
|
+
rescue Tuppari::AuthenticationError
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should create a new application" do
|
55
|
+
tuppari = Tuppari.new(:log_level => Logger::WARN).login(account_name, password)
|
56
|
+
name = Time.now.tv_sec.to_s
|
57
|
+
app = tuppari.create_application(name)
|
58
|
+
app.name.should eq(name)
|
59
|
+
app.id.nil?.should eq(false)
|
60
|
+
app.access_key_id.nil?.should eq(false)
|
61
|
+
app.access_secret_key.nil?.should eq(false)
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should raise Tuppari::AuthenticationError when getting my application list without authentication" do
|
65
|
+
tuppari = Tuppari.new()
|
66
|
+
begin
|
67
|
+
tuppari.get_application_list()
|
68
|
+
fail()
|
69
|
+
rescue Tuppari::AuthenticationError
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should get my application list" do
|
74
|
+
tuppari = Tuppari.new(:log_level => Logger::WARN).login(account_name, password)
|
75
|
+
response = tuppari.get_application_list()
|
76
|
+
response.size.should > 0
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should raise Tuppari::AuthenticationError when publishing messages without authentication" do
|
80
|
+
tuppari = Tuppari.new()
|
81
|
+
begin
|
82
|
+
response = tuppari.publish_message(
|
83
|
+
:application_name => 'tuppari-sample',
|
84
|
+
:channel => 'channel_name',
|
85
|
+
:event => 'event_name',
|
86
|
+
:message => 'Hello, Tuppari!'
|
87
|
+
)
|
88
|
+
fail()
|
89
|
+
rescue Tuppari::AuthenticationError
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
|
94
|
+
it "should publish messages" do
|
95
|
+
tuppari = Tuppari.new(:log_level => Logger::WARN).login(account_name, password)
|
96
|
+
response = tuppari.publish_message(
|
97
|
+
:application_name => 'tuppari-sample',
|
98
|
+
:channel => 'channel_name',
|
99
|
+
:event => 'event_name',
|
100
|
+
:message => 'Hello, Tuppari!'
|
101
|
+
)
|
102
|
+
response.size.should > 0
|
103
|
+
end
|
104
|
+
|
105
|
+
it "should get the service info" do
|
106
|
+
info = Tuppari.new().get_service_info()
|
107
|
+
info['name'].should eq("gyoji")
|
108
|
+
info['version'].nil?.should eq(false)
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
data/tuppari.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
$:.push File.expand_path("../lib", __FILE__)
|
4
|
+
require "tuppari/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = "tuppari"
|
8
|
+
s.version = Tuppari::VERSION
|
9
|
+
s.authors = ["Kazuhiro Sera"]
|
10
|
+
s.email = ["seratch@gmail.com"]
|
11
|
+
s.homepage = "https://github.com/tuppari/tuppari-ruby"
|
12
|
+
s.summary = %q{Tuppari client for Ruby}
|
13
|
+
s.description = %q{Tuppari is an experimental implementation of unbreakable message broadcasting system using WebSocket and Amazon Web Services by @hakobera.}
|
14
|
+
|
15
|
+
s.rubyforge_project = "tuppari"
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
|
20
|
+
s.require_paths = ["lib"]
|
21
|
+
|
22
|
+
s.required_ruby_version = '>= 1.9.0'
|
23
|
+
|
24
|
+
s.add_dependency('rest-client', '1.6.7')
|
25
|
+
s.add_dependency('json', '1.7.4')
|
26
|
+
|
27
|
+
end
|
28
|
+
|
metadata
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: tuppari
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Kazuhiro Sera
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-08-14 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rest-client
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - '='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 1.6.7
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - '='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 1.6.7
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: json
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - '='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 1.7.4
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - '='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 1.7.4
|
46
|
+
description: Tuppari is an experimental implementation of unbreakable message broadcasting
|
47
|
+
system using WebSocket and Amazon Web Services by @hakobera.
|
48
|
+
email:
|
49
|
+
- seratch@gmail.com
|
50
|
+
executables: []
|
51
|
+
extensions: []
|
52
|
+
extra_rdoc_files: []
|
53
|
+
files:
|
54
|
+
- .gitignore
|
55
|
+
- Gemfile
|
56
|
+
- README.md
|
57
|
+
- lib/tuppari.rb
|
58
|
+
- lib/tuppari/application.rb
|
59
|
+
- lib/tuppari/auth.rb
|
60
|
+
- lib/tuppari/authentication_error.rb
|
61
|
+
- lib/tuppari/client.rb
|
62
|
+
- lib/tuppari/client_error.rb
|
63
|
+
- lib/tuppari/server_url.rb
|
64
|
+
- lib/tuppari/version.rb
|
65
|
+
- spec/README.md
|
66
|
+
- spec/tuppari/auth_spec.rb
|
67
|
+
- spec/tuppari_spec.rb
|
68
|
+
- tuppari.gemspec
|
69
|
+
homepage: https://github.com/tuppari/tuppari-ruby
|
70
|
+
licenses: []
|
71
|
+
post_install_message:
|
72
|
+
rdoc_options: []
|
73
|
+
require_paths:
|
74
|
+
- lib
|
75
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
76
|
+
none: false
|
77
|
+
requirements:
|
78
|
+
- - ! '>='
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: 1.9.0
|
81
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
82
|
+
none: false
|
83
|
+
requirements:
|
84
|
+
- - ! '>='
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: '0'
|
87
|
+
requirements: []
|
88
|
+
rubyforge_project: tuppari
|
89
|
+
rubygems_version: 1.8.24
|
90
|
+
signing_key:
|
91
|
+
specification_version: 3
|
92
|
+
summary: Tuppari client for Ruby
|
93
|
+
test_files:
|
94
|
+
- spec/README.md
|
95
|
+
- spec/tuppari/auth_spec.rb
|
96
|
+
- spec/tuppari_spec.rb
|