plezi 0.9.2 → 0.10.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 +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
|