hara 0.3.0 → 0.4.0

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