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