Koa需要Node v12或更高版本以支持ES2015和异步函数。 您可以使用您喜欢的版本管理器快速安装一个支持的Node版本:
nvm install 12
npm install koa
node my-koa-app.js
Koa应用是一个包含中间件函数数组的对象,这些函数在请求时以类似堆栈的方式组合并执行
在Koa框架中,ctx
是上下文对象,它封装了原生的Node.js请求和响应对象,并且提供了许多方便的方法和属性来处理HTTP请求和响应
ctx.req
和 ctx.res
是Node.js的原生请求和响应对象。这些对象提供了低级别的请求和响应处理方法
ctx.res.end('ok')
是直接调用Node.js原生响应对象的 end
方法来结束响应并发送字符串 ok
1.server.js
// 导入koa模块
const Koa = require('./koa');
// 创建一个Koa应用实例
const app = new Koa();
app.use(function (ctx) {
// 处理请求,返回'hello'
ctx.res.end('hello');
});
// 监听3000端口,并在控制台打印服务器运行信息
app.listen(3000, () => console.log('server is running at http://localhost:3000'));
koa\lib\application.js
// 导入http模块
const http = require('http');
module.exports = class Application {
use(fn) {
// 设置中间件处理函数
this.middleware = fn;
}
listen(...args) {
// 创建一个HTTP服务器实例,并使用回调函数作为请求处理程序
const server = http.createServer(this.callback());
// 监听指定的端口,并返回监听器对象
return server.listen(...args);
}
callback() {
const handleRequest = (req, res) => {
// 创建上下文对象
const ctx = this.createContext(req, res);
// 调用中间件处理函数
this.middleware(ctx);
};
// 返回请求处理函数
return handleRequest;
}
createContext(req, res) {
//创建上下文对象
const context = {};
//设置请求对象
context.req = req;
//设置响应对象
context.res = res;
//返回上下文对象
return context;
}
}
在Koa中,ctx.request
和 ctx.response
是封装了 Node.js 原生请求和响应对象(ctx.req
和 ctx.res
)的对象。相比于 Node.js 的原生请求和响应对象,ctx.request
和 ctx.response
提供了更多的方法和属性,使得处理 HTTP 请求和响应更加方便和简单。
ctx.request
:ctx.request.query
: 一个包含解析过的查询字符串的对象ctx.request.method
: 请求方法,例如 'GET', 'POST' 等ctx.request.url
: 请求的 URLctx.request.header
: 请求头对象ctx.request.body
: 请求体(需要额外的中间件如koa-bodyparser来解析请求体)ctx.response
:ctx.response.body
: 可以设置响应体的内容,它可以是一个字符串、对象或者流ctx.response.status
: 可以设置响应的 HTTP 状态码ctx.response.message
: HTTP 状态消息ctx.response.header
: 响应头对象// 引入Koa框架
const Koa = require('./koa');
// 实例化Koa对象
const app = new Koa();
// 使用中间件处理请求
app.use(async ctx => {
+ // 打印请求方法
+ console.log(ctx.request.method, ctx.method)
+ // 打印请求URL
+ console.log(ctx.request.url, ctx.url)
+ // 打印请求路径
+ console.log(ctx.request.path, ctx.path)
+ // 打印查询字符串参数
+ console.log(ctx.request.query, ctx.query)
+ // 打印请求头
+ console.log(ctx.request.header, ctx.header)
+ // 设置响应状态码
+ ctx.response.status = 200;
+ ctx.status = 200;
+ // 设置响应消息
+ ctx.response.message = 'OK';
+ ctx.message = 'OK';
+ ctx.set('Content-Type', 'text/html;charset=utf-8');
+ // 设置响应体
+ ctx.response.body = 'Hello';
+ ctx.body = 'Hello';
});
// 监听3000端口,启动服务
app.listen(3000, () => console.log('server is running at http://localhost:3000'));
koa\lib\application.js
// 导入http模块
const http = require('http');
// 导入自定义的context模块
const context = require('./context.js');
// 导入自定义的request模块
const request = require('./request');
// 导入自定义的response模块
const response = require('./response');
module.exports = class Application {
// 构造函数
constructor() {
// 创建一个新的context对象,这个对象继承自自定义的context模块
this.context = Object.create(context)
// 创建一个新的request对象,这个对象继承自自定义的request模块
this.request = Object.create(request)
// 创建一个新的response对象,这个对象继承自自定义的response模块
this.response = Object.create(response)
}
use(fn) {
// 设置中间件处理函数
this.middleware = fn;
}
listen(...args) {
// 创建一个HTTP服务器实例,并使用回调函数作为请求处理程序
const server = http.createServer(this.callback());
// 监听指定的端口,并返回监听器对象
return server.listen(...args);
}
callback() {
const handleRequest = (req, res) => {
// 创建上下文对象
const ctx = this.createContext(req, res);
// 调用中间件处理函数
this.middleware(ctx);
// 获取context对象的body属性
+ let body = ctx.body;
+ // 结束响应并发送body
+ return res.end(body);
};
// 返回请求处理函数
return handleRequest;
}
+ createContext(req, res) {
+ // 创建一个新的context对象,这个对象继承自this.context
+ const context = Object.create(this.context)
+ // 创建一个新的request对象,这个对象继承自this.request,并将这个对象赋值给context.request
+ const request = context.request = Object.create(this.request)
+ // 创建一个新的response对象,这个对象继承自this.response,并将这个对象赋值给context.response
+ const response = context.response = Object.create(this.response)
+ // 将原生的req和res对象赋值给context、request和response的req和res属性
+ context.req = request.req = req;
+ context.res = response.res = res;
+ // 返回创建的context对象
+ return context;
+ }
}
koa\lib\context.js
// 导入自定义的代理(delegate)模块
const delegate = require('./delegates')
// 创建一个空对象proto并将其导出
const proto = module.exports = {}
// 使用代理模块将proto对象的一些属性代理到'request'对象
delegate(proto, 'request')
.access('method') // 将'request'对象的'method'属性代理到proto对象
.access('query') // 将'request'对象的'query'属性代理到proto对象
.access('url') // 将'request'对象的'url'属性代理到proto对象
.access('path') // 将'request'对象的'path'属性代理到proto对象
.getter('header') // 将'request'对象的'header'属性的获取代理到proto对象
// 使用代理模块将proto对象的一些属性代理到'response'对象
delegate(proto, 'response')
.access('status') // 将'response'对象的'status'属性代理到proto对象
.access('message') // 将'response'对象的'message'属性代理到proto对象
.access('body') // 将'response'对象的'body'属性代理到proto对象
.method('set') // 将'response'对象的'set'方法代理到proto对象
koa\lib\request.js
// 导入 parseurl 模块,用于解析 URL
const parse = require('parseurl')
// 导入 querystring 模块,用于解析查询字符串
const qs = require('querystring')
// 导出一个对象,这个对象定义了一些属性的 getter
module.exports = {
// 获取 HTTP 请求的头部
get header () {
return this.req.headers
},
// 获取 HTTP 请求的 URL
get url () {
return this.req.url
},
get path () {
return parse(this.req).pathname
},
// 获取 HTTP 请求的方法
get method () {
return this.req.method
},
// 获取 HTTP 请求的查询字符串
get querystring () {
return parse(this.req).query;
},
// 获取 HTTP 请求的查询参数,这些参数是一个对象,它由查询字符串解析得到
get query () {
return qs.parse(this.querystring)
}
}
koa\lib\response.js
// 定义一个模块,暴露出一些属性和方法
module.exports = {
// 设置状态码的方法
set status(code) {
this.res.statusCode = code;
},
// 设置状态消息的方法
set message(msg) {
this.res.statusMessage = msg;
},
// 设置body的方法
set body(val) {
this._body = val;
},
// 获取body的方法
get body () {
return this._body
},
set (field, val) {
this.res.setHeader(field, val)
}
}
koa\lib\delegates.js
// 定义一个Delegator函数,接收proto和target两个参数
function Delegator(proto, target) {
// 如果当前函数不是通过new关键字调用的,那么就返回一个新的Delegator实例
if (!(this instanceof Delegator)) return new Delegator(proto, target);
// 将proto和target保存为Delegator实例的属性
this.proto = proto;
this.target = target;
}
// 在Delegator原型上定义一个access方法,这个方法接收一个name参数
Delegator.prototype.access = function (name) {
// 调用getter和setter方法,将name属性的获取和设置代理到target对象
return this.getter(name).setter(name);
};
// 在Delegator原型上定义一个getter方法,这个方法接收一个name参数
Delegator.prototype.getter = function (name) {
// 保存proto和target属性
var proto = this.proto;
var target = this.target;
// 在proto对象上定义一个name属性,这个属性的获取被代理到target对象
Object.defineProperty(proto, name, {
get: function () {
return this[target][name];
},
configurable:true
});
// 返回当前Delegator实例
return this;
};
// 在Delegator原型上定义一个setter方法,这个方法接收一个name参数
Delegator.prototype.setter = function (name) {
// 保存proto和target属性
var proto = this.proto;
var target = this.target;
// 在proto对象上定义一个name属性,这个属性的设置被代理到target对象
Object.defineProperty(proto, name, {
set: function (val) {
this[target][name] = val;
},
configurable:true
});
// 返回当前Delegator实例
return this;
};
Delegator.prototype.method = function(name){
var proto = this.proto;
var target = this.target;
proto[name] = function(){
return this[target][name].apply(this[target], arguments);
};
return this;
};
// 导出Delegator函数
module.exports = Delegator;
3.server.js
const Koa = require('koa');
const app = new Koa();
const middleware1 = (ctx, next) => {
console.log(1);
next();
console.log(2);
};
const middleware2 = (ctx, next) => {
console.log(3);
next();
console.log(4);
};
const middleware3 = (ctx) => {
console.log(5);
};
app.use(middleware1);
app.use(middleware2);
app.use(middleware3);
app.listen(3000, () => console.log('server is running at http://localhost:3000'));
(ctx, next) => {
console.log(1);
(ctx, next) => {
console.log(3);
(ctx) => {
console.log(5);
}
console.log(4);
}
console.log(2);
}
const Koa = require('koa');
const app = new Koa();
const middleware1 = async (ctx, next) => {
console.time('cost');
console.log(1);
await next();
console.log(2);
console.timeEnd('cost')
};
const middleware2 = async (ctx, next) => {
console.log(3);
await new Promise((resolve) => {
setTimeout(() => {
console.log('middleware2 sleep 1s');
resolve();
}, 1000);
});
await next();
console.log(4);
};
const middleware3 = async (ctx) => {
console.log(5);
await new Promise((resolve) => {
setTimeout(() => {
console.log('middleware3 sleep 1s');
resolve();
}, 1000);
});
ctx.body = 'hello';
console.log(6);
};
app.use(middleware1);
app.use(middleware2);
app.use(middleware3);
app.listen(3000, () => console.log('server is running at http://localhost:3000'));
1
3
middleware2 sleep 1s
5
middleware3 sleep 1s
6
4
2
cost: 2.022s
koa\lib\application.js
// 导入http模块
const http = require('http');
// 导入自定义的context模块
const context = require('./context.js');
// 导入自定义的request模块
const request = require('./request');
// 导入自定义的response模块
const response = require('./response');
+const compose = require('./koa-compose')
module.exports = class Application {
// 构造函数
constructor() {
// 创建一个新的context对象,这个对象继承自自定义的context模块
this.context = Object.create(context)
// 创建一个新的request对象,这个对象继承自自定义的request模块
this.request = Object.create(request)
// 创建一个新的response对象,这个对象继承自自定义的response模块
this.response = Object.create(response)
+ this.middleware = []
}
use(fn) {
// 设置中间件处理函数
+ this.middleware.push(fn)
+ return this
}
listen(...args) {
// 创建一个HTTP服务器实例,并使用回调函数作为请求处理程序
const server = http.createServer(this.callback());
// 监听指定的端口,并返回监听器对象
return server.listen(...args);
}
callback() {
+ const fn = compose(this.middleware)
const handleRequest = (req, res) => {
// 创建上下文对象
const ctx = this.createContext(req, res);
+ return this.handleRequest(ctx, fn);
};
// 返回请求处理函数
return handleRequest;
}
+ handleRequest(ctx, fnMiddleware) {
+ const handleResponse = () => respond(ctx);
+ return fnMiddleware(ctx).then(handleResponse);
+ }
createContext(req, res) {
// 创建一个新的context对象,这个对象继承自this.context
const context = Object.create(this.context)
// 创建一个新的request对象,这个对象继承自this.request,并将这个对象赋值给context.request
const request = context.request = Object.create(this.request)
// 创建一个新的response对象,这个对象继承自this.response,并将这个对象赋值给context.response
const response = context.response = Object.create(this.response)
// 将原生的req和res对象赋值给context、request和response的req和res属性
context.req = request.req = req;
context.res = response.res = res;
// 返回创建的context对象
return context;
}
}
+function respond(ctx) {
+ let res = ctx.res;
+ let body = ctx.body;
+ return res.end(body);
+}
koa\lib\koa-compose.js
function compose(middleware) {
return function (context) {
function dispatch(i) {
let fn = middleware[i];
return Promise.resolve(fn(context, () => dispatch(i + 1)));
}
return dispatch(0);
};
}
module.exports = compose;
const Koa = require('./koa');
const app = new Koa();
const middleware1 = async (ctx, next) => {
//throw new Error('middleware1 error');
await next();
};
const middleware2 = async (ctx, next) => {
throw new Error('middleware2 error');
};
app.use(middleware1);
app.use(middleware2);
app.listen(3000, () => console.log('server is running at http://localhost:3000'));
koa\lib\koa-compose.js
function compose(middleware) {
return function (context) {
function dispatch(i) {
let fn = middleware[i];
+ if (!fn) return Promise.resolve()
+ try {
+ return Promise.resolve(fn(context, () => dispatch(i + 1)));
+ } catch (err) {
+ return Promise.reject(err)
+ }
}
return dispatch(0);
};
}
module.exports = compose;
koa\lib\application.js
// 导入http模块
const http = require('http');
// 导入自定义的context模块
const context = require('./context.js');
// 导入自定义的request模块
const request = require('./request');
// 导入自定义的response模块
const response = require('./response');
const compose = require('./koa-compose')
module.exports = class Application {
// 构造函数
constructor() {
// 创建一个新的context对象,这个对象继承自自定义的context模块
this.context = Object.create(context)
// 创建一个新的request对象,这个对象继承自自定义的request模块
this.request = Object.create(request)
// 创建一个新的response对象,这个对象继承自自定义的response模块
this.response = Object.create(response)
this.middleware = []
}
use(fn) {
// 设置中间件处理函数
this.middleware.push(fn)
return this
}
listen(...args) {
// 创建一个HTTP服务器实例,并使用回调函数作为请求处理程序
const server = http.createServer(this.callback());
// 监听指定的端口,并返回监听器对象
return server.listen(...args);
}
callback() {
const fn = compose(this.middleware)
const handleRequest = (req, res) => {
// 创建上下文对象
const ctx = this.createContext(req, res);
return this.handleRequest(ctx, fn);
};
// 返回请求处理函数
return handleRequest;
}
handleRequest(ctx, fnMiddleware) {
const handleResponse = () => respond(ctx);
+ const onerror = err => ctx.onerror(err)
+ return fnMiddleware(ctx).then(handleResponse).catch(onerror);
}
createContext(req, res) {
// 创建一个新的context对象,这个对象继承自this.context
const context = Object.create(this.context)
// 创建一个新的request对象,这个对象继承自this.request,并将这个对象赋值给context.request
const request = context.request = Object.create(this.request)
// 创建一个新的response对象,这个对象继承自this.response,并将这个对象赋值给context.response
const response = context.response = Object.create(this.response)
// 将原生的req和res对象赋值给context、request和response的req和res属性
context.req = request.req = req;
context.res = response.res = res;
// 返回创建的context对象
return context;
}
}
function respond(ctx) {
let res = ctx.res;
let body = ctx.body;
return res.end(body);
}
koa\lib\context.js
// 导入自定义的代理(delegate)模块
const delegate = require('./delegates')
// 创建一个空对象proto并将其导出
const proto = module.exports = {
+ onerror(err) {
+ const { res } = this;
+ this.status = 500;
+ res.end(err.message)
+ }
}
// 使用代理模块将proto对象的一些属性代理到'request'对象
delegate(proto, 'request')
.access('method') // 将'request'对象的'method'属性代理到proto对象
.access('query') // 将'request'对象的'query'属性代理到proto对象
.access('url') // 将'request'对象的'url'属性代理到proto对象
.access('path') // 将'request'对象的'path'属性代理到proto对象
.getter('header') // 将'request'对象的'header'属性的获取代理到proto对象
// 使用代理模块将proto对象的一些属性代理到'response'对象
delegate(proto, 'response')
.access('status') // 将'response'对象的'status'属性代理到proto对象
.access('message') // 将'response'对象的'message'属性代理到proto对象
.access('body') // 将'response'对象的'body'属性代理到proto对象
.method('set') // 将'response'对象的'set'方法代理到proto对象
const Koa = require('./koa');
const app = new Koa();
const middleware1 = async (ctx, next) => {
+ await next();
+ await next();
};
const middleware2 = async (ctx, next) => {
};
app.use(middleware1);
app.use(middleware2);
app.listen(3000, () => console.log('server is running at http://localhost:3000'));
koa\lib\koa-compose.js
function compose(middleware) {
return function (context) {
+ let index = -1;
function dispatch(i) {
+ if (i <= index) return Promise.reject(new Error('next() called multiple times'));
+ index = i;
let fn = middleware[i];
if (!fn) return Promise.resolve()
try {
return Promise.resolve(fn(context, () => dispatch(i + 1)));
} catch (err) {
return Promise.reject(err)
}
}
return dispatch(0);
};
}
module.exports = compose;
const Koa = require('./koa');
const app = new Koa();
const fs = require('fs');
const middleware1 = async (ctx, next) => {
+ ctx.body = 'hello world';
+ //ctx.body = Buffer.from('hello world');
+ //ctx.body = fs.createReadStream('./package.json');
+ //ctx.body = {name:'zhufeng'};
};
app.use(middleware1);
app.listen(3000, () => console.log('server is running at http://localhost:3000'));
koa\lib\application.js
// 导入http模块
const http = require('http');
// 导入自定义的context模块
const context = require('./context.js');
// 导入自定义的request模块
const request = require('./request');
// 导入自定义的response模块
const response = require('./response');
const compose = require('./koa-compose')
+const {Stream} = require('stream')
module.exports = class Application {
// 构造函数
constructor() {
// 创建一个新的context对象,这个对象继承自自定义的context模块
this.context = Object.create(context)
// 创建一个新的request对象,这个对象继承自自定义的request模块
this.request = Object.create(request)
// 创建一个新的response对象,这个对象继承自自定义的response模块
this.response = Object.create(response)
this.middleware = []
}
use(fn) {
// 设置中间件处理函数
this.middleware.push(fn)
return this
}
listen(...args) {
// 创建一个HTTP服务器实例,并使用回调函数作为请求处理程序
const server = http.createServer(this.callback());
// 监听指定的端口,并返回监听器对象
return server.listen(...args);
}
callback() {
const fn = compose(this.middleware)
const handleRequest = (req, res) => {
// 创建上下文对象
const ctx = this.createContext(req, res);
return this.handleRequest(ctx, fn);
};
// 返回请求处理函数
return handleRequest;
}
handleRequest(ctx, fnMiddleware) {
const handleResponse = () => respond(ctx);
const onerror = err => ctx.onerror(err)
return fnMiddleware(ctx).then(handleResponse).catch(onerror);
}
createContext(req, res) {
// 创建一个新的context对象,这个对象继承自this.context
const context = Object.create(this.context)
// 创建一个新的request对象,这个对象继承自this.request,并将这个对象赋值给context.request
const request = context.request = Object.create(this.request)
// 创建一个新的response对象,这个对象继承自this.response,并将这个对象赋值给context.response
const response = context.response = Object.create(this.response)
// 将原生的req和res对象赋值给context、request和response的req和res属性
context.req = request.req = req;
context.res = response.res = res;
// 返回创建的context对象
return context;
}
}
function respond(ctx) {
+ let {res,body} = ctx;
+ if (Buffer.isBuffer(body)) return res.end(body)
+ if (typeof body === 'string') return res.end(body)
+ if (body instanceof Stream) return body.pipe(res)
+ res.end(JSON.stringify(body))
}
const Koa = require('./koa');
const static = require('./koa-static');
const app = new Koa();
app.use(static(__dirname + '/static'));
app.listen(3000, () => console.log('server is running at http://localhost:3000'));
koa-static.js
const { access } = require('fs/promises')
const { createReadStream } = require('fs')
const path = require('path')
module.exports = function (root) {
return async function (ctx, next) {
await next()
if (!!ctx.body) return;
const filename = path.join(root, ctx.path);
if (await exists(filename)) {
ctx.body = createReadStream(filename)
}
}
}
async function exists(path) {
try {
await access(path)
return true
} catch (e) {
return false
}
}
const Koa = require('./koa');
const bodyparser = require('./koa-bodyparser');
const app = new Koa();
app.use(bodyparser());
app.use(async ctx => {
if (ctx.url === '/' && ctx.method === 'GET') {
// 显示表单页面
let html = `
<form method="POST" action="/">
<p>userName</p>
<input name="username"/><br/>
<button type="submit">submit</button>
</form>
`;
//设置响应头的类型text/html
ctx.set('Content-Type', 'text/html;charset=utf-8');
ctx.body = html;
} else if (ctx.url === '/' && ctx.method === 'POST') {
ctx.body = ctx.request.body;
} else {
ctx.body = '<h1>404!</h1>';
}
});
app.listen(3000, () => console.log('server is running at http://localhost:3000'));
koa-bodyparser.js
module.exports = function () {
return async function bodyParser(ctx, next) {
if (!!ctx.request.body)
return await next();
ctx.request.body = await parseBody(ctx.req);
await next();
};
};
function parseBody(req) {
return new Promise((resolve) => {
let buffers = [];
req.on('data', chunk => {
buffers.push(chunk);
});
req.on('end', () => {
resolve(Buffer.concat(buffers).toString());
});
});
}
server.js
const Koa = require('./koa');
const app = new Koa();
const Router = require('./koa-router.js');
const router = new Router();
router.get('/', async (ctx, next) => {
ctx.body = 'Home';
await next();
});
router.get('/user', async (ctx, next) => {
ctx.body = 'User';
await next();
});
app.use(router.routes());
app.listen(3000, () => console.log('server is running at http://localhost:3000'));
koa-router.js
const methods = ['head', 'options', 'get', 'put', 'patch', 'post', 'delete'];
function Router() {
this.stack = [];
}
for (const method of methods) {
Router.prototype[method] = function (path, middleware) {
this.stack.push({ path, method, middleware });
};
}
Router.prototype.routes = function () {
return async (ctx, next) => {
const matched = this.stack.find(layer => {
return layer.path === ctx.path && layer.method === ctx.method.toLowerCase();
});
if (!matched) return next();
await matched.middleware(ctx, next);
}
};
module.exports = Router;