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/process.rb
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
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
|
+
# Derived from dante
|
|
5
|
+
|
|
6
|
+
module Waxx::Process
|
|
7
|
+
|
|
8
|
+
class Runner
|
|
9
|
+
MAX_START_TRIES = 5
|
|
10
|
+
|
|
11
|
+
attr_accessor :options, :name, :description, :verify_options_hook
|
|
12
|
+
|
|
13
|
+
class << self
|
|
14
|
+
def run(*args, &block)
|
|
15
|
+
self.new(*args, &block)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def initialize(name, defaults={}, &block)
|
|
20
|
+
@name = name
|
|
21
|
+
@startup_command = block
|
|
22
|
+
@options = {
|
|
23
|
+
:host => '0.0.0.0',
|
|
24
|
+
:pid_path => "/var/run/#{@name}.pid",
|
|
25
|
+
:log_path => false,
|
|
26
|
+
:debug => true
|
|
27
|
+
}.merge(defaults)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Accepts options for the process
|
|
31
|
+
# `@runner.with_options { |opts| opts.on(...) }`
|
|
32
|
+
def with_options(&block)
|
|
33
|
+
@with_options = block
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Executes the runner based on options
|
|
37
|
+
# `@runner.execute`
|
|
38
|
+
# `@runner.execute { ... }`
|
|
39
|
+
def execute(opts={}, &block)
|
|
40
|
+
parse_options
|
|
41
|
+
self.options.merge!(opts)
|
|
42
|
+
|
|
43
|
+
@verify_options_hook.call(self.options) if @verify_options_hook
|
|
44
|
+
|
|
45
|
+
if options.include?(:kill)
|
|
46
|
+
self.stop
|
|
47
|
+
else # create process
|
|
48
|
+
self.stop if options.include?(:restart)
|
|
49
|
+
|
|
50
|
+
# If a username, uid, groupname, or gid is passed,
|
|
51
|
+
# drop privileges accordingly.
|
|
52
|
+
|
|
53
|
+
if options[:group]
|
|
54
|
+
gid = options[:group].is_a?(Integer) ? options[:group] : Etc.getgrnam(options[:group]).gid
|
|
55
|
+
Process::GID.change_privilege(gid)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
if options[:user]
|
|
59
|
+
uid = options[:user].is_a?(Integer) ? options[:user] : Etc.getpwnam(options[:user]).uid
|
|
60
|
+
Process::UID.change_privilege(uid)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
@startup_command = block if block_given?
|
|
64
|
+
options[:daemonize] ? daemonize : start
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def daemonize
|
|
69
|
+
return log("Process is already started") if self.daemon_running? # daemon already started
|
|
70
|
+
|
|
71
|
+
if !options[:log_path]
|
|
72
|
+
options[:log_path] = "/var/log/#{@name}.log"
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Start process
|
|
76
|
+
pid = fork do
|
|
77
|
+
exit if fork
|
|
78
|
+
Process.setsid
|
|
79
|
+
exit if fork
|
|
80
|
+
store_pid(Process.pid)
|
|
81
|
+
File.umask 0000
|
|
82
|
+
redirect_output!
|
|
83
|
+
start
|
|
84
|
+
end
|
|
85
|
+
Process.waitpid pid
|
|
86
|
+
# Ensure process is running
|
|
87
|
+
if until_true(MAX_START_TRIES) { self.daemon_running? }
|
|
88
|
+
log "Daemon has started successfully"
|
|
89
|
+
true
|
|
90
|
+
else # Failed to start
|
|
91
|
+
log "Daemonized process couldn't be started"
|
|
92
|
+
false
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def start
|
|
97
|
+
log "Starting #{@name} service..."
|
|
98
|
+
|
|
99
|
+
if log_path = options[:log_path] && options[:daemonize].nil?
|
|
100
|
+
redirect_output!
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
trap("INT") {
|
|
104
|
+
interrupt
|
|
105
|
+
exit
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
trap("TERM"){
|
|
109
|
+
log "Trying to stop #{@name}..."
|
|
110
|
+
exit
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
@startup_command.call(self.options) if @startup_command
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Stops a daemonized process
|
|
117
|
+
def stop(kill_arg=nil)
|
|
118
|
+
if self.daemon_running?
|
|
119
|
+
kill_pid(kill_arg || options[:kill])
|
|
120
|
+
until_true(MAX_START_TRIES) { self.daemon_stopped? }
|
|
121
|
+
else # not running
|
|
122
|
+
log "No #{@name} processes are running"
|
|
123
|
+
false
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def restart
|
|
128
|
+
self.stop
|
|
129
|
+
self.start
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def interrupt
|
|
133
|
+
if options[:debug]
|
|
134
|
+
raise Interrupt
|
|
135
|
+
sleep 1
|
|
136
|
+
else
|
|
137
|
+
log "Interrupt received; stopping #{@name}"
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Returns true if process is not running
|
|
142
|
+
def daemon_stopped?
|
|
143
|
+
! self.daemon_running?
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Returns running for the daemonized process
|
|
147
|
+
# self.daemon_running?
|
|
148
|
+
def daemon_running?
|
|
149
|
+
return false unless File.exist?(options[:pid_path])
|
|
150
|
+
Process.kill 0, File.read(options[:pid_path]).to_i
|
|
151
|
+
true
|
|
152
|
+
rescue Errno::ESRCH
|
|
153
|
+
false
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
protected
|
|
157
|
+
|
|
158
|
+
def parse_options
|
|
159
|
+
headline = [@name, @description].compact.join(" - ")
|
|
160
|
+
OptionParser.new do |opts|
|
|
161
|
+
opts.summary_width = 25
|
|
162
|
+
opts.banner = [headline, "\n\n",
|
|
163
|
+
"Usage: #{@name} [-p port] [-P file] [-d] [-k]\n",
|
|
164
|
+
" #{@name} --help\n"].compact.join("")
|
|
165
|
+
opts.separator ""
|
|
166
|
+
|
|
167
|
+
opts.on("-p", "--port PORT", Integer, "Specify port", "(default: #{options[:port]})") do |v|
|
|
168
|
+
options[:port] = v
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
opts.on("-P", "--pid FILE", String, "save PID in FILE when using -d option.", "(default: #{options[:pid_path]})") do |v|
|
|
172
|
+
options[:pid_path] = File.expand_path(v)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
opts.on("-d", "--daemon", "Daemonize mode") do |v|
|
|
176
|
+
options[:daemonize] = v
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
opts.on("-l", "--log FILE", String, "Logfile for output", "(default: /var/log/#{@name}.log)") do |v|
|
|
180
|
+
options[:log_path] = v
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
opts.on("-k", "--kill [PORT]", String, "Kill specified running daemons - leave blank to kill all.") do |v|
|
|
184
|
+
options[:kill] = v
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
opts.on("-u", "--user USER", String, "User to run as") do |user|
|
|
188
|
+
options[:user] = user
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
opts.on("-G", "--group GROUP", String, "Group to run as") do |group|
|
|
192
|
+
options[:group] = group
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
opts.on_tail("-?", "--help", "Display this usage information.") do
|
|
196
|
+
puts "#{opts}\n"
|
|
197
|
+
exit
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# Load options specified through 'with_options'
|
|
201
|
+
instance_exec(opts, &@with_options) if @with_options
|
|
202
|
+
end.parse!
|
|
203
|
+
options
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def store_pid(pid)
|
|
207
|
+
FileUtils.mkdir_p(File.dirname(options[:pid_path]))
|
|
208
|
+
File.open(options[:pid_path], 'w'){|f| f.write("#{pid}\n")}
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def kill_pid(k='*')
|
|
212
|
+
Dir[options[:pid_path]].each do |f|
|
|
213
|
+
begin
|
|
214
|
+
pid = IO.read(f).chomp.to_i
|
|
215
|
+
FileUtils.rm f
|
|
216
|
+
Process.kill('TERM', pid)
|
|
217
|
+
log "Stopped PID: #{pid} at #{f}"
|
|
218
|
+
rescue => e
|
|
219
|
+
log "Failed to stop! #{k}: #{e}"
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# Redirect output based on log settings (reopens stdout/stderr to specified logfile)
|
|
225
|
+
# If log_path is nil, redirect to /dev/null to quiet output
|
|
226
|
+
def redirect_output!
|
|
227
|
+
if log_path = options[:log_path]
|
|
228
|
+
# if the log directory doesn't exist, create it
|
|
229
|
+
FileUtils.mkdir_p File.dirname(options[:log_path]), :mode => 0755
|
|
230
|
+
# touch the log file to create it
|
|
231
|
+
FileUtils.touch log_path
|
|
232
|
+
# Set permissions on the log file
|
|
233
|
+
File.chmod(0644, log_path)
|
|
234
|
+
# Reopen $stdout (NOT +STDOUT+) to start writing to the log file
|
|
235
|
+
$stdout.reopen(log_path, 'a')
|
|
236
|
+
# Redirect $stderr to $stdout
|
|
237
|
+
$stderr.reopen $stdout
|
|
238
|
+
$stdout.sync = true
|
|
239
|
+
else # redirect to /dev/null
|
|
240
|
+
# We're not bothering to sync if we're dumping to /dev/null
|
|
241
|
+
# because /dev/null doesn't care about buffered output
|
|
242
|
+
$stdin.reopen '/dev/null'
|
|
243
|
+
$stdout.reopen '/dev/null', 'a'
|
|
244
|
+
$stderr.reopen $stdout
|
|
245
|
+
end
|
|
246
|
+
log_path = options[:log_path] ? options[:log_path] : '/dev/null'
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
# Runs until the block condition is met or the timeout_seconds is exceeded
|
|
250
|
+
# until_true(10) { ...return_condition... }
|
|
251
|
+
def until_true(timeout_seconds, interval=1, &block)
|
|
252
|
+
elapsed_seconds = 0
|
|
253
|
+
while elapsed_seconds < timeout_seconds && block.call != true
|
|
254
|
+
elapsed_seconds += interval
|
|
255
|
+
sleep(interval)
|
|
256
|
+
end
|
|
257
|
+
elapsed_seconds < timeout_seconds
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
def log(message)
|
|
261
|
+
puts message
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def self.run(name, options={}, &blk)
|
|
267
|
+
Waxx::Process::Runner.new(name, options, &blk).execute
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
end
|
data/lib/waxx/req.rb
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# Waxx Copyright (c) 2016-2017 ePark labs Inc. & Daniel J. Fitzpatrick <dan@eparklabs.com>
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
module Waxx
|
|
16
|
+
|
|
17
|
+
##
|
|
18
|
+
# The Request Struct gets instanciated with each request (x.req)
|
|
19
|
+
# ```
|
|
20
|
+
# x.req.env # A hash of the request environment
|
|
21
|
+
# x.req.data # The raw body of put, post, patch requests
|
|
22
|
+
# x.req.meth # Request method as a string: GET, PUT, POST, PATCH, DELETE
|
|
23
|
+
# x.req.uri # The URI
|
|
24
|
+
# x.req.get # The GET/URL parameters (has with string keys and values) shortcut: x['name'] => 'value'
|
|
25
|
+
# x.req.post # The POST/BODY params with string keys and values) shortcut: x['name'] => 'value'
|
|
26
|
+
# ```
|
|
27
|
+
#
|
|
28
|
+
# GET params can be delimited with & or ;. The following are equivilant:
|
|
29
|
+
#
|
|
30
|
+
# ```
|
|
31
|
+
# http://localhost:7777/waxx/env?x=1&y=2&z=3
|
|
32
|
+
# http://localhost:7777/waxx/env?x=1;y=2;z=3
|
|
33
|
+
# ```
|
|
34
|
+
#
|
|
35
|
+
# Note that single params are single values and will be set to the last value received.
|
|
36
|
+
# Params names with "[]" appended are array values.
|
|
37
|
+
#
|
|
38
|
+
# ```
|
|
39
|
+
# http://localhost:7777/waxx/env?foo=1;foo=2;foo=3
|
|
40
|
+
# x['foo'] => "3"
|
|
41
|
+
#
|
|
42
|
+
# http://localhost:7777/waxx/env?foo[]=1;foo[]=2;foo[]=3
|
|
43
|
+
# x['foo'] => ["1", "2", "3"]
|
|
44
|
+
#
|
|
45
|
+
# ```
|
|
46
|
+
# If you are uploading JSON directly in the body (with the content type application/json or text/json), then the types are matched.
|
|
47
|
+
#
|
|
48
|
+
# Given the a request like:
|
|
49
|
+
#
|
|
50
|
+
# ```
|
|
51
|
+
# Content-Type: application/json
|
|
52
|
+
#
|
|
53
|
+
# {foo:123,bar:['a','1',2]}
|
|
54
|
+
# ```
|
|
55
|
+
#
|
|
56
|
+
# The following vars are of the type submitted
|
|
57
|
+
#
|
|
58
|
+
# ```
|
|
59
|
+
# x['foo'] => 123 (as an int)
|
|
60
|
+
# x['bar'] => ['a','1',2] (1 is a string and 2 is an int)
|
|
61
|
+
# ```
|
|
62
|
+
#
|
|
63
|
+
# ### File Uploads
|
|
64
|
+
# Given the form:
|
|
65
|
+
#
|
|
66
|
+
# ```
|
|
67
|
+
# <form action="/file/upload" method="post" enctype="multipart/form-data">
|
|
68
|
+
# <input type="file" name="file">
|
|
69
|
+
# <button type="submit">Upload File</button>
|
|
70
|
+
# </form>
|
|
71
|
+
# ```
|
|
72
|
+
#
|
|
73
|
+
# The following hash is available (symbol keys):
|
|
74
|
+
#
|
|
75
|
+
# ```
|
|
76
|
+
# x['file'][:filename] => 'file_name.ext'
|
|
77
|
+
# x['file'][:data] => The content of the file
|
|
78
|
+
# x['file'][:content_type] => The Content-Type as sent by the browser
|
|
79
|
+
# x['file'][:headers] => An hash of other headers send by the browser regarding this file
|
|
80
|
+
# ```
|
|
81
|
+
#
|
|
82
|
+
# How to save a file to the tmp folder:
|
|
83
|
+
#
|
|
84
|
+
# **app/file/file.rb**
|
|
85
|
+
#
|
|
86
|
+
# ```
|
|
87
|
+
# module App::File
|
|
88
|
+
# extend Waxx::Object
|
|
89
|
+
# runs(
|
|
90
|
+
# upload: {
|
|
91
|
+
# desc: 'Upload a file to the tmp folder',
|
|
92
|
+
# post: -> (x) {
|
|
93
|
+
# # Strip any non-word chars and save the file to the tmp folder
|
|
94
|
+
# File.open("#{Waxx::Root}/tmp/(x/:file/:filename).gsub(/[\W\.]/,'-'),'wb'){|f|
|
|
95
|
+
# f << x/:file/:data
|
|
96
|
+
# }
|
|
97
|
+
# x << "Your file has been uploaded."
|
|
98
|
+
# }
|
|
99
|
+
# }
|
|
100
|
+
# )
|
|
101
|
+
# end
|
|
102
|
+
# ```
|
|
103
|
+
#
|
|
104
|
+
Req = Struct.new(
|
|
105
|
+
:env,
|
|
106
|
+
:data,
|
|
107
|
+
:meth,
|
|
108
|
+
:uri,
|
|
109
|
+
:get,
|
|
110
|
+
:post,
|
|
111
|
+
:cookies,
|
|
112
|
+
:start_time
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
end
|
|
116
|
+
|
data/lib/waxx/res.rb
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# Waxx Copyright (c) 2016-2017 ePark labs Inc. & Daniel J. Fitzpatrick <dan@eparklabs.com>
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
module Waxx
|
|
16
|
+
|
|
17
|
+
##
|
|
18
|
+
# The Response Struct gets instanciated with each request (x.res)
|
|
19
|
+
#
|
|
20
|
+
# ```
|
|
21
|
+
# x.res[name] = value # Set a response header
|
|
22
|
+
# x.res.as(extention) # Set the Content-Type header based on extension
|
|
23
|
+
# x.res.redirect '/path' # Redirect the client with 302 / Location header
|
|
24
|
+
# x.res.location '/path' # Redirect the client with 302 / Location header
|
|
25
|
+
# x.res.cookie( # Set a cookie
|
|
26
|
+
# name: "",
|
|
27
|
+
# value: nil,
|
|
28
|
+
# domain: nil,
|
|
29
|
+
# expires: nil,
|
|
30
|
+
# path: "/",
|
|
31
|
+
# secure: true,
|
|
32
|
+
# http_only: false,
|
|
33
|
+
# same_site: "Lax"
|
|
34
|
+
# )
|
|
35
|
+
# x << "ouput" # Append output to the response body
|
|
36
|
+
# x.res << "output" # Append output to the response body
|
|
37
|
+
# ```
|
|
38
|
+
Res = Struct.new(
|
|
39
|
+
:server,
|
|
40
|
+
:status,
|
|
41
|
+
:headers,
|
|
42
|
+
:out,
|
|
43
|
+
:error,
|
|
44
|
+
:cookies,
|
|
45
|
+
:no_cookies
|
|
46
|
+
) do
|
|
47
|
+
|
|
48
|
+
# Send output to the client (may be buffered)
|
|
49
|
+
def << str
|
|
50
|
+
out << str
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def [](n,v)
|
|
54
|
+
headers[n]
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def []=(n,v)
|
|
58
|
+
headers[n] = v
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def as(ext)
|
|
62
|
+
headers['Content-Type'] = Waxx::Http.ctype(ext)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def location(uri)
|
|
66
|
+
self.status = 302
|
|
67
|
+
headers['Location'] = uri
|
|
68
|
+
end
|
|
69
|
+
alias redirect location
|
|
70
|
+
|
|
71
|
+
# Return the response headers
|
|
72
|
+
def head
|
|
73
|
+
[
|
|
74
|
+
"HTTP/1.1 #{status} #{Waxx::Http::Status[status.to_s]}",
|
|
75
|
+
headers.map{|n,v| "#{n}: #{v}"},
|
|
76
|
+
(cookies.map{|c|
|
|
77
|
+
"Set-Cookie: #{c}"
|
|
78
|
+
} unless no_cookies),
|
|
79
|
+
"\r\n"
|
|
80
|
+
].flatten.join("\r\n")
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Output the headers and the body
|
|
84
|
+
def complete
|
|
85
|
+
re = out.join
|
|
86
|
+
headers["Content-Length"] = re.bytesize
|
|
87
|
+
server.print head
|
|
88
|
+
server.print re
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def cookie(name:"", value:nil, domain:nil, expires:nil, path:"/", secure:true, http_only: false, same_site: "Lax")
|
|
92
|
+
expires = expires.nil? ? "" : "expires=#{Time === expires ? expires.rfc2822 : expires}; "
|
|
93
|
+
cookies << "#{name}=#{Waxx::Http.escape(value.to_s)}; #{expires}#{";domain=#{domain}" if domain}; path=#{path}#{"; secure" if secure}#{"; HttpOnly" if http_only}; SameSite=#{same_site}"
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
end
|
|
98
|
+
|