jebanni 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 4d07871c71c8e61694588786942ce74c259fc363
4
+ data.tar.gz: dd45b3a2e722a3693d7127fd37103e6858db88e3
5
+ SHA512:
6
+ metadata.gz: 6fe6a45abdcef3393740964022bb4650367f43bf0623d8e5817b3afddc81689ce1475a06d549b9dc858f196546f8c275195922a59db98146ba8989edeb7be350
7
+ data.tar.gz: 5e510d61a613b73391e062de9d10873c299cd8248c7dde97d7f0405793122b2a9593c5af4bd92e0f55ca8b1e3d18ffd3a74c75518536c06e2fd81a0bd8d3c523
data/.gitignore ADDED
@@ -0,0 +1,22 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in jebanni.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 uu59
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,52 @@
1
+ # Jebanni
2
+
3
+ Jebanni is a Sinatra-style Server-Sent Event kit its based on [Reel](https://github.com/celluloid/reel).
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'jebanni'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install jebanni
18
+
19
+ ## Usage
20
+
21
+ ```ruby
22
+ class App < Jebanni::Base
23
+ set :interval, 1
24
+ set :port, 63123
25
+
26
+ get "/:channel_id" do
27
+ on_first_connect do
28
+ every(settings[:interval]) do
29
+ broadcast(Time.now)
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ App.run
36
+ ```
37
+
38
+ See <./example/> directory for more examples.
39
+
40
+ ## Thanks
41
+
42
+ Jebanni is heavyly inspired by [Angelo](https://github.com/kenichi/angelo).
43
+
44
+ If you looking for WebSocket stream server, try it!
45
+
46
+ ## Contributing
47
+
48
+ 1. Fork it ( https://github.com/[my-github-username]/jebanni/fork )
49
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
50
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
51
+ 4. Push to the branch (`git push origin my-new-feature`)
52
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
data/example/basic.rb ADDED
@@ -0,0 +1,69 @@
1
+ require "bundler/setup"
2
+ require "jebanni"
3
+
4
+ =begin
5
+ $ bundle exec ruby ./example/basic.rb
6
+ I, [2014-06-22T17:56:32.467348 #27470] INFO -- : Listen on 0.0.0.0:63123
7
+
8
+
9
+ at other console
10
+ $ curl -vNH 'Accept: text/event-stream' -H 'Last-Event-ID: 1' -H 'Cache-Control: no-cache' 'http://127.0.0.1:63123/foo'
11
+ * Hostname was NOT found in DNS cache
12
+ * Trying 127.0.0.1...
13
+ * Connected to 127.0.0.1 (127.0.0.1) port 63123 (#0)
14
+ > GET /foo HTTP/1.1
15
+ > User-Agent: curl/7.37.0
16
+ > Host: 127.0.0.1:63123
17
+ > Accept: text/event-stream
18
+ > Last-Event-ID: 1
19
+ > Cache-Control: no-cache
20
+ >
21
+ < HTTP/1.1 200 OK
22
+ < Content-Type: text/event-stream; charset=utf-8
23
+ < Cache-Control: no-cache
24
+ < X-Accel-Buffering: no
25
+ < Access-Control-Allow-Origin: *
26
+ < Transfer-Encoding: identity
27
+ * no chunk, no close, no size. Assume close to signal end
28
+ <
29
+ retry: 5000
30
+ id
31
+
32
+ id: 1
33
+ data: "2014-06-22 17:56:40 +0900"
34
+
35
+ id: 2
36
+ data: "2014-06-22 17:56:41 +0900"
37
+
38
+ id: 3
39
+ data: "2014-06-22 17:56:42 +0900"
40
+
41
+ : ping
42
+ id: 4
43
+ data: "2014-06-22 17:56:43 +0900"
44
+
45
+ id: 5
46
+ data: "2014-06-22 17:56:44 +0900"
47
+
48
+ id: 6
49
+ data: "2014-06-22 17:56:45 +0900"
50
+
51
+ ^C
52
+
53
+
54
+ =end
55
+
56
+ class App < Jebanni::Base
57
+ set :interval, 1
58
+ set :port, 63123
59
+
60
+ get "/:channel_id" do
61
+ on_first_connect do
62
+ every(1) do
63
+ broadcast(Time.now)
64
+ end
65
+ end
66
+ end
67
+ end
68
+
69
+ App.run
@@ -0,0 +1,49 @@
1
+ require "bundler/setup"
2
+ require "jebanni"
3
+
4
+ =begin
5
+ $ bundle exec ruby ./example/broadcast_by_post.rb
6
+ I, [2014-06-22T18:01:24.997142 #28625] INFO -- : Listen on 0.0.0.0:63311
7
+
8
+ at console A:
9
+ A $ curl -vNH 'Accept: text/event-stream' -H 'Last-Event-ID: 1' -H 'Cache-Control: no-cache' 'http://127.0.0.1:63311/foo'
10
+ : ping
11
+ : ping
12
+
13
+ at console B:
14
+ B $ curl -d 'number=42' 'http://localhost:63311/foo'
15
+ B $ curl -d '{"json": "capable"}' -H 'Content-Type: application/json' 'http://localhost:63311/foo'
16
+ B $ curl -XPOST 'http://localhost:63311/foo/now'
17
+
18
+ at A:
19
+ : ping
20
+ : ping
21
+
22
+ id: 1
23
+ data: {"number":"42","channel_id":"foo"}
24
+
25
+ id: 2
26
+ data: {"json":"capable","channel_id":"foo"}
27
+
28
+ id: 3
29
+ data: "2014-06-22 18:04:38 +0900"
30
+
31
+
32
+ =end
33
+
34
+ class App < Jebanni::Base
35
+ get "/:channel_id" do
36
+ end
37
+
38
+ post "/:channel_id" do
39
+ broadcast(params)
40
+ finish! # finish! means this connection doesn't require streaming. normal HTTP request/response operation
41
+ end
42
+
43
+ post "/:channel_id/now" do
44
+ broadcast(Time.now)
45
+ finish!
46
+ end
47
+ end
48
+
49
+ App.run
data/jebanni.gemspec ADDED
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'jebanni/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "jebanni"
8
+ spec.version = Jebanni::VERSION
9
+ spec.authors = ["uu59"]
10
+ spec.email = ["k@uu59.org"]
11
+ spec.summary = %q{SSE Streaming server (not Rack)}
12
+ spec.description = %q{SSE Streaming server (not Rack)}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "reel"
22
+ spec.add_dependency "mustermann"
23
+ spec.add_dependency "rack" # for Rack::Utils#parse_nested_query
24
+ spec.add_development_dependency "bundler", "~> 1.6"
25
+ spec.add_development_dependency "rake"
26
+ spec.add_development_dependency "pry"
27
+ end
data/lib/jebanni.rb ADDED
@@ -0,0 +1,7 @@
1
+ require "jebanni/version"
2
+ require "jebanni/base"
3
+ require "jebanni/server"
4
+
5
+ module Jebanni
6
+ # Your code goes here...
7
+ end
@@ -0,0 +1,24 @@
1
+ require "jebanni/dsl"
2
+ require "jebanni/server"
3
+
4
+ module Jebanni
5
+ class Base
6
+ include ::Jebanni::DSL
7
+
8
+ def self.run
9
+ new.run
10
+ end
11
+
12
+ def run
13
+ bind = settings[:bind] || "0.0.0.0"
14
+ port = settings[:port] || 63311
15
+ @server = Server.new(bind, port)
16
+ trap "INT" do
17
+ @server.terminate if @server and @server.alive?
18
+ exit
19
+ end
20
+ @server.send(:info, "Listen on #{bind}:#{port}")
21
+ sleep
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,38 @@
1
+ module Jebanni
2
+ class Channel
3
+ attr_reader :id, :history, :connections, :server
4
+
5
+ def initialize(id, server)
6
+ @id = id
7
+ @history = []
8
+ @last_event_id = 0
9
+ @server = server
10
+ @connections = []
11
+ end
12
+
13
+ def join(connection)
14
+ @connections << connection
15
+ end
16
+
17
+ def leave(connection)
18
+ @connections.delete connection
19
+ end
20
+
21
+ def refrain(since)
22
+ @history.each do |history|
23
+ next if history[:id] <= since
24
+ server.broadcast_to(self, history[:data], history[:event], history[:id])
25
+ end
26
+ end
27
+
28
+ def broadcast(data, event = nil)
29
+ @last_event_id += 1
30
+ @history << {id: @last_event_id, event: event, data: data}
31
+ #only keep the last 5000 Events
32
+ if @history.size >= 6000
33
+ @history.slice!(0, @history.size - 1000)
34
+ end
35
+ server.broadcast_to(self, data, event, @last_event_id)
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,35 @@
1
+ require "jebanni/request_handler"
2
+ require "jebanni/settings"
3
+
4
+ module Jebanni
5
+ module DSL
6
+ extend Forwardable
7
+ def self.included(base)
8
+ base.extend ClassMethods
9
+ base.include Mixin
10
+ end
11
+
12
+ module Mixin
13
+ def set(key, value)
14
+ ::Jebanni::Settings[key] = value
15
+ end
16
+
17
+ def settings
18
+ ::Jebanni::Settings.to_hash
19
+ end
20
+ end
21
+
22
+ module ClassMethods
23
+ include Mixin
24
+ def register_route(verb, path, options = {}, &process)
25
+ RequestHandler.register_route(verb, path, options, &process)
26
+ end
27
+
28
+ %w(get post put patch delete head options link unlink).each do |verb|
29
+ define_method(verb) do |path, options = {}, &process|
30
+ register_route(verb, path, options, &process)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,88 @@
1
+ require "mustermann"
2
+ require "rack/utils"
3
+ require "jebanni/request_handler/processor"
4
+
5
+ module Jebanni
6
+ class RequestHandler
7
+ include Rack::Utils # for #parse_nested_query
8
+
9
+ attr_reader :request, :server
10
+
11
+ def self.register_route(verb, path, options = {}, &process)
12
+ verb.downcase!
13
+ @routes ||= {}
14
+ @routes[verb] ||= []
15
+ @routes[verb] << {
16
+ matcher: Mustermann.new(path, options),
17
+ process: process
18
+ }
19
+ end
20
+
21
+ def initialize(request, server)
22
+ @request = request
23
+ @server = server
24
+ end
25
+
26
+ def route!
27
+ processor.response
28
+ end
29
+
30
+ def find_route
31
+ known = self.class.instance_variable_get(:@routes)
32
+ found = known[request.method.downcase].find do |route|
33
+ route[:matcher] === request.path
34
+ end
35
+ raise "not found" unless found
36
+ found
37
+ end
38
+
39
+ def processor
40
+ @processor ||=
41
+ begin
42
+ # Processor define class method internally, so avoid race condition for Class.new(Processor)
43
+ processor = Class.new(Processor).new(self)
44
+ processor.route = find_route
45
+ processor.params = params
46
+ processor
47
+ end
48
+ end
49
+
50
+ def request_params
51
+ query = parse_nested_query(request.query_string)
52
+ return query if %w(get head options).include?(request.method.downcase)
53
+
54
+ content_type = request.headers.find do |(key, _)|
55
+ key.downcase == "content-type"
56
+ end
57
+ if content_type && content_type.last.downcase == "application/json"
58
+ json_params = JSON.parse(request.body.to_s) rescue {}
59
+ query.merge! json_params
60
+ else
61
+ query.merge! parse_nested_query(request.body.to_s)
62
+ end
63
+ query
64
+ end
65
+
66
+ def route_params
67
+ find_route[:matcher].params(request.path)
68
+ end
69
+
70
+ def params
71
+ # NOTE: route params is prior than query params for don't accidentally override params with query
72
+ indiferrent_keys request_params.merge(route_params)
73
+ end
74
+
75
+ def indiferrent_keys(hash)
76
+ # Either accessible params[:foo] or params["foo"]
77
+ def hash.[](key)
78
+ super(key.to_s) || begin
79
+ # NOTE: Guard from DoS attack using Symbol:
80
+ # http://sequel.jeremyevans.net/2011/07/16/dangerous-reflection.html
81
+ super(key.to_sym) if self.keys.map(&:to_s).include?(key.to_s)
82
+ end
83
+ end
84
+ hash
85
+ end
86
+
87
+ end
88
+ end
@@ -0,0 +1,60 @@
1
+ require "jebanni/settings"
2
+ require "jebanni/channel"
3
+
4
+ module Jebanni
5
+ class RequestHandler
6
+ class Processor
7
+ extend Forwardable
8
+ def_delegators :@request_handler, :request, :server
9
+ def_delegators :server, :async, :after, :every
10
+ def_delegators :channel, :broadcast
11
+
12
+ attr_accessor :params
13
+
14
+ def initialize(request_handler)
15
+ @request_handler = request_handler
16
+ end
17
+
18
+ def route=(found_route)
19
+ @route = found_route
20
+ self.class.send(:define_method, :process, @route[:process])
21
+ end
22
+
23
+ def channel_id(id)
24
+ @channel_id = id
25
+ unless server.channels[@channel_id]
26
+ server.channels[@channel_id] = Channel.new(@channel_id, server)
27
+ end
28
+ end
29
+
30
+ def channel
31
+ return unless @channel_id
32
+ server.channels[@channel_id]
33
+ end
34
+
35
+ def on_first_connect(&block)
36
+ if channel.connections.length == 0
37
+ instance_eval(&block)
38
+ end
39
+ end
40
+
41
+ def finish!
42
+ @finish = true
43
+ end
44
+
45
+ def finished?
46
+ @finish
47
+ end
48
+
49
+ def response
50
+ channel_id params[:channel_id] if params[:channel_id]
51
+ process
52
+ self
53
+ end
54
+
55
+ def settings
56
+ Settings.to_hash
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,109 @@
1
+ require 'time'
2
+ require 'reel'
3
+
4
+ # Based on Reel's sample
5
+ # https://github.com/celluloid/reel/blob/master/examples/server_sent_events.rb
6
+
7
+ module Jebanni
8
+ class Server < Reel::Server::HTTP
9
+ include Celluloid::Logger
10
+
11
+ def initialize(ip = '127.0.0.1', port = 63310)
12
+ @channels = {}
13
+ super(ip, port, &method(:on_connection))
14
+ async.ping
15
+ end
16
+
17
+ def broadcast_to(channel, data, event, id)
18
+ channel.connections.each do |socket|
19
+ async.send_sse(socket, data, event, id)
20
+ end
21
+ true
22
+ end
23
+
24
+ def channels
25
+ @channels
26
+ end
27
+
28
+ def all_connections
29
+ @channels.map{|(_, channel)| channel.connections}.flatten
30
+ end
31
+
32
+ private
33
+ #event and id are optional, Eventsource only needs data
34
+ def send_sse(socket, data, event = nil, id = nil)
35
+ unless data.is_a? String
36
+ data = JSON.dump(data)
37
+ end
38
+ socket.id id if id
39
+ socket.event event if event
40
+ socket.data data
41
+ rescue Reel::SocketError
42
+ leave_channel(socket)
43
+ end
44
+
45
+ def leave_channel(socket)
46
+ @channels.each do |id, ch|
47
+ next unless ch.connections.include?(socket)
48
+ debug("Disconnect from channel:#{id}")
49
+ ch.leave(socket)
50
+ end
51
+ end
52
+
53
+ #Lines that start with a Colon are Comments and will be ignored
54
+ def send_ping
55
+ all_connections.each do |socket|
56
+ begin
57
+ socket << ": ping\n"
58
+ rescue Reel::SocketError
59
+ leave_channel(socket)
60
+ end
61
+ end
62
+ end
63
+
64
+ #apache 2.2 closes connections after five seconds when nothing is send, see this as a poor mans Keep-Alive
65
+ def ping
66
+ every(5) do
67
+ send_ping
68
+ end
69
+ end
70
+
71
+ def handle_request(request)
72
+ response = RequestHandler.new(request, self).route!
73
+ return request.respond 204 if response.finished?
74
+
75
+ request.respond Reel::StreamResponse.new(:ok, response_headers, event_stream(request, response))
76
+ end
77
+
78
+ def response_headers
79
+ #X-Accel-Buffering is nginx(?) specific. Setting this to "no" will allow unbuffered responses suitable for Comet and HTTP streaming applications
80
+ {
81
+ 'Content-Type' => 'text/event-stream; charset=utf-8',
82
+ 'Cache-Control' => 'no-cache',
83
+ 'X-Accel-Buffering' => 'no',
84
+ 'Access-Control-Allow-Origin' => '*',
85
+ }
86
+ end
87
+
88
+ def event_stream(request, response)
89
+ Reel::EventStream.new do |socket|
90
+ channel = response.channel
91
+ channel.join socket
92
+ socket.retry 5000
93
+ #after a Connection reset resend newer Messages to the Client, query['lastEventId'] is needed for https://github.com/Yaffle/EventSource
94
+ query = Hash[URI.decode_www_form(request.query_string || "")]
95
+ id = (request.headers['Last-Event-ID'] || query['lastEventId'])
96
+ socket << "id\n\n"
97
+ if id && id.to_i > 0
98
+ channel.refrain(id.to_i)
99
+ end
100
+ end
101
+ end
102
+
103
+ def on_connection(connection)
104
+ connection.each_request do |request|
105
+ handle_request(request)
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,19 @@
1
+ module Jebanni
2
+ module Settings
3
+ class << self
4
+ def []=(key, value)
5
+ @settings ||= {}
6
+ @settings[key] = value
7
+ end
8
+
9
+ def [](key)
10
+ @settings ||= {}
11
+ @settings[key]
12
+ end
13
+
14
+ def to_hash
15
+ @settings || {}
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,3 @@
1
+ module Jebanni
2
+ VERSION = "0.0.1"
3
+ end
metadata ADDED
@@ -0,0 +1,145 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jebanni
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - uu59
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-06-22 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: reel
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: mustermann
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rack
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
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: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.6'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.6'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: pry
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: SSE Streaming server (not Rack)
98
+ email:
99
+ - k@uu59.org
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - ".gitignore"
105
+ - Gemfile
106
+ - LICENSE.txt
107
+ - README.md
108
+ - Rakefile
109
+ - example/basic.rb
110
+ - example/broadcast_by_post.rb
111
+ - jebanni.gemspec
112
+ - lib/jebanni.rb
113
+ - lib/jebanni/base.rb
114
+ - lib/jebanni/channel.rb
115
+ - lib/jebanni/dsl.rb
116
+ - lib/jebanni/request_handler.rb
117
+ - lib/jebanni/request_handler/processor.rb
118
+ - lib/jebanni/server.rb
119
+ - lib/jebanni/settings.rb
120
+ - lib/jebanni/version.rb
121
+ homepage: ''
122
+ licenses:
123
+ - MIT
124
+ metadata: {}
125
+ post_install_message:
126
+ rdoc_options: []
127
+ require_paths:
128
+ - lib
129
+ required_ruby_version: !ruby/object:Gem::Requirement
130
+ requirements:
131
+ - - ">="
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ required_rubygems_version: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ requirements: []
140
+ rubyforge_project:
141
+ rubygems_version: 2.2.2
142
+ signing_key:
143
+ specification_version: 4
144
+ summary: SSE Streaming server (not Rack)
145
+ test_files: []