noder 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -0,0 +1,327 @@
1
+ Noder
2
+ =====
3
+ Node.js for Ruby
4
+
5
+ Overview
6
+ --------
7
+
8
+ Noder brings the architecture of Node.js to Ruby. It focuses on the implementation of Node.js's HTTP-related support, as Ruby's standard library and other gems already provide great analogs of many of Node.js's other core modules.
9
+
10
+ You may also be interested in [Expressr](https://github.com/tombenner/expressr) (Express.js for Ruby), which Noder was built to support, and [EM-Synchrony](https://github.com/igrigorik/em-synchrony). Noder runs on [EventMachine](https://github.com/eventmachine/eventmachine).
11
+
12
+ Example
13
+ -------
14
+
15
+ A web server can be created and started using the following script:
16
+
17
+ ```ruby
18
+ require 'noder'
19
+
20
+ server = Noder::HTTP::Server.new do |request, response|
21
+ response.write_head(200, { 'Content-Type' => 'text/plain' })
22
+ response.end('Hello world!')
23
+ end
24
+ server.listen(1337, '127.0.0.1')
25
+ ```
26
+
27
+ To start the app, put the code into a file named `my_server.rb` and run it:
28
+
29
+ ```bash
30
+ $ ruby my_server.rb
31
+ Running Noder at 127.0.0.1:1337...
32
+ ```
33
+
34
+ HTTP
35
+ ----
36
+
37
+ ### Noder::HTTP::Server
38
+
39
+ `Noder::HTTP::Server` lets you create and run HTTP servers.
40
+
41
+ #### .new(options={}, &block)
42
+
43
+ Creates the server.
44
+
45
+ ##### options
46
+
47
+ * `:address` - The server's address (default: `'0.0.0.0'`)
48
+ * `:port` - The server's port (default: `8000`)
49
+ * `:environment` - The server's environment name (default: `'development'`)
50
+ * `:threadpool_size` - The size of the server's threadpool default: `20`)
51
+ * `:enable_ssl` - A boolean of whether SSL is enabled (default: `false`)
52
+ * `:ssl_key` - A filepath to the SSL key (default: `nil`)
53
+ * `:ssl_cert` - A filepath to the SSL cert (default: `nil`)
54
+
55
+ ##### &block
56
+
57
+ A block that will be called for every request. It will be passed the request (a Noder::HTTP::Request) and response (a Noder::HTTP::Response) as arguments.
58
+
59
+ #### #listen(port=nil, address=nil, options={}, &block)
60
+
61
+ Starts accepting connections to the server. `options` are the same as the options in `.new`, and `&block` behaves the same as in `.new`.
62
+
63
+ ```ruby
64
+ server = Noder::HTTP::Server.new
65
+ server.listen(8001) do |request, response|
66
+ response.write("Hello world!")
67
+ response.end
68
+ end
69
+ ```
70
+
71
+ #### #close
72
+
73
+ Stops the server. This is called when an `INT` or `TERM` signal is sent to a running server's process (e.g. when `Control-C` is pressed).
74
+
75
+ #### Event 'request'
76
+
77
+ Emitted for every request. The request and response are passed as arguments.
78
+
79
+ ```ruby
80
+ server.on('request') do |request, response|
81
+ Noder.logger.info "Request params: #{request.params}"
82
+ response.set_header('MyHeader', 'My value')
83
+ end
84
+ ```
85
+
86
+ #### Event 'close'
87
+
88
+ Emitted when the server is closing. No arguments are passed.
89
+
90
+ ```ruby
91
+ server.on('close') do
92
+ Noder.logger.info "Stopping server..."
93
+ end
94
+ ```
95
+
96
+ ### Noder::HTTP::Request
97
+
98
+ A representation of an HTTP request.
99
+
100
+ #### #params
101
+
102
+ A hash of the request's params (the query string and POST data). The hash's keys are strings (e.g. `/?foo=bar` yields `{ 'foo' => 'bar' }`)
103
+
104
+ #### #headers
105
+
106
+ A hash of the request's headers (e.g. `{ 'Accept' => '*/*', 'User-Agent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_5) [...]' }`).
107
+
108
+ #### #request_method
109
+
110
+ The request's verb (e.g. `'GET'`, `'POST'`, etc).
111
+
112
+ #### #cookie
113
+
114
+ The request's Cookie header (e.g. `'my_cookie=123; my_other_cookie=foo'`).
115
+
116
+ #### #content_type
117
+
118
+ The request's Content-Type header (e.g. `'application/x-www-form-urlencoded'`).
119
+
120
+ #### #request_uri
121
+
122
+ The request's path, without the query string (e.g. `'/users/1/profile'`).
123
+
124
+ #### #query_string
125
+
126
+ The request's query string (e.g. `/about?foo=bar&baz=1` yields `'foo=bar&baz=1'`).
127
+
128
+ #### #protocol
129
+
130
+ The request's protocol (e.g. `'HTTP/1.1'`).
131
+
132
+ #### #ip
133
+
134
+ The client's IP address (e.g. `'68.1.8.45'`).
135
+
136
+ ### Noder::HTTP::Response
137
+
138
+ A representation of an HTTP response.
139
+
140
+ #### #write(content)
141
+
142
+ Writes the content to the response's body.
143
+
144
+ ```ruby
145
+ response.write('Hello world!')
146
+ ```
147
+
148
+ #### #write_head(status, headers={})
149
+
150
+ Sets the response's status code and sets the specified headers (if any).
151
+
152
+ ```ruby
153
+ response.write_head(500, { 'MyHeader' => 'My value' })
154
+ ```
155
+
156
+ #### #status_code
157
+
158
+ Gets or sets the response's status code
159
+
160
+ ```ruby
161
+ response.status_code # 200
162
+ response.status_code = 500
163
+ ```
164
+
165
+ #### #set_header(name, value)
166
+
167
+ Sets the specified header.
168
+
169
+ ```ruby
170
+ response.set_header('MyHeader', 'My value')
171
+ ```
172
+
173
+ #### #get_header(name, value)
174
+
175
+ Gets the specified header.
176
+
177
+ ```ruby
178
+ response.get_header('MyHeader') # 'My value'
179
+ ```
180
+
181
+ #### #remove_header(name)
182
+
183
+ Gets the specified header.
184
+
185
+ ```ruby
186
+ response.remove_header('MyHeader')
187
+ ```
188
+
189
+ #### #end(content=nil)
190
+
191
+ Sends the response. This must be called on every response instance.
192
+
193
+ If `content` is provided, it is equivalent to calling `write(content)` followed by `end`.
194
+
195
+ Events
196
+ ------
197
+
198
+ ### Noder::Events::EventEmitter
199
+
200
+ Include `EventEmitter` in classes which should manage events. For example:
201
+
202
+ ```ruby
203
+ class MyServer
204
+ include Noder::Events::EventEmitter
205
+
206
+ def initialize(&block)
207
+ on('start', &block)
208
+ on('stop', proc { puts 'Stopping...' })
209
+ end
210
+
211
+ def run
212
+ emit('start')
213
+ emit('stop')
214
+ end
215
+ end
216
+
217
+ server = MyServer.new do
218
+ puts 'Starting up...'
219
+ end
220
+ server.on('start') do
221
+ puts 'Still starting up...'
222
+ end
223
+
224
+ server.run
225
+ # Starting up...
226
+ # Still starting up...
227
+ # Stopping...
228
+ ```
229
+
230
+ #### #on(event, callback=nil, options={}, &block)
231
+
232
+ Adds a listener to the specified event. The listener can either be an instance of a Proc (as the `callback` argument) or a block.
233
+
234
+ `add_listener` is an alias of `on`.
235
+
236
+ #### #emit(event)
237
+
238
+ Call the listeners for the specified event.
239
+
240
+ #### #remove_listener(event, listener)
241
+
242
+ Remove the listener. Listeners are compared using the `==` operator.
243
+
244
+ ```ruby
245
+ listener = proc { puts 'Working...' }
246
+ server.on('start', listener)
247
+ server.remove_listener('start', listener)
248
+ ```
249
+
250
+ #### #remove_all_listeners(event)
251
+
252
+ Removes all of the listeners from the event. You probably don't want to call this on core Noder events.
253
+
254
+ ```ruby
255
+ server.remove_all_listeners('start')
256
+ ```
257
+
258
+ #### #set_max_listeners(event, count)
259
+
260
+ Sets the maximum number of listeners for the specified event. A warning will be logged every time any additional listeners are added.
261
+
262
+ ```ruby
263
+ server.set_max_listeners('start', 100)
264
+ ```
265
+
266
+ #### #listeners(event)
267
+
268
+ Returns an array of the listeners for the specified event.
269
+
270
+ ```ruby
271
+ server.listeners('start')
272
+ ```
273
+
274
+ #### #listener_count(event)
275
+
276
+ Returns the number of listeners for the specified event.
277
+
278
+ ```ruby
279
+ server.listener_count('start')
280
+ ```
281
+
282
+ Logging
283
+ -------
284
+
285
+ Noder's `Logger` is available at `Noder.logger`. You can write to it:
286
+
287
+ ```ruby
288
+ Noder.logger.debug 'My debug message...'
289
+ Noder.logger.error 'My error message...'
290
+ ```
291
+
292
+ You can modify it or replace it if you like, too:
293
+
294
+ ```ruby
295
+ # Adjust attributes of the logger
296
+ Noder.logger.level = Logger::DEBUG
297
+
298
+ # Or create a custom Logger
299
+ Noder.logger = Logger.new(STDOUT)
300
+ Noder.logger.level = Logger::DEBUG
301
+ ```
302
+
303
+ See the [Logger docs](http://www.ruby-doc.org/stdlib-2.0/libdoc/logger/rdoc/Logger.html) for more.
304
+
305
+ HTTPS
306
+ -----
307
+
308
+ To support HTTPS, set `:enable_ssl` to `true` and set the `:ssl_key` and `:ssl_cert` values to the appropriate file paths:
309
+
310
+ ```ruby
311
+ options = {
312
+ enable_ssl: true,
313
+ ssl_key: File.expand_path('../certs/key.pem', __FILE__),
314
+ ssl_cert: File.expand_path('../certs/cert.pem', __FILE__)
315
+ }
316
+ server = Noder::HTTP::Server.new(options)
317
+ ```
318
+
319
+ Notes
320
+ -----
321
+
322
+ Noder is not currently a full implementation of Node.js, and some of its underlying architecture differs from Node.js's. If you see any places where it could be improved or added to, absolutely feel free to submit a PR.
323
+
324
+ License
325
+ -------
326
+
327
+ Noder is released under the MIT License. Please see the MIT-LICENSE file for details.
@@ -1,4 +1,21 @@
1
- require "#{directory}/noder/version.rb"
1
+ require 'eventmachine'
2
+ require 'logger'
3
+
4
+ directory = File.dirname(File.absolute_path(__FILE__))
5
+ Dir.glob("#{directory}/noder/*.rb") { |file| require file }
2
6
 
3
7
  module Noder
8
+ class << self
9
+ def logger
10
+ @logger ||= Logger.new(STDOUT)
11
+ end
12
+
13
+ def logger=(logger)
14
+ @logger = logger
15
+ end
16
+
17
+ def with(operation, callback=nil, &block)
18
+ EM.defer(operation, callback || block)
19
+ end
20
+ end
4
21
  end
@@ -0,0 +1,7 @@
1
+ directory = File.dirname(File.absolute_path(__FILE__))
2
+ Dir.glob("#{directory}/events/**/*.rb") { |file| require file }
3
+
4
+ module Noder
5
+ module Events
6
+ end
7
+ end
@@ -0,0 +1,55 @@
1
+ module Noder
2
+ module Events
3
+ class EMEventNode
4
+ attr_accessor :callback, :next_node
5
+
6
+ def initialize(options={})
7
+ @callback = options[:callback]
8
+ @argument_keys = options[:argument_keys]
9
+ raise 'No callback provided' if @callback.nil?
10
+ end
11
+
12
+ def call(env)
13
+ if callback.respond_to?(:matches_env?) && !callback.matches_env?(env)
14
+ if next_node
15
+ operation = proc { next_node.call(env) }
16
+ EM.defer(operation)
17
+ end
18
+ return
19
+ end
20
+ operation = proc { call_operation(env) }
21
+ if next_node
22
+ callback = proc { |env| next_node.call(env) }
23
+ EM.defer(operation, callback)
24
+ else
25
+ EM.defer(operation)
26
+ end
27
+ env
28
+ end
29
+
30
+ protected
31
+
32
+ def call_operation(env)
33
+ @env = env
34
+ if @argument_keys
35
+ arguments = Utils.slice_hash(@env, @argument_keys).values
36
+ else
37
+ arguments = [@env]
38
+ end
39
+ perform_callback(arguments)
40
+ @env
41
+ end
42
+
43
+ def perform_callback(arguments)
44
+ continue_method = proc { EM.signal_loopbreak }
45
+ if @callback.is_a?(Proc)
46
+ @callback.call(*arguments, continue_method)
47
+ elsif @callback.is_a?(Class)
48
+ @env = @callback.new(continue_method).call(*arguments)
49
+ else
50
+ @env = @callback.call(*arguments, continue_method)
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,78 @@
1
+ module Noder
2
+ module Events
3
+ module EventEmitter
4
+ def event_stacks
5
+ @event_stacks ||= {}
6
+ end
7
+
8
+ def max_listener_counts
9
+ @max_listener_counts ||= {}
10
+ end
11
+
12
+ def on(event, callback=nil, options={}, &block)
13
+ max_count = max_listener_counts[event]
14
+ current_count = listener_count(event)
15
+ if max_count && current_count >= max_count
16
+ Noder.logger.warn "Maximum listener count exceeded for #{self.class} (max count is #{max_count}; current count is #{current_count})."
17
+ end
18
+ callback ||= block
19
+ options[:callback] = callback
20
+ event_stacks[event] ||= EventStack.new(node_class: node_class_for_event(event))
21
+ event_stacks[event].push(options)
22
+ end
23
+
24
+ def emit(event, *arguments)
25
+ return if event_stacks[event].nil?
26
+ event_stacks[event].call(*arguments)
27
+ end
28
+
29
+ def remove_listener(event, listener)
30
+ event_stacks[event].remove(listener)
31
+ end
32
+
33
+ def remove_all_listeners(event)
34
+ event_stacks[event].remove_all
35
+ end
36
+
37
+ def set_max_listeners(event, count)
38
+ max_listener_counts[event] = count
39
+ end
40
+
41
+ def listeners(event)
42
+ if event_stacks[event]
43
+ event_stacks[event].items.map { |item| item[:callback] }
44
+ else
45
+ []
46
+ end
47
+ end
48
+
49
+ def listener_count(event)
50
+ if event_stacks[event]
51
+ event_stacks[event].length
52
+ else
53
+ 0
54
+ end
55
+ end
56
+
57
+ def event_stack(event)
58
+ event_stacks[event]
59
+ end
60
+
61
+ alias_method :add_listener, :on
62
+
63
+ protected
64
+
65
+ def set_node_class_for_event(klass, event)
66
+ event_node_classes[event] = klass
67
+ end
68
+
69
+ def event_node_classes
70
+ @event_node_classes ||= {}
71
+ end
72
+
73
+ def node_class_for_event(event)
74
+ event_node_classes[event] || EMEventNode
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,43 @@
1
+ module Noder
2
+ module Events
3
+ class EventNode
4
+ attr_accessor :callback, :next_node
5
+
6
+ def initialize(options={})
7
+ @callback = options[:callback]
8
+ @argument_keys = options[:argument_keys]
9
+ @has_continued = false
10
+ raise 'No callback provided' if @callback.nil?
11
+ end
12
+
13
+ def call(env)
14
+ @env = env
15
+ if @argument_keys
16
+ arguments = Utils.slice_hash(@env, @argument_keys).values
17
+ else
18
+ arguments = [@env]
19
+ end
20
+ perform_callback(arguments)
21
+ continue unless @has_continued
22
+ end
23
+
24
+ def continue(env=nil)
25
+ @has_continued = true
26
+ next_node.call(env || @env) if next_node
27
+ end
28
+
29
+ protected
30
+
31
+ def perform_callback(arguments)
32
+ continue_method = method(:continue)
33
+ if @callback.is_a?(Proc)
34
+ @callback.call(*arguments, continue_method)
35
+ elsif @callback.is_a?(Class)
36
+ @env = @callback.new(continue_method).call(*arguments)
37
+ else
38
+ @env = @callback.call(*arguments, continue_method)
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,63 @@
1
+ module Noder
2
+ module Events
3
+ class EventStack
4
+ extend Forwardable
5
+
6
+ attr_reader :items
7
+
8
+ def_delegators :@items, :length
9
+
10
+ def initialize(options={})
11
+ @items = []
12
+ @node_class = options[:node_class] || EMEventNode
13
+ end
14
+
15
+ def push(options={})
16
+ @items << options
17
+ end
18
+
19
+ def insert_before(target_callback, item)
20
+ index = index_of_callback(target_callback)
21
+ raise "Item not found for callback: #{target_callback}" if index.nil?
22
+ @items.insert(index, item)
23
+ end
24
+
25
+ def replace(target_callback, item)
26
+ index = index_of_callback(target_callback)
27
+ raise "Item not found for callback: #{target_callback}" if index.nil?
28
+ @items[index] = item
29
+ end
30
+
31
+ def remove(target_callback)
32
+ index = index_of_callback(target_callback)
33
+ @items.delete_at(index) if index
34
+ end
35
+
36
+ def remove_all
37
+ @items = []
38
+ end
39
+
40
+ def index_of_callback(callback)
41
+ @items.index { |item| item[:callback] == callback }
42
+ end
43
+
44
+ def call(env=nil)
45
+ empty_node = @node_class.new({ callback: proc { |env| env } })
46
+ nodes = @items.map { |item| @node_class.new(item) }
47
+ first_node = nodes.reverse.inject(empty_node) do |next_node, current_node|
48
+ current_node.next_node = next_node
49
+ current_node
50
+ end
51
+ first_node.call(env)
52
+ end
53
+
54
+ protected
55
+
56
+ def does_item_match?(item, env)
57
+ callback = item[:callback]
58
+ return true unless callback.respond_to?(:matches_env?)
59
+ callback.matches_env?(env)
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,13 @@
1
+ module Noder
2
+ module Events
3
+ module Listeners
4
+ class Base
5
+ attr_accessor :callback
6
+
7
+ def initialize(callback)
8
+ @callback = callback
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,7 @@
1
+ directory = File.dirname(File.absolute_path(__FILE__))
2
+ Dir.glob("#{directory}/http/**/*.rb") { |file| require file }
3
+
4
+ module Noder
5
+ module HTTP
6
+ end
7
+ end
@@ -0,0 +1,52 @@
1
+ require 'evma_httpserver'
2
+ require 'socket'
3
+
4
+ module Noder
5
+ module HTTP
6
+ class Connection < EM::Connection
7
+ include EventMachine::HttpServer
8
+
9
+ attr_accessor :app, :environment, :request_stack, :settings
10
+
11
+ def initialize(*args)
12
+ super(*args)
13
+ @settings = args[1]
14
+ end
15
+
16
+ def post_init
17
+ super
18
+ if settings[:enable_ssl]
19
+ start_tls(:private_key_file => settings[:ssl_key], :cert_chain_file => settings[:ssl_cert], :verify_peer => false)
20
+ end
21
+ end
22
+
23
+ def process_http_request
24
+ env = {
25
+ connection: self,
26
+ request_env: request_env,
27
+ request: nil,
28
+ response: nil
29
+ }
30
+ EM.defer do
31
+ request_stack.call(env)
32
+ end
33
+ end
34
+
35
+ def request_env
36
+ port, ip = Socket.unpack_sockaddr_in(get_peername)
37
+ {
38
+ request_method: @http_request_method,
39
+ cookie: @http_cookie,
40
+ content_type: @http_content_type,
41
+ path_info: @http_path_info,
42
+ request_uri: @http_request_uri,
43
+ query_string: @http_query_string,
44
+ post_content: @http_post_content,
45
+ headers: @http_headers,
46
+ protocol: @http_protocol,
47
+ ip: ip
48
+ }
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,20 @@
1
+ module Noder
2
+ module HTTP
3
+ module Listeners
4
+ class NotFound < Events::Listeners::Base
5
+ def call(env)
6
+ callback.call(env) if callback
7
+ response = env[:response]
8
+ render_not_found(response) unless response.is_rendered?
9
+ env
10
+ end
11
+
12
+ def render_not_found(response)
13
+ response.status_code = 404
14
+ response.write('Not Found')
15
+ response.end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,13 @@
1
+ module Noder
2
+ module HTTP
3
+ module Listeners
4
+ class Request < Events::Listeners::Base
5
+ def call(env)
6
+ env[:request] ||= Noder::HTTP::Request.new(env[:request_env])
7
+ callback.call(env) if callback
8
+ env
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ module Noder
2
+ module HTTP
3
+ module Listeners
4
+ class Response < Events::Listeners::Base
5
+ def call(env)
6
+ env[:response] ||= Noder::HTTP::Response.new(env)
7
+ callback.call(env) if callback
8
+ env
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,53 @@
1
+ module Noder
2
+ module HTTP
3
+ class Request
4
+ attr_accessor :params
5
+ attr_reader :env
6
+
7
+ def initialize(env)
8
+ @env = env
9
+ @query = HTTP::Utils.parse(env[:query_string])
10
+ @params = @query
11
+ if env[:post_content] && env[:post_content] != ''
12
+ @params.merge!(HTTP::Utils.parse(env[:post_content]))
13
+ end
14
+ end
15
+
16
+ def headers
17
+ @headers ||= HTTP::Utils.parse_headers(env[:headers])
18
+ end
19
+
20
+ def request_method
21
+ env[:request_method]
22
+ end
23
+
24
+ def cookie
25
+ env[:cookie]
26
+ end
27
+
28
+ def content_type
29
+ env[:content_type]
30
+ end
31
+
32
+ def request_uri
33
+ env[:request_uri]
34
+ end
35
+
36
+ def query_string
37
+ env[:query_string]
38
+ end
39
+
40
+ def post_content
41
+ env[:post_content]
42
+ end
43
+
44
+ def protocol
45
+ env[:protocol]
46
+ end
47
+
48
+ def ip
49
+ env[:ip]
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,64 @@
1
+ module Noder
2
+ module HTTP
3
+ class Response < EventMachine::DelegatedHttpResponse
4
+ attr_accessor :params
5
+
6
+ def initialize(env)
7
+ super(env[:connection])
8
+ @params = env[:request].params
9
+ @is_rendered = false
10
+ end
11
+
12
+ def write(content)
13
+ self.content ||= ''
14
+ self.content << content
15
+ end
16
+
17
+ def write_head(status, headers={})
18
+ self.status = status
19
+ @headers.merge!(headers)
20
+ end
21
+
22
+ def status_code=(status)
23
+ self.status = status
24
+ end
25
+
26
+ def status_code
27
+ self.status
28
+ end
29
+
30
+ def set_header(name, value)
31
+ @headers[name] = value
32
+ end
33
+
34
+ def get_header(name)
35
+ @headers[name]
36
+ end
37
+
38
+ def remove_header(name)
39
+ @headers.delete(name)
40
+ end
41
+
42
+ def end(content=nil)
43
+ return if @is_rendered
44
+ @is_rendered = true
45
+ write(content) if content
46
+ send_response
47
+ end
48
+
49
+ def is_rendered?
50
+ @is_rendered
51
+ end
52
+
53
+ protected
54
+
55
+ def app
56
+ @delegate.app
57
+ end
58
+
59
+ def request_env
60
+ @delegate.request_env
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,91 @@
1
+ require 'em-synchrony'
2
+
3
+ module Noder
4
+ module HTTP
5
+ class Server
6
+ include Events::EventEmitter
7
+
8
+ attr_accessor :options
9
+
10
+ def initialize(options={}, &block)
11
+ defaults = {
12
+ address: '0.0.0.0',
13
+ port: 8000,
14
+ app: nil,
15
+ environment: 'development',
16
+ threadpool_size: 20,
17
+ enable_ssl: false,
18
+ ssl_key: nil,
19
+ ssl_cert: nil
20
+ }
21
+ @options = defaults.merge(options)
22
+ # The 'close' event is emitted as EM is stopped, so we need to handle the callbacks outside of
23
+ # the EM event loop with Events::EventNode instead of Events::EMEventNode
24
+ set_node_class_for_event(Events::EventNode, 'close')
25
+ push_default_callbacks
26
+ on('request', &block) if block
27
+ end
28
+
29
+ def listen(port=nil, address=nil, options={}, &block)
30
+ @options.merge!(options)
31
+ @options[:port] = port if port
32
+ @options[:address] = address if address
33
+ EM.threadpool_size = @options[:threadpool_size]
34
+ EM.epoll
35
+ EM.synchrony do
36
+ trap('INT') { close }
37
+ trap('TERM') { close }
38
+ # Listeners::NotFound should run after all other listeners, so we'll add it here
39
+ add_listener('request', Listeners::NotFound)
40
+
41
+ Noder.logger.info "Running Noder at #{@options[:address]}:#{@options[:port]}..."
42
+ emit('start')
43
+ connection_settings = Noder::Utils.slice_hash(@options, [:enable_ssl, :ssl_key, :ssl_cert])
44
+ EM.start_server(@options[:address], @options[:port], Noder::HTTP::Connection, block, connection_settings) do |connection|
45
+ connection.request_stack = event_stack('request')
46
+ connection.app = @options[:app]
47
+ connection.environment = @options[:environment]
48
+ end
49
+ end
50
+ end
51
+
52
+ def on(event, callback=nil, &block)
53
+ callback ||= block
54
+ case event
55
+ when 'request'
56
+ super('request', callback, argument_keys: [:request, :response])
57
+ when 'close'
58
+ super('close', callback)
59
+ else
60
+ super(event, callback)
61
+ end
62
+ end
63
+
64
+ def close
65
+ Noder.logger.info 'Stopping Noder...'
66
+ emit('close')
67
+ EM.stop
68
+ end
69
+
70
+ protected
71
+
72
+ def push_default_callbacks
73
+ default_callbacks.each do |event, items|
74
+ items.each do |item|
75
+ add_listener(event, item)
76
+ end
77
+ end
78
+ end
79
+
80
+ def default_callbacks
81
+ {
82
+ 'close' => [],
83
+ 'request' => [
84
+ Listeners::Request,
85
+ Listeners::Response
86
+ ]
87
+ }
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,96 @@
1
+ require 'uri'
2
+
3
+ module Noder
4
+ module HTTP
5
+ module Utils
6
+ DEFAULT_SEP = /[&;] */n
7
+
8
+ class << self
9
+ attr_accessor :key_space_limit
10
+ end
11
+
12
+ # The default number of bytes to allow parameter keys to take up.
13
+ # This helps prevent a rogue client from flooding a Request.
14
+ self.key_space_limit = 65536
15
+
16
+ class KeySpaceConstrainedParams
17
+ def initialize(limit = Utils.key_space_limit)
18
+ @limit = limit
19
+ @size = 0
20
+ @params = {}
21
+ end
22
+
23
+ def [](key)
24
+ @params[key]
25
+ end
26
+
27
+ def []=(key, value)
28
+ @size += key.size if key && !@params.key?(key)
29
+ raise RangeError, 'exceeded available parameter key space' if @size > @limit
30
+ @params[key] = value
31
+ end
32
+
33
+ def key?(key)
34
+ @params.key?(key)
35
+ end
36
+
37
+ def to_params_hash
38
+ hash = @params
39
+ hash.keys.each do |key|
40
+ value = hash[key]
41
+ if value.kind_of?(self.class)
42
+ hash[key] = value.to_params_hash
43
+ elsif value.kind_of?(Array)
44
+ value.map! {|x| x.kind_of?(self.class) ? x.to_params_hash : x}
45
+ end
46
+ end
47
+ hash
48
+ end
49
+ end
50
+
51
+ if defined?(::Encoding)
52
+ def unescape(s, encoding = Encoding::UTF_8)
53
+ URI.decode_www_form_component(s, encoding)
54
+ end
55
+ else
56
+ def unescape(s, encoding = nil)
57
+ URI.decode_www_form_component(s, encoding)
58
+ end
59
+ end
60
+ module_function :unescape
61
+
62
+ def parse(qs, d = nil, &unescaper)
63
+ unescaper ||= method(:unescape)
64
+
65
+ params = KeySpaceConstrainedParams.new
66
+
67
+ (qs || '').split(d ? /[#{d}] */n : DEFAULT_SEP).each do |p|
68
+ next if p.empty?
69
+ k, v = p.split('=', 2).map(&unescaper)
70
+
71
+ if cur = params[k]
72
+ if cur.class == Array
73
+ params[k] << v
74
+ else
75
+ params[k] = [cur, v]
76
+ end
77
+ else
78
+ params[k] = v
79
+ end
80
+ end
81
+
82
+ return params.to_params_hash
83
+ end
84
+ module_function :parse
85
+
86
+ def parse_headers(string)
87
+ string.split("\x00").reduce({}) do |hash, string|
88
+ key, value = string.split(': ', 2)
89
+ hash[key] = value
90
+ hash
91
+ end
92
+ end
93
+ module_function :parse_headers
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,10 @@
1
+ require 'uri'
2
+
3
+ module Noder
4
+ module Utils
5
+ def slice_hash(hash, keys)
6
+ keys.each_with_object({}) { |k, new_hash| new_hash[k] = hash[k] if hash.has_key?(k) }
7
+ end
8
+ module_function :slice_hash
9
+ end
10
+ end
@@ -1,3 +1,3 @@
1
1
  module Noder
2
- VERSION = '0.0.1'
2
+ VERSION = '0.0.2'
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: noder
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,8 +9,56 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-05-07 00:00:00.000000000 Z
12
+ date: 2014-05-13 00:00:00.000000000 Z
13
13
  dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: eventmachine
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: eventmachine_httpserver
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
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'
46
+ - !ruby/object:Gem::Dependency
47
+ name: em-synchrony
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: 1.0.0
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: 1.0.0
14
62
  - !ruby/object:Gem::Dependency
15
63
  name: rspec
16
64
  requirement: !ruby/object:Gem::Requirement
@@ -34,10 +82,25 @@ executables: []
34
82
  extensions: []
35
83
  extra_rdoc_files: []
36
84
  files:
85
+ - lib/noder/events/em_event_node.rb
86
+ - lib/noder/events/event_emitter.rb
87
+ - lib/noder/events/event_node.rb
88
+ - lib/noder/events/event_stack.rb
89
+ - lib/noder/events/listeners/base.rb
90
+ - lib/noder/events.rb
91
+ - lib/noder/http/connection.rb
92
+ - lib/noder/http/listeners/not_found.rb
93
+ - lib/noder/http/listeners/request.rb
94
+ - lib/noder/http/listeners/response.rb
95
+ - lib/noder/http/request.rb
96
+ - lib/noder/http/response.rb
97
+ - lib/noder/http/server.rb
98
+ - lib/noder/http/utils.rb
99
+ - lib/noder/http.rb
100
+ - lib/noder/utils.rb
37
101
  - lib/noder/version.rb
38
102
  - lib/noder.rb
39
103
  - MIT-LICENSE
40
- - Rakefile
41
104
  - README.md
42
105
  homepage: https://github.com/tombenner/noder
43
106
  licenses:
data/Rakefile DELETED
@@ -1,12 +0,0 @@
1
- #!/usr/bin/env rake
2
- require "bundler/gem_tasks"
3
- require "rake/testtask"
4
-
5
- task :default => :test
6
-
7
- Rake::TestTask.new do |t|
8
- t.libs << "lib"
9
- t.libs << "test"
10
- t.test_files = FileList["test/**/*_test.rb"]
11
- t.verbose = true
12
- end