hara 0.3.0 → 0.4.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7f83818ae52c8a4eb9db097c057149a4b1c4078e
4
- data.tar.gz: ba02fe00a42900cea3af4d7ee8a60aa74ecb31f8
3
+ metadata.gz: 5863328a79f220ece656a08e162f10675e758dd7
4
+ data.tar.gz: addd1f740c431d625188663a8640aebdcf6f9ea2
5
5
  SHA512:
6
- metadata.gz: 4b60a714370f02384741883e26995d5f3136166e7df01236691502b3a739510e49d0eea863e487f21711bbdb87dcfd6c2aba7300a84449119edc386ad330e7b3
7
- data.tar.gz: f33ffb17c068aab2a9cb35b02b8f17a98084884e7700113474ad3d4d52f507a573e94f85964084efb15e1df4a9e0fb868f91cd2f128d7f703c8fd7d6e2e70372
6
+ metadata.gz: 13a36a9802a40a714c18e357cf70e43c5a806cdc6181e69a3688403e965def6f0d6a25510687fcbdb67f4025bc9831b7ff3ac48beeb8f0c441bffb2956281e50
7
+ data.tar.gz: 104134728df1f2a724fdad3b68f3a4757b54d4d40e45de4906deb18ba2586dda4b90a66e837a2f4dd3f5b17dc04d6856ddae0f3fbd5a3e60f2fd779969eea6d9
data/Gemfile CHANGED
@@ -1,6 +1,7 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
3
  gem 'rspec'
4
+ gem 'therubyracer', :group => [:development, :test]
4
5
 
5
6
  # Specify your gem's dependencies in hara.gemspec
6
7
  gemspec
data/README.md CHANGED
@@ -1,13 +1,13 @@
1
- # Hara
1
+ # Hara(袚)
2
2
 
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 websocket based application framework, build upon [em-websocket](https://github.com/igrigorik/em-websocket).
6
+ Hara(袚) is a websocket based application framework, build upon [em-websocket](https://github.com/igrigorik/em-websocket).
7
7
 
8
8
  * Simple, easy to use.
9
- * OO, Actor model(Celluloid).
10
- * Event-io(em-websocket).
9
+ * Actor model(celluloid).
10
+ * Event-IO(em-websocket).
11
11
 
12
12
  ## Installation
13
13
 
@@ -23,18 +23,21 @@ Or install it yourself as:
23
23
 
24
24
  $ gem install hara
25
25
 
26
+ Client:
27
+
28
+ copy it from ./client
29
+
26
30
  ## Basic Usage
27
31
 
28
32
  *server*
29
33
  ```ruby
30
- #test.rb
31
34
  require 'hara'
32
35
 
33
- class Echo
36
+ class Test
34
37
  include Hara::App
35
38
 
36
- define_action :echo do |str|
37
- send_msg str
39
+ define_action :reverse do |str|
40
+ response_msg str.reverse
38
41
  end
39
42
  end
40
43
 
@@ -43,28 +46,19 @@ Hara::Server.start 'localhost', '3000'
43
46
 
44
47
  *client*
45
48
  ```javascript
46
- var msg = JSON.stringify({action: 'echo',args:['hello world']})
47
- var ws = new WebSocket('ws://localhost:3000')
48
- ws.onmessage = function(msg){alert(msg.data)}
49
-
50
- //after a while
51
- ws.send(msg)
52
- //hello world
49
+ var client = new Hara();
50
+ client.connect('ws://localhost:3000');
51
+ client.send('reverse', ['hello world'], function(msg){alert(msg)});
53
52
  ```
54
53
 
55
- *start server*
56
- `ruby test.rb`
57
-
58
- `ruby test.rb -h` to view options
59
-
60
54
  ## Full Usages
61
55
 
56
+ *server*
62
57
  ```ruby
63
58
  require 'hara'
64
59
 
65
- class Echo
66
- #include Hara::App make your Echo class become Celluloid::Actor,
67
- #hara use actor per connection
60
+ class Clock
61
+ #include Hara::App make your class become Celluloid::Actor
68
62
  include Hara::App
69
63
 
70
64
  #Hara::App provide some callbacks
@@ -72,22 +66,29 @@ class Echo
72
66
  def after_connect
73
67
  puts 'first called'
74
68
  p headers
69
+ # push message to client
70
+ send_msg "connected"
75
71
  end
76
72
 
77
73
  def before_action action, *args
78
74
  puts 'called when action comming'
79
75
  end
80
76
 
81
- define_action :echo do |str|
77
+ define_action :start do
82
78
  puts "#{client_ip} #{client_port}"
83
- #send message to client
84
- send_msg str
79
+
80
+ # push time to client every 1 sec
81
+ @timer = every(1){ send_msg Time.now.to_s}
82
+
83
+ # different between send_msg & response_msg
84
+ # send_msg means push to client, trigger client onmessage callback
85
+ # response_msg respond client request, and trigger send callback(if it present)
86
+ response_msg 'started'
85
87
  end
86
88
 
87
- define_action :exit do
88
- puts "#{client_ip} exit"
89
- # close client connection
90
- close
89
+ define_action :stop do
90
+ @timer.cancel
91
+ response_msg 'stoped'
91
92
  end
92
93
 
93
94
  def after_action action, *args
@@ -95,7 +96,7 @@ class Echo
95
96
  end
96
97
 
97
98
  def action_missing action, *args
98
- send_msg 'error'
99
+ puts 'error'
99
100
  super
100
101
  end
101
102
 
@@ -109,8 +110,36 @@ server_options = {
109
110
  #...some options, same as EM::Websocket.run
110
111
  }
111
112
  Hara::Server.start 'localhost', '3000', server_options
113
+ ```
112
114
 
115
+ *client*
116
+ ```javascript
117
+ var client = new Hara();
118
+
119
+ //handle pushed messages(send_msg)
120
+ client.onmessage = function(msg){
121
+ console.log("current time:" + msg);
122
+ }
123
+
124
+ //connect to server
125
+ client.connect('ws://localhost:3000');
126
+
127
+ //call server side action
128
+ client.send('start', [], function(msg){console.log(msg)});
129
+ //started
130
+ //current time:2013-08-05 10:48:04 +0800
131
+ //current time:2013-08-05 10:48:05 +0800
132
+ //current time:2013-08-05 10:48:06 +0800
133
+ //current time:2013-08-05 10:48:07 +0800
134
+ client.send('stop', [], function(msg){console.log(msg)});
135
+ //stoped
136
+
137
+ //close connection
138
+ client.close();
139
+ ```
113
140
 
141
+ ####use filter####
142
+ ```ruby
114
143
  # Hara::Filter
115
144
  # Filter can help you filter some connections before dispatched to app actor.
116
145
  # Example: use Filter to authentication
@@ -124,6 +153,9 @@ end
124
153
  #class name is not matter
125
154
  class Authentication
126
155
  include Hara::Filter
156
+
157
+ #default value is 10
158
+ self.pool_size = 20
127
159
 
128
160
  # You must implement filter method, return value should be ture or false
129
161
  def filter
@@ -135,12 +167,6 @@ end
135
167
  Hara::Server.start 'localhost', '3000'
136
168
  ```
137
169
 
138
- ## Client
139
-
140
- js client is processing
141
-
142
- current format is JSON.stringify({action: 'echo',args:['hello world']})
143
-
144
170
  ## Contributing
145
171
 
146
172
  1. Fork it
@@ -0,0 +1,78 @@
1
+ function Hara() {
2
+ this.send_message_ids = {};
3
+ this.onopen = this.onclose = this.onerror = this.onmessage = function(){};
4
+ }
5
+
6
+ Hara.prototype.send = function(action, args, callback){
7
+ var id = Hara.uuid();
8
+ var message = Hara.encode({_id: id, action: action, args: args});
9
+ this.send_message_ids[id] = callback;
10
+ this.websocket.send(message);
11
+ }
12
+
13
+ Hara.prototype.connect = function(url, protocol){
14
+ this.websocket = new WebSocket(url, protocol);
15
+ this.setup_callbacks();
16
+ }
17
+
18
+ Hara.prototype.setup_callbacks = function(){
19
+ var that = this;
20
+ this.websocket.onopen = function(){
21
+ that.onopen.apply(that, arguments);
22
+ };
23
+ this.websocket.onclose = function(){
24
+ that.onclose.apply(that, arguments);
25
+ };
26
+ this.websocket.onerror = function(){
27
+ that.onerror.apply(that, arguments);
28
+ };
29
+ this.websocket.onmessage = function(msg){
30
+ var message = Hara.decode(msg.data);
31
+ switch(message.type){
32
+ case 'response':
33
+ that.handle_response(message);
34
+ break;
35
+ case 'push':
36
+ that.handle_push(message);
37
+ break;
38
+ default:
39
+ throw 'Unknown message type'
40
+ }
41
+ };
42
+ };
43
+
44
+ Hara.prototype.handle_response = function(message) {
45
+ var id = message._id;
46
+ var args = message.args;
47
+ var callback = this.send_message_ids[id];
48
+ if(callback) {
49
+ callback.apply(this, [args]);
50
+ }
51
+ delete this.send_message_ids[id];
52
+ };
53
+
54
+ Hara.prototype.handle_push = function(message) {
55
+ var args = message.args;
56
+ this.onmessage(args);
57
+ };
58
+
59
+ Hara.prototype.close = function(){
60
+ this.websocket.close.apply(this.websocket, arguments);
61
+ this.send_message_ids = {};
62
+ }
63
+
64
+ Hara.uuid = function() {
65
+ var uuid = ('xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
66
+ var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
67
+ return v.toString(16);
68
+ }));
69
+ return uuid;
70
+ }
71
+
72
+ Hara.encode = function(args) {
73
+ return JSON.stringify(args);
74
+ }
75
+
76
+ Hara.decode = function(args){
77
+ return JSON.parse(args);
78
+ }
@@ -4,12 +4,11 @@ module Hara
4
4
  class << self
5
5
  #decode message, return action and args
6
6
  def decode_msg msg
7
- msg = JSON.parse(msg)
8
- msg.values_at 'action', 'args'
7
+ JSON.parse msg
9
8
  end
10
9
 
11
- def encode_msg action, *args
12
- {action: action, args: args}.to_json
10
+ def encode_msg msg
11
+ msg.to_json
13
12
  end
14
13
 
15
14
  def filter_class
@@ -46,13 +46,13 @@ module Hara
46
46
 
47
47
  ##################
48
48
 
49
- #like method_missing
49
+ # like method_missing
50
50
  def action_missing action, args
51
51
  info "#{client_ip} request action: #{action} args: #{args.inspect}, action not defined"
52
52
  raise NoMethodError, "undefined action '#{action}' for #{self}:#{self.class}"
53
53
  end
54
54
 
55
- #below are internal functions(should not been overriding)
55
+ # below are internal functions(should not been overriding)
56
56
 
57
57
  def initialize handshake, socket
58
58
  socket_setup handshake, socket
@@ -65,14 +65,31 @@ module Hara
65
65
  end
66
66
 
67
67
  def process_msg message
68
- action, args = Hara.decode_msg(message)
69
- info "#{client_ip} request action: #{action} args: #{args.inspect}"
70
- before_action action, *args
71
- call_action action, *args
72
- after_action action, *args
73
- rescue StandardError => e
74
- info "#{client_ip} processing error:\n#{e.inspect}"
75
- terminate
68
+ exclusive do
69
+ begin
70
+ id, action, args = Hara.decode_msg(message).values_at('_id', 'action', 'args')
71
+ info "#{client_ip} request action: #{action} args: #{args.inspect}"
72
+ before_action action, *args
73
+ call_action action, *args
74
+ after_action action, *args
75
+ send_response_msg id
76
+ rescue StandardError => e
77
+ info "#{client_ip} processing error:\n#{e.inspect}"
78
+ terminate
79
+ end
80
+ end
81
+ end
82
+
83
+ # respond to client
84
+ def response_msg msg
85
+ raise DuplicateResponseError if @_response_msg
86
+ @_response_msg = msg
87
+ end
88
+
89
+ def send_response_msg message_id
90
+ msg, @_response_msg = @_response_msg, nil
91
+ message = Hara.encode_msg(_id: message_id, type: :response, args: msg)
92
+ socket.send message
76
93
  end
77
94
 
78
95
  def call_action action, *args
@@ -1,4 +1,7 @@
1
1
  module Hara
2
+ class DuplicateResponseError < StandardError
3
+ end
4
+
2
5
  module ClientInteraction
3
6
  attr_reader :socket, :handshake, :client_ip, :client_port
4
7
 
@@ -15,7 +18,12 @@ module Hara
15
18
 
16
19
  # send msg to client
17
20
  def send_msg msg
18
- socket.send msg
21
+ message = Hara.encode_msg(type: :push, args: msg)
22
+ socket.send message
23
+ end
24
+
25
+ def response_msg msg
26
+ raise NotImplementedError
19
27
  end
20
28
 
21
29
  # close connection
@@ -1,3 +1,3 @@
1
1
  module Hara
2
- VERSION = "0.3.0"
2
+ VERSION = "0.4.0"
3
3
  end
@@ -40,8 +40,8 @@ describe Hara::App do
40
40
  end
41
41
 
42
42
  before :each do
43
- @handshake = FayeHandshake.new
44
- @socket = FayeSocket.new
43
+ @handshake = FakeHandshake.new
44
+ @socket = FakeSocket.new
45
45
  @app = Hara::Application.new @handshake, @socket
46
46
  @socket.app = @app
47
47
  end
@@ -59,35 +59,33 @@ describe Hara::App do
59
59
  end
60
60
 
61
61
  it 'remote call actions' do
62
- @app.process_msg(Hara.encode_msg(:hello, ' world'))
62
+ @socket.client_send(:hello, [' world'])
63
63
  msg = nil
64
64
  wait_until{msg = @socket.client_read}
65
65
  msg.should == 'hello world'
66
66
  end
67
67
 
68
68
  it 'close should close connection' do
69
- @app.process_msg(Hara.encode_msg(:exit))
69
+ @socket.client_send(:exit, [])
70
70
  wait_until{!@app.alive?}
71
71
  @socket.alive?.should == false
72
72
  @socket.close_info.should == [3333, 'Bye']
73
73
  end
74
74
 
75
75
  it 'error remote call' do
76
- @app.process_msg('a error call')
76
+ @socket.client_send(:hello, [])
77
77
  wait_until{!@app.alive?}
78
- msg = @socket.client_read
79
- msg.should == nil
80
78
  end
81
79
 
82
80
  it 'action_missing should work' do
83
- @app.process_msg(Hara.encode_msg(:hello_world, 'hello', 'world'))
81
+ @socket.client_send(:hello_world, ['hello', 'world'])
84
82
  msg = nil
85
83
  wait_until{msg = @socket.client_read}
86
- msg.should == [:action_missing, 'hello_world', ['hello', 'world']]
84
+ msg.should == ['action_missing', 'hello_world', ['hello', 'world']]
87
85
  end
88
86
 
89
87
  it 'callbacks should work' do
90
- 2.times{ @app.process_msg(Hara.encode_msg(:hello, ' world'))}
88
+ 2.times{ @socket.client_send(:hello, [' world'])}
91
89
  states = @app.states
92
90
  sleep 0.2
93
91
  @app.terminate
@@ -27,8 +27,8 @@ describe Hara::Filter do
27
27
 
28
28
  describe 'filter' do
29
29
  before :each do
30
- @handshake = FayeHandshake.new
31
- @socket = FayeSocket.new
30
+ @handshake = FakeHandshake.new
31
+ @socket = FakeSocket.new
32
32
  @socket.alive = true
33
33
  wait_until{Hara.filter_pool}
34
34
  end
@@ -9,10 +9,7 @@ describe Hara do
9
9
  end
10
10
 
11
11
  it 'Hara.encode_msg & decode_msg should work' do
12
- action = 'hello_world'
13
- args = ['hello', 'world']
14
- action_d, args_d = Hara.decode_msg Hara.encode_msg(action, *args)
15
- action.should == action_d
16
- args.should == args_d
12
+ msg = {'action' => 'hello_world', 'args' => ['hello', 'world']}
13
+ Hara.decode_msg(Hara.encode_msg msg).should == msg
17
14
  end
18
15
  end
@@ -8,13 +8,13 @@ def wait_until wait_time = 3
8
8
  end
9
9
  end
10
10
 
11
- class FayeHandshake
11
+ class FakeHandshake
12
12
  def headers_downcased
13
13
  {'host' => 'localhost:8080'}
14
14
  end
15
15
  end
16
16
 
17
- class FayeSocket
17
+ class FakeSocket
18
18
  attr_accessor :remote_ip, :close_info, :app
19
19
 
20
20
  def initialize
@@ -22,6 +22,7 @@ class FayeSocket
22
22
  @server_messages = []
23
23
  @mri_peername = "\x02\x00\x00P\x7F\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00"
24
24
  @jruby_peername = "\x00\x02\x8Av\x7F\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00"
25
+ @send_message_ids = {}
25
26
  end
26
27
 
27
28
  def app= app
@@ -49,12 +50,18 @@ class FayeSocket
49
50
  end
50
51
  end
51
52
 
53
+ def client_send action, args
54
+ id = SecureRandom.uuid
55
+ @server_messages << Hara.encode_msg(id: id, action: action, args: args)
56
+ @app.process_msg @server_messages.shift
57
+ end
58
+
52
59
  def send message
53
60
  @client_messages << message
54
61
  end
55
62
 
56
63
  def client_read
57
- @client_messages.shift
64
+ Hara.decode_msg(@client_messages.shift)['args']
58
65
  end
59
66
 
60
67
  [:onclose, :onmessage, :onopen].each do |method|
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hara
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - jjy
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-07-29 00:00:00.000000000 Z
11
+ date: 2013-08-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: em-websocket
@@ -93,6 +93,7 @@ files:
93
93
  - LICENSE.txt
94
94
  - README.md
95
95
  - Rakefile
96
+ - client/hara.js
96
97
  - hara.gemspec
97
98
  - lib/hara.rb
98
99
  - lib/hara/app.rb
@@ -125,7 +126,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
125
126
  version: '0'
126
127
  requirements: []
127
128
  rubyforge_project:
128
- rubygems_version: 2.0.5
129
+ rubygems_version: 2.0.6
129
130
  signing_key:
130
131
  specification_version: 4
131
132
  summary: Hara build upon em-websocket, easy to use.