waxx 0.1.2
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 +7 -0
- checksums.yaml.gz.sig +0 -0
- data/LICENSE +201 -0
- data/README.md +879 -0
- data/bin/waxx +120 -0
- data/lib/waxx/app.rb +173 -0
- data/lib/waxx/conf.rb +54 -0
- data/lib/waxx/console.rb +204 -0
- data/lib/waxx/csrf.rb +14 -0
- data/lib/waxx/database.rb +80 -0
- data/lib/waxx/encrypt.rb +38 -0
- data/lib/waxx/error.rb +60 -0
- data/lib/waxx/html.rb +33 -0
- data/lib/waxx/http.rb +268 -0
- data/lib/waxx/init.rb +273 -0
- data/lib/waxx/irb.rb +44 -0
- data/lib/waxx/irb_env.rb +18 -0
- data/lib/waxx/json.rb +23 -0
- data/lib/waxx/mongodb.rb +221 -0
- data/lib/waxx/mysql2.rb +234 -0
- data/lib/waxx/object.rb +115 -0
- data/lib/waxx/patch.rb +138 -0
- data/lib/waxx/pdf.rb +69 -0
- data/lib/waxx/pg.rb +246 -0
- data/lib/waxx/process.rb +270 -0
- data/lib/waxx/req.rb +116 -0
- data/lib/waxx/res.rb +98 -0
- data/lib/waxx/server.rb +304 -0
- data/lib/waxx/sqlite3.rb +237 -0
- data/lib/waxx/supervisor.rb +47 -0
- data/lib/waxx/test.rb +162 -0
- data/lib/waxx/util.rb +57 -0
- data/lib/waxx/version.rb +3 -0
- data/lib/waxx/view.rb +389 -0
- data/lib/waxx/waxx.rb +73 -0
- data/lib/waxx/x.rb +103 -0
- data/lib/waxx.rb +50 -0
- data/skel/README.md +11 -0
- data/skel/app/app/app.rb +39 -0
- data/skel/app/app/error/app_error.rb +16 -0
- data/skel/app/app/error/dhtml.rb +9 -0
- data/skel/app/app/error/html.rb +8 -0
- data/skel/app/app/error/json.rb +8 -0
- data/skel/app/app/error/pdf.rb +13 -0
- data/skel/app/app/log/app_log.rb +13 -0
- data/skel/app/app.rb +20 -0
- data/skel/app/home/home.rb +16 -0
- data/skel/app/home/html.rb +145 -0
- data/skel/app/html.rb +192 -0
- data/skel/app/usr/email.rb +66 -0
- data/skel/app/usr/html.rb +115 -0
- data/skel/app/usr/list.rb +51 -0
- data/skel/app/usr/password.rb +54 -0
- data/skel/app/usr/record.rb +98 -0
- data/skel/app/usr/usr.js +67 -0
- data/skel/app/usr/usr.rb +277 -0
- data/skel/app/waxx/waxx.rb +109 -0
- data/skel/bin/README.md +1 -0
- data/skel/db/README.md +11 -0
- data/skel/db/app/0-init.sql +88 -0
- data/skel/lib/README.md +1 -0
- data/skel/log/README.md +1 -0
- data/skel/opt/dev/config.yaml +1 -0
- data/skel/opt/prod/config.yaml +1 -0
- data/skel/opt/stage/config.yaml +1 -0
- data/skel/opt/test/config.yaml +1 -0
- data/skel/private/README.md +1 -0
- data/skel/public/lib/site.css +202 -0
- data/skel/public/lib/waxx/w.ico +0 -0
- data/skel/public/lib/waxx/w.png +0 -0
- data/skel/public/lib/waxx/waxx.js +111 -0
- data/skel/tmp/pids/README.md +1 -0
- data.tar.gz.sig +0 -0
- metadata +140 -0
- metadata.gz.sig +3 -0
data/lib/waxx/server.rb
ADDED
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
# Waxx Copyright (c) 2016 ePark labs Inc. & Daniel J. Fitzpatrick <dan@eparklabs.com> All rights reserved.
|
|
2
|
+
# Released under the Apache Version 2 License. See LICENSE.txt.
|
|
3
|
+
|
|
4
|
+
##
|
|
5
|
+
# This is the core of waxx.
|
|
6
|
+
# Process:
|
|
7
|
+
# start: A TCP server is setup listening on Waxx['server']['host'] on port Waxx['server']['port']
|
|
8
|
+
# setup_threads: The thread pool is created with dedicated database connection(s)
|
|
9
|
+
# loop: The requests are appended to the queue and the threads take one and:
|
|
10
|
+
# process_request: The request is parsed (query, string, multipart, etc). The "x" vairable is defined
|
|
11
|
+
# run_app: The proc/lambda is called with the x variable and any other optional variables
|
|
12
|
+
# finish: The response is sent to the client, background jobs are processed, the thread takes the next request
|
|
13
|
+
module Waxx::Server
|
|
14
|
+
extend self
|
|
15
|
+
|
|
16
|
+
attr :last_load_time
|
|
17
|
+
attr :queue
|
|
18
|
+
|
|
19
|
+
def parse_uri(r)
|
|
20
|
+
Waxx.debug "parse_uri"
|
|
21
|
+
meth, uri, ver = r.split(" ")
|
|
22
|
+
path, params = uri.split("?", 2)
|
|
23
|
+
_, app, act, arg = path.split(".").first.split("/", 4)
|
|
24
|
+
app = Waxx['default']['app'] if app.to_s == ''
|
|
25
|
+
act = App[app.to_sym][:default] if act.to_s == '' and App[app.to_sym]
|
|
26
|
+
act = Waxx['default']['act'] if act.to_s == ''
|
|
27
|
+
oid = arg.split("/").first.gsub(/[^0-9]/,"").to_i rescue 0
|
|
28
|
+
ext = path =~ /\./ ? path.split(".").last.downcase : Waxx['default']['ext']
|
|
29
|
+
args = arg.split("/") rescue []
|
|
30
|
+
get = Waxx::Http.query_string_to_hash(params).freeze
|
|
31
|
+
Waxx.debug "parse_uri.oid #{oid}"
|
|
32
|
+
[meth, uri, app, act, oid, args, ext, get]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def default_cookies
|
|
36
|
+
{
|
|
37
|
+
"#{Waxx['cookie']['user']['name']}" => {uk: Waxx.random_string(20), la: Time.now.to_i},
|
|
38
|
+
"#{Waxx['cookie']['agent']['name']}" => {la: Time.now.to_i}
|
|
39
|
+
}
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def csrf?(x)
|
|
43
|
+
Waxx.debug "csrf?"
|
|
44
|
+
if %w(PATCH POST PUT DELETE).include? x.req.meth
|
|
45
|
+
if not Waxx::Csrf.ok?(x)
|
|
46
|
+
true
|
|
47
|
+
end
|
|
48
|
+
puts "Cleared CSRF"
|
|
49
|
+
end
|
|
50
|
+
false
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def default_response_headers(req, ext)
|
|
54
|
+
{
|
|
55
|
+
"Content-Type" => (Waxx::Http.content_types()[ext.to_sym] || "text/html; charset=utf-8"),
|
|
56
|
+
"App-Server" => "waxx/1.0"
|
|
57
|
+
}
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def serve_file(io, uri)
|
|
61
|
+
return false if Waxx['file'].nil?
|
|
62
|
+
file = "#{Waxx/:opts/:base}/#{Waxx/:file/:path}#{uri.gsub("..","").split("?").first}"
|
|
63
|
+
file = file + "/index.html" if File.directory?(file)
|
|
64
|
+
return false unless File.exist? file
|
|
65
|
+
ext = file.split(".").last
|
|
66
|
+
io.print([
|
|
67
|
+
"HTTP/1.1 200 OK",
|
|
68
|
+
"Content-Type: #{(Waxx::Http.content_types/ext || 'octet-stream')}",
|
|
69
|
+
"Content-Length: #{File.size(file)}",
|
|
70
|
+
"",
|
|
71
|
+
File.open(file,"rb") {|fh| fh.read}
|
|
72
|
+
].join("\r\n"))
|
|
73
|
+
io.close
|
|
74
|
+
::Thread.current[:status] = "idle"
|
|
75
|
+
true
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def process_request(io, db)
|
|
79
|
+
begin
|
|
80
|
+
Waxx.debug "process request", 4
|
|
81
|
+
::Thread.current[:status] = "working"
|
|
82
|
+
start_time = Time.new
|
|
83
|
+
r = io.gets
|
|
84
|
+
#Waxx.debug r, 9
|
|
85
|
+
meth, uri, app, act, oid, args, ext, get = parse_uri(r)
|
|
86
|
+
#Waxx.debug([meth, uri, app, act, oid, args, ext, get].join(" "), 8)
|
|
87
|
+
if meth == "GET" and Waxx['file'] and Waxx['file']['serve']
|
|
88
|
+
#Waxx.debug "server_file"
|
|
89
|
+
return if serve_file(io, uri)
|
|
90
|
+
end
|
|
91
|
+
#Waxx.debug "no-file"
|
|
92
|
+
env, head = Waxx::Http.parse_head(io)
|
|
93
|
+
#Waxx.debug head, 9
|
|
94
|
+
cookie = Waxx::Http.parse_cookie(env['Cookie'])
|
|
95
|
+
begin
|
|
96
|
+
usr = cookie[Waxx['cookie']['user']['name']] ? JSON.parse(::App.decrypt(cookie[Waxx['cookie']['user']['name']][0])) : default_cookies[Waxx['cookie']['user']['name']]
|
|
97
|
+
rescue => e
|
|
98
|
+
Waxx.debug e.to_s, 1
|
|
99
|
+
usr = default_cookies[Waxx['cookie']['user']['name']]
|
|
100
|
+
end
|
|
101
|
+
begin
|
|
102
|
+
ua = cookie[Waxx['cookie']['agent']['name']] ? JSON.parse(::App.decrypt(cookie[Waxx['cookie']['agent']['name']][0])) : {}
|
|
103
|
+
rescue => e
|
|
104
|
+
Waxx.debug e.to_s, 1
|
|
105
|
+
ua = {}
|
|
106
|
+
end
|
|
107
|
+
post, data = Waxx::Http.parse_data(env, meth, io, head)
|
|
108
|
+
req = Waxx::Req.new(env, data, meth, uri, get, post, cookie, start_time).freeze
|
|
109
|
+
res = Waxx::Res.new(io, 200, default_response_headers(req, ext), [], [], [])
|
|
110
|
+
jobs = []
|
|
111
|
+
x = Waxx::X.new(req, res, usr, ua, db, meth.downcase.to_sym, app, act, oid, args, ext, jobs).freeze
|
|
112
|
+
if csrf?(x)
|
|
113
|
+
Waxx::App.csrf_failure(x)
|
|
114
|
+
finish(x, io)
|
|
115
|
+
return
|
|
116
|
+
end
|
|
117
|
+
run_app(x)
|
|
118
|
+
finish(x, io)
|
|
119
|
+
rescue => e
|
|
120
|
+
fatal_error(x, e)
|
|
121
|
+
finish(x, io)
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def run_app(x)
|
|
126
|
+
Waxx.debug "run_request"
|
|
127
|
+
app = x.app.to_sym
|
|
128
|
+
if App[app]
|
|
129
|
+
act = App[app][x.act.to_sym] ? x.act.to_sym : App[app][x.act] ? x.act : :not_found
|
|
130
|
+
if App[app][act]
|
|
131
|
+
if App.access?(x, acl:App[app][act][:acl])
|
|
132
|
+
return App.run(x, app, act, x.meth.to_sym, x.args)
|
|
133
|
+
else
|
|
134
|
+
return App.login_needed(x)
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
App.not_found(x)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def set_cookies(x)
|
|
142
|
+
return if x.usr/:no_cookies
|
|
143
|
+
x.res.cookie(
|
|
144
|
+
name: Waxx['cookie']['user']['name'],
|
|
145
|
+
value: App.encrypt(x.usr.to_json),
|
|
146
|
+
secure: Waxx['cookie']['user']['secure']
|
|
147
|
+
)
|
|
148
|
+
x.res.cookie(
|
|
149
|
+
name: Waxx['cookie']['agent']['name'],
|
|
150
|
+
value: App.encrypt(x.ua.to_json),
|
|
151
|
+
expires: Time.now + (Waxx['cookie']['agent']['expires_years'].to_i * 31536000),
|
|
152
|
+
secure: Waxx['cookie']['agent']['secure']
|
|
153
|
+
)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def finish(x, io)
|
|
157
|
+
# Set last activity
|
|
158
|
+
x.usr['la'] = Time.new.to_i
|
|
159
|
+
set_cookies(x)
|
|
160
|
+
Waxx.debug "Process time: #{(Time.new - x.req.start_time)*1000} ms."
|
|
161
|
+
x.res.complete
|
|
162
|
+
io.close
|
|
163
|
+
x.jobs.each{|job|
|
|
164
|
+
job[0].call(*(job.slice(1, job.length)))
|
|
165
|
+
}
|
|
166
|
+
::Thread.current[:status] = "idle"
|
|
167
|
+
::Thread.current[:last_used] = Time.new.to_i
|
|
168
|
+
x # Return x for the console interfaces
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def fatal_error(x, e)
|
|
172
|
+
x.res.status = 503
|
|
173
|
+
puts "FATAL ERROR: #{e}\n#{e.backtrace}"
|
|
174
|
+
report = "APPLICATION ERROR\n=================\n\nUSR:\n\n#{x.usr.map{|n,v| "#{n}: #{v}"}.join("\n")}\n\nERROR:\n\n#{e}\n#{e.backtrace.join("\n")}\n\nENV:\n\n#{x.req.env.map{|n,v| "#{n}: #{v}"}.join("\n")}\n\nGET:\n\n#{x.req.get.map{|n,v| "#{n}: #{v}"}.join("\n")}\n\nPOST:\n\n#{x.req.post.map{|n,v| "#{n}: #{v}"}.join("\n")}\n\n"
|
|
175
|
+
if Waxx['debug']['on_screen']
|
|
176
|
+
x << "<pre>#{report.h}</pre>"
|
|
177
|
+
else
|
|
178
|
+
App::Html.page(x,
|
|
179
|
+
title: "System Error",
|
|
180
|
+
content: "<h4><span class='glyphicon glyphicon-thumbs-down'></span> Sorry! Something went wrong on our end.</h4>
|
|
181
|
+
<h4><span class='glyphicon glyphicon-thumbs-up'></span> The tech support team has been notified.</h4>
|
|
182
|
+
<p>We will contact you if we need addition information. </p>
|
|
183
|
+
<p>Sorry for the inconvenience.</p>"
|
|
184
|
+
)
|
|
185
|
+
end
|
|
186
|
+
if Waxx['debug']['send_email'] and Waxx['debug']['email']
|
|
187
|
+
begin
|
|
188
|
+
to_email = Waxx['debug']['email']
|
|
189
|
+
from_email = Waxx['site']['support_email']
|
|
190
|
+
subject = "[Bug] #{Waxx['site']['name']} #{x.meth}:#{x.req.uri}"
|
|
191
|
+
# Send email via DB
|
|
192
|
+
App::Email.post(x, d:{
|
|
193
|
+
to_email: to_email,
|
|
194
|
+
from_email: from_email,
|
|
195
|
+
subject: subject,
|
|
196
|
+
body_text: report
|
|
197
|
+
})
|
|
198
|
+
rescue => e2
|
|
199
|
+
begin
|
|
200
|
+
# Send email directly
|
|
201
|
+
Mail.deliver do
|
|
202
|
+
from from_email
|
|
203
|
+
to to_email
|
|
204
|
+
subject subject
|
|
205
|
+
body report
|
|
206
|
+
end
|
|
207
|
+
rescue => e3
|
|
208
|
+
puts "FATAL ERROR: Could not send bug report email: #{e2}\n#{e2.backtrace} AND #{e3}\n#{e3.backtrace}"
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# Require first level apps in the Waxx::Root/app directory
|
|
215
|
+
def require_apps
|
|
216
|
+
Dir["#{Waxx["opts"][:base]}/app/*"].each{|f|
|
|
217
|
+
next if f =~ /\/app\.rb$/ # Don't reinclude app.rb
|
|
218
|
+
require f if f =~ /\.rb$/ # Load files in the app directory
|
|
219
|
+
if File.directory? f # Load top-level apps
|
|
220
|
+
name = f.split("/").last
|
|
221
|
+
require "#{f}/#{name}" if File.exist? "#{f}/#{name}.rb"
|
|
222
|
+
end
|
|
223
|
+
}
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def reload
|
|
227
|
+
reload_code
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def reload_code
|
|
231
|
+
require_apps
|
|
232
|
+
$LOADED_FEATURES.each{|f|
|
|
233
|
+
if f =~ /^\//
|
|
234
|
+
if not f =~ /^\/usr\/local/
|
|
235
|
+
if File.ctime(f) > @@last_load_time
|
|
236
|
+
load(f)
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
}
|
|
241
|
+
@@last_load_time = Time.new
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def create_thread(id)
|
|
245
|
+
Thread.new do
|
|
246
|
+
Thread.current[:name]="waxx-#{Time.new.to_i}-#{id}"
|
|
247
|
+
Thread.current[:status]="idle"
|
|
248
|
+
Thread.current[:db] = Waxx['databases'].nil? ? {} : Waxx::Database.connections(Waxx['databases'])
|
|
249
|
+
Thread.current[:last_used] = Time.new.to_i
|
|
250
|
+
Waxx.debug "Create thread #{Thread.current[:name]}"
|
|
251
|
+
loop do
|
|
252
|
+
Waxx::Server.process_request(@@queue.pop, Thread.current[:db])
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def setup_threads
|
|
258
|
+
Waxx.debug "setup_threads"
|
|
259
|
+
Thread.current[:name]="main"
|
|
260
|
+
@@queue = Queue.new
|
|
261
|
+
thread_count = Waxx['server']['min_threads'] || Waxx['server']['threads'] || 4
|
|
262
|
+
1.upto(thread_count).each do |i|
|
|
263
|
+
create_thread(i)
|
|
264
|
+
end
|
|
265
|
+
thread_count
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
def restart
|
|
269
|
+
stop
|
|
270
|
+
sleep(1)
|
|
271
|
+
start
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
def start(options={})
|
|
275
|
+
@@last_load_time = Time.new
|
|
276
|
+
Waxx.debug "start #{$$}"
|
|
277
|
+
thread_count = setup_threads
|
|
278
|
+
server = TCPServer.new(Waxx['server']['host'], Waxx['server']['port'])
|
|
279
|
+
puts "Listening on #{server.addr} with #{thread_count} threads (max_threads: #{Waxx['server']['max_threads'] || Waxx['server']['threads']})"
|
|
280
|
+
while s = server.accept
|
|
281
|
+
Waxx.debug "server.accept", 7
|
|
282
|
+
reload_code if Waxx['debug']['auto_reload_code']
|
|
283
|
+
@@queue << s
|
|
284
|
+
Waxx.debug "q.size: #{@@queue.size}", 7
|
|
285
|
+
Waxx.debug "q.waiting: #{@@queue.num_waiting}", 7
|
|
286
|
+
# Check threads
|
|
287
|
+
Waxx::Supervisor.check
|
|
288
|
+
end
|
|
289
|
+
Waxx.debug "end start", 9
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
def stop(opts={})
|
|
293
|
+
Waxx.debug "stop #{$$}"
|
|
294
|
+
puts "Stopping #{Thread.list.size - 1} worker threads..."
|
|
295
|
+
Thread.list.each{|t|
|
|
296
|
+
next if t[:name] == "main"
|
|
297
|
+
puts "Killing #{t[:name]} with status #{t[:status]}"
|
|
298
|
+
t[:db].close
|
|
299
|
+
t.exit
|
|
300
|
+
}
|
|
301
|
+
puts "Done"
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
|
data/lib/waxx/sqlite3.rb
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
# Waxx Copyright (c) 2016 ePark labs Inc. & Daniel J. Fitzpatrick <dan@eparklabs.com> All rights reserved.
|
|
2
|
+
# Released under the Apache Version 2 License. See LICENSE.txt.
|
|
3
|
+
|
|
4
|
+
##
|
|
5
|
+
# The SQLite Object methods
|
|
6
|
+
module Waxx::Sqlite3
|
|
7
|
+
extend Waxx::Object
|
|
8
|
+
extend self
|
|
9
|
+
|
|
10
|
+
attr :app
|
|
11
|
+
attr :db
|
|
12
|
+
attr :table
|
|
13
|
+
attr :columns
|
|
14
|
+
attr :pkey
|
|
15
|
+
attr :joins
|
|
16
|
+
attr :orders
|
|
17
|
+
|
|
18
|
+
##
|
|
19
|
+
# Connect to an sqlite database
|
|
20
|
+
#
|
|
21
|
+
# Set in config.yaml:
|
|
22
|
+
# databases:
|
|
23
|
+
# blog: sqlite:///path/to/database.db
|
|
24
|
+
# Or call this with the path to the db file
|
|
25
|
+
def connect(str)
|
|
26
|
+
conn = SQLite3::Database.new( str )
|
|
27
|
+
conn.results_as_hash = true
|
|
28
|
+
conn
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def init(app:nil, db:"app", table:nil, pk:"id", cols:nil)
|
|
32
|
+
@app ||= (app || App.table_from_class(name)).to_sym
|
|
33
|
+
@db ||= db.to_sym
|
|
34
|
+
@table ||= (table || App.table_from_class(name)).to_sym
|
|
35
|
+
@pkey ||= pk.to_sym
|
|
36
|
+
@columns ||= {}
|
|
37
|
+
@joins ||= {}
|
|
38
|
+
@orders ||= {}
|
|
39
|
+
has(cols) if cols
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def has(opts=nil)
|
|
43
|
+
init if @table.nil?
|
|
44
|
+
return @columns if opts.nil?
|
|
45
|
+
@columns = opts
|
|
46
|
+
@columns.each{|n,v|
|
|
47
|
+
v[:table] = @table
|
|
48
|
+
v[:column] = n
|
|
49
|
+
v[:views] = []
|
|
50
|
+
v[:label] ||= Waxx::Util.label(n)
|
|
51
|
+
@orders[n] = v[:order] || n
|
|
52
|
+
@orders["_#{n}".to_sym] = v[:_order] || "#{n} DESC"
|
|
53
|
+
@pkey = n if v[:pkey]
|
|
54
|
+
if v[:is]
|
|
55
|
+
r, tc = v[:is].split(":")
|
|
56
|
+
t, c = tc.split(".")
|
|
57
|
+
@joins[r] = {join: "INNER", table: t, col: c}
|
|
58
|
+
end
|
|
59
|
+
if v[:has]
|
|
60
|
+
r, tc = v[:has].split(":")
|
|
61
|
+
t, c = tc.split(".")
|
|
62
|
+
@joins[r] = {join: "LEFT", table: t, col: c}
|
|
63
|
+
end
|
|
64
|
+
}
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def [](n)
|
|
68
|
+
@columns[n.to_sym]
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def /(n)
|
|
72
|
+
@columns[n.to_sym]
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def get_cols(*args)
|
|
76
|
+
re = {}
|
|
77
|
+
args.flatten.map{|a| re[a] = @columns[a.to_sym]}
|
|
78
|
+
re
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def runs(opts=nil)
|
|
82
|
+
init if @app.nil?
|
|
83
|
+
return App[@app] if opts.nil?
|
|
84
|
+
App[@app] = opts
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def run(x, act, meth, *args)
|
|
88
|
+
App[@app][act.to_sym][meth.to_sym][x, *args]
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def parse_select(select, view)
|
|
92
|
+
raise "Can not define both select and view in Waxx::Object.parse_select (#{name})." if select and view
|
|
93
|
+
return select || "*" if view.nil?
|
|
94
|
+
view.columns.map{|n,c|
|
|
95
|
+
raise "Column #{n} not defined in #{view}" if c.nil?
|
|
96
|
+
if c[:sql_select]
|
|
97
|
+
"#{c[:sql_select]} AS #{n}"
|
|
98
|
+
elsif n != c[:column]
|
|
99
|
+
"#{c[:table]}.#{c[:column]} AS #{n}"
|
|
100
|
+
else
|
|
101
|
+
"#{c[:table]}.#{c[:column]}"
|
|
102
|
+
end
|
|
103
|
+
}.join(", ")
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def get(x, select:nil, id:nil, joins:nil, where:nil, having:nil, order:nil, limit:nil, offset:nil, view:nil, &blk)
|
|
107
|
+
Waxx.debug "object.get"
|
|
108
|
+
select = parse_select(select, view)
|
|
109
|
+
where = ["#{@table}.#{@pkey} = ?",id] if id and where.nil?
|
|
110
|
+
# Block SQL injection in order clause. All order options must be defined in @orders.
|
|
111
|
+
if order
|
|
112
|
+
if not @orders/order
|
|
113
|
+
Waxx.debug("ERROR: Object.get order (#{order}) not found in @orders [#{@orders.keys.join(", ")}]. Sorting by #{@pkey} instead.")
|
|
114
|
+
order = @pkey
|
|
115
|
+
else
|
|
116
|
+
order = @orders/order
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
if joins.nil? and view
|
|
120
|
+
joins = view.joins_to_sql
|
|
121
|
+
end
|
|
122
|
+
q = {select:select, joins:joins, where:where, having:having, order:order, limit:limit, offset:offset}
|
|
123
|
+
yield q if block_given?
|
|
124
|
+
Waxx.debug "object.get.select: #{q[:select]}"
|
|
125
|
+
return [] if q[:select].empty?
|
|
126
|
+
sql = []
|
|
127
|
+
sql << "SELECT #{q[:select] || "*"}"
|
|
128
|
+
sql << "FROM #{@table} #{q[:joins]}"
|
|
129
|
+
sql << "WHERE #{q[:where][0]}" if q[:where]
|
|
130
|
+
sql << "HAVING #{q[:having[0]]}" if q[:having]
|
|
131
|
+
sql << "ORDER BY #{q[:order]}" if q[:order]
|
|
132
|
+
sql << "LIMIT #{q[:limit].to_i}" if q[:limit]
|
|
133
|
+
sql << "OFFSET #{q[:offset].to_i}" if q[:offset]
|
|
134
|
+
vals = []
|
|
135
|
+
vals << q[:where][1] if q[:where] and q[:where][1]
|
|
136
|
+
vals << q[:having][1] if q[:having] and q[:having][1]
|
|
137
|
+
#[sql.join(" "), vals.flatten]
|
|
138
|
+
Waxx.debug sql
|
|
139
|
+
Waxx.debug vals.join(", ")
|
|
140
|
+
x.db[@db].execute(sql.join(" "), vals.flatten)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def get_by_id(x, id, select=nil, view:nil)
|
|
144
|
+
get(x, id: id, select: select, view: view).first
|
|
145
|
+
end
|
|
146
|
+
alias by_id get_by_id
|
|
147
|
+
|
|
148
|
+
def post(x, data, cols:nil, returning:nil, view:nil, &blk)
|
|
149
|
+
if view
|
|
150
|
+
cols = view.columns.select{|n,c| c[:table] == @table}
|
|
151
|
+
else
|
|
152
|
+
cols ||= @columns
|
|
153
|
+
end
|
|
154
|
+
data = blk.call if block_given?
|
|
155
|
+
sql = "INSERT INTO #{@table} ("
|
|
156
|
+
names = []
|
|
157
|
+
vars = []
|
|
158
|
+
vals = []
|
|
159
|
+
ret = []
|
|
160
|
+
i = 1
|
|
161
|
+
cols.each{|n,v|
|
|
162
|
+
if data/n
|
|
163
|
+
names << n.to_s
|
|
164
|
+
vars << "?"
|
|
165
|
+
vals << cast(v, data/n)
|
|
166
|
+
i += 1
|
|
167
|
+
end
|
|
168
|
+
ret << n.to_s
|
|
169
|
+
}
|
|
170
|
+
sql << names.join(",")
|
|
171
|
+
sql << ") VALUES (#{vars.join(",")})"
|
|
172
|
+
Waxx.debug(sql, 4)
|
|
173
|
+
Waxx.debug(vals, 4)
|
|
174
|
+
x.db[@db].execute(sql, vals)
|
|
175
|
+
new_id = x.db[@db].last_insert_row_id
|
|
176
|
+
x.db[@db].execute("SELECT #{returning || ret.join(",")} FROM #{@table} WHERE #{@pkey} = ?", [new_id]).first
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def put(x, id, data, cols:nil, returning:nil, view:nil, where:nil, &blk)
|
|
180
|
+
if view
|
|
181
|
+
cols = view.columns.select{|n,c| c[:table] == @table}
|
|
182
|
+
else
|
|
183
|
+
cols ||= @columns
|
|
184
|
+
end
|
|
185
|
+
data = blk.call if block_given?
|
|
186
|
+
sql = "UPDATE #{@table} SET "
|
|
187
|
+
set = []
|
|
188
|
+
vals = []
|
|
189
|
+
ret = []
|
|
190
|
+
i = 1
|
|
191
|
+
Waxx.debug "data: #{data}"
|
|
192
|
+
cols.each{|n,v|
|
|
193
|
+
Waxx.debug "col: #{n}: #{v.inspect}"
|
|
194
|
+
if data.has_key? n.to_s or data.has_key? n.to_sym
|
|
195
|
+
set << "#{n} = ?"
|
|
196
|
+
vals << cast(v, data/n)
|
|
197
|
+
ret << n.to_s
|
|
198
|
+
i += 1
|
|
199
|
+
end
|
|
200
|
+
}
|
|
201
|
+
sql << set.join(",")
|
|
202
|
+
sql << " WHERE #{@pkey} = ? #{where} RETURNING #{returning || ret.join(",")}"
|
|
203
|
+
vals << id
|
|
204
|
+
Waxx.debug(sql)
|
|
205
|
+
Waxx.debug(vals)
|
|
206
|
+
x.db[@db].execute(sql, vals).first
|
|
207
|
+
end
|
|
208
|
+
alias patch put
|
|
209
|
+
|
|
210
|
+
def put_post(x, id, data, cols:nil, returning:nil, view: nil)
|
|
211
|
+
q = nil
|
|
212
|
+
q = get_by_id(x, id, @pkey) if id.to_i > 0
|
|
213
|
+
return post(x, data, cols: cols, returning: returning, view: view) if q.nil?
|
|
214
|
+
put(x, id, data, cols: cols, returning: returning, view: view)
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def cast(col, val)
|
|
218
|
+
case col[:type].to_sym
|
|
219
|
+
when :int
|
|
220
|
+
val.to_s.empty? ? nil : val.to_i
|
|
221
|
+
when :float, :numeric
|
|
222
|
+
val.to_s.empty? ? nil : val.to_f
|
|
223
|
+
else
|
|
224
|
+
val
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def delete(x, id)
|
|
229
|
+
x.db[@db].execute("DELETE FROM #{@table} WHERE #{@pkey} = ?", [id])
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def order(req_order, default_order='')
|
|
233
|
+
return default_order if req_order.nil?
|
|
234
|
+
return orders[req_order.to_sym] if orders.has_key? req_order.to_sym
|
|
235
|
+
@pkey
|
|
236
|
+
end
|
|
237
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Waxx Copyright (c) 2016 ePark labs Inc. & Daniel J. Fitzpatrick <dan@eparklabs.com> All rights reserved.
|
|
2
|
+
# Released under the Apache Version 2 License. See LICENSE.txt.
|
|
3
|
+
|
|
4
|
+
# Supervises the thread pool
|
|
5
|
+
module Waxx::Supervisor
|
|
6
|
+
extend self
|
|
7
|
+
|
|
8
|
+
##
|
|
9
|
+
# Check the thread pool and add or remove threads
|
|
10
|
+
def check
|
|
11
|
+
start_threads = Waxx['server']['min_threads'] || Waxx['server']['threads']
|
|
12
|
+
max_threads = Waxx['server']['max_threads'] || Waxx['server']['threads']
|
|
13
|
+
idle_thread_timeout = Waxx['server']['idle_thread_timeout'] || 600
|
|
14
|
+
ts = Thread.list.select{|t| t[:name] =~ /waxx/}
|
|
15
|
+
Waxx.debug "Supervisor.check_threads: #{ts.size}", 5
|
|
16
|
+
# Ensure the minimum threads
|
|
17
|
+
if ts.size < start_threads
|
|
18
|
+
0.upto(start_threads - ts.size).each do |i|
|
|
19
|
+
Waxx::Server.create_thread(i)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
# If no idle threads add more (up-to max)
|
|
23
|
+
if ts.size < max_threads
|
|
24
|
+
idle_count = 0
|
|
25
|
+
ts.each{|t|
|
|
26
|
+
idle_count += 1 if t[:status] == 'idle'
|
|
27
|
+
}
|
|
28
|
+
if idle_count == 0
|
|
29
|
+
Waxx::Server.create_thread(1)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
# Terminate old threads
|
|
33
|
+
ts.each{|t|
|
|
34
|
+
if ts.size > start_threads
|
|
35
|
+
if Time.new.to_i - t[:last_used].to_i > idle_thread_timeout and t[:status] == 'idle' and Thread.current != t
|
|
36
|
+
Waxx.debug "Terminate expired thread #{t[:name]}", 3
|
|
37
|
+
Waxx['databases'].each{|n,v|
|
|
38
|
+
t[:db][n.to_sym].close rescue "already closed"
|
|
39
|
+
}
|
|
40
|
+
t.exit
|
|
41
|
+
ts.delete t
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
}
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
end
|