tynn 2.0.0.alpha → 2.0.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![Build Status](https://travis-ci.org/frodsan/tynn.svg?branch=master)](https://travis-ci.org/frodsan/tynn)
|
4
4
|
[![Dependency Status](https://gemnasium.com/badges/github.com/frodsan/tynn.svg)](https://gemnasium.com/github.com/frodsan/tynn)
|
5
5
|
[![Code Climate](https://codeclimate.com/github/frodsan/tynn/badges/gpa.svg)](https://codeclimate.com/github/frodsan/tynn)
|
6
|
-
[![Join the chat at https://gitter.im/frodsan/tynn](https://badges.gitter.im/frodsan/tynn.svg)](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
|