tynn 2.0.0.alpha → 2.0.0.beta1
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 +168 -29
- data/lib/tynn.rb +0 -4
- data/lib/tynn/base.rb +368 -24
- data/lib/tynn/errors.rb +7 -0
- data/lib/tynn/json.rb +18 -5
- data/lib/tynn/render.rb +36 -25
- data/lib/tynn/request.rb +86 -15
- data/lib/tynn/response.rb +214 -12
- data/lib/tynn/secure_headers.rb +2 -6
- data/lib/tynn/session.rb +14 -5
- data/lib/tynn/ssl.rb +19 -16
- data/lib/tynn/test.rb +45 -38
- data/lib/tynn/utils.rb +15 -0
- data/lib/tynn/version.rb +1 -1
- data/test/default_headers_test.rb +1 -1
- data/test/environment_test.rb +1 -1
- data/test/helper.rb +9 -0
- data/test/json_test.rb +21 -6
- data/test/middleware_test.rb +23 -13
- data/test/plugin_test.rb +1 -1
- data/test/render_test.rb +24 -15
- data/test/request_headers_test.rb +8 -4
- data/test/request_test.rb +9 -0
- data/test/response_test.rb +217 -0
- data/test/routing_test.rb +128 -38
- data/test/secure_headers_test.rb +1 -1
- data/test/session_test.rb +6 -6
- data/test/settings_test.rb +3 -3
- data/test/ssl_test.rb +3 -3
- data/test/static_test.rb +1 -1
- metadata +14 -24
- data/lib/tynn/default_headers.rb +0 -50
- data/lib/tynn/settings.rb +0 -107
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2017e12fdca96b52fb5c2f954307b33e208c2ecb
|
4
|
+
data.tar.gz: 02cc565109da5f90593c370f818f213969c63c86
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: afcf9ebeb3457d3a7a9bd06878aa98cd9c88f8bf47dc1ecfeea008e4ad7571f3a9d1b0abff93b8ea68073edce4d2e0d59de72ce479c89e2fff458b4d5d954cfd
|
7
|
+
data.tar.gz: 4da2694ca6ec7484471c7c3ae51ca4406be40e0663ba6dce147adc2cc38d5d0abef0b88a214bc81cc8efe7334b2adcde456f6fe5e93ff66bbdc4921c094b8338
|
data/README.md
CHANGED
@@ -3,7 +3,6 @@
|
|
3
3
|
[](https://travis-ci.org/frodsan/tynn)
|
4
4
|
[](https://gemnasium.com/github.com/frodsan/tynn)
|
5
5
|
[](https://codeclimate.com/github/frodsan/tynn)
|
6
|
-
[](https://gitter.im/frodsan/tynn?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
7
6
|
|
8
7
|
A thin library for web development in Ruby.
|
9
8
|
|
@@ -14,9 +13,20 @@ A thin library for web development in Ruby.
|
|
14
13
|
* [Under the Hood](#under-the-hood)
|
15
14
|
* [Tynn on Rack](#tynn-on-rack)
|
16
15
|
* [Routing Basics](#routing-basics)
|
16
|
+
* [HTTP Verbs](#http-verbs)
|
17
|
+
* [Matching path segments](#matching-path-segments)
|
18
|
+
* [Capturing path segments](#capturing-path-segments)
|
19
|
+
* [Matching more than paths](#matching-more-than-paths)
|
20
|
+
* [Routing rules](#routing-rules)
|
21
|
+
* [Composing Applications](#composing-applications)
|
17
22
|
* [Managing Request and Response](#managing-request-and-response)
|
23
|
+
* [Accessing Request Parameters](#accessing-request-parameters)
|
24
|
+
* [Setting a Status Code](#setting-a-status-code)
|
25
|
+
* [Writting to the Response Body](#writting-to-the-response-body)
|
26
|
+
* [Setting HTTP Headers](#setting-http-headers)
|
18
27
|
* [Redirecting a Request](#redirecting-a-request)
|
19
28
|
* [Halting a Request](#halting-a-request)
|
29
|
+
* [Cookies](#cookies)
|
20
30
|
* [Extending Tynn](#extending-tynn)
|
21
31
|
* [Middleware](#middleware)
|
22
32
|
* [Plugins](#plugins)
|
@@ -24,8 +34,13 @@ A thin library for web development in Ruby.
|
|
24
34
|
* [Default Plugins](#default-plugins)
|
25
35
|
* [Environments](#environments)
|
26
36
|
* [Method Override](#method-override)
|
37
|
+
* [JSON](#json)
|
38
|
+
* [Rendering Templates](#rendering-templates)
|
27
39
|
* [Static Files](#static-files)
|
40
|
+
* [Sessions](#sessions)
|
28
41
|
* [Security](#security)
|
42
|
+
* [Secure Headers](#secure-headers)
|
43
|
+
* [HTTPS](#https)
|
29
44
|
* [Testing](#testing)
|
30
45
|
* [API Reference](http://api.tynn.xyz/2.0.0)
|
31
46
|
* [Changelog](#changelog)
|
@@ -46,7 +61,7 @@ Tynn is not a framework. There is no built-in support for connecting to database
|
|
46
61
|
|
47
62
|
Like most web libraries in Ruby, Tynn is built on top of [Rack], a Ruby webserver interface. Because of this, it has the benefits of using existing libraries and a variety of web servers for free.
|
48
63
|
|
49
|
-
Tynn takes design cues from other web libraries like [Rails], [Sinatra], and [Cuba].
|
64
|
+
Tynn takes design cues from other web libraries like [Rails], [Sinatra], and [Cuba].
|
50
65
|
|
51
66
|
### Assumptions
|
52
67
|
|
@@ -91,7 +106,7 @@ require "tynn"
|
|
91
106
|
|
92
107
|
Tynn.define do
|
93
108
|
on root? do
|
94
|
-
get do
|
109
|
+
on get do
|
95
110
|
res.write("Hello World!")
|
96
111
|
end
|
97
112
|
end
|
@@ -133,7 +148,7 @@ That's a little taste of what we call routing matchers in Tynn. The purpose of t
|
|
133
148
|
One line below there is another type of matcher:
|
134
149
|
|
135
150
|
```ruby
|
136
|
-
get do
|
151
|
+
on get do
|
137
152
|
res.write("Hello World!")
|
138
153
|
end
|
139
154
|
```
|
@@ -142,7 +157,7 @@ Tynn also includes methods for matching all the HTTP verbs (GET, POST, etc.). By
|
|
142
157
|
|
143
158
|
```ruby
|
144
159
|
Tynn.define do
|
145
|
-
get do
|
160
|
+
on get do
|
146
161
|
res.write("Hello World!")
|
147
162
|
end
|
148
163
|
end
|
@@ -175,10 +190,72 @@ Most popular web servers, like [Puma], [Unicorn] or [Thin], have built-in suppor
|
|
175
190
|
|
176
191
|
This section covers the routing features of Tynn, such as route definitions, composing applications, and so on.
|
177
192
|
|
193
|
+
### HTTP Verbs
|
194
|
+
|
195
|
+
TODO.
|
196
|
+
|
197
|
+
### Matching path segments
|
198
|
+
|
199
|
+
TODO.
|
200
|
+
|
201
|
+
### Capturing path segments
|
202
|
+
|
203
|
+
TODO.
|
204
|
+
|
205
|
+
### Matching more than paths
|
206
|
+
|
207
|
+
TODO.
|
208
|
+
|
209
|
+
### Routing rules
|
210
|
+
|
211
|
+
TODO.
|
212
|
+
|
213
|
+
### Composing Applications
|
214
|
+
|
215
|
+
TODO.
|
216
|
+
|
178
217
|
## Managing Request and Response
|
179
218
|
|
180
219
|
This section covers the many features Tynn provides to handle requests and responses.
|
181
220
|
|
221
|
+
### Accessing Request Parameters
|
222
|
+
|
223
|
+
TODO.
|
224
|
+
|
225
|
+
### Setting a Status Code
|
226
|
+
|
227
|
+
TODO.
|
228
|
+
|
229
|
+
### Writting to the Response Body
|
230
|
+
|
231
|
+
TODO.
|
232
|
+
|
233
|
+
### Setting HTTP Headers
|
234
|
+
|
235
|
+
To set HTTP headers in the response, use the `res.headers` hash.
|
236
|
+
|
237
|
+
```ruby
|
238
|
+
Tynn.define do
|
239
|
+
on get do
|
240
|
+
res.headers["Content-Type"] = "application/json"
|
241
|
+
|
242
|
+
res.write(JSON.generate(status: "ok"))
|
243
|
+
end
|
244
|
+
end
|
245
|
+
```
|
246
|
+
|
247
|
+
Default headers for the application can be set through the `:default_headers` setting.
|
248
|
+
|
249
|
+
```ruby
|
250
|
+
Tynn.set(:default_headers, "Content-Type" => "application/json")
|
251
|
+
|
252
|
+
Tynn.define do
|
253
|
+
on get do
|
254
|
+
res.write(JSON.generate(status: "ok"))
|
255
|
+
end
|
256
|
+
end
|
257
|
+
```
|
258
|
+
|
182
259
|
### Redirecting a Request
|
183
260
|
|
184
261
|
To redirect a request to a different location, use `res.redirect`.
|
@@ -219,8 +296,14 @@ end
|
|
219
296
|
# This won't be reached if current_user is nil
|
220
297
|
```
|
221
298
|
|
299
|
+
### Cookies
|
300
|
+
|
301
|
+
TODO.
|
302
|
+
|
222
303
|
## Extending Tynn
|
223
304
|
|
305
|
+
TODO.
|
306
|
+
|
224
307
|
### Middleware
|
225
308
|
|
226
309
|
Tynn runs on [Rack](https://github.com/rack/rack). Therefore it is possible
|
@@ -286,7 +369,7 @@ Here is the plugin in action:
|
|
286
369
|
App.currency # => "$"
|
287
370
|
|
288
371
|
App.define do
|
289
|
-
get do
|
372
|
+
on get do
|
290
373
|
res.write(to_currency(4567))
|
291
374
|
end
|
292
375
|
end
|
@@ -330,17 +413,7 @@ end
|
|
330
413
|
|
331
414
|
## Default Plugins
|
332
415
|
|
333
|
-
Tynn ships with a set of default plugins
|
334
|
-
|
335
|
-
| Name | Description
|
336
|
-
| --------------------- | -----------------------------------------------------------------
|
337
|
-
| [Tynn::Environment] | Adds helper methods to get and check the current environment.
|
338
|
-
| [Tynn::JSON] | Adds helper methods for json generation.
|
339
|
-
| [Tynn::Render] | Adds support for rendering templates through different engines.
|
340
|
-
| [Tynn::Session] | Adds simple cookie based session management.
|
341
|
-
| [Tynn::Static] | Adds support for static files (javascript files, images, etc.).
|
342
|
-
|
343
|
-
The following sections cover the default plugins and extensions shipped with Tynn.
|
416
|
+
Tynn ships with a set of default plugins. The following sections cover the default plugins and extensions shipped with Tynn.
|
344
417
|
|
345
418
|
### Environments
|
346
419
|
|
@@ -398,6 +471,33 @@ Tynn.configure(:production) do |app|
|
|
398
471
|
end
|
399
472
|
```
|
400
473
|
|
474
|
+
### JSON
|
475
|
+
|
476
|
+
Tynn ships with [Tynn::JSON] to serve JSON resources. It adds a helper method, called `json`, that generates a JSON document and writes it to the response body. This also automatically sets the content type header to `application/json`.
|
477
|
+
|
478
|
+
```ruby
|
479
|
+
require "tynn"
|
480
|
+
require "tynn/json"
|
481
|
+
|
482
|
+
Tynn.define do
|
483
|
+
on "array" do
|
484
|
+
on get do
|
485
|
+
json(["foo", "bar", "baz"])
|
486
|
+
end
|
487
|
+
end
|
488
|
+
|
489
|
+
on "object" do
|
490
|
+
on get do
|
491
|
+
json(foo: "bar")
|
492
|
+
end
|
493
|
+
end
|
494
|
+
end
|
495
|
+
```
|
496
|
+
|
497
|
+
### Rendering Templates
|
498
|
+
|
499
|
+
TODO.
|
500
|
+
|
401
501
|
### Method Override
|
402
502
|
|
403
503
|
HTML Forms only support GET and POST requests. To perform other actions such as PUT, PATCH or DELETE, use the [Rack::MethodOverride] middleware. Note that there is no need to add any new dependencies to the application as it's included in Rack already.
|
@@ -420,7 +520,7 @@ Now, this will trigger the `put` matcher in the application.
|
|
420
520
|
|
421
521
|
```ruby
|
422
522
|
Posts.define do
|
423
|
-
put do
|
523
|
+
on put do
|
424
524
|
post.update(req.params["post"])
|
425
525
|
end
|
426
526
|
end
|
@@ -462,8 +562,45 @@ Tynn.plugin(
|
|
462
562
|
)
|
463
563
|
```
|
464
564
|
|
565
|
+
### Sessions
|
566
|
+
|
567
|
+
TODO.
|
568
|
+
|
465
569
|
## Security
|
466
570
|
|
571
|
+
Many of the default plugins include secure default options. Here are some plugins that can improve the security of your web application.
|
572
|
+
|
573
|
+
### Secure Headers
|
574
|
+
|
575
|
+
Tynn ships with the [Tynn::SecureHeaders] plugin.
|
576
|
+
|
577
|
+
```ruby
|
578
|
+
require "tynn"
|
579
|
+
require "tynn/secure_headers"
|
580
|
+
|
581
|
+
Tynn.plugin(Tynn::SecureHeaders)
|
582
|
+
```
|
583
|
+
|
584
|
+
This plugins adds the following security related HTTP headers to each response.
|
585
|
+
|
586
|
+
- **X-Content-Type-Options:** Prevents IE and Chrome from [Content Type Sniffing][sniffing]. Defaults to `"nosniff"`.
|
587
|
+
|
588
|
+
- **X-Frame-Options:** Provides [Clickjacking] protection. Defaults to `"deny"`.
|
589
|
+
|
590
|
+
- **X-XSS-Protection:** Enables the XSS protection filter built into IE, Chrome and Safari. This filter is usually enabled by default, the use of this header is to re-enable it if it was turned off by the user. Defaults to `"1; mode=block"</tt`.
|
591
|
+
|
592
|
+
You can configure the default headers through the `:default_headers` setting.
|
593
|
+
|
594
|
+
```ruby
|
595
|
+
Tynn.set(:default_headers, {
|
596
|
+
"X-Frame-Options" => "sameorigin"
|
597
|
+
})
|
598
|
+
```
|
599
|
+
|
600
|
+
### HTTTPS
|
601
|
+
|
602
|
+
TODO.
|
603
|
+
|
467
604
|
## Testing
|
468
605
|
|
469
606
|
Tynn ships with [Tynn::Test], a simple helper class to simulate requests to your application.
|
@@ -473,16 +610,16 @@ require "tynn"
|
|
473
610
|
require "tynn/test"
|
474
611
|
|
475
612
|
Tynn.define do
|
476
|
-
|
613
|
+
on get do
|
477
614
|
res.write("hei")
|
478
615
|
end
|
479
616
|
end
|
480
617
|
|
481
|
-
|
482
|
-
|
618
|
+
ts = Tynn::Test.new
|
619
|
+
ts.get("/")
|
483
620
|
|
484
|
-
200 ==
|
485
|
-
"hei" ==
|
621
|
+
200 == ts.res.status # => true
|
622
|
+
"hei" == ts.res.body.join # => true
|
486
623
|
```
|
487
624
|
|
488
625
|
[Tynn::Test] is test-framework agnostic. The following example uses [Minitest]:
|
@@ -493,15 +630,15 @@ require "tynn/test"
|
|
493
630
|
|
494
631
|
class GuestsRouteTest < Minitest::Test
|
495
632
|
def setup
|
496
|
-
@
|
633
|
+
@ts = Tynn::Test.new
|
497
634
|
end
|
498
635
|
|
499
636
|
def test_home
|
500
|
-
@
|
637
|
+
@ts.get("/")
|
501
638
|
|
502
|
-
assert_equal 200, @
|
503
|
-
assert_equal "Hello World!", @
|
504
|
-
assert_equal "text/html", @
|
639
|
+
assert_equal 200, @ts.res.status
|
640
|
+
assert_equal "Hello World!", @ts.res.body.join
|
641
|
+
assert_equal "text/html", @ts.res.headers["Content-Type"]
|
505
642
|
end
|
506
643
|
end
|
507
644
|
```
|
@@ -546,6 +683,7 @@ Tynn is released under the [MIT License](http://www.opensource.org/licenses/MIT)
|
|
546
683
|
|
547
684
|
[bundler]: http://bundler.io/
|
548
685
|
[capybara]: https://github.com/jnicklas/capybara
|
686
|
+
[clickjacking]: https://www.owasp.org/index.php/Clickjacking
|
549
687
|
[cuba]: http://cuba.is/
|
550
688
|
[minitest]: https://github.com/seattlerb/minitest
|
551
689
|
[puma]: http://puma.io/
|
@@ -554,11 +692,12 @@ Tynn is released under the [MIT License](http://www.opensource.org/licenses/MIT)
|
|
554
692
|
[rack::methodoverride]: http://www.rubydoc.info/github/rack/rack/Rack/MethodOverride
|
555
693
|
[rails]: http://rubyonrails.org/
|
556
694
|
[sinatra]: http://www.sinatrarb.com/
|
557
|
-
[
|
695
|
+
[sniffing]: https://msdn.microsoft.com/library/gg622941(v=vs.85).aspx
|
558
696
|
[thin]: http://code.macournoyer.com/thin/
|
559
697
|
[tynn::environment]: http://api.tynn.xyz/2.0.0/Tynn/Environment.html
|
560
698
|
[tynn::json]: http://api.tynn.xyz/2.0.0/Tynn/JSON.html
|
561
699
|
[tynn::render]: http://api.tynn.xyz/2.0.0/Tynn/Render.html
|
700
|
+
[tynn::secureheaders]: http://api.tynn.xyz/2.0.0/Tynn/SecureHeaders.html
|
562
701
|
[tynn::session]: http://api.tynn.xyz/2.0.0/Tynn/Session.html
|
563
702
|
[tynn::static]: http://api.tynn.xyz/2.0.0/Tynn/Static.html
|
564
703
|
[tynn::test]: http://api.tynn.xyz/2.0.0/Tynn/Test.html
|
data/lib/tynn.rb
CHANGED
@@ -1,8 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative "tynn/base"
|
4
|
-
require_relative "tynn/default_headers"
|
5
|
-
require_relative "tynn/settings"
|
6
4
|
require_relative "tynn/version"
|
7
5
|
|
8
6
|
class Tynn
|
@@ -69,6 +67,4 @@ class Tynn
|
|
69
67
|
end
|
70
68
|
|
71
69
|
plugin(Tynn::Base)
|
72
|
-
plugin(Tynn::Settings)
|
73
|
-
plugin(Tynn::DefaultHeaders)
|
74
70
|
end
|
data/lib/tynn/base.rb
CHANGED
@@ -1,8 +1,32 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
# Copyright (c) 2016 Francesco Rodriguez
|
4
|
+
# Copyright (c) 2015-2016 Michel Martens (Portions of https://github.com/soveran/syro)
|
5
|
+
#
|
6
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
7
|
+
# of this software and associated documentation files (the "Software"), to deal
|
8
|
+
# in the Software without restriction, including without limitation the rights
|
9
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
10
|
+
# copies of the Software, and to permit persons to whom the Software is
|
11
|
+
# furnished to do so, subject to the following conditions:
|
12
|
+
#
|
13
|
+
# The above copyright notice and this permission notice shall be included in
|
14
|
+
# all copies or substantial portions of the Software.
|
15
|
+
#
|
16
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
17
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
18
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
19
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
20
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
21
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
22
|
+
# THE SOFTWARE.
|
23
|
+
|
24
|
+
require "rack"
|
25
|
+
require "seg"
|
26
|
+
require_relative "errors"
|
4
27
|
require_relative "request"
|
5
28
|
require_relative "response"
|
29
|
+
require_relative "utils"
|
6
30
|
|
7
31
|
class Tynn
|
8
32
|
module Base
|
@@ -13,19 +37,19 @@ class Tynn
|
|
13
37
|
# end
|
14
38
|
#
|
15
39
|
# Users.define do
|
16
|
-
# on
|
17
|
-
# get do
|
40
|
+
# on :id do |id|
|
41
|
+
# on get do
|
18
42
|
# res.write("GET /users/#{ id }")
|
19
43
|
# end
|
20
44
|
#
|
21
|
-
# post do
|
45
|
+
# on post do
|
22
46
|
# res.write("POST /users/#{ id }")
|
23
47
|
# end
|
24
48
|
# end
|
25
49
|
# end
|
26
50
|
#
|
27
51
|
def define(&block)
|
28
|
-
@__app = build_app(
|
52
|
+
@__app = build_app(proc { |env| new(block).call(env) })
|
29
53
|
end
|
30
54
|
|
31
55
|
def build_app(app) # :nodoc:
|
@@ -44,8 +68,27 @@ class Tynn
|
|
44
68
|
# Tynn.use(Rack::CommonLogger)
|
45
69
|
# Tynn.use(Rack::ShowExceptions)
|
46
70
|
#
|
71
|
+
# If applications handler is already set, it raises an error.
|
72
|
+
#
|
73
|
+
# Tynn.define {}
|
74
|
+
#
|
75
|
+
# Tynn.use(Rack::CommonLogger)
|
76
|
+
# # => Application middleware is frozen.
|
77
|
+
#
|
47
78
|
def use(middleware, *args, &block)
|
48
|
-
self.middleware.
|
79
|
+
if self.middleware.frozen?
|
80
|
+
raise Tynn::Error, <<~MSG
|
81
|
+
Application middleware is frozen and cannot be changed.
|
82
|
+
|
83
|
+
Please, set the middleware before setting the application handler:
|
84
|
+
|
85
|
+
#{ self }.use(#{ middleware })
|
86
|
+
|
87
|
+
#{ self }.define { ... }
|
88
|
+
MSG
|
89
|
+
else
|
90
|
+
self.middleware.push(proc { |app| middleware.new(app, *args, &block) })
|
91
|
+
end
|
49
92
|
end
|
50
93
|
|
51
94
|
def middleware # :nodoc:
|
@@ -58,39 +101,340 @@ class Tynn
|
|
58
101
|
|
59
102
|
def app # :nodoc:
|
60
103
|
(defined?(@__app) && @__app) or
|
61
|
-
raise("Application handler is missing. Try #{ self }.define {}")
|
104
|
+
raise(Tynn::Error, "Application handler is missing. Try #{ self }.define {}")
|
105
|
+
end
|
106
|
+
|
107
|
+
# Copies settings into the subclass. If a setting is not found,
|
108
|
+
# checks parent's settings.
|
109
|
+
def inherited(subclass) # :nodoc:
|
110
|
+
subclass.settings.replace(Tynn::Utils.deepclone_hash(settings))
|
111
|
+
subclass.settings.default_proc = proc { |h, k| h[k] = settings[k] }
|
112
|
+
end
|
113
|
+
|
114
|
+
# Returns a Hash with the application settings.
|
115
|
+
#
|
116
|
+
# Tynn.set(:environment, :development)
|
117
|
+
#
|
118
|
+
# Tynn.settings
|
119
|
+
# # => { :environment => :development }
|
120
|
+
#
|
121
|
+
def settings
|
122
|
+
@settings ||= {}
|
123
|
+
end
|
124
|
+
|
125
|
+
# Sets an <tt>option</tt> to the given </tt>value</tt>. If a setting
|
126
|
+
# with the <tt>option</tt> key exists and is a hash value, it merges
|
127
|
+
# the stored hash with <tt>value</tt>.
|
128
|
+
#
|
129
|
+
# Tynn.set(:environment, :staging)
|
130
|
+
#
|
131
|
+
# Tynn.settings[:environment]
|
132
|
+
# # => :staging
|
133
|
+
#
|
134
|
+
# Tynn.default_headers
|
135
|
+
# # => { "Content-Type" => "text/html" }
|
136
|
+
#
|
137
|
+
# Tynn.set(:default_headers, "X-Frame-Options" => "DENY")
|
138
|
+
#
|
139
|
+
# Tynn.default_headers
|
140
|
+
# # => { "Content-Type" => "text/html", "X-Frame-Options" => "DENY" }
|
141
|
+
#
|
142
|
+
def set(option, value)
|
143
|
+
v = settings[option]
|
144
|
+
|
145
|
+
if Hash === v
|
146
|
+
set!(option, v.merge(value))
|
147
|
+
else
|
148
|
+
set!(option, value)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
# Sets an <tt>option</tt> to the given </tt>value</tt>.
|
153
|
+
#
|
154
|
+
# Tynn.set!(:environment, :staging)
|
155
|
+
#
|
156
|
+
# Tynn.settings[:environment]
|
157
|
+
# # => :staging
|
158
|
+
#
|
159
|
+
# Tynn.default_headers
|
160
|
+
# # => { "Content-Type" => "text/html" }
|
161
|
+
#
|
162
|
+
# Tynn.set!(:default_headers, "X-Frame-Options" => "DENY")
|
163
|
+
#
|
164
|
+
# Tynn.default_headers
|
165
|
+
# # => { "X-Frame-Options" => "DENY" }
|
166
|
+
#
|
167
|
+
def set!(option, value)
|
168
|
+
settings[option] = value
|
169
|
+
end
|
170
|
+
|
171
|
+
# Returns a Hash with the default headers.
|
172
|
+
#
|
173
|
+
# Tynn.set(:default_headers, {
|
174
|
+
# "Content-Type" => "application/json"
|
175
|
+
# })
|
176
|
+
#
|
177
|
+
# Tynn.default_headers["Content-Type"]
|
178
|
+
# # => "application/json"
|
179
|
+
#
|
180
|
+
def default_headers
|
181
|
+
settings.fetch(:default_headers, {})
|
62
182
|
end
|
63
183
|
end
|
64
184
|
|
65
|
-
|
66
|
-
|
185
|
+
# Monkey-patches capture to return the path segment instead of storing it
|
186
|
+
# in a hash.
|
187
|
+
class Seg < ::Seg # :nodoc:
|
188
|
+
def capture
|
189
|
+
return nil if root?
|
190
|
+
|
191
|
+
len = (@path.index(SLASH, @pos) || @size) - @pos
|
192
|
+
|
193
|
+
segment = @path[@pos, len]
|
194
|
+
|
195
|
+
move(len)
|
196
|
+
|
197
|
+
segment
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
module InstanceMethods
|
202
|
+
def initialize(code) # :nodoc:
|
203
|
+
@__code = code
|
204
|
+
end
|
205
|
+
|
206
|
+
def call(env) # :nodoc:
|
207
|
+
@__env = env
|
208
|
+
@__req = Tynn::Request.new(env)
|
209
|
+
@__res = Tynn::Response.new(Hash[self.class.default_headers])
|
210
|
+
@__seg = Tynn::Base::Seg.new(env["PATH_INFO"])
|
211
|
+
|
212
|
+
catch(:halt) do
|
213
|
+
instance_eval(&@__code)
|
214
|
+
|
215
|
+
@__res.finish
|
216
|
+
end
|
217
|
+
end
|
67
218
|
|
68
|
-
#
|
69
|
-
#
|
219
|
+
# Returns the incoming request object. This object is an instance of
|
220
|
+
# Tynn::Request.
|
70
221
|
#
|
71
|
-
#
|
222
|
+
# req.post?
|
223
|
+
# # => true
|
224
|
+
#
|
225
|
+
# req.params
|
226
|
+
# # => { "username" => "bob", "password" => "secret" }
|
227
|
+
#
|
228
|
+
# req.headers["Content-Type"]
|
229
|
+
# # => "application/x-www-form-urlencoded"
|
230
|
+
#
|
231
|
+
def req
|
232
|
+
@__req
|
233
|
+
end
|
234
|
+
|
235
|
+
# Returns the current response object. This object is an instance of
|
236
|
+
# Tynn::Response.
|
237
|
+
#
|
238
|
+
# res.status = 200
|
239
|
+
# res.headers["Content-Type"] = "text/html"
|
240
|
+
# res.write("<h1>Welcome back!</h1>")
|
241
|
+
#
|
242
|
+
def res
|
243
|
+
@__res
|
244
|
+
end
|
245
|
+
|
246
|
+
# Executes a given block if <tt>arg</tt> matches one of these conditions.
|
247
|
+
#
|
248
|
+
# If <tt>arg</tt> is a string, it matches a path segment.
|
249
|
+
#
|
250
|
+
# Tynn.define do
|
251
|
+
# # Matches if /foo
|
252
|
+
# on "foo" do
|
253
|
+
# # Matches if /foo/bar/baz
|
254
|
+
# on "bar/baz" do
|
255
|
+
# res.write("/foo/bar/baz")
|
256
|
+
# end
|
257
|
+
# end
|
72
258
|
# end
|
73
259
|
#
|
74
|
-
#
|
260
|
+
# If <tt>arg</tt> is a symbol, it matches and capture a path segment.
|
75
261
|
#
|
76
|
-
#
|
77
|
-
#
|
262
|
+
# Tynn.define do
|
263
|
+
# # Matches if /users
|
264
|
+
# on "users" do
|
265
|
+
# # Captures id if matches /users/id
|
266
|
+
# on :id do |id|
|
267
|
+
# res.write("/users/#{ id }")
|
268
|
+
# end
|
269
|
+
# end
|
270
|
+
# end
|
271
|
+
#
|
272
|
+
# Finally, if <tt>arg</tt> is <tt>true</tt>, it executes the given block
|
273
|
+
# inconditionally.
|
274
|
+
#
|
275
|
+
# Tynn.define do
|
276
|
+
# on current_user.nil? do
|
277
|
+
# res.status = 401
|
278
|
+
# res.write("Unauthorized")
|
279
|
+
# end
|
78
280
|
# end
|
79
281
|
#
|
80
282
|
def on(arg)
|
81
|
-
|
283
|
+
(v = match(arg)) && yield(v)
|
284
|
+
end
|
285
|
+
|
286
|
+
def match(arg)
|
287
|
+
case arg
|
288
|
+
when String then @__seg.consume(arg)
|
289
|
+
when Symbol then @__seg.capture
|
290
|
+
when true then true
|
291
|
+
else false
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
private :match
|
296
|
+
|
297
|
+
# Immediately stops the request and returns <tt>response</tt>
|
298
|
+
# as per Rack's specification.
|
299
|
+
#
|
300
|
+
# halt([200, { "Content-Type" => "text/html" }, ["hello"]])
|
301
|
+
# halt(res.finish)
|
302
|
+
#
|
303
|
+
def halt(response)
|
304
|
+
throw(:halt, response)
|
305
|
+
end
|
306
|
+
|
307
|
+
# Runs a Tynn <tt>app</tt> and pass an optional hash of values to it.
|
308
|
+
#
|
309
|
+
# Tynn.define do
|
310
|
+
# on "admin" do
|
311
|
+
# run(Admin, subdomain: subdomain)
|
312
|
+
# end
|
313
|
+
# end
|
314
|
+
#
|
315
|
+
def run(app, inbox = nil)
|
316
|
+
path, script = @__env["PATH_INFO"], @__env["SCRIPT_NAME"]
|
317
|
+
|
318
|
+
@__env["PATH_INFO"] = @__seg.curr
|
319
|
+
@__env["SCRIPT_NAME"] = @__seg.prev
|
320
|
+
@__env["tynn.inbox"] = inbox
|
321
|
+
|
322
|
+
halt(app.call(@__env))
|
323
|
+
ensure
|
324
|
+
@__env["PATH_INFO"], @__env["SCRIPT_NAME"] = path, script
|
325
|
+
end
|
326
|
+
|
327
|
+
# Returns a hash with variables passed to the application.
|
328
|
+
#
|
329
|
+
# Tynn.define do
|
330
|
+
# on "api/v1" do
|
331
|
+
# run(API, version: 1)
|
332
|
+
# end
|
333
|
+
#
|
334
|
+
# on "api/v2" do
|
335
|
+
# run(API, version: 2)
|
336
|
+
# end
|
337
|
+
# end
|
338
|
+
#
|
339
|
+
# API.define do
|
340
|
+
# version = inbox[:version]
|
341
|
+
# end
|
342
|
+
#
|
343
|
+
def inbox
|
344
|
+
@__env["tynn.inbox"] || {}
|
345
|
+
end
|
346
|
+
|
347
|
+
# Returns <tt>true</tt> if the request method is <tt>GET</tt> and
|
348
|
+
# the path yet to be consumed is empty. Otherwise, returns <tt>false</tt>.
|
349
|
+
#
|
350
|
+
# Tynn.define do
|
351
|
+
# on "users" do
|
352
|
+
# on get do
|
353
|
+
# res.write("GET /users")
|
354
|
+
# end
|
355
|
+
# end
|
356
|
+
# end
|
357
|
+
#
|
358
|
+
def get
|
359
|
+
root? && req.get?
|
82
360
|
end
|
83
361
|
|
84
|
-
#
|
85
|
-
#
|
86
|
-
|
87
|
-
|
362
|
+
# Returns <tt>true</tt> if the request method is <tt>POST</tt> and
|
363
|
+
# the path yet to be consumed is empty. Otherwise, returns <tt>false</tt>.
|
364
|
+
#
|
365
|
+
# Tynn.define do
|
366
|
+
# on "users" do
|
367
|
+
# post do
|
368
|
+
# res.write("POST /users")
|
369
|
+
# end
|
370
|
+
# end
|
371
|
+
# end
|
372
|
+
#
|
373
|
+
def post
|
374
|
+
root? && req.post?
|
88
375
|
end
|
89
376
|
|
90
|
-
#
|
91
|
-
#
|
92
|
-
|
93
|
-
|
377
|
+
# Returns <tt>true</tt> if the request method is <tt>PATCH</tt> and
|
378
|
+
# the path yet to be consumed is empty. Otherwise, returns <tt>false</tt>.
|
379
|
+
#
|
380
|
+
# Tynn.define do
|
381
|
+
# on "users" do
|
382
|
+
# on :id do |id|
|
383
|
+
# on patch do
|
384
|
+
# res.write("PATCH /users/#{ id }")
|
385
|
+
# end
|
386
|
+
# end
|
387
|
+
# end
|
388
|
+
# end
|
389
|
+
#
|
390
|
+
def patch
|
391
|
+
root? && req.patch?
|
392
|
+
end
|
393
|
+
|
394
|
+
# Returns <tt>true</tt> if the request method is <tt>PUT</tt> and
|
395
|
+
# the path yet to be consumed is empty. Otherwise, returns <tt>false</tt>.
|
396
|
+
#
|
397
|
+
# Tynn.define do
|
398
|
+
# on "users" do
|
399
|
+
# on :id do |id|
|
400
|
+
# on put do
|
401
|
+
# res.write("PUT /users/#{ id }")
|
402
|
+
# end
|
403
|
+
# end
|
404
|
+
# end
|
405
|
+
# end
|
406
|
+
#
|
407
|
+
def put
|
408
|
+
root? && req.put?
|
409
|
+
end
|
410
|
+
|
411
|
+
# Returns <tt>true</tt> if the request method is <tt>DELETE</tt> and
|
412
|
+
# the path yet to be consumed is empty. Otherwise, returns <tt>false</tt>.
|
413
|
+
#
|
414
|
+
# Tynn.define do
|
415
|
+
# on "users" do
|
416
|
+
# on :id do |id|
|
417
|
+
# on delete do
|
418
|
+
# res.write("DELETE /users/#{ id }")
|
419
|
+
# end
|
420
|
+
# end
|
421
|
+
# end
|
422
|
+
# end
|
423
|
+
#
|
424
|
+
def delete
|
425
|
+
root? && req.delete?
|
426
|
+
end
|
427
|
+
|
428
|
+
# Returns <tt>true</tt> if the path yet to be consumed is empty.
|
429
|
+
#
|
430
|
+
# Tynn.define do
|
431
|
+
# on root? do
|
432
|
+
# res.write("/")
|
433
|
+
# end
|
434
|
+
# end
|
435
|
+
#
|
436
|
+
def root?
|
437
|
+
@__seg.root?
|
94
438
|
end
|
95
439
|
end
|
96
440
|
end
|