gorsse 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +3 -0
- data/LICENCE +165 -0
- data/README.md +219 -0
- data/lib/gorsse.rb +41 -0
- data/lib/gorsse/client.rb +13 -0
- data/lib/gorsse/command.rb +16 -0
- data/lib/gorsse/config.rb +5 -0
- data/lib/gorsse/connection.rb +40 -0
- data/lib/gorsse/event.rb +41 -0
- data/lib/gorsse/protocol.rb +23 -0
- data/lib/gorsse/version.rb +3 -0
- data/spec/gorsse/event_spec.rb +51 -0
- data/spec/test_helper.rb +14 -0
- metadata +115 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: bffb89b9e63f2abe4eea6f3b91f65c4f8b08d24d
|
4
|
+
data.tar.gz: fee3dec259477b40b7cf65dfe5b1b53ad6bbe364
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 4c1aa5c596190dc4c21ccfd30c5d99ca9a0431d47955de8191d6fbe0600c134c340c18e402040a9d59b726733585f2e7278eee12f08636c724fd664e9e04d9f7
|
7
|
+
data.tar.gz: 6229d81e78d340d42cd3a9b51a4818b55abe557036d5c7021123e9d02d53cb4c6c001d96fb04a26ee698708d9bf3fae75b102a41061e58ca658a82ed9f4d1cc9
|
data/Gemfile
ADDED
data/LICENCE
ADDED
@@ -0,0 +1,165 @@
|
|
1
|
+
GNU LESSER GENERAL PUBLIC LICENSE
|
2
|
+
Version 3, 29 June 2007
|
3
|
+
|
4
|
+
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
5
|
+
Everyone is permitted to copy and distribute verbatim copies
|
6
|
+
of this license document, but changing it is not allowed.
|
7
|
+
|
8
|
+
|
9
|
+
This version of the GNU Lesser General Public License incorporates
|
10
|
+
the terms and conditions of version 3 of the GNU General Public
|
11
|
+
License, supplemented by the additional permissions listed below.
|
12
|
+
|
13
|
+
0. Additional Definitions.
|
14
|
+
|
15
|
+
As used herein, "this License" refers to version 3 of the GNU Lesser
|
16
|
+
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
17
|
+
General Public License.
|
18
|
+
|
19
|
+
"The Library" refers to a covered work governed by this License,
|
20
|
+
other than an Application or a Combined Work as defined below.
|
21
|
+
|
22
|
+
An "Application" is any work that makes use of an interface provided
|
23
|
+
by the Library, but which is not otherwise based on the Library.
|
24
|
+
Defining a subclass of a class defined by the Library is deemed a mode
|
25
|
+
of using an interface provided by the Library.
|
26
|
+
|
27
|
+
A "Combined Work" is a work produced by combining or linking an
|
28
|
+
Application with the Library. The particular version of the Library
|
29
|
+
with which the Combined Work was made is also called the "Linked
|
30
|
+
Version".
|
31
|
+
|
32
|
+
The "Minimal Corresponding Source" for a Combined Work means the
|
33
|
+
Corresponding Source for the Combined Work, excluding any source code
|
34
|
+
for portions of the Combined Work that, considered in isolation, are
|
35
|
+
based on the Application, and not on the Linked Version.
|
36
|
+
|
37
|
+
The "Corresponding Application Code" for a Combined Work means the
|
38
|
+
object code and/or source code for the Application, including any data
|
39
|
+
and utility programs needed for reproducing the Combined Work from the
|
40
|
+
Application, but excluding the System Libraries of the Combined Work.
|
41
|
+
|
42
|
+
1. Exception to Section 3 of the GNU GPL.
|
43
|
+
|
44
|
+
You may convey a covered work under sections 3 and 4 of this License
|
45
|
+
without being bound by section 3 of the GNU GPL.
|
46
|
+
|
47
|
+
2. Conveying Modified Versions.
|
48
|
+
|
49
|
+
If you modify a copy of the Library, and, in your modifications, a
|
50
|
+
facility refers to a function or data to be supplied by an Application
|
51
|
+
that uses the facility (other than as an argument passed when the
|
52
|
+
facility is invoked), then you may convey a copy of the modified
|
53
|
+
version:
|
54
|
+
|
55
|
+
a) under this License, provided that you make a good faith effort to
|
56
|
+
ensure that, in the event an Application does not supply the
|
57
|
+
function or data, the facility still operates, and performs
|
58
|
+
whatever part of its purpose remains meaningful, or
|
59
|
+
|
60
|
+
b) under the GNU GPL, with none of the additional permissions of
|
61
|
+
this License applicable to that copy.
|
62
|
+
|
63
|
+
3. Object Code Incorporating Material from Library Header Files.
|
64
|
+
|
65
|
+
The object code form of an Application may incorporate material from
|
66
|
+
a header file that is part of the Library. You may convey such object
|
67
|
+
code under terms of your choice, provided that, if the incorporated
|
68
|
+
material is not limited to numerical parameters, data structure
|
69
|
+
layouts and accessors, or small macros, inline functions and templates
|
70
|
+
(ten or fewer lines in length), you do both of the following:
|
71
|
+
|
72
|
+
a) Give prominent notice with each copy of the object code that the
|
73
|
+
Library is used in it and that the Library and its use are
|
74
|
+
covered by this License.
|
75
|
+
|
76
|
+
b) Accompany the object code with a copy of the GNU GPL and this license
|
77
|
+
document.
|
78
|
+
|
79
|
+
4. Combined Works.
|
80
|
+
|
81
|
+
You may convey a Combined Work under terms of your choice that,
|
82
|
+
taken together, effectively do not restrict modification of the
|
83
|
+
portions of the Library contained in the Combined Work and reverse
|
84
|
+
engineering for debugging such modifications, if you also do each of
|
85
|
+
the following:
|
86
|
+
|
87
|
+
a) Give prominent notice with each copy of the Combined Work that
|
88
|
+
the Library is used in it and that the Library and its use are
|
89
|
+
covered by this License.
|
90
|
+
|
91
|
+
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
92
|
+
document.
|
93
|
+
|
94
|
+
c) For a Combined Work that displays copyright notices during
|
95
|
+
execution, include the copyright notice for the Library among
|
96
|
+
these notices, as well as a reference directing the user to the
|
97
|
+
copies of the GNU GPL and this license document.
|
98
|
+
|
99
|
+
d) Do one of the following:
|
100
|
+
|
101
|
+
0) Convey the Minimal Corresponding Source under the terms of this
|
102
|
+
License, and the Corresponding Application Code in a form
|
103
|
+
suitable for, and under terms that permit, the user to
|
104
|
+
recombine or relink the Application with a modified version of
|
105
|
+
the Linked Version to produce a modified Combined Work, in the
|
106
|
+
manner specified by section 6 of the GNU GPL for conveying
|
107
|
+
Corresponding Source.
|
108
|
+
|
109
|
+
1) Use a suitable shared library mechanism for linking with the
|
110
|
+
Library. A suitable mechanism is one that (a) uses at run time
|
111
|
+
a copy of the Library already present on the user's computer
|
112
|
+
system, and (b) will operate properly with a modified version
|
113
|
+
of the Library that is interface-compatible with the Linked
|
114
|
+
Version.
|
115
|
+
|
116
|
+
e) Provide Installation Information, but only if you would otherwise
|
117
|
+
be required to provide such information under section 6 of the
|
118
|
+
GNU GPL, and only to the extent that such information is
|
119
|
+
necessary to install and execute a modified version of the
|
120
|
+
Combined Work produced by recombining or relinking the
|
121
|
+
Application with a modified version of the Linked Version. (If
|
122
|
+
you use option 4d0, the Installation Information must accompany
|
123
|
+
the Minimal Corresponding Source and Corresponding Application
|
124
|
+
Code. If you use option 4d1, you must provide the Installation
|
125
|
+
Information in the manner specified by section 6 of the GNU GPL
|
126
|
+
for conveying Corresponding Source.)
|
127
|
+
|
128
|
+
5. Combined Libraries.
|
129
|
+
|
130
|
+
You may place library facilities that are a work based on the
|
131
|
+
Library side by side in a single library together with other library
|
132
|
+
facilities that are not Applications and are not covered by this
|
133
|
+
License, and convey such a combined library under terms of your
|
134
|
+
choice, if you do both of the following:
|
135
|
+
|
136
|
+
a) Accompany the combined library with a copy of the same work based
|
137
|
+
on the Library, uncombined with any other library facilities,
|
138
|
+
conveyed under the terms of this License.
|
139
|
+
|
140
|
+
b) Give prominent notice with the combined library that part of it
|
141
|
+
is a work based on the Library, and explaining where to find the
|
142
|
+
accompanying uncombined form of the same work.
|
143
|
+
|
144
|
+
6. Revised Versions of the GNU Lesser General Public License.
|
145
|
+
|
146
|
+
The Free Software Foundation may publish revised and/or new versions
|
147
|
+
of the GNU Lesser General Public License from time to time. Such new
|
148
|
+
versions will be similar in spirit to the present version, but may
|
149
|
+
differ in detail to address new problems or concerns.
|
150
|
+
|
151
|
+
Each version is given a distinguishing version number. If the
|
152
|
+
Library as you received it specifies that a certain numbered version
|
153
|
+
of the GNU Lesser General Public License "or any later version"
|
154
|
+
applies to it, you have the option of following the terms and
|
155
|
+
conditions either of that published version or of any later version
|
156
|
+
published by the Free Software Foundation. If the Library as you
|
157
|
+
received it does not specify a version number of the GNU Lesser
|
158
|
+
General Public License, you may choose any version of the GNU Lesser
|
159
|
+
General Public License ever published by the Free Software Foundation.
|
160
|
+
|
161
|
+
If the Library as you received it specifies that a proxy can decide
|
162
|
+
whether future versions of the GNU Lesser General Public License shall
|
163
|
+
apply, that proxy's public statement of acceptance of any version is
|
164
|
+
permanent authorization for you to choose that version for the
|
165
|
+
Library.
|
data/README.md
ADDED
@@ -0,0 +1,219 @@
|
|
1
|
+
# Gorsse (Go Ruby SSE)
|
2
|
+
|
3
|
+
<a href="http://twitter.com/nicoolas25"><img src="http://www.pairprogramwith.me/assets/badge.svg" style="height:40px" title="We can pair on this!" /></a>
|
4
|
+
|
5
|
+
This is an proof of concept for an SSE system that will support many
|
6
|
+
users without compromising a Ruby server that isn't that good to handle
|
7
|
+
a lot of alive connections.
|
8
|
+
|
9
|
+
The typical flow is:
|
10
|
+
|
11
|
+
1. The server povides an application
|
12
|
+
2. The client needs realtime informations from the server
|
13
|
+
3. The client establish a SSE connection to the application
|
14
|
+
4. The server send the informations via the SSE connection
|
15
|
+
5. The client is happy to have real-time updates
|
16
|
+
|
17
|
+
If your application get too many client, your server will have to handle
|
18
|
+
as much as connections. Since Ruby isn't very well suited for parallelism,
|
19
|
+
the existing webservers forks and/or threads the application to handle
|
20
|
+
multiple requests at a time. To serve two clients exactly at the same time,
|
21
|
+
you have to run two instances of your application. If those two client need
|
22
|
+
to keep the connection alive then your application is down to the rest of
|
23
|
+
the world...
|
24
|
+
|
25
|
+
This may be not the case on all the Ruby implementation and for all
|
26
|
+
webservers, see the alternative section for more details about it.
|
27
|
+
|
28
|
+
## Usage examples
|
29
|
+
|
30
|
+
This project can be useful when you're using:
|
31
|
+
|
32
|
+
* chat systems,
|
33
|
+
* asynchronous job notifications,
|
34
|
+
* live monitoring,
|
35
|
+
* et cetera
|
36
|
+
|
37
|
+
|
38
|
+
## Architecture
|
39
|
+
|
40
|
+
The whole idea is to keep your Ruby application as it is, adding one
|
41
|
+
or two other elements to your existing architecture.
|
42
|
+
|
43
|
+
### The connection handler
|
44
|
+
|
45
|
+
When you run the connection handler, it will accept HTTP requests and
|
46
|
+
stream the response to the client. It will maintain the connection to the
|
47
|
+
client open.
|
48
|
+
|
49
|
+
The connection handler will also listen events from your application. When
|
50
|
+
a such event is received, it is forwarded to the relevent clients.
|
51
|
+
|
52
|
+
The connection handler lives in a separate project. It's written in a
|
53
|
+
language that have a more advanced concurrency and parallelism model than
|
54
|
+
Ruby.
|
55
|
+
|
56
|
+
See the documentation of the connection handler to configure it.
|
57
|
+
|
58
|
+
### The callback receiver (optionnal)
|
59
|
+
|
60
|
+
This element receive callback from the connection handler. Those
|
61
|
+
callbacks can trigger business Ruby code depending on your needs.
|
62
|
+
|
63
|
+
If you don't need the callback receiver, you should disable them in
|
64
|
+
the connection handler configuration. In this case, you dont have to
|
65
|
+
run this component at all.
|
66
|
+
|
67
|
+
### Communication
|
68
|
+
|
69
|
+
ZeroMQ allows the communication between:
|
70
|
+
|
71
|
+
* your application, that send event to the connection handler,
|
72
|
+
* the connection handler, that receive those events via SSE and
|
73
|
+
* the callback receiver.
|
74
|
+
|
75
|
+
## Integration
|
76
|
+
|
77
|
+
This is probably the part that you've been waiting...
|
78
|
+
|
79
|
+
### Configuration
|
80
|
+
|
81
|
+
First of all, your application must require 'gorsse' and configure it
|
82
|
+
properly:
|
83
|
+
|
84
|
+
~~~ruby
|
85
|
+
require 'gorsse'
|
86
|
+
|
87
|
+
Gorsse.configure do |config|
|
88
|
+
config.receiver = 'tcp://127.0.0.1:4567'
|
89
|
+
config.handler = 'tcp://127.0.0.1:4568'
|
90
|
+
end
|
91
|
+
~~~
|
92
|
+
|
93
|
+
The addresses are directly passed to ZeroMQ, feel free to take advantage
|
94
|
+
of it.
|
95
|
+
|
96
|
+
The receiver line isn't required unless you use the callback receiver.
|
97
|
+
|
98
|
+
### Protocols & scopes
|
99
|
+
|
100
|
+
Gorsse is using two concepts to create SSE channels where it is possible
|
101
|
+
to publish informations.
|
102
|
+
|
103
|
+
*Protocols* match a specific communication pattern from your server to
|
104
|
+
your clients. This is an example of a two empty protocol:
|
105
|
+
|
106
|
+
~~~ruby
|
107
|
+
class PostFeed < Gorsse::Protocol ; end
|
108
|
+
class ChatFeed < Gorsse::Protocol ; end
|
109
|
+
~~~
|
110
|
+
|
111
|
+
*Scopes* are like an instance of the protocol, it's narrowing it. For
|
112
|
+
instance, if your application is a blog provider SaaS then you'll have
|
113
|
+
one `PostFeed` scope per blog. Or, if your application is a chat server
|
114
|
+
then you'll have one `ChatFeed` scope per channel.
|
115
|
+
|
116
|
+
~~~ruby
|
117
|
+
endpoint = ChatFeed.new('room42')
|
118
|
+
~~~
|
119
|
+
|
120
|
+
You can see the protocol and scope as an endpoint like `/ChatFeed/room42`.
|
121
|
+
|
122
|
+
### Sending events
|
123
|
+
|
124
|
+
From the previous example, you can publish a message to all the connected
|
125
|
+
clients with the following code:
|
126
|
+
|
127
|
+
~~~ruby
|
128
|
+
class Message < Struct.new(:author, :content)
|
129
|
+
# This is required to be send as an event by Gorsse.
|
130
|
+
# @return String
|
131
|
+
def to_sse
|
132
|
+
"#{author}|#{content}"
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
message = Message.new('Bob', 'Hello!')
|
137
|
+
endpoint.signal(message)
|
138
|
+
~~~
|
139
|
+
|
140
|
+
The previous code will generate the following lines in the SSE stream:
|
141
|
+
|
142
|
+
~~~
|
143
|
+
event: Message
|
144
|
+
data: Bob|Hello!
|
145
|
+
|
146
|
+
~~~
|
147
|
+
|
148
|
+
### Private messages
|
149
|
+
|
150
|
+
You can achieve private messages with scopes.
|
151
|
+
|
152
|
+
There is also a notion of Client that allows you to do something like this:
|
153
|
+
|
154
|
+
~~~ruby
|
155
|
+
client = Gorsse::Client.new('124df54b0')
|
156
|
+
message = Message.new('Alice', 'Goodbye...')
|
157
|
+
endpoint.signal(message, target: client)
|
158
|
+
~~~
|
159
|
+
|
160
|
+
*This section should be completed.*
|
161
|
+
|
162
|
+
The missing piece here is that we need something to pass the client identity
|
163
|
+
from the server to the client then to the connection handler...
|
164
|
+
|
165
|
+
## Installation
|
166
|
+
|
167
|
+
Add the classic line to your Gemfile:
|
168
|
+
|
169
|
+
~~~
|
170
|
+
gem 'gorsse'
|
171
|
+
~~~
|
172
|
+
|
173
|
+
Compile your own binaries for the connection handler (see the dedicated README
|
174
|
+
in the `gorsse_server` directory). You can also ask me for a binary version.
|
175
|
+
|
176
|
+
## Alternative
|
177
|
+
|
178
|
+
There are many other solutions out there that are aiming the same goal.
|
179
|
+
You can try to fix the server with Goliath or you can rely on external
|
180
|
+
components with Faye.
|
181
|
+
|
182
|
+
There is a good article about [SSE in Ruby][ruby-sse] with Goliath and
|
183
|
+
EventMachine. The implementation now relies on EM channels that are
|
184
|
+
something native in Go. The backend of Gorsse could have been written as
|
185
|
+
described here.
|
186
|
+
|
187
|
+
I don't think webservers like Puma are a viable alternative even if it
|
188
|
+
is using threads to handle requests. I'll be glad to have your feedback
|
189
|
+
on it.
|
190
|
+
|
191
|
+
Of course all of this project is tied to the Ruby world. You can have
|
192
|
+
it all for free with other platforms like Meteor...
|
193
|
+
|
194
|
+
## TODOs
|
195
|
+
|
196
|
+
* Documentation about proxying via nginx
|
197
|
+
* Tests both for the Go code and the Ruby code
|
198
|
+
* Benchmarking versus the alternatives
|
199
|
+
* Any other useful stuff that I didn't think of...
|
200
|
+
|
201
|
+
## Contributions
|
202
|
+
|
203
|
+
They're welcome! Just know that this is a toy project for me yet.
|
204
|
+
Even if I'll be glad to receive contributions and advices about it
|
205
|
+
I may not be able to provide a excellent level of support.
|
206
|
+
|
207
|
+
## Licence
|
208
|
+
|
209
|
+
This program is free software: you can redistribute it and/or modify
|
210
|
+
it under the terms of the GNU Lesser General Public License as
|
211
|
+
published by the Free Software Foundation, either version 3 of the
|
212
|
+
License, or (at your option) any later version.
|
213
|
+
|
214
|
+
This program is distributed in the hope that it will be useful,
|
215
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
216
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
217
|
+
GNU General Public License for more details.
|
218
|
+
|
219
|
+
[ruby-sse]: http://robots.thoughtbot.com/chat-example-app-using-server-sent-events
|
data/lib/gorsse.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
module Gorsse
|
2
|
+
{
|
3
|
+
:Client => 'client',
|
4
|
+
:Command => 'command',
|
5
|
+
:Config => 'config',
|
6
|
+
:Connection => 'connection',
|
7
|
+
:Event => 'event',
|
8
|
+
:Protocol => 'protocol',
|
9
|
+
:VERSION => 'version',
|
10
|
+
}.each { |mod, file| autoload mod, "gorsse/#{file}" }
|
11
|
+
|
12
|
+
def self.configure(&block)
|
13
|
+
block.call(config)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.config
|
17
|
+
@config ||= Config.new
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.close_connections
|
21
|
+
@conn && @conn.close
|
22
|
+
@receiver_conn && @receiver_conn.close
|
23
|
+
Connection::ZCTX.terminate
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.conn
|
27
|
+
@conn ||= Connection.new(config.handler, mode: :push, method: :connect)
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.receiver_conn
|
31
|
+
@receiver_conn ||= Connection.new(config.receiver, mode: :pull, method: :bind)
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.start_receiver_loop!
|
35
|
+
loop do
|
36
|
+
message = receiver_conn.receive
|
37
|
+
command = Command.new(message)
|
38
|
+
command.run!
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module Gorsse
|
4
|
+
class Command
|
5
|
+
def initialize(command)
|
6
|
+
@command = JSON.parse(command)
|
7
|
+
end
|
8
|
+
|
9
|
+
def run!
|
10
|
+
protocol_class = Object.const_get(@command['Protocol'])
|
11
|
+
protocol = protocol_class.new(@command['Scope'])
|
12
|
+
client = Client.new(@command['Client'])
|
13
|
+
protocol.__send__(@command['Callback'], client)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'ffi-rzmq'
|
2
|
+
|
3
|
+
module Gorsse
|
4
|
+
# This class hide the ZMQ library but it is closely tied to it.
|
5
|
+
class Connection
|
6
|
+
ZCTX = ZMQ::Context.new(1)
|
7
|
+
|
8
|
+
CONNECTION_MODE = {
|
9
|
+
rep: ZMQ::REP,
|
10
|
+
req: ZMQ::REQ,
|
11
|
+
pub: ZMQ::PUB,
|
12
|
+
sub: ZMQ::SUB,
|
13
|
+
pull: ZMQ::PULL,
|
14
|
+
push: ZMQ::PUSH,
|
15
|
+
}
|
16
|
+
|
17
|
+
# Open a connection to an URL. You can act as a server or as
|
18
|
+
# a client by tunning the 'server' parameter.
|
19
|
+
def initialize(url, mode: :push, method: :connect)
|
20
|
+
@zmq_socket = ZCTX.socket(CONNECTION_MODE[mode])
|
21
|
+
@zmq_socket.__send__(method, url)
|
22
|
+
puts '%s with %s to %s' % [method, mode, url]
|
23
|
+
end
|
24
|
+
|
25
|
+
def send(string, flags: 0)
|
26
|
+
puts 'Sending: %s' % string
|
27
|
+
@zmq_socket.send_string(string, flags)
|
28
|
+
end
|
29
|
+
|
30
|
+
def receive(flags: 0)
|
31
|
+
message = ''
|
32
|
+
@zmq_socket.recv_string(message, flags)
|
33
|
+
message
|
34
|
+
end
|
35
|
+
|
36
|
+
def close
|
37
|
+
@zmq_socket.close
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/lib/gorsse/event.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module Gorsse
|
4
|
+
class Event
|
5
|
+
def initialize(protocol, target, entity)
|
6
|
+
@protocol = protocol
|
7
|
+
@target = target
|
8
|
+
@entity = entity
|
9
|
+
end
|
10
|
+
|
11
|
+
def send!
|
12
|
+
json = JSON.generate(msg)
|
13
|
+
Gorsse.conn.send(json)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def msg
|
19
|
+
hash = {
|
20
|
+
'proto' => sse_class_for(@protocol),
|
21
|
+
'scope' => @protocol.scope,
|
22
|
+
'client' => @target.kind_of?(Client) ? @target.uid : 'all',
|
23
|
+
}
|
24
|
+
|
25
|
+
if @entity.respond_to?(:to_sse)
|
26
|
+
hash['title'] = sse_class_for(@entity)
|
27
|
+
hash['content'] = @entity.to_sse
|
28
|
+
else
|
29
|
+
hash['title'] = @entity.to_s
|
30
|
+
hash['content'] = ''
|
31
|
+
end
|
32
|
+
|
33
|
+
hash
|
34
|
+
end
|
35
|
+
|
36
|
+
def sse_class_for(instance)
|
37
|
+
klass = instance.class
|
38
|
+
klass.respond_to?(:sse_name) ? klass.sse_name : klass.name
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Gorsse
|
2
|
+
class Protocol
|
3
|
+
attr_reader :scope
|
4
|
+
|
5
|
+
def initialize(scope)
|
6
|
+
@scope = scope
|
7
|
+
end
|
8
|
+
|
9
|
+
# Send a message to the client through the Gorsse server.
|
10
|
+
def signal(entity, target: :all)
|
11
|
+
event = Event.new(self, target, entity)
|
12
|
+
event.send!
|
13
|
+
end
|
14
|
+
|
15
|
+
def after_connect(client_id)
|
16
|
+
# Do nothing. Override in subclasses.
|
17
|
+
end
|
18
|
+
|
19
|
+
def eql?(other)
|
20
|
+
self.class == other.class && self.scope == other.scope
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'test_helper.rb'
|
2
|
+
|
3
|
+
describe Gorsse::Event do
|
4
|
+
let(:protocol_class) { stub_const('Protocol', Class.new(Gorsse::Protocol)) }
|
5
|
+
let(:event) { Gorsse::Event.new(protocol, target, entity) }
|
6
|
+
|
7
|
+
describe '#send!' do
|
8
|
+
before { allow(Gorsse.conn).to receive(:send).and_return(nil) }
|
9
|
+
|
10
|
+
subject { event.send! }
|
11
|
+
|
12
|
+
let(:scope) { 'scope' }
|
13
|
+
let(:protocol) { protocol_class.new(scope) }
|
14
|
+
let(:target) { :all }
|
15
|
+
let(:entity) { 'no_data' }
|
16
|
+
|
17
|
+
it 'sends json event data though the Gorsse.conn connection' do
|
18
|
+
expected_json = %Q({"proto":"Protocol","scope":"scope","client":"all","title":"no_data","content":""})
|
19
|
+
expect(Gorsse.conn).to receive(:send).with(expected_json)
|
20
|
+
subject
|
21
|
+
end
|
22
|
+
|
23
|
+
context 'when the target is a client' do
|
24
|
+
let(:target) { Gorsse::Client.new('1234') }
|
25
|
+
|
26
|
+
it 'sends the client uid in the "client" fields' do
|
27
|
+
expected_json = %Q({"proto":"Protocol","scope":"scope","client":"1234","title":"no_data","content":""})
|
28
|
+
expect(Gorsse.conn).to receive(:send).with(expected_json)
|
29
|
+
subject
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context 'when the entity respond to the "to_sse" method' do
|
34
|
+
let(:entity) { double( class: double( name: 'Entity' ) ) }
|
35
|
+
|
36
|
+
it 'sends the entity class name as the "title" field' do
|
37
|
+
expected_json = %Q({"proto":"Protocol","scope":"scope","client":"all","title":"Entity","content":""})
|
38
|
+
allow(entity).to receive(:to_sse).and_return('')
|
39
|
+
expect(Gorsse.conn).to receive(:send).with(expected_json)
|
40
|
+
subject
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'sends the result of the "to_sse" call as the "content" field' do
|
44
|
+
expected_json = %Q({"proto":"Protocol","scope":"scope","client":"all","title":"Entity","content":"data"})
|
45
|
+
allow(entity).to receive(:to_sse).and_return('data')
|
46
|
+
expect(Gorsse.conn).to receive(:send).with(expected_json)
|
47
|
+
subject
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/spec/test_helper.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'gorsse'
|
2
|
+
|
3
|
+
Gorsse.configure do |config|
|
4
|
+
config.receiver = 'tcp://127.0.0.1:4567'
|
5
|
+
config.handler = 'tcp://127.0.0.1:4568'
|
6
|
+
end
|
7
|
+
|
8
|
+
class FakeConnection ; end
|
9
|
+
|
10
|
+
RSpec.configure do |config|
|
11
|
+
config.before(:example) do
|
12
|
+
allow(Gorsse).to receive(:conn).and_return(FakeConnection.new)
|
13
|
+
end
|
14
|
+
end
|
metadata
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: gorsse
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Nicolas ZERMATI
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-07-21 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: ffi-rzmq
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.6'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.6'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description:
|
70
|
+
email:
|
71
|
+
- nicoolas25@gmail.com
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- Gemfile
|
77
|
+
- LICENCE
|
78
|
+
- README.md
|
79
|
+
- lib/gorsse.rb
|
80
|
+
- lib/gorsse/client.rb
|
81
|
+
- lib/gorsse/command.rb
|
82
|
+
- lib/gorsse/config.rb
|
83
|
+
- lib/gorsse/connection.rb
|
84
|
+
- lib/gorsse/event.rb
|
85
|
+
- lib/gorsse/protocol.rb
|
86
|
+
- lib/gorsse/version.rb
|
87
|
+
- spec/gorsse/event_spec.rb
|
88
|
+
- spec/test_helper.rb
|
89
|
+
homepage: ''
|
90
|
+
licenses:
|
91
|
+
- LGPL
|
92
|
+
metadata: {}
|
93
|
+
post_install_message:
|
94
|
+
rdoc_options: []
|
95
|
+
require_paths:
|
96
|
+
- lib
|
97
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
98
|
+
requirements:
|
99
|
+
- - ">="
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
103
|
+
requirements:
|
104
|
+
- - ">="
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: '0'
|
107
|
+
requirements: []
|
108
|
+
rubyforge_project:
|
109
|
+
rubygems_version: 2.2.2
|
110
|
+
signing_key:
|
111
|
+
specification_version: 4
|
112
|
+
summary: Have nice and fast SSE capabilities with any Ruby webserver.
|
113
|
+
test_files:
|
114
|
+
- spec/gorsse/event_spec.rb
|
115
|
+
- spec/test_helper.rb
|