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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +26 -0
- data/README.md +3 -3
- data/lib/plezi.rb +35 -36
- data/lib/plezi/common/cache.rb +94 -0
- data/lib/plezi/{base → common}/dsl.rb +87 -68
- data/lib/plezi/{base → common}/logging.rb +15 -15
- data/lib/plezi/eventmachine/connection.rb +192 -0
- data/lib/plezi/eventmachine/em.rb +95 -0
- data/lib/plezi/eventmachine/io.rb +265 -0
- data/lib/plezi/eventmachine/protocol.rb +54 -0
- data/lib/plezi/eventmachine/queue.rb +51 -0
- data/lib/plezi/eventmachine/ssl_connection.rb +138 -0
- data/lib/plezi/eventmachine/timers.rb +117 -0
- data/lib/plezi/eventmachine/workers.rb +35 -0
- data/lib/plezi/handlers/http_host.rb +4 -4
- data/lib/plezi/handlers/magic_helpers.rb +1 -21
- data/lib/plezi/handlers/route.rb +3 -3
- data/lib/plezi/server/{helpers/http.rb → http.rb} +1 -57
- data/lib/plezi/server/{protocols/http_protocol.rb → http_protocol.rb} +18 -35
- data/lib/plezi/server/{protocols/http_request.rb → http_request.rb} +1 -1
- data/lib/plezi/server/{protocols/http_response.rb → http_response.rb} +45 -22
- data/lib/plezi/server/{helpers/mime_types.rb → mime_types.rb} +0 -0
- data/lib/plezi/server/{protocols/websocket.rb → websocket.rb} +34 -37
- data/lib/plezi/server/{protocols/ws_response.rb → ws_response.rb} +37 -19
- data/lib/plezi/version.rb +1 -1
- data/plezi.gemspec +3 -3
- data/resources/config.ru +9 -11
- metadata +27 -30
- data/lib/plezi/base/cache.rb +0 -89
- data/lib/plezi/base/connections.rb +0 -33
- data/lib/plezi/base/engine.rb +0 -77
- data/lib/plezi/base/events.rb +0 -93
- data/lib/plezi/base/io_reactor.rb +0 -62
- data/lib/plezi/base/rack_app.rb +0 -89
- data/lib/plezi/base/services.rb +0 -57
- data/lib/plezi/base/timers.rb +0 -71
- data/lib/plezi/server/README.md +0 -33
- data/lib/plezi/server/services/basic_service.rb +0 -224
- data/lib/plezi/server/services/no_service.rb +0 -196
- data/lib/plezi/server/services/ssl_service.rb +0 -193
@@ -0,0 +1,54 @@
|
|
1
|
+
module Plezi
|
2
|
+
|
3
|
+
|
4
|
+
module EventMachine
|
5
|
+
|
6
|
+
# this module is the protocol (controller) for the HTTP server.
|
7
|
+
#
|
8
|
+
#
|
9
|
+
# to do: implemet logging, support body types: multipart (non-ASCII form data / uploaded files), json & xml
|
10
|
+
class Protocol
|
11
|
+
|
12
|
+
attr_accessor :connection, :params
|
13
|
+
attr_reader :buffer, :locker
|
14
|
+
|
15
|
+
# initializes the protocol object
|
16
|
+
def initialize connection, params
|
17
|
+
@buffer, @connection, @params, @locker = [], connection, params, connection.locker
|
18
|
+
end
|
19
|
+
|
20
|
+
# called when connection is initialized.
|
21
|
+
def on_connect
|
22
|
+
end
|
23
|
+
|
24
|
+
# called after data is recieved.
|
25
|
+
#
|
26
|
+
# this method is called within a lock on the connection (Mutex) - craeful from double locking.
|
27
|
+
def on_message
|
28
|
+
end
|
29
|
+
|
30
|
+
# # called when a disconnect is fired
|
31
|
+
# # (socket was disconnected / service should be disconnected / shutdown / socket error)
|
32
|
+
def on_disconnect
|
33
|
+
end
|
34
|
+
|
35
|
+
# called when an exception was raised
|
36
|
+
# (socket was disconnected / service should be disconnected / shutdown / socket error)
|
37
|
+
def on_exception
|
38
|
+
EventMachine.remove_io connection.io
|
39
|
+
Plezi.error e
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
## Heroku/extra headers info
|
46
|
+
|
47
|
+
# All headers are considered to be case-insensitive, as per HTTP Specification.
|
48
|
+
# X-Forwarded-For: the originating IP address of the client connecting to the Heroku router
|
49
|
+
# X-Forwarded-Proto: the originating protocol of the HTTP request (example: https)
|
50
|
+
# X-Forwarded-Port: the originating port of the HTTP request (example: 443)
|
51
|
+
# X-Request-Start: unix timestamp (milliseconds) when the request was received by the router
|
52
|
+
# X-Request-Id: the Heroku HTTP Request ID
|
53
|
+
# Via: a code name for the Heroku router
|
54
|
+
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Plezi
|
2
|
+
module EventMachine
|
3
|
+
module_function
|
4
|
+
|
5
|
+
QUEUE = []
|
6
|
+
QUEUE_LOCKER = Mutex.new
|
7
|
+
|
8
|
+
def queue args, job
|
9
|
+
raise "Job missing" unless job
|
10
|
+
QUEUE_LOCKER.synchronize { QUEUE << [job, args]}
|
11
|
+
true
|
12
|
+
end
|
13
|
+
|
14
|
+
def do_job
|
15
|
+
job, args = QUEUE_LOCKER.synchronize { QUEUE.shift }
|
16
|
+
job ? (job.call(*args) || true) : false
|
17
|
+
end
|
18
|
+
|
19
|
+
def queue_empty?
|
20
|
+
QUEUE.empty?
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
module_function
|
26
|
+
|
27
|
+
# Accepts a block and runs it asynchronously. This method runs asynchronously and returns immediately.
|
28
|
+
#
|
29
|
+
# use:
|
30
|
+
#
|
31
|
+
# Plezi.run_async(arg1, arg2, arg3 ...) { |arg1, arg2, arg3...| do_something }
|
32
|
+
#
|
33
|
+
# the block will be run within the current context, allowing access to current methods and variables.
|
34
|
+
def run_async *args, &block
|
35
|
+
EventMachine.queue args, block
|
36
|
+
end
|
37
|
+
|
38
|
+
# This method runs asynchronously and returns immediately.
|
39
|
+
#
|
40
|
+
# This method accepts:
|
41
|
+
# object:: an object who's method will be called.
|
42
|
+
# method:: the method's name to be called. type: Symbol.
|
43
|
+
# *args:: any arguments to be passed to the method.
|
44
|
+
#
|
45
|
+
# This method also accepts an optional block which will be run with the method's returned value within the existing context.
|
46
|
+
#
|
47
|
+
def callback object, method, *args, &block
|
48
|
+
block ? EventMachine.queue( args, (proc { |ar| block.call( object.method(method).call(*ar) ) }) ) : EventMachine.queue(args, object.method(method) )
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
|
4
|
+
|
5
|
+
module Plezi
|
6
|
+
|
7
|
+
module EventMachine
|
8
|
+
|
9
|
+
# represents an SSL connection
|
10
|
+
class SSLConnection < Connection
|
11
|
+
#the SSL socket
|
12
|
+
attr_reader :ssl_socket
|
13
|
+
|
14
|
+
def initialize socket, params
|
15
|
+
if params[:ssl] || params[:ssl_key] || params[:ssl_cert]
|
16
|
+
params[:ssl_cert], params[:ssl_key] = SSLConnection.self_cert unless params[:ssl_key] && params[:ssl_cert]
|
17
|
+
context = OpenSSL::SSL::SSLContext.new
|
18
|
+
context.set_params verify_mode: OpenSSL::SSL::VERIFY_NONE # OpenSSL::SSL::VERIFY_PEER #OpenSSL::SSL::VERIFY_NONE
|
19
|
+
# context.options DoNotReverseLookup: true
|
20
|
+
context.cert, context.key = params[:ssl_cert], params[:ssl_key]
|
21
|
+
context.cert_store = OpenSSL::X509::Store.new
|
22
|
+
context.cert_store.set_default_paths
|
23
|
+
@ssl_socket = OpenSSL::SSL::SSLSocket.new(socket, context)
|
24
|
+
@ssl_socket.sync_close = true
|
25
|
+
@ssl_socket.accept
|
26
|
+
end
|
27
|
+
raise "Not an SSL connection or SSL Socket creation failed" unless ssl_socket
|
28
|
+
super
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
# 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).
|
33
|
+
def io
|
34
|
+
@ssl_socket
|
35
|
+
end
|
36
|
+
|
37
|
+
# makes sure any data in the que is send and calls `flush` on the socket, to make sure the buffer is sent.
|
38
|
+
def flush
|
39
|
+
begin
|
40
|
+
send
|
41
|
+
@ssl_socket.flush
|
42
|
+
rescue => e
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# returns true if the service is disconnected
|
48
|
+
def disconnected?
|
49
|
+
(@socket.closed? || @ssl_socket.closed? || @socket.stat.mode == 0140222) rescue true # if mode is read only, it's the same as closed.
|
50
|
+
end
|
51
|
+
|
52
|
+
# identification markers
|
53
|
+
|
54
|
+
#returns the service type - set to normal
|
55
|
+
def service_type
|
56
|
+
'ssl'
|
57
|
+
end
|
58
|
+
#returns true if the service is encrypted using the OpenSSL library.
|
59
|
+
def ssl?
|
60
|
+
true
|
61
|
+
end
|
62
|
+
|
63
|
+
#################
|
64
|
+
# overide the followind methods for any child class.
|
65
|
+
|
66
|
+
# this is a public method and it should be used by child classes to implement each
|
67
|
+
# read(_nonblock) action. accepts one argument ::size for an optional buffer size to be read.
|
68
|
+
def read size = 1048576
|
69
|
+
data = ''
|
70
|
+
begin
|
71
|
+
loop { data << @ssl_socket.read_nonblock(size).to_s }
|
72
|
+
rescue => e
|
73
|
+
|
74
|
+
end
|
75
|
+
return false if data.to_s.empty?
|
76
|
+
touch
|
77
|
+
data
|
78
|
+
end
|
79
|
+
|
80
|
+
protected
|
81
|
+
|
82
|
+
# this is a protected method, it should be used by child classes to implement each
|
83
|
+
# send action.
|
84
|
+
def _send data
|
85
|
+
@active_time += 7200
|
86
|
+
@ssl_socket.write data
|
87
|
+
touch
|
88
|
+
end
|
89
|
+
|
90
|
+
public
|
91
|
+
|
92
|
+
# SSL certificate
|
93
|
+
|
94
|
+
# returns the current self-signed certificate - or creates a new one, if there is no current certificate.
|
95
|
+
def self.self_cert bits=2048, cn=nil, comment='a self signed certificate for when we only need encryption and no more.'
|
96
|
+
@@self_cert ||= create_cert
|
97
|
+
return *@@self_cert
|
98
|
+
end
|
99
|
+
#creates a self-signed certificate
|
100
|
+
def self.create_cert bits=2048, cn=nil, comment='a self signed certificate for when we only need encryption and no more.'
|
101
|
+
unless cn
|
102
|
+
host_name = Socket::gethostbyname(Socket::gethostname)[0].split('.')
|
103
|
+
cn = ''
|
104
|
+
host_name.each {|n| cn << "/DC=#{n}"}
|
105
|
+
cn << "/CN=#{host_name.join('.')}"
|
106
|
+
end
|
107
|
+
# cn ||= "CN=#{Socket::gethostbyname(Socket::gethostname)[0] rescue Socket::gethostname}"
|
108
|
+
|
109
|
+
rsa = OpenSSL::PKey::RSA.new(bits)
|
110
|
+
cert = OpenSSL::X509::Certificate.new
|
111
|
+
cert.version = 2
|
112
|
+
cert.serial = 1
|
113
|
+
name = OpenSSL::X509::Name.parse(cn)
|
114
|
+
cert.subject = name
|
115
|
+
cert.issuer = name
|
116
|
+
cert.not_before = Time.now
|
117
|
+
cert.not_after = Time.now + (365*24*60*60)
|
118
|
+
cert.public_key = rsa.public_key
|
119
|
+
|
120
|
+
ef = OpenSSL::X509::ExtensionFactory.new(nil,cert)
|
121
|
+
ef.issuer_certificate = cert
|
122
|
+
cert.extensions = [
|
123
|
+
ef.create_extension("basicConstraints","CA:FALSE"),
|
124
|
+
ef.create_extension("keyUsage", "keyEncipherment"),
|
125
|
+
ef.create_extension("subjectKeyIdentifier", "hash"),
|
126
|
+
ef.create_extension("extendedKeyUsage", "serverAuth"),
|
127
|
+
ef.create_extension("nsComment", comment),
|
128
|
+
]
|
129
|
+
aki = ef.create_extension("authorityKeyIdentifier",
|
130
|
+
"keyid:always,issuer:always")
|
131
|
+
cert.add_extension(aki)
|
132
|
+
cert.sign(rsa, OpenSSL::Digest::SHA1.new)
|
133
|
+
|
134
|
+
return cert, rsa
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
module Plezi
|
2
|
+
module EventMachine
|
3
|
+
|
4
|
+
# Every timed event is a member of the TimedEvent class and responds to it's methods.
|
5
|
+
class TimedEvent
|
6
|
+
|
7
|
+
# Sets/gets how often a timed event repeats, in seconds.
|
8
|
+
attr_accessor :interval
|
9
|
+
# Sets/gets how many times a timed event repeats.
|
10
|
+
# If set to false or -1, the timed event will repead until the application quits.
|
11
|
+
attr_accessor :repeat_limit
|
12
|
+
|
13
|
+
# Initialize a timed event.
|
14
|
+
def initialize interval, repeat_limit = -1, args=[], job=nil
|
15
|
+
@interval = interval
|
16
|
+
@repeat_limit = repeat_limit ? repeat_limit.to_i : -1
|
17
|
+
@job = job || (Proc.new { stop! })
|
18
|
+
@next = Time.now + interval
|
19
|
+
@args = args
|
20
|
+
end
|
21
|
+
|
22
|
+
# stops a timed event.
|
23
|
+
def stop!
|
24
|
+
@repeat_limit = 0
|
25
|
+
end
|
26
|
+
|
27
|
+
# Returns true if the timer is finished.
|
28
|
+
#
|
29
|
+
# If the timed event is due, this method will also add the event to the queue.
|
30
|
+
def done?(time = Time.now)
|
31
|
+
return false unless @next <= time
|
32
|
+
return true if @repeat_limit == 0
|
33
|
+
@repeat_limit -= 1 if @repeat_limit.to_i > 0
|
34
|
+
EventMachine.queue @args, @job
|
35
|
+
@next = time + @interval
|
36
|
+
@repeat_limit == 0
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
module_function
|
41
|
+
|
42
|
+
# the timers stack.
|
43
|
+
TIMERS = []
|
44
|
+
# the timers stack Mutex.
|
45
|
+
TIMERS_LOCK = Mutex.new
|
46
|
+
|
47
|
+
# Creates a TimedEvent object and adds it to the Timers stack.
|
48
|
+
def timed_job seconds, limit = false, args = [], block = nil
|
49
|
+
TIMERS_LOCK.synchronize {TIMERS << TimedEvent.new(seconds, limit, args, block); TIMERS.last}
|
50
|
+
end
|
51
|
+
|
52
|
+
# returns true if there are any unhandled events
|
53
|
+
def timers?
|
54
|
+
TIMERS.any?
|
55
|
+
end
|
56
|
+
|
57
|
+
# cycles through timed jobs, executing and/or deleting them if their time has come.
|
58
|
+
def fire_timers
|
59
|
+
TIMERS_LOCK.synchronize { time = Time.now; TIMERS.delete_if {|t| t.done? time} }
|
60
|
+
end
|
61
|
+
# clears all timers
|
62
|
+
def clear_timers
|
63
|
+
TIMERS_LOCK.synchronize { TIMERS.clear }
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
module_function
|
69
|
+
|
70
|
+
# # returns true if there are any unhandled events
|
71
|
+
# def timers?
|
72
|
+
# EventMachine.timers?
|
73
|
+
# end
|
74
|
+
|
75
|
+
# pushes a timed event to the timers's stack
|
76
|
+
#
|
77
|
+
# accepts:
|
78
|
+
# seconds:: the minimal amount of seconds to wait before calling the handler's `call` method.
|
79
|
+
# *arg:: any arguments that will be passed to the handler's `call` method.
|
80
|
+
# &block:: the block to execute.
|
81
|
+
#
|
82
|
+
# A block is required.
|
83
|
+
#
|
84
|
+
# Timed event's time of execution is dependant on the workload and continuous uptime of the process (timed events AREN'T persistant).
|
85
|
+
def run_after seconds, *args, &block
|
86
|
+
EventMachine.timed_job seconds, 1, args, block
|
87
|
+
end
|
88
|
+
|
89
|
+
# pushes a timed event to the timers's stack
|
90
|
+
#
|
91
|
+
# accepts:
|
92
|
+
# time:: the time at which the job should be executed.
|
93
|
+
# *arg:: any arguments that will be passed to the handler's `call` method.
|
94
|
+
# &block:: the block to execute.
|
95
|
+
#
|
96
|
+
# A block is required.
|
97
|
+
#
|
98
|
+
# Timed event's time of execution is dependant on the workload and continuous uptime of the process (timed events AREN'T persistant).
|
99
|
+
|
100
|
+
def run_at time, *args, &block
|
101
|
+
EventMachine.timed_job( (Time.now - time), 1, args, block)
|
102
|
+
end
|
103
|
+
# pushes a repeated timed event to the timers's stack
|
104
|
+
#
|
105
|
+
# accepts:
|
106
|
+
# seconds:: the minimal amount of seconds to wait before calling the handler's `call` method.
|
107
|
+
# limit:: the amount of times the event should repeat itself. The event will repeat every x amount of `seconds`. The event will repeat forever if limit is set to false.
|
108
|
+
# *arg:: any arguments that will be passed to the handler's `call` method.
|
109
|
+
# &block:: the block to execute.
|
110
|
+
#
|
111
|
+
# A block is required.
|
112
|
+
#
|
113
|
+
# Timed event's time of execution is dependant on the workload and continuous uptime of the process (timed events AREN'T persistant).
|
114
|
+
def run_every seconds, limit = -1, *args, &block
|
115
|
+
EventMachine.timed_job seconds, limit, args, block
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Plezi
|
2
|
+
module EventMachine
|
3
|
+
|
4
|
+
# A single worker.
|
5
|
+
class Worker
|
6
|
+
def initialize
|
7
|
+
@stop = false
|
8
|
+
wait = Worker.get_wait
|
9
|
+
@thread = Thread.new { EventMachine.run wait until @stop }
|
10
|
+
end
|
11
|
+
def stop
|
12
|
+
@stop = true
|
13
|
+
end
|
14
|
+
def join
|
15
|
+
stop
|
16
|
+
@thread.join rescue true
|
17
|
+
end
|
18
|
+
def alive?
|
19
|
+
@thread.alive?
|
20
|
+
end
|
21
|
+
def status
|
22
|
+
@thread.status
|
23
|
+
end
|
24
|
+
def self.get_wait
|
25
|
+
@primes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37]
|
26
|
+
@instances ||= -1
|
27
|
+
@instances += 1 if @instances < 7
|
28
|
+
@primes[@instances] / 10.0
|
29
|
+
end
|
30
|
+
def self.reset_wait
|
31
|
+
@instances = -1
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -13,12 +13,12 @@ module Plezi
|
|
13
13
|
# parameters are the same (almost) as `add_service` and include `root` for file root, `assets`
|
14
14
|
# and other non service related options.
|
15
15
|
def initialize params = {}
|
16
|
-
@params = params
|
16
|
+
@params = params.dup
|
17
17
|
@routes = []
|
18
18
|
# params[:save_assets] = true unless params[:save_assets] == false
|
19
|
-
params[:index_file] ||= 'index.html'
|
20
|
-
params[:assets_public] ||= '/assets'
|
21
|
-
params[:assets_public].chomp! '/'
|
19
|
+
@params[:index_file] ||= 'index.html'
|
20
|
+
@params[:assets_public] ||= '/assets'
|
21
|
+
@params[:assets_public].chomp! '/'
|
22
22
|
|
23
23
|
@sass_cache = Sass::CacheStores::Memory.new if defined?(::Sass)
|
24
24
|
# @sass_cache_lock = Mutex.new
|
@@ -17,29 +17,9 @@ module Plezi
|
|
17
17
|
elsif key.is_a?(String) && self.has_key?( key.to_sym)
|
18
18
|
key = key.to_sym
|
19
19
|
end
|
20
|
-
@controller.response.set_cookie key, (val ? val.dup : nil) if @controller
|
20
|
+
@controller.response.set_cookie key, (val ? val.to_s.dup : nil) if @controller
|
21
21
|
super
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
25
|
-
# tweeks a hash object to read both :symbols and strings (similar to Rails but without).
|
26
|
-
def self.make_hash_accept_symbols hash
|
27
|
-
@magic_hash_proc ||= Proc.new do |hs,k|
|
28
|
-
if k.is_a?(Symbol) && hs.has_key?( k.to_s)
|
29
|
-
hs[k.to_s]
|
30
|
-
elsif k.is_a?(String) && hs.has_key?( k.to_sym)
|
31
|
-
hs[k.to_sym]
|
32
|
-
elsif k.is_a?(Numeric) && hs.has_key?(k.to_s.to_sym)
|
33
|
-
hs[k.to_s.to_sym]
|
34
|
-
end
|
35
|
-
end
|
36
|
-
hash.default_proc = @magic_hash_proc
|
37
|
-
hash.values.each do |v|
|
38
|
-
if v.is_a?(Hash)
|
39
|
-
make_hash_accept_symbols v
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
|
45
25
|
end
|