web_pipe 0.8.0 → 0.9.0
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/CHANGELOG.md +5 -0
- data/Gemfile +4 -2
- data/README.md +60 -310
- data/Rakefile +5 -3
- data/bin/console +4 -3
- data/docs/_config.yml +1 -0
- data/docs/building_a_rack_application.md +25 -0
- data/docs/composing_applications.md +43 -0
- data/docs/connection_struct/configuring_the_connection_struct.md +25 -0
- data/docs/connection_struct/halting_the_pipe.md +71 -0
- data/docs/connection_struct/sharing_data_downstream.md +46 -0
- data/docs/connection_struct.md +77 -0
- data/docs/design_model.md +44 -0
- data/docs/dsl_free_usage.md +26 -0
- data/docs/extensions/container.md +41 -0
- data/docs/extensions/cookies.md +47 -0
- data/docs/extensions/dry_schema.md +51 -0
- data/docs/extensions/dry_view.md +113 -0
- data/docs/extensions/flash.md +41 -0
- data/docs/extensions/params.md +117 -0
- data/docs/extensions/redirect.md +25 -0
- data/docs/extensions/router_params.md +40 -0
- data/docs/extensions/session.md +39 -0
- data/docs/extensions/url.md +11 -0
- data/docs/extensions.md +13 -0
- data/docs/introduction.md +73 -0
- data/docs/plugging_operations/composing_operations.md +36 -0
- data/docs/plugging_operations/injecting_operations.md +32 -0
- data/docs/plugging_operations/resolving_operations.md +71 -0
- data/docs/plugging_operations.md +21 -0
- data/docs/plugs/config.md +18 -0
- data/docs/plugs/content_type.md +16 -0
- data/docs/plugs.md +13 -0
- data/docs/recipes/dry_rb_integration.md +18 -0
- data/docs/recipes/hanami_router_integration.md +25 -0
- data/docs/recipes/using_all_restful_methods.md +25 -0
- data/docs/using_rack_middlewares/composing_middlewares.md +26 -0
- data/docs/using_rack_middlewares/injecting_middlewares.md +47 -0
- data/docs/using_rack_middlewares.md +22 -0
- data/lib/web_pipe/app.rb +4 -2
- data/lib/web_pipe/conn.rb +5 -3
- data/lib/web_pipe/conn_support/builder.rb +2 -0
- data/lib/web_pipe/conn_support/composition.rb +9 -7
- data/lib/web_pipe/conn_support/errors.rb +3 -1
- data/lib/web_pipe/conn_support/headers.rb +12 -10
- data/lib/web_pipe/conn_support/types.rb +11 -9
- data/lib/web_pipe/dsl/builder.rb +5 -3
- data/lib/web_pipe/dsl/class_context.rb +5 -3
- data/lib/web_pipe/dsl/dsl_context.rb +7 -5
- data/lib/web_pipe/dsl/instance_methods.rb +7 -5
- data/lib/web_pipe/extensions/container/container.rb +2 -0
- data/lib/web_pipe/extensions/cookies/cookies.rb +4 -3
- data/lib/web_pipe/extensions/dry_schema/dry_schema.rb +2 -0
- data/lib/web_pipe/extensions/dry_schema/plugs/sanitize_params.rb +4 -2
- data/lib/web_pipe/extensions/dry_view/dry_view.rb +13 -9
- data/lib/web_pipe/extensions/flash/flash.rb +7 -7
- data/lib/web_pipe/extensions/params/params/transf.rb +3 -1
- data/lib/web_pipe/extensions/params/params.rb +7 -5
- data/lib/web_pipe/extensions/redirect/redirect.rb +8 -6
- data/lib/web_pipe/extensions/router_params/router_params.rb +4 -2
- data/lib/web_pipe/extensions/session/session.rb +6 -4
- data/lib/web_pipe/extensions/url/url.rb +3 -1
- data/lib/web_pipe/plug.rb +7 -5
- data/lib/web_pipe/plugs.rb +7 -1
- data/lib/web_pipe/rack_support/app_with_middlewares.rb +3 -1
- data/lib/web_pipe/rack_support/middleware.rb +2 -0
- data/lib/web_pipe/rack_support/middleware_specification.rb +5 -3
- data/lib/web_pipe/types.rb +3 -1
- data/lib/web_pipe/version.rb +3 -1
- data/lib/web_pipe.rb +5 -3
- data/web_pipe.gemspec +34 -34
- metadata +82 -48
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fc647eb09c8b7eb1d3bcfb910cc55e840285c6b1ab9bd9c69ea9b630f7a116fd
|
4
|
+
data.tar.gz: 6b667b8e71f1fac8d7c46a8160cbff273c70042bf1cc9b06da60c82a83fea5b2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f52d9d4c5b381d13de8f27ae03e8fc8bfb2cfe7e020d507f934341c9823f7c2cb2d94ebddc500d9337a59c2437c8ae3899e1b7ef942ed5e1cfc073591f8ac56f
|
7
|
+
data.tar.gz: 0be15319332d738f2b8ff07d7cb9d73583d00bf907e8787a2b8ab0fadc88d235f441b08e546bf162a6fb6fca8953ab68c34077cf300c0d6c1bb26aa37fe8f964
|
data/CHANGELOG.md
CHANGED
@@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file.
|
|
4
4
|
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
5
5
|
and this project adheres to [Semantic Versioning](http://semver.org/).
|
6
6
|
|
7
|
+
## [0.9.0] - 2019-08-31
|
8
|
+
### Added
|
9
|
+
- Comprehensive documentation.
|
10
|
+
[[#35]](https://github.com/waiting-for-dev/web_pipe/pull/35)
|
11
|
+
|
7
12
|
## [0.8.0] - 2019-08-30
|
8
13
|
### Added
|
9
14
|
- **BREAKING**. Rename `Rack` module to `RackSupport`.
|
data/Gemfile
CHANGED
@@ -1,6 +1,8 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
source 'https://rubygems.org'
|
4
|
+
|
5
|
+
git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
|
4
6
|
|
5
7
|
# Specify your gem's dependencies in web_pipe.gemspec
|
6
8
|
gemspec
|
data/README.md
CHANGED
@@ -3,339 +3,89 @@
|
|
3
3
|
|
4
4
|
# WebPipe
|
5
5
|
|
6
|
-
`web_pipe` is a rack application builder through a pipe of
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
[
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
the propagation of the pipe, leaving any downstream operation
|
48
|
-
unexecuted. This is mainly useful to unauthorize a request while being
|
49
|
-
sure that nothing else will be done to the response.
|
50
|
-
|
51
|
-
As you may know, this is the same model used by Elixir's
|
52
|
-
[`plug`](https://hexdocs.pm/plug/readme.html), from which `web_pipe`
|
53
|
-
takes inspiration.
|
54
|
-
|
55
|
-
This library has been designed to work frictionless along the
|
56
|
-
[`dry-rb`]( https://dry-rb.org/) ruby ecosystem and it uses some of
|
57
|
-
its libraries internally.
|
58
|
-
|
59
|
-
## Usage
|
60
|
-
|
61
|
-
This is a sample `config.ru` for a contrived application built with
|
62
|
-
`web_pipe`. It simply fetches a user from an `id` request
|
63
|
-
parameter. If the user is not found, it returns a not found
|
64
|
-
response. If it is found, it will unauthorize when it is a non `admin`
|
65
|
-
user or greet it otherwise:
|
66
|
-
|
67
|
-
```
|
68
|
-
rackup --port 4000
|
69
|
-
# http://localhost:4000?id=1 => Hello Alice
|
70
|
-
# http://localhost:4000?id=2 => Unauthorized
|
71
|
-
# http://localhost:4000?id=3 => Not found
|
72
|
-
```
|
6
|
+
`web_pipe` is a modular rack application builder through a pipe of
|
7
|
+
operations on an immutable struct.
|
8
|
+
|
9
|
+
In order to use in conjunction with [dry-rb](https://dry-rb.org/)
|
10
|
+
ecosystem, see also
|
11
|
+
[`dry-web-web_pipe`](https://github.com/waiting-for-dev/dry-web-web_pipe).
|
12
|
+
|
13
|
+
1. [Introduction](/docs/introduction.md)
|
14
|
+
1. [Design model](/docs/design_model.md)
|
15
|
+
1. [Building a rack application](/docs/building_a_rack_application.md)
|
16
|
+
1. [Plugging operations](/docs/plugging_operations.md)
|
17
|
+
1. [Resolving operations](/docs/plugging_operations/resolving_operations.md)
|
18
|
+
1. [Injecting operations](/docs/plugging_operations/injecting_operations.md)
|
19
|
+
1. [Composing operations](/docs/plugging_operations/composing_operations.md)
|
20
|
+
1. [Using rack middlewares](/docs/using_rack_middlewares.md)
|
21
|
+
1. [Injecting middlewares](/docs/using_rack_middlewares/injecting_middlewares.md)
|
22
|
+
1. [Composing middlewares](/docs/using_rack_middlewares/composing_middlewares.md)
|
23
|
+
1. [Composing applications](/docs/composing_applications.md)
|
24
|
+
1. [Connection struct](/docs/connection_struct.md)
|
25
|
+
1. [Sharing data downstream](/docs/connection_struct/sharing_data_downstream.md)
|
26
|
+
1. [Halting the pipe](/docs/connection_struct/halting_the_pipe.md)
|
27
|
+
1. [Configuring the connection struct](/docs/connection_struct/configuring_the_connection_struct.md)
|
28
|
+
1. [DSL free usage](/docs/dsl_free_usage.md)
|
29
|
+
1. [Plugs](/docs/plugs.md)
|
30
|
+
1. [Config](/docs/plugs/config.md)
|
31
|
+
1. [ContentType](/docs/plugs/content_type.md)
|
32
|
+
1. [Extensions](/docs/extensions.md)
|
33
|
+
1. [Container](/docs/extensions/container.md)
|
34
|
+
1. [Cookies](/docs/extensions/cookies.md)
|
35
|
+
1. [Flash](/docs/extensions/flash.md)
|
36
|
+
1. [Dry Schema](/docs/extensions/dry_schema.md)
|
37
|
+
1. [Dry View](/docs/extensions/dry_view.md)
|
38
|
+
1. [Params](/docs/extensions/params.md)
|
39
|
+
1. [Redirect](/docs/extensions/redirect.md)
|
40
|
+
1. [Router params](/docs/extensions/router_params.md)
|
41
|
+
1. [Session](/docs/extensions/session.md)
|
42
|
+
1. [URL](/docs/extensions/url.md)
|
43
|
+
1. Recipes
|
44
|
+
1. [dry-rb integration](/docs/recipes/dry_rb_integration.md)
|
45
|
+
1. [hanami-router integration](/docs/recipes/hanami_router_integration.md)
|
46
|
+
1. [Using all RESTful methods](/docs/recipes/using_all_restful_methods.mb)
|
73
47
|
|
74
48
|
```ruby
|
75
49
|
# config.ru
|
76
|
-
require
|
50
|
+
require 'web_pipe'
|
77
51
|
|
78
|
-
|
79
|
-
1 => { name: 'Alice', admin: true },
|
80
|
-
2 => { name: 'Joe', admin: false }
|
81
|
-
}
|
52
|
+
WebPipe.load_extensions(:params)
|
82
53
|
|
83
|
-
class
|
54
|
+
class HelloApp
|
84
55
|
include WebPipe
|
85
|
-
|
86
|
-
|
87
|
-
|
56
|
+
|
57
|
+
AUTHORIZED_USERS = %w[Alice Joe]
|
58
|
+
|
59
|
+
plug :html
|
88
60
|
plug :authorize
|
89
61
|
plug :greet
|
90
|
-
|
62
|
+
|
91
63
|
private
|
92
|
-
|
93
|
-
def
|
94
|
-
conn.add_response_header(
|
95
|
-
'Content-Type', 'text/html'
|
96
|
-
)
|
97
|
-
end
|
98
|
-
|
99
|
-
def fetch_user(conn)
|
100
|
-
user = UsersRepo[conn.params['id'].to_i]
|
101
|
-
if user
|
102
|
-
conn.
|
103
|
-
add(:user, user)
|
104
|
-
else
|
105
|
-
conn.
|
106
|
-
set_status(404).
|
107
|
-
set_response_body('<h1>Not foud</h1>').
|
108
|
-
halt
|
109
|
-
end
|
64
|
+
|
65
|
+
def html(conn)
|
66
|
+
conn.add_response_header('Content-Type', 'text/html')
|
110
67
|
end
|
111
|
-
|
68
|
+
|
112
69
|
def authorize(conn)
|
113
|
-
|
114
|
-
|
70
|
+
user = conn.params['user']
|
71
|
+
if AUTHORIZED_USERS.include?(user)
|
72
|
+
conn.add(:user, user)
|
115
73
|
else
|
116
74
|
conn.
|
117
75
|
set_status(401).
|
118
|
-
set_response_body('<h1>
|
76
|
+
set_response_body('<h1>Not authorized</h1>').
|
119
77
|
halt
|
120
78
|
end
|
121
79
|
end
|
122
80
|
|
123
81
|
def greet(conn)
|
124
|
-
conn.
|
125
|
-
set_response_body("<h1>Hello #{conn.fetch(:user)[:name]}</h1>")
|
126
|
-
end
|
127
|
-
end
|
128
|
-
|
129
|
-
run GreetingAdminApp.new
|
130
|
-
```
|
131
|
-
|
132
|
-
As you see, steps required are:
|
133
|
-
|
134
|
-
- Include `WebPipe` in a class.
|
135
|
-
- Specify the stack of operations with `plug`.
|
136
|
-
- Implement those operations.
|
137
|
-
- Initialize the class to obtain resulting rack application.
|
138
|
-
|
139
|
-
`WebPipe::Conn` is a struct of request and response date, seasoned
|
140
|
-
with methods that act on its data. These methods are designed to
|
141
|
-
return a new instance of the struct each time, so they encourage
|
142
|
-
immutability and make method chaining possible.
|
143
|
-
|
144
|
-
Each operation in the pipe must accept a single argument of a
|
145
|
-
`WebPipe::Conn` instance and it must also return an instance of it.
|
146
|
-
In fact, what the first operation in the pipe takes is a
|
147
|
-
`WebPipe::Conn::Ongoing` subclass instance. When one of your operations
|
148
|
-
calls `#halt` on it, a `WebPipe::Conn::Halted` is returned and the pipe
|
149
|
-
is halted. This one or the 'ongoing' instance that reaches the end of
|
150
|
-
the pipe will be in command of the web response.
|
151
|
-
|
152
|
-
Operations have the chance to prepare data to be consumed by
|
153
|
-
downstream operations. Data can be added to the struct through
|
154
|
-
`#add(key, value)`, while it can be consumed with `#fetch(key)`.
|
155
|
-
|
156
|
-
Attributes and methods in `WebPipe::Conn` are [fully
|
157
|
-
documented](https://www.rubydoc.info/github/waiting-for-dev/web_pipe/master/WebPipe/Conn).
|
158
|
-
|
159
|
-
### Specifying operations
|
160
|
-
|
161
|
-
There are several ways you can `plug` operations to the pipe:
|
162
|
-
|
163
|
-
#### Instance methods
|
164
|
-
|
165
|
-
Operations can be just methods defined in the pipe class. This is what
|
166
|
-
you saw in the previous example:
|
167
|
-
|
168
|
-
```ruby
|
169
|
-
class App
|
170
|
-
include WebPipe
|
171
|
-
|
172
|
-
plug :hello
|
173
|
-
|
174
|
-
private
|
175
|
-
|
176
|
-
def hello(conn)
|
177
|
-
# ...
|
178
|
-
end
|
179
|
-
```
|
180
|
-
|
181
|
-
#### Proc (or anything responding to `#call`)
|
182
|
-
|
183
|
-
Operations can also be defined inline as anything that responds to
|
184
|
-
`#call`, like a `Proc`, or also like a block:
|
185
|
-
|
186
|
-
```ruby
|
187
|
-
class App
|
188
|
-
include WebPipe
|
189
|
-
|
190
|
-
plug :hello, ->(conn) { conn }
|
191
|
-
plug(:hello2) { |conn| conn }
|
192
|
-
end
|
193
|
-
```
|
194
|
-
|
195
|
-
The representation of a `WebPipe` as a Proc is itself an operation
|
196
|
-
accepting a `Conn` and returning a `Conn`: the composition of all its
|
197
|
-
plugs. Therefore, it can be plugged to any other `WebPipe`:
|
198
|
-
|
199
|
-
```ruby
|
200
|
-
class HtmlApp
|
201
|
-
include WebPipe
|
202
|
-
|
203
|
-
plug :html
|
204
|
-
|
205
|
-
private
|
206
|
-
|
207
|
-
def html(conn)
|
208
|
-
conn.add_response_header('Content-Type', 'text/html')
|
82
|
+
conn.set_response_body("<h1>Hello #{conn.fetch(:user)}</h1>")
|
209
83
|
end
|
210
84
|
end
|
211
85
|
|
212
|
-
|
213
|
-
include WebPipe
|
214
|
-
|
215
|
-
plug :html, HtmlApp.new
|
216
|
-
plug :body
|
217
|
-
|
218
|
-
private
|
219
|
-
|
220
|
-
def body(conn)
|
221
|
-
conn.set_response_body('Hello, world!')
|
222
|
-
end
|
223
|
-
end
|
224
|
-
```
|
225
|
-
|
226
|
-
#### Container
|
227
|
-
|
228
|
-
When a `String` or a `Symbol` is given, it can be used as the key to
|
229
|
-
resolve an operation from a container. A container is just anything
|
230
|
-
responding to `#[]`.
|
231
|
-
|
232
|
-
The container to be used is configured when you include `WebPipe`:
|
233
|
-
|
234
|
-
```ruby
|
235
|
-
class App
|
236
|
-
Container = Hash[
|
237
|
-
'plugs.hello' => ->(conn) { conn }
|
238
|
-
]
|
239
|
-
|
240
|
-
include WebPipe.(container: Container)
|
241
|
-
|
242
|
-
plug :hello, 'plugs.hello'
|
243
|
-
end
|
86
|
+
run HelloApp.new
|
244
87
|
```
|
245
88
|
|
246
|
-
### Operations injection
|
247
|
-
|
248
|
-
Operations can be injected when the application is initialized,
|
249
|
-
overriding those configured through `plug`:
|
250
|
-
|
251
|
-
```ruby
|
252
|
-
class App
|
253
|
-
include WebPipe
|
254
|
-
|
255
|
-
plug :hello, ->(conn) { conn.set_response_body('Hello') }
|
256
|
-
end
|
257
|
-
|
258
|
-
run App.new(plugs: {
|
259
|
-
hello: ->(conn) { conn.set_response_body('Injected') }
|
260
|
-
}
|
261
|
-
)
|
262
|
-
```
|
263
|
-
|
264
|
-
In the previous example, resulting response body would be `Injected`.
|
265
|
-
|
266
|
-
### Rack middlewares
|
267
|
-
|
268
|
-
Rack middlewares can be added to the generated application through
|
269
|
-
`use`. They will be executed in declaration order before the pipe of
|
270
|
-
plugs:
|
271
|
-
|
272
|
-
```ruby
|
273
|
-
class App
|
274
|
-
include WebPipe
|
275
|
-
|
276
|
-
use :middleware_1, Middleware1
|
277
|
-
use :middleware_1, Middleware2, option_1: value_1
|
278
|
-
|
279
|
-
plug :hello, ->(conn) { conn }
|
280
|
-
end
|
281
|
-
```
|
282
|
-
|
283
|
-
It is also possible to compose all the middlewares from another pipe
|
284
|
-
class. Extending from previous example:
|
285
|
-
|
286
|
-
```ruby
|
287
|
-
class App2
|
288
|
-
include WebPipe
|
289
|
-
|
290
|
-
use :app, App.new # it will also use Middleware1 and Middleware2
|
291
|
-
|
292
|
-
plug :hello, ->(conn) { conn }
|
293
|
-
end
|
294
|
-
```
|
295
|
-
|
296
|
-
Middlewares can also be injected on initialization:
|
297
|
-
|
298
|
-
```ruby
|
299
|
-
App.new(middlewares: {
|
300
|
-
middleware_1: [AnotherMiddleware, options]
|
301
|
-
})
|
302
|
-
```
|
303
|
-
|
304
|
-
### Standalone usage
|
305
|
-
|
306
|
-
If you prefer, you can use the application builder without the
|
307
|
-
DSL. For that, you just have to initialize a `WebPipe::App` with an
|
308
|
-
array of all the operations to be performed:
|
309
|
-
|
310
|
-
```ruby
|
311
|
-
require 'web_pipe/app`
|
312
|
-
|
313
|
-
op_1 = ->(conn) { conn.set_status(200) }
|
314
|
-
op_2 = ->(conn) { conn.set_response_body('Hello') }
|
315
|
-
|
316
|
-
WebPipe::App.new([op_1, op_2])
|
317
|
-
```
|
318
|
-
|
319
|
-
## Plugs
|
320
|
-
|
321
|
-
`web_pipe` ships with a series of common operations you can take
|
322
|
-
advantage in order to build your application:
|
323
|
-
|
324
|
-
- [content_type](lib/web_pipe/plugs/content_type.rb): Sets
|
325
|
-
`Content-Type` response header.
|
326
|
-
|
327
|
-
## Extensions
|
328
|
-
|
329
|
-
By default, `web_pipe` behavior is the very minimal you need to build
|
330
|
-
a web application. However, you can extend it with the following
|
331
|
-
extensions (click on each name for details on the usage):
|
332
|
-
|
333
|
-
- [container](lib/web_pipe/plugs/container.rb): Allows
|
334
|
-
configuring a container to resolve dependencies.
|
335
|
-
- [dry-view](lib/web_pipe/extensions/dry_view/dry_view.rb):
|
336
|
-
Integration with [`dry-view`](https://dry-rb.org/gems/dry-view/)
|
337
|
-
rendering system.
|
338
|
-
|
339
89
|
## Current status
|
340
90
|
|
341
91
|
`web_pipe` is in active development. The very basic features to build
|
@@ -350,4 +100,4 @@ https://github.com/waiting-for-dev/web_pipe.
|
|
350
100
|
|
351
101
|
## Release Policy
|
352
102
|
|
353
|
-
`web_pipe` follows the principles of [semantic versioning](http://semver.org/).
|
103
|
+
`web_pipe` follows the principles of [semantic versioning](http://semver.org/).
|
data/Rakefile
CHANGED
data/bin/console
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
|
-
require
|
4
|
-
require
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'web_pipe'
|
5
6
|
|
6
7
|
# You can add fixtures and/or initialization code here to make experimenting
|
7
8
|
# with your gem easier. You can also use a different console, if you like.
|
@@ -10,5 +11,5 @@ require "web_pipe"
|
|
10
11
|
# require "pry"
|
11
12
|
# Pry.start
|
12
13
|
|
13
|
-
require
|
14
|
+
require 'pry'
|
14
15
|
Pry.start
|
data/docs/_config.yml
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
theme: jekyll-theme-slate
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# Building a rack application
|
2
|
+
|
3
|
+
In order to build a rack application with `web_pipe` you have to include
|
4
|
+
`WebPipe` module in a class:
|
5
|
+
|
6
|
+
```ruby
|
7
|
+
require 'web_pipe'
|
8
|
+
|
9
|
+
class MyApp
|
10
|
+
include WebPipe
|
11
|
+
|
12
|
+
# ...
|
13
|
+
end
|
14
|
+
```
|
15
|
+
|
16
|
+
Then, you can plug the operations and add the rack middlewares you need.
|
17
|
+
|
18
|
+
The instance of that class will be the rack application:
|
19
|
+
|
20
|
+
```ruby
|
21
|
+
# config.ru
|
22
|
+
require 'my_app'
|
23
|
+
|
24
|
+
run MyApp.new
|
25
|
+
```
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# Composing applications
|
2
|
+
|
3
|
+
Previously, we have seen how to [compose plugged
|
4
|
+
operations](/docs/plugging_operations/composing_operations.md) and how to [compose
|
5
|
+
rack middlewares](/docs/using_rack_middlewares/composing_middlewares.md). The logical
|
6
|
+
next step is thinking about composing `web_pipe` applications, which is exactly
|
7
|
+
the same as composing both operations and middlewares at the same time.
|
8
|
+
|
9
|
+
The DSL method `compose` does exactly that:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
class HtmlApp
|
13
|
+
include WebPipe
|
14
|
+
|
15
|
+
use :session, Rack::Session::Cookie, key: 'my_app.session', secret: 'long'
|
16
|
+
use :csrf, Rack::Csrf, raise: true
|
17
|
+
|
18
|
+
plug :content_type
|
19
|
+
plug :default_status
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def content_type(conn)
|
24
|
+
conn.add_response_header('Content-Type' => 'text/html')
|
25
|
+
end
|
26
|
+
|
27
|
+
def default_status(conn)
|
28
|
+
conn.set_status(404)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class MyApp
|
33
|
+
include WebPipe
|
34
|
+
|
35
|
+
compose :web, HtmlApp.new
|
36
|
+
# It does exactly the same than:
|
37
|
+
# use :web, HtmlApp.new
|
38
|
+
# plug :web, HtmlApp.new
|
39
|
+
|
40
|
+
# use ...
|
41
|
+
# plug ...
|
42
|
+
end
|
43
|
+
```
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# Configuring the connection struct
|
2
|
+
|
3
|
+
[Extensions](/docs/extensions.md) add extra behaviour to the connection struct.
|
4
|
+
Sometimes they need some user provided value to work properly or they may allow
|
5
|
+
some tweak depending on user needs.
|
6
|
+
|
7
|
+
For this reason, you can add configuration data to a `WebPipe::Conn` instance
|
8
|
+
so that extensions can fetch it. This shared place where extensions look for
|
9
|
+
what they need is `#config` attribute, which is very similar to `#bag` except
|
10
|
+
for its more private intention.
|
11
|
+
|
12
|
+
In order to interact with `#config`, you can use the method `#add_config(key,
|
13
|
+
value)` or [`Config` plug](/docs/plugs/config.md).
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
class MyApp
|
17
|
+
include WebPipe
|
18
|
+
|
19
|
+
plug(:config) do |conn|
|
20
|
+
conn.add_config(
|
21
|
+
foo: :bar
|
22
|
+
)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
```
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# Halting the pipe
|
2
|
+
|
3
|
+
Each operation in a pipe takes a single `WebPipe::Conn` instance as argument
|
4
|
+
and returns another (or same) instance of it. In this way, a series of
|
5
|
+
operations on the connection struct is propagated until final response is sent
|
6
|
+
to the client.
|
7
|
+
|
8
|
+
More often than not, you may need to conditionally stop the propagation of a
|
9
|
+
pipe at a given operation. A lot of times the requirement to do something like
|
10
|
+
that will be authorization policies. For example, you could fetch the user
|
11
|
+
requesting a resource. In the case she was granted to perform required action
|
12
|
+
you would go on. However, if she wasn't you would like to halt the connection
|
13
|
+
and respond with a 4xx http status code.
|
14
|
+
|
15
|
+
In order to stop the pipe, you simply have to call `#halt` on the connection
|
16
|
+
struct.
|
17
|
+
|
18
|
+
At implementation level, we must admit that we've not been 100% accurate until
|
19
|
+
now. We said that the first operation in the pipe recerived a `WebPipe::Conn`
|
20
|
+
instance. That's true. However, it is more precise saying that it gets a
|
21
|
+
`WebPipe::Conn::Ongoing` instance (`WebPipe::Conn::Ongoing` being a subclass of
|
22
|
+
`WebPipe::Conn`).
|
23
|
+
|
24
|
+
As long as an operation responds with a `WebPipe::Conn::Ongoing`
|
25
|
+
instance, the propagation will go on. However, when an operation
|
26
|
+
returns a `WebPipe::Conn::Halted` instance (another subclass of
|
27
|
+
`WebPipe::Conn`) then any operation downstream will be ignored.
|
28
|
+
Calling `#halt` simply copies all attributes to a `WebPipe::Conn::Halted`
|
29
|
+
instance and returns it.
|
30
|
+
|
31
|
+
This made-up example checks if the user in the request has an admin role. If
|
32
|
+
she has, it returns solicited resource. Otherwise she is unauthorized and never
|
33
|
+
gets the resource.
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
WebPipe.load_extensions(:params)
|
37
|
+
|
38
|
+
class ShowTaskApp
|
39
|
+
include WebPipe
|
40
|
+
|
41
|
+
plug :fetch_user
|
42
|
+
plug :authorize
|
43
|
+
plug :render_task
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def fetch_user(conn)
|
48
|
+
conn.add(
|
49
|
+
:user, UserRepo.find(conn.params[:user_id])
|
50
|
+
)
|
51
|
+
end
|
52
|
+
|
53
|
+
def authorize(conn)
|
54
|
+
if conn.fetch(:user).admin?
|
55
|
+
conn
|
56
|
+
else
|
57
|
+
conn.
|
58
|
+
set_status(401).
|
59
|
+
halt
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def render_task(conn)
|
64
|
+
conn.set_response_body(
|
65
|
+
TaskRepo.find(conn.params[:id]).to_json
|
66
|
+
)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
run ShowTaskApp.new
|
71
|
+
```
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# Sharing data downstream
|
2
|
+
|
3
|
+
Usually you'll find the need to prepare some data in one operation with the
|
4
|
+
intention for it to be consumed by another downstream operation. The connection
|
5
|
+
struct has a `#bag` attribute which is useful for this purpose.
|
6
|
+
|
7
|
+
`WebPipe::Conn#bag` is a `Hash` with `Symbol` keys where values can
|
8
|
+
be anything you need to share. To help with the process we have following methods:
|
9
|
+
|
10
|
+
- `#add(key, value)`: Assigns a value to a key.
|
11
|
+
- `#fetch(key)`, `#fetch(key, default)`: Retrieves value associated
|
12
|
+
to given key. If it is not found, `default` is returned when
|
13
|
+
provided.
|
14
|
+
|
15
|
+
This is a simple example of a web application which reads a `name`
|
16
|
+
parameter and normalizes it before using in the response body.
|
17
|
+
|
18
|
+
```ruby
|
19
|
+
# config.ru
|
20
|
+
require 'web_pipe'
|
21
|
+
|
22
|
+
WebPipe.load_extensions(:params)
|
23
|
+
|
24
|
+
class NormalizeNameApp
|
25
|
+
include WebPipe
|
26
|
+
|
27
|
+
plug :normalize_name
|
28
|
+
plug :respond
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def normalize_name(conn)
|
33
|
+
conn.add(
|
34
|
+
:name, conn.params[:name].downcase.capitalize
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
38
|
+
def respond(conn)
|
39
|
+
conn.set_response_body(
|
40
|
+
conn.fetch(:name)
|
41
|
+
)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
run NormalizeNameApp.new
|
46
|
+
```
|