tynn 0.0.4 → 1.0.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/tynn/all_methods.rb +17 -0
- data/lib/tynn/environment.rb +12 -8
- data/lib/tynn/matchers.rb +37 -19
- data/lib/tynn/request.rb +4 -0
- data/lib/tynn/response.rb +151 -0
- data/lib/tynn/secure_headers.rb +28 -4
- data/lib/tynn/session.rb +74 -3
- data/lib/tynn/ssl.rb +2 -2
- data/lib/tynn/static.rb +26 -0
- data/lib/tynn/test.rb +37 -2
- data/lib/tynn/version.rb +7 -2
- data/lib/tynn.rb +88 -8
- data/test/all_methods_test.rb +16 -0
- data/test/core_test.rb +0 -86
- data/test/matchers_test.rb +11 -8
- data/test/middleware_test.rb +79 -0
- data/test/protection_test.rb +37 -0
- data/test/session_test.rb +1 -6
- metadata +45 -52
- data/.gems +0 -10
- data/docs/bin/build +0 -38
- data/docs/guides/security.md +0 -1
- data/docs/index.md +0 -89
- data/docs/layout.html +0 -61
- data/docs/public/.gitignore +0 -1
- data/docs/public/css/styles.css +0 -111
- data/docs/public/guides/.gitignore +0 -2
- data/docs/syro/syro.rb +0 -112
- data/examples/composition.ru +0 -40
- data/examples/hello.ru +0 -9
- data/examples/protection.ru +0 -18
- data/examples/render.ru +0 -13
- data/examples/views/home.erb +0 -1
- data/examples/views/layout.erb +0 -5
- data/lib/tynn/csrf.rb +0 -48
- data/lib/tynn/options.rb +0 -9
- data/makefile +0 -15
- data/test/csrf_test.rb +0 -98
- data/test/options_test.rb +0 -16
- data/test/views/custom_layout.erb +0 -1
- data/test/views/custom_layout.mote +0 -1
- data/test/views/layout.erb +0 -1
- data/test/views/layout.mote +0 -1
- data/test/views/partial.erb +0 -1
- data/test/views/partial.mote +0 -1
- data/test/views/view.erb +0 -1
- data/test/views/view.mote +0 -1
- data/tynn.gemspec +0 -24
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4778b186382c25ab99b1687323ec3562c045a0ee
|
4
|
+
data.tar.gz: 3b8c8d0c9a80cf5cce5cfb235e27be3b34093197
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ef2895b73ebb6b9180e747a28499de003fb430a6e6eaee6027f7d7ae5c2569c9161347aed31aacb25552ce010dff48a01ebdbdf39af026f61702316c38ac3036
|
7
|
+
data.tar.gz: 47e3ccf946f417ba6c444d4197d511eef8df1c987e83468214a5949d3ffa6c46a89e891da13d032e077bdd6b1781ec64313a60a3a399e5eb259ced0513fe6869
|
data/lib/tynn/environment.rb
CHANGED
@@ -1,19 +1,23 @@
|
|
1
1
|
# Adds helper methods to get and check the current environment.
|
2
2
|
#
|
3
|
-
#
|
4
|
-
#
|
3
|
+
# ```
|
4
|
+
# require "tynn"
|
5
|
+
# require "tynn/environment"
|
5
6
|
#
|
6
|
-
#
|
7
|
+
# Tynn.helpers(Tynn::Environment)
|
7
8
|
#
|
8
|
-
#
|
9
|
+
# Tynn.environment # => :development
|
9
10
|
#
|
10
|
-
#
|
11
|
-
#
|
12
|
-
#
|
11
|
+
# Tynn.development? # => true
|
12
|
+
# Tynn.production? # => false
|
13
|
+
# Tynn.test? # => false
|
14
|
+
# ```
|
13
15
|
#
|
14
16
|
# By default, the environment is based on `ENV["RACK_ENV"]`.
|
15
17
|
#
|
16
|
-
#
|
18
|
+
# ```
|
19
|
+
# Tynn.helpers(Tynn::Environment, env: ENV["RACK_ENV"])
|
20
|
+
# ```
|
17
21
|
#
|
18
22
|
module Tynn::Environment
|
19
23
|
def self.setup(app, env: ENV["RACK_ENV"]) # :nodoc:
|
data/lib/tynn/matchers.rb
CHANGED
@@ -1,22 +1,26 @@
|
|
1
1
|
# Adds extra matchers to Tynn.
|
2
2
|
#
|
3
|
-
#
|
4
|
-
#
|
3
|
+
# ```
|
4
|
+
# require "tynn"
|
5
|
+
# require "tynn/matchers"
|
5
6
|
#
|
6
|
-
#
|
7
|
+
# Tynn.helpers(Tynn::Matchers)
|
8
|
+
# ```
|
7
9
|
#
|
8
10
|
module Tynn::Matchers
|
9
11
|
# A catch-all matcher.
|
10
12
|
#
|
11
|
-
#
|
12
|
-
#
|
13
|
-
#
|
14
|
-
#
|
13
|
+
# ```
|
14
|
+
# Tynn.define do
|
15
|
+
# authenticated? do
|
16
|
+
# # ...
|
17
|
+
# end
|
15
18
|
#
|
16
|
-
#
|
17
|
-
#
|
18
|
-
#
|
19
|
-
#
|
19
|
+
# default do # on true
|
20
|
+
# # ...
|
21
|
+
# end
|
22
|
+
# end
|
23
|
+
# ```
|
20
24
|
#
|
21
25
|
# :call-seq: default(&block)
|
22
26
|
#
|
@@ -26,15 +30,29 @@ module Tynn::Matchers
|
|
26
30
|
halt(res.finish)
|
27
31
|
end
|
28
32
|
|
29
|
-
# Match if the given `
|
33
|
+
# Match if the given `key` is present in `req.params`.
|
30
34
|
#
|
31
|
-
#
|
32
|
-
#
|
33
|
-
#
|
34
|
-
#
|
35
|
-
# end
|
35
|
+
# ```
|
36
|
+
# Tynn.define do
|
37
|
+
# param(:user) do |params|
|
38
|
+
# user = User.create(params)
|
36
39
|
#
|
37
|
-
|
38
|
-
|
40
|
+
# # ...
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
# default do
|
44
|
+
# res.write("missing param")
|
45
|
+
# end
|
46
|
+
# end
|
47
|
+
# ```
|
48
|
+
#
|
49
|
+
# :call-seq: param(key, &block)
|
50
|
+
#
|
51
|
+
def param(key)
|
52
|
+
if (v = req[key]) && !v.empty?
|
53
|
+
yield(v)
|
54
|
+
|
55
|
+
halt(res.finish)
|
56
|
+
end
|
39
57
|
end
|
40
58
|
end
|
data/lib/tynn/request.rb
ADDED
@@ -0,0 +1,151 @@
|
|
1
|
+
class Tynn
|
2
|
+
class Response < Syro::Response
|
3
|
+
##
|
4
|
+
# :method: []
|
5
|
+
#
|
6
|
+
# Returns the response header corresponding to `key`.
|
7
|
+
#
|
8
|
+
# res["Content-Type"] # => "text/html"
|
9
|
+
# res["Content-Length"] # => "42"
|
10
|
+
|
11
|
+
##
|
12
|
+
# :method: []=
|
13
|
+
# :call-seq: []=(value)
|
14
|
+
#
|
15
|
+
# Sets the given `value` with the header corresponding to `key`.
|
16
|
+
#
|
17
|
+
# res["Content-Type"] = "application/json"
|
18
|
+
# res["Content-Type"] # => "application/json"
|
19
|
+
|
20
|
+
##
|
21
|
+
# :method: body
|
22
|
+
#
|
23
|
+
# Returns the body of the response.
|
24
|
+
#
|
25
|
+
# res.body
|
26
|
+
# # => []
|
27
|
+
#
|
28
|
+
# res.write("there is")
|
29
|
+
# res.write("no try")
|
30
|
+
#
|
31
|
+
# res.body
|
32
|
+
# # => ["there is", "no try"]
|
33
|
+
|
34
|
+
##
|
35
|
+
# :method: finish
|
36
|
+
#
|
37
|
+
# Returns an array with three elements: the status, headers and body.
|
38
|
+
# If the status is not set, the status is set to 404 if empty body,
|
39
|
+
# otherwise the status is set to 200 and updates the `Content-Type`
|
40
|
+
# header to `text/html`.
|
41
|
+
#
|
42
|
+
# res.status = 200
|
43
|
+
# res.finish
|
44
|
+
# # => [200, {}, []]
|
45
|
+
#
|
46
|
+
# res.status = nil
|
47
|
+
# res.finish
|
48
|
+
# # => [404, {}, []]
|
49
|
+
#
|
50
|
+
# res.status = nil
|
51
|
+
# res.write("yo!")
|
52
|
+
# res.finish
|
53
|
+
# # => [200, { "Content-Type" => "text/html" }, ["yo!"]]
|
54
|
+
|
55
|
+
##
|
56
|
+
# :method: headers
|
57
|
+
#
|
58
|
+
# Returns a hash with the response headers.
|
59
|
+
#
|
60
|
+
# res.headers
|
61
|
+
# # => { "Content-Type" => "text/html", "Content-Length" => "42" }
|
62
|
+
|
63
|
+
##
|
64
|
+
# :method: redirect
|
65
|
+
# :call-seq: redirect(path, 302)
|
66
|
+
#
|
67
|
+
# Sets the `Location` header to `path` and updates the status to
|
68
|
+
# `status`. By default, `status` is `302`.
|
69
|
+
#
|
70
|
+
# res.redirect("/path")
|
71
|
+
#
|
72
|
+
# res["Location"] # => "/path"
|
73
|
+
# res.status # => 302
|
74
|
+
#
|
75
|
+
# res.redirect("http://tynn.ru", 303)
|
76
|
+
#
|
77
|
+
# res["Location"] # => "http://tynn.ru"
|
78
|
+
# res.status # => 303
|
79
|
+
|
80
|
+
##
|
81
|
+
# :method: status
|
82
|
+
#
|
83
|
+
# Returns the status of the response.
|
84
|
+
#
|
85
|
+
# res.status # => 200
|
86
|
+
#
|
87
|
+
|
88
|
+
##
|
89
|
+
# :method: status=
|
90
|
+
# :call-seq: status=(status)
|
91
|
+
#
|
92
|
+
# Sets the status of the response.
|
93
|
+
#
|
94
|
+
# res.status = 200
|
95
|
+
#
|
96
|
+
|
97
|
+
##
|
98
|
+
# :method: write
|
99
|
+
# :call-seq: write(str)
|
100
|
+
#
|
101
|
+
# Appends `str` to `body` and updates the `Content-Length` header.
|
102
|
+
#
|
103
|
+
# res.body # => []
|
104
|
+
#
|
105
|
+
# res.write("foo")
|
106
|
+
# res.write("bar")
|
107
|
+
#
|
108
|
+
# res.body
|
109
|
+
# # => ["foo", "bar"]
|
110
|
+
#
|
111
|
+
# res["Content-Length"]
|
112
|
+
# # => 6
|
113
|
+
|
114
|
+
##
|
115
|
+
# :method: set_cookie
|
116
|
+
# :call-seq: set_cookie(key, value)
|
117
|
+
#
|
118
|
+
# Sets a cookie into the response.
|
119
|
+
#
|
120
|
+
# res.set_cookie("foo", "bar")
|
121
|
+
# res["Set-Cookie"] # => "foo=bar"
|
122
|
+
#
|
123
|
+
# res.set_cookie("foo2", "bar2")
|
124
|
+
# res["Set-Cookie"] # => "foo=bar\nfoo2=bar2"
|
125
|
+
#
|
126
|
+
# res.set_cookie("bar", {
|
127
|
+
# domain: ".example.com",
|
128
|
+
# path: "/",
|
129
|
+
# # max_age: 0,
|
130
|
+
# # expires: Time.now + 10_000,
|
131
|
+
# secure: true,
|
132
|
+
# httponly: true,
|
133
|
+
# value: "bar"
|
134
|
+
# })
|
135
|
+
#
|
136
|
+
# res["Set-Cookie"].split("\n").last
|
137
|
+
# # => "bar=bar; domain=.example.com; path=/; secure; HttpOnly
|
138
|
+
#
|
139
|
+
# **NOTE:** This method doesn't sign and/or encrypt the value of the cookie.
|
140
|
+
|
141
|
+
##
|
142
|
+
# :method: delete_cookie
|
143
|
+
# :call-seq: delete_cookie(key, value = {})
|
144
|
+
#
|
145
|
+
# Deletes cookie.
|
146
|
+
#
|
147
|
+
# res.set_cookie("foo", "bar")
|
148
|
+
# res["Set-Cookie"]
|
149
|
+
# # => "foo=; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 -0000"
|
150
|
+
end
|
151
|
+
end
|
data/lib/tynn/secure_headers.rb
CHANGED
@@ -1,9 +1,33 @@
|
|
1
1
|
# Adds security related HTTP headers.
|
2
2
|
#
|
3
|
-
#
|
4
|
-
#
|
3
|
+
# ```
|
4
|
+
# require "tynn"
|
5
|
+
# require "tynn/secure_headers"
|
5
6
|
#
|
6
|
-
#
|
7
|
+
# Tynn.helpers(Tynn::SecureHeaders)
|
8
|
+
# ```
|
9
|
+
#
|
10
|
+
# This helper applies the following headers:
|
11
|
+
#
|
12
|
+
# * **X-Content-Type-Options:** Prevents IE and Chrome from
|
13
|
+
# [content type sniffing][mime-sniffing].
|
14
|
+
#
|
15
|
+
# * **X-Frame-Options (XFO):** Provides [Clickjacking][clickjacking]
|
16
|
+
# protection. Check the [X-Frame-Options draft][x-frame-options] for
|
17
|
+
# more information.
|
18
|
+
#
|
19
|
+
# * **X-Permitted-Cross-Domain-Policies:** Restricts Adobe Flash Player's
|
20
|
+
# access to data. Check this [article][pcdp] for more information.
|
21
|
+
#
|
22
|
+
# * **X-XSS-Protection:** Enables the [XSS][xss] protection filter built
|
23
|
+
# into IE, Chrome and Safari. This filter is usually enabled by default,
|
24
|
+
# the use of this header is to re-enable it if it was disabled by the user.
|
25
|
+
#
|
26
|
+
# [clickjacking]: https://www.owasp.org/index.php/Clickjacking
|
27
|
+
# [mime-sniffing]: https://msdn.microsoft.com/library/gg622941(v=vs.85).aspx
|
28
|
+
# [pcdp]: https://www.adobe.com/devnet/adobe-media-server/articles/cross-domain-xml-for-streaming.html
|
29
|
+
# [x-frame-options]: https://tools.ietf.org/html/draft-ietf-websec-x-frame-options-02
|
30
|
+
# [xss]: https://www.owasp.org/index.php/Cross-site_Scripting_(XSS)
|
7
31
|
#
|
8
32
|
module Tynn::SecureHeaders
|
9
33
|
HEADERS = {
|
@@ -13,7 +37,7 @@ module Tynn::SecureHeaders
|
|
13
37
|
"X-XSS-Protection" => "1; mode=block"
|
14
38
|
} # :nodoc:
|
15
39
|
|
16
|
-
def default_headers
|
40
|
+
def default_headers # :nodoc:
|
17
41
|
return super.merge(HEADERS)
|
18
42
|
end
|
19
43
|
end
|
data/lib/tynn/session.rb
CHANGED
@@ -1,13 +1,84 @@
|
|
1
|
+
# Adds simple cookie based session management. If a secret token is
|
2
|
+
# given, it signs the cookie data to ensure that it cannot be altered
|
3
|
+
# by unauthorized means.
|
4
|
+
#
|
5
|
+
# ```
|
6
|
+
# require "tynn"
|
7
|
+
# require "tynn/session"
|
8
|
+
#
|
9
|
+
# Tynn.helpers(Tynn::Session, secret: "__change_me__")
|
10
|
+
#
|
11
|
+
# Tynn.define do
|
12
|
+
# root do
|
13
|
+
# res.write(sprintf("hei %s", session[:username]))
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# on(:username) do |username|
|
17
|
+
# session[:username] = username
|
18
|
+
# end
|
19
|
+
# end
|
20
|
+
# ```
|
21
|
+
#
|
22
|
+
# The following command generates a cryptographically secure secret ready
|
23
|
+
# to use:
|
24
|
+
#
|
25
|
+
# ```
|
26
|
+
# $ ruby -r securerandom -e "puts SecureRandom.hex(64)"
|
27
|
+
# ```
|
28
|
+
#
|
29
|
+
# It's important to keep the token secret. Knowing the token allows an
|
30
|
+
# attacker to tamper the data. So, it's recommended to load the token
|
31
|
+
# from the environment.
|
32
|
+
#
|
33
|
+
# ```
|
34
|
+
# Tynn.helpers(Tynn::Session, secret: ENV["SESSION_SECRET"])
|
35
|
+
# ```
|
36
|
+
#
|
37
|
+
# Under the hood, Tynn::Session uses the [Rack::Session::Cookie][rack-session]
|
38
|
+
# middleware. Thus, supports all the options available for this middleware.
|
39
|
+
#
|
40
|
+
# * `:key` - the name of the cookie. Defaults to `"rack.session"`.
|
41
|
+
# * `:expire_after` - sets the lifespan of the cookie. If `nil`,
|
42
|
+
# the cookie will be deleted after the user close the browser.
|
43
|
+
# Defaults to `nil`.
|
44
|
+
# * `:httponly` - if `true`, sets the [HttpOnly][cookie-httponly] attribute.
|
45
|
+
# This mitigates the risk of client side scripting accessing the cookie.
|
46
|
+
# Defaults to `true`.
|
47
|
+
# * `:secure` - if `true`, sets the [Secure][cookie-secure] attribute.
|
48
|
+
# This tells the browser to only transmit the cookie over HTTPS. Defaults
|
49
|
+
# to `false`.
|
50
|
+
#
|
51
|
+
# ```
|
52
|
+
# Tynn.helpers(
|
53
|
+
# Tynn::Session,
|
54
|
+
# key: "app",
|
55
|
+
# secret: ENV["SESSION_SECRET"],
|
56
|
+
# expire_after: 36_000, # seconds
|
57
|
+
# httponly: true,
|
58
|
+
# secure: true
|
59
|
+
# )
|
60
|
+
# ```
|
61
|
+
#
|
62
|
+
# [cookie-httponly]: https://www.owasp.org/index.php/Session_Management_Cheat_Sheet#HttpOnly_Attribute
|
63
|
+
# [cookie-secure]: https://www.owasp.org/index.php/Session_Management_Cheat_Sheet#Secure_Attribute
|
64
|
+
# [rack-session]: http://www.rubydoc.info/gems/rack/Rack/Session/Cookie
|
65
|
+
#
|
1
66
|
module Tynn::Session
|
2
67
|
RACK_SESSION = "rack.session".freeze # :nodoc:
|
3
68
|
|
4
69
|
def self.setup(app, options = {}) # :nodoc:
|
5
|
-
options = options.dup
|
6
|
-
options[:http_only] ||= true
|
7
|
-
|
8
70
|
app.use(Rack::Session::Cookie, options)
|
9
71
|
end
|
10
72
|
|
73
|
+
# Returns the session hash.
|
74
|
+
#
|
75
|
+
# ```
|
76
|
+
# session # => {}
|
77
|
+
#
|
78
|
+
# session[:foo] = "foo"
|
79
|
+
# session[:foo] # => "foo"
|
80
|
+
# ```
|
81
|
+
#
|
11
82
|
def session
|
12
83
|
return env[RACK_SESSION]
|
13
84
|
end
|
data/lib/tynn/ssl.rb
CHANGED
data/lib/tynn/static.rb
CHANGED
@@ -1,3 +1,29 @@
|
|
1
|
+
# Adds support for static files (javascript files, images, stylesheets, etc).
|
2
|
+
#
|
3
|
+
# ```
|
4
|
+
# require "tynn"
|
5
|
+
# require "tynn/static"
|
6
|
+
#
|
7
|
+
# Tynn.helpers(Tynn::Static, ["/js", "/css"])
|
8
|
+
# ```
|
9
|
+
#
|
10
|
+
# By default, serve all requests beginning with the given paths from the folder
|
11
|
+
# `public` in the current directory (e.g. `public/js/*`, `public/css/*`). You
|
12
|
+
# can change the default by passing the `:root` option.
|
13
|
+
#
|
14
|
+
# ```
|
15
|
+
# Tynn.helpers(Tynn::Static, ["/js", "/css"], root: "assets")
|
16
|
+
# ```
|
17
|
+
#
|
18
|
+
# Under the hood, it uses the [Rack::Static][rack-static] middleware.
|
19
|
+
# Thus, supports all the options available for this middleware.
|
20
|
+
#
|
21
|
+
# ```
|
22
|
+
# Tynn.helpers(Tynn::Static, ["/js", "/css"], index: "index.html")
|
23
|
+
# ```
|
24
|
+
#
|
25
|
+
# [rack-static]: http://www.rubydoc.info/gems/rack/Rack/Static
|
26
|
+
#
|
1
27
|
module Tynn::Static
|
2
28
|
def self.setup(app, urls, options = {}) # :nodoc:
|
3
29
|
options = options.dup
|
data/lib/tynn/test.rb
CHANGED
@@ -1,10 +1,45 @@
|
|
1
1
|
require "rack/test"
|
2
2
|
|
3
|
+
# A simple helper class that uses [rack-test][rack-test] to simulate requests
|
4
|
+
# to your application.
|
5
|
+
#
|
6
|
+
# ```
|
7
|
+
# require "tynn"
|
8
|
+
# require "tynn/test"
|
9
|
+
#
|
10
|
+
# Tynn.define do
|
11
|
+
# root do
|
12
|
+
# res.write("hei")
|
13
|
+
# end
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# app = Tynn::Test.new
|
17
|
+
# app.get("/")
|
18
|
+
#
|
19
|
+
# 200 == app.res.status # => true
|
20
|
+
# "hei" == app.res.body # => true
|
21
|
+
# ```
|
22
|
+
#
|
23
|
+
# **NOTE:** Tynn doesn't ship with [rack-test][rack-test]. In order to
|
24
|
+
# use this plugin, you need to install it first.
|
25
|
+
#
|
26
|
+
# [rack-test]: http://rubygems.org/gems/rack-test
|
27
|
+
#
|
3
28
|
class Tynn::Test
|
4
29
|
include Rack::Test::Methods
|
5
30
|
|
6
|
-
|
7
|
-
|
31
|
+
# Instantiates a new Tynn::Test object with the given `application` to test.
|
32
|
+
#
|
33
|
+
# ```
|
34
|
+
# class API < Tynn
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
# app = Tynn::Test.new(API)
|
38
|
+
# app.get("/json")
|
39
|
+
# ```
|
40
|
+
#
|
41
|
+
def initialize(application = Tynn)
|
42
|
+
@app = application
|
8
43
|
end
|
9
44
|
|
10
45
|
def app # :nodoc:
|
data/lib/tynn/version.rb
CHANGED
data/lib/tynn.rb
CHANGED
@@ -1,15 +1,48 @@
|
|
1
1
|
require "seteable"
|
2
2
|
require "syro"
|
3
|
+
require_relative "tynn/request"
|
4
|
+
require_relative "tynn/response"
|
5
|
+
require_relative "tynn/version"
|
3
6
|
|
4
|
-
class Tynn
|
7
|
+
class Tynn
|
5
8
|
include Seteable
|
9
|
+
include Syro::Deck::API
|
6
10
|
|
11
|
+
# Sets the application handler.
|
12
|
+
#
|
13
|
+
# ```
|
14
|
+
# class Users < Tynn
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# Users.define do
|
18
|
+
# on(:id) do |id|
|
19
|
+
# get do
|
20
|
+
# res.write("GET /users")
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# post do
|
24
|
+
# res.write("POST /users")
|
25
|
+
# end
|
26
|
+
# end
|
27
|
+
# end
|
28
|
+
# ```
|
29
|
+
#
|
7
30
|
def self.define(&block)
|
8
31
|
@syro = Syro.new(self, &block)
|
9
32
|
end
|
10
33
|
|
11
|
-
|
12
|
-
|
34
|
+
# Adds given Rack `middleware` to the stack.
|
35
|
+
#
|
36
|
+
# ```
|
37
|
+
# require "rack/common_logger"
|
38
|
+
# require "rack/show_exceptions"
|
39
|
+
#
|
40
|
+
# Tynn.use(Rack::CommonLogger)
|
41
|
+
# Tynn.use(Rack::ShowExceptions)
|
42
|
+
# ```
|
43
|
+
#
|
44
|
+
def self.use(middleware, *args, &block)
|
45
|
+
__middleware << proc { |app| middleware.new(app, *args, &block) }
|
13
46
|
end
|
14
47
|
|
15
48
|
def self.call(env) # :nodoc:
|
@@ -19,14 +52,14 @@ class Tynn < Syro::Deck
|
|
19
52
|
def self.to_app # :nodoc:
|
20
53
|
fail("Missing application handler. Try #{ self }.define") unless @syro
|
21
54
|
|
22
|
-
if
|
55
|
+
if __middleware.empty?
|
23
56
|
return @syro
|
24
57
|
else
|
25
|
-
return
|
58
|
+
return __middleware.reverse.inject(@syro) { |a, m| m.call(a) }
|
26
59
|
end
|
27
60
|
end
|
28
61
|
|
29
|
-
def self.
|
62
|
+
def self.__middleware # :nodoc:
|
30
63
|
return @middleware ||= []
|
31
64
|
end
|
32
65
|
|
@@ -35,6 +68,47 @@ class Tynn < Syro::Deck
|
|
35
68
|
@middleware = []
|
36
69
|
end
|
37
70
|
|
71
|
+
# Extends Tynn functionality with the given `helper` module.
|
72
|
+
#
|
73
|
+
# ```
|
74
|
+
# module AppName
|
75
|
+
# def self.setup(app, name)
|
76
|
+
# app.settings[:app_name] = name
|
77
|
+
# end
|
78
|
+
#
|
79
|
+
# def app_name
|
80
|
+
# return self.class.app_name
|
81
|
+
# end
|
82
|
+
#
|
83
|
+
# module ClassMethods
|
84
|
+
# def app_name=(new_name)
|
85
|
+
# settings[:app_name] = new_name
|
86
|
+
# end
|
87
|
+
#
|
88
|
+
# def app_name
|
89
|
+
# return settings[:app_name]
|
90
|
+
# end
|
91
|
+
# end
|
92
|
+
# end
|
93
|
+
#
|
94
|
+
# Tynn.helpers(AppName, "MyApplication")
|
95
|
+
#
|
96
|
+
# Tynn.app_name # => "MyApplication"
|
97
|
+
#
|
98
|
+
# Tynn.app_name = "MyGreatestApp"
|
99
|
+
# Tynn.app_name # => "MyGreatestApp"
|
100
|
+
#
|
101
|
+
# Tynn.define do
|
102
|
+
# root do
|
103
|
+
# res.write(app_name)
|
104
|
+
# end
|
105
|
+
# end
|
106
|
+
# ```
|
107
|
+
#
|
108
|
+
# Check the [helpers][examples] that come with tynn for more examples.
|
109
|
+
#
|
110
|
+
# [examples]: https://github.com/harmoni/tynn/tree/master/lib/tynn
|
111
|
+
#
|
38
112
|
def self.helpers(helper, *args, &block)
|
39
113
|
self.include(helper)
|
40
114
|
|
@@ -46,6 +120,12 @@ class Tynn < Syro::Deck
|
|
46
120
|
helper.setup(self, *args, &block)
|
47
121
|
end
|
48
122
|
end
|
49
|
-
end
|
50
123
|
|
51
|
-
|
124
|
+
def request_class # :nodoc:
|
125
|
+
return Tynn::Request
|
126
|
+
end
|
127
|
+
|
128
|
+
def response_class # :nodoc:
|
129
|
+
return Tynn::Response
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require_relative "../lib/tynn/all_methods"
|
2
|
+
|
3
|
+
Tynn.helpers(Tynn::AllMethods)
|
4
|
+
|
5
|
+
test "methods" do
|
6
|
+
[:head, :options].each do |method|
|
7
|
+
Tynn.define do
|
8
|
+
send(method) { res.write "" }
|
9
|
+
end
|
10
|
+
|
11
|
+
app = Tynn::Test.new
|
12
|
+
app.send(method, "/")
|
13
|
+
|
14
|
+
assert_equal 200, app.res.status
|
15
|
+
end
|
16
|
+
end
|