简单实现Koa.js
入口文件application.js
const http = require('http')
const { Stream } = require('stream')
const context = require('./context')
const request = require('./request')
const response = require('./response')
class Application {
constructor() {
this.middleware = [] // 保存用户添加的中间件函数
this.context = Object.create(context)
this.request = Object.create(request)
this.response = Object.create(response)
}
listen(...args) {
const server = http.createServer(this.callback())
server.listen(...args)
}
use(fn) {
this.middleware.push(fn)
}
// 异步递归遍历调用中间件处理函数,实现洋葱模型
compose(middleware) {
return function (context) {
const dispatch = index => {
if (index >= middleware.length) return Promise.resolve()
const fn = middleware[index]
return Promise.resolve(
fn(context, () => dispatch(index + 1))
)
}
return dispatch(0)
}
}
// 构造上下文对象
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
}
// 服务器启动后的回调函数
callback() {
const fnMiddleware = this.compose(this.middleware)
const handleRequest = (req, res) => {
const context = this.createContext(req, res)
fnMiddleware(context).then(() => {
// 处理body的不同格式
respond(context)
// res.end(context.body)
}).catch(err => {
res.end(err.message)
})
}
return handleRequest
}
}
function respond(ctx) {
const body = ctx.body
const res = ctx.res
if (body === null) {
res.statusCode = 204
return res.end()
}
if (typeof body === 'string') return res.end(body)
if (Buffer.isBuffer(body)) return res.end(body)
if (body instanceof Stream) return body.pipe(ctx.res)
if (typeof body === 'number') return res.end(body + '')
if (typeof body === 'object') {
console.log(2)
const jsonStr = JSON.stringify(body)
return res.end(jsonStr)
}
}
module.exports = Application
context.js(简单实现,不完整)
const context = {
// get method () {
// return this.request.method
// }
}
defineProperty('request', 'method')
defineProperty('request', 'url')
defineProperty('response', 'body')
// 这里将每个方法的get set封装成一个函数
function defineProperty (target, name) {
Object.defineProperty(context, name, {
get () {
return this[target][name]
},
set (value) {
this[target][name] = value
}
})
// context.__defineGetter__(name, function () {
// return this[target][name]
// })
}
module.exports = context
request.js(简单实现,不完整)
const url = require('url')
const request = {
get method () {
return this.req.method
},
get header () {
return this.req.headers
},
get url () {
return this.req.url
},
get path () {
return url.parse(this.req.url).pathname
},
get query () {
return url.parse(this.req.url, true).query
}
}
module.exports = request
response.js(简单实现,不完整)
const response = {
set status (value) {
this.res.statusCode = value
}
}
module.exports = response
直接使用
const Koa = require('./koa')
const app = new Koa()
const one = (ctx, next) => {
console.log('>> one')
ctx.body = 'My koa1'
next()
ctx.body = {name: 'xp'}
console.log('<< one')
}
const two = (ctx, next) => {
console.log('>> two')
ctx.body = 'My koa2'
next()
console.log('<< two')
}
const three = (ctx, next) => {
console.log('>> three')
next()
console.log('<< three')
}
app.use(one)
app.use(two)
app.use(three)
app.listen(3000, () => {
console.log('Server is running at 8000')
})