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 +4 -4
- data/CHANGELOG.md +15 -0
- data/README.md +1 -1
- data/lib/plezi.rb +54 -14
- data/lib/plezi/base/engine.rb +6 -22
- data/lib/plezi/base/events.rb +9 -0
- data/lib/plezi/base/io_reactor.rb +24 -3
- data/lib/plezi/base/logging.rb +1 -3
- data/lib/plezi/base/rack_app.rb +8 -8
- data/lib/plezi/handlers/controller_magic.rb +16 -16
- data/lib/plezi/handlers/http_echo.rb +1 -1
- data/lib/plezi/handlers/http_host.rb +1 -1
- data/lib/plezi/handlers/http_router.rb +4 -4
- data/lib/plezi/handlers/stubs.rb +3 -2
- data/lib/plezi/server/helpers/http.rb +12 -12
- data/lib/plezi/server/protocols/http_protocol.rb +23 -22
- data/lib/plezi/server/protocols/http_request.rb +8 -0
- data/lib/plezi/server/protocols/http_response.rb +11 -9
- data/lib/plezi/server/protocols/websocket.rb +1 -1
- data/lib/plezi/version.rb +1 -1
- data/resources/redis_config.rb +6 -2
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ba4ae19c5a61ca23f12826d1e3eabcde4ca8ba21
|
4
|
+
data.tar.gz: 9fa3579011986dc14efbe679a16f867d54e92a2c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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
|
-
#
|
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
|
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
|
87
|
+
# route "*", Plezi::StubRESTCtrl
|
88
|
+
# exit # will start the service.
|
89
|
+
#
|
90
|
+
# Controller classes don't need to inherit anything :-)
|
84
91
|
#
|
85
|
-
#
|
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
|
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
|
93
|
-
#
|
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 "
|
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::
|
105
|
-
# #
|
106
|
-
# #
|
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
|
-
#
|
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
|
data/lib/plezi/base/engine.rb
CHANGED
@@ -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
|
-
|
13
|
-
|
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
|
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
|
-
|
66
|
+
# rescue Exception => e
|
67
|
+
# error e
|
68
|
+
# # raise if e.is_a?(SignalException) || e.is_a?(SystemExit)
|
85
69
|
end
|
86
70
|
end
|
data/lib/plezi/base/events.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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)
|
data/lib/plezi/base/logging.rb
CHANGED
@@ -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
|
|
data/lib/plezi/base/rack_app.rb
CHANGED
@@ -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[
|
19
|
-
env['rack.input'] = StringIO.new env[
|
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(
|
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[
|
37
|
-
env[
|
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[
|
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[
|
55
|
-
response.cookies.each {|k,v| headers[
|
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 =
|
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[
|
114
|
+
response['content-type'] = options[:type]
|
115
115
|
options.delete :type
|
116
116
|
end
|
117
117
|
if options[:inline]
|
118
|
-
content_disposition =
|
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[
|
129
|
-
response[
|
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 +
|
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 +
|
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 +
|
285
|
-
Plezi.clear_cached(self.superclass.name +
|
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 +
|
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 +
|
335
|
-
raise "Redis connction failed for: #{ENV['PL_REDIS_URL']}" unless Plezi.cached?(self.superclass.name +
|
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 +
|
351
|
-
Plezi.get_cached(self.superclass.name +
|
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, {
|
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],
|
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, {
|
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[
|
60
|
-
hosts[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, {
|
64
|
+
[404, {'content-type' => 'text/plain', 'content-length' => '15'}, ['host not found.'] ]
|
65
65
|
end
|
66
66
|
end
|
67
67
|
end
|
data/lib/plezi/handlers/stubs.rb
CHANGED
@@ -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
|
-
|
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!(/&/i, '&')
|
101
101
|
object.gsub!(/"/i, '"')
|
102
|
-
object.gsub!(/>/i,
|
103
|
-
object.gsub!(/</i,
|
102
|
+
object.gsub!(/>/i, '>')
|
103
|
+
object.gsub!(/</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
|
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!('&',
|
133
|
-
object.gsub!('"',
|
134
|
-
object.gsub!(
|
135
|
-
object.gsub!(
|
136
|
-
object.gsub!(/[^\sa-zA-Z\d\&\;]/) {|m|
|
132
|
+
object.gsub!('&', '&')
|
133
|
+
object.gsub!('"', '"')
|
134
|
+
object.gsub!('>', '>')
|
135
|
+
object.gsub!('<', '<')
|
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
|
138
|
+
object.force_encoding 'binary'
|
139
139
|
when :utf8
|
140
|
-
object.gsub!(/[^\sa-zA-Z\d]/) {|m|
|
141
|
-
object.force_encoding
|
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(
|
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 ||= {
|
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(
|
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[
|
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[
|
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[
|
145
|
-
@parser_length = @parser_data[
|
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
|
210
|
+
when 'TRACE'
|
210
211
|
return true
|
211
|
-
when
|
212
|
+
when 'OPTIONS'
|
212
213
|
Plezi.push_event Proc.new do
|
213
214
|
response = HTTPResponse.new request
|
214
|
-
response[:Allow] =
|
215
|
-
response[
|
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
|
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[
|
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[
|
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[
|
253
|
-
boundry = headers[
|
254
|
-
if headers[
|
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[
|
257
|
+
name_prefix << HTTP.decode(headers['content-disposition'].to_s.match(/name="([^"]*)"/)[1])
|
257
258
|
else
|
258
|
-
name_prefix << "[#{HTTP.decode(headers[
|
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[
|
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[
|
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[
|
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[
|
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?
|
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 << (
|
125
|
+
value << ('; Max-Age=%s' % params[:max_age])
|
126
126
|
else
|
127
|
-
value << (
|
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 <<
|
132
|
-
value <<
|
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[
|
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[
|
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
|
-
|
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?
|
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
|
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
data/resources/redis_config.rb
CHANGED
@@ -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.
|
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-
|
11
|
+
date: 2015-05-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|