tynn 1.0.0.rc2 → 1.0.0.rc3
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 +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
|