Koa principle implementation
Zero, Koa source directory structure
. ├ ─ ─ the History. The md ├ ─ ─ LICENSE ├ ─ ─ the Readme. Md ├ ─ ─ dist │ └ ─ ─ koa. MJS ├ ─ ─ lib │ ├ ─ ─ application. Js # at the core of module │ ├ ─ ─ the context, js ├─ ├─ ├─ download.json # ├─ ├─ download.json # ├─ ├─ ├─ download.jsonCopy the code
1. Basic structure
Use:
const Koa = require('./koa')
const app = new Koa()
app.listen(3000)
Copy the code
application.js
module.exports = class Application {
listen(. args) {
const server = http.createServer((req, res) = > {
res.end('My Koa')})returnserver.listen(... args) } }Copy the code
Second, realize the middleware function
-
Koa will combine all the middleware into one big Promise
-
When this Promise completes, the current CTx.body is used for the result response
-
Next must be preceded by await or return next, otherwise the execution order will not be as expected
-
If it is all synchronous, it doesn’t matter whether we await it or not
-
I don’t know if there will be asynchronous logic, so I recommend writing with “await next”
Collecting middleware
Usage:
const Koa = require('./koa')
const app = new Koa()
app.use((ctx, next) = > {
ctx.body = 'foo'
})
app.use((ctx, next) = > {
ctx.body = 'Koa'
})
app.listen(3000)
Copy the code
application.js
const http = require("http");
module.exports = class Application {
constructor() {
this.middleware = [];
}
use(fn) {
if (typeoffn ! = ="function") {
throw new TypeError("middleware must be a function!");
}
this.middleware.push(fn);
}
listen(. args) {
const server = http.createServer((req, res) = > {
res.end("My Koa");
});
return server.listen(...args);
}
};
Copy the code
const http = require("http");
class Application {
constructor() {
this.middleware = []; // Save the middleware functions added by the user
}
listen(. args) {
const server = http.createServer(this.callback()); server.listen(... args); }use(fn) {
this.middleware.push(fn);
}
// Asynchronous recursive traversal calls middleware handler functions
compose(middleware) {
return function () {
const dispatch = (index) = > {
if (index >= middleware.length) return Promise.resolve();
const fn = middleware[index];
return Promise.resolve(
// TODO:Context object
fn({}, () = > dispatch(index + 1)) // This is the next function
);
};
// Returns the first middleware handler
return dispatch(0);
};
}
callback() {
const fnMiddleware = this.compose(this.middleware);
const handleRequest = (req, res) = > {
fnMiddleware()
.then(() = > {
console.log("end");
res.end("My Koa");
})
.catch((err) = > {
res.end(err.message);
});
};
returnhandleRequest; }}module.exports = Application;
Copy the code
Test the middleware execution process
/** * - Koa middleware features * + collect when use * + call when request comes in */
const Koa = require("./koa");
const app = new Koa();
const one = (ctx, next) = > {
console.log(">> one");
next();
console.log("<< one");
};
const two = (ctx, next) = > {
console.log(">> two");
next();
console.log("<< two");
};
const three = (ctx, next) = > {
console.log(">> three");
next();
console.log("<< three");
};
app.use(one);
app.use(two);
app.use(three);
// console.log(app.middleware)
app.listen(3000);
Copy the code
3. Handle context objects
Initialize the context object
Context How context objects can be used:
/** * Koa Context */
const Koa = require("./koa");
const app = new Koa();
app.use(async (ctx, next) => {
// Koa Context encapsulates node's request and response objects into a single object, providing many useful methods for writing Web applications and apis.
// Each request creates a Context and is referenced as a parameter in the middleware
console.log(ctx); / / the Context object
console.log(ctx.req.url);
console.log(ctx.req.method);
console.log(ctx.request.req.url);
console.log(ctx.request.req.method);
console.log(ctx.req); // Node request object
console.log(ctx.res); // Node's response object
console.log(ctx.req.url);
console.log(ctx.request); // The request object encapsulated in Koa
console.log(ctx.request.header); // Get the request header object
console.log(ctx.request.method); // Get the request method
console.log(ctx.request.url); // Get the request path
console.log(ctx.request.path); // Get the request path that does not contain the query string
console.log(ctx.request.query); // Get the query string in the request path
/ / Request alias
// See https://koa.bootcss.com/#request- for a complete list
console.log(ctx.header);
console.log(ctx.method);
console.log(ctx.url);
console.log(ctx.path);
console.log(ctx.query);
// The response object encapsulated in Koa
console.log(ctx.response);
ctx.response.status = 200;
ctx.response.message = "Success";
ctx.response.type = "plain";
ctx.response.body = "Hello Koa";
/ / Response alias
ctx.status = 200;
ctx.message = "Success";
ctx.type = "plain";
ctx.body = "Hello Koa";
});
app.listen(3000);
Copy the code
context.js
const context = {
}
module.exports = context
Copy the code
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;
Copy the code
response.js
const response = {
set status(value) {
this.res.statusCode = value; }};module.exports = response;
Copy the code
application.js
const http = require("http");
const context = require("./context");
const request = require("./request");
const response = require("./response");
class Application {
constructor() {
this.middleware = []; // Save the middleware functions added by the user
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);
}
// Asynchronous recursive traversal calls middleware handler functions
compose(middleware) {
return function (context) {
const dispatch = (index) = > {
if (index >= middleware.length) return Promise.resolve();
const fn = middleware[index];
return Promise.resolve(
// TODO:Context object
fn(context, () = > dispatch(index + 1)) // This is the next function
);
};
// Returns the first middleware handler
return dispatch(0);
};
}
// Construct the context object
createContext(req, res) {
// An instance can handle multiple requests, and different requests should have different context objects. To avoid cross-contamination of data during requests, a new copy of this data is made here
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) = > {
// Each request creates a separate Context object that does not pollute each other
const context = this.createContext(req, res);
fnMiddleware(context)
.then(() = > {
res.end("My Koa");
})
.catch((err) = > {
res.end(err.message);
});
};
returnhandleRequest; }}module.exports = Application;
Copy the code
Handles aliases in context objects
context.js
const context = {
// get method () {
// return this.request.method
// },
// get url () {
// return this.request.url
// }
};
defineProperty("request"."method");
defineProperty("request"."url");
function defineProperty(target, name) {
context.__defineGetter__(name, function () {
return this[target][name];
});
// Object.defineProperty(context, name, {
// get () {
// return this[target][name]
/ /}
// })
}
module.exports = context;
Copy the code
4. Handle CTx. body
Save body data
response.js
const response = {
set status(value) {
this.res.statusCode = value;
},
_body: "".// It is used to store data
get body() {
return this._body;
},
set body(value) {
this._body = value; }};module.exports = response;
Copy the code
Sending body data
context.js
const context = {
// get method () {
// return this.request.method
// },
// get url () {
// return this.request.url
// }
};
defineProperty("request"."method");
defineProperty("request"."url");
defineProperty("response"."body");
function defineProperty(target, name) {
// context.__defineGetter__(name, function () {
// return this[target][name]
// })
Object.defineProperty(context, name, {
get() {
return this[target][name];
},
set(value) {
this[target][name] = value; }}); }module.exports = context;
Copy the code
application.js
callback() {
const fnMiddleware = this.compose(this.middleware);
const handleRequest = (req, res) = > {
// Each request creates a separate Context object that does not pollute each other
const context = this.createContext(req, res);
fnMiddleware(context)
.then(() = > {
res.end(context.body)
// res.end('My Koa')
})
.catch((err) = > {
res.end(err.message);
});
};
return handleRequest;
}
Copy the code
More flexible body data
callback() {
const fnMiddleware = this.compose(this.middleware);
const handleRequest = (req, res) = > {
// Each request creates a separate Context object that does not pollute each other
const context = this.createContext(req, res);
fnMiddleware(context)
.then(() = > {
respond(context);
// res.end(context.body)
// res.end('My Koa')
})
.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") {
const jsonStr = JSON.stringify(body);
returnres.end(jsonStr); }}Copy the code