plezi 0.7.7 → 0.8.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|