cannon 0.0.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
- data/.bundle/config +2 -0
- data/.gitignore +4 -0
- data/.rspec +2 -0
- data/Gemfile +5 -0
- data/README +3 -0
- data/Rakefile +20 -0
- data/bin/bundler +16 -0
- data/bin/coderay +16 -0
- data/bin/htmldiff +16 -0
- data/bin/ldiff +16 -0
- data/bin/mustache +16 -0
- data/bin/pry +16 -0
- data/bin/rspec +16 -0
- data/cannon.gemspec +29 -0
- data/lib/cannon.rb +16 -0
- data/lib/cannon/app.rb +196 -0
- data/lib/cannon/concerns/path_cache.rb +48 -0
- data/lib/cannon/concerns/signature.rb +14 -0
- data/lib/cannon/config.rb +58 -0
- data/lib/cannon/cookie_jar.rb +99 -0
- data/lib/cannon/handler.rb +25 -0
- data/lib/cannon/middleware.rb +47 -0
- data/lib/cannon/middleware/content_type.rb +15 -0
- data/lib/cannon/middleware/cookies.rb +18 -0
- data/lib/cannon/middleware/files.rb +33 -0
- data/lib/cannon/middleware/flush_and_benchmark.rb +20 -0
- data/lib/cannon/middleware/request_logger.rb +13 -0
- data/lib/cannon/middleware/router.rb +19 -0
- data/lib/cannon/request.rb +36 -0
- data/lib/cannon/response.rb +165 -0
- data/lib/cannon/route.rb +80 -0
- data/lib/cannon/route_action.rb +92 -0
- data/lib/cannon/version.rb +3 -0
- data/lib/cannon/views.rb +28 -0
- data/spec/app_spec.rb +20 -0
- data/spec/config_spec.rb +41 -0
- data/spec/environments_spec.rb +68 -0
- data/spec/features/action_types_spec.rb +154 -0
- data/spec/features/cookies_spec.rb +62 -0
- data/spec/features/files_spec.rb +17 -0
- data/spec/features/method_types_spec.rb +104 -0
- data/spec/features/requests_spec.rb +59 -0
- data/spec/features/views_spec.rb +31 -0
- data/spec/fixtures/public/background.jpg +0 -0
- data/spec/fixtures/views/render_test.html +1 -0
- data/spec/fixtures/views/test.html +1 -0
- data/spec/spec_helper.rb +98 -0
- data/spec/support/cannon_test.rb +108 -0
- metadata +219 -0
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
|
3
|
+
module Signature
|
4
|
+
class CookieSecretNotSet < StandardError; end
|
5
|
+
|
6
|
+
def signature(value)
|
7
|
+
raise CookieSecretNotSet, 'Set config.cookies.secret to use signed cookies' if Cannon.config.cookies.secret.nil?
|
8
|
+
OpenSSL::HMAC.hexdigest(digest, Cannon.config.cookies.secret, value)
|
9
|
+
end
|
10
|
+
|
11
|
+
def digest
|
12
|
+
@digest ||= OpenSSL::Digest.new('sha1')
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Cannon
|
2
|
+
class UnknownLogLevel < StandardError; end
|
3
|
+
|
4
|
+
class Config
|
5
|
+
attr_accessor :middleware, :public_path, :view_path, :reload_on_request, :benchmark_requests, :port, :ip_address
|
6
|
+
attr_reader :logger, :log_level
|
7
|
+
|
8
|
+
DEFAULT_MIDDLEWARE = %w{RequestLogger Files Cookies Router ContentType}
|
9
|
+
|
10
|
+
LOG_LEVELS = {
|
11
|
+
unknown: Logger::UNKNOWN,
|
12
|
+
fatal: Logger::FATAL,
|
13
|
+
error: Logger::ERROR,
|
14
|
+
warn: Logger::WARN,
|
15
|
+
info: Logger::INFO,
|
16
|
+
debug: Logger::DEBUG,
|
17
|
+
}
|
18
|
+
|
19
|
+
def initialize
|
20
|
+
self.ip_address = '127.0.0.1'
|
21
|
+
self.port = 5030
|
22
|
+
self.middleware = DEFAULT_MIDDLEWARE
|
23
|
+
self.public_path = 'public'
|
24
|
+
self.view_path = 'views'
|
25
|
+
self.reload_on_request = false
|
26
|
+
self.benchmark_requests = true
|
27
|
+
@log_level = :info
|
28
|
+
self.logger = Logger.new(STDOUT)
|
29
|
+
end
|
30
|
+
|
31
|
+
def logger=(value)
|
32
|
+
@logger = value
|
33
|
+
logger.datetime_format = '%Y-%m-%d %H:%M:%S'
|
34
|
+
logger.formatter = proc do |severity, datetime, progname, msg|
|
35
|
+
"#{msg}\n"
|
36
|
+
end
|
37
|
+
self.log_level = log_level
|
38
|
+
end
|
39
|
+
|
40
|
+
def log_level=(value)
|
41
|
+
raise UnknownLogLevel unless LOG_LEVELS.keys.include? value.to_sym
|
42
|
+
@log_level = value
|
43
|
+
logger.level = LOG_LEVELS[value.to_sym]
|
44
|
+
end
|
45
|
+
|
46
|
+
def cookies
|
47
|
+
@cookies ||= Cookies.new
|
48
|
+
end
|
49
|
+
|
50
|
+
class Cookies
|
51
|
+
attr_accessor :secret
|
52
|
+
|
53
|
+
def initialize
|
54
|
+
self.secret = nil
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'msgpack'
|
2
|
+
|
3
|
+
module Cannon
|
4
|
+
class CookieJar
|
5
|
+
include Signature
|
6
|
+
|
7
|
+
class EndOfString < Exception; end
|
8
|
+
|
9
|
+
def initialize(http_cookie: nil, cookies: nil, signed: false)
|
10
|
+
@http_cookie = http_cookie
|
11
|
+
@cookies = cookies
|
12
|
+
@signed = signed
|
13
|
+
|
14
|
+
self.define_singleton_method(:signed) do
|
15
|
+
@signed_cookies ||= CookieJar.new(cookies: cookies_with_signatures, signed: true)
|
16
|
+
end if !@signed
|
17
|
+
end
|
18
|
+
|
19
|
+
def [](cookie_name)
|
20
|
+
cookie = cookies[cookie_name]
|
21
|
+
if cookie
|
22
|
+
@signed ? verified_signature(cookie_name, cookie) : cookie['value']
|
23
|
+
else
|
24
|
+
nil
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def verified_signature(name, cookie)
|
31
|
+
return cookie['value'] if cookie['verified']
|
32
|
+
|
33
|
+
if cookie['signature'] == signature(cookie['value'])
|
34
|
+
cookie['verified'] = true
|
35
|
+
cookie['value']
|
36
|
+
else
|
37
|
+
cookies.delete(name)
|
38
|
+
nil
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def cookies_with_signatures
|
43
|
+
cookies.select { |k, v| v.include? 'signature' }
|
44
|
+
end
|
45
|
+
|
46
|
+
def cookies
|
47
|
+
@cookies ||= parse_cookies
|
48
|
+
end
|
49
|
+
|
50
|
+
def parse_cookies
|
51
|
+
cookies = {}
|
52
|
+
return cookies if @http_cookie.nil? || @http_cookie == ''
|
53
|
+
|
54
|
+
begin
|
55
|
+
pos = 0
|
56
|
+
loop do
|
57
|
+
pos = read_whitespace(@http_cookie, pos)
|
58
|
+
name, pos = read_cookie_name(@http_cookie, pos)
|
59
|
+
value, pos = read_cookie_value(@http_cookie, pos)
|
60
|
+
begin
|
61
|
+
cookies[name.to_sym] = MessagePack.unpack(value)
|
62
|
+
rescue StandardError; end
|
63
|
+
end
|
64
|
+
rescue EndOfString
|
65
|
+
end
|
66
|
+
|
67
|
+
cookies
|
68
|
+
end
|
69
|
+
|
70
|
+
def read_whitespace(cookie, pos)
|
71
|
+
raise EndOfString if cookie[pos] == nil
|
72
|
+
pos = pos + 1 while cookie[pos] == ' ' && pos < cookie.length
|
73
|
+
pos
|
74
|
+
end
|
75
|
+
|
76
|
+
def read_cookie_name(cookie, pos)
|
77
|
+
start_pos = pos
|
78
|
+
pos = pos + 1 while !['=', nil].include?(cookie[pos])
|
79
|
+
return cookie[start_pos..(pos - 1)], pos + 1
|
80
|
+
end
|
81
|
+
|
82
|
+
def read_cookie_value(cookie, pos)
|
83
|
+
in_quotes = false
|
84
|
+
pos = pos + 1 and in_quotes = true if cookie[pos] == '"'
|
85
|
+
start_pos = pos
|
86
|
+
|
87
|
+
if in_quotes
|
88
|
+
pos = pos + 1 while pos < cookie.length && !(cookie[pos] == '"' && cookie[pos - 1] != '\\')
|
89
|
+
value = cookie[start_pos..(pos - 1)].gsub("\\\"", '"')
|
90
|
+
pos = pos + 1 while ![';', nil].include?(cookie[pos])
|
91
|
+
else
|
92
|
+
pos = pos + 1 while ![';', nil].include?(cookie[pos])
|
93
|
+
value = cookie[start_pos..(pos - 1)]
|
94
|
+
end
|
95
|
+
|
96
|
+
return value, pos + 1
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Cannon
|
2
|
+
class Handler < EventMachine::Connection
|
3
|
+
include EventMachine::HttpServer
|
4
|
+
|
5
|
+
def app
|
6
|
+
# magically defined by Cannon::App
|
7
|
+
self.class.app
|
8
|
+
end
|
9
|
+
|
10
|
+
def process_http_request
|
11
|
+
request = Request.new(self, app)
|
12
|
+
response = Response.new(self, app)
|
13
|
+
|
14
|
+
app.reload_environment if app.config.reload_on_request
|
15
|
+
|
16
|
+
app.middleware_runner.run(request, response) if middleware?
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def middleware?
|
22
|
+
app.config.middleware.size > 0
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'cannon/middleware/flush_and_benchmark'
|
2
|
+
require 'cannon/middleware/request_logger'
|
3
|
+
require 'cannon/middleware/files'
|
4
|
+
require 'cannon/middleware/router'
|
5
|
+
require 'cannon/middleware/content_type'
|
6
|
+
require 'cannon/middleware/cookies'
|
7
|
+
|
8
|
+
module Cannon
|
9
|
+
class MiddlewareRunner
|
10
|
+
include EventMachine::Deferrable
|
11
|
+
|
12
|
+
def initialize(ware, callback:, app:)
|
13
|
+
@app = app
|
14
|
+
@ware, @callback = instantiate(ware), callback
|
15
|
+
end
|
16
|
+
|
17
|
+
def run(request, response)
|
18
|
+
next_proc = -> do
|
19
|
+
setup_callback
|
20
|
+
self.succeed(request, response)
|
21
|
+
end
|
22
|
+
|
23
|
+
result = @ware.run(request, response, next_proc)
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def setup_callback
|
29
|
+
set_deferred_status nil
|
30
|
+
callback do |request, response|
|
31
|
+
@callback.run(request, response) unless @callback.nil?
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def instantiate(ware)
|
36
|
+
if ware.is_a?(String)
|
37
|
+
begin
|
38
|
+
Object.const_get(ware).new(@app)
|
39
|
+
rescue NameError
|
40
|
+
Object.const_get("Cannon::Middleware::#{ware}").new(@app)
|
41
|
+
end
|
42
|
+
else
|
43
|
+
ware.new(@app)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Cannon
|
2
|
+
module Middleware
|
3
|
+
class ContentType
|
4
|
+
def initialize(app)
|
5
|
+
@app = app
|
6
|
+
end
|
7
|
+
|
8
|
+
def run(request, response, next_proc)
|
9
|
+
return next_proc.call unless response.headers['Content-Type'].nil?
|
10
|
+
response.headers['Content-Type'] = 'text/plain; charset=us-ascii'
|
11
|
+
next_proc.call
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Cannon
|
2
|
+
module Middleware
|
3
|
+
class Cookies
|
4
|
+
|
5
|
+
def initialize(app)
|
6
|
+
@app = app
|
7
|
+
end
|
8
|
+
|
9
|
+
def run(request, response, next_proc)
|
10
|
+
request.define_singleton_method(:cookies) do
|
11
|
+
@cookie_jar ||= CookieJar.new(http_cookie: request.http_cookie)
|
12
|
+
end
|
13
|
+
|
14
|
+
next_proc.call
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Cannon
|
2
|
+
module Middleware
|
3
|
+
class Files
|
4
|
+
include PathCache
|
5
|
+
|
6
|
+
def initialize(app)
|
7
|
+
@app = app
|
8
|
+
|
9
|
+
self.cache = :files
|
10
|
+
self.base_path = build_base_path
|
11
|
+
end
|
12
|
+
|
13
|
+
def run(request, response, next_proc)
|
14
|
+
reload_cache if outdated_cache?
|
15
|
+
|
16
|
+
if path_array.include? request.path
|
17
|
+
file, content_type = *file_and_content_type("#{base_path}#{request.path}")
|
18
|
+
response.header('Content-Type', content_type)
|
19
|
+
response.send(file)
|
20
|
+
response.flush
|
21
|
+
else
|
22
|
+
next_proc.call
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def build_base_path
|
29
|
+
@app.config.public_path =~ /^\// ? @app.config.public_path : "#{Cannon.root}/#{@app.config.public_path}"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Cannon
|
2
|
+
module Middleware
|
3
|
+
class FlushAndBenchmark
|
4
|
+
def initialize(app)
|
5
|
+
@app = app
|
6
|
+
end
|
7
|
+
|
8
|
+
def run(request, response, next_proc)
|
9
|
+
response.flush unless response.flushed?
|
10
|
+
Cannon.logger.debug "Response took #{time_ago_in_ms(request.start_time)}ms" if @app.config.benchmark_requests
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def time_ago_in_ms(time_ago)
|
16
|
+
Time.at((Time.now - time_ago)).strftime('%6N').to_i/1000.0
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Cannon
|
2
|
+
module Middleware
|
3
|
+
class Router
|
4
|
+
def initialize(app)
|
5
|
+
@app = app
|
6
|
+
end
|
7
|
+
|
8
|
+
def run(request, response, next_proc)
|
9
|
+
matched_route = @app.routes.find { |route| route.matches? request }
|
10
|
+
if matched_route.nil?
|
11
|
+
response.not_found
|
12
|
+
next_proc.call
|
13
|
+
else
|
14
|
+
matched_route.handle(request, response, next_proc)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
|
3
|
+
module Cannon
|
4
|
+
class Request
|
5
|
+
attr_accessor :protocol, :method, :http_cookie, :content_type, :path, :uri, :query_string, :post_content, :headers,
|
6
|
+
:start_time
|
7
|
+
|
8
|
+
def initialize(http_server, app)
|
9
|
+
self.protocol = http_server.instance_variable_get('@http_protocol')
|
10
|
+
self.method = http_server.instance_variable_get('@http_request_method')
|
11
|
+
self.http_cookie = http_server.instance_variable_get('@http_cookie')
|
12
|
+
self.content_type = http_server.instance_variable_get('@http_content_type')
|
13
|
+
self.path = http_server.instance_variable_get('@http_path_info')
|
14
|
+
self.uri = http_server.instance_variable_get('@http_request_uri')
|
15
|
+
self.query_string = http_server.instance_variable_get('@http_query_string')
|
16
|
+
self.post_content = http_server.instance_variable_get('@http_post_content')
|
17
|
+
self.headers = http_server.instance_variable_get('@http_headers')
|
18
|
+
self.start_time = Time.now
|
19
|
+
end
|
20
|
+
|
21
|
+
def params
|
22
|
+
@params ||= parse_params
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def parse_params
|
28
|
+
case method.downcase
|
29
|
+
when 'get'
|
30
|
+
Hash[CGI::parse(query_string || '').map { |(k, v)| [k.to_sym, v.last] }]
|
31
|
+
else
|
32
|
+
Hash[CGI::parse(post_content || '').map { |(k, v)| [k.to_sym, v.count > 1 ? v : v.first] }]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,165 @@
|
|
1
|
+
require 'msgpack'
|
2
|
+
|
3
|
+
module Cannon
|
4
|
+
class Response
|
5
|
+
extend Forwardable
|
6
|
+
include Views
|
7
|
+
include Signature
|
8
|
+
|
9
|
+
attr_reader :delegated_response, :headers
|
10
|
+
attr_accessor :status
|
11
|
+
|
12
|
+
delegate :content => :delegated_response
|
13
|
+
delegate :content= => :delegated_response
|
14
|
+
|
15
|
+
HTTP_STATUS = {
|
16
|
+
continue: 100,
|
17
|
+
switching_protocols: 101,
|
18
|
+
ok: 200,
|
19
|
+
created: 201,
|
20
|
+
accepted: 202,
|
21
|
+
non_authoritative_information: 203,
|
22
|
+
no_content: 204,
|
23
|
+
reset_content: 205,
|
24
|
+
partial_content: 206,
|
25
|
+
multiple_choices: 300,
|
26
|
+
moved_permanently: 301,
|
27
|
+
found: 302,
|
28
|
+
see_other: 303,
|
29
|
+
not_modified: 304,
|
30
|
+
use_proxy: 305,
|
31
|
+
temporary_redirect: 307,
|
32
|
+
bad_request: 400,
|
33
|
+
unauthorized: 401,
|
34
|
+
payment_required: 402,
|
35
|
+
forbidden: 403,
|
36
|
+
not_found: 404,
|
37
|
+
method_not_allowed: 405,
|
38
|
+
not_acceptable: 406,
|
39
|
+
proxy_authentication_required: 407,
|
40
|
+
request_timeout: 408,
|
41
|
+
conflict: 409,
|
42
|
+
gone: 410,
|
43
|
+
length_required: 411,
|
44
|
+
precondition_failed: 412,
|
45
|
+
request_entity_too_large: 413,
|
46
|
+
request_uri_too_long: 414,
|
47
|
+
unsupported_media_type: 415,
|
48
|
+
requested_range_not_satisfied: 416,
|
49
|
+
expectation_failed: 417,
|
50
|
+
internal_server_error: 500,
|
51
|
+
not_implemented: 501,
|
52
|
+
bad_gateway: 502,
|
53
|
+
service_unavailable: 503,
|
54
|
+
gateway_timeout: 504,
|
55
|
+
http_version_not_supported: 505,
|
56
|
+
}
|
57
|
+
|
58
|
+
def initialize(http_server, app)
|
59
|
+
@app = app
|
60
|
+
@delegated_response = EventMachine::DelegatedHttpResponse.new(http_server)
|
61
|
+
@flushed = false
|
62
|
+
@headers = {}
|
63
|
+
@cookies = {}
|
64
|
+
|
65
|
+
initialize_views
|
66
|
+
|
67
|
+
self.status = :ok
|
68
|
+
end
|
69
|
+
|
70
|
+
def flushed?
|
71
|
+
@flushed
|
72
|
+
end
|
73
|
+
|
74
|
+
def send(content, status: self.status)
|
75
|
+
self.content ||= ''
|
76
|
+
self.status = status
|
77
|
+
delegated_response.status = converted_status(status)
|
78
|
+
delegated_response.content += content
|
79
|
+
end
|
80
|
+
|
81
|
+
def flush
|
82
|
+
unless flushed?
|
83
|
+
set_cookie_headers
|
84
|
+
delegated_response.headers = self.headers
|
85
|
+
delegated_response.send_response
|
86
|
+
@flushed = true
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def header(key, value)
|
91
|
+
headers[key] = value
|
92
|
+
end
|
93
|
+
|
94
|
+
def location_header(location)
|
95
|
+
header('Location', location)
|
96
|
+
end
|
97
|
+
|
98
|
+
def permanent_redirect(location)
|
99
|
+
location_header(location)
|
100
|
+
self.status = :moved_permanently
|
101
|
+
flush
|
102
|
+
end
|
103
|
+
|
104
|
+
def temporary_redirect(location)
|
105
|
+
location_header(location)
|
106
|
+
self.status = :found
|
107
|
+
flush
|
108
|
+
end
|
109
|
+
|
110
|
+
def not_found
|
111
|
+
send('Not Found', status: :not_found)
|
112
|
+
end
|
113
|
+
|
114
|
+
def internal_server_error(title:, content:)
|
115
|
+
html = "<html><head><title>Internal Server Error: #{title}</title></head><body><h1>#{title}</h1><p>#{content}</p></body></html>"
|
116
|
+
header('Content-Type', 'text/html')
|
117
|
+
send(html, status: :internal_server_error)
|
118
|
+
end
|
119
|
+
|
120
|
+
def cookie(cookie, value:, expires: nil, httponly: nil, signed: false)
|
121
|
+
cookie_options = {:value => value}
|
122
|
+
cookie_options[:expires] = expires unless expires.nil?
|
123
|
+
cookie_options[:httponly] = httponly unless httponly.nil?
|
124
|
+
cookie_options[:signed] = signed
|
125
|
+
@cookies[cookie] = cookie_options
|
126
|
+
end
|
127
|
+
|
128
|
+
private
|
129
|
+
|
130
|
+
def set_cookie_headers
|
131
|
+
cookie_headers = (headers['Set-Cookie'] = [])
|
132
|
+
@cookies.each do |cookie, cookie_options|
|
133
|
+
cookie_headers << build_cookie_value(cookie, cookie_options)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def build_cookie_value(name, options)
|
138
|
+
cookie = "#{name}=#{cookie_value(options[:value], signed: options[:signed])}"
|
139
|
+
cookie << "; Expires=#{options[:expires].httpdate}" if options.include?(:expires)
|
140
|
+
cookie << '; HttpOnly' if options[:httponly] == true
|
141
|
+
cookie
|
142
|
+
end
|
143
|
+
|
144
|
+
def converted_status(status)
|
145
|
+
if status.is_a?(Symbol)
|
146
|
+
HTTP_STATUS[status] || status.to_s
|
147
|
+
elsif status.is_a?(Fixnum)
|
148
|
+
status
|
149
|
+
else
|
150
|
+
status.to_s
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def cookie_value(value, signed:)
|
155
|
+
cookie_hash = {'value' => value}
|
156
|
+
cookie_hash['signature'] = signature(value) if signed
|
157
|
+
escape_cookie_value(cookie_hash.to_msgpack)
|
158
|
+
end
|
159
|
+
|
160
|
+
def escape_cookie_value(value)
|
161
|
+
return value unless value.match(/([\x00-\x20\x7F",;\\])/)
|
162
|
+
"\"#{value.gsub(/([\\"])/, "\\\\\\1")}\""
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|