plezi 0.9.2 → 0.10.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +30 -0
  3. data/README.md +44 -31
  4. data/bin/plezi +3 -3
  5. data/lib/plezi.rb +21 -43
  6. data/lib/plezi/common/defer.rb +21 -0
  7. data/lib/plezi/common/dsl.rb +115 -91
  8. data/lib/plezi/common/redis.rb +44 -0
  9. data/lib/plezi/common/settings.rb +58 -0
  10. data/lib/plezi/handlers/controller_core.rb +132 -0
  11. data/lib/plezi/handlers/controller_magic.rb +85 -259
  12. data/lib/plezi/handlers/http_router.rb +139 -60
  13. data/lib/plezi/handlers/route.rb +9 -178
  14. data/lib/plezi/handlers/stubs.rb +2 -2
  15. data/lib/plezi/helpers/http_sender.rb +72 -0
  16. data/lib/plezi/helpers/magic_helpers.rb +12 -0
  17. data/lib/plezi/{server → helpers}/mime_types.rb +0 -0
  18. data/lib/plezi/version.rb +1 -1
  19. data/plezi.gemspec +3 -11
  20. data/resources/Gemfile +20 -21
  21. data/resources/controller.rb +2 -2
  22. data/resources/oauth_config.rb +1 -1
  23. data/resources/redis_config.rb +2 -0
  24. data/test/plezi_tests.rb +39 -46
  25. metadata +24 -33
  26. data/lib/plezi/common/logging.rb +0 -60
  27. data/lib/plezi/eventmachine/connection.rb +0 -190
  28. data/lib/plezi/eventmachine/em.rb +0 -98
  29. data/lib/plezi/eventmachine/io.rb +0 -272
  30. data/lib/plezi/eventmachine/protocol.rb +0 -54
  31. data/lib/plezi/eventmachine/queue.rb +0 -51
  32. data/lib/plezi/eventmachine/ssl_connection.rb +0 -144
  33. data/lib/plezi/eventmachine/timers.rb +0 -117
  34. data/lib/plezi/eventmachine/workers.rb +0 -33
  35. data/lib/plezi/handlers/http_echo.rb +0 -27
  36. data/lib/plezi/handlers/http_host.rb +0 -214
  37. data/lib/plezi/handlers/magic_helpers.rb +0 -32
  38. data/lib/plezi/server/http.rb +0 -129
  39. data/lib/plezi/server/http_protocol.rb +0 -319
  40. data/lib/plezi/server/http_request.rb +0 -146
  41. data/lib/plezi/server/http_response.rb +0 -319
  42. data/lib/plezi/server/websocket.rb +0 -251
  43. data/lib/plezi/server/websocket_client.rb +0 -178
  44. data/lib/plezi/server/ws_response.rb +0 -161
@@ -1,146 +0,0 @@
1
- module Plezi
2
-
3
- # class is the base for the HTTP server.
4
- #
5
- # the class is initialized with a TCP/IP connection socket and starts
6
- # an event driven cycle using the `EventStack.push` and `EventStack.reverse_async`.
7
- #
8
- # to-do: fox logging.
9
- class HTTPRequest < Hash
10
-
11
- def initialize service
12
- super()
13
- self[:plezi_service] = service
14
- end
15
-
16
- public
17
-
18
- # the request's headers
19
- def headers
20
- self.select {|k,v| k.is_a? String }
21
- end
22
- # the request's method (GET, POST... etc').
23
- def request_method
24
- self[:method]
25
- end
26
- # set request's method (GET, POST... etc').
27
- def request_method= value
28
- self[:method] = value
29
- end
30
- # the parameters sent by the client.
31
- def params
32
- self[:params]
33
- end
34
- # the cookies sent by the client.
35
- def cookies
36
- self[:cookies]
37
- end
38
-
39
- # the query string
40
- def query
41
- self[:query]
42
- end
43
-
44
- # the original (frozen) path (resource requested).
45
- def original_path
46
- self[:original_path]
47
- end
48
-
49
- # the requested path (rewritable).
50
- def path
51
- self[:path]
52
- end
53
- def path=(new_path)
54
- self[:path] = new_path
55
- end
56
-
57
- # the base url ([http/https]://host[:port])
58
- def base_url switch_protocol = nil
59
- "#{switch_protocol || self[:requested_protocol]}://#{self[:host_name]}#{self[:port]? ":#{self[:port]}" : ''}"
60
- end
61
-
62
- # the request's url, without any GET parameters ([http/https]://host[:port]/path)
63
- def request_url switch_protocol = nil
64
- "#{base_url switch_protocol}#{self[:original_path]}"
65
- end
66
-
67
- # the service (socket wrapper) that answered this request
68
- def service
69
- self[:plezi_service]
70
- end
71
- # the protocol managing this request
72
- def protocol
73
- self[:requested_protocol]
74
- end
75
- # the handler dealing with this request
76
- def handler
77
- self[:plezi_service].handler
78
- end
79
-
80
- # method recognition
81
-
82
- # returns true of the method == GET
83
- def get?
84
- self[:method] == 'GET'
85
- end
86
- # returns true of the method == HEAD
87
- def head?
88
- self[:method] == 'HEAD'
89
- end
90
- # returns true of the method == POST
91
- def post?
92
- self[:method] == 'POST'
93
- end
94
- # returns true of the method == PUT
95
- def put?
96
- self[:method] == 'PUT'
97
- end
98
- # returns true of the method == DELETE
99
- def delete?
100
- self[:method] == 'DELETE'
101
- end
102
- # returns true of the method == TRACE
103
- def trace?
104
- self[:method] == 'TRACE'
105
- end
106
- # returns true of the method == OPTIONS
107
- def options?
108
- self[:method] == 'OPTIONS'
109
- end
110
- # returns true of the method == CONNECT
111
- def connect?
112
- self[:method] == 'CONNECT'
113
- end
114
- # returns true of the method == PATCH
115
- def patch?
116
- self[:method] == 'PATCH'
117
- end
118
- # returns true if the request is of type JSON.
119
- def json?
120
- self['content-type'].match /application\/json/
121
- end
122
- # returns true if the request is of type XML.
123
- def xml?
124
- self['content-type'].match /text\/xml/
125
- end
126
-
127
- end
128
- end
129
-
130
-
131
- ######
132
- ## example requests
133
-
134
- # GET / HTTP/1.1
135
- # Host: localhost:3000
136
- # Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
137
- # Cookie: user_token=2INa32_vDgx8Aa1qe43oILELpSdIe9xwmT8GTWjkS-w
138
- # User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10) AppleWebKit/600.1.25 (KHTML, like Gecko) Version/8.0 Safari/600.1.25
139
- # Accept-Language: en-us
140
- # Accept-Encoding: gzip, deflate
141
- # Connection: keep-alive
142
- #
143
- # => "GET / HTTP/1.1\n\rHost: localhost:2000\n\rAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\n\rCookie: user_token=2INa32_vDgx8Aa1qe43oILELpSdIe9xwmT8GTWjkS-w\n\rUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10) AppleWebKit/600.1.25 (KHTML, like Gecko) Version/8.0 Safari/600.1.25\n\rAccept-Language: en-us\n\rAccept-Encoding: gzip, deflate\n\rConnection: keep-alive\n\r\n\r"
144
- # => "GET /people/are/friendly HTTP/1.1\n\rHost: localhost:2000\n\rAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\n\rCookie: user_token=2INa32_vDgx8Aa1qe43oILELpSdIe9xwmT8GTWjkS-w\n\rUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10) AppleWebKit/600.1.25 (KHTML, like Gecko) Version/8.0 Safari/600.1.25\n\rAccept-Language: en-us\n\rAccept-Encoding: gzip, deflate\n\rConnection: keep-alive\n\r\n\r"
145
- # => "GET /girls?sexy=true HTTP/1.1\n\rHost: localhost:2000\n\rAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\n\rCookie: user_token=2INa32_vDgx8Aa1qe43oILELpSdIe9xwmT8GTWjkS-w\n\rUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10) AppleWebKit/600.1.25 (KHTML, like Gecko) Version/8.0 Safari/600.1.25\n\rAccept-Language: en-us\n\rAccept-Encoding: gzip, deflate\n\rConnection: keep-alive\n\r\n\r"
146
- # chunked => "17d; ignored data=boaz\r\nGET / HTTP/1.1\r\nHost: localhost:3000\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\nCookie: user_token=2INa32_vDgx8Aa1qe43oILELpSdIe9xwmT8GTWjkS-w\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10) AppleWebKit/600.1.25 (KHTML, like Gecko) Version/8.0 Safari/600.1.25\r\nAccept-Language: en-us\r\nAccept-Encoding: gzip, deflate\r\nConnection: keep-alive\r\nc\r\n\r\nparsed as:\r\n\r\n4f4\r\n{:raw=>\"GET / HTTP/1.1\\r\\nHost: localhost:3000\\r\\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\\r\\nCookie: user_token=2INa32_vDgx8Aa1qe43oILELpSdIe9xwmT8GTWjkS-w\\r\\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10) AppleWebKit/600.1.25 (KHTML, like Gecko) Version/8.0 Safari/600.1.25\\r\\nAccept-Language: en-us\\r\\nAccept-Encoding: gzip, deflate\\r\\nConnection: keep-alive\\r\\n\\r\\n\", :plezi_service=>#<Plezi::BasicService:0x007ff4daab5ac8 @handler=Plezi::HTTPEcho, @socket=#<TCPSocket:fd 9>, @in_que=\"\", @out_que=[], @locker=#<Mutex:0x007ff4daab5a28>, @parameters={:protocol=>Plezi::HTTPProtocol, :handler=>Plezi::HTTPEcho}, @protocol=Plezi::HTTPProtocol>, :params=>{}, :cookies=>{:user_token=>\"2INa32_vDgx8Aa1qe43oILELpSdIe9xwmT8GTWjkS-w\"}, :method=>\"GET\", :query=>\"/\", :original_path=>\"/\", :path=>\"/\", :version=>\"HTTP/1.1\", \"host\"=>\"localhost:3000\", \"accept\"=>\"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\", \"cookie\"=>\"user_token=2INa32_vDgx8Aa1qe43oILELpSdIe9xwmT8GTWjkS-w\", \"user-agent\"=>\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10) AppleWebKit/600.1.25 (KHTML, like Gecko) Version/8.0 Safari/600.1.25\", \"accept-language\"=>\"en-us\", \"accept-encoding\"=>\"gzip, deflate\", \"connection\"=>\"keep-alive\"}\r\n0\r\n\r\n"
@@ -1,319 +0,0 @@
1
- module Plezi
2
-
3
- # this class handles HTTP response objects.
4
- #
5
- # learning from rack, the basic response objects imitates the [0, {}, []] structure... with some updates.
6
- #
7
- # the Response's body should respond to each (and optionally to close).
8
- #
9
- # The response can be sent asynchronously, but headers and status cannot be changed once the response started sending data.
10
- class HTTPResponse
11
-
12
- #the response's status code
13
- attr_accessor :status
14
- #the response's headers
15
- attr_reader :headers
16
- #the flash cookie-jar (single-use cookies, that survive only one request)
17
- attr_reader :flash
18
- #the response's body container (defaults to an array, but can be replaces by any obect that supports `each` - `close` is NOT supported - call `close` as a callback block after `send` if you need to close the object).
19
- attr_accessor :body
20
- #bytes sent to the asynchronous que so far - excluding headers (only the body object).
21
- attr_reader :bytes_sent
22
- #the service through which the response will be sent.
23
- attr_reader :service
24
- #the request.
25
- attr_accessor :request
26
- #the http version header
27
- attr_accessor :http_version
28
- #Danger Zone! direct access to cookie headers - don't use this unless you know what you're doing!
29
- attr_reader :cookies
30
-
31
- # the response object responds to a specific request on a specific service.
32
- # hence, to initialize a response object, a request must be set.
33
- #
34
- # use, at the very least `HTTPResponse.new request`
35
- def initialize request, status = 200, headers = {}, body = []
36
- @request, @status, @headers, @body, @service = request, status, headers, body, request[:plezi_service]
37
- @http_version = 'HTTP/1.1' # request.version
38
- @bytes_sent = 0
39
- @finished = @streaming = false
40
- @cookies = {}
41
- @chunked = false
42
- # propegate flash object
43
- @flash = Hash.new do |hs,k|
44
- hs["plezi_flash_#{k.to_s}".to_sym] if hs.has_key? "plezi_flash_#{k.to_s}".to_sym
45
- end
46
- request.cookies.each do |k,v|
47
- @flash[k] = v if k.to_s.start_with? 'plezi_flash_'
48
- end
49
- end
50
-
51
- # returns true if headers were already sent
52
- def headers_sent?
53
- @headers.frozen?
54
- end
55
-
56
- # returns true if the response is already finished (the client isn't expecting any more data).
57
- def finished?
58
- @finished
59
- end
60
-
61
- # returns true if the response is set to http streaming (you will need to close the response manually by calling #finish).
62
- def streaming?
63
- @streaming
64
- end
65
-
66
- # sets the http streaming flag, so that the response could be handled asynchronously.
67
- #
68
- # if this flag is not set, the response will try to automatically finish its job
69
- # (send its data and close the connection) once the controllers method has finished.
70
- #
71
- # If HTTP streaming is set, you will need to manually call `response.finish`
72
- # of the connection will not close properly.
73
- def start_http_streaming
74
- @streaming = @chunked = true
75
- end
76
-
77
- # pushes data to the body of the response. this is the preferred way to add data to the response.
78
- #
79
- # if HTTP streaming is used, remember to call #send to send the data.
80
- # it is also possible to only use #send while streaming, although performance should be considered when streaming using #send rather then caching using #<<.
81
- def << str
82
- body.push str
83
- # send if streaming?
84
- end
85
-
86
- # returns a response header, if set.
87
- def [] header
88
- headers[header] # || @cookies[header]
89
- end
90
-
91
- # sets a response header. response headers should be a down-case String or Symbol.
92
- #
93
- # this is the prefered to set a header.
94
- #
95
- # returns the value set for the header.
96
- #
97
- # see HTTP response headers for valid headers and values: http://en.wikipedia.org/wiki/List_of_HTTP_header_fields
98
- def []= header, value
99
- header.is_a?(String) ? header.downcase! : (header.is_a?(Symbol) ? (header = header.to_s.downcase.to_sym) : (return false))
100
- headers[header] = value
101
- end
102
-
103
- # sets/deletes cookies when headers are sent.
104
- #
105
- # accepts:
106
- # name:: the cookie's name
107
- # value:: the cookie's value
108
- # parameters:: a parameters Hash for cookie creation.
109
- #
110
- # parameters accept any of the following Hash keys and values:
111
- #
112
- # expires:: a Time object with the expiration date. defaults to 10 years in the future.
113
- # max_age:: a Max-Age HTTP cookie string.
114
- # path:: the path from which the cookie is acessible. defaults to '/'.
115
- # domain:: the domain for the cookie (best used to manage subdomains). defaults to the active domain (sub-domain limitations might apply).
116
- # secure:: if set to `true`, the cookie will only be available over secure connections. defaults to false.
117
- # http_only:: if true, the HttpOnly flag will be set (not accessible to javascript). defaults to false.
118
- #
119
- def set_cookie name, value, params = {}
120
- params[:expires] = (Time.now - 315360000) unless value
121
- value ||= 'deleted'
122
- params[:expires] ||= (Time.now + 315360000) unless params[:max_age]
123
- params[:path] ||= '/'
124
- value = HTTP.encode(value.to_s)
125
- if params[:max_age]
126
- value << ('; Max-Age=%s' % params[:max_age])
127
- else
128
- value << ('; Expires=%s' % params[:expires].httpdate)
129
- end
130
- value << "; Path=#{params[:path]}"
131
- value << "; Domain=#{params[:domain]}" if params[:domain]
132
- value << '; Secure' if params[:secure]
133
- value << '; HttpOnly' if params[:http_only]
134
- @cookies[HTTP.encode(name.to_s).to_sym] = value
135
- end
136
-
137
- # deletes a cookie (actually calls `set_cookie name, nil`)
138
- def delete_cookie name
139
- set_cookie name, nil
140
- end
141
-
142
- # clears the response object, unless headers were already sent (use `response.body.clear` to clear only the unsent body).
143
- #
144
- # returns false if the response was already sent.
145
- def clear
146
- return false if headers.frozen? || @finished
147
- @status, @body, @headers, @cookies = 200, [], {}, {}
148
- true
149
- end
150
-
151
- # sends the response object. headers will be frozen (they can only be sent at the head of the response).
152
- #
153
- # the response will remain open for more data to be sent through (using `response << data` and `response.send`).
154
- def send(str = nil)
155
- raise 'HTTPResponse SERVICE MISSING: cannot send http response without a service.' unless service
156
- body << str if str && body.is_a?(Array)
157
- send_headers
158
- return if request.head?
159
- if @chunked
160
- body.each do |s|
161
- service.send "#{s.bytesize.to_s(16)}\r\n"
162
- service.send s
163
- service.send "\r\n"
164
- @bytes_sent += s.bytesize
165
- end
166
- else
167
- body.each do |s|
168
- service.send s
169
- @bytes_sent += s.bytesize
170
- end
171
- end
172
- @body.is_a?(Array) ? @body.clear : ( @body = [] )
173
- end
174
-
175
- # prep for rack response
176
- def prep_rack
177
- @headers['content-length'] ||= body[0].bytesize.to_s if !headers_sent? && body.is_a?(Array) && body.length == 1
178
- fix_cookie_headers
179
- end
180
-
181
-
182
- if defined?(PLEZI_ON_RACK)
183
- # does nothing.
184
- def finish
185
- false
186
- end
187
- else
188
- # sends the response and flags the response as complete. future data should not be sent. the flag will only be enforced be the Plezi router. your code might attempt sending data (which would probbaly be ignored by the client or raise an exception).
189
- def finish
190
- @headers['content-length'] ||= body[0].bytesize if !headers_sent? && body.is_a?(Array) && body.length == 1
191
- self.send
192
- service.send( (@chunked) ? "0\r\n\r\n" : nil)
193
- @finished = true
194
- # service.disconnect unless headers['keep-alive']
195
- # log
196
- Plezi.log_raw "#{request[:client_ip]} [#{Time.now.utc}] \"#{request[:method]} #{request[:original_path]} #{request[:requested_protocol]}\/#{request[:version]}\" #{status} #{bytes_sent.to_s} #{"%0.3f" % ((Time.now - request[:time_recieved])*1000)}ms\n"
197
- end
198
-
199
- end
200
-
201
- # Danger Zone (internally used method, use with care): attempts to finish the response - if it was not flaged as streaming or completed.
202
- def try_finish
203
- finish unless @finished || @streaming
204
- end
205
-
206
- # Danger Zone (internally used method, use with care): fix response's headers before sending them (date, connection and transfer-coding).
207
- def fix_cookie_headers
208
- # remove old flash cookies
209
- request.cookies.keys.each do |k|
210
- if k.to_s.start_with? 'plezi_flash_'
211
- set_cookie k, nil
212
- flash.delete k
213
- end
214
- end
215
- #set new flash cookies
216
- @flash.each do |k,v|
217
- set_cookie "plezi_flash_#{k.to_s}", v
218
- end
219
- end
220
- # Danger Zone (internally used method, use with care): fix response's headers before sending them (date, connection and transfer-coding).
221
- def send_headers
222
- return false if @headers.frozen?
223
- fix_cookie_headers
224
- headers['cache-control'] ||= 'no-cache'
225
-
226
- service.send "#{@http_version} #{status} #{STATUS_CODES[status] || 'unknown'}\r\nDate: #{Time.now.httpdate}\r\n"
227
-
228
- unless headers['connection']
229
- service.send "Connection: Keep-Alive\r\nKeep-Alive: timeout=5\r\n"
230
- end
231
-
232
- if headers['content-length']
233
- @chunked = false
234
- else
235
- @chunked = true
236
- service.send "Transfer-Encoding: chunked\r\n"
237
- end
238
-
239
- headers.each {|k,v| service.send "#{k.to_s}: #{v}\r\n"}
240
- @cookies.each {|k,v| service.send "Set-Cookie: #{k.to_s}=#{v.to_s}\r\n"}
241
- service.send "\r\n"
242
- @headers.freeze
243
- # @cookies.freeze
244
- end
245
-
246
- # response status codes, as defined.
247
- STATUS_CODES = {100=>"Continue",
248
- 101=>"Switching Protocols",
249
- 102=>"Processing",
250
- 200=>"OK",
251
- 201=>"Created",
252
- 202=>"Accepted",
253
- 203=>"Non-Authoritative Information",
254
- 204=>"No Content",
255
- 205=>"Reset Content",
256
- 206=>"Partial Content",
257
- 207=>"Multi-Status",
258
- 208=>"Already Reported",
259
- 226=>"IM Used",
260
- 300=>"Multiple Choices",
261
- 301=>"Moved Permanently",
262
- 302=>"Found",
263
- 303=>"See Other",
264
- 304=>"Not Modified",
265
- 305=>"Use Proxy",
266
- 306=>"(Unused)",
267
- 307=>"Temporary Redirect",
268
- 308=>"Permanent Redirect",
269
- 400=>"Bad Request",
270
- 401=>"Unauthorized",
271
- 402=>"Payment Required",
272
- 403=>"Forbidden",
273
- 404=>"Not Found",
274
- 405=>"Method Not Allowed",
275
- 406=>"Not Acceptable",
276
- 407=>"Proxy Authentication Required",
277
- 408=>"Request Timeout",
278
- 409=>"Conflict",
279
- 410=>"Gone",
280
- 411=>"Length Required",
281
- 412=>"Precondition Failed",
282
- 413=>"Payload Too Large",
283
- 414=>"URI Too Long",
284
- 415=>"Unsupported Media Type",
285
- 416=>"Range Not Satisfiable",
286
- 417=>"Expectation Failed",
287
- 422=>"Unprocessable Entity",
288
- 423=>"Locked",
289
- 424=>"Failed Dependency",
290
- 426=>"Upgrade Required",
291
- 428=>"Precondition Required",
292
- 429=>"Too Many Requests",
293
- 431=>"Request Header Fields Too Large",
294
- 500=>"Internal Server Error",
295
- 501=>"Not Implemented",
296
- 502=>"Bad Gateway",
297
- 503=>"Service Unavailable",
298
- 504=>"Gateway Timeout",
299
- 505=>"HTTP Version Not Supported",
300
- 506=>"Variant Also Negotiates",
301
- 507=>"Insufficient Storage",
302
- 508=>"Loop Detected",
303
- 510=>"Not Extended",
304
- 511=>"Network Authentication Required"
305
- }
306
- end
307
- end
308
-
309
- ######
310
- ## example requests
311
-
312
- # GET / HTTP/1.1
313
- # Host: localhost:2000
314
- # Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
315
- # Cookie: user_token=2INa32_vDgx8Aa1qe43oILELpSdIe9xwmT8GTWjkS-w
316
- # User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10) AppleWebKit/600.1.25 (KHTML, like Gecko) Version/8.0 Safari/600.1.25
317
- # Accept-Language: en-us
318
- # Accept-Encoding: gzip, deflate
319
- # Connection: keep-alive