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,32 +0,0 @@
1
- module Plezi
2
-
3
- # set magic cookies
4
- #
5
- # magic cookies keep track of both incoming and outgoing cookies, setting the response's cookies as well as the combined cookie respetory (held by the request object).
6
- #
7
- # use only the []= for magic cookies. merge and update might not set the response cookies.
8
- class Cookies < ::Hash
9
- # sets the Magic Cookie's controller object (which holds the response object and it's `set_cookie` method).
10
- def set_controller controller
11
- @controller = controller
12
- end
13
- # overrides the []= method to set the cookie for the response (by encoding it and preparing it to be sent), as well as to save the cookie in the combined cookie jar (unencoded and available).
14
- def []= key, val
15
- if key.is_a?(Symbol) && self.has_key?( key.to_s)
16
- key = key.to_s
17
- elsif self.has_key?( key.to_s.to_sym)
18
- key = key.to_s.to_sym
19
- end
20
- @controller.response.set_cookie key, (val ? val.to_s.dup : nil) if @controller
21
- super
22
- end
23
- end
24
-
25
- module Helpers
26
- # a proc that allows Hashes to search for key-value pairs while also converting keys from objects to symbols and from symbols to strings.
27
- #
28
- # (key type agnostic search Hash proc)
29
- HASH_SYM_PROC = Proc.new {|h,k| k = (Symbol === k ? k.to_s : k.to_s.to_sym); h[k] if h.has_key?(k) }
30
- end
31
-
32
- end
@@ -1,129 +0,0 @@
1
- # encoding: UTF-8
2
-
3
-
4
-
5
- module Plezi
6
-
7
- # includes general helper methods for HTTP protocol and related (url encoding etc')
8
- module HTTP
9
- module_function
10
-
11
- # Based on the WEBRick source code, escapes &, ", > and < in a String object
12
- def escape(string)
13
- string.gsub(/&/n, '&amp;')
14
- .gsub(/\"/n, '&quot;')
15
- .gsub(/>/n, '&gt;')
16
- .gsub(/</n, '&lt;')
17
- end
18
- def add_param_to_hash param_name, param_value, target_hash
19
- begin
20
- a = target_hash
21
- p = param_name.gsub(']',' ').split(/\[/)
22
- val = rubyfy! param_value
23
- p.each_index { |i| p[i].strip! ; n = p[i].match(/^[0-9]+$/) ? p[i].to_i : p[i].to_sym ; p[i+1] ? [ ( a[n] ||= ( p[i+1] == ' ' ? [] : {} ) ), ( a = a[n]) ] : ( a.is_a?(Hash) ? (a[n] ? (a[n].is_a?(Array) ? (a << val) : a[n] = [a[n], val] ) : (a[n] = val) ) : (a << val) ) }
24
- rescue Exception => e
25
- Plezi.error e
26
- Plezi.error "(Silent): parameters parse error for #{param_name} ... maybe conflicts with a different set?"
27
- target_hash[param_name] = rubyfy! param_value
28
- end
29
- end
30
-
31
- def decode object, decode_method = :form
32
- if object.is_a?(Hash)
33
- object.values.each {|v| decode v, decode_method}
34
- elsif object.is_a?(Array)
35
- object.each {|v| decode v, decode_method}
36
- elsif object.is_a?(String)
37
- case decode_method
38
- when :form
39
- object.gsub!('+', '%20')
40
- object.gsub!(/\%[0-9a-fA-F][0-9a-fA-F]/) {|m| m[1..2].to_i(16).chr}
41
- when :uri, :url
42
- object.gsub!(/\%[0-9a-fA-F][0-9a-fA-F]/) {|m| m[1..2].to_i(16).chr}
43
- when :html
44
- object.gsub!(/&amp;/i, '&')
45
- object.gsub!(/&quot;/i, '"')
46
- object.gsub!(/&gt;/i, '>')
47
- object.gsub!(/&lt;/i, '<')
48
- when :utf8
49
-
50
- else
51
-
52
- end
53
- object.gsub!(/&#([0-9a-fA-F]{2});/) {|m| m.match(/[0-9a-fA-F]{2}/)[0].hex.chr}
54
- object.gsub!(/&#([0-9]{4});/) {|m| [m.match(/[0-9]+/)[0].to_i].pack 'U'}
55
- make_utf8! object
56
- return object
57
- elsif object.is_a?(Symbol)
58
- str = object.to_str
59
- decode str, decode_method
60
- return str.to_sym
61
- else
62
- raise "Plezi Raising Hell (don't misuse us)!"
63
- end
64
- end
65
- def encode object, decode_method = :form
66
- if object.is_a?(Hash)
67
- object.values.each {|v| encode v, decode_method}
68
- elsif object.is_a?(Array)
69
- object.each {|v| encode v, decode_method}
70
- elsif object.is_a?(String)
71
- case decode_method
72
- when :uri, :url, :form
73
- object.force_encoding 'binary'
74
- object.gsub!(/[^a-zA-Z0-9\*\.\_\-]/) {|m| m.ord <= 16 ? "%0#{m.ord.to_s(16)}" : "%#{m.ord.to_s(16)}"}
75
- when :html
76
- object.gsub!('&', '&amp;')
77
- object.gsub!('"', '&quot;')
78
- object.gsub!('>', '&gt;')
79
- object.gsub!('<', '&lt;')
80
- object.gsub!(/[^\sa-zA-Z\d\&\;]/) {|m| '&#%04d;' % m.unpack('U')[0] }
81
- # object.gsub!(/[^\s]/) {|m| "&#%04d;" % m.unpack('U')[0] }
82
- object.force_encoding 'binary'
83
- when :utf8
84
- object.gsub!(/[^\sa-zA-Z\d]/) {|m| '&#%04d;' % m.unpack('U')[0] }
85
- object.force_encoding 'binary'
86
- else
87
-
88
- end
89
- return object
90
- elsif object.is_a?(Symbol)
91
- str = object.to_str
92
- encode str, decode_method
93
- return str.to_sym
94
- else
95
- raise "Plezi Raising Hell (don't misuse us)!"
96
- end
97
- end
98
- # extracts parameters from the query
99
- def extract_data data, target_hash, decode = :form
100
- data.each do |set|
101
- list = set.split('=')
102
- list.each {|s| HTTP.decode s, decode if s}
103
- add_param_to_hash list.shift, list.join('='), target_hash
104
- end
105
- end
106
-
107
- # re-encodes a string into UTF-8
108
- def make_utf8!(string, encoding= 'utf-8')
109
- return false unless string
110
- string.force_encoding('binary').encode!(encoding, 'binary', invalid: :replace, undef: :replace, replace: '') unless string.force_encoding(encoding).valid_encoding?
111
- string
112
- end
113
-
114
- # Changes String to a Ruby Object, if it's a special string
115
- def rubyfy!(string)
116
- return false unless string
117
- # make_utf8! string
118
- if string == 'true'
119
- string = true
120
- elsif string == 'false'
121
- string = false
122
- elsif string.match(/[0-9]/) && !string.match(/[^0-9]/)
123
- string = string.to_i
124
- end
125
- string
126
- end
127
-
128
- end
129
- end
@@ -1,319 +0,0 @@
1
- module Plezi
2
-
3
- # this module is the protocol (controller) for the HTTP server.
4
- #
5
- #
6
- # to do: implemet logging, support body types: multipart (non-ASCII form data / uploaded files), json & xml
7
- class HTTPProtocol < EventMachine::Protocol
8
-
9
- HTTP_METHODS = %w{GET HEAD POST PUT DELETE TRACE OPTIONS}
10
- HTTP_METHODS_REGEXP = /^#{HTTP_METHODS.join('|')}/
11
- HTTP_FIRE_REQUEST = Proc.new {|handler, request| handler.on_request request}
12
-
13
- def initialize connection, params
14
- super
15
- @parser_stage = 0
16
- @parser_data = {}
17
- @parser_body = ''
18
- @parser_chunk = ''
19
- @parser_length = 0
20
- @@rack_dictionary ||= {'HOST'.freeze => :host_name, 'REQUEST_METHOD'.freeze => :method,
21
- 'PATH_INFO'.freeze => :path, 'QUERY_STRING'.freeze => :query,
22
- 'SERVER_NAME'.freeze => :host_name, 'SERVER_PORT'.freeze => :port,
23
- 'rack.url_scheme'.freeze => :requested_protocol}
24
- end
25
-
26
- # called when data is recieved.
27
- #
28
- # this method is called within a lock on the connection (Mutex) - craeful from double locking.
29
- #
30
- # typically returns an Array with any data not yet processed (to be returned to the in-que)... but here it always processes (or discards) the data.
31
- def on_message
32
- # parse the request
33
- parse_message
34
- # if (@parser_stage == 1) && @parser_data[:version] >= 1.1
35
- # # send 100 continue message????? doesn't work! both Crome and Safari go crazy if this is sent after the request was sent (but before all the packets were recieved... msgs over 1 Mb).
36
- # # Plezi.push_event Proc.new { Plezi.info "sending continue signal."; connection.send_nonblock "100 Continue\r\n\r\n" }
37
- # # connection.send_unsafe_interrupt "100 Continue\r\n\r\n" # causes double lock on connection
38
- # end
39
- true
40
- end
41
-
42
-
43
- # Protocol specific helper methods.
44
-
45
- # parses incoming data
46
- def parse_message data = nil
47
- data ||= connection.read.to_s.lines.to_a
48
- # require 'pry'; binding.pry
49
- if @parser_stage == 0
50
- return false unless parse_method data
51
- end
52
- if @parser_stage == 1
53
- return false unless parse_head data
54
- end
55
- if @parser_stage == 2
56
- return false unless parse_body data
57
- end
58
- true
59
- end
60
-
61
- # parses the method request (the first line in the HTTP request).
62
- def parse_method data
63
- return false unless data[0] && data[0].match(HTTP_METHODS_REGEXP)
64
- @parser_data[:time_recieved] = Time.now
65
- @parser_data[:params] = {}
66
- @parser_data[:cookies] = Cookies.new
67
- @parser_data[:method] = ''
68
- @parser_data[:query] = ''
69
- @parser_data[:original_path] = ''
70
- @parser_data[:path] = ''
71
- if defined? ::Rack
72
- @parser_data['rack.version'] = Rack::VERSION
73
- @parser_data['rack.multithread'] = true
74
- @parser_data['rack.multiprocess'] = false
75
- @parser_data['rack.hijack?'] = false
76
- @parser_data['rack.logger'] = Plezi.logger
77
- end
78
- @parser_data[:method], @parser_data[:query], @parser_data[:version] = data.shift.split(/[\s]+/)
79
- @parser_data[:version] = (@parser_data[:version] || 'HTTP/1.1').match(/[0-9\.]+/).to_s.to_f
80
- data.shift while data[0].to_s.match /^[\r\n]+/
81
- @parser_stage = 1
82
- end
83
-
84
- #parses the head on a request (headers and values).
85
- def parse_head data
86
- until data[0].nil? || data[0].match(/^[\r\n]+$/)
87
- m = data.shift.match(/^([^:]*):[\s]*([^\r\n]*)/)
88
- # move cookies to cookie-jar, all else goes to headers
89
- case m[1].downcase
90
- when 'cookie'
91
- HTTP.extract_data m[2].split(/[;,][\s]?/), @parser_data[:cookies], :uri
92
- end
93
- @parser_data[ HTTP.make_utf8!(m[1]).downcase ] ? (@parser_data[ HTTP.make_utf8!(m[1]).downcase ] << ", #{HTTP.make_utf8! m[2]}"): (@parser_data[ HTTP.make_utf8!(m[1]).downcase ] = HTTP.make_utf8! m[2])
94
- end
95
- return false unless data[0]
96
- data.shift while data[0] && data[0].match(/^[\r\n]+$/)
97
- if @parser_data['transfer-coding'] || (@parser_data['content-length'] && @parser_data['content-length'].to_i != 0) || @parser_data['content-type']
98
- @parser_stage = 2
99
- else
100
- # create request object and hand over to handler
101
- complete_request
102
- return parse_message data unless data.empty?
103
- end
104
- true
105
- end
106
-
107
- #parses the body of a request.
108
- def parse_body data
109
- # check for body is needed, if exists and if complete
110
- if @parser_data['transfer-coding'] == 'chunked'
111
- until data.empty? || data[0].to_s.match(/0(\r)?\n/)
112
- if @parser_length == 0
113
- @parser_length = data.to_s.shift.match(/^[a-z0-9A-Z]+/).to_i(16)
114
- @parser_chunk.clear
115
- end
116
- unless @parser_length == 0
117
- @parser_chunk << data.shift while ( (@parser_length >= @parser_chunk.bytesize) && data[0])
118
- end
119
- if @parser_length <= @parser_chunk.bytesize
120
- @parser_body << @parser_chunk.byteslice(0, @parser_body.bytesize)
121
- @parser_length = 0
122
- @parser_chunk.clear
123
- end
124
- end
125
- return false unless data[0].to_s.match(/0(\r)?\n/)
126
- true until data.empty? || data.shift.match(/^[\r\n]+$/)
127
- data.shift while data[0].to_s.match /^[\r\n]+$/
128
- elsif @parser_data['content-length'].to_i
129
- @parser_length = @parser_data['content-length'].to_i if @parser_length == 0
130
- @parser_chunk << data.shift while @parser_length > @parser_chunk.bytesize && data[0]
131
- return false if @parser_length > @parser_chunk.bytesize
132
- @parser_body = @parser_chunk.byteslice(0, @parser_length)
133
- @parser_chunk.clear
134
- else
135
- Plezi.warn 'bad body request - trying to read'
136
- @parser_body << data.shift while data[0] && !data[0].match(/^[\r\n]+$/)
137
- end
138
- # parse body (POST parameters)
139
- read_body
140
-
141
- # complete request
142
- complete_request
143
-
144
- #read next request unless data is finished
145
- return parse_message data unless data.empty?
146
- true
147
- end
148
-
149
- # completes the parsing of the request and sends the request to the handler.
150
- def complete_request
151
- #finalize params and query properties
152
- m = @parser_data[:query].match /(([a-z0-9A-Z]+):\/\/)?(([^\/\:]+))?(:([0-9]+))?([^\?\#]*)(\?([^\#]*))?/
153
- @parser_data[:requested_protocol] = m[1] || (connection.ssl? ? 'https' : 'http')
154
- @parser_data[:host_name] = m[4] || (@parser_data['host'] ? @parser_data['host'].match(/^[^:]*/).to_s : nil)
155
- @parser_data[:port] = m[6] || (@parser_data['host'] ? @parser_data['host'].match(/:([0-9]*)/).to_a[1] : nil)
156
- @parser_data[:original_path] = HTTP.decode(m[7], :uri) || '/'
157
- @parser_data['host'] ||= "#{@parser_data[:host_name]}:#{@parser_data[:port]}"
158
- # parse query for params - m[9] is the data part of the query
159
- if m[9]
160
- HTTP.extract_data m[9].split(/[&;]/), @parser_data[:params]
161
- end
162
-
163
- HTTP.make_utf8! @parser_data[:original_path]
164
- @parser_data[:path] = @parser_data[:original_path].chomp('/')
165
- @parser_data[:original_path].freeze
166
-
167
- # the following can be used to extract the request's 'format':
168
- # /(^.*)(\.[^\.\/]*)$/.match @parser_data[:path]
169
- # ...? should this be done? Could be limited to certain formats:
170
- # /(^.*)(\.(txt|html|json|js|xml|xhtml|csv))$/.match @parser_data[:path]
171
- # ... even worst?
172
-
173
- HTTP.make_utf8! @parser_data[:host_name] if @parser_data[:host_name]
174
- HTTP.make_utf8! @parser_data[:query]
175
-
176
- @parser_data[:client_ip] = @parser_data['x-forwarded-for'].to_s.split(/,[\s]?/)[0] || (connection.socket.remote_address.ip_address) rescue 'unknown IP'
177
-
178
- @@rack_dictionary.each {|k,v| @parser_data[k] = @parser_data[v]}
179
-
180
- #create request
181
- request = HTTPRequest.new connection
182
- request.update @parser_data
183
-
184
- #clear current state
185
- @parser_data.clear
186
- @parser_body.clear
187
- @parser_chunk.clear
188
- @parser_length = 0
189
- @parser_stage = 0
190
-
191
- #check for server-responses
192
- case request.request_method
193
- when 'TRACE'
194
- return true
195
- when 'OPTIONS'
196
- Plezi.run_async do
197
- response = HTTPResponse.new request
198
- response[:Allow] = 'GET,HEAD,POST,PUT,DELETE,OPTIONS'
199
- response['access-control-allow-origin'] = '*'
200
- response['content-length'] = 0
201
- response.finish
202
- end
203
- return true
204
- end
205
-
206
- #pass it to the handler or decler error.
207
- if connection && connection.handler
208
- EventMachine.queue [connection.handler, request], HTTP_FIRE_REQUEST
209
- else
210
- Plezi.error 'No Handler for this HTTP connection.'
211
- end
212
- end
213
-
214
- # read the body's data and parse any incoming data.
215
- def read_body
216
- # parse content
217
- case @parser_data['content-type'].to_s
218
- when /x-www-form-urlencoded/
219
- HTTP.extract_data @parser_body.split(/[&;]/), @parser_data[:params], :form # :uri
220
- when /multipart\/form-data/
221
- read_multipart @parser_data, @parser_body
222
- when /text\/xml/
223
- # to-do support xml?
224
- @parser_data[:body] = @parser_body.dup
225
- when /application\/json/
226
- @parser_data[:body] = @parser_body.dup
227
- JSON.parse(HTTP.make_utf8! @parser_data[:body]).each {|k, v| HTTP.add_param_to_hash k, v, @parser_data[:params]}
228
- else
229
- @parser_data[:body] = @parser_body.dup
230
- Plezi.error "POST body type (#{@parser_data['content-type']}) cannot be parsed. raw body is kept in the request's data as request[:body]: #{@parser_body}"
231
- end
232
- end
233
-
234
- # parse a mime/multipart body or part.
235
- def read_multipart headers, part, name_prefix = ''
236
- if headers['content-type'].to_s.match /multipart/
237
- boundry = headers['content-type'].match(/boundary=([^\s]+)/)[1]
238
- if headers['content-disposition'].to_s.match /name=/
239
- if name_prefix.empty?
240
- name_prefix << HTTP.decode(headers['content-disposition'].to_s.match(/name="([^"]*)"/)[1])
241
- else
242
- name_prefix << "[#{HTTP.decode(headers['content-disposition'].to_s.match(/name="([^"]*)"/)[1])}]"
243
- end
244
- end
245
- part.split(/([\r]?\n)?--#{boundry}(--)?[\r]?\n/).each do |p|
246
- unless p.strip.empty? || p=='--'
247
- # read headers
248
- h = {}
249
- p = p.lines
250
- while p[0].match(/^[^:]+:[^\r\n]+/)
251
- m = p.shift.match(/^([^:]+):[\s]?([^\r\n]+)/)
252
- h[m[1].downcase] = m[2]
253
- end
254
- if p[0].strip.empty?
255
- p.shift
256
- else
257
- Plezi.error 'Expected empty line after last header - empty line missing.'
258
- end
259
- # send headers and body to be read
260
- read_multipart h, p.join, name_prefix
261
- end
262
- end
263
- return
264
- end
265
-
266
- # require a part body to exist (data exists) for parsing
267
- return true if part.to_s.empty?
268
-
269
- # convert part to `charset` if charset is defined?
270
-
271
- if !headers['content-disposition']
272
- Plezi.error "Wrong multipart format with headers: #{headers} and body: #{part}"
273
- return
274
- end
275
-
276
- cd = {}
277
-
278
- HTTP.extract_data headers['content-disposition'].match(/[^;];([^\r\n]*)/)[1].split(/[;,][\s]?/), cd, :uri
279
-
280
- name = name_prefix.dup
281
-
282
- if name_prefix.empty?
283
- name << HTTP.decode(cd[:name][1..-2])
284
- else
285
- name << "[#{HTTP.decode(cd[:name][1..-2])}]"
286
- end
287
- if headers['content-type']
288
- HTTP.add_param_to_hash "#{name}[data]", part, @parser_data[:params]
289
- HTTP.add_param_to_hash "#{name}[type]", HTTP.make_utf8!(headers['content-type']), @parser_data[:params]
290
- cd.each {|k,v| HTTP.add_param_to_hash "#{name}[#{k.to_s}]", HTTP.make_utf8!(v[1..-2]), @parser_data[:params] unless k == :name}
291
- else
292
- HTTP.add_param_to_hash name, HTTP.decode(part, :utf8), @parser_data[:params]
293
- end
294
- true
295
- end
296
- end
297
- end
298
-
299
-
300
- # # HTTPProtocol - to do list
301
- #
302
- # XML::
303
- # support for XML HTTP body types?
304
- #
305
- # Charset::
306
- # parse chareset for incoming content-type in the multipart request body? (or leave if binary?)
307
-
308
-
309
-
310
- ## Heroku/extra headers info
311
-
312
- # All headers are considered to be case-insensitive, as per HTTP Specification.
313
- # X-Forwarded-For: the originating IP address of the client connecting to the Heroku router
314
- # X-Forwarded-Proto: the originating protocol of the HTTP request (example: https)
315
- # X-Forwarded-Port: the originating port of the HTTP request (example: 443)
316
- # X-Request-Start: unix timestamp (milliseconds) when the request was received by the router
317
- # X-Request-Id: the Heroku HTTP Request ID
318
- # Via: a code name for the Heroku router
319
-