Koa (KOAJS) Source code interpretation (master must SEE)

Source: Internet
Author: User
Tags generator instance method require

Koa is a Web development framework similar to Express, and the founders are TJ. The main feature of Koa is that the generator function of ES6 is used to redesign the architecture. The KOA principle and internal structure are much like Express, but the syntax and internal structure are upgraded.

Creating KOA Applications

Creating a KOA is very simple:

var koa = require (' KOA ');

var app = Koa ();

App.listen (3000);
Or you can purple:

var koa = require (' KOA ');
var http = require (' http ');

var app = Koa ();

Http.createserver (App.callback ()). Listen (4000);
Both of these methods are equivalent within the KOA, and in the application module, listen invokes its own callback:

The realization of Listen
App.listen = function () {
Debug (' Listen ');
var server = Http.createserver (This.callback ());
return server.listen.apply (server, arguments);
};
The function returned by callback serves as a callback for the server:

App.callback = function () {

/**
* Omitted code
**/

return function (req, res) {
Res.statuscode = 404;
var ctx = Self.createcontext (req, res);
Onfinished (res, ctx.onerror);
Fn.call (CTX). Then (function () {
Respond.call (CTX);
). catch (Ctx.onerror);
}
};
Callback also turns multiple middleware into a FN, which is easy to invoke when building a server function. The status code defaults to 404, that is, no middleware modification is 404.

Each request creates a context object through Createcontext, whose parameters are Node's request object and the Response object, respectively:

App.createcontext = function (req, res) {


var context = Object.create (This.context);


var request = Context.request = Object.create (this.request);


var 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.onerror = Context.onerror.bind (context);


Context.originalurl = Request.originalurl = Req.url;


Context.cookies = new Cookies (req, res, {


Keys:this.keys,


Secure:request.secure


});


Context.accept = Request.accept = accepts (req);


Context.state = {};


return context;


};


For the received parameters, koa injects the parameters into its own request object and response object before returning the contextual context, and Ctx.request and Ctx.response return the corresponding objects for KOA, Ctx.req, and CTX. RES returns the corresponding object of Node, and also registers the app on the Context/respose/request object for easy invocation in its own module:

var app = Application.prototype;

Module.exports = Application;

function application () {
if (!) ( This is instanceof application) return to new application;
this.env = Process.env.NODE_ENV | |  ' Development '; Environment variables
This.subdomainoffset = 2; Child domain Offset
This.middleware = []; Middleware arrays
This.proxy = false; Whether to trust header field Proxy
This.context = object.create (context); KOA Context (This)
This.request = object.create (request); KOA's Request object
This.response = object.create (response); The Reponse object of KOA
}
Context: Contexts

The context object is extended by the KOA context module, adding attributes such as state, Cookie, req, res, and so on.

Onfinished is a third-party function that listens to the HTTP response End event and executes the callback. If the Context.onerror method is found, this is the KOA default error-handling function, which handles the end of the exception caused by the error. The wrong processing is monitored in the callback:

Callback
if (!this.listeners (' error '). Length) This.on (' Error ', this.onerror);
The KOA itself does not define an event-handling mechanism, and its event-handling mechanism inherits from Node's events:

var emitter = require (' Events '). Eventemitter;
Object.setprototypeof (Application.prototype, Emitter.prototype);
The default error distribution is in the context module:

Onerror:function (Err) {
Some code
This.app.emit (' Error ', err, this);
Some code
}
In addition, in the context module, some methods and properties of the request object and the response object are delegated to a context object:

//response Delegate
Delegate (proto, ' response ')
  method (' attachment ')
  (' append ')
  Access (' status ')
 . Access (' body ')
 . Getter (' headersent ')
 . Getter (' writable ');
  .....
 
 //request delegate
  delegate (proto, ' request ')
 . Method (' Acceptslanguages ')
& nbsp. Method (' get ')
  (' is ')
 . Access (' QueryString ')
 . Access (' URL ')
 . Getter (' origin ')
  getter (' href ')
 . Getter (' subdomains ')
 . Getter (' protocol ')
  Getter (' host ')
  ....
The methods defined in the Response module and Request module are delegated to the context object through a Third-party module delegate, so some of the following are equivalent:

In each request, this is used to refer to the context contexts created by this request (CTX)
This.body ==> This.response.body
This.status ==> This.response.status
This.href ==> This.request.href
This.host ==> This.request.host
.....
In the Createcontext method, the key attribute is also defined for the context

Context.state = {}
This attribute can be shared by various middleware to pass data between the middleware, which is also recommended by KOA:

This.state.user = yield user.find (ID);
Middleware

Middleware is a function of processing HTTP requests, which are processed by middleware for each request. In KOA, the middleware is registered by using use and must be a generator function (This.experimental not turned on):

App.use (function* F1 (next) {
Console.log (' F1:pre Next ');
Yield next;
This.body = ' Hello Koa ';
Console.log (' F1:post Next ');
});

App.use (function* f2 (next) {
Console.log (' F2:pre Next ');
Console.log (' F2:post Next ');
});
The output is as follows:

F1:pre Next
F2:pre Next
F2:post Next
F1:post Next
Unlike Express middleware sequential execution, in KOA, middleware is called "onion model" or cascade (cascading) structure, that is, it belongs to the layer call, the first middleware calls the second middleware, the second calls the third, and so on. Upstream middleware must wait for downstream middleware to return results before it continues to execute.

KOA is not limited to the number of middleware and can register multiple middleware at will. However, if there are multiple middleware, as long as there is a middleware missing yield Next statement, the following middleware will not execute:

App.use (function * (next) {
Console.log (' >> one ');
Yield next;
Console.log (' << one ');
});

App.use (function * (next) {
Console.log (' >> two ');
This.body = ' two ';
Console.log (' << two ');
});

App.use (function * (next) {
Console.log (' >> three ');
Yield next;
Console.log (' << three ');
});
In the code above, because the second middleware is less yield next, the third middleware does not execute.

If you want to skip a middleware, you can simply write the return yield next on the first line of the middleware statement:

App.use (function* (next) {
if (skip) return yield next;
})
In KOA, the only parameter for the middleware is next. If you want to pass in other parameters, you must write a separate function that returns the generator function.

This.experimental is to determine whether to support ES7, the middleware can pass in the async function after this property is opened:

App.use (Async function (next) {
await next;
This.body = body;
});
But KOA default is not supported ES7, if you want to support, you need to explicitly specify This.experimental = True in code

App.use = function (fn) {
if (!this.experimental) {
ES7 async functions are not allowed,
So we have to make sure the ' FN ' is a generator function
ASSERT (FN && ' generatorfunction ' = Fn.constructor.name, ' App.use () requires a generator function ');
}
Debug (' Use%s ', Fn._name | | fn.name | | '-');
This.middleware.push (FN);
return this;
};
To output an error message in callback:

App.callback = function () {
if (this.experimental) {
Console.error (' experimental ES7 Async Function support is deprecated. Please look in into Koa v2 as the middleware signature has changed. ')
}
var fn = This.experimental
? Compose_es7 (This.middleware)
: Co.wrap (Compose (this.middleware));
Omitted
};
Compose's full name is Koa-compose, and its role is to cascade disparate middleware together:

There are 3 middleware
This.middlewares = [function *m1 () {}, Function *m2 () {}, Function *m3 () {}];

Converting via Compose
var middleware = compose (this.middlewares);

After the conversion, the middleware is like this.
function * () {
Yield *m1 (M2 (M3 (noop ()))
}
From the implementation of the use used above, it is possible to make a chained call because each invocation of the will returns this.

App.use (function *m1 () {}). Use (function *m2 () {}). Use (function *m3 () {})
Routing processing

The KOA itself has the request object and the response object to handle the route, and a simple routing process is as follows:

App.use (function* () {
if (This.path = = '/') {
This.body = ' Hello Koa ';
else if (This.path = = '/get ') {
This.body = ' get ';
} else {
this.body = ' 404 ';
}
});
You can also get the request header by This.request.headers. Because the response header is not set, the default response header type is text/plain and can be set by Response.set:

App.use (function* (next) {
if (This.path = = '/') {
This.body = ' Hello Koa ';
else if (This.path = = '/get ') {
This.body = ' get ';
} else {
Yield next;
}
});

App.use (function* () {
This.response.set (' Content-type ', ' application/json;charset=utf-8 ');
return this.body = {message: ' OK ', statuscode:200};
});
In the code above, each middleware is responsible for part of the path, and if the path does not conform, it is passed to the next middleware.

Complex routing requires installation of Koa-router:

var app = require (' KOA ') ();
var Router = require (' Koa-router ');

var myrouter = new Router ();

Myrouter.get ('/', function * (next) {
This.response.body = ' Hello world! ';
});

App.use (Myrouter.routes ());

App.listen (4000);
Because KOA uses generator as a middleware, myrouter.routes () returns a generator and is equivalent to Myrouter.middleware:

Router.prototype.routes = Router.prototype.middleware = function () {
var router = this;

var dispatch = function *dispatch (next) {
Code
}
Omitted
return dispatch;
};
Koa-router provides a series of methods corresponding to the HTTP verb:

Router.get ()
Router.post ()
Router.put ()
Router.del ()
Router.patch ()
Del is the alias for delete:

Alias for ' router.delete () ' Because the delete is a reserved word
Router.prototype.del = router.prototype[' delete '];
These verb methods can accept two parameters, the first is the path pattern, and the second is the corresponding controller method (middleware), which defines the server behavior when the user requests the path.

Note that when a path matches, the query string is not taken into account. For example,/index?param=xyz matching path/index.

More details about Koa-router and listen to let's.

Chained call

In KOA, the use of middleware is to support link invocation. Same

For requests for multiple paths, Koa-router also supports chained calls:

Router
. Get ('/', function * (next) {
This.body = ' Hello world! ';
})
. Post ('/users ', function * (next) {
// ...
})
. put ('/users/:id ', function * (next) {
// ...
})
. del ('/users/:id ', function * (next) {
// ...
});
Because each verb method returns the router itself:

Methods.foreach (function (method) {
Router.prototype[method] = function (name, path, middleware) {
VAR middleware;

if (typeof Path = = ' String ' | | path instanceof REGEXP) {
Middleware = Array.prototype.slice.call (arguments, 2);
} else {
Middleware = Array.prototype.slice.call (arguments, 1);
Path = name;
name = NULL;
}

This.register (Path, [method], middleware, {
Name:name
});

return this;
};
});
Routing implementation

Node itself provides dozens of HTTP request verbs, and koa-router only implements some of the most commonly used:

function Router (opts) {
if (!) ( This instanceof Router)) {
return new Router (opts);
}

this.opts = OPTs | | {};
This.methods = This.opts.methods | | [
' Head ',
' OPTIONS ',
' Get ',
' Put ',
' PATCH ',
' POST ',
' DELETE '
];
Omitted
};
The implementation of these request verbs is supported by the Third-party module methods, which is then registered within the Koa-router:

Methods.foreach (function (method) {
Router.prototype[method] = function (name, path, middleware) {
See the code above
This.register (Path, [method], middleware, {
Name:name
});

return this;
};
});
This.register accepts the request path, method, and middleware as parameters to return the already registered route:

Router.prototype.register = function (path, methods, middleware, opts) {
opts = opts | | {};

var stack = This.stack;
Create route
var route = new Layer (path, methods, middleware, {
Layer is a specific implementation, including matching, middleware processing, etc.
End:opts.end = = False? Opts.end:true,
Name:opts.name,
sensitive:opts.sensitive | | this.opts.sensitive | | False
strict:opts.strict | | this.opts.strict | | False
Prefix:opts.prefix | | This.opts.prefix | | "",
});
Other code
return route;
};
It is known from the above code that Koa-router supports middleware to handle routing:

Myrouter.use (function* (next) {
Console.log (' aaaaaa ');
Yield next;
});

Myrouter.use (function* (next) {
Console.log (' bbbbbb ');
Yield next;
});

Myrouter.get ('/', function * (next) {
Console.log (' CCCCCCC ');
This.response.body = ' Hello world! ';
});

Myrouter.get ('/test ', function * (next) {
Console.log (' dddddd ');
this.response.body = ' Test router middleware ';
});
By Router.use to register the middleware, the middleware is executed sequentially and is invoked before the callback of the matching route:

Router Middleware

does not call for mismatched routes. Also, if the registered route is less yield next, then the subsequent middleware and the callback of the matching route are not invoked, and the routing middleware supports the link invocation:

Router.prototype.use = function () {
var router = this;
Other code
return this;
};
Middleware also supports specific routes and array routing:

Session middleware'll run before authorize
Router
. Use (session ())
. Use (Authorize ());

Use middleware given path
Router.use ('/users ', Userauth ());

Or with an array of paths
Router.use (['/users ', '/admin '], Userauth ());
From the above analysis, we can use multiple middleware to handle the same route:

Router.get (
'/users/:id ',
function (CTX, next) {
Return User.findone (ctx.params.id). Then (function (User) {
Ctx.user = user;
return next ();
});
},
function (CTX) {
Console.log (Ctx.user);
=> {id:17, Name: "Alex"}
}
);
The wording will look more compact.

Route prefix

Koa-router allows you to add a prefix for a path uniformly:

var myrouter = new Router ({
Prefix: '/koa '
});

Equivalent to "/koa"
Myrouter.get ('/', function* () {
This.response.body = ' KOA router ';
});

Equivalent to "/koa/:id"
Myrouter.get ('/:id ', function* () {
This.response.body = ' Koa router-1 ';
});
You can also set a uniform prefix after routing initialization, Koa-router provides a prefix method:

Router.prototype.prefix = function (prefix) {
prefix = prefix.replace (/\/$/, ");

This.opts.prefix = prefix;

This.stack.forEach (function (route) {
Route.setprefix (prefix);
});

return this;
};
So the following code is equivalent to the above:

var myrouter = new Router ();
Myrouter.prefix ('/koa ');

Equivalent to "/koa"
Myrouter.get ('/', function* () {
This.response.body = ' KOA router ';
});

Equivalent to "/koa/:id"
Myrouter.get ('/:id ', function* () {
This.response.body = ' Koa router-1 ';
});
parameter Handling and redirection

The parameters of the path are obtained by the This.params property, which returns an object with all the path parameters being members of the object:

Visit/programming/how-to-node
Router.get ('/:category/:title ', function * (next) {
Console.log (This.params);
=> {category: ' Programming ', Title: ' How-to-node '}
});
The Param method can set conditions on parameters that can be used for general validation and automatic load validation:

Router
. Get ('/users/:user ', function * (next) {
This.body = This.user;
})
. param (' user ', function * (ID, next) {
var users = [' No. 0 users ', ' number 1th ', ' 2nd users '];
This.user = Users[id];
if (!this.user) return this.status = 404;
Yield next;
})
Param accepts two parameters: routing parameters and middleware for processing parameters:

Router.prototype.param = function (param, middleware) {
This.params[param] = middleware;
This.stack.forEach (function (route) {
Route.param (param, middleware);
});
return this;
};
If the/users/:user parameter user does not correspond to a valid user (such as an Access/USERS/3), the middleware registered by the Param method will find it and return a 404 error.

You can also verify that a parameter is not routed through redirect to another path, and returns a 301 status code:

Router.redirect ('/login ', ' sign-in ');

Equal to
Router.all ('/login ', function * () {
This.redirect ('/sign-in ');
This.status = 301;
});
All is a private method that handles all verb requests for a route, equivalent to a middleware. If you have a verb method that handles the same route before or after all, you will call yield next, or the other will not execute:

Myrouter.get ('/login ', function* (next) {
This.body = ' login ';
No yield next,all will not execute
Yield next;
}). Get ('/sign ', function* () {
This.body = ' sign ';
}). All ('/login ', function* () {
Console.log (' login ');
});

Myrouter.get ('/sign2 ', function* () {
This.body = ' sign ';
}). All ('/login2 ', function* () {
Console.log (' login2 ');
No yield next,get will not execute
Yield next;
}). Get ('/login2 ', function* (next) {
This.body = ' login ';
});
The first parameter of the redirect method is the source of the request, the second parameter is the destination, both can be replaced by the alias of the path pattern, and the third parameter is the status code, the default is 301:

Router.prototype.redirect = function (source, destination, code) {
Lookup source route by name
if (source[0]!== '/') {
Source = This.url (source);
}

Lookup destination Route by name
if (destination[0]!== '/') {
Destination = This.url (destination);
}

Return This.all (source, function * () {
This.redirect (destination);
This.status = Code | | 301;
});
};
Named routes and nested routines are created by the

For very complex routes, Koa-router supports the aliasing of complex path patterns. The alias is passed to the verb method as the first argument:

Router.get (' user ', '/users/:id ', function * (next) {
// ...
});
You can then generate the route by using the URL instance method:

Router.url (' user ', 3);
=> "/USERS/3"

Equivalent to
Router.url (' user ', {id:3});
=> ' USERS/3 '
The method receives two parameters: routing alias and Parameter object:

Router.prototype.url = function (name, params) {
var route = This.route (name);

if (route) {
var args = Array.prototype.slice.call (arguments, 1);
Return route.url.apply (route, args);
}

return new Error ("No Route found for name:" + name);
};
The first parameter is used to find the matching alias route, and returns true if found, otherwise returns false:

Router.prototype.route = function (name) {
var routes = This.stack; Routing Alias

for (var len = routes.length, i=0 i<len; i++) {
if (routes[i].name && routes[i].name = = name) {
return routes[i];
}
}

return false;
};
In addition to the instance method URL, Koa-router provides a static method URL generation route:

var url = router.url ('/users/:id ', {id:1});
=> "/USERS/1"
The first parameter is the path pattern, and the second parameter is the Parameter object.

In addition to naming a route, Koa-router also supports routing nested processing:

var forums = new Router ();
var posts = new Router ();

Posts.get ('/', function (CTX, next) {...});
Posts.get ('/:p ID ', function (CTX, next) {...});
Forums.use ('/forums/:fid/posts ', posts.routes (), Posts.allowedmethods ());

Responds to "/forums/123/posts" and "/forums/123/posts/123"
App.use (Forums.routes ());

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.