plezi 0.9.2 → 0.10.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 +30 -0
- data/README.md +44 -31
- data/bin/plezi +3 -3
- data/lib/plezi.rb +21 -43
- data/lib/plezi/common/defer.rb +21 -0
- data/lib/plezi/common/dsl.rb +115 -91
- data/lib/plezi/common/redis.rb +44 -0
- data/lib/plezi/common/settings.rb +58 -0
- data/lib/plezi/handlers/controller_core.rb +132 -0
- data/lib/plezi/handlers/controller_magic.rb +85 -259
- data/lib/plezi/handlers/http_router.rb +139 -60
- data/lib/plezi/handlers/route.rb +9 -178
- data/lib/plezi/handlers/stubs.rb +2 -2
- data/lib/plezi/helpers/http_sender.rb +72 -0
- data/lib/plezi/helpers/magic_helpers.rb +12 -0
- data/lib/plezi/{server → helpers}/mime_types.rb +0 -0
- data/lib/plezi/version.rb +1 -1
- data/plezi.gemspec +3 -11
- data/resources/Gemfile +20 -21
- data/resources/controller.rb +2 -2
- data/resources/oauth_config.rb +1 -1
- data/resources/redis_config.rb +2 -0
- data/test/plezi_tests.rb +39 -46
- metadata +24 -33
- data/lib/plezi/common/logging.rb +0 -60
- data/lib/plezi/eventmachine/connection.rb +0 -190
- data/lib/plezi/eventmachine/em.rb +0 -98
- data/lib/plezi/eventmachine/io.rb +0 -272
- data/lib/plezi/eventmachine/protocol.rb +0 -54
- data/lib/plezi/eventmachine/queue.rb +0 -51
- data/lib/plezi/eventmachine/ssl_connection.rb +0 -144
- data/lib/plezi/eventmachine/timers.rb +0 -117
- data/lib/plezi/eventmachine/workers.rb +0 -33
- data/lib/plezi/handlers/http_echo.rb +0 -27
- data/lib/plezi/handlers/http_host.rb +0 -214
- data/lib/plezi/handlers/magic_helpers.rb +0 -32
- data/lib/plezi/server/http.rb +0 -129
- data/lib/plezi/server/http_protocol.rb +0 -319
- data/lib/plezi/server/http_request.rb +0 -146
- data/lib/plezi/server/http_response.rb +0 -319
- data/lib/plezi/server/websocket.rb +0 -251
- data/lib/plezi/server/websocket_client.rb +0 -178
- data/lib/plezi/server/ws_response.rb +0 -161
@@ -1,54 +0,0 @@
|
|
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
|
-
|
@@ -1,51 +0,0 @@
|
|
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
|
@@ -1,144 +0,0 @@
|
|
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_client]
|
16
|
-
context = OpenSSL::SSL::SSLContext.new
|
17
|
-
context.set_params verify_mode: OpenSSL::SSL::VERIFY_NONE # OpenSSL::SSL::VERIFY_PEER #OpenSSL::SSL::VERIFY_NONE
|
18
|
-
@ssl_socket = OpenSSL::SSL::SSLSocket.new(socket, context)
|
19
|
-
@ssl_socket.sync_close = true
|
20
|
-
@ssl_socket.connect
|
21
|
-
elsif params[:ssl] || params[:ssl_key] || params[:ssl_cert]
|
22
|
-
params[:ssl_cert], params[:ssl_key] = SSLConnection.self_cert unless params[:ssl_key] && params[:ssl_cert]
|
23
|
-
context = OpenSSL::SSL::SSLContext.new
|
24
|
-
context.set_params verify_mode: OpenSSL::SSL::VERIFY_NONE # OpenSSL::SSL::VERIFY_PEER #OpenSSL::SSL::VERIFY_NONE
|
25
|
-
# context.options DoNotReverseLookup: true
|
26
|
-
context.cert, context.key = params[:ssl_cert], params[:ssl_key]
|
27
|
-
context.cert_store = OpenSSL::X509::Store.new
|
28
|
-
context.cert_store.set_default_paths
|
29
|
-
@ssl_socket = OpenSSL::SSL::SSLSocket.new(socket, context)
|
30
|
-
@ssl_socket.sync_close = true
|
31
|
-
@ssl_socket.accept
|
32
|
-
end
|
33
|
-
raise "Not an SSL connection or SSL Socket creation failed" unless @ssl_socket
|
34
|
-
super
|
35
|
-
end
|
36
|
-
|
37
|
-
|
38
|
-
# 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).
|
39
|
-
def io
|
40
|
-
@ssl_socket
|
41
|
-
end
|
42
|
-
|
43
|
-
# makes sure any data in the que is send and calls `flush` on the socket, to make sure the buffer is sent.
|
44
|
-
def flush
|
45
|
-
begin
|
46
|
-
send
|
47
|
-
@ssl_socket.flush
|
48
|
-
rescue => e
|
49
|
-
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
# returns true if the service is disconnected
|
54
|
-
def disconnected?
|
55
|
-
(@socket.closed? || @ssl_socket.closed? || @socket.stat.mode != 0140666) rescue true # if mode is read only, it's the same as closed.
|
56
|
-
end
|
57
|
-
|
58
|
-
# identification markers
|
59
|
-
|
60
|
-
#returns the service type - set to normal
|
61
|
-
def service_type
|
62
|
-
'ssl'
|
63
|
-
end
|
64
|
-
#returns true if the service is encrypted using the OpenSSL library.
|
65
|
-
def ssl?
|
66
|
-
true
|
67
|
-
end
|
68
|
-
|
69
|
-
#################
|
70
|
-
# overide the followind methods for any child class.
|
71
|
-
|
72
|
-
# this is a public method and it should be used by child classes to implement each
|
73
|
-
# read(_nonblock) action. accepts one argument ::size for an optional buffer size to be read.
|
74
|
-
def read size = 1048576
|
75
|
-
data = ''
|
76
|
-
begin
|
77
|
-
(data << @ssl_socket.read_nonblock(size).to_s) until data.bytesize >= size
|
78
|
-
rescue => e
|
79
|
-
|
80
|
-
end
|
81
|
-
return false if data.to_s.empty?
|
82
|
-
touch
|
83
|
-
data
|
84
|
-
end
|
85
|
-
|
86
|
-
protected
|
87
|
-
|
88
|
-
# this is a protected method, it should be used by child classes to implement each
|
89
|
-
# send action.
|
90
|
-
def _send data
|
91
|
-
@active_time += 7200
|
92
|
-
@ssl_socket.write data
|
93
|
-
touch
|
94
|
-
end
|
95
|
-
|
96
|
-
public
|
97
|
-
|
98
|
-
# SSL certificate
|
99
|
-
|
100
|
-
# returns the current self-signed certificate - or creates a new one, if there is no current certificate.
|
101
|
-
def self.self_cert bits=2048, cn=nil, comment='a self signed certificate for when we only need encryption and no more.'
|
102
|
-
@@self_cert ||= create_cert
|
103
|
-
return *@@self_cert
|
104
|
-
end
|
105
|
-
#creates a self-signed certificate
|
106
|
-
def self.create_cert bits=2048, cn=nil, comment='a self signed certificate for when we only need encryption and no more.'
|
107
|
-
unless cn
|
108
|
-
host_name = Socket::gethostbyname(Socket::gethostname)[0].split('.')
|
109
|
-
cn = ''
|
110
|
-
host_name.each {|n| cn << "/DC=#{n}"}
|
111
|
-
cn << "/CN=#{host_name.join('.')}"
|
112
|
-
end
|
113
|
-
# cn ||= "CN=#{Socket::gethostbyname(Socket::gethostname)[0] rescue Socket::gethostname}"
|
114
|
-
|
115
|
-
rsa = OpenSSL::PKey::RSA.new(bits)
|
116
|
-
cert = OpenSSL::X509::Certificate.new
|
117
|
-
cert.version = 2
|
118
|
-
cert.serial = 1
|
119
|
-
name = OpenSSL::X509::Name.parse(cn)
|
120
|
-
cert.subject = name
|
121
|
-
cert.issuer = name
|
122
|
-
cert.not_before = Time.now
|
123
|
-
cert.not_after = Time.now + (365*24*60*60)
|
124
|
-
cert.public_key = rsa.public_key
|
125
|
-
|
126
|
-
ef = OpenSSL::X509::ExtensionFactory.new(nil,cert)
|
127
|
-
ef.issuer_certificate = cert
|
128
|
-
cert.extensions = [
|
129
|
-
ef.create_extension("basicConstraints","CA:FALSE"),
|
130
|
-
ef.create_extension("keyUsage", "keyEncipherment"),
|
131
|
-
ef.create_extension("subjectKeyIdentifier", "hash"),
|
132
|
-
ef.create_extension("extendedKeyUsage", "serverAuth"),
|
133
|
-
ef.create_extension("nsComment", comment),
|
134
|
-
]
|
135
|
-
aki = ef.create_extension("authorityKeyIdentifier",
|
136
|
-
"keyid:always,issuer:always")
|
137
|
-
cert.add_extension(aki)
|
138
|
-
cert.sign(rsa, OpenSSL::Digest::SHA1.new)
|
139
|
-
|
140
|
-
return cert, rsa
|
141
|
-
end
|
142
|
-
end
|
143
|
-
end
|
144
|
-
end
|
@@ -1,117 +0,0 @@
|
|
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.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
|
@@ -1,33 +0,0 @@
|
|
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
|
-
@instances = -1
|
13
|
-
@stop = true
|
14
|
-
end
|
15
|
-
def join
|
16
|
-
stop
|
17
|
-
@thread.join rescue true
|
18
|
-
end
|
19
|
-
def alive?
|
20
|
-
@thread.alive?
|
21
|
-
end
|
22
|
-
def status
|
23
|
-
@thread.status
|
24
|
-
end
|
25
|
-
def self.get_wait
|
26
|
-
@primes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37]
|
27
|
-
@instances ||= -1
|
28
|
-
@instances += 1 if @instances < 7
|
29
|
-
@primes[@instances] / 10.0
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
@@ -1,27 +0,0 @@
|
|
1
|
-
module Plezi
|
2
|
-
#####
|
3
|
-
# this is a Handler stub class for an HTTP echo server.
|
4
|
-
module HTTPEcho
|
5
|
-
module_function
|
6
|
-
|
7
|
-
# handles requests by printing out the parsed data. gets the `request` parameter from the HTTP protocol.
|
8
|
-
def on_request request
|
9
|
-
response = HTTPResponse.new request, 200, {'content-type' => 'text/plain'}, ["parsed as:\r\n", request.to_s]
|
10
|
-
response.body.last << "\n\n params:"
|
11
|
-
request.params.each {|k,v| response.body.last << "\n#{k}: #{v}"}
|
12
|
-
response.send
|
13
|
-
response.finish
|
14
|
-
end
|
15
|
-
|
16
|
-
# does nothing - a simple stub as required from handlers
|
17
|
-
def add_route *args
|
18
|
-
self
|
19
|
-
end
|
20
|
-
|
21
|
-
# does nothing - a simple stub as required from handlers
|
22
|
-
def add_host *args
|
23
|
-
self
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
end
|
@@ -1,214 +0,0 @@
|
|
1
|
-
module Plezi
|
2
|
-
#####
|
3
|
-
# this is a Handler stub class for an HTTP echo server.
|
4
|
-
class HTTPHost
|
5
|
-
|
6
|
-
# the parameters / settings for the Host.
|
7
|
-
attr_reader :params
|
8
|
-
# the routing array
|
9
|
-
attr_reader :routes
|
10
|
-
|
11
|
-
# initializes an HTTP host with the parameters for the specific host.
|
12
|
-
#
|
13
|
-
# parameters are the same (almost) as `add_service` and include `root` for file root, `assets`
|
14
|
-
# and other non service related options.
|
15
|
-
def initialize params = {}
|
16
|
-
@params = params.dup
|
17
|
-
@routes = []
|
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! '/'
|
22
|
-
|
23
|
-
@sass_cache = Sass::CacheStores::Memory.new if defined?(::Sass)
|
24
|
-
# @sass_cache_lock = Mutex.new
|
25
|
-
end
|
26
|
-
|
27
|
-
# adds a route under the specific host
|
28
|
-
def add_route path, controller, &block
|
29
|
-
routes << Route.new(path, controller, params, &block)
|
30
|
-
end
|
31
|
-
|
32
|
-
# handles requests sent to the host. returns true if the host delt with the request.
|
33
|
-
#
|
34
|
-
# since hosts are required to handle the requests (send 404 errors if resources arrn't found),
|
35
|
-
# this method always returns true.
|
36
|
-
def on_request request
|
37
|
-
begin
|
38
|
-
# render any assets?
|
39
|
-
return true if render_assets request
|
40
|
-
|
41
|
-
# send static file, if exists and root is set.
|
42
|
-
return true if send_static_file request
|
43
|
-
|
44
|
-
# return if a route answered the request
|
45
|
-
routes.each {|r| return true if r.on_request(request) }
|
46
|
-
|
47
|
-
# send folder listing if root is set, directory listing is set and folder exists
|
48
|
-
|
49
|
-
#to-do
|
50
|
-
|
51
|
-
#return error code or 404 not found
|
52
|
-
send_by_code request, 404
|
53
|
-
rescue Exception => e
|
54
|
-
# return 500 internal server error.
|
55
|
-
Plezi.error e
|
56
|
-
send_by_code request, 500
|
57
|
-
end
|
58
|
-
true
|
59
|
-
end
|
60
|
-
|
61
|
-
# Dresses up as a Rack app (If you don't like WebSockets, it's a reasonable aaproach).
|
62
|
-
def call request
|
63
|
-
request = Rack::Request.new request if defined? Rack
|
64
|
-
ret = nil
|
65
|
-
begin
|
66
|
-
# render any assets?
|
67
|
-
ret = render_assets request
|
68
|
-
return ret if ret
|
69
|
-
|
70
|
-
# send static file, if exists and root is set.
|
71
|
-
ret = send_static_file request
|
72
|
-
return ret if ret
|
73
|
-
|
74
|
-
# return if a route answered the request
|
75
|
-
routes.each {|r| ret = r.call(request); return ret if ret }
|
76
|
-
|
77
|
-
# send folder listing if root is set, directory listing is set and folder exists
|
78
|
-
|
79
|
-
#to-do
|
80
|
-
|
81
|
-
#return error code or 404 not found
|
82
|
-
return send_by_code request, 404
|
83
|
-
rescue Exception => e
|
84
|
-
# return 500 internal server error.
|
85
|
-
Plezi.error e
|
86
|
-
return send_by_code request, 500
|
87
|
-
end
|
88
|
-
true
|
89
|
-
end
|
90
|
-
|
91
|
-
################
|
92
|
-
## basic responses
|
93
|
-
## (error codes and static files)
|
94
|
-
|
95
|
-
# sends a response for an error code, rendering the relevent file (if exists).
|
96
|
-
def send_by_code request, code, headers = {}
|
97
|
-
begin
|
98
|
-
@base_code_path ||= params[:templates] || File.expand_path('.')
|
99
|
-
if defined?(::Slim) && Plezi.file_exists?(fn = File.join(@base_code_path, "#{code}.html.slim"))
|
100
|
-
Plezi.cache_data fn, Slim::Template.new( fn ) unless Plezi.cached? fn
|
101
|
-
return send_raw_data request, Plezi.get_cached( fn ).render( self, request: request ), 'text/html', code, headers
|
102
|
-
elsif defined?(::Haml) && Plezi.file_exists?(fn = File.join(@base_code_path, "#{code}.html.haml"))
|
103
|
-
Plezi.cache_data fn, Haml::Engine.new( IO.read( fn ) ) unless Plezi.cached? fn
|
104
|
-
return send_raw_data request, Plezi.get_cached( File.join(@base_code_path, "#{code}.html.haml") ).render( self ), 'text/html', code, headers
|
105
|
-
elsif defined?(::ERB) && Plezi.file_exists?(fn = File.join(@base_code_path, "#{code}.html.erb"))
|
106
|
-
return send_raw_data request, ERB.new( Plezi.load_file( fn ) ).result(binding), 'text/html', code, headers
|
107
|
-
elsif Plezi.file_exists?(fn = File.join(@base_code_path, "#{code}.html"))
|
108
|
-
return send_file(request, fn, code, headers)
|
109
|
-
end
|
110
|
-
return true if send_raw_data(request, HTTPResponse::STATUS_CODES[code], 'text/plain', code, headers)
|
111
|
-
rescue Exception => e
|
112
|
-
Plezi.error e
|
113
|
-
end
|
114
|
-
false
|
115
|
-
end
|
116
|
-
|
117
|
-
# attempts to send a static file by the request path (using `send_file` and `send_raw_data`).
|
118
|
-
#
|
119
|
-
# returns true if data was sent.
|
120
|
-
def send_static_file request
|
121
|
-
return false unless params[:root]
|
122
|
-
file_requested = request[:path].to_s.split('/')
|
123
|
-
unless file_requested.include? '..'
|
124
|
-
file_requested.shift
|
125
|
-
file_requested = File.join(params[:root], *file_requested)
|
126
|
-
return true if send_file request, file_requested
|
127
|
-
return send_file request, File.join(file_requested, params[:index_file])
|
128
|
-
end
|
129
|
-
false
|
130
|
-
end
|
131
|
-
|
132
|
-
# sends a file/cacheed data if it exists. otherwise returns false.
|
133
|
-
def send_file request, filename, status_code = 200, headers = {}
|
134
|
-
if Plezi.file_exists?(filename) && !::File.directory?(filename)
|
135
|
-
return send_raw_data request, Plezi.load_file(filename), MimeTypeHelper::MIME_DICTIONARY[::File.extname(filename)], status_code, headers
|
136
|
-
end
|
137
|
-
return false
|
138
|
-
end
|
139
|
-
# sends raw data through the connection. always returns true (data send).
|
140
|
-
def send_raw_data request, data, mime, status_code = 200, headers = {}
|
141
|
-
response = HTTPResponse.new request, status_code, headers
|
142
|
-
response['cache-control'] = 'public, max-age=86400'
|
143
|
-
response << data
|
144
|
-
response['content-length'] = data.bytesize
|
145
|
-
response.finish
|
146
|
-
true
|
147
|
-
end
|
148
|
-
|
149
|
-
###############
|
150
|
-
## asset rendering and responses
|
151
|
-
|
152
|
-
# renders assets, if necessary, and places the rendered result in the cache and in the public folder.
|
153
|
-
def render_assets request
|
154
|
-
# contine only if assets are defined and called for
|
155
|
-
return false unless @params[:assets] && request.path.match(/^#{params[:assets_public]}\/.+/)
|
156
|
-
# review callback, if defined
|
157
|
-
return true if params[:assets_callback] && params[:assets_callback].call(request)
|
158
|
-
|
159
|
-
# get file requested
|
160
|
-
source_file = File.join(params[:assets], *(request.path.match(/^#{params[:assets_public]}\/(.+)/)[1].split('/')))
|
161
|
-
|
162
|
-
# stop if file name is reserved / has security issues
|
163
|
-
return false if source_file.match(/(scss|sass|coffee|\.\.\/)$/)
|
164
|
-
|
165
|
-
# set where to store the rendered asset
|
166
|
-
target_file = false
|
167
|
-
target_file = File.join( params[:root], params[:assets_public], *request.path.match(/^#{params[:assets_public]}\/(.*)/)[1].split('/') ) if params[:root]
|
168
|
-
|
169
|
-
# send the file if it exists (no render needed)
|
170
|
-
if File.exists?(source_file)
|
171
|
-
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)
|
172
|
-
return (data ? send_raw_data(request, data, MimeTypeHelper::MIME_DICTIONARY[::File.extname(source_file)]) : false)
|
173
|
-
end
|
174
|
-
|
175
|
-
# render supported assets
|
176
|
-
case source_file
|
177
|
-
when /\.css$/
|
178
|
-
sass = source_file.gsub /css$/, 'sass'
|
179
|
-
sass.gsub! /sass$/, 'scss' unless Plezi.file_exists?(sass)
|
180
|
-
return false unless Plezi.file_exists?(sass)
|
181
|
-
# review mtime and render sass if necessary
|
182
|
-
if defined?(::Sass) && refresh_sass?(sass)
|
183
|
-
eng = Sass::Engine.for_file(sass, cache_store: @sass_cache)
|
184
|
-
Plezi.cache_data sass, eng.dependencies
|
185
|
-
css, map = eng.render_with_sourcemap(params[:assets_public])
|
186
|
-
Plezi.save_file target_file, css, params[:save_assets]
|
187
|
-
Plezi.save_file (target_file + ".map"), map, params[:save_assets]
|
188
|
-
end
|
189
|
-
# try to send the cached css file which started the request.
|
190
|
-
return send_file request, target_file
|
191
|
-
when /\.js$/
|
192
|
-
coffee = source_file.gsub /js$/i, 'coffee'
|
193
|
-
return false unless Plezi.file_exists?(coffee)
|
194
|
-
# review mtime and render coffee if necessary
|
195
|
-
if defined?(::CoffeeScript) && Plezi.cache_needs_update?(coffee)
|
196
|
-
# render coffee to cache
|
197
|
-
Plezi.cache_data coffee, nil
|
198
|
-
Plezi.save_file target_file, CoffeeScript.compile(IO.read coffee), params[:save_assets]
|
199
|
-
end
|
200
|
-
# try to send the cached js file which started the request.
|
201
|
-
return send_file request, target_file
|
202
|
-
end
|
203
|
-
false
|
204
|
-
end
|
205
|
-
def refresh_sass? sass
|
206
|
-
return false unless File.exists?(sass)
|
207
|
-
return true if Plezi.cache_needs_update?(sass)
|
208
|
-
mt = Plezi.file_mtime(sass)
|
209
|
-
Plezi.get_cached(sass).each {|e| return true if File.exists?(e.options[:filename]) && (File.mtime(e.options[:filename]) > mt)} # fn = File.join( e.options[:load_paths][0].root, e.options[:filename])
|
210
|
-
false
|
211
|
-
end
|
212
|
-
end
|
213
|
-
|
214
|
-
end
|