const http = require('http');
http
.createServer((req, res) => {
res.write('hello world');
res.end();
})
.listen(3000);
hello world
我們把 (req, res) => { ... } 叫做
http library 的 "handleRequest"
import http from 'http';
http
.createServer((req, res) => {
if (req.method === 'GET' && req.url === '/') {
res.write('hello world');
res.end();
}
if (req.method === 'GET' && req.url === '/users') {
res.write('no users');
res.end();
}
...
})
.listen(3000);
這個 handleRequest 就會變得很長
是一個基於 Node.js 的 http library 的框架
他的核心運作方式
就是
middleware
middleware1
middleware2
middleware3
...
大的 function
compose
http 的
handleRequest
包裝一下
例如
logger
auth-checker
body-parser
...
(req, res)
response
middleware
middleware
middleware
middleware
routing
/
/home
/explore
...
(req, res)
middleware
middleware
middleware
middleware
response
Philosophically, Koa aims to "fix and replace node", whereas Express "augments node".
koa 的特點
1. 單純基於 middleware
2. 自己有另一套 api (ctx)
const express = require('express');
const app = express();
app.get('/', (req, res) => {
res.send('Hello World!');
});
app.listen(3000);
const Koa = require('koa');
const app = new Koa();
app.use(ctx => {
ctx.response.body = 'Hello World!';
});
app.listen(3000);
express
koa
import Koa from 'koa';
const app = new Koa();
app.use((ctx, next) => {
ctx.response.body = 17;
return next();
});
app.use((ctx, next) => {
ctx.response.body += 2;
return next();
});
app.use((ctx) => {
ctx.response.body *= 5;
});
app.listen(3000);
middlewareA
middlewareB
middlewareC
middlewareA
(req, res)
middlewareB
middlewareC
createContext
ctx
ctx
pass ctx
ctx
ctx
ctx
respond (操作 res)
http 的 handleRequest
fnMiddleware
response
pass ctx
pass ctx
pass ctx
1. 事先把 middleware 組成一個 fnMiddleware
2. 當 request 來的時候
(1) 產生 ctx
(2) 把 ctx 丟到 fnMiddleware
(3) 以 ctx 的結果去操作 res,產生 response
compose
createContext
respond
callback() {
const fnMiddleware = compose(this.middleware);
...
const handleRequest = (req, res) => {
const ctx = this.createContext(req, res);
return this.handleRequest(ctx, fnMiddleware);
};
return handleRequest;
}
(array)
import Koa from 'koa';
const app = new Koa();
app.use((ctx, next) => {
ctx.response.body = 17;
return next();
});
app.use((ctx, next) => {
ctx.response.body += 2;
return next();
});
app.use((ctx) => {
ctx.response.body *= 5;
});
app.listen(3000);
class Application extends Emitter {
...
constructor(options) {
...
this.middleware = [];
...
}
...
use(fn) {
...
this.middleware.push(fn);
...
}
...
}
class Application extends Emitter {
...
listen(...args) {
...
const server = http.createServer(this.callback());
return server.listen(...args);
}
...
callback() {
const fnMiddleware = compose(this.middleware);
const handleRequest = (req, res) => {
const ctx = this.createContext(req, res);
return this.handleRequest(ctx, fnMiddleware);
};
return handleRequest;
}
...
}
產生 http 的 handleRequest
class Application extends Emitter {
...
callback() {
const fnMiddleware = compose(this.middleware);
const handleRequest = (req, res) => {
const ctx = this.createContext(req, res);
return this.handleRequest(ctx, fnMiddleware);
};
return handleRequest;
}
...
handleRequest(ctx, fnMiddleware) {
...
return fnMiddleware(ctx).then(() => respond(ctx)).catch(onerror);
}
...
}
基本上就是把 ctx 丟給 fnMiddleware,
fnMiddleware 層做完以後它再接手 respond
use 把我們寫的 function 加到 middleware
class Application extends Emitter {
...
use(fn) {
...
this.middleware.push(fn);
...
}
...
}
然後 listen 的時候幫我們把 middleware 組起來
再包成 http 的 handleRequest 丟給 http.createServer
class Application extends Emitter {
...
listen(...args) {
...
const fnMiddleware = compose(this.middleware);
const server = http.createServer((req, res) => {
const ctx = this.createContext(req, res);
return fnMiddleware(ctx).then(() => respond(ctx)).catch(onerror);
});
return server.listen(...args);
}
...
}
class Application extends Emitter {
...
createContext(req, res) {
const context = Object.create(this.context);
const request = context.request = Object.create(this.request);
const response = context.response = Object.create(this.response);
context.app = request.app = response.app = this;
context.req = request.req = response.req = req;
context.res = request.res = response.res = res;
request.ctx = response.ctx = context;
request.response = response;
response.request = request;
context.originalUrl = request.originalUrl = req.url;
context.state = {};
return context;
}
...
}
function respond(ctx) {
...
// responses
if (Buffer.isBuffer(body)) return res.end(body);
if ('string' === typeof body) return res.end(body);
if (body instanceof Stream) return body.pipe(res);
body = JSON.stringify(body);
if (!res.headersSent) {
ctx.length = Buffer.byteLength(body);
}
res.end(body);
}
function compose (middleware) {
...
return function (context, next) {
// last called middleware #
let index = -1
return dispatch(0)
function dispatch (i) {
if (i <= index) return Promise.reject(new Error('next() called multiple times'))
index = i
let fn = middleware[i]
if (i === middleware.length) fn = next
if (!fn) return Promise.resolve()
try {
return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
} catch (err) {
return Promise.reject(err)
}
}
}
}
把 middleware (array of functions) 組成 fnMiddleware (function)
function compose (middleware) {
...
return function (context) {
function dispatch (i) {
const fn = middleware[i];
if (!fn) return;
return fn(context, dispatch(i + 1));
}
return dispatch(0);
}
}
不檢查錯誤 + 不考慮非同步 + 不 call next + 不管 this 的版本
function compose (middleware) {
...
return function (context, next) {
function dispatch (i) {
let fn = middleware[i];
if (i === middleware.length) fn = next;
if (!fn) return;
return fn(context, dispatch(i + 1));
}
return dispatch(0);
}
}
把 next 加回來的版本
function compose (middleware) {
...
return function (context, next) {
function dispatch (i) {
const fn = middleware[i];
if (i === middleware.length) fn = next;
if (!fn) return Promise.resolve();
try {
return Promise.resolve(fn(context, dispatch(i + 1)));
} catch (error) {
return Promise.reject(error);
}
}
return dispatch(0);
}
}
把 middleware (array) 組成 fnMiddleware
把 next 加回來 + async function 的版本
可以把幾個 middleware 組成一個更大的 middleware
compose 出來的 function 一樣是以 (ctx, next) 為參數的 function
其實 compose 出來的 fnMiddleware 沒有用到 next
class Application extends Emitter {
...
handleRequest(ctx, fnMiddleware) {
...
return fnMiddleware(ctx).then(() => respond(ctx)).catch(onerror);
}
...
}
但是 koa-compose 是一個獨立的套件
給別人用很方便
是另外一個 koa 生態系的 library
他 library 裡面也是用 koa-compose 來組出一個大 middleware
var Koa = require('koa');
var Router = require('koa-router');
var app = new Koa();
var router = new Router();
router
.get('/', (ctx, next) => {
ctx.body = 'Hello World!';
})
.post('/users', (ctx, next) => {
// ...
})
.put('/users/:id', (ctx, next) => {
// ...
})
.del('/users/:id', (ctx, next) => {
// ...
})
app.use(router.routes());
app.listen(3000);
import Koa from 'koa';
const app = new Koa();
app.use(async (ctx, next) => {
ctx.response.body = 17;
await next();
ctx.response.body *= 5;
});
app.use((ctx, next) => {
ctx.response.body += 2;
return next();
});
app.use((ctx, next) => {
return next();
});
app.listen(3000);
middelware
middelware
middelware
ctx
ctx
ctx
ctx
ctx
ctx
ctx
ctx
ctx
import Koa from 'koa';
const app = new Koa();
app.use(async (ctx, next) => {
ctx.response.body = 17;
await next();
ctx.response.body *= 5;
});
app.use((ctx) => {
ctx.response.body += 2;
return;
});
app.use((ctx, next) => {
ctx.response.body = 999;
return next();
});
app.listen(3000);
middelware
middelware
middelware
ctx
ctx
ctx
ctx
ctx
ctx