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 CHANGED
@@ -3,3 +3,7 @@ rvm:
3
3
  - 2.0.0
4
4
  - 1.9.3
5
5
  - jruby-19mode
6
+
7
+ matrix:
8
+ allow_failures:
9
+ - rvm: jruby-19mode
data/README.md CHANGED
@@ -3,7 +3,13 @@
3
3
  [![Gem Version](https://badge.fury.io/rb/hara.png)](http://badge.fury.io/rb/hara)
4
4
  [![Build Status](https://travis-ci.org/jjyr/hara.png?branch=master)](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 << str
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){console.log(msg.data)}
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 is a simple framework, help you build [web ]socket server. Power by reel and celluloid.}
12
- spec.summary = %q{Hara can help your build [web ]socket server easier.}
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 "reel", "~> 0.3"
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 'celluloid/io'
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::IO
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
- ::Hara.const_set :Application, klass
37
+ ::Hara.const_set :Application, klass
20
38
  end
21
39
 
22
40
  def define_action action, &block
23
- action = action.to_s
24
- warn "Action #{action} duplication defined" if Actions.has_key? action
25
- Hara::Application.send :define_method, action, &block
26
- method = Hara::Application.send :instance_method, action
27
- Hara::Application.send :remove_method, action
28
- Actions[action] = method
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 " #{socket.remote_ip} request action: #{action} args: #{args.inspect}, action not defined"
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 command
63
- @command || {}
64
+ def headers
65
+ handshake.headers_downcased
64
66
  end
65
67
 
66
68
  def on_close
67
69
  end
68
70
 
69
- private
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 setup
76
- init
77
- info "#{socket.remote_ip} coming"
78
- unless authentication
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 run
87
- while msg = @socket.read
88
- process msg
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 process message
105
- @command = JSON.parse(message)
106
- action = @command["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 "#{socket.remote_ip} processing error:\n#{e.inspect}"
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
- Actions[action].bind(self).call *args
97
+ Actions[action].bind(self).call *args
125
98
  else
126
- action_missing action, *args
99
+ action_missing action, *args
127
100
  end
128
101
  end
129
102
 
130
103
  def app_finalizer
131
- on_close unless @closed
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 'reel'
1
+ require 'em-websocket'
2
+ require 'eventmachine'
2
3
  require 'hara/base'
3
4
 
4
5
  module Hara
5
- class Server < Reel::Server
6
- include Celluloid::Logger
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
- def initialize host, port
9
- info "Server starting on #{host}:#{port}"
10
- super(host, port, &method(:on_connection))
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
- Hara.request_handler do |connection, request|
29
- info "#{request.remote_ip} request #{request.url}, not support"
30
- request.close
31
- end
32
-
33
- def handle_websocket socket
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
- def self.start host, port
38
- self.supervise_as :hara, host, port
39
- sleep
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
@@ -1,3 +1,3 @@
1
1
  module Hara
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
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 init
7
- @states = []
6
+ def after_connect
7
+ @states = []
8
8
  end
9
9
 
10
10
  def before_action action, *args
11
- @states << [:before_action, action, args]
11
+ @states << [:before_action, action, args]
12
12
  end
13
13
 
14
14
  def after_action action, *args
15
- @states << [:after_action, action, args]
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
- @states << :closed
19
+ @states << :closed
24
20
  end
25
21
 
26
22
  def action_missing action, *args
27
- socket << [:action_missing, action, args]
23
+ socket.send [:action_missing, action, args]
28
24
  end
29
25
 
30
26
  define_action :hello do |msg|
31
- socket << "hello#{msg}"
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
- @states
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
- @socket.remote_ip = 'localhost'
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
- @socket.client_send({action: :hello, args: [' world']}.to_json)
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
- @socket.client_send("a error call")
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
- @socket.client_send({action: :hello_world, args: ['hello', 'world']}.to_json)
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 = Hara::Application.new @socket
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.halt
71
+ @app.terminate
106
72
  sleep 0.1 while @app.alive?
107
- states.should == [:success, [:before_action, "hello", [" world"]], [:after_action, "hello", [" world"]], :after_process,:closed]
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.request_handler should work' do
14
- handler = Hara.request_handler
15
- handler.class.should == Proc
16
- Hara.request_handler do |conn, req|
17
- conn + req
18
- end
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 read
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.1.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-06-18 00:00:00.000000000 Z
12
+ date: 2013-07-16 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
- name: reel
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: '0.3'
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: '0.3'
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 is a simple framework, help you build [web ]socket server. Power
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: Hara can help your build [web ]socket server easier.
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