plezi 0.7.5 → 0.7.6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 09b7229f402afa705079f518c81040b58b0bcdb9
4
- data.tar.gz: 051c27c22d072d67a071f131db12ff81f118b743
3
+ metadata.gz: ba4ae19c5a61ca23f12826d1e3eabcde4ca8ba21
4
+ data.tar.gz: 9fa3579011986dc14efbe679a16f867d54e92a2c
5
5
  SHA512:
6
- metadata.gz: 808a747010627be737fac767f277b650de4ae57ebd28cf3b3098a2b5c44790ddf0f69ffd1bec58ae4018fdb5e67f9953ad768890528627c450436416a0935e74
7
- data.tar.gz: 35f5e69c499cb04f1e1e23b3f647ad209f960dae41248ce059a70ad958dca3357dfc79319130854a34c4d5a6735d149933281b0757005395ceed4f424b0e4289
6
+ metadata.gz: 967cacbbf63c8fcaa6a136051d5894f8cc93a49f38d3d21495dd3cc2ab38bb21121f73dc37de46f96c4d51b5a2203b7c351d7eb6ece02405959b7224687b9cab
7
+ data.tar.gz: a66e08cb87f9742d756a3c59122f1422474a6a455c1e83bf49f9156cb51bad7d77c8a21a298a762fbe6ac8a7c4dc4983109c4937b11e4632ea66af4f22620277
data/CHANGELOG.md CHANGED
@@ -2,6 +2,21 @@
2
2
 
3
3
  ***
4
4
 
5
+ Change log v.0.7.7
6
+ (pre-release)
7
+
8
+ ***
9
+
10
+ Change log v.0.7.6
11
+
12
+ **performance**: minor performance improvements.
13
+
14
+ **API**: minor additions to the Plezi API, such as the `Plezi.run_async` method.
15
+
16
+ **fix**: Some HTTP refinements. for example, keep-alive headers are now enforced for all connections (not standard, but better performance). Patch requests are now pipelined to the controller's RESTful API.
17
+
18
+ ***
19
+
5
20
  Change log v.0.7.5
6
21
 
7
22
  **fix**: fixed an issue where form data might not be decoded correctly, resulting in remainin '+' signs that weren't properly converted to spaces.
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
  [![Gem Version](https://badge.fury.io/rb/plezi.svg)](http://badge.fury.io/rb/plezi)
3
3
  [![Inline docs](http://inch-ci.org/github/boazsegev/plezi.svg?branch=master)](http://inch-ci.org/github/boazsegev/plezi)
4
4
 
5
- > People who are serious about their frameworks, should make their own servers...
5
+ > People who are serious about their frameworks, should write their own servers...
6
6
 
7
7
  _(if to para-phrase "People who are serious about their software, should make their own hardware.")_
8
8
 
data/lib/plezi.rb CHANGED
@@ -12,6 +12,7 @@ require 'securerandom'
12
12
  require 'time'
13
13
  require 'json'
14
14
  require 'uri'
15
+ require 'set'
15
16
  ### version
16
17
 
17
18
  require "plezi/version"
@@ -70,40 +71,56 @@ end
70
71
 
71
72
 
72
73
  ##############################################################################
73
- # To make something new, we leap to the unknown.
74
- ##############################################################################
75
- # Plezi is a stand alone web services app, which supports RESTful HTTP, HTTP Streaming and WebSockets.
74
+ #
75
+ # Plezi is an easy to use Ruby Websocket Framework, with full RESTful routing support and HTTP streaming support. It's name comes from the word "fun" in Haitian, since Plezi is really fun to work with and it keeps our code clean and streamlined.
76
76
  #
77
77
  # Plezi is a wonderful alternative to Socket.io which makes writing the server using Ruby a breeze.
78
78
  #
79
- # Plezi routes accept Regexp's (regular exceptions) for route paths. for example:
79
+ # Plezi is multi-threaded by default (you can change this) and supports asynchronous callbacks,
80
+ # so it will keep going even if some requests take a while to compute.
81
+ #
82
+ # Plezi routes accept controller classes, which makes RESTful and WebSocket applications easy to write.
83
+ # For example, open your Ruby terminal (the `irb` command) and type:
80
84
  #
81
85
  # require 'plezi'
82
86
  # listen
83
- # route(/[.]*/) {|request, response| response << "Your request, master: #{request.path}."}
87
+ # route "*", Plezi::StubRESTCtrl
88
+ # exit # will start the service.
89
+ #
90
+ # Controller classes don't need to inherit anything :-)
84
91
  #
85
- # The catch-all route (/[.]*/) has a shortcut '*', so it's possible to write:
92
+ # Plezi's routing systems inherits the Controller class, allowing you more freedom in your code.
86
93
  #
87
94
  # require 'plezi'
95
+ # class MyController
96
+ # def index
97
+ # "Hello World!"
98
+ # end
99
+ # end
88
100
  # listen
89
- # route('*') {|request, response| response << "Your request, master: #{request.path}."}
101
+ # route "*", MyController
102
+ #
103
+ # exit # I'll stop writing this line every time.
90
104
  #
105
+ # Amazing(!), right? - You can read more in the documentation for Plezi::StubWSCtrl and Plezi::StubRESTCtrl, which are stub classes used for testing routes.
91
106
  #
92
- # Plezi accepts an optional class object that can be passed using the `route` command. Passing a class object is especially useful for RESTful and WebSocket applications.
93
- # read more at the Plezi::StubWSCtrl and Plezi::StubRESTCtrl documentation, which are stub classes used for testing routes.
107
+ # Plezi routes accept Regexp's (regular exceptions) for route paths.
108
+ # Plezi also accepts an optional block instead of the conrtoller Class object for example:
94
109
  #
95
110
  # require 'plezi'
96
111
  # listen
97
- # route "*", Plezi::StubRESTCtrl
112
+ # route(/[.]*/) {|request, response| response << "Your request, master: #{request.path}."}
113
+ #
114
+ # As you may have noticed before, the catch-all route (/[.]*/) has a shortcut: '*'.
98
115
  #
99
116
  # class routes that have a specific path (including root, but not a catch-all or Regexp path)
100
117
  # accept an implied `params[:id]` variable. the following path ('/'):
101
118
  #
102
119
  # require 'plezi'
103
120
  # listen
104
- # route "/", Plezi::StubRESTCtrl
105
- # # client requests: /1
106
- # # => Plezi::StubRESTCtrl.new.show() # where params[:id] == 1
121
+ # route "/", Plezi::StubWSCtrl
122
+ # # go to: http://localhost:3000/1
123
+ # # => Plezi::StubRESTCtrl.new.show() # where params[:id] == 1
107
124
  #
108
125
  # it is possible to use "magic" routes (i.e. `/resource/:type/(:id)/(:date){/[0-9]{8}}/:foo`) and it is also possible to set the appropriate parameters within the `before` method of the Conltroller.
109
126
  #
@@ -116,7 +133,30 @@ end
116
133
  # end
117
134
  # route('*') {|request, response| response.body << "Ahhh... I love cats!"}
118
135
  #
119
- # all the examples above shuold be good to run from irb. updated examples can be found at the Readme file in the Github project: https://github.com/boazsegev/plezi
136
+ # The Plezi module (also `PL`) also has methods to help with asynchronous tasking, callbacks, timers and customized shutdown cleanup.
137
+ #
138
+ # Heres a short demo fir asynchronous callbacks (works only while services are active and running):
139
+ #
140
+ # require 'plezi'
141
+ #
142
+ # def my_shutdown_proc time_start
143
+ # puts "Services were running for #{Time.now - time_start} ms."
144
+ # end
145
+ #
146
+ # # shutdown callbacks
147
+ # PL.on_shutdown(Kernel, :my_shutdown_proc, Time.now) { puts "this will run after shutdown." }
148
+ # PL.on_shutdown() { puts "this will run too." }
149
+ #
150
+ # # a timer
151
+ # PL.run_after 2, -> {puts "this will wait 2 seconds to run... too late. for this example"}
152
+ #
153
+ # # an asynchronous method call with an optional callback block
154
+ # PL.callback(Kernel, :puts, "Plezi will start eating our code once we exit terminal.") {puts 'first output finished'}
155
+ #
156
+ # # remember to exit to make it all start
157
+ # exit
158
+ #
159
+ # all the examples above shoold be good to run from irb. updated examples can be found at the Readme file in the Github project: https://github.com/boazsegev/plezi
120
160
  #
121
161
  # thanks to Russ Olsen for his ideas for a DSL and his blog post at:
122
162
  # http://www.jroller.com/rolsen/entry/building_a_dsl_in_ruby1
@@ -9,24 +9,8 @@ module Plezi
9
9
  end
10
10
  # Plezi event cycle settings: sets how many worker threads Plezi will run.
11
11
  def max_threads= value
12
- @max_threads = value
13
- end
14
-
15
- # Plezi event cycle settings: how long to wait for IO activity before forcing another cycle.
16
- #
17
- # No timing methods will be called during this interval.
18
- #
19
- # get the current idle setting
20
- def idle_sleep
21
- @idle_sleep ||= 0.1
22
- end
23
- # Plezi event cycle settings: how long to wait for IO activity before forcing another cycle.
24
- #
25
- # No timing methods will be called during this interval.
26
- #
27
- # set the current idle setting
28
- def idle_sleep= value
29
- @idle_sleep = value
12
+ raise "Plezi will hang and do nothing if there isn't at least one (1) working thread. Cannot set Plezi.max_threads = #{value}" if value.to_i <= 0
13
+ @max_threads = value.to_i
30
14
  end
31
15
 
32
16
  # Plezi Engine, DO NOT CALL. creates the thread pool and starts cycling through the events.
@@ -45,7 +29,7 @@ module Plezi
45
29
  # set signal tarps
46
30
  trap('INT'){ exit_flag = true; raise "close Plezi" }
47
31
  trap('TERM'){ exit_flag = true; raise "close Plezi" }
48
- puts 'Services running. Press ^C to stop'
32
+ puts "Services running Plezi version #{Plezi::VERSION}. Press ^C to stop"
49
33
  # sleep until trap raises exception (cycling might cause the main thread to ignor signals and lose attention)
50
34
  (sleep unless SERVICES.empty?) rescue true
51
35
  # start shutdown.
@@ -79,8 +63,8 @@ module Plezi
79
63
  true while fire_event
80
64
  fire_timers
81
65
 
82
- rescue Exception => e
83
-
84
- error e
66
+ # rescue Exception => e
67
+ # error e
68
+ # # raise if e.is_a?(SignalException) || e.is_a?(SystemExit)
85
69
  end
86
70
  end
@@ -33,6 +33,15 @@ module Plezi
33
33
  end
34
34
  end
35
35
 
36
+ # Public API. Runs the block asynchronously by pushin it as an event to the event's stack
37
+ #
38
+ # accepts a block to be executed asynchronously.
39
+ #
40
+ def run_async *args, &block
41
+ LOCKER.synchronize {EVENTS << [ block, args ]} if block
42
+ block
43
+ end
44
+
36
45
  # Public API. creates an asynchronous call to a method, with an optional callback:
37
46
  # demo use:
38
47
  # `callback( Kernel, :sleep, 1 ) { puts "this is a demo" }`
@@ -3,6 +3,27 @@ module Plezi
3
3
 
4
4
  module_function
5
5
 
6
+ # set the default idle waiting time.
7
+ @idle_sleep = 0.1
8
+
9
+ # Plezi event cycle settings: how long to wait for IO activity before forcing another cycle.
10
+ #
11
+ # No timing methods will be called during this interval.
12
+ #
13
+ # Gets the current idle setting. The default setting is 0.1 seconds.
14
+ def idle_sleep
15
+ @idle_sleep
16
+ end
17
+ # Plezi event cycle settings: how long to wait for IO activity before forcing another cycle.
18
+ #
19
+ # No timing methods will be called during this interval (#run_after / #run_every).
20
+ #
21
+ # It's rare, but it's one of the reasons for the timeout: some connections might wait for the timeout befor being established.
22
+ #
23
+ # set the current idle setting
24
+ def idle_sleep= value
25
+ @idle_sleep = value
26
+ end
6
27
 
7
28
  # DANGER ZONE - Plezi Engine. the io reactor mutex
8
29
  IO_LOCKER = Mutex.new
@@ -13,13 +34,13 @@ module Plezi
13
34
  return false unless EVENTS.empty?
14
35
  united = SERVICES.keys + IO_CONNECTION_DIC.keys
15
36
  return false if united.empty?
16
- io_r = (IO.select(united, nil, united, idle_sleep) ) #rescue false)
37
+ io_r = (IO.select(united, nil, united, @idle_sleep) ) #rescue false)
17
38
  if io_r
18
39
  io_r[0].each do |io|
19
40
  if SERVICES[io]
20
41
  begin
21
42
  connection = io.accept_nonblock
22
- callback Plezi, :add_connection, connection, SERVICES[io]
43
+ push_event method(:add_connection), connection, SERVICES[io]
23
44
  rescue Errno::EWOULDBLOCK => e
24
45
 
25
46
  rescue Exception => e
@@ -27,7 +48,7 @@ module Plezi
27
48
  # SERVICES.delete s if s.closed?
28
49
  end
29
50
  elsif IO_CONNECTION_DIC[io]
30
- callback(IO_CONNECTION_DIC[io], :on_message)
51
+ push_event IO_CONNECTION_DIC[io].method(:on_message)
31
52
  else
32
53
  IO_CONNECTION_DIC.delete(io)
33
54
  SERVICES.delete(io)
@@ -21,10 +21,8 @@ module Plezi
21
21
  # log_file:: a log file name to be used for logging
22
22
  # copy_to_stdout:: if false, log will only log to file. defaults to true.
23
23
  def create_logger log_file = STDOUT, copy_to_stdout = false
24
- @copy_to_stdout = false
25
- @copy_to_stdout = ::Logger.new(STDOUT) if copy_to_stdout
24
+ @copy_to_stdout = ( copy_to_stdout ? (::Logger.new(STDOUT)) : false )
26
25
  @logger = ::Logger.new(log_file)
27
- @logger
28
26
  end
29
27
  alias :set_logger :create_logger
30
28
 
@@ -15,12 +15,12 @@ module Plezi
15
15
  Object.const_set('PLEZI_ON_RACK', true) unless defined? PLEZI_ON_RACK
16
16
 
17
17
  # re-encode to utf-8, as it's all BINARY encoding at first
18
- env["rack.input"].rewind
19
- env['rack.input'] = StringIO.new env["rack.input"].read.encode("utf-8", "binary", invalid: :replace, undef: :replace, replace: '')
18
+ env['rack.input'].rewind
19
+ env['rack.input'] = StringIO.new env['rack.input'].read.encode('utf-8', 'binary', invalid: :replace, undef: :replace, replace: '')
20
20
  env.each do |k, v|
21
21
  if k.to_s.match /^[A-Z]/
22
22
  if v.is_a?(String) && !v.frozen?
23
- v.force_encoding("binary").encode!("utf-8", "binary", invalid: :replace, undef: :replace, replace: '') unless v.force_encoding("utf-8").valid_encoding?
23
+ v.force_encoding('binary').encode!('utf-8', 'binary', invalid: :replace, undef: :replace, replace: '') unless v.force_encoding('utf-8').valid_encoding?
24
24
  end
25
25
  end
26
26
  end
@@ -33,11 +33,11 @@ module Plezi
33
33
  make_hash_accept_symbols(env)
34
34
 
35
35
  # use Plezi Cookies
36
- env["rack.request.cookie_string"] = env["HTTP_COOKIE"]
37
- env["rack.request.cookie_hash"] = Plezi::Cookies.new.update(env["rack.request.cookie_hash"] || {})
36
+ env['rack.request.cookie_string'] = env['HTTP_COOKIE']
37
+ env['rack.request.cookie_hash'] = Plezi::Cookies.new.update(env['rack.request.cookie_hash'] || {})
38
38
 
39
39
  # chomp path
40
- env["PATH_INFO"].chomp! '/'
40
+ env['PATH_INFO'].chomp! '/'
41
41
 
42
42
  # get response
43
43
  response = Plezi::SERVICES[0][1][:handler].call env
@@ -51,8 +51,8 @@ module Plezi
51
51
  headers.delete 'transfer-encoding'
52
52
  headers.delete 'connection'
53
53
  unless response.cookies.empty?
54
- headers["Set-Cookie"] = []
55
- response.cookies.each {|k,v| headers["Set-Cookie"] << ("#{k.to_s}=#{v.to_s}")}
54
+ headers['Set-Cookie'] = []
55
+ response.cookies.each {|k,v| headers['Set-Cookie'] << ("#{k.to_s}=#{v.to_s}")}
56
56
  end
57
57
  [response.status, headers, response.body]
58
58
  end
@@ -106,16 +106,16 @@ module Plezi
106
106
  response << data
107
107
 
108
108
  # set headers
109
- content_disposition = "attachment"
109
+ content_disposition = 'attachment'
110
110
 
111
111
  options[:type] ||= MimeTypeHelper::MIME_DICTIONARY[::File.extname(options[:filename])] if options[:filename]
112
112
 
113
113
  if options[:type]
114
- response["content-type"] = options[:type]
114
+ response['content-type'] = options[:type]
115
115
  options.delete :type
116
116
  end
117
117
  if options[:inline]
118
- content_disposition = "inline"
118
+ content_disposition = 'inline'
119
119
  options.delete :inline
120
120
  end
121
121
  if options[:attachment]
@@ -125,8 +125,8 @@ module Plezi
125
125
  content_disposition << "; filename=#{options[:filename]}"
126
126
  options.delete :filename
127
127
  end
128
- response["content-length"] = data.bytesize rescue true
129
- response["content-disposition"] = content_disposition
128
+ response['content-length'] = data.bytesize rescue true
129
+ response['content-disposition'] = content_disposition
130
130
  response.finish
131
131
  true
132
132
  end
@@ -189,7 +189,7 @@ module Plezi
189
189
  # respond to websocket special case
190
190
  return :pre_connect if request['upgrade'] && request['upgrade'].to_s.downcase == 'websocket' && request['connection'].to_s.downcase == 'upgrade'
191
191
  # respond to save 'new' special case
192
- return :save if request.request_method.match(/POST|PUT/) && params[:id].nil? || params[:id] == 'new'
192
+ return :save if request.request_method.match(/POST|PUT|PATCH/) && params[:id].nil? || params[:id] == 'new'
193
193
  # set DELETE method if simulated
194
194
  request.request_method = 'DELETE' if params[:_method].to_s.downcase == 'delete'
195
195
  # respond to special :id routing
@@ -199,7 +199,7 @@ module Plezi
199
199
  when 'GET', 'HEAD'
200
200
  return :index unless params[:id]
201
201
  return :show
202
- when 'POST', 'PUT'
202
+ when 'POST', 'PUT', 'PATCH'
203
203
  return :update
204
204
  when 'DELETE'
205
205
  return :delete
@@ -270,19 +270,19 @@ module Plezi
270
270
  # lists the available methods that will be exposed to HTTP requests
271
271
  def available_public_methods
272
272
  # set class global to improve performance while checking for supported methods
273
- Plezi.cached?(self.superclass.name + "_p&rt") ? Plezi.get_cached(self.superclass.name + "_p&rt") : Plezi.cache_data(self.superclass.name + "_p&rt", available_routing_methods - [:before, :after, :save, :show, :update, :delete, :initialize, :on_message, :pre_connect, :on_connect, :on_disconnect])
273
+ Plezi.cached?(self.superclass.name + '_p&rt') ? Plezi.get_cached(self.superclass.name + "_p&rt") : Plezi.cache_data(self.superclass.name + "_p&rt", (available_routing_methods - [:before, :after, :save, :show, :update, :delete, :initialize, :on_message, :pre_connect, :on_connect, :on_disconnect]).to_set )
274
274
  end
275
275
 
276
276
  # lists the available methods that will be exposed to the HTTP router
277
277
  def available_routing_methods
278
278
  # set class global to improve performance while checking for supported methods
279
- Plezi.cached?(self.superclass.name + "_r&rt") ? Plezi.get_cached(self.superclass.name + "_r&rt") : Plezi.cache_data(self.superclass.name + "_r&rt", (((public_instance_methods - Object.public_instance_methods) - Plezi::ControllerMagic::InstanceMethods.instance_methods).delete_if {|m| m.to_s[0] == '_'}) )
279
+ Plezi.cached?(self.superclass.name + '_r&rt') ? Plezi.get_cached(self.superclass.name + "_r&rt") : Plezi.cache_data(self.superclass.name + "_r&rt", (((public_instance_methods - Object.public_instance_methods) - Plezi::ControllerMagic::InstanceMethods.instance_methods).delete_if {|m| m.to_s[0] == '_'}).to_set )
280
280
  end
281
281
 
282
282
  # resets this controller's router, to allow for dynamic changes
283
283
  def reset_routing_cache
284
- Plezi.clear_cached(self.superclass.name + "_p&rt")
285
- Plezi.clear_cached(self.superclass.name + "_r&rt")
284
+ Plezi.clear_cached(self.superclass.name + '_p&rt')
285
+ Plezi.clear_cached(self.superclass.name + '_r&rt')
286
286
  available_routing_methods
287
287
  available_public_methods
288
288
  end
@@ -329,10 +329,10 @@ module Plezi
329
329
  # raise "Redis connction failed for: #{ENV['PL_REDIS_URL']}" unless @@redis
330
330
  # @@redis
331
331
  return false unless defined?(Redis) && ENV['PL_REDIS_URL']
332
- return Plezi.get_cached(self.superclass.name + "_b") if Plezi.cached?(self.superclass.name + "_b")
332
+ return Plezi.get_cached(self.superclass.name + '_b') if Plezi.cached?(self.superclass.name + '_b')
333
333
  @@redis_uri ||= URI.parse(ENV['PL_REDIS_URL'])
334
- Plezi.cache_data self.superclass.name + "_b", Redis.new(host: @@redis_uri.host, port: @@redis_uri.port, password: @@redis_uri.password)
335
- raise "Redis connction failed for: #{ENV['PL_REDIS_URL']}" unless Plezi.cached?(self.superclass.name + "_b")
334
+ Plezi.cache_data self.superclass.name + '_b', Redis.new(host: @@redis_uri.host, port: @@redis_uri.port, password: @@redis_uri.password)
335
+ raise "Redis connction failed for: #{ENV['PL_REDIS_URL']}" unless Plezi.cached?(self.superclass.name + '_b')
336
336
  t = Thread.new do
337
337
  begin
338
338
  Redis.new(host: @@redis_uri.host, port: @@redis_uri.port, password: @@redis_uri.password).subscribe(redis_channel_name) do |on|
@@ -347,8 +347,8 @@ module Plezi
347
347
  retry
348
348
  end
349
349
  end
350
- Plezi.cache_data self.superclass.name + "_t", t
351
- Plezi.get_cached(self.superclass.name + "_b")
350
+ Plezi.cache_data self.superclass.name + '_t', t
351
+ Plezi.get_cached(self.superclass.name + '_b')
352
352
  end
353
353
 
354
354
  # returns a Redis channel name for this controller.
@@ -6,7 +6,7 @@ module Plezi
6
6
 
7
7
  # handles requests by printing out the parsed data. gets the `request` parameter from the HTTP protocol.
8
8
  def on_request request
9
- response = HTTPResponse.new request, 200, {"content-type" => "text/plain"}, ["parsed as:\r\n", request.to_s]
9
+ response = HTTPResponse.new request, 200, {'content-type' => 'text/plain'}, ["parsed as:\r\n", request.to_s]
10
10
  response.body.last << "\n\n params:"
11
11
  request.params.each {|k,v| response.body.last << "\n#{k}: #{v}"}
12
12
  response.send
@@ -108,7 +108,7 @@ module Plezi
108
108
  return send_file(request, File.join(params[:root], "#{code}.html"), code, headers)
109
109
  end
110
110
  end
111
- return true if send_raw_data(request, HTTPResponse::STATUS_CODES[code], "text/plain", code, headers)
111
+ return true if send_raw_data(request, HTTPResponse::STATUS_CODES[code], 'text/plain', code, headers)
112
112
  rescue Exception => e
113
113
  Plezi.error e
114
114
  end
@@ -50,18 +50,18 @@ module Plezi
50
50
  elsif hosts[:default]
51
51
  hosts[:default].on_request request
52
52
  else
53
- HTTPResponse.new( request, 404, {"content-type" => "text/plain", "content-length" => "15"}, ["host not found."]).finish
53
+ HTTPResponse.new( request, 404, {'content-type' => 'text/plain', 'content-length' => '15'}, ['host not found.']).finish
54
54
  end
55
55
  request.service.timeout = 5
56
56
  end
57
57
  # handles requests send by Rack - dresses up as Middleware, for Rack (if you're don't like WebSockets, go ahead...)
58
58
  def call env
59
- if env["HOST"] && hosts[env["HOST"].downcase]
60
- hosts[env["HOST"].downcase].call env
59
+ if env['HOST'] && hosts[env['HOST'].downcase]
60
+ hosts[env['HOST'].downcase].call env
61
61
  elsif hosts[:default]
62
62
  hosts[:default].call env
63
63
  else
64
- [404, {"content-type" => "text/plain", "content-length" => "15"}, ["host not found."] ]
64
+ [404, {'content-type' => 'text/plain', 'content-length' => '15'}, ['host not found.'] ]
65
65
  end
66
66
  end
67
67
  end
@@ -103,6 +103,7 @@ module Plezi
103
103
  # data is a string that contains binary or UTF8 (message dependent) data.
104
104
  def on_message data
105
105
  broadcast :_push, data
106
+ _push "your message was sent: #{data.to_s}"
106
107
  end
107
108
 
108
109
  # called when a disconnect packet has been recieved or the connection has been cut
@@ -116,7 +117,7 @@ module Plezi
116
117
  # BUT, broadcasted methods must be public (or the broadcast will quietly fail)... so we have to use
117
118
  # the _underscore for this method.
118
119
  def _push data
119
- response << data
120
+ response << data.to_s
120
121
  end
121
122
 
122
123
  #####
@@ -125,7 +126,7 @@ module Plezi
125
126
 
126
127
  # called when request is GET and params\[:id] isn't defined
127
128
  def index
128
- {message: 'index', data: {id: nil, token: nil}}.to_json
129
+ "This stub controller is used to test websockets.\n\r\n\rVisit http://www.websocket.org/echo.html for WS testing.\n\r\n\rOr add a nickname to the route to view long-pulling stub. i.e.: #{request.base_url}/nickname"
129
130
  end
130
131
 
131
132
  # called when request is GET and params\[:id] exists (unless params\[:id] == "new").
@@ -99,8 +99,8 @@ module Plezi
99
99
  when :html
100
100
  object.gsub!(/&amp;/i, '&')
101
101
  object.gsub!(/&quot;/i, '"')
102
- object.gsub!(/&gt;/i, ">")
103
- object.gsub!(/&lt;/i, "<")
102
+ object.gsub!(/&gt;/i, '>')
103
+ object.gsub!(/&lt;/i, '<')
104
104
  when :utf8
105
105
 
106
106
  else
@@ -126,19 +126,19 @@ module Plezi
126
126
  elsif object.is_a?(String)
127
127
  case decode_method
128
128
  when :uri, :url, :form
129
- object.force_encoding "binary"
129
+ object.force_encoding 'binary'
130
130
  object.gsub!(/[^a-zA-Z0-9\*\.\_\-]/) {|m| m.ord <= 16 ? "%0#{m.ord.to_s(16)}" : "%#{m.ord.to_s(16)}"}
131
131
  when :html
132
- object.gsub!('&', "&amp;")
133
- object.gsub!('"', "&quot;")
134
- object.gsub!(">", "&gt;")
135
- object.gsub!("<", "&lt;")
136
- object.gsub!(/[^\sa-zA-Z\d\&\;]/) {|m| "&#%04d;" % m.unpack('U')[0] }
132
+ object.gsub!('&', '&amp;')
133
+ object.gsub!('"', '&quot;')
134
+ object.gsub!('>', '&gt;')
135
+ object.gsub!('<', '&lt;')
136
+ object.gsub!(/[^\sa-zA-Z\d\&\;]/) {|m| '&#%04d;' % m.unpack('U')[0] }
137
137
  # object.gsub!(/[^\s]/) {|m| "&#%04d;" % m.unpack('U')[0] }
138
- object.force_encoding "binary"
138
+ object.force_encoding 'binary'
139
139
  when :utf8
140
- object.gsub!(/[^\sa-zA-Z\d]/) {|m| "&#%04d;" % m.unpack('U')[0] }
141
- object.force_encoding "binary"
140
+ object.gsub!(/[^\sa-zA-Z\d]/) {|m| '&#%04d;' % m.unpack('U')[0] }
141
+ object.force_encoding 'binary'
142
142
  else
143
143
 
144
144
  end
@@ -163,7 +163,7 @@ module Plezi
163
163
  # re-encodes a string into UTF-8
164
164
  def make_utf8!(string, encoding= 'utf-8')
165
165
  return false unless string
166
- string.force_encoding("binary").encode!(encoding, "binary", invalid: :replace, undef: :replace, replace: '') unless string.force_encoding(encoding).valid_encoding?
166
+ string.force_encoding('binary').encode!(encoding, 'binary', invalid: :replace, undef: :replace, replace: '') unless string.force_encoding(encoding).valid_encoding?
167
167
  string
168
168
  end
169
169
 
@@ -7,6 +7,7 @@ module Plezi
7
7
  class HTTPProtocol
8
8
 
9
9
  HTTP_METHODS = %w{GET HEAD POST PUT DELETE TRACE OPTIONS}
10
+ HTTP_METHODS_REGEXP = /^#{HTTP_METHODS.join('|')}/
10
11
 
11
12
  attr_accessor :service
12
13
 
@@ -18,7 +19,7 @@ module Plezi
18
19
  @parser_chunk = ''
19
20
  @parser_length = 0
20
21
  @locker = Mutex.new
21
- @@rack_dictionary ||= {"HOST".freeze => :host_name, 'REQUEST_METHOD'.freeze => :method,
22
+ @@rack_dictionary ||= {'HOST'.freeze => :host_name, 'REQUEST_METHOD'.freeze => :method,
22
23
  'PATH_INFO'.freeze => :path, 'QUERY_STRING'.freeze => :query,
23
24
  'SERVER_NAME'.freeze => :host_name, 'SERVER_PORT'.freeze => :port,
24
25
  'rack.url_scheme'.freeze => :requested_protocol}
@@ -76,7 +77,7 @@ module Plezi
76
77
 
77
78
  # parses the method request (the first line in the HTTP request).
78
79
  def parse_method data
79
- return false unless data[0] && data[0].match(/^#{HTTP_METHODS.join('|')}/)
80
+ return false unless data[0] && data[0].match(HTTP_METHODS_REGEXP)
80
81
  @parser_data[:time_recieved] = Time.now
81
82
  @parser_data[:params] = {}
82
83
  @parser_data[:cookies] = Cookies.new
@@ -110,7 +111,7 @@ module Plezi
110
111
  end
111
112
  return false unless data[0]
112
113
  data.shift while data[0] && data[0].match(/^[\r\n]+$/)
113
- if @parser_data["transfer-coding"] || (@parser_data["content-length"] && @parser_data["content-length"].to_i != 0) || @parser_data["content-type"]
114
+ if @parser_data['transfer-coding'] || (@parser_data['content-length'] && @parser_data['content-length'].to_i != 0) || @parser_data['content-type']
114
115
  @parser_stage = 2
115
116
  else
116
117
  # create request object and hand over to handler
@@ -123,7 +124,7 @@ module Plezi
123
124
  #parses the body of a request.
124
125
  def parse_body data
125
126
  # check for body is needed, if exists and if complete
126
- if @parser_data["transfer-coding"] == "chunked"
127
+ if @parser_data['transfer-coding'] == 'chunked'
127
128
  until data.empty? || data[0].to_s.match(/0(\r)?\n/)
128
129
  if @parser_length == 0
129
130
  @parser_length = data.to_s.shift.match(/^[a-z0-9A-Z]+/).to_i(16)
@@ -141,8 +142,8 @@ module Plezi
141
142
  return false unless data[0].to_s.match(/0(\r)?\n/)
142
143
  true until data.empty? || data.shift.match(/^[\r\n]+$/)
143
144
  data.shift while data[0].to_s.match /^[\r\n]+$/
144
- elsif @parser_data["content-length"].to_i
145
- @parser_length = @parser_data["content-length"].to_i if @parser_length == 0
145
+ elsif @parser_data['content-length'].to_i
146
+ @parser_length = @parser_data['content-length'].to_i if @parser_length == 0
146
147
  @parser_chunk << data.shift while @parser_length > @parser_chunk.bytesize && data[0]
147
148
  return false if @parser_length > @parser_chunk.bytesize
148
149
  @parser_body = @parser_chunk.byteslice(0, @parser_length)
@@ -206,13 +207,13 @@ module Plezi
206
207
 
207
208
  #check for server-responses
208
209
  case request.request_method
209
- when "TRACE"
210
+ when 'TRACE'
210
211
  return true
211
- when "OPTIONS"
212
+ when 'OPTIONS'
212
213
  Plezi.push_event Proc.new do
213
214
  response = HTTPResponse.new request
214
- response[:Allow] = "GET,HEAD,POST,PUT,DELETE,OPTIONS"
215
- response["access-control-allow-origin"] = "*"
215
+ response[:Allow] = 'GET,HEAD,POST,PUT,DELETE,OPTIONS'
216
+ response['access-control-allow-origin'] = '*'
216
217
  response['content-length'] = 0
217
218
  response.finish
218
219
  end
@@ -223,14 +224,14 @@ module Plezi
223
224
  if service && service.handler
224
225
  Plezi.callback service.handler, :on_request, request
225
226
  else
226
- Plezi.error "No Handler for this HTTP service."
227
+ Plezi.error 'No Handler for this HTTP service.'
227
228
  end
228
229
  end
229
230
 
230
231
  # read the body's data and parse any incoming data.
231
232
  def read_body
232
233
  # parse content
233
- case @parser_data["content-type"].to_s
234
+ case @parser_data['content-type'].to_s
234
235
  when /x-www-form-urlencoded/
235
236
  HTTP.extract_data @parser_body.split(/[&;]/), @parser_data[:params], :form # :uri
236
237
  when /multipart\/form-data/
@@ -243,19 +244,19 @@ module Plezi
243
244
  JSON.parse(HTTP.make_utf8! @parser_data[:body]).each {|k, v| HTTP.add_param_to_hash k, v, @parser_data[:params]}
244
245
  else
245
246
  @parser_data[:body] = @parser_body.dup
246
- 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}"
247
+ 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}"
247
248
  end
248
249
  end
249
250
 
250
251
  # parse a mime/multipart body or part.
251
252
  def read_multipart headers, part, name_prefix = ''
252
- if headers["content-type"].to_s.match /multipart/
253
- boundry = headers["content-type"].match(/boundary=([^\s]+)/)[1]
254
- if headers["content-disposition"].to_s.match /name=/
253
+ if headers['content-type'].to_s.match /multipart/
254
+ boundry = headers['content-type'].match(/boundary=([^\s]+)/)[1]
255
+ if headers['content-disposition'].to_s.match /name=/
255
256
  if name_prefix.empty?
256
- name_prefix << HTTP.decode(headers["content-disposition"].to_s.match(/name="([^"]*)"/)[1])
257
+ name_prefix << HTTP.decode(headers['content-disposition'].to_s.match(/name="([^"]*)"/)[1])
257
258
  else
258
- name_prefix << "[#{HTTP.decode(headers["content-disposition"].to_s.match(/name="([^"]*)"/)[1])}]"
259
+ name_prefix << "[#{HTTP.decode(headers['content-disposition'].to_s.match(/name="([^"]*)"/)[1])}]"
259
260
  end
260
261
  end
261
262
  part.split(/([\r]?\n)?--#{boundry}(--)?[\r]?\n/).each do |p|
@@ -284,14 +285,14 @@ module Plezi
284
285
 
285
286
  # convert part to `charset` if charset is defined?
286
287
 
287
- if !headers["content-disposition"]
288
+ if !headers['content-disposition']
288
289
  Plezi.error "Wrong multipart format with headers: #{headers} and body: #{part}"
289
290
  return
290
291
  end
291
292
 
292
293
  cd = {}
293
294
 
294
- HTTP.extract_data headers["content-disposition"].match(/[^;];([^\r\n]*)/)[1].split(/[;,][\s]?/), cd, :uri
295
+ HTTP.extract_data headers['content-disposition'].match(/[^;];([^\r\n]*)/)[1].split(/[;,][\s]?/), cd, :uri
295
296
 
296
297
  name = name_prefix.dup
297
298
 
@@ -300,9 +301,9 @@ module Plezi
300
301
  else
301
302
  name << "[#{HTTP.decode(cd[:name][1..-2])}]"
302
303
  end
303
- if headers["content-type"]
304
+ if headers['content-type']
304
305
  HTTP.add_param_to_hash "#{name}[data]", part, @parser_data[:params]
305
- HTTP.add_param_to_hash "#{name}[type]", HTTP.make_utf8!(headers["content-type"]), @parser_data[:params]
306
+ HTTP.add_param_to_hash "#{name}[type]", HTTP.make_utf8!(headers['content-type']), @parser_data[:params]
306
307
  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}
307
308
  else
308
309
  HTTP.add_param_to_hash name, HTTP.decode(part, :utf8), @parser_data[:params]
@@ -110,6 +110,14 @@ module Plezi
110
110
  def patch?
111
111
  self[:method] == 'PATCH'
112
112
  end
113
+ # returns true if the request is of type JSON.
114
+ def json?
115
+ self['content-type'].match /application\/json/
116
+ end
117
+ # returns true if the request is of type XML.
118
+ def xml?
119
+ self['content-type'].match /text\/xml/
120
+ end
113
121
 
114
122
  end
115
123
  end
@@ -43,7 +43,7 @@ module Plezi
43
43
  hs["plezi_flash_#{k.to_s}".to_sym] if hs.has_key? "plezi_flash_#{k.to_s}".to_sym
44
44
  end
45
45
  request.cookies.each do |k,v|
46
- @flash[k] = v if k.to_s.start_with? "plezi_flash_"
46
+ @flash[k] = v if k.to_s.start_with? 'plezi_flash_'
47
47
  end
48
48
  end
49
49
 
@@ -122,14 +122,14 @@ module Plezi
122
122
  params[:path] ||= '/'
123
123
  value = HTTP.encode(value.to_s)
124
124
  if params[:max_age]
125
- value << ("; Max-Age=%s" % params[:max_age])
125
+ value << ('; Max-Age=%s' % params[:max_age])
126
126
  else
127
- value << ("; Expires=%s" % params[:expires].httpdate)
127
+ value << ('; Expires=%s' % params[:expires].httpdate)
128
128
  end
129
129
  value << "; Path=#{params[:path]}"
130
130
  value << "; Domain=#{params[:domain]}" if params[:domain]
131
- value << "; Secure" if params[:secure]
132
- value << "; HttpOnly" if params[:http_only]
131
+ value << '; Secure' if params[:secure]
132
+ value << '; HttpOnly' if params[:http_only]
133
133
  @cookies[HTTP.encode(name.to_s).to_sym] = value
134
134
  end
135
135
 
@@ -155,7 +155,7 @@ module Plezi
155
155
  body << str if str && body.is_a?(Array)
156
156
  send_headers
157
157
  return if request.head?
158
- if headers["transfer-encoding"] == "chunked"
158
+ if headers['transfer-encoding'] == 'chunked'
159
159
  body.each do |s|
160
160
  service.send "#{s.bytesize.to_s(16)}\r\n"
161
161
  service.send s
@@ -177,8 +177,9 @@ module Plezi
177
177
  return self if defined?(PLEZI_ON_RACK)
178
178
  raise 'HTTPResponse SERVICE MISSING: cannot send http response without a service.' unless service
179
179
  self.send
180
- service.send( (headers["transfer-encoding"] == "chunked") ? "0\r\n\r\n" : nil)
180
+ service.send( (headers['transfer-encoding'] == 'chunked') ? "0\r\n\r\n" : nil)
181
181
  @finished = true
182
+ # service.disconnect unless headers['keep-alive']
182
183
  # log
183
184
  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"
184
185
  end
@@ -190,13 +191,14 @@ module Plezi
190
191
 
191
192
  # Danger Zone (internally used method, use with care): fix response's headers before sending them (date, connection and transfer-coding).
192
193
  def fix_headers
193
- # headers['Connection'] ||= "Keep-Alive"
194
+ headers['connection'] = "Keep-Alive"
195
+ headers['keep-alive'] = "timeout=5"
194
196
  headers['date'] = Time.now.httpdate
195
197
  headers['transfer-encoding'] ||= 'chunked' if !headers['content-length']
196
198
  headers['cache-control'] ||= 'no-cache'
197
199
  # remove old flash cookies
198
200
  request.cookies.keys.each do |k|
199
- if k.to_s.start_with? "plezi_flash_"
201
+ if k.to_s.start_with? 'plezi_flash_'
200
202
  set_cookie k, nil
201
203
  flash.delete k
202
204
  end
@@ -45,7 +45,7 @@ module Plezi
45
45
  service.timeout = @timeout_interval
46
46
  # Plezi.callback service, :timeout=, @timeout_interval
47
47
  Plezi.callback @service.handler, :on_connect if @service.handler.methods.include?(:on_connect)
48
- Plezi.info "Upgraded HTTP to WebSockets. Logging only errors."
48
+ Plezi.info 'Upgraded HTTP to WebSockets. Logging only errors.'
49
49
  end
50
50
 
51
51
  # called when data is recieved
data/lib/plezi/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Plezi
2
- VERSION = "0.7.5"
2
+ VERSION = "0.7.6"
3
3
  end
@@ -18,17 +18,21 @@ if defined? Redis
18
18
  # ENV['PL_REDIS_URL'] = ENV['REDISCLOUD_URL'] ||= ENV["REDISTOGO_URL"] ||= "redis://username:password@my.host:6389"
19
19
 
20
20
 
21
+ # ## Custom Redis Automation
22
+ # ## ====
23
+ # ##
21
24
  # ## create a listening thread - rewrite the following code for your own Redis tailored solution.
22
25
  # ##
23
26
  # ## the following is only sample code for you to change:
24
- # RADIS_CHANNEL = appsecret
27
+ # RADIS_CHANNEL = 'appsecret'
25
28
  # RADIS_URI = URI.parse(ENV['REDISCLOUD_URL'] || "redis://username:password@my.host:6389")
26
29
  # RADIS_CONNECTION = Redis.new(host: RADIS_URI.host, port: RADIS_URI.port, password: RADIS_URI.password)
27
30
  # RADIS_THREAD = Thread.new do
28
31
  # Redis.new(host: RADIS_URI.host, port: RADIS_URI.port, password: RADIS_URI.password).subscribe(RADIS_CHANNEL) do |on|
29
32
  # on.message do |channel, msg|
30
33
  # msg = JSON.parse(msg)
31
- # # do stuff
34
+ # # do stuff, i.e.:
35
+ # # Plezi.run_async(msg) { |m| Plezi.info m.to_s }
32
36
  # end
33
37
  # end
34
38
  # end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: plezi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.5
4
+ version: 0.7.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Boaz Segev
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-03-29 00:00:00.000000000 Z
11
+ date: 2015-05-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler