tynn 1.0.0.rc2 → 1.0.0.rc3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +1 -3
- data/lib/tynn.rb +61 -102
- data/lib/tynn/all_methods.rb +11 -9
- data/lib/tynn/environment.rb +13 -13
- data/lib/tynn/force_ssl.rb +55 -0
- data/lib/tynn/hmote.rb +34 -0
- data/lib/tynn/hsts.rb +64 -0
- data/lib/tynn/json.rb +34 -30
- data/lib/tynn/matchers.rb +50 -48
- data/lib/tynn/not_found.rb +11 -9
- data/lib/tynn/protection.rb +29 -18
- data/lib/tynn/render.rb +24 -23
- data/lib/tynn/response.rb +138 -126
- data/lib/tynn/secure_headers.rb +29 -30
- data/lib/tynn/session.rb +62 -64
- data/lib/tynn/settings.rb +28 -0
- data/lib/tynn/static.rb +23 -20
- data/lib/tynn/test.rb +30 -31
- data/lib/tynn/version.rb +1 -1
- data/test/default_headers_test.rb +14 -0
- data/test/environment_test.rb +14 -18
- data/test/force_ssl_test.rb +32 -0
- data/test/hmote_test.rb +78 -0
- data/test/hsts_test.rb +29 -0
- data/test/protection_test.rb +0 -30
- data/test/render_test.rb +14 -1
- data/test/secure_headers_test.rb +8 -1
- metadata +15 -22
- data/lib/tynn/erubis.rb +0 -17
- data/lib/tynn/ssl.rb +0 -73
- data/test/erubis_test.rb +0 -20
- data/test/ssl_test.rb +0 -82
data/lib/tynn/json.rb
CHANGED
@@ -1,42 +1,46 @@
|
|
1
1
|
require "json"
|
2
2
|
|
3
3
|
class Tynn
|
4
|
-
# Adds helper methods for json generation.
|
4
|
+
# Public: Adds helper methods for json generation.
|
5
5
|
#
|
6
|
-
#
|
7
|
-
# require "tynn"
|
8
|
-
# require "tynn/json"
|
6
|
+
# Examples
|
9
7
|
#
|
10
|
-
#
|
11
|
-
#
|
8
|
+
# require "tynn"
|
9
|
+
# require "tynn/json"
|
10
|
+
#
|
11
|
+
# Tynn.helpers(Tynn::JSON)
|
12
12
|
#
|
13
13
|
module JSON
|
14
|
-
|
14
|
+
CONTENT_TYPE = "application/json".freeze
|
15
15
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
16
|
+
module InstanceMethods
|
17
|
+
# Public: Calls +to_json+ on +data+ and writes the generated \JSON
|
18
|
+
# object into the response body. Also, It automatically sets the
|
19
|
+
# +Content-Type+ header to +application/json+.
|
20
|
+
#
|
21
|
+
# data - Any object that responds to +to_json+.
|
22
|
+
#
|
23
|
+
# Examples
|
24
|
+
#
|
25
|
+
# Tynn.define do
|
26
|
+
# on("hash") do
|
27
|
+
# json(foo: "bar")
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# on("array") do
|
31
|
+
# json([1, 2, 3])
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# on("to_json") do
|
35
|
+
# json(Model.first)
|
36
|
+
# end
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
def json(data)
|
40
|
+
res.headers[Rack::CONTENT_TYPE] = Tynn::JSON::CONTENT_TYPE
|
38
41
|
|
39
|
-
|
42
|
+
res.write(data.to_json)
|
43
|
+
end
|
40
44
|
end
|
41
45
|
end
|
42
46
|
end
|
data/lib/tynn/matchers.rb
CHANGED
@@ -1,59 +1,61 @@
|
|
1
1
|
class Tynn
|
2
|
-
# Adds extra matchers to Tynn.
|
2
|
+
# Public: Adds extra matchers to Tynn.
|
3
3
|
#
|
4
|
-
#
|
5
|
-
# require "tynn"
|
6
|
-
# require "tynn/matchers"
|
4
|
+
# Examples
|
7
5
|
#
|
8
|
-
#
|
9
|
-
#
|
6
|
+
# require "tynn"
|
7
|
+
# require "tynn/matchers"
|
8
|
+
#
|
9
|
+
# Tynn.helpers(Tynn::Matchers)
|
10
10
|
#
|
11
11
|
module Matchers
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
12
|
+
module InstanceMethods
|
13
|
+
# Public: A catch-all matcher.
|
14
|
+
#
|
15
|
+
# Examples
|
16
|
+
#
|
17
|
+
# Tynn.define do
|
18
|
+
# authenticated? do
|
19
|
+
# # ...
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# default do # on true
|
23
|
+
# # ...
|
24
|
+
# end
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# :call-seq: default(&block)
|
28
|
+
#
|
29
|
+
def default
|
30
|
+
yield
|
30
31
|
|
31
|
-
|
32
|
-
|
32
|
+
halt(res.finish)
|
33
|
+
end
|
33
34
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
35
|
+
# Public: Match if the given +key+ is present in +req.params+.
|
36
|
+
#
|
37
|
+
# Examples
|
38
|
+
#
|
39
|
+
# Tynn.define do
|
40
|
+
# param(:user) do |params|
|
41
|
+
# user = User.create(params)
|
42
|
+
#
|
43
|
+
# # ...
|
44
|
+
# end
|
45
|
+
#
|
46
|
+
# default do
|
47
|
+
# res.write("missing user param")
|
48
|
+
# end
|
49
|
+
# end
|
50
|
+
#
|
51
|
+
# :call-seq: param(key, &block)
|
52
|
+
#
|
53
|
+
def param(key)
|
54
|
+
if (v = req[key]) && !v.empty?
|
55
|
+
yield(v)
|
55
56
|
|
56
|
-
|
57
|
+
halt(res.finish)
|
58
|
+
end
|
57
59
|
end
|
58
60
|
end
|
59
61
|
end
|
data/lib/tynn/not_found.rb
CHANGED
@@ -1,18 +1,20 @@
|
|
1
1
|
class Tynn
|
2
2
|
module NotFound
|
3
|
-
|
4
|
-
|
3
|
+
module InstanceMethods
|
4
|
+
def call(*) # :nodoc:
|
5
|
+
result = super
|
5
6
|
|
6
|
-
|
7
|
-
|
7
|
+
if result[0] == 404 && result[2].empty?
|
8
|
+
not_found
|
8
9
|
|
9
|
-
|
10
|
-
|
11
|
-
|
10
|
+
return res.finish
|
11
|
+
else
|
12
|
+
return result
|
13
|
+
end
|
12
14
|
end
|
13
|
-
end
|
14
15
|
|
15
|
-
|
16
|
+
def not_found # :nodoc:
|
17
|
+
end
|
16
18
|
end
|
17
19
|
end
|
18
20
|
end
|
data/lib/tynn/protection.rb
CHANGED
@@ -1,41 +1,52 @@
|
|
1
1
|
require_relative "secure_headers"
|
2
2
|
|
3
3
|
class Tynn
|
4
|
-
# Adds security measures against common attacks.
|
4
|
+
# Public: Adds security measures against common attacks.
|
5
5
|
#
|
6
|
-
#
|
7
|
-
# require "tynn"
|
8
|
-
# require "tynn/protection"
|
6
|
+
# Examples
|
9
7
|
#
|
10
|
-
#
|
11
|
-
#
|
8
|
+
# require "tynn"
|
9
|
+
# require "tynn/protection"
|
10
|
+
#
|
11
|
+
# Tynn.helpers(Tynn::Protection)
|
12
12
|
#
|
13
13
|
# If you are using SSL/TLS (HTTPS), it's recommended to set
|
14
|
-
# the
|
14
|
+
# the +:ssl+ option:
|
15
|
+
#
|
16
|
+
# Examples
|
15
17
|
#
|
16
|
-
#
|
17
|
-
#
|
18
|
-
# require "tynn/protection"
|
18
|
+
# require "tynn"
|
19
|
+
# require "tynn/protection"
|
19
20
|
#
|
20
|
-
#
|
21
|
-
# ```
|
21
|
+
# Tynn.helpers(Tynn::Protection, ssl: true)
|
22
22
|
#
|
23
23
|
# By default, it includes the following security helpers:
|
24
24
|
#
|
25
|
-
#
|
25
|
+
# - Tynn::SecureHeaders
|
26
26
|
#
|
27
|
-
# If the
|
27
|
+
# If the +:ssl+ option is +true+, includes:
|
28
28
|
#
|
29
|
-
#
|
29
|
+
# - Tynn::HSTS
|
30
|
+
#
|
31
|
+
# - Tynn::ForceSSL
|
30
32
|
#
|
31
33
|
module Protection
|
32
|
-
|
34
|
+
# Internal: Configures security related extensions.
|
35
|
+
def self.setup(app, ssl: false, force_ssl: ssl, hsts: {})
|
33
36
|
app.helpers(Tynn::SecureHeaders)
|
34
37
|
|
35
38
|
if ssl
|
36
|
-
|
39
|
+
app.settings[:ssl] = true
|
40
|
+
|
41
|
+
require_relative "hsts"
|
42
|
+
|
43
|
+
app.helpers(Tynn::HSTS, hsts)
|
44
|
+
end
|
45
|
+
|
46
|
+
if force_ssl
|
47
|
+
require_relative "force_ssl"
|
37
48
|
|
38
|
-
app.helpers(Tynn::
|
49
|
+
app.helpers(Tynn::ForceSSL)
|
39
50
|
end
|
40
51
|
end
|
41
52
|
end
|
data/lib/tynn/render.rb
CHANGED
@@ -8,41 +8,42 @@ class Tynn
|
|
8
8
|
views: options.fetch(:views, File.expand_path("views", Dir.pwd)),
|
9
9
|
engine: options.fetch(:engine, "erb"),
|
10
10
|
engine_opts: {
|
11
|
-
|
12
|
-
outvar: "@_output"
|
11
|
+
escape_html: true
|
13
12
|
}.merge!(options.fetch(:options, {}))
|
14
13
|
)
|
15
14
|
end
|
16
15
|
|
17
|
-
|
18
|
-
|
16
|
+
module InstanceMethods
|
17
|
+
def render(template, locals = {}, layout = settings[:layout])
|
18
|
+
res.headers[Rack::CONTENT_TYPE] ||= Syro::Response::DEFAULT
|
19
19
|
|
20
|
-
|
21
|
-
|
20
|
+
res.write(view(template, locals, layout))
|
21
|
+
end
|
22
22
|
|
23
|
-
|
24
|
-
|
25
|
-
|
23
|
+
def view(template, locals = {}, layout = settings[:layout])
|
24
|
+
return partial(layout, locals.merge(content: partial(template, locals)))
|
25
|
+
end
|
26
26
|
|
27
|
-
|
28
|
-
|
29
|
-
|
27
|
+
def partial(template, locals = {})
|
28
|
+
return tilt(template_path(template), locals, settings[:engine_opts])
|
29
|
+
end
|
30
30
|
|
31
|
-
|
31
|
+
private
|
32
32
|
|
33
|
-
|
34
|
-
|
35
|
-
|
33
|
+
def tilt(file, locals = {}, opts = {})
|
34
|
+
return tilt_cache.fetch(file) { Tilt.new(file, 1, opts) }.render(self, locals)
|
35
|
+
end
|
36
36
|
|
37
|
-
|
38
|
-
|
39
|
-
|
37
|
+
def tilt_cache
|
38
|
+
return Thread.current[:tilt_cache] ||= Tilt::Cache.new
|
39
|
+
end
|
40
40
|
|
41
|
-
|
42
|
-
|
43
|
-
|
41
|
+
def template_path(template)
|
42
|
+
dir = settings[:views]
|
43
|
+
ext = settings[:engine]
|
44
44
|
|
45
|
-
|
45
|
+
return File.join(dir, "#{ template }.#{ ext }")
|
46
|
+
end
|
46
47
|
end
|
47
48
|
end
|
48
49
|
end
|
data/lib/tynn/response.rb
CHANGED
@@ -1,185 +1,197 @@
|
|
1
1
|
class Tynn
|
2
|
-
# It provides convenience methods to construct a Rack response.
|
2
|
+
# Public: It provides convenience methods to construct a Rack response.
|
3
3
|
#
|
4
|
-
#
|
5
|
-
# res = Tynn::Response.new
|
6
|
-
# res.status = 200
|
7
|
-
# res["Content-Type"] = "text/html"
|
8
|
-
# res.write("foo")
|
9
|
-
# ```
|
4
|
+
# Examples
|
10
5
|
#
|
11
|
-
#
|
12
|
-
# [Rack's specification][rack-spec].
|
6
|
+
# res = Tynn::Response.new
|
13
7
|
#
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
17
|
-
# ```
|
8
|
+
# res.status = 200
|
9
|
+
# res["Content-Type"] = "text/html"
|
10
|
+
# res.write("foo")
|
18
11
|
#
|
19
|
-
#
|
20
|
-
# [
|
12
|
+
# res.finish
|
13
|
+
# # => [200, { "Content-Type" => "text/html", "Content-Length" => 3 }, ["foo"]]
|
21
14
|
#
|
22
15
|
class Response < Syro::Response
|
23
|
-
|
24
|
-
# :method: new
|
25
|
-
# :call-seq: new(headers = {})
|
16
|
+
# Public: Initializes a new response object.
|
26
17
|
#
|
27
|
-
#
|
18
|
+
# headers - A Hash of initial headers. Defaults to <tt>{}</tt>.
|
28
19
|
#
|
29
|
-
#
|
30
|
-
# Tynn::Response.new.headers
|
31
|
-
# # => {}
|
20
|
+
# Examples
|
32
21
|
#
|
33
|
-
#
|
34
|
-
#
|
35
|
-
#
|
22
|
+
# Tynn::Response.new.headers
|
23
|
+
# # => {}
|
24
|
+
#
|
25
|
+
# Tynn::Response.new("Content-Type" => "text/plain").headers
|
26
|
+
# # => { "Content-Type" => "text/plain" }
|
27
|
+
#
|
28
|
+
# Signature
|
29
|
+
#
|
30
|
+
# new(headers = {})
|
31
|
+
#
|
32
|
+
# Inherited by Syro::Response.
|
36
33
|
|
37
|
-
|
38
|
-
#
|
34
|
+
# Public: Returns the response header corresponding to +key+.
|
35
|
+
#
|
36
|
+
# key - A String HTTP header field name.
|
39
37
|
#
|
40
|
-
#
|
38
|
+
# Examples
|
41
39
|
#
|
42
40
|
# res["Content-Type"] # => "text/html"
|
43
41
|
# res["Content-Length"] # => "42"
|
42
|
+
#
|
43
|
+
# Signature
|
44
|
+
#
|
45
|
+
# [](key)
|
46
|
+
#
|
47
|
+
# Inherited by Syro::Response.
|
44
48
|
|
45
|
-
|
46
|
-
#
|
47
|
-
#
|
49
|
+
# Public: Sets the given +value+ with the header corresponding to +key+.
|
50
|
+
#
|
51
|
+
# key - A String HTTP header field name.
|
52
|
+
# value - A String HTTP header field value.
|
48
53
|
#
|
49
|
-
#
|
54
|
+
# Examples
|
50
55
|
#
|
51
56
|
# res["Content-Type"] = "application/json"
|
52
57
|
# res["Content-Type"] # => "application/json"
|
58
|
+
#
|
59
|
+
# Signature
|
60
|
+
#
|
61
|
+
# []=(key, value)
|
62
|
+
#
|
63
|
+
# Inherited by Syro::Response.
|
53
64
|
|
54
|
-
|
55
|
-
# :method: body
|
65
|
+
# Public: Returns the body of the response.
|
56
66
|
#
|
57
|
-
#
|
67
|
+
# Examples
|
58
68
|
#
|
59
|
-
#
|
60
|
-
#
|
69
|
+
# res.body
|
70
|
+
# # => []
|
61
71
|
#
|
62
|
-
#
|
63
|
-
#
|
72
|
+
# res.write("there is")
|
73
|
+
# res.write("no try")
|
64
74
|
#
|
65
|
-
#
|
66
|
-
#
|
67
|
-
|
68
|
-
|
69
|
-
# :method: finish
|
70
|
-
#
|
71
|
-
# Returns an array with three elements: the status, headers and body.
|
72
|
-
# If the status is not set, the status is set to 404 if empty body,
|
73
|
-
# otherwise the status is set to 200 and updates the `Content-Type`
|
74
|
-
# header to `text/html`.
|
75
|
-
#
|
76
|
-
# res.status = 200
|
77
|
-
# res.finish
|
78
|
-
# # => [200, {}, []]
|
79
|
-
#
|
80
|
-
# res.status = nil
|
81
|
-
# res.finish
|
82
|
-
# # => [404, {}, []]
|
83
|
-
#
|
84
|
-
# res.status = nil
|
85
|
-
# res.write("yo")
|
86
|
-
# res.finish
|
87
|
-
# # => [200, { "Content-Type" => "text/html", "Content-Length" => 2 }, ["yo"]]
|
88
|
-
|
89
|
-
##
|
90
|
-
# :method: headers
|
75
|
+
# res.body
|
76
|
+
# # => ["there is", "no try"]
|
77
|
+
#
|
78
|
+
# Signature
|
91
79
|
#
|
92
|
-
#
|
80
|
+
# body()
|
93
81
|
#
|
94
|
-
#
|
95
|
-
# # => { "Content-Type" => "text/html", "Content-Length" => "42" }
|
82
|
+
# Inherited by Syro::Response.
|
96
83
|
|
97
|
-
|
98
|
-
#
|
99
|
-
#
|
84
|
+
# Public: Returns an Array with three elements: the status, headers
|
85
|
+
# and body. If the status is not set, the status is set to +404+ if
|
86
|
+
# empty body, otherwise the status is set to +200+ and updates the
|
87
|
+
# +Content-Type+ header to +text/html+.
|
88
|
+
#
|
89
|
+
# Examples
|
90
|
+
#
|
91
|
+
# res.status = 200
|
92
|
+
# res.finish
|
93
|
+
# # => [200, {}, []]
|
100
94
|
#
|
101
|
-
#
|
102
|
-
#
|
95
|
+
# res.status = nil
|
96
|
+
# res.finish
|
97
|
+
# # => [404, {}, []]
|
103
98
|
#
|
104
|
-
#
|
99
|
+
# res.status = nil
|
100
|
+
# res.write("yo")
|
101
|
+
# res.finish
|
102
|
+
# # => [200, { "Content-Type" => "text/html", "Content-Length" => 2 }, ["yo"]]
|
105
103
|
#
|
106
|
-
#
|
107
|
-
# res.status # => 302
|
104
|
+
# Signature
|
108
105
|
#
|
109
|
-
#
|
106
|
+
# finish()
|
110
107
|
#
|
111
|
-
#
|
112
|
-
# res.status # => 303
|
108
|
+
# Inherited by Syro::Response.
|
113
109
|
|
114
|
-
|
115
|
-
# :method: status
|
110
|
+
# Public: Returns a Hash with the response headers.
|
116
111
|
#
|
117
|
-
#
|
112
|
+
# Examples
|
118
113
|
#
|
119
|
-
#
|
114
|
+
# res.headers
|
115
|
+
# # => { "Content-Type" => "text/html", "Content-Length" => "42" }
|
116
|
+
#
|
117
|
+
# Signature
|
118
|
+
#
|
119
|
+
# headers()
|
120
120
|
#
|
121
|
+
# Inherited by Syro::Response.
|
121
122
|
|
122
|
-
|
123
|
-
#
|
124
|
-
#
|
123
|
+
# Public: Sets the +Location+ header to +url+ and updates the status
|
124
|
+
# to +status+.
|
125
|
+
#
|
126
|
+
# url - A String URL (relative or absolute) to redirect to.
|
127
|
+
# status - An Integer status code. Defaults to +302+.
|
128
|
+
#
|
129
|
+
# Examples
|
130
|
+
#
|
131
|
+
# res.redirect("/path")
|
132
|
+
#
|
133
|
+
# res["Location"] # => "/path"
|
134
|
+
# res.status # => 302
|
135
|
+
#
|
136
|
+
# res.redirect("http://tynn.ru", 303)
|
137
|
+
#
|
138
|
+
# res["Location"] # => "http://tynn.ru"
|
139
|
+
# res.status # => 303
|
125
140
|
#
|
126
|
-
#
|
141
|
+
# Signature
|
127
142
|
#
|
128
|
-
#
|
143
|
+
# redirect(url, status = 302)
|
129
144
|
#
|
145
|
+
# Inherited by Syro::Response.
|
130
146
|
|
131
|
-
|
132
|
-
# :method: write
|
133
|
-
# :call-seq: write(str)
|
147
|
+
# Public: Returns the status of the response.
|
134
148
|
#
|
135
|
-
#
|
149
|
+
# Examples
|
136
150
|
#
|
137
|
-
# res.
|
151
|
+
# res.status # => 200
|
138
152
|
#
|
139
|
-
#
|
140
|
-
# res.write("bar")
|
153
|
+
# Signature
|
141
154
|
#
|
142
|
-
#
|
143
|
-
# # => ["foo", "bar"]
|
155
|
+
# status()
|
144
156
|
#
|
145
|
-
#
|
146
|
-
# # => 6
|
157
|
+
# Inherited by Syro::Response.
|
147
158
|
|
148
|
-
|
149
|
-
# :method: set_cookie
|
150
|
-
# :call-seq: set_cookie(key, value)
|
159
|
+
# Public: Sets the status of the response.
|
151
160
|
#
|
152
|
-
#
|
161
|
+
# status - An Integer HTTP status code.
|
153
162
|
#
|
154
|
-
#
|
155
|
-
# res["Set-Cookie"] # => "foo=bar"
|
163
|
+
# Examples
|
156
164
|
#
|
157
|
-
#
|
158
|
-
# res["Set-Cookie"] # => "foo=bar\nfoo2=bar2"
|
165
|
+
# res.status = 200
|
159
166
|
#
|
160
|
-
#
|
161
|
-
# domain: ".example.com",
|
162
|
-
# path: "/",
|
163
|
-
# # max_age: 0,
|
164
|
-
# # expires: Time.now + 10_000,
|
165
|
-
# secure: true,
|
166
|
-
# httponly: true,
|
167
|
-
# value: "bar"
|
168
|
-
# })
|
167
|
+
# Signature
|
169
168
|
#
|
170
|
-
#
|
171
|
-
# # => "bar=bar; domain=.example.com; path=/; secure; HttpOnly
|
169
|
+
# status=(status)
|
172
170
|
#
|
173
|
-
#
|
171
|
+
# Inherited by Syro::Response.
|
174
172
|
|
175
|
-
|
176
|
-
#
|
177
|
-
#
|
173
|
+
# Public: Appends +str+ to the response body and updates the
|
174
|
+
# +Content-Length+ header.
|
175
|
+
#
|
176
|
+
# str - Any object that responds to +to_s+.
|
177
|
+
#
|
178
|
+
# Examples
|
179
|
+
#
|
180
|
+
# res.body # => []
|
181
|
+
#
|
182
|
+
# res.write("foo")
|
183
|
+
# res.write("bar")
|
184
|
+
#
|
185
|
+
# res.body
|
186
|
+
# # => ["foo", "bar"]
|
187
|
+
#
|
188
|
+
# res["Content-Length"]
|
189
|
+
# # => 6
|
190
|
+
#
|
191
|
+
# Signature
|
178
192
|
#
|
179
|
-
#
|
193
|
+
# write(str)
|
180
194
|
#
|
181
|
-
#
|
182
|
-
# res["Set-Cookie"]
|
183
|
-
# # => "foo=; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 -0000"
|
195
|
+
# Inherited by Syro::Response.
|
184
196
|
end
|
185
197
|
end
|