plezi 0.7.7 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +26 -0
  3. data/README.md +3 -3
  4. data/lib/plezi.rb +35 -36
  5. data/lib/plezi/common/cache.rb +94 -0
  6. data/lib/plezi/{base → common}/dsl.rb +87 -68
  7. data/lib/plezi/{base → common}/logging.rb +15 -15
  8. data/lib/plezi/eventmachine/connection.rb +192 -0
  9. data/lib/plezi/eventmachine/em.rb +95 -0
  10. data/lib/plezi/eventmachine/io.rb +265 -0
  11. data/lib/plezi/eventmachine/protocol.rb +54 -0
  12. data/lib/plezi/eventmachine/queue.rb +51 -0
  13. data/lib/plezi/eventmachine/ssl_connection.rb +138 -0
  14. data/lib/plezi/eventmachine/timers.rb +117 -0
  15. data/lib/plezi/eventmachine/workers.rb +35 -0
  16. data/lib/plezi/handlers/http_host.rb +4 -4
  17. data/lib/plezi/handlers/magic_helpers.rb +1 -21
  18. data/lib/plezi/handlers/route.rb +3 -3
  19. data/lib/plezi/server/{helpers/http.rb → http.rb} +1 -57
  20. data/lib/plezi/server/{protocols/http_protocol.rb → http_protocol.rb} +18 -35
  21. data/lib/plezi/server/{protocols/http_request.rb → http_request.rb} +1 -1
  22. data/lib/plezi/server/{protocols/http_response.rb → http_response.rb} +45 -22
  23. data/lib/plezi/server/{helpers/mime_types.rb → mime_types.rb} +0 -0
  24. data/lib/plezi/server/{protocols/websocket.rb → websocket.rb} +34 -37
  25. data/lib/plezi/server/{protocols/ws_response.rb → ws_response.rb} +37 -19
  26. data/lib/plezi/version.rb +1 -1
  27. data/plezi.gemspec +3 -3
  28. data/resources/config.ru +9 -11
  29. metadata +27 -30
  30. data/lib/plezi/base/cache.rb +0 -89
  31. data/lib/plezi/base/connections.rb +0 -33
  32. data/lib/plezi/base/engine.rb +0 -77
  33. data/lib/plezi/base/events.rb +0 -93
  34. data/lib/plezi/base/io_reactor.rb +0 -62
  35. data/lib/plezi/base/rack_app.rb +0 -89
  36. data/lib/plezi/base/services.rb +0 -57
  37. data/lib/plezi/base/timers.rb +0 -71
  38. data/lib/plezi/server/README.md +0 -33
  39. data/lib/plezi/server/services/basic_service.rb +0 -224
  40. data/lib/plezi/server/services/no_service.rb +0 -196
  41. data/lib/plezi/server/services/ssl_service.rb +0 -193
@@ -1,62 +0,0 @@
1
-
2
- module Plezi
3
-
4
- module_function
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
27
-
28
- # DANGER ZONE - Plezi Engine. the io reactor mutex
29
- IO_LOCKER = Mutex.new
30
-
31
- # Plezi Engine, DO NOT CALL. waits on IO and pushes events. all threads hang while reactor is active (unless events are already 'in the pipe'.)
32
- def io_reactor
33
- IO_LOCKER.synchronize do
34
- return false unless EVENTS.empty?
35
- united = SERVICES.keys + IO_CONNECTION_DIC.keys
36
- return false if united.empty?
37
- io_r = (IO.select(united, nil, united, @idle_sleep) ) #rescue false)
38
- if io_r
39
- io_r[0].each do |io|
40
- if SERVICES[io]
41
- begin
42
- connection = io.accept_nonblock
43
- push_event method(:add_connection), connection, SERVICES[io]
44
- rescue Errno::EWOULDBLOCK => e
45
-
46
- rescue Exception => e
47
- error e
48
- # SERVICES.delete s if s.closed?
49
- end
50
- elsif IO_CONNECTION_DIC[io]
51
- push_event IO_CONNECTION_DIC[io].method(:on_message)
52
- else
53
- IO_CONNECTION_DIC.delete(io)
54
- SERVICES.delete(io)
55
- end
56
- end
57
- io_r[2].each { |io| (IO_CONNECTION_DIC.delete(io) || SERVICES.delete(io)).close rescue true }
58
- end
59
- end
60
- true
61
- end
62
- end
@@ -1,89 +0,0 @@
1
-
2
- module Plezi
3
-
4
- # Rack application model support
5
- module_function
6
-
7
- # todo: move falsh into the special cookie class...?
8
-
9
-
10
- # Plezi dresses up for Rack - this is a watered down version missing some features (such as flash and WebSockets).
11
- # a full featured Plezi app, with WebSockets, requires the use of the Plezi server
12
- # (the built-in server)
13
- def call env
14
- raise "No Plezi Services" unless Plezi::SERVICES[0]
15
- Object.const_set('PLEZI_ON_RACK', true) unless defined? PLEZI_ON_RACK
16
-
17
- # re-encode to utf-8, as it's all BINARY encoding at first
18
- env['rack.input'].rewind
19
- env['rack.input'] = StringIO.new env['rack.input'].read.encode('utf-8', 'binary', invalid: :replace, undef: :replace, replace: '')
20
- env.each do |k, v|
21
- if k.to_s.match /^[A-Z]/
22
- if v.is_a?(String) && !v.frozen?
23
- v.force_encoding('binary').encode!('utf-8', 'binary', invalid: :replace, undef: :replace, replace: '') unless v.force_encoding('utf-8').valid_encoding?
24
- end
25
- end
26
- end
27
- # re-key params
28
- # new_params = {}
29
- # env[:params].each {|k,v| HTTP.add_param_to_hash k, v, new_params}
30
- # env[:params] = new_params
31
-
32
- # make hashes magical
33
- make_hash_accept_symbols(env)
34
-
35
- # use Plezi Cookies
36
- env['rack.request.cookie_string'] = env['HTTP_COOKIE']
37
- env['rack.request.cookie_hash'] = Plezi::Cookies.new.update(env['rack.request.cookie_hash'] || {})
38
-
39
- # chomp path
40
- env['PATH_INFO'].chomp! '/'
41
-
42
- # get response
43
- response = Plezi::SERVICES[0][1][:handler].call env
44
-
45
- return response if response.is_a?(Array)
46
-
47
- response.finish
48
- response.fix_headers
49
- headers = response.headers
50
- # set cookie headers
51
- headers.delete 'transfer-encoding'
52
- headers.delete 'connection'
53
- unless response.cookies.empty?
54
- headers['Set-Cookie'] = []
55
- response.cookies.each {|k,v| headers['Set-Cookie'] << ("#{k.to_s}=#{v.to_s}")}
56
- end
57
- [response.status, headers, response.body]
58
- end
59
- end
60
-
61
- # # rack code to set cookie headers
62
- # # File actionpack/lib/action_controller/vendor/rack-1.0/rack/response.rb, line 56
63
- # def set_cookie(key, value)
64
- # case value
65
- # when Hash
66
- # domain = "; domain=" + value[:domain] if value[:domain]
67
- # path = "; path=" + value[:path] if value[:path]
68
- # # According to RFC 2109, we need dashes here.
69
- # # N.B.: cgi.rb uses spaces...
70
- # expires = "; expires=" + value[:expires].clone.gmtime.
71
- # strftime("%a, %d-%b-%Y %H:%M:%S GMT") if value[:expires]
72
- # secure = "; secure" if value[:secure]
73
- # httponly = "; HttpOnly" if value[:httponly]
74
- # value = value[:value]
75
- # end
76
- # value = [value] unless Array === value
77
- # cookie = Utils.escape(key) + "=" +
78
- # value.map { |v| Utils.escape v }.join("&") +
79
- # "#{domain}#{path}#{expires}#{secure}#{httponly}"
80
-
81
- # case self["Set-Cookie"]
82
- # when Array
83
- # self["Set-Cookie"] << cookie
84
- # when String
85
- # self["Set-Cookie"] = [self["Set-Cookie"], cookie]
86
- # when nil
87
- # self["Set-Cookie"] = cookie
88
- # end
89
- # end
@@ -1,57 +0,0 @@
1
-
2
- module Plezi
3
-
4
- module_function
5
-
6
- #######################
7
- ## Services pooling and calling
8
-
9
- # DANGER ZONE - Plezi Engine. the services store
10
- SERVICES = {}
11
- # DANGER ZONE - Plezi Engine. the services mutex
12
- S_LOCKER = Mutex.new
13
-
14
- # public API to add a service to the framework.
15
- # accepts:
16
- # port:: port number
17
- # parameters:: a hash of parameters that are passed on to the service for handling (and from there, service dependent, to the protocol and/or handler).
18
- #
19
- # parameters are any of the following:
20
- # host:: the host name. defaults to any host not explicitly defined (a catch-all).
21
- # alias:: a String or an Array of Strings which represent alternative host names (i.e. `alias: ["admin.google.com", "admin.gmail.com"]`).
22
- # root:: the public root folder. if this is defined, static files will be served from the location.
23
- # assets:: the assets root folder. defaults to nil (no assets support). if the path is defined, assets will be served from `/assets/...` (or the public_asset path defined) before any static files. assets will not be served if the file in the /public/assets folder if up to date (a rendering attempt will be made for systems that allow file writing).
24
- # assets_public:: the assets public uri location (uri format, NOT a file path). defaults to `/assets`. assets will be saved (or rendered) to the assets public folder and served as static files.
25
- # assets_callback:: a method that accepts one parameters: `request` and renders any custom assets. the method should return `false` unless it has created a response object (`response = Plezi::HTTPResponse.new(request)`) and sent a response to the client using `response.finish`.
26
- # save_assets:: saves the rendered assets to the filesystem, under the public folder. defaults to false.
27
- # templates:: the templates root folder. defaults to nil (no template support). templates can be rendered by a Controller class, using the `render` method.
28
- # ssl:: if true, an SSL service will be attempted. if no certificate is defined, an attempt will be made to create a self signed certificate.
29
- # ssl_key:: the public key for the SSL service.
30
- # ssl_cert:: the certificate for the SSL service.
31
- #
32
- #
33
- # assets:
34
- #
35
- # assets support will render `.sass`, `.scss` and `.coffee` and save them as local files (`.css`, `.css`, and `.js` respectively) before sending them as static files. if it is impossible to write the files, they will be rendered dynamically for every request (it would be better to render them before-hand).
36
- #
37
- # templates:
38
- #
39
- # templates can be either an ERB file on a Haml file.
40
- #
41
- def add_service port, parameters = {}
42
- parameters[:port] ||= port
43
- parameters[:service_type] ||= ( parameters[:ssl] ? SSLService : BasicService)
44
- service = nil
45
- service = parameters[:service_type].create_service(port, parameters) unless ( defined?(BUILDING_PLEZI_TEMPLATE) || defined?(PLEZI_ON_RACK) )
46
- S_LOCKER.synchronize {SERVICES[service] = parameters}
47
- Plezi.callback Plezi, :info, "Started listening on port #{port}."
48
- true
49
- end
50
-
51
- # Plezi Engine, DO NOT CALL. stops all services - active connection will remain open until completion.
52
- def stop_services
53
- info 'Stopping services'
54
- S_LOCKER.synchronize {SERVICES.each {|s, p| s.close rescue true; info "Stoped listening on port #{p[:port]}"}; SERVICES.clear }
55
- end
56
-
57
- end
@@ -1,71 +0,0 @@
1
-
2
- module Plezi
3
-
4
- module_function
5
-
6
- #######################
7
- ## Timed Events / Multi-tasking
8
-
9
- # DANGER ZONE - Plezi Engine. an Array containing all the current events.
10
- TIMERS = []
11
- # DANGER ZONE - Plezi Engine. the Mutex locker for the event machine.
12
- T_LOCKER = Mutex.new
13
-
14
- # This class is used by Plezi to hold events and push them into the events stack when the time comes.
15
- class TimedEvent
16
-
17
- def initialize seconds, repeat, handler, args, block
18
- @time = Time.now + seconds
19
- @seconds, @repeat, @handler, @args, @block = seconds, repeat, handler, args, block
20
- end
21
-
22
- def done?
23
- return false unless @time <= Time.now
24
- Plezi.push_event @handler, *@args, &@block
25
- return true unless @repeat
26
- @time = Time.now + @seconds
27
- false
28
- end
29
-
30
- def stop_repeat
31
- @repeat = false
32
- end
33
-
34
- attr_reader :timed
35
- end
36
-
37
- # returns true if there are any unhandled events
38
- def timers?
39
- T_LOCKER.synchronize {!TIMERS.empty?}
40
- end
41
-
42
- # pushes a timed event to the timers's stack
43
- #
44
- # accepts:
45
- # seconds:: the minimal amount of seconds to wait before calling the handler's `call` method.
46
- # handler:: an object that answers to `call`, usually a Proc or a method.
47
- # *arg:: any arguments that will be passed to the handler's `call` method.
48
- #
49
- # if a block is passed along, it will be used as a callback: the block will be called with the values returned by the handler's `call` method.
50
- def run_after seconds, handler, *args, &block
51
- T_LOCKER.synchronize {TIMERS << TimedEvent.new(seconds, false, handler, args, block); TIMERS.last}
52
- end
53
-
54
- # pushes a repeated timed event to the timers's stack
55
- #
56
- # accepts:
57
- # seconds:: the minimal amount of seconds to wait before calling the handler's `call` method.
58
- # handler:: an object that answers to `call`, usually a Proc or a method.
59
- # *arg:: any arguments that will be passed to the handler's `call` method.
60
- #
61
- # if a block is passed along, it will be used as a callback: the block will be called with the values returned by the handler's `call` method.
62
- def run_every seconds, handler, *args, &block
63
- T_LOCKER.synchronize {TIMERS << TimedEvent.new(seconds, true, handler, args, block); TIMERS.last}
64
- end
65
-
66
- # DANGER ZONE - Used by the Plezi engine to review timed events and push them to the event stack
67
- def fire_timers
68
- return false if T_LOCKER.locked?
69
- T_LOCKER.synchronize { TIMERS.delete_if {|t| t.done? } }
70
- end
71
- end
@@ -1,33 +0,0 @@
1
- # server object\file structure
2
-
3
- - services
4
-
5
- this folder holds the modules and classes regarding the core-services classes (normal TCP/IP service and SSL service should be here).
6
-
7
- services have one protocol that parses incoming requests and one handler that takes the parsed request and responds to it.
8
-
9
- both protocols and handlers can be changed mid-stream, allowing a service to switch protocols (such as from HTTP to WebSockets, HTTP/1.1 to SPDY etc') or a handler.
10
-
11
- - protocols
12
-
13
- this folder holds the different protocols that could be run over the each socket-service (HTTP / WebSockets etc' should be here).
14
-
15
- the protocols are devided into two different classes/object types:
16
-
17
- 1. parsing input.
18
- 2. formatting output.
19
-
20
- - handlers
21
-
22
- this folder holds the classes and modules used to actually handle the requests parsed by the protocol layer.
23
-
24
- these are the classes and modules the Plezi framework users (developers) connect with when writing their web apps.
25
-
26
- ## servers, services and protocols ... what?
27
-
28
- services are the part of the server that recieves and sends data - services run specific protocols that together make up the whole of a server.
29
-
30
- this division allows the user to change protocols mid-stream when allowed (such as switching from HTTP to WebSockets).
31
-
32
- this abstraction to the sockets layer allows support for future or custom protocols without any changes to the abstraction layer.
33
-
@@ -1,224 +0,0 @@
1
- module Plezi
2
-
3
- # this class is a basic TCP socket service.
4
- #
5
- # a protocol should be assigned, or the service will fall back to an echo service.
6
- #
7
- # a protocol should answer to: on_connect(service), on_message(service, data), on_disconnect(service) and on_exception(service, exception)
8
- #
9
- # the on_message method should return any data that wasn't used (to be sent again as part of the next `on_message` call, once more data is received).
10
- #
11
- # if the protocol is a class, these methods should be instance methods.
12
- # a protocol class should support the initialize(service, parameters={}) method as well.
13
- #
14
- # to-do: fix logging
15
- class BasicService
16
-
17
- # create a listener (io) - will create a TCPServer socket
18
- #
19
- # listeners are 'server sockets' that answer to `accept` by creating a new connection socket (io).
20
- def self.create_service port, parameters
21
- TCPServer.new(port)
22
- end
23
-
24
- # instance methods
25
-
26
- attr_reader :socket, :locker, :closed, :parameters, :out_que, :active_time
27
- attr_accessor :protocol, :handler, :timeout
28
-
29
- # creates a new connection wrapper object for the new socket that was recieved from the `accept_nonblock` method call.
30
- def initialize socket, parameters = {}
31
- @handler = parameters[:handler]
32
- @socket = socket
33
- # socket.setsockopt Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, "\n\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" #== [10 sec 0 usec].pack '1_2'
34
- @out_que = []
35
- @locker = Mutex.new
36
- @parameters = parameters
37
- @protocol = parameters[:protocol]
38
- @protocol = protocol.new self, parameters if protocol.is_a?(Class)
39
- @protocol.on_connect self if @protocol && @protocol.methods.include?(:on_connect)
40
- touch
41
- @timeout ||= 5
42
- # Plezi.callback self, :on_message
43
- end
44
-
45
- # # sets a connection timeout
46
- # def set_timeout timeout = 8
47
- # @timeout = timeout
48
- # end
49
-
50
- # checks if a connection timed out
51
- def timedout?
52
- Time.now - @active_time > @timeout
53
- end
54
-
55
- # resets the timer for the connection timeout
56
- def touch
57
- @active_time = Time.now
58
- end
59
-
60
- # returns an IO-like object used for reading/writing (unlike the original IO object, this can be an SSL layer or any other wrapper object).
61
- def io
62
- touch
63
- @socket
64
- end
65
-
66
- # sends data immidiately - forcing the data to be sent, flushing any pending messages in the que
67
- def send data = nil
68
- return if @out_que.empty? && data.nil?
69
- locker.synchronize do
70
- unless @out_que.empty?
71
- @out_que.each { |d| _send d rescue disconnect }
72
- @out_que.clear
73
- end
74
- (_send data rescue disconnect) if data
75
- touch
76
- end
77
- end
78
-
79
- # sends data immidiately, interrupting any pending que and ignoring thread safety.
80
- def send_unsafe_interrupt data = nil
81
- touch
82
- _send data rescue disconnect
83
- end
84
-
85
- # sends data without waiting - data might be sent in a different order then intended.
86
- def send_nonblock data
87
- touch
88
- locker.synchronize {@out_que << data}
89
- Plezi.callback(self, :send)
90
- end
91
-
92
- # adds data to the out buffer - but doesn't send the data until a send event is called.
93
- def << data
94
- touch
95
- locker.synchronize {@out_que << data}
96
- end
97
-
98
- # makes sure any data in the que is send and calls `flush` on the socket, to make sure the buffer is sent.
99
- def flush
100
- begin
101
- send
102
- socket.flush
103
- rescue Exception => e
104
-
105
- end
106
- end
107
-
108
- # event based interface for messages.
109
-
110
- # notice: since it is all async evet base - multipart messages might be garbled up...?
111
- # todo: protect from garbeling.
112
- def on_message
113
- # return false if locker.locked?
114
- return false if locker.locked?
115
- begin
116
-
117
- locker.synchronize do
118
- return disconnect if _disconnected?
119
- protocol.on_message(self)
120
- end
121
-
122
- rescue Exception => e
123
- return disconnect
124
- end
125
- end
126
-
127
- # called once a socket is disconnected or needs to be disconnected.
128
- def on_disconnect
129
- Plezi.callback Plezi, :remove_connection, self
130
- locker.synchronize do
131
- @out_que.each { |d| _send d rescue true}
132
- @out_que.clear
133
- end
134
- Plezi.callback protocol, :on_disconnect, self if protocol
135
- close
136
- end
137
-
138
- # status markers
139
-
140
- # closes the connection
141
- def close
142
- locker.synchronize do
143
- _close rescue true
144
- end
145
- end
146
- # returns true if the service is disconnected
147
- def disconnected?
148
- _disconnected?
149
- end
150
- # disconects the service.
151
- def disconnect
152
- Plezi.callback self, :on_disconnect
153
- end
154
- # returns true if the socket has content to be read.
155
- def has_incoming_data?
156
- (socket.stat.size > 0) rescue false
157
- end
158
-
159
-
160
- # identification markers
161
-
162
- #returns the service type - set to normal
163
- def service_type
164
- 'normal'
165
- end
166
- #returns true if the service is encrypted using the OpenSSL library.
167
- def ssl?
168
- false
169
- end
170
-
171
- #################
172
- # overide the followind methods for any child class.
173
-
174
- # this is a public method and it should be used by child classes to implement each
175
- # read(_nonblock) action. accepts one argument ::size for an optional buffer size to be read.
176
- def read size = 1048576
177
- data = @socket.recv_nonblock( size )
178
- touch unless data.nil? || data.empty?
179
- return data
180
- rescue Exception => e
181
- return ''
182
- end
183
-
184
- protected
185
-
186
- # this is a protected method, it should be used by child classes to implement each
187
- # send action.
188
- def _send data
189
- # data.force_encoding "binary" rescue false
190
- len = data.bytesize
191
- act = @socket.send data, 0
192
- while len > act
193
- act += @socket.send data.byteslice(act..-1) , 0
194
- touch
195
- end
196
- end
197
- # this is a protected method, it should be used by child classes to implement each
198
- # close action. doesn't accept any arguments.
199
- def _close
200
- socket.flush rescue true
201
- socket.close
202
- end
203
- # this is a protected method, it should be used by child classes to tell if the socket
204
- # was closed (returns true/false).
205
- def _disconnected?
206
- socket.closed? rescue true # || socket.stat.mode == 0140222 rescue true # if mode is read only, it's the same as closed.
207
- end
208
-
209
- end
210
- end
211
-
212
-
213
- ######
214
- ## example requests
215
-
216
- # GET /parsed_request HTTP/1.1
217
- # Host: localhost:2000
218
- # Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
219
- # Cookie: user_token=2INa32_vDgx8Aa1qe43oILELpSdIe9xwmT8GTWjkS-w
220
- # User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10) AppleWebKit/600.1.25 (KHTML, like Gecko) Version/8.0 Safari/600.1.25
221
- # Accept-Language: en-us
222
- # Accept-Encoding: gzip, deflate
223
- # Connection: keep-alive
224
- # X-Forwarded-For: 127.0.0.3