hara 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.
- data/.travis.yml +4 -0
- data/README.md +11 -3
- data/hara.gemspec +4 -3
- data/lib/hara/app.rb +50 -77
- data/lib/hara/base.rb +0 -21
- data/lib/hara/server.rb +25 -33
- data/lib/hara/version.rb +1 -1
- data/spec/app_spec.rb +17 -50
- data/spec/hara_spec.rb +6 -8
- data/spec/spec_helper.rb +7 -11
- metadata +24 -8
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -3,7 +3,13 @@
|
|
3
3
|
[](http://badge.fury.io/rb/hara)
|
4
4
|
[](https://travis-ci.org/jjyr/hara)
|
5
5
|
|
6
|
-
Hara is a simple framework, help you build websocket server.
|
6
|
+
Hara is a simple framework, help you build async & concurrent websocket server.
|
7
|
+
|
8
|
+
Hara's async ability from [em-websocket](https://github.com/igrigorik/em-websocket),
|
9
|
+
|
10
|
+
Hara's concurrent ability from [celluloid](https://github.com/celluloid/celluloid).
|
11
|
+
|
12
|
+
Yes, hara just a combination of them, eventmachine and celluloid work well together :)
|
7
13
|
|
8
14
|
## Notice!!
|
9
15
|
|
@@ -32,7 +38,7 @@ require 'hara'
|
|
32
38
|
|
33
39
|
class Echo < Hara::App
|
34
40
|
define_action :echo do |str|
|
35
|
-
socket
|
41
|
+
socket.send str
|
36
42
|
end
|
37
43
|
end
|
38
44
|
```
|
@@ -41,7 +47,9 @@ end
|
|
41
47
|
```javascript
|
42
48
|
var msg = JSON.stringify({action: 'echo',args:['hello world']})
|
43
49
|
var ws = new WebSocket('ws://localhost:3210')
|
44
|
-
ws.onmessage = function(msg){
|
50
|
+
ws.onmessage = function(msg){alert(msg.data)}
|
51
|
+
|
52
|
+
//after a while
|
45
53
|
ws.send(msg)
|
46
54
|
//hello world
|
47
55
|
```
|
data/hara.gemspec
CHANGED
@@ -8,8 +8,8 @@ Gem::Specification.new do |spec|
|
|
8
8
|
spec.version = Hara::VERSION
|
9
9
|
spec.authors = ["jjy"]
|
10
10
|
spec.email = ["jjyruby@gmail.com"]
|
11
|
-
spec.description = %q{Hara
|
12
|
-
spec.summary = %q{
|
11
|
+
spec.description = %q{Hara help you build async & concurrent websocket server.}
|
12
|
+
spec.summary = %q{In fact, hara just a combination of em-websocket and celluloid, but it really help you write applications easily.}
|
13
13
|
spec.homepage = "http://github.com/jjyr/hara"
|
14
14
|
spec.license = "MIT"
|
15
15
|
|
@@ -19,7 +19,8 @@ Gem::Specification.new do |spec|
|
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
21
|
|
22
|
-
spec.add_dependency "
|
22
|
+
spec.add_dependency "em-websocket", "~> 0.5.0"
|
23
|
+
spec.add_dependency "celluloid", "~> 0.14.1"
|
23
24
|
|
24
25
|
spec.add_development_dependency "bundler", "~> 1.3"
|
25
26
|
spec.add_development_dependency "rake"
|
data/lib/hara/app.rb
CHANGED
@@ -1,14 +1,32 @@
|
|
1
|
-
require
|
1
|
+
require "hara/version"
|
2
|
+
require 'celluloid'
|
2
3
|
require 'json'
|
3
4
|
|
4
5
|
module Hara
|
6
|
+
class << self
|
7
|
+
def env
|
8
|
+
@_env ||= (ENV['APP_ENV'] || :development).to_sym
|
9
|
+
end
|
10
|
+
|
11
|
+
def env= env
|
12
|
+
@_env = env
|
13
|
+
end
|
14
|
+
|
15
|
+
def decode_msg msg
|
16
|
+
msg = JSON.parse(msg)
|
17
|
+
msg.values_at 'action', 'args'
|
18
|
+
end
|
19
|
+
|
20
|
+
def encode_msg action, *args
|
21
|
+
{action: action, args: args}.to_json
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
5
25
|
class App
|
6
|
-
include Celluloid
|
26
|
+
include Celluloid
|
7
27
|
include Celluloid::Logger
|
8
28
|
|
9
|
-
attr_reader :socket
|
10
|
-
|
11
|
-
alias halt terminate
|
29
|
+
attr_reader :socket, :handshake
|
12
30
|
|
13
31
|
finalizer :app_finalizer
|
14
32
|
|
@@ -16,34 +34,20 @@ module Hara
|
|
16
34
|
|
17
35
|
class << self
|
18
36
|
def inherited klass
|
19
|
-
|
37
|
+
::Hara.const_set :Application, klass
|
20
38
|
end
|
21
39
|
|
22
40
|
def define_action action, &block
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
41
|
+
action = action.to_s
|
42
|
+
warn "Action #{action} duplication defined" if Actions.has_key? action
|
43
|
+
Hara::Application.send :define_method, action, &block
|
44
|
+
method = Hara::Application.send :instance_method, action
|
45
|
+
Hara::Application.send :remove_method, action
|
46
|
+
Actions[action] = method
|
29
47
|
end
|
30
48
|
end
|
31
49
|
|
32
|
-
|
33
|
-
def init
|
34
|
-
end
|
35
|
-
|
36
|
-
#return true or false
|
37
|
-
def authentication
|
38
|
-
true
|
39
|
-
end
|
40
|
-
|
41
|
-
def authentication_failed
|
42
|
-
halt
|
43
|
-
end
|
44
|
-
|
45
|
-
#after_authentication only called when authentication succeeded
|
46
|
-
def after_authentication
|
50
|
+
def after_connect
|
47
51
|
end
|
48
52
|
|
49
53
|
def before_action action, args
|
@@ -52,83 +56,52 @@ module Hara
|
|
52
56
|
def after_action action, args
|
53
57
|
end
|
54
58
|
|
55
|
-
def after_process
|
56
|
-
end
|
57
|
-
|
58
59
|
def action_missing action, args
|
59
|
-
info "
|
60
|
+
info "#{headers['host']} request action: #{action} args: #{args.inspect}, action not defined"
|
61
|
+
raise NoMethodError, "undefined action '#{action}' for #{self}:#{self.class}"
|
60
62
|
end
|
61
63
|
|
62
|
-
def
|
63
|
-
|
64
|
+
def headers
|
65
|
+
handshake.headers_downcased
|
64
66
|
end
|
65
67
|
|
66
68
|
def on_close
|
67
69
|
end
|
68
70
|
|
69
|
-
|
70
|
-
def initialize socket
|
71
|
-
@socket = socket
|
72
|
-
async.setup
|
73
|
-
end
|
71
|
+
#below are internal functions(should not been overriding)
|
74
72
|
|
75
|
-
def
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
authentication_failed
|
80
|
-
else
|
81
|
-
after_authentication
|
82
|
-
async.run
|
83
|
-
end
|
73
|
+
def initialize handshake, socket
|
74
|
+
@handshake = handshake
|
75
|
+
@socket = socket
|
76
|
+
async.hara_setup
|
84
77
|
end
|
85
78
|
|
86
|
-
def
|
87
|
-
|
88
|
-
|
89
|
-
if @closed
|
90
|
-
terminate
|
91
|
-
return
|
92
|
-
end
|
93
|
-
end
|
94
|
-
rescue Reel::SocketError, EOFError #client disconnect
|
95
|
-
info "#{socket.remote_ip} disconnect"
|
96
|
-
begin
|
97
|
-
@closed = true unless @closed
|
98
|
-
on_close
|
99
|
-
ensure
|
100
|
-
terminate
|
101
|
-
end
|
79
|
+
def hara_setup
|
80
|
+
info "#{headers['host']} coming"
|
81
|
+
after_connect
|
102
82
|
end
|
103
83
|
|
104
|
-
def
|
105
|
-
|
106
|
-
action
|
107
|
-
args = @command["args"]
|
108
|
-
info "#{socket.remote_ip} request action: #{action} args: #{args.inspect}"
|
84
|
+
def process_msg message
|
85
|
+
action, args = Hara.decode_msg(message)
|
86
|
+
info "#{headers['host']} request action: #{action} args: #{args.inspect}"
|
109
87
|
before_action action, *args
|
110
88
|
call_action action, *args
|
111
89
|
after_action action, *args
|
112
|
-
rescue JSON::ParserError
|
113
|
-
info "#{socket.remote_ip} message can't parse"
|
114
|
-
terminate
|
115
90
|
rescue StandardError => e
|
116
|
-
info "#{
|
91
|
+
info "#{headers['host']} processing error:\n#{e.inspect}"
|
117
92
|
terminate
|
118
|
-
ensure
|
119
|
-
after_process
|
120
93
|
end
|
121
94
|
|
122
95
|
def call_action action, *args
|
123
96
|
if Actions.has_key? action
|
124
|
-
|
97
|
+
Actions[action].bind(self).call *args
|
125
98
|
else
|
126
|
-
|
99
|
+
action_missing action, *args
|
127
100
|
end
|
128
101
|
end
|
129
102
|
|
130
103
|
def app_finalizer
|
131
|
-
on_close
|
104
|
+
on_close
|
132
105
|
ensure
|
133
106
|
@socket.close if @socket
|
134
107
|
end
|
data/lib/hara/base.rb
CHANGED
@@ -1,24 +1,3 @@
|
|
1
1
|
require "hara/version"
|
2
2
|
require 'hara/app'
|
3
|
-
|
4
|
-
module Hara
|
5
|
-
class << self
|
6
|
-
def env
|
7
|
-
@_env ||= (ENV['APP_ENV'] || :development).to_sym
|
8
|
-
end
|
9
|
-
|
10
|
-
def env= env
|
11
|
-
@_env = env
|
12
|
-
end
|
13
|
-
|
14
|
-
def request_handler &blk
|
15
|
-
if blk
|
16
|
-
@_request_handler = blk
|
17
|
-
else
|
18
|
-
@_request_handler
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
3
|
require 'hara/server'
|
data/lib/hara/server.rb
CHANGED
@@ -1,42 +1,34 @@
|
|
1
|
-
require '
|
1
|
+
require 'em-websocket'
|
2
|
+
require 'eventmachine'
|
2
3
|
require 'hara/base'
|
3
4
|
|
4
5
|
module Hara
|
5
|
-
class Server
|
6
|
-
|
6
|
+
class Server
|
7
|
+
class << self
|
8
|
+
def start host, port
|
9
|
+
puts "Server starting on #{host}:#{port}"
|
10
|
+
EM.epoll
|
11
|
+
EM.run {
|
12
|
+
EM::WebSocket.run(host: host, port: port) do |ws|
|
13
|
+
actor = nil
|
7
14
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
end
|
12
|
-
|
13
|
-
def on_connection(connection)
|
14
|
-
while request = connection.request
|
15
|
-
case request
|
16
|
-
when Reel::WebSocket
|
17
|
-
info "Received a WebSocket connection"
|
18
|
-
handle_websocket request
|
19
|
-
when Reel::Request
|
20
|
-
Hara.request_handler.call connection, request
|
21
|
-
else
|
22
|
-
warn "Connection not support"
|
23
|
-
request.close
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
15
|
+
ws.onopen { |handshake|
|
16
|
+
actor = Hara::Application.new handshake, ws
|
17
|
+
}
|
27
18
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
Hara::Application.new socket
|
35
|
-
end
|
19
|
+
ws.onclose {
|
20
|
+
begin
|
21
|
+
actor.terminate! if actor.alive?
|
22
|
+
rescue Celluloid::DeadActorError => e
|
23
|
+
end
|
24
|
+
}
|
36
25
|
|
37
|
-
|
38
|
-
|
39
|
-
|
26
|
+
ws.onmessage { |msg|
|
27
|
+
actor.async.process_msg msg
|
28
|
+
}
|
29
|
+
end
|
30
|
+
}
|
31
|
+
end
|
40
32
|
end
|
41
33
|
end
|
42
34
|
end
|
data/lib/hara/version.rb
CHANGED
data/spec/app_spec.rb
CHANGED
@@ -3,57 +3,40 @@ require 'spec_helper'
|
|
3
3
|
describe Hara::App do
|
4
4
|
before :all do
|
5
5
|
Class.new Hara::App do
|
6
|
-
def
|
7
|
-
|
6
|
+
def after_connect
|
7
|
+
@states = []
|
8
8
|
end
|
9
9
|
|
10
10
|
def before_action action, *args
|
11
|
-
|
11
|
+
@states << [:before_action, action, args]
|
12
12
|
end
|
13
13
|
|
14
14
|
def after_action action, *args
|
15
|
-
|
16
|
-
end
|
17
|
-
|
18
|
-
def after_process
|
19
|
-
@states << :after_process
|
15
|
+
@states << [:after_action, action, args]
|
20
16
|
end
|
21
17
|
|
22
18
|
def on_close
|
23
|
-
|
19
|
+
@states << :closed
|
24
20
|
end
|
25
21
|
|
26
22
|
def action_missing action, *args
|
27
|
-
|
23
|
+
socket.send [:action_missing, action, args]
|
28
24
|
end
|
29
25
|
|
30
26
|
define_action :hello do |msg|
|
31
|
-
|
32
|
-
end
|
33
|
-
|
34
|
-
def authentication
|
35
|
-
socket.remote_ip == 'localhost'
|
36
|
-
end
|
37
|
-
|
38
|
-
def authentication_failed
|
39
|
-
@states << :fail
|
40
|
-
sleep 0.1
|
41
|
-
end
|
42
|
-
|
43
|
-
def after_authentication
|
44
|
-
@states << :success
|
45
|
-
sleep 0.1
|
27
|
+
socket.send "hello#{msg}"
|
46
28
|
end
|
47
29
|
|
48
30
|
def states
|
49
|
-
|
31
|
+
@states
|
50
32
|
end
|
51
33
|
end
|
52
34
|
end
|
53
35
|
|
54
36
|
before :each do
|
37
|
+
@handshake = FayeHandshake.new
|
55
38
|
@socket = FayeSocket.new
|
56
|
-
@
|
39
|
+
@app = Hara::Application.new @handshake, @socket
|
57
40
|
end
|
58
41
|
|
59
42
|
after :each do
|
@@ -62,48 +45,32 @@ describe Hara::App do
|
|
62
45
|
end
|
63
46
|
end
|
64
47
|
|
65
|
-
it 'authentication_failed' do
|
66
|
-
@socket.remote_ip = '0.0.0.0'
|
67
|
-
@app = Hara::Application.new @socket
|
68
|
-
sleep 0.1 while @app.states.size == 0
|
69
|
-
@app.states.should == [:fail]
|
70
|
-
end
|
71
|
-
|
72
|
-
it 'authentication_succeeded' do
|
73
|
-
@app = Hara::Application.new @socket
|
74
|
-
sleep 0.1 while @app.states.size == 0
|
75
|
-
@app.states.should == [:success]
|
76
|
-
end
|
77
|
-
|
78
48
|
it 'remote call actions' do
|
79
|
-
@
|
80
|
-
@app = Hara::Application.new @socket
|
49
|
+
@app.process_msg(Hara.encode_msg(:hello, ' world'))
|
81
50
|
sleep 0.1 until msg = @socket.client_read
|
82
51
|
msg.should == 'hello world'
|
83
52
|
end
|
84
53
|
|
85
54
|
it 'error remote call' do
|
86
|
-
@
|
87
|
-
@app = Hara::Application.new @socket
|
55
|
+
@app.process_msg('a error call')
|
88
56
|
sleep 0.1 while @app.alive?
|
89
57
|
msg = @socket.client_read
|
90
58
|
msg.should == nil
|
91
59
|
end
|
92
60
|
|
93
61
|
it 'action_missing should work' do
|
94
|
-
@
|
95
|
-
@app = Hara::Application.new @socket
|
62
|
+
@app.process_msg(Hara.encode_msg(:hello_world, 'hello', 'world'))
|
96
63
|
sleep 0.1 until msg = @socket.client_read
|
97
64
|
msg.should == [:action_missing, 'hello_world', ['hello', 'world']]
|
98
65
|
end
|
99
66
|
|
100
67
|
it 'callbacks should work' do
|
101
|
-
@app
|
102
|
-
@socket.client_send({action: :hello, args: [' world']}.to_json)
|
68
|
+
2.times{ @app.process_msg(Hara.encode_msg(:hello, ' world'))}
|
103
69
|
states = @app.states
|
104
70
|
sleep 0.2
|
105
|
-
@app.
|
71
|
+
@app.terminate
|
106
72
|
sleep 0.1 while @app.alive?
|
107
|
-
|
73
|
+
action_callbacks = [[:before_action, "hello", [" world"]], [:after_action, "hello", [" world"]]]
|
74
|
+
states.should == [*(action_callbacks * 2),:closed]
|
108
75
|
end
|
109
76
|
end
|
data/spec/hara_spec.rb
CHANGED
@@ -10,13 +10,11 @@ describe Hara do
|
|
10
10
|
Hara.env.should == :development
|
11
11
|
end
|
12
12
|
|
13
|
-
it 'Hara.
|
14
|
-
|
15
|
-
|
16
|
-
Hara.
|
17
|
-
|
18
|
-
|
19
|
-
Hara.request_handler.call(40, 2).should == 42
|
20
|
-
Hara.request_handler &handler
|
13
|
+
it 'Hara.encode_msg & decode_msg should work' do
|
14
|
+
action = 'hello_world'
|
15
|
+
args = ['hello', 'world']
|
16
|
+
action_d, args_d = Hara.decode_msg Hara.encode_msg(action, *args)
|
17
|
+
action.should == action_d
|
18
|
+
args.should == args_d
|
21
19
|
end
|
22
20
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,9 +1,13 @@
|
|
1
1
|
require 'hara/base'
|
2
2
|
|
3
|
-
require 'json'
|
4
|
-
|
5
3
|
Celluloid.logger = nil
|
6
4
|
|
5
|
+
class FayeHandshake
|
6
|
+
def headers_downcased
|
7
|
+
{'host' => 'localhost:8080'}
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
7
11
|
class FayeSocket
|
8
12
|
attr_accessor :remote_ip
|
9
13
|
|
@@ -20,19 +24,11 @@ class FayeSocket
|
|
20
24
|
@alive = false
|
21
25
|
end
|
22
26
|
|
23
|
-
def
|
24
|
-
@server_messages.shift
|
25
|
-
end
|
26
|
-
|
27
|
-
def << message
|
27
|
+
def send message
|
28
28
|
@client_messages << message
|
29
29
|
end
|
30
30
|
|
31
31
|
def client_read
|
32
32
|
@client_messages.shift
|
33
33
|
end
|
34
|
-
|
35
|
-
def client_send message
|
36
|
-
@server_messages << message
|
37
|
-
end
|
38
34
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hara
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,16 +9,16 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-07-16 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
|
-
name:
|
15
|
+
name: em-websocket
|
16
16
|
requirement: !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ~>
|
20
20
|
- !ruby/object:Gem::Version
|
21
|
-
version:
|
21
|
+
version: 0.5.0
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
24
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -26,7 +26,23 @@ dependencies:
|
|
26
26
|
requirements:
|
27
27
|
- - ~>
|
28
28
|
- !ruby/object:Gem::Version
|
29
|
-
version:
|
29
|
+
version: 0.5.0
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: celluloid
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 0.14.1
|
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: 0.14.1
|
30
46
|
- !ruby/object:Gem::Dependency
|
31
47
|
name: bundler
|
32
48
|
requirement: !ruby/object:Gem::Requirement
|
@@ -59,8 +75,7 @@ dependencies:
|
|
59
75
|
- - ! '>='
|
60
76
|
- !ruby/object:Gem::Version
|
61
77
|
version: '0'
|
62
|
-
description: Hara
|
63
|
-
by reel and celluloid.
|
78
|
+
description: Hara help you build async & concurrent websocket server.
|
64
79
|
email:
|
65
80
|
- jjyruby@gmail.com
|
66
81
|
executables: []
|
@@ -107,7 +122,8 @@ rubyforge_project:
|
|
107
122
|
rubygems_version: 1.8.23
|
108
123
|
signing_key:
|
109
124
|
specification_version: 3
|
110
|
-
summary:
|
125
|
+
summary: In fact, hara just a combination of em-websocket and celluloid, but it really
|
126
|
+
help you write applications easily.
|
111
127
|
test_files:
|
112
128
|
- spec/app_spec.rb
|
113
129
|
- spec/hara_spec.rb
|