plezi 0.10.17 → 0.11.0

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: c509d0a1c5c9aa5fec5827c0f7b6a27dbf143840
4
- data.tar.gz: c1cef78d006408e83140295a0a392553ee8b1521
3
+ metadata.gz: 7faa8f51c3d95810d3e7a315b970d55276d5f3dc
4
+ data.tar.gz: ac93d924d781fe4dd9c9e388a8405a971b76f623
5
5
  SHA512:
6
- metadata.gz: edfc62fc655f782518bc355b0fd83b6784e141b445867f4307c948b8d92dd79d5d20dbb7b843143cc4e45f4cf0a9a1867b7a9df433e952c57b117644a6c2453c
7
- data.tar.gz: 46af810bb76558461e3933645e9e6064109228257e57aea3f09063425a3676fcf61a06d5cb96b6849aed99376f56dc29f14f794ae628b981a98d2d0e7eb774e6
6
+ metadata.gz: 6e8ec22249a50788ace60a4f12af6a589264cd3d91d6e2c2873002915be5c3a0a14ece6301f39306b293de65129f620f64c04c7b38984ac0234187237cef4d06
7
+ data.tar.gz: 8c23d6ac45147933bb294e97b266ee49b10481ce1747d1649b7866e8ff6e1f087201083607b53557a85b6d60632c0b5d9154e3a2b7028bbc7ae86d3a145c0730
@@ -0,0 +1 @@
1
+ yardoc lib/**/*.rb ext/**/*.c - docs/*.md
@@ -2,6 +2,22 @@
2
2
 
3
3
  ***
4
4
 
5
+ Change log v.0.11.0
6
+
7
+ **Update**: Requires GRHttp server and GReactor version 0.1.0 or above, adjusted to the updated API.
8
+
9
+ **Update**: Better pinging and timout support courtesy of the updated GRHttp server.
10
+
11
+ **Update**: The default number of threads is now 30. It seems that once we move beyond 1 thread (which is also supported), the added threads are adding more security against blocking code without effecting performance as much. It is expected that advanced users will consider moving away from multi-threading to muli-processing while avoiding blocking code. All these options are supported by Plezi, GRHttp and GReactor.
12
+
13
+ **Fix**: Fixed an issue where requests for folders within the assets folder (folder indexing) would fail with an internal error message (error 500) instead of a not found message (error 404).
14
+
15
+ **Fix**: fixed an issue that caused the static file service to fail when using the preferred `:public` vs. the older `:root` option used to set the public folder's path.
16
+
17
+ **Minor**: minor adjustments and improvements, such as: auto-setting the `content-type` header when using `render`.
18
+
19
+ ***
20
+
5
21
  Change log v.0.10.17
6
22
 
7
23
  **Update**: Requires a newer version of the GRHttp server and GReactor, building on it's WSClient and HTTP decoding improvements.
data/README.md CHANGED
@@ -1,6 +1,4 @@
1
1
  # [Plezi](https://github.com/boazsegev/plezi), The Ruby framework for realtime web-apps
2
- [![Gem Version](https://badge.fury.io/rb/plezi.svg)](http://badge.fury.io/rb/plezi)
3
- [![Inline docs](http://inch-ci.org/github/boazsegev/plezi.svg?branch=master)](http://www.rubydoc.info/github/boazsegev/plezi/master)
4
2
 
5
3
  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", or "pleasure", since Plezi is a pleasure to work with.
6
4
 
@@ -14,6 +12,12 @@ With Plezi, you can easily:
14
12
 
15
13
  Plezi leverages [GRHttp server's](https://github.com/boazsegev/GRHttp) new architecture. GRHttp is a pure Ruby HTTP and Websocket Generic Server built using [GReactor](https://github.com/boazsegev/GReactor) - a multi-threaded pure ruby alternative to EventMachine with basic process forking support (enjoy forking, if your code is scaling ready).
16
14
 
15
+ ### Plezi version data
16
+ [![Gem Version](https://badge.fury.io/rb/plezi.svg)](http://badge.fury.io/rb/plezi)
17
+ [![Inline docs](http://inch-ci.org/github/boazsegev/plezi.svg?branch=master)](http://www.rubydoc.info/github/boazsegev/plezi/master)
18
+
19
+ The `master` branch always refers to the latest edge version, which might also be a broken version. Please refer to the relevent version by using the version's `tag` in the branch selector.
20
+
17
21
  ## Installation
18
22
 
19
23
  Add this line to your application's Gemfile:
@@ -115,7 +119,9 @@ Remember to connect to the service from at least two browser windows - to truly
115
119
  route '/', BroadcastCtrl
116
120
  ```
117
121
 
118
- method names starting with an underscore ('_') will NOT be made public by the router: so while both '/hello' and '/humans.txt' are public ( [try it](http://localhost:3000/humans.txt) ), '/_send_message' will return a 404 not found error ( [try it](http://localhost:3000/_send_message) ).
122
+ method names starting with an underscore ('_') will NOT be made public by the router.
123
+
124
+ This is why even though both '/hello' and '/humans.txt' are public ( [try it](http://localhost:3000/humans.txt) ), '/_send_message' will return a 404 not found error ( [try it](http://localhost:3000/_send_message) ).
119
125
 
120
126
  ## Adding Websockets to your existing Rails/Sinatra/Rack application
121
127
 
data/bin/plezi CHANGED
@@ -65,7 +65,7 @@ if ARGV[0] == 'new' || ARGV[0] == 'n' || ARGV[0] == "force" || ARGV[0] == 'mini'
65
65
 
66
66
  # building
67
67
  template = Plezi::AppBuilder.new
68
- (ARGV[0] == 'mini' || ARGV[0] == 'm' ) ? template.build_mini : template.build
68
+ (ARGV[0] == 'mini' || ARGV[0] == 'm' ) ? template.build_mini(ARGV[1]) : template.build(ARGV[1])
69
69
  elsif ARGV[0] == 'server' || ARGV[0] == 'start' || ARGV[0] == 's'
70
70
  ARGV.shift
71
71
  load File.expand_path(Dir["."][0], (File.expand_path(Dir["."][0]).split(/[\\\/]/).last) ) rescue load( File.expand_path(Dir["."][0], (File.expand_path(Dir["."][0]).split(/[\\\/]/).last ) ) )
@@ -0,0 +1,195 @@
1
+ # Plezi's Asynchronous Engine
2
+
3
+ (todo: write documentation)
4
+
5
+ Inside Plezi's core code is a pure Ruby IO reactor called [GReactor](https://github.com/boazsegev/GReactor) (Generic Reactor), a very powerful Asynchronous Workflow Engine that allows us to enjoy both Multi-Threading and Multi-Processing.
6
+
7
+ Although multi-threading is highly regarded, it should be pointed out that using the GReactor with just one thread is both faster and more efficient. But, since some tasks that take more time (blocking tasks) can't be broken down into smaller tasks, using a number of threads (and/or processes) is a better practice.
8
+
9
+ You can read more about the [GReactor](https://github.com/boazsegev/GReactor) and it's amazing features in it's [documentation](http://www.rubydoc.info/github/boazsegev/GReactor/master).
10
+
11
+ Here we will discuss the methods used for asynchronous processing of different tasks that allow us to break big heavy tasks into smaller bits, allowing our application to 'flow' and stay responsive even while under heavy loads.
12
+
13
+ ## Asynchronous HTTP responses
14
+
15
+ Inside Plezi's core code is a pure Ruby HTTP and Websocket Server (and client) called [GRHttp](https://github.com/boazsegev/GRHttp) (Generic HTTP), which allows for native HTTP streaming.
16
+
17
+ Asynchronous HTTP method calls can be nested, but shouldn't be called on after the other.
18
+
19
+ i.e.:
20
+
21
+ ```ruby
22
+ # right
23
+ response.stream_async { response.stream_async {'do after'}; 'do first' }
24
+ # wrong
25
+ response.stream_async { "who's first?" }
26
+ response.stream_async { "I don't know..." }
27
+ ```
28
+
29
+ Since streaming is done asynchronously, and since Plezi is multi-threaded by default (this can be changed to single threaded, but is less recomended unless you know your code doesn't block - see `Plezi::Settings.max_threads = number`), Asynchronous HTTP method nesting makes sure that the code doesn't conflict and that race conditions don't occure within the same HTTP response.
30
+
31
+
32
+ #### GRHttp's `response.stream_async &block`
33
+
34
+ GRHttp's response object, which is accessed by the controller using the `response` method (or the `@response` object), allows easy access to HTTP streaming.
35
+
36
+ For example (run this in the terminal using `irb`):
37
+
38
+ ```ruby
39
+ require `plezi`
40
+
41
+ class MyController
42
+ def index
43
+ response.stream_async do
44
+ response << "This will stream.\n"
45
+ response.stream_async do
46
+ response << "Streaming can be nested."
47
+ end
48
+ end
49
+ end
50
+ end
51
+
52
+ listen
53
+ route '/', MyController
54
+
55
+ exit
56
+ ```
57
+
58
+ As noted above, `response.stream_async` calls should always be nested and never called in 'parallel'.
59
+
60
+ Calling `response.stream_async`
61
+
62
+ #### GRHttp's `response.stream_array enum, &block`
63
+
64
+ To make nesting easier, GRHttp's response object provides the `response.stream_array enum, &block` method.
65
+
66
+ Here's our modified example:
67
+
68
+ ```ruby
69
+ require `plezi`
70
+
71
+ class MyController
72
+ def index
73
+ data = ["This will stream.\n", "Streaming can be nested."]
74
+ response.stream_array(data) {|s| response << s}
75
+ end
76
+ end
77
+
78
+ listen
79
+ route '/', MyController
80
+
81
+ exit
82
+ ```
83
+
84
+ You can also add data to the array while 'looping', which allows you to use the array as a 'flag' for looped streaming. The following is a very limited example, which could be used for "lazy loading" data from a database, in order to save on system resources or send large table data using JSON "packets".
85
+
86
+ ```ruby
87
+ require `plezi`
88
+
89
+ class MyController
90
+ def index
91
+ data = ["This will stream.\n", "Streaming can be nested."]
92
+ flag = [true]
93
+ response.stream_array(flag) do
94
+ response << data.shift
95
+ flag << true unless data.empty?
96
+ end
97
+ end
98
+ end
99
+
100
+ listen
101
+ route '/', MyController
102
+
103
+ exit
104
+ ```
105
+
106
+ ## Asynchronous code execution
107
+
108
+ [GReactor](https://github.com/boazsegev/GReactor) (Generic Reactor), a very powerful Asynchronous Workflow Engine which offers a very intuitve and easy to use API both for queuing code snippets (blocks / methods) and for schedualing non-persistent timed events (future timed events are discarded during shutdown and need to be re-initiated).
109
+
110
+ ### The Asynchronous "Queue"
111
+
112
+ `GReactor` (in short: `GR`) offers a number of methods that allow us to easily queue code execution.
113
+
114
+
115
+ #### `GR.run_async(arg1, arg2, arg3...) {block}`
116
+
117
+ `GR.run_async` takes arguments to be passed to a block of code prior to execution. This allows us to seperate the `Proc` object creation fron the data handling and possibly (but not always) optimize our code.
118
+
119
+ For example:
120
+
121
+ ```ruby
122
+ require `plezi`
123
+
124
+ class MyController
125
+ def index
126
+ GR.run_async(Time.now) {|t| puts "Someone poked me at: #{t}"} # maybe send an email?
127
+ "Hello World"
128
+ end
129
+ end
130
+
131
+ listen
132
+ route '/', MyController
133
+
134
+ exit
135
+ ```
136
+
137
+ #### `GR.callback(object, method, arg1, arg2...) {|returned_value| callback block}`
138
+
139
+ Another common method imployed is the `GR.callback`, which allows us to layer asynchronous code:
140
+
141
+ ```ruby
142
+ require `plezi`
143
+
144
+ class MyController
145
+ def index
146
+ GR.callback(self, :print_poke, Time.now) { puts "Printed poke."}
147
+ "Hello World"
148
+ end
149
+ protected
150
+ def print_poke time
151
+ puts "Someone poked me at: #{time}"
152
+ end
153
+ end
154
+
155
+ listen
156
+ route '/', MyController
157
+
158
+ exit
159
+ ```
160
+
161
+ #### `GR.queue(proc, arguments_array)`
162
+
163
+ GReactor's asynchronous engine is, in it's core, based off this simple method which accepts two elements:
164
+
165
+ 1. An object that answers to `call`
166
+ 2. An array of arguments to be passed to that object (or `nil`).
167
+
168
+ This method allows us to easily reuse Proc objects without constantly creating new Proc objects and releasing them from memory.
169
+
170
+ For example:
171
+
172
+ ```ruby
173
+ require `plezi`
174
+
175
+ class MyController
176
+
177
+ POKE_TASK = -> {|time| puts "Someone poked me at: #{time}"}
178
+
179
+ def index
180
+ GR.queue POKE_TASK, [Time.now]
181
+ "Hello World"
182
+ end
183
+ end
184
+
185
+ listen
186
+ route '/', MyController
187
+
188
+ exit
189
+ ```
190
+
191
+
192
+ ### Timed events
193
+
194
+ ## The Graceful Shutdown
195
+
@@ -0,0 +1,9 @@
1
+ # HTTP Helpers inherited from GRHttp
2
+
3
+ Inside Plezi's core code is a pure Ruby HTTP and Websocket Server (and client) called [GRHttp](https://github.com/boazsegev/GRHttp) (Generic HTTP), a very powerful server that [isn't limited by Rack's API](https://github.com/boazsegev/GRHttp/blob/master/HTTP.md).
4
+
5
+
6
+ (todo: write documentation)
7
+
8
+
9
+
@@ -0,0 +1,14 @@
1
+ # Plezi's Logging
2
+
3
+ (todo: write documentation)
4
+
5
+ Inside Plezi's core code is a pure Ruby IO reactor called [GReactor](https://github.com/boazsegev/GReactor) (Generic Reactor), a very powerful Asynchronous Workflow Engine that allows us to enjoy both Multi-Threading and Multi-Processing.
6
+
7
+ Plezi leverages [GReactor's](https://github.com/boazsegev/GReactor) logging support to help you log to both files and STDOUT (terminal screen) - either one or both
8
+
9
+ You can read more about [GReactor](https://github.com/boazsegev/GReactor) and it's amazing features in it's [documentation](http://www.rubydoc.info/github/boazsegev/GReactor/master).
10
+
11
+ ## Setting up a Logger
12
+
13
+
14
+ ## Logging Helpers Methods
@@ -0,0 +1,37 @@
1
+ # Plezi's Smart Routing System
2
+
3
+ In the core of Plezi's framework is a smart Object Oriented Router which actls like a "virtual folder" with RESTful routing and Websocket support.
4
+
5
+ RESTful routing and Websocket callback support both allow us to use conventionally named methods in our Controller to achive common tasks. Such names methods, as will be explored further on, include the `update`, `save` and `show` RESTful method names, as well as the `on_open`, `on_message(data)` and `on_close` Websocket callbacks.
6
+
7
+ ## Defining a Route
8
+
9
+ (todo: write documentation)
10
+
11
+ ### The `:id` parameter
12
+
13
+ (todo: write documentation)
14
+
15
+ ### More inline parameters
16
+
17
+ (todo: write documentation)
18
+
19
+ ### Re-Write Routes and Proc Controllers
20
+
21
+ ## The Controller
22
+
23
+ (todo: write documentation)
24
+
25
+ ### A Virtual Folder
26
+
27
+ (todo: write documentation)
28
+
29
+ ### RESTful methods
30
+
31
+ (todo: write documentation)
32
+
33
+ ### Websocket Callbacks
34
+
35
+ (todo: write documentation)
36
+
37
+
@@ -0,0 +1,9 @@
1
+ # Plezi Websockets
2
+
3
+
4
+ (todo: write documentation)
5
+
6
+
7
+
8
+
9
+
@@ -11,28 +11,28 @@ module Plezi
11
11
  def app_tree
12
12
  @app_tree ||= {}
13
13
  end
14
- def build_mini
14
+ def build_mini app_name = ARGV[1]
15
15
  require 'plezi/version'
16
- @app_tree["#{ARGV[1]}"] ||= IO.read( File.join(@root, "resources" ,"mini_exec.rb")).gsub('appname', ARGV[1])
17
- @app_tree["#{ARGV[1]}.rb"] ||= IO.read( File.join(@root, "resources" ,"mini_app.rb")).gsub('appname', ARGV[1]).gsub('appsecret', "#{ARGV[1]}_#{SecureRandom.hex}")
16
+ app_tree["#{app_name}"] ||= IO.read( File.join(@root, "resources" ,"mini_exec.rb")).gsub('appname', app_name)
17
+ app_tree["#{app_name}.rb"] ||= IO.read( File.join(@root, "resources" ,"mini_app.rb")).gsub('appname', app_name).gsub('appsecret', "#{app_name}_#{SecureRandom.hex}")
18
18
  app_tree["Procfile"] ||= ""
19
- app_tree["Procfile"] << "\nweb: bundle exec ruby ./#{ARGV[1]} -p $PORT\n"
19
+ app_tree["Procfile"] << "\nweb: bundle exec ruby ./#{app_name} -p $PORT\n"
20
20
  app_tree["Gemfile"] ||= ''
21
21
  app_tree["Gemfile"] << "source 'https://rubygems.org'\n\n####################\n# core gems\n\n# include the basic plezi framework and server\ngem 'plezi', '~> #{Plezi::VERSION}'\n"
22
22
  app_tree["Gemfile"] << "\n\n\nruby '#{RUBY_VERSION}'\n"
23
23
  app_tree["templates"] ||= {}
24
24
  app_tree["templates"]["404.html.erb"] ||= IO.read(File.join(@root, "resources" ,"404.erb"))
25
25
  app_tree["templates"]["500.html.erb"] ||= IO.read(File.join(@root, "resources" ,"500.erb"))
26
- app_tree["templates"]["welcome.html.erb"] ||= IO.read(File.join(@root, "resources" ,"mini_welcome_page.html")).gsub('appname', ARGV[1])
26
+ app_tree["templates"]["welcome.html.erb"] ||= IO.read(File.join(@root, "resources" ,"mini_welcome_page.html")).gsub('appname', app_name)
27
27
  app_tree["assets"] ||= {}
28
- app_tree["assets"]["websocket.js"] ||= IO.read(File.join(@root, "resources" ,"websockets.js")).gsub('appname', ARGV[1])
29
- finalize
28
+ app_tree["assets"]["websocket.js"] ||= IO.read(File.join(@root, "resources" ,"websockets.js")).gsub('appname', app_name)
29
+ finalize app_name
30
30
  end
31
31
 
32
- def build
32
+ def build app_name = ARGV[1]
33
33
  require 'plezi/version'
34
34
  # plezi run script
35
- @app_tree["#{ARGV[1]}"] ||= IO.read File.join(@root,"resources" ,"code.rb")
35
+ app_tree["#{app_name}"] ||= IO.read File.join(@root,"resources" ,"code.rb")
36
36
 
37
37
  # set up application files
38
38
  app_tree["app"] ||= {}
@@ -55,15 +55,15 @@ module Plezi
55
55
  app_tree["assets"] ||= {}
56
56
  app_tree["assets"]["stylesheets"] ||= {}
57
57
  app_tree["assets"]["javascripts"] ||= {}
58
- app_tree["assets"]["javascripts"]["websocket.js"] ||= IO.read(File.join(@root,"resources" ,"websockets.js")).gsub('appname', ARGV[1])
59
- app_tree["assets"]["welcome.html"] ||= IO.read(File.join(@root,"resources" ,"welcome_page.html")).gsub('appname', ARGV[1])
58
+ app_tree["assets"]["javascripts"]["websocket.js"] ||= IO.read(File.join(@root,"resources" ,"websockets.js")).gsub('appname', app_name)
59
+ app_tree["assets"]["welcome.html"] ||= IO.read(File.join(@root,"resources" ,"welcome_page.html")).gsub('appname', app_name)
60
60
 
61
61
  # app core files.
62
62
  app_tree["environment.rb"] ||= IO.read File.join(@root,"resources" ,"environment.rb")
63
63
  app_tree["routes.rb"] ||= IO.read File.join(@root,"resources" ,"routes.rb")
64
64
  app_tree["rakefile"] ||= IO.read File.join(@root,"resources" ,"rakefile")
65
65
  app_tree["Procfile"] ||= ""
66
- app_tree["Procfile"] << "\nweb: bundle exec ruby ./#{ARGV[1]} -p $PORT\n"
66
+ app_tree["Procfile"] << "\nweb: bundle exec ruby ./#{app_name} -p $PORT\n"
67
67
  app_tree["Gemfile"] ||= ''
68
68
  app_tree["Gemfile"] << "source 'https://rubygems.org'\n\n####################\n# core gems\n\n# include the basic plezi framework and server\ngem 'plezi', '~> #{Plezi::VERSION}'\n"
69
69
  app_tree["Gemfile"] << IO.read( File.join(@root,"resources" ,"Gemfile"))
@@ -78,7 +78,7 @@ module Plezi
78
78
  app_tree["config"]["haml.rb"] ||= IO.read(File.join(@root,"resources" ,"haml_config.rb"))
79
79
  app_tree["config"]["slim.rb"] ||= IO.read(File.join(@root,"resources" ,"slim_config.rb"))
80
80
  app_tree["config"]["i18n.rb"] ||= IO.read(File.join(@root,"resources" ,"i18n_config.rb"))
81
- app_tree["config"]["redis.rb"] ||= (IO.read(File.join(@root,"resources" ,"redis_config.rb"))).gsub('appsecret', "#{ARGV[1]}_#{SecureRandom.hex}")
81
+ app_tree["config"]["redis.rb"] ||= (IO.read(File.join(@root,"resources" ,"redis_config.rb"))).gsub('appsecret', "#{app_name}_#{SecureRandom.hex}")
82
82
 
83
83
  #set up database stub folders
84
84
  app_tree["db"] ||= {}
@@ -106,29 +106,29 @@ module Plezi
106
106
  app_tree["public"]["assets"]["stylesheets"] ||= {}
107
107
  app_tree["public"]["assets"]["javascripts"] ||= {}
108
108
  app_tree["public"]["images"] ||= {}
109
- finalize
109
+ finalize app_name
110
110
  end
111
- def finalize
111
+ def finalize app_name = ARGV[1]
112
112
  begin
113
- Dir.mkdir ARGV[1]
114
- puts "created the #{ARGV[1]} application directory.".green
113
+ Dir.mkdir app_name
114
+ puts "created the #{app_name} application directory.".green
115
115
  rescue Exception => e
116
- puts "the #{ARGV[1]} application directory exists - trying to rebuild (no overwrite).".pink
116
+ puts "the #{app_name} application directory exists - trying to rebuild (no overwrite).".pink
117
117
  end
118
- Dir.chdir ARGV[1]
118
+ Dir.chdir app_name
119
119
  puts "starting to write template data...".red
120
120
  puts ""
121
121
  Builder.write_files app_tree
122
- File.chmod 0775, "#{ARGV[1]}"
122
+ File.chmod 0775, "#{app_name}"
123
123
  puts "tried to update execution permissions. this is system dependent and might have failed.".pink
124
- puts "use: chmod +x ./#{ARGV[1]} to set execution permissions on Unix machines."
124
+ puts "use: chmod +x ./#{app_name} to set execution permissions on Unix machines."
125
125
  puts ""
126
126
  puts "done."
127
127
  puts "\n#{@end_comments.join("\n")}" unless @end_comments.empty?
128
128
  puts ""
129
- puts "please change directory into the app directory: cd #{ARGV[1]}"
129
+ puts "please change directory into the app directory: cd #{app_name}"
130
130
  puts ""
131
- puts "run the #{ARGV[1]} app using: ./#{ARGV[1]} or using: plezi s"
131
+ puts "run the #{app_name} app using: ./#{app_name} or using: plezi s"
132
132
  puts ""
133
133
  end
134
134
  end
@@ -109,9 +109,9 @@ module Plezi
109
109
  def start_async
110
110
  Object.const_set("NO_PLEZI_AUTO_START", true) unless defined?(NO_PLEZI_AUTO_START)
111
111
  return GReactor.start if GReactor.running?
112
- puts "Starting Plezi #{Plezi::VERSION} Services using the GRHttp #{GRHttp::VERSION} server."
112
+ puts "Starting Plezi #{Plezi::VERSION} Services using GRHttp #{GRHttp::VERSION} and GReactor #{GReactor::VERSION}."
113
113
  GReactor.on_shutdown { puts "Plezi shutdown. It was fun to serve you." }
114
- GReactor.start Plezi::Settings.max_threads
114
+ GReactor.start ::Plezi::Settings.max_threads
115
115
  end
116
116
  # This allows you to run the Plezi framework along side another framework - WITHOUT running the actual server.
117
117
  #
@@ -136,4 +136,4 @@ end
136
136
  Encoding.default_internal = 'utf-8'
137
137
  Encoding.default_external = 'utf-8'
138
138
 
139
- NO_PLEZI_AUTO_START = true if defined?(::Rack::Builder)
139
+ Object.const_set("NO_PLEZI_AUTO_START", true) if defined?(::Rack::Builder) && !defined?(NO_PLEZI_AUTO_START)
@@ -105,5 +105,4 @@ unless defined? PLEZI_NON_DSL
105
105
 
106
106
  # sets to start the services once dsl script is finished loading.
107
107
  at_exit { start_services }
108
- GReactor::Settings.force_graceful = false
109
108
  end
@@ -8,7 +8,7 @@ module Plezi
8
8
 
9
9
  # The maximum number of threads that are used for concurrency.
10
10
  def max_threads
11
- @max_threads ||= 8
11
+ @max_threads ||= 30
12
12
  end
13
13
  # Sets the maximum number of threads that are used for concurrency.
14
14
  def max_threads=val
@@ -13,6 +13,12 @@ module Plezi
13
13
  # response:: the HTTPResponse **OR** the WSResponse object that formats the response and sends it. use `response << data`. This object can be used to send partial data (such as headers, or partial html content) in blocking mode as well as sending data in the default non-blocking mode.
14
14
  # host_params:: a copy of the parameters used to create the host and service which accepted the request and created this instance of the controller class.
15
15
  #
16
+ # For Controller Class menthods, please read the documentation about {Plezi::ControllerMagic::ClassMethods}.
17
+ #
18
+ # For Controller Instance methods, please read the documentation about {Plezi::ControllerMagic::InstanceMethods}.
19
+ #
20
+ # {include: Plezi::ControllerMagic::InstanceMethods}
21
+ #
16
22
  module ControllerMagic
17
23
  def self.included base
18
24
  base.send :include, InstanceMethods
@@ -38,7 +44,7 @@ module Plezi
38
44
  #
39
45
  # The first time this method is called, the session object will be created. The session object must be created BEFORE the headers are set , if it is to be used.
40
46
  #
41
- # Sessions are not automatically created, because they are memory hogs. The one exception is the Websocket connection that will force a session object into existence.
47
+ # Sessions are not automatically created, because they require more resources. The one exception is the Websocket connection that will force a session object into existence, as it's very common to use session data in Websocket connections and the extra connection time is less relevant for a long term connection.
42
48
  def session
43
49
  response.session
44
50
  end
@@ -73,14 +79,14 @@ module Plezi
73
79
  #
74
80
  def redirect_to url, options = {}
75
81
  return super *[] if defined? super
76
- raise 'Cannot redirect after headers were sent' if response.headers_sent?
82
+ raise 'Cannot redirect once a Websocket connection was established.' if response.is_a?(::GRHttp::WSEvent)
83
+ raise 'Cannot redirect after headers were sent.' if response.headers_sent?
77
84
  url = "#{request.base_url}/#{url.to_s.gsub('_', '/')}" if url.is_a?(Symbol) || ( url.is_a?(String) && url.empty? ) || url.nil?
78
85
  # redirect
79
86
  response.status = options.delete(:status) || 302
80
87
  response['Location'] = url
81
88
  response['content-length'] ||= 0
82
89
  flash.update options
83
- response.finish
84
90
  true
85
91
  end
86
92
 
@@ -123,6 +129,8 @@ module Plezi
123
129
  # filename:: sets a filename for the browser to "save as". defaults to empty.
124
130
  #
125
131
  def send_data data, options = {}
132
+ raise 'Cannot use "send_data" once a Websocket connection was established.' if response.is_a?(::GRHttp::WSEvent)
133
+ # return response.write(data) if response.is_a?(::GRHttp::WSEvent)
126
134
  raise 'Cannot use "send_data" after headers were sent' if response.headers_sent?
127
135
  Plezi.warn 'HTTP response buffer is cleared by `#send_data`' if response.body && response.body.any? && response.body.clear
128
136
  response << data
@@ -131,15 +139,15 @@ module Plezi
131
139
  content_disposition = options[:inline] ? 'inline' : 'attachment'
132
140
  content_disposition << "; filename=#{options[:filename]}" if options[:filename]
133
141
 
134
- response['content-type'] = options[:type] ||= MimeTypeHelper::MIME_DICTIONARY[::File.extname(options[:filename])] if options[:filename]
142
+ response['content-type'] = (options[:type] ||= MimeTypeHelper::MIME_DICTIONARY[::File.extname(options[:filename])])
135
143
  response['content-length'] = data.bytesize rescue true
136
144
  response['content-disposition'] = content_disposition
137
- response.finish
138
145
  true
139
146
  end
140
147
 
141
- # renders a template file (.slim/.erb/.haml) or an html file (.html) to text
142
- # for example, to render the file `body.html.slim` with the layout `main_layout.html.haml`:
148
+ # Renders a template file (.slim/.erb/.haml) or an html file (.html) to text and attempts to set the response's 'content-type' header (if it's still empty).
149
+ #
150
+ # For example, to render the file `body.html.slim` with the layout `main_layout.html.haml`:
143
151
  # render :body, layout: :main_layout
144
152
  #
145
153
  # or, for example, to render the file `json.js.slim`
@@ -171,14 +179,16 @@ module Plezi
171
179
  # set up defaults
172
180
  options[:type] ||= 'html'
173
181
  options[:locale] ||= params[:locale].to_sym if params[:locale]
182
+ #update content-type header
183
+ response['content-type'] ||= (MimeTypeHelper::MIME_DICTIONARY[options[:type]] + "; charset=utf-8".freeze)
174
184
  # options[:locals] ||= {}
175
185
  I18n.locale = options[:locale] || I18n.default_locale if defined?(I18n) # sets the locale to nil for default behavior even if the locale was set by a previous action - removed: # && options[:locale]
176
186
  # find template and create template object
177
187
  filename = template.is_a?(String) ? File.join( host_params[:templates].to_s, template) : (File.join( host_params[:templates].to_s, *template.to_s.split('_')) + (options[:type].empty? ? '': ".#{options[:type]}") + '.slim')
178
188
  return ( Plezi.cache_needs_update?(filename) ? Plezi.cache_data( filename, ( Slim::Template.new() { IO.binread filename } ) ) : (Plezi.get_cached filename) ).render(self, &block) if defined?(::Slim) && Plezi.file_exists?(filename)
179
- filename.gsub! /\.slim$/, '.haml'
189
+ filename.sub! /\.slim$/, '.haml'
180
190
  return ( Plezi.cache_needs_update?(filename) ? Plezi.cache_data( filename, ( Haml::Engine.new( IO.binread(filename) ) ) ) : (Plezi.get_cached filename) ).render(self, &block) if defined?(::Haml) && Plezi.file_exists?(filename)
181
- filename.gsub! /\.haml$/, '.erb'
191
+ filename.sub! /\.haml$/, '.erb'
182
192
  return ( Plezi.cache_needs_update?(filename) ? Plezi.cache_data( filename, ( ERB.new( IO.binread(filename) ) ) ) : (Plezi.get_cached filename) ).result(binding, &block) if defined?(::ERB) && Plezi.file_exists?(filename)
183
193
  return false
184
194
  end
@@ -107,42 +107,41 @@ module Plezi
107
107
  source_file = File.join(params[:assets], *(request.path.match(/^#{params[:assets_public]}\/(.+)/)[1].split('/')))
108
108
 
109
109
  # stop if file name is reserved / has security issues
110
- return false if source_file.match(/(scss|sass|coffee|\.\.\/)$/)
110
+ return false if File.directory?(source_file) || source_file.match(/(scss|sass|coffee|\.\.\/)$/)
111
111
 
112
112
  # set where to store the rendered asset
113
- target_file = false
114
- target_file = File.join( params[:root], params[:assets_public], *request.path.match(/^#{params[:assets_public]}\/(.*)/)[1].split('/') ) if params[:root]
113
+ target_file = File.join( params[:public].to_s, params[:assets_public].to_s, *request.path.match(/^#{params[:assets_public]}\/(.*)/)[1].split('/') )
115
114
 
116
115
  # send the file if it exists (no render needed)
117
116
  if File.exists?(source_file)
118
- data = Plezi.cache_needs_update?(source_file) ? Plezi.save_file(target_file, Plezi.reload_file(source_file), params[:save_assets]) : Plezi.load_file(source_file)
117
+ data = Plezi.cache_needs_update?(source_file) ? Plezi.save_file(target_file, Plezi.reload_file(source_file), (params[:public] && params[:save_assets])) : Plezi.load_file(source_file)
119
118
  return (data ? Base::HTTPSender.send_raw_data(request, response, data, MimeTypeHelper::MIME_DICTIONARY[::File.extname(source_file)]) : false)
120
119
  end
121
120
 
122
121
  # render supported assets
123
122
  case source_file
124
123
  when /\.css$/
125
- sass = source_file.gsub /css$/, 'sass'
126
- sass.gsub! /sass$/, 'scss' unless Plezi.file_exists?(sass)
124
+ sass = source_file.sub /css$/, 'sass'
125
+ sass.sub! /sass$/, 'scss' unless Plezi.file_exists?(sass)
127
126
  return false unless Plezi.file_exists?(sass)
128
127
  # review mtime and render sass if necessary
129
128
  if defined?(::Sass) && refresh_sass?(sass)
130
129
  eng = Sass::Engine.for_file(sass, cache_store: @sass_cache)
131
130
  Plezi.cache_data sass, eng.dependencies
132
131
  css, map = eng.render_with_sourcemap(params[:assets_public])
133
- Plezi.save_file target_file, css, params[:save_assets]
134
- Plezi.save_file (target_file + ".map"), map, params[:save_assets]
132
+ Plezi.save_file target_file, css, (params[:public] && params[:save_assets])
133
+ Plezi.save_file (target_file + ".map"), map, (params[:public] && params[:save_assets])
135
134
  end
136
135
  # try to send the cached css file which started the request.
137
136
  return Base::HTTPSender.send_file request, response, target_file
138
137
  when /\.js$/
139
- coffee = source_file.gsub /js$/i, 'coffee'
138
+ coffee = source_file.sub /js$/i, 'coffee'
140
139
  return false unless Plezi.file_exists?(coffee)
141
140
  # review mtime and render coffee if necessary
142
141
  if defined?(::CoffeeScript) && Plezi.cache_needs_update?(coffee)
143
142
  # render coffee to cache
144
143
  Plezi.cache_data coffee, nil
145
- Plezi.save_file target_file, CoffeeScript.compile(IO.binread coffee), params[:save_assets]
144
+ Plezi.save_file target_file, CoffeeScript.compile(IO.binread coffee), (params[:public] && params[:save_assets])
146
145
  end
147
146
  # try to send the cached js file which started the request.
148
147
  return Base::HTTPSender.send_file request, response, target_file
@@ -68,7 +68,7 @@ module Plezi
68
68
  self.read
69
69
  GR.warn "Placebo IO recieved IO signal - this is unexpected..."
70
70
  end
71
- def on_disconnect
71
+ def on_close
72
72
  @params[:out].close rescue nil
73
73
  @cache[:websocket_handler].on_close if @cache[:websocket_handler]
74
74
  end
@@ -86,8 +86,7 @@ module Plezi
86
86
  Object.const_set(new_class_name, new_class)
87
87
  end
88
88
  i, o = IO.pipe
89
- io = Placebo::Base::PlaceboIO.new i, out: o
90
- io = GReactor.add_raw_io i, io
89
+ io = Placebo::Base::PlaceboIO.new i, out: o, reactor: ::GReactor
91
90
  new_class.new(io)
92
91
  end
93
92
  end
@@ -17,7 +17,7 @@ module Plezi
17
17
  fill_parameters = match request.path
18
18
  return false unless fill_parameters
19
19
  old_params = request.params.dup
20
- fill_parameters.each {|k,v| GRHttp::HTTP.add_param_to_hash k, v, request.params }
20
+ fill_parameters.each {|k,v| Plezi::Base::Helpers.add_param_to_hash k, v, request.params }
21
21
  ret = false
22
22
  if controller
23
23
  ret = controller.new(request, response)._route_path_to_methods_and_set_the_response_
@@ -112,7 +112,7 @@ module Plezi
112
112
  param_name = param_name[1].to_sym if param_name
113
113
 
114
114
  if param_name && dest[param_name]
115
- url << GRHttp::HTTP.encode_url(dest.delete(param_name))
115
+ url << Plezi::Base::Helpers.encode_url(dest.delete(param_name))
116
116
  url << '/'
117
117
  elsif !param_name
118
118
  url << sec
@@ -125,7 +125,7 @@ module Plezi
125
125
  end
126
126
  unless dest.empty?
127
127
  add = '?'
128
- dest.each {|k, v| url << "#{add}#{GRHttp::HTTP.encode_url k}=#{GRHttp::HTTP.encode_url v}"; add = '&'}
128
+ dest.each {|k, v| url << "#{add}#{Plezi::Base::Helpers.encode_url k}=#{Plezi::Base::Helpers.encode_url v}"; add = '&'}
129
129
  end
130
130
  url
131
131
 
@@ -229,7 +229,7 @@ module Plezi
229
229
  # m = nil
230
230
  # unless @fill_parameters.values.include?("format")
231
231
  # if (m = path.match /([^\.]*)\.([^\.\/]+)$/)
232
- # GRHttp::HTTP.add_param_to_hash 'format', m[2], hash
232
+ # Plezi::Base::Helpers.add_param_to_hash 'format', m[2], hash
233
233
  # path = m[1]
234
234
  # end
235
235
  # end
@@ -5,9 +5,8 @@ module Plezi
5
5
  # returns a session object
6
6
  def fetch id
7
7
  return Plezi::Session.new(id) if Plezi.redis # avoid a local cache if Redis is used.
8
- @session_cache[id] || (@session_cache[id] = Plezi::Session.new(id))
8
+ GRHttp::Base::FileSessionStorage.fetch id # use the tmp-file-session logic if Redis isn't present
9
9
  end
10
- @session_cache = {}
11
10
  end
12
11
  end
13
12
  # A hash like interface for storing request session data.
@@ -22,9 +21,9 @@ module Plezi
22
21
  def initialize id
23
22
  @id = id
24
23
  if id && (conn=Plezi.redis)
25
- @data=conn.hgetall(id)
24
+ return self
26
25
  end
27
- @data ||= {}
26
+ failed
28
27
  end
29
28
  # Get a key from the session data store. If a Redis server is supplied, it will be used to synchronize session data.
30
29
  #
@@ -34,9 +33,9 @@ module Plezi
34
33
  key = key.to_s
35
34
  if conn=Plezi.redis
36
35
  conn.expire @id, SESSION_LIFETIME
37
- @data[key] = conn.hget @id, key
36
+ return conn.hget @id, key
38
37
  end
39
- @data[key]
38
+ failed
40
39
  end
41
40
  alias :fetch :[]
42
41
 
@@ -49,8 +48,9 @@ module Plezi
49
48
  if (conn=Plezi.redis)
50
49
  conn.hset @id, key, value
51
50
  conn.expire @id, SESSION_LIFETIME
51
+ return value
52
52
  end
53
- @data[key] = value
53
+ failed
54
54
  end
55
55
  alias :store :[]=
56
56
 
@@ -58,9 +58,9 @@ module Plezi
58
58
  def to_h
59
59
  if (conn=Plezi.redis)
60
60
  conn.expire @id, SESSION_LIFETIME
61
- return (@data=conn.hgetall(@id)).dup
61
+ return conn.hgetall(@id)
62
62
  end
63
- @data.dup
63
+ failed
64
64
  end
65
65
 
66
66
  # Removes a key from the session's data store.
@@ -68,17 +68,26 @@ module Plezi
68
68
  key = key.to_s
69
69
  if (conn=Plezi.redis)
70
70
  conn.expire @id, SESSION_LIFETIME
71
+ ret = conn.hget @id, key
71
72
  conn.hdel @id, key
73
+ return ret
72
74
  end
73
- @data.delete key
75
+ failed
74
76
  end
75
77
 
76
78
  # Clears the session's data.
77
79
  def clear
78
80
  if (conn=Plezi.redis)
79
- conn.del @id
81
+ return conn.del @id
80
82
  end
81
- @data.clear
83
+ failed
84
+ end
85
+
86
+ protected
87
+
88
+ def failed
89
+ raise 'Redis connection failed while using Redis Session Storage.'
90
+
82
91
  end
83
92
  end
84
93
  GRHttp::SessionManager.storage = Plezi::Base::SessionStorage
@@ -24,7 +24,7 @@ module Plezi
24
24
  elsif Plezi.file_exists?(fn = File.join(base_code_path, "#{code}.html"))
25
25
  return send_file(request, response, fn, code, headers)
26
26
  end
27
- return true if send_raw_data(request, response, GRHttp::HTTPResponse::STATUS_CODES[code], 'text/plain', code, headers)
27
+ return true if send_raw_data(request, response, response.class::STATUS_CODES[code], 'text/plain', code, headers)
28
28
  rescue Exception => e
29
29
  Plezi.error e
30
30
  end
@@ -1,8 +1,5 @@
1
1
  module Plezi
2
2
 
3
- # use GRHttp's helpers for escaping data etc'.
4
- HTTP = GRHttp::HTTP
5
-
6
3
  module Base
7
4
  # some helper methods used internally.
8
5
  module Helpers
@@ -12,7 +9,7 @@ module Plezi
12
9
  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) }
13
10
 
14
11
  # tweeks a hash object to read both :symbols and strings (similar to Rails but without).
15
- def make_hash_accept_symbols hash
12
+ def self.make_hash_accept_symbols hash
16
13
  @magic_hash_proc ||= Proc.new do |hs,k|
17
14
  if k.is_a?(Symbol) && hs.has_key?( k.to_s)
18
15
  hs[k.to_s]
@@ -29,6 +26,71 @@ module Plezi
29
26
  end
30
27
  end
31
28
  end
29
+
30
+ # encodes URL data
31
+ def self.encode_url str
32
+ (str.to_s.gsub(/[^a-z0-9\*\.\_\-]/i) {|m| '%%%02x'.freeze % m.ord }).force_encoding(::Encoding::ASCII_8BIT)
33
+ end
34
+
35
+ # Adds paramaters to a Hash object, according to the GRHttp's server conventions.
36
+ def self.add_param_to_hash name, value, target
37
+ begin
38
+ c = target
39
+ val = rubyfy! value
40
+ a = name.chomp('[]'.freeze).split('['.freeze)
41
+
42
+ a[0...-1].inject(target) do |h, n|
43
+ n.chomp!(']'.freeze);
44
+ n.strip!;
45
+ raise "malformed parameter name for #{name}" if n.empty?
46
+ n = (n.to_i.to_s == n) ? n.to_i : n.to_sym
47
+ c = (h[n] ||= {})
48
+ end
49
+ n = a.last
50
+ n.chomp!(']'); n.strip!;
51
+ n = n.empty? ? nil : ( (n.to_i.to_s == n) ? n.to_i : n.to_sym )
52
+ if n
53
+ if c[n]
54
+ c[n].is_a?(Array) ? (c[n] << val) : (c[n] = [c[n], val])
55
+ else
56
+ c[n] = val
57
+ end
58
+ else
59
+ if c[n]
60
+ c[n].is_a?(Array) ? (c[n] << val) : (c[n] = [c[n], val])
61
+ else
62
+ c[n] = [val]
63
+ end
64
+ end
65
+ val
66
+ rescue => e
67
+ GReactor.error e
68
+ GReactor.error "(Silent): parameters parse error for #{name} ... maybe conflicts with a different set?"
69
+ target[name] = val
70
+ end
71
+ end
72
+ # Changes String to a Ruby Object, if it's a special string...
73
+ def self.rubyfy!(string)
74
+ return string unless string.is_a?(String)
75
+ try_utf8! string
76
+ if string == 'true'.freeze
77
+ string = true
78
+ elsif string == 'false'.freeze
79
+ string = false
80
+ elsif string.to_i.to_s == string
81
+ string = string.to_i
82
+ end
83
+ string
84
+ end
85
+
86
+ # re-encodes a string into UTF-8 unly when the encoding will remail valid.
87
+ def self.try_utf8!(string, encoding= ::Encoding::UTF_8)
88
+ return false unless string
89
+ string.force_encoding(::Encoding::ASCII_8BIT) unless string.force_encoding(encoding).valid_encoding?
90
+ string
91
+ end
92
+
93
+
32
94
  end
33
95
  end
34
96
 
@@ -139,7 +139,7 @@ module Plezi
139
139
  # @user ||= app_login || OAuth2Ctrl.auth(:facebook, fb_token, self) || OAuth2Ctrl.auth(:google, google_token, self) || ....
140
140
  def self.auth service_name, service_token, controller
141
141
  service = SERVICES[service_name]
142
- retrun false unless service
142
+ return false unless service
143
143
  # auth_res = controller.cookies[c_name] ? (JSON.parse URI.parse("#{service[:profile_url]}?access_token=#{controller.cookies[c_name]}").read rescue ({}) ) : {}
144
144
  # controller.cookies[c_name] = nil unless auth_res['id']
145
145
  # auth_res['id'] ? controller.instance_exec( service_name, auth_res['id'], auth_res['email'], auth_res, &auth_callback) : ( controller.instance_exec( service_name, nil, nil, auth_res, &auth_callback) && false)
@@ -156,7 +156,7 @@ module Plezi
156
156
  def update
157
157
  service_name = params[:id].to_s.to_sym
158
158
  service = SERVICES[service_name]
159
- retrun false unless service
159
+ return false unless service
160
160
  if params[:error]
161
161
  instance_exec( service_name, nil, nil, nil, {}, &self.class.auth_callback)
162
162
  return redirect_to(flash[:redirect_after])
@@ -190,7 +190,7 @@ module Plezi
190
190
  def _auth_url_for service_name
191
191
  service = SERVICES[service_name]
192
192
  return nil unless service
193
- redirect_uri = HTTP::encode "#{request.base_url}/auth/#{service_name.to_s}", :url #response_type
193
+ redirect_uri = Plezi::Base::Helpers.encode_url "#{request.base_url}/auth/#{service_name.to_s}", :url #response_type
194
194
  return "#{service[:auth_url]}?client_id=#{service[:app_id]}&redirect_uri=#{redirect_uri}&scope=#{service[:scope]}&response_type=code"
195
195
  end
196
196
 
@@ -1,3 +1,3 @@
1
1
  module Plezi
2
- VERSION = "0.10.17"
2
+ VERSION = "0.11.0"
3
3
  end
@@ -18,7 +18,7 @@ Gem::Specification.new do |spec|
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ["lib"]
20
20
 
21
- spec.add_dependency "grhttp", "~> 0.0.23"
21
+ spec.add_dependency "grhttp", "~> 0.1.0"
22
22
  spec.add_development_dependency "bundler", "~> 1.7"
23
23
  spec.add_development_dependency "rake", "~> 10.0"
24
24
 
@@ -22,7 +22,7 @@
22
22
  # 2. you have better control over Middleware then you could have with Plezi.
23
23
  # ("wait", you might say, "there is no Middleware in Plezi!"... "Ahhh", I will answer, "so much to discover...")
24
24
 
25
- NO_PLEZI_AUTO_START = true
25
+ Object.const_set("NO_PLEZI_AUTO_START", true) unless defined?(NO_PLEZI_AUTO_START)
26
26
 
27
27
  # load all framework and gems
28
28
  load ::File.expand_path(File.join("..", "environment.rb"), __FILE__)
@@ -31,11 +31,15 @@ load ::File.expand_path(File.join("..", "environment.rb"), __FILE__)
31
31
  load ::File.expand_path(File.join("..", "routes.rb"), __FILE__)
32
32
 
33
33
  # start the plezi EM, to make sure that the plezi async code doesn't break.
34
- GReactor.clear_listeners
35
- GReactor.start Plezi::Settings.max_threads
34
+ if Rack::Handler.default == GRHttp::Base::Rack
35
+ run(Proc.new { [404, {}, []] })
36
+ else
37
+ GReactor.clear_listeners
38
+ GReactor.start Plezi::Settings.max_threads
36
39
 
37
- # run the Rack app - not yet supported
38
- # run Plezi::Base::Rack
40
+ # run the Rack app - not yet supported
41
+ # run Plezi::Base::Rack
39
42
 
40
- # # exit rack to start the plezi server
41
- # Process.kill 'TERM'
43
+ # # exit rack to start the plezi server
44
+ # Process.kill 'TERM'
45
+ end
@@ -6,27 +6,26 @@
6
6
  ## Set up root object, it might be used by the environment and\or the plezi extension gems.
7
7
  Root ||= Pathname.new(File.dirname(__FILE__)).expand_path
8
8
  ## Set a persistent session token id name
9
- GRHttp.session_token = 'appname_uui'
10
- ## make sure all file access and file loading is relative to the application's root folder
11
- # Dir.chdir Root.to_s
12
- ## load code from a subfolder called 'code'
13
- # Dir[File.join "{code}", "**" , "*.rb"].each {|file| load File.expand_path(file)}
14
- ## OR load code from all the ruby files in the main forlder (subfolder inclussion will fail on PaaS)
15
- # Dir[File.join File.dirname(__FILE__), "*.rb"].each {|file| load File.expand_path(file) unless file == __FILE__}
16
-
17
9
  ## If this app is independant, use bundler to load gems (including the plezi gem).
18
10
  ## Else, use the original app's Gemfile and start Plezi's Rack mode.
19
11
  require 'bundler'
20
12
  Bundler.require(:default, ENV['ENV'].to_s.to_sym)
21
13
  ## OR:
22
14
  # Plesi.start_rack # remember
15
+ GRHttp.session_token = 'appname_uui'
16
+ ## make sure all file access and file loading is relative to the application's root folder
17
+ # Dir.chdir Root.to_s
18
+ ## load code from a subfolder called 'app'
19
+ # Dir[File.join "{app}", "**" , "*.rb"].each {|file| load File.expand_path(file)}
20
+ ## OR load code from all the ruby files in the main forlder (subfolder inclussion will fail on PaaS)
21
+ # Dir[File.join File.dirname(__FILE__), "*.rb"].each {|file| load File.expand_path(file) unless file == __FILE__}
23
22
 
24
23
  ## Uncomment to create a log file
25
24
  # GReactor.create_logger File.expand_path(Root.join('server.log').to_s)
26
25
 
27
26
  ## Options for Scaling the app (across processes or machines):
28
- ## uncomment to set up forking.
29
- # GReactor::Settings.set_forking 4
27
+ ## uncomment to set up forking for 3 more processes (total of 4).
28
+ # GReactor.forking 3
30
29
  ## Redis scaling
31
30
  # Plezi::Settings.redis_channel_name = 'appsecret'
32
31
  # ENV['PL_REDIS_URL'] ||= ENV['REDIS_URL'] || ENV['REDISCLOUD_URL'] || ENV['REDISTOGO_URL'] || "redis://username:password@my.host:6389"
@@ -248,10 +248,10 @@ function send_text()
248
248
  <p class="bold">Reviewing the sample code and feeding <a href="https://github.com/boazsegev/plezi">Plezi</a> your code:</p>
249
249
  <ul>
250
250
  <li>Review and update MyController in your 'appname.rb'.</li>
251
- <li>Delete this file (appname/templates/welcome.html.erb).</li>
252
- <li>Write your own controller and code, using a sub-foler as suggested in the 'appname.rb' file.</li>
251
+ <li>Edit or delete this file (appname/templates/welcome.html.erb).</li>
252
+ <li>Write your own controller and code, maybe using a sub-foler as suggested in the 'appname.rb' file.</li>
253
253
  <li>Set up your routes in the 'appname.rb' file.</li>
254
- <li>Edit the javascript for the client in your 'appname/websockets.js' file.</li>
254
+ <li>Edit the javascript for the client in your 'appname/websockets.js' file and link to it from your html.</li>
255
255
  </ul>
256
256
  </li>
257
257
  <li>
@@ -88,7 +88,7 @@ class TestCtrl
88
88
  true
89
89
  end
90
90
  def _stream_out
91
- response.send "streamed"
91
+ response << "streamed"
92
92
  true
93
93
  end
94
94
  def file_test
@@ -303,7 +303,11 @@ module PleziTestTasks
303
303
  puts e.message
304
304
  end
305
305
  remote = GRHttp::WSClient.connect_to("wss://echo.websocket.org/") {|ws| puts " * Extra Websocket Remote test (SSL: echo.websocket.org): #{RESULTS[ws.data == 'Hello websockets!']}"; response.close}
306
- remote << "Hello websockets!"
306
+ if remote.closed?
307
+ puts " * Extra Websocket Remote test (SSL: echo.websocket.org): #{RESULTS[false]}"
308
+ else
309
+ remote << "Hello websockets!"
310
+ end
307
311
  sleep 0.5
308
312
  [ws1, ws2, ws3, ws4, remote].each {|ws| ws.close}
309
313
  PL.on_shutdown {puts " * Websocket connection message test: #{RESULTS[connection_test]}" unless connection_test}
@@ -475,7 +479,7 @@ Plezi.start_async
475
479
  PleziTestTasks.run_tests
476
480
 
477
481
  # ENV['PL_REDIS_URL'] ||= ENV['REDIS_URL'] || ENV['REDISCLOUD_URL'] || ENV['REDISTOGO_URL'] || "redis://test:1234@pub-redis-11008.us-east-1-4.5.ec2.garantiadata.com:11008"
478
- # GReactor::Settings.set_forking 4
482
+ # GReactor.forking 4
479
483
  # GR.run_async { PleziTestTasks.run_tests }
480
484
  # start_services
481
485
 
@@ -109,7 +109,7 @@ Last, but not least, we tell Plezi to connect the root of our web application to
109
109
  route '/', ChatController
110
110
  ```
111
111
 
112
- Plezi controller classes are like virtual folders with special support for RESTful methods (`index`, `new`, `save`, `update`, `delete`), HTTP filters and helpers (`before`, `after`, `redirect_to`, `send_data`), WebSockets methods (`on_connect`, `on_message(data)`, `on_disconnect`), and WebSockets filters and helpers (`pre-connect`, `broadcast`, `collect`).
112
+ Plezi controller classes are like virtual folders with special support for RESTful methods (`index`, `new`, `save`, `update`, `delete`), HTTP filters and helpers (`before`, `after`, `redirect_to`, `send_data`), WebSockets methods (`on_open`, `on_message(data)`, `on_close`), and WebSockets filters and helpers (`pre-connect`, `broadcast`, `unicast` etc').
113
113
 
114
114
  Plezi uses a common special parameter called 'id' to help with all this magic... if we don't define this parameter ourselves, Plezi will try to append this parameter to the end our route's path. So, actually, our route looks like this:
115
115
 
@@ -213,11 +213,11 @@ end
213
213
  To design a chatroom we will need a few things:
214
214
 
215
215
  1. We will need to force people identify themselves by choosing nicknames - to do this we will define the `on_connect` method to refuse any connections that don't have a nickname.
216
- 2. We will want to make sure these nicknames are unique and don't give a wrong sense of authority (nicknames such as 'admin' should be forbidden) - for now, we will simply collect the nicknames from all the other active connections using the `collect` method and use that in our `on_connect` method.
216
+ 2. We will want to make sure these nicknames are unique and don't give a wrong sense of authority (nicknames such as 'admin' should be forbidden) - for now, we will simply refuse the 'wrong' type of nicknames and leave uniqieness for another time.
217
217
  3. We will want to push messages we recieve to all the other chatroom members - to do this we will use the `broadcast` method in our `on_message(data)` method.
218
218
  4. We will also want to tell people when someone left the chatroom - to do this we can define an `on_disconnect` method and use the `broadcast` method in there.
219
219
 
220
- We can use the :id parameter to collect the nickname.
220
+ We can use the :id parameter to set the nickname.
221
221
 
222
222
  the :id is an automatic parameter that Plezi appended to our path like already explained and it's perfect for our simple needs.
223
223
 
@@ -225,9 +225,9 @@ We could probably rewrite our route to something like this: `route '/(:id)/(:nic
225
225
 
226
226
  ####Broadcasting chat (websocket) messages
227
227
 
228
- When we get a chat message, with `on_message(data)`, we will want to broadcast this message to all the _other_ ChatController connections.
228
+ When we get a chat message, with `on_message(data)`, we will want to broadcast this message to all the _other_ ChatController connections.
229
229
 
230
- Using JSON, our new `on_message(data)` method can look something like this:
230
+ Using JSON, our new `on_message(data)` method can look something like this:
231
231
 
232
232
  ```ruby
233
233
  def on_message data
@@ -239,7 +239,7 @@ def on_message data
239
239
  return false
240
240
  end
241
241
  message = {}
242
- message[:message] = data["message"]
242
+ message[:message] = GRHttp.HTTP.escape data['message'] # we should sanitize the data
243
243
  message[:event] = :chat
244
244
  message[:from] = params[:id]
245
245
  message[:at] = Time.now
@@ -258,11 +258,11 @@ def on_message data
258
258
  response.close
259
259
  return false
260
260
  end
261
- broadcast :_send_message, {event: :chat, from: params[:id], message: data["message"], at: Time.now}.to_json
261
+ broadcast :_send_message, {event: :chat, from: params[:id], message: GRHttp.HTTP.escape(data['message']), at: Time.now}.to_json
262
262
  end
263
263
  ```
264
264
 
265
- Now that the code is shorter, let's look at that last line - the one that calls `broadcast`
265
+ Now that the boring stuff is condenced, let's look at that last line - the one that calls `broadcast`
266
266
 
267
267
  `broadcast` is an interesing Plezi feature that allows us to tell all the _other_ connection to run a method. It is totally asynchroneos, so we don't wait for it to complete.
268
268
 
@@ -274,9 +274,9 @@ Let's start with the name - why the underscore at the beginning?
274
274
 
275
275
  Plezi knows that sometimes we will want to create public methods that aren't available as a path - remember the `people` method, it was automatically recognized as an HTTP path...
276
276
 
277
- Plezi allows us to 'exclude' some methods from this auto-recogntion. protected methods and methods starting with an underscore (\_) aren't recognized by the Plezi router.
277
+ Plezi allows us to 'exclude' some methods from this auto-recogntion. protected methods and methods starting with an underscore (\_) are ignored by the Plezi router.
278
278
 
279
- Since we want the `_send_message` to be called by the `broadcast` method - it must be a public method (otherwise, we will not be able to call it for _other_ connections, only for our own connection).
279
+ Since I was too lzy to write the `protected` keyword, I just added an underscore at the begining of the name.
280
280
 
281
281
  This will be our `_send_message` method:
282
282
 
@@ -288,7 +288,7 @@ end
288
288
 
289
289
  Did you notice the difference between WebSocket responses and HTTP?
290
290
 
291
- In WebSockets, we don't automatically send string data (this is an important safeguard) and we must use the `<<` method to add data to the response stream.
291
+ Many times, Websockets are used to do internal work. This is why information is safeguarded and isn't automatically sent back (unlike HTTP, where a response is expected). In WebSockets, we must use the `<<` method to add data to the response stream.
292
292
 
293
293
 
294
294
  ####Telling people that we left the chatroom
@@ -322,7 +322,7 @@ If we ever write a real chatroom, our login process will look somewhat different
322
322
  First, we will ensure the new connection has a nickname (the connection was made to '/nickname' rather then the root of our application '/'):
323
323
 
324
324
  ```ruby
325
- def on_connect
325
+ def on_open
326
326
  if params[:id].nil?
327
327
  response << {event: :error, from: :system, at: Time.now, message: "Error: cannot connect without a nickname!"}.to_json
328
328
  response.close
@@ -331,18 +331,26 @@ def on_connect
331
331
  end
332
332
  ```
333
333
 
334
- Easy.
334
+ Easy?
335
+
336
+ There's an even easier and safer way to do this, which doesn't send an error message back, it looks like this:
337
+
338
+ ```ruby
339
+ def pre_connect
340
+ return false if params[:id].nil?
341
+ true
342
+ end
343
+ ```
335
344
 
336
- Next, we will ask everybody else who is connected to tell us their nicknames - we will test the new nickname against this list and make sure the nickname is unique.
345
+ Since Websocket connections start as an HTTP GET request, the pre-connect is called while still in 'HTTP mode', allowing us to use HTTP logic and refuse connections even before any websocket data can be sent by the 'client'. This is definitly the safer approach.
337
346
 
338
- We will also add some reserved names to this list, to make sure nobody impersonates a system administrator... let's add this code to our `on_connect` method:
347
+ Next, we will check if the nickname is on the reserved names list, to make sure nobody impersonates a system administrator... let's add this code to our `on_open` method:
339
348
 
340
349
  ```ruby
341
350
  message = {from: '', at: Time.now}
342
- list = collect(:_ask_nickname)
343
- if (list + ['admin', 'system', 'sys', 'administrator']).include? params[:id]
351
+ if params[:id].match /admin|admn|system/i
344
352
  message[:event] = :error
345
- message[:message] = "The nickname '#{params[:id]}' is already taken."
353
+ message[:message] = "The nickname '#{params[:id]}' is refused."
346
354
  response << message.to_json
347
355
  params[:id] = false
348
356
  response.close
@@ -350,20 +358,11 @@ We will also add some reserved names to this list, to make sure nobody impersona
350
358
  end
351
359
  ```
352
360
 
353
- Hmm.. **collect**? what is the `collect` method? - well, this is a little bit of more Plezi magic that allows us to ask and collect information from all the _other_ active connections. This method returns an array of all the responses.
354
-
355
- We will use `collect` to get an array of all the connected nicknames - we will write the `_ask_nickname` method in just a bit.
356
-
357
361
  Then, if all is good, we will welcome the new connection to our chatroom. We will also tell the new guest who is already connected and broadcast their arrivale to everybody else...:
358
362
 
359
363
  ```ruby
360
364
  message = {from: '', at: Time.now}
361
365
  message[:event] = :chat
362
- if list.empty?
363
- message[:message] = "Welcome! You're the first one here."
364
- else
365
- message[:message] = "Welcome! #{list[0..-2].join(', ')} #{list[1] ? 'and' : ''} #{list.last} #{list[1] ? 'are' : 'is'} already here."
366
- end
367
366
  response << message.to_json
368
367
  message[:message] = "#{params[:id]} joined the chatroom."
369
368
  broadcast :_send_message, message.to_json
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.10.17
4
+ version: 0.11.0
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-08-10 00:00:00.000000000 Z
11
+ date: 2015-09-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: grhttp
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 0.0.23
19
+ version: 0.1.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 0.0.23
26
+ version: 0.1.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: bundler
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -62,12 +62,18 @@ extensions: []
62
62
  extra_rdoc_files: []
63
63
  files:
64
64
  - ".gitignore"
65
+ - ".yardopts"
65
66
  - CHANGELOG.md
66
67
  - Gemfile
67
68
  - LICENSE.txt
68
69
  - README.md
69
70
  - Rakefile
70
71
  - bin/plezi
72
+ - docs/async_helpers.md
73
+ - docs/http_helpers.md
74
+ - docs/logging.md
75
+ - docs/routes.md
76
+ - docs/websockets.md
71
77
  - lib/plezi.rb
72
78
  - lib/plezi/builders/app_builder.rb
73
79
  - lib/plezi/builders/builder.rb
@@ -148,7 +154,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
148
154
  version: '0'
149
155
  requirements: []
150
156
  rubyforge_project:
151
- rubygems_version: 2.4.7
157
+ rubygems_version: 2.4.5.1
152
158
  signing_key:
153
159
  specification_version: 4
154
160
  summary: Plezi - the easy way to add Websockets, RESTful routing and HTTP streaming