preface
Both Koa and Express are next-generation Web development frameworks based on the Node.js platform, built by the same people behind Express,
Express and Koa:
- Express source code is written in ES5, koA source code based on ES6
- Express has a lot of features built in; The KOA internal core is very small (we can extend it with extended plug-ins)
- Both Express and Koa are free to implement MVC functionality on their own
- Express handles asynchrony in the form of callback functions, and functions handle async+await
Koa startup
Initialize the project
npm init -y
The installation of Koa
npm install koa
Use method of Koa
- Use is a piece of middleware that generates a context CTX for each execution
- Res.en () in node can be implemented using ctx.body
- Context CTX contains the main parts
- App Current application instance,
- Req and res correspond to req and res in native node
- Koa’s own encapsulated Request and Response is a layer of abstraction and extension to the native REQ and RES, with more functionality
const Koa = require("koa"); const app = new Koa(); app.use((ctx) => ctx.body = "hello1111"; //ctx.body is equivalent to the res.en() function in the node server to write data console.log(CTX); // Print CTX to view properties}); app.listen(3000, function () { console.log("server start 3000"); }); // Listen on the same port number as the HTTP LISTEN method on our nodeCopy the code
{request: {method: 'GET', URL: '/', header: {host: 'localhost:3000', connection: 'keep-alive', 'cache-control': 'max-age=0', 'sec-ch-ua': '" Not A; Brand"; v="99", "Chromium"; v="90", "Google Chrome"; v="90"', 'sec-ch-ua-mobile': '? 0', 'upgrade-insecure ': '1', 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.85 Safari/537.36, Accept: 'text/html,application/xhtml+xml,application/xml; Q = 0.9, image/avif, image/webp image/apng, _ / _; Q = 0.8, application/signed - exchange; v=b3; Q = 0.9 ', 'the SEC - fetch - site' : 'none', 'the SEC - fetch - mode' : 'navigate', 'the SEC - fetch - user' : '? 1', 'sec-fetch-dest': 'document', 'accept-encoding': 'gzip, deflate, br', 'accept-language': 'zh-CN,zh; Q = 0.9 ', cookies: \ _uab_collina = 161916550781026855009784; zoe=27' } }, response: { status: 404, message: 'Not Found', header: [Object: null prototype] {} }, app: { subdomainOffset: 2, proxy: false, env: 'development' }, originalUrl: '/', req: '<original node req>', res: '<original node res>', socket: '<original node socket>' }Copy the code
Without further saying, let’s start with the context of Koa’s implementation
Implementation of Koa context
The directory to create
Reference koA source code under the file structure of lib in turn new corresponding text
package.json
Set entry file
{
"main":"lib/application.js"
}
Copy the code
Context. js, request.js, and Response. js are created in sequence
const response = {};
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() {
}
}
module.exports = Application;
Copy the code
Listen is implemented when KOA starts the server
Ideas:
- Receive two parameters, the port number and the callback
- Internally encapsulate the Node’s LISTEN and pass in the instance’s LISTEN parameter
Implementation:
//handleRequest to implement listen(... args) { const server = http.createServer(this.handleRequest); server.listen(... args); }Copy the code
- Invocation: Same as the Koa framework
app.listen(3000, function () {
console.log("server start 3000");
});
Copy the code
Use implementation
The use of the role
Each execution produces a context CTX
Ideas:
- Use The function to save user writes
- When the service starts, the use saved by the user is triggered, creating the context CTX
- The CTX in the use of each instance and request should be independent of each other
- CTX should include node’s native REq and RES as well as CTX’s own extended request and response
Implementation:
- application.js
Constructor () {this.context = object.create (context); this.context = object.create (context); this.context = object.create (context); This.request = object.create (request); this.response = Object.create(response); } use(fn) {// save the user-written function this.fn = fn; } // Implement the context createContext(req, res) {let CTX = object.create (this.context); // Object. Create is the middle layer wrapping (isolation), which ensures that the context generated by each request is independent and the space is its own context. Let request = object.create (this.request); let request = object.create (this.request); let response = Object.create(this.response); Request = request; // CTX own extended request and response ctx.request = request; ctx.response = response; // Native request ctx.req = ctx.request.req = req; // The default context contains the native req, and your own requst needs to be able to fetch the native req ctx.res = res; // The default context contains the native req, and your own requst needs to be able to fetch the native req ctx.res = res; // The default context contains the native res return CTX; } handleRequest = (req, res) => {// Let CTX = this.createcontext (req, res); // Use this.fn(CTX); };Copy the code
- request.js
Request to implement contextual CTX
const url = require("url"); Const request = {get url() {// In the application, request is called by context CTX, so this refers to context this.req.url; Query get path() {return url.parse(this.req.url).pathName; }, get query() { return url.parse(this.req.url).query; }}; module.exports = request;Copy the code
test
call
- 1.server.js
const Koa = require("./koa"); Const app = new koa (); const app = new koa (); App.use ((CTX) => {console.log(ctX.req.url) console.log(ctX.request.req.url) console.log(ctX.request.url)// Request in app The Object. Create package searches for the URL twice CTX. Request. __proto__. __proto__ console. The log (CTX) url) console. The log (' -- -- -- -- -- -- -- -- the console end ')}); app.listen(3000, function () { console.log("server start 3000"); }); // Listen on the same port number as the HTTP LISTEN method on our nodeCopy the code
The test results
Console. log(ctx.url) was not printed successfully
CTX. Url
- context.js
- The same idea that we used in request.js is that what we’re referring to here is the object that we’re finally calling which is our context CTX
- The URL is going to fetch the URL property of our request on this
const context = { get url() { return this.request["url"]; }}; module.exports = context;Copy the code
- The printout of console.log(ctx.url)
-
But the current context implementation has to copy and paste if it wants to get other paths or queries. Can’t endure. So refer to koA source code in the context to obtain the implementation
1.The core
:defineGetter MDN has been deprecated and may not be supported in the future. However, the implementation of KOA is mainly studied, and this interface is not discussed in depth here
- Make adjustments after referring to the source code
const context = { } function defienGetter(target,key) { context.__defineGetter__(key, Function () {// tips: function () cannot be an arrow function, otherwise this would point to the context and become an empty object. Return this[target][key]; }); } defienGetter("request", "url"); defienGetter("request", "path"); module.exports = context;Copy the code
- Test the console result for url, path
Implementation of Koa response body
Use of ctx.body in native KOA
- server.js
app.use((ctx) => {
ctx.body = "hello ZOE";
//ctx.response.body ="hello ZOE"
});
Copy the code
Ctx. body and ctx.Response. body can be written to the page
CTX. Body and CTX. The response body
What is the relationship between the two?
- Test: use with the following code & View console
Ctx.response. body = "hello Zoe implemented by ctx.response.body "; console.log("ctx.body :", ctx.body, ctx.response.body === ctx.body);Copy the code
- Test result: ctx.body and ctx.response.body point to the same thing
- Conclusion: the set of ctx.body value should be the set that represents ctx.Response. Body
Implementation of CTx. body in KOA
The response of the processing
Ctx. response points to the native RES
Go back to the custom application.js file and add response to the native res in the createContext function
2.ctx. response = response; // The default context contains the native res, so we can get the native res from this. Res in our response object, and ctx.res = ctx.response.res = res; // The default context contains the native res, so we can get the native res from this.Copy the code
Ctx.response. body set and get
Response.js: The value in set and get should also point to the same value. The response object sets an additional _body variable, and both set and get operate on _body
const response = { _body: undefined, get body() { return this._body; }, set body(value) { this._body = value; }}; module.exports = response;Copy the code
server.js:
App.use ((CTX) => {ctx.response.body = "Zoe custom KOa responsebody test "; console.log(ctx.response.body); });Copy the code
Test results:
However, ctx.body is not the same as res.use, so the page does not show what our body wrote
Ctx.response. body implements writing to the page
application.js
- Add res.end(ctx.response.body) to handleRequest;
server.js:
app.use((ctx) => {
ctx.response.body = " hello Zoe";
});
Copy the code
- Page output
Set and get implementations of ctx.body
- get
-
Req is a proxy for ctx.request. Req; Similarly, ctx.body can also represent ctx.Response.body
-
Add a defiant getter (“response”, “body”); When fetching ctx.body, fetching response. Body under the current CTX pointed to by this
-
- set
- The proxy is actually set to ctx.response.body
Context.js adds code to the original context
const context = {
function defienGetter(target, key) {
context.__defineGetter__(key, function () {
return this[target][key];
});
}
function defineSetter(target, key) {//proxy ,defineProperty
context.__defineSetter__(key, function (value) {
return (this[target][key] = value);
});
}
defienGetter("response", "body");
defineSetter("response", "body");
module.exports = context;
Copy the code
- test
sever.js
app.use((ctx) => { ctx.body = " hello Zoe"; Console. log(' ${cxx.body} from the test 'of cxx.body); });Copy the code
- The test results
Ctx. body boundary handling
- 404 handling when ctx.body is empty Otherwise call res.end and write body
404
- application.js
handleRequest = (req, res) => { let ctx = this.createContext(req, res); res.statusCode = 404; If 404 has a value, call set body, and change the statusCode to 200 this.fn(CTX); if (ctx.body) { res.end(ctx.body); } else { res.end("Not Found"); }};Copy the code
- response.js
Set body(value) {// if the user called ctx.body=' CXX 'then set the response statusCode 200 this.res.statuscode = 200; this._body = value; },Copy the code
- App. use does not do ctx.body assignment test results
Ctx. body supports multiple types of data
- Added ctx.body Type judgment
- The string or buffer uses the original logic
- Numbers are turned into strings
- Object handling json.stringify ()
application.js
handleRequest = (req, res) => {
let ctx = this.createContext(req, res);
res.statusCode = 404;
this.fn(ctx);
let _body = ctx.body;
if (_body) {
if (isString(_body) || Buffer.isBuffer(_body)) {
return res.end(_body);
} else if (isNumber(_body)) {
return res.end(_body + "");
} else if (isObject(_body)) {
return res.end(JSON.stringify(_body));
}
} else {
res.end("Not Found");
}
Copy the code
Data type determination (using your own written utility function implementation can refer to) type
Ctx.response. body = {name: “ZOE”};
Result: I want to steal a lazy no graph (interested. You can write it like I did.)
Support for multiple uses
For now: the use function supports only one call and now becomes an array to hold user-written functions
MiddleWares cache arrays
Initialize the
constructor() {
this.middleWares = [];
}
Copy the code
Use to rewrite
use(middleWare) {
// this.fn = fn;
this.middleWares.push(middleWare);
}
Copy the code
HandleRequest adjustment
You need to write to the body of middleWares in sequence instead of writing directly to the body
- The this.compose handler executes the functions in the middleWares array one by one
- The callback handles each function’s own ctx.body write
Implementation approach
- This.com pose returns a big promise
- This promise wraps each use’s handler into a small internal promise
- The inner small promise’s THEN handles its own body writing
- Internal Promise execution order: the previous promise waits for the returned promise to finish (recursion)
The implementation code
handleRequest
This.com pose (CTX). Then (() = > {/ / parcel _body original writing logic code}Copy the code
Compose implementation
-
MiddleWares. Length ===0 directly returns a promise.resolve
-
Promise. Resolve (handle current use save function, cb callback next use save function)
MiddleWares [0(CTX, this.middlewares [1]
-
Error handling for multiple calls to next in the same use
- I cache the index of each function that is executed. How about the next dispacth that is executed? Index === I indicates the same use of two next, reject exceptions
Compose (CTX) {for example, compose(CTX) : compose(CTX); let i = -1; const dispatch = () => { if (index === i) return Promise.reject("multiple call next()"); If (this.middlewares. Length === index) return promise.resolve (); i = index; return Promise.resolve(this.middleWares[index++](ctx, dispatch)); }; return dispatch(0); }Copy the code
Test results: write a lonely, do not throw exceptions, because I put in the above way to dispatch the outer, execute to the second next next before when I was modified as a result, the index of = = = I will never meet the conditions
/node_modules/koa-compose/index.js
- I only comes from the dispatch input argument
Adjustment results:
Compose (CTX) {// compose(CTX) {// compose(CTX) {// compose(CTX) {// compose(CTX) {// compose(CTX) {// compose(CTX) {// compose(CTX) {// compose(CTX) {// compose(CTX) {// compose(CTX) {// compose(CTX) {// compose(CTX) {// compose(CTX) <= index) return Promise.reject(new Error("next() called multiple times")); index = i; if (this.middleWares.length === i) return Promise.resolve(); Return promise.resolve (this.middlewares [I]) (CTX,) {return promise.resolve (this.middlewares [I]) (CTX,) () => dispatch(i + 1))); }; return dispatch(0); }Copy the code
Testing:
app.use(async (ctx, next) => {
console.log(1);
await next();
await next();
console.log(2);
});
app.use(async (ctx, next) => {
console.log(3);
await new Promise((resolve, reject) => {
setTimeout(() => {
console.log("zoe");
resolve();
}, 1000);
});
next();
console.log(4);
});
app.use((ctx, next) => {
console.log(5);
next();
console.log(6);
});
app.listen(3000, () => {
console.log("server start 3000");
});
Copy the code
Test results:Additional explanation
: Why does dispatch return promise.resolve?
If “next” is written without awiat this.compose().then, it will not work properly
The elegant handling of err
Koa error handling
Support for custom error message handling
app.on('error',function(err){
console.log({err})
})
Copy the code
implementation
- Application inherits EventEmitter (remember super)
- This.emit (“error”, err); this.emit(“error”, err); Error events subscribed by users
const EventEmitter = require("events"); class Application extends EventEmitter { constructor() { super(); } handleRequest(){this.pose (CTX).then(() => {//ctx.body }). Catch ((err) => {this.emit("error", err); }); }}Copy the code
Test results:
summary
To be continued….
Finally, if you find this article helpful, please click "Like" three times. Thank you very much