1 |
|
2 | # Guide
|
3 |
|
4 | This guide covers Baiji topics are not directly API related, such as best practices for writing middleware,
|
5 | application structure suggestions.
|
6 |
|
7 | ## Writing Middleware
|
8 |
|
9 | Baiji middleware are simple functions which return a `GeneratorFunction`, and accept another. When
|
10 | the middleware is run by an "upstream" middleware, it must manually `yield` to the "downstream" middleware.
|
11 |
|
12 | For example if you wanted to track how long it takes for a request to propagate through Koa by adding an
|
13 | `X-Response-Time` header field the middleware would look like the following:
|
14 |
|
15 | ```js
|
16 | function *responseTime(next) {
|
17 | var start = new Date;
|
18 | yield next;
|
19 | var ms = new Date - start;
|
20 | this.set('X-Response-Time', ms + 'ms');
|
21 | }
|
22 |
|
23 | app.use(responseTime);
|
24 | ```
|
25 |
|
26 | Here's another way to write the same thing, inline:
|
27 |
|
28 | ```js
|
29 | app.use(function *(next){
|
30 | var start = new Date;
|
31 | yield next;
|
32 | var ms = new Date - start;
|
33 | this.set('X-Response-Time', ms + 'ms');
|
34 | });
|
35 | ```
|
36 |
|
37 | If you're a front-end developer you can think any code before `yield next;` as the "capture" phase,
|
38 | while any code after is the "bubble" phase. This crude gif illustrates how ES6 generators allow us
|
39 | to properly utilize stack flow to implement request and response flows:
|
40 |
|
41 | ![koa middleware](/docs/middleware.gif)
|
42 |
|
43 | 1. Create a date to track duration
|
44 | 2. Yield control to the next middleware
|
45 | 3. Create another date to track response time
|
46 | 4. Yield control to the next middleware
|
47 | 5. Yield immediately since `contentLength` only works with responses
|
48 | 6. Yield upstream to Koa's noop middleware
|
49 | 7. Ignore setting the body unless the path is "/"
|
50 | 8. Set the response to "Hello World"
|
51 | 9. Ignore setting `Content-Length` when no body is present
|
52 | 10. Set the field
|
53 | 11. Output log line
|
54 | 12. Set `X-Response-Time` header field before response
|
55 | 13. Hand off to Koa to handle the response
|
56 |
|
57 |
|
58 | Note that the final middleware (step __6__) yields to what looks to be nothing - it's actually
|
59 | yielding to a no-op generator within Koa. This is so that every middleware can conform with the
|
60 | same API, and may be placed before or after others. If you removed `yield next;` from the furthest
|
61 | "downstream" middleware everything would function appropriately, however it would no longer conform
|
62 | to this behaviour.
|
63 |
|
64 | For example this would be fine:
|
65 |
|
66 | ```js
|
67 | app.use(function *response(){
|
68 | if ('/' != this.url) return;
|
69 | this.body = 'Hello World';
|
70 | });
|
71 | ```
|
72 |
|
73 | Next we'll look at the best practices for creating Koa middleware.
|
74 |
|
75 | ## Middleware Best Practices
|
76 |
|
77 | This section covers middleware authoring best practices, such as middleware
|
78 | accepting options, named middleware for debugging, among others.
|
79 |
|
80 | ### Middleware options
|
81 |
|
82 | When creating public middleware it's useful to conform to the convention of
|
83 | wrapping the middleware in a function that accepts options, allowing users to
|
84 | extend functionality. Even if your middleware accepts _no_ options, this is still
|
85 | a good idea to keep things uniform.
|
86 |
|
87 | Here our contrived `logger` middleware accepts a `format` string for customization,
|
88 | and returns the middleware itself:
|
89 |
|
90 | ```js
|
91 | function logger(format) {
|
92 | format = format || ':method ":url"';
|
93 |
|
94 | return function *(next){
|
95 | var str = format
|
96 | .replace(':method', this.method)
|
97 | .replace(':url', this.url);
|
98 |
|
99 | console.log(str);
|
100 |
|
101 | yield next;
|
102 | }
|
103 | }
|
104 |
|
105 | app.use(logger());
|
106 | app.use(logger(':method :url'));
|
107 | ```
|
108 |
|
109 | ### Named middleware
|
110 |
|
111 | Naming middleware is optional, however it's useful for debugging purposes to assign a name.
|
112 |
|
113 | ```js
|
114 | function logger(format) {
|
115 | return function *logger(next){
|
116 |
|
117 | }
|
118 | }
|
119 | ```
|
120 |
|
121 | ### Combining multiple middleware
|
122 |
|
123 | Sometimes you want to "compose" multiple middleware into a single middleware for easy re-use or exporting. To do so, you may chain them together with `.call(this, next)`s, then return another function that yields the chain.
|
124 |
|
125 | ```js
|
126 | function *random(next) {
|
127 | if ('/random' == this.path) {
|
128 | this.body = Math.floor(Math.random()*10);
|
129 | } else {
|
130 | yield next;
|
131 | }
|
132 | };
|
133 |
|
134 | function *backwards(next) {
|
135 | if ('/backwards' == this.path) {
|
136 | this.body = 'sdrawkcab';
|
137 | } else {
|
138 | yield next;
|
139 | }
|
140 | }
|
141 |
|
142 | function *pi(next) {
|
143 | if ('/pi' == this.path) {
|
144 | this.body = String(Math.PI);
|
145 | } else {
|
146 | yield next;
|
147 | }
|
148 | }
|
149 |
|
150 | function *all(next) {
|
151 | yield random.call(this, backwards.call(this, pi.call(this, next)));
|
152 | }
|
153 |
|
154 | app.use(all);
|
155 | ```
|
156 |
|
157 | This is exactly what [koa-compose](https://github.com/koajs/compose) does, which Koa internally uses to create and dispatch the middleware stack.
|
158 |
|
159 | ### Response Middleware
|
160 |
|
161 | Middleware that decide to respond to a request and wish to bypass downstream middleware may
|
162 | simply omit `yield next`. Typically this will be in routing middleware, but this can be performed by
|
163 | any. For example the following will respond with "two", however all three are executed, giving the
|
164 | downstream "three" middleware a chance to manipulate the response.
|
165 |
|
166 | ```js
|
167 | app.use(function *(next){
|
168 | console.log('>> one');
|
169 | yield next;
|
170 | console.log('<< one');
|
171 | });
|
172 |
|
173 | app.use(function *(next){
|
174 | console.log('>> two');
|
175 | this.body = 'two';
|
176 | yield next;
|
177 | console.log('<< two');
|
178 | });
|
179 |
|
180 | app.use(function *(next){
|
181 | console.log('>> three');
|
182 | yield next;
|
183 | console.log('<< three');
|
184 | });
|
185 | ```
|
186 |
|
187 | The following configuration omits `yield next` in the second middleware, and will still respond
|
188 | with "two", however the third (and any other downstream middleware) will be ignored:
|
189 |
|
190 | ```js
|
191 | app.use(function *(next){
|
192 | console.log('>> one');
|
193 | yield next;
|
194 | console.log('<< one');
|
195 | });
|
196 |
|
197 | app.use(function *(next){
|
198 | console.log('>> two');
|
199 | this.body = 'two';
|
200 | console.log('<< two');
|
201 | });
|
202 |
|
203 | app.use(function *(next){
|
204 | console.log('>> three');
|
205 | yield next;
|
206 | console.log('<< three');
|
207 | });
|
208 | ```
|
209 |
|
210 | When the furthest downstream middleware executes `yield next;` it's really yielding to a noop
|
211 | function, allowing the middleware to compose correctly anywhere in the stack.
|
212 |
|
213 | ## Async operations
|
214 |
|
215 | The [Co](https://github.com/visionmedia/co) forms Koa's foundation for generator delegation, allowing
|
216 | you to write non-blocking sequential code. For example this middleware reads the filenames from `./docs`,
|
217 | and then reads the contents of each markdown file in parallel before assigning the body to the joint result.
|
218 |
|
219 |
|
220 | ```js
|
221 | var fs = require('co-fs');
|
222 |
|
223 | app.use(function *(){
|
224 | var paths = yield fs.readdir('docs');
|
225 |
|
226 | var files = yield paths.map(function(path){
|
227 | return fs.readFile('docs/' + path, 'utf8');
|
228 | });
|
229 |
|
230 | this.type = 'markdown';
|
231 | this.body = files.join('');
|
232 | });
|
233 | ```
|
234 |
|
235 | ## Debugging Koa
|
236 |
|
237 | Koa along with many of the libraries it's built with support the __DEBUG__ environment variable from [debug](https://github.com/visionmedia/debug) which provides simple conditional logging.
|
238 |
|
239 | For example
|
240 | to see all koa-specific debugging information just pass `DEBUG=koa*` and upon boot you'll see the list of middleware used, among other things.
|
241 |
|
242 | ```
|
243 | $ DEBUG=koa* node --harmony examples/simple
|
244 | koa:application use responseTime +0ms
|
245 | koa:application use logger +4ms
|
246 | koa:application use contentLength +0ms
|
247 | koa:application use notfound +0ms
|
248 | koa:application use response +0ms
|
249 | koa:application listen +0ms
|
250 | ```
|
251 |
|
252 | Since JavaScript does not allow defining function names at
|
253 | runtime, you can also set a middleware's name as `._name`.
|
254 | This useful when you don't have control of a middleware's name.
|
255 | For example:
|
256 |
|
257 | ```js
|
258 | var path = require('path');
|
259 | var serve = require('koa-static');
|
260 |
|
261 | var publicFiles = serve(path.join(__dirname, 'public'));
|
262 | publicFiles._name = 'serve /public';
|
263 |
|
264 | app.use(publicFiles);
|
265 | ```
|
266 |
|
267 | Now, instead of just seeing "serve" when debugging, you will see:
|
268 |
|
269 | ```
|
270 | koa:application use serve /public +0ms
|
271 | ```
|