Introduction of koa

Koa is a NodeJS server framework developed by the original Express team. Koa uses ES2017’s new standard: Async Function to implement middleware in its true sense. Koa’s source code is extremely simple, but its powerful middleware extensibility makes koA an extremely powerful server framework. With middleware, you can do anything NodeJS can do.

Some tedious explanations

This article is not annotated like other code analyses, so you need to open the source code at [email protected] to read it yourself.

Koa has been updated to version 2.x. Previous versions of 1.x are no longer compatible with [email protected]. This article focuses on a code analysis of [email protected].

In addition, the KOA source code contains some HTTP protocol content, which will not be overemphasized in this article. By default, the reader has already mastered the basic knowledge.

Also, koA2 is written with new ES2017 features, so you’ll need to know some of the new ES2017 syntax.

To keep this article simple, I have intentionally omitted error handling, parameter judgments, and the like.

You can also find this article here.

The $1. Check the package. Json

For nodeJS or even JavaScript projects, the first thing to look at is its package.json. You can find a lot of useful information in package.json.

We opened the [email protected] directory and found that it relies on quite a few libraries. In fact, most of these libraries are very simple, and koA is written on the principle of splitting functionality into other libraries. Let’s ignore these dependencies for a moment.

We find the main field, and here is the ‘door to the new world’. Open lib/application.js along main.

The $2. Analysis application. Js

Boy, it starts with a bunch of introductions, and that’s not a good thing, so let’s not look at them. Take a look at the code below.

We define an Application class first, and then some variables in the constructor. We focus on the following variables because they are the most useful:

    this.middleware = [];
    this.context = Object.create(context);
    this.request = Object.create(request);
    this.response = Object.create(response);
Copy the code

Object. Create is used to clone objects, and it clones three objects, the three most important koA objects: Request, Response, and Context. These three objects are almost all of koA. We’ll look at them one by one later.

Let’s move on to the listen function, which you’re all familiar with, that listens for ports. Koa’s listen function is also simple.

    const server = http.createServer(this.callback());
    returnserver.listen(... args);Copy the code

Two short lines, and for those of you who are not familiar with NodeJS, the advice stops here. What is this.callback()? It returns a function that takes two arguments, Request and Response, which is the createServer callback, described in more detail in the Middleware Principles section.

Then there’s toJSSON’s approach. Json.stringify calls a method whose purpose is to return the specified properties, not all, when you JSON an application instance. This is of little use, hardly any use.

Inspect is just calling the toJSON method.

And then there’s the use function. The use function itself is not very complex, but the middleware behind the use function as an interface is a bit complex. For this purpose, the middleware source code is specifically interpreted later in this article, which is skipped for the moment.

Callback, handleRequest, respond these methods involve middleware, so they will be covered in the middleware section.

CreateContext this method is used to encapsulate the context. This context is the first CTX argument you pass to the callback function you’re using koA’s use method. The most important action that createContext does is to set context.request to request and context.response to response. And set Response.resh and request. req to the native Response and Request, respectively.

Why? It has to go back to context.js and request.js and response.js, etc.

It is important to note that Request and Response are not in NodeJS, but are wrapped in KOA. To distinguish between native and KOA encapsulated, I call Request and Response wrapped, and Request and Response native. What you need to remember is that context.res is the native response, and context.response is the encapsulated response. Request and so on.

Encapsulation doesn’t look fancy, it just simplifies some common methods. Just like jquery simplifies JS manipulation of the DOM.

The $3. Analysis of the context. Js

Open context.js, not much code, but a lot of gold. The first is to assign proto to an object, which is also the exported value of the module.

Inspect and toJSON functions are the same as those in application.js.

Then you see an assert, which is similar to nodeJS assert, which provides some assertion actions. Equal, notEqual, strictEqual. Interestingly, Assert provides a deep comparison method called deepEqual, which is a good thing. Depth comparison in JS is always a troublesome problem, experienced programmers will use JSON to compare, here provides a better performance method. The code is not complex, just a reference to the deep-eqaul library, if you are interested in it, please go to see it.

Skip the two error-handling functions (not covered in this article) and get to the best part of context.js. Here we use the delegate library. What is this? Delegate is actually very simple, you don’t even need to look at the delegate source code, just watch me explain.

A delegate provides a proxy-like device called a Proxy. Agent for what? In particular, the delegate(proto, ‘response’) code simply means to delegate properties from proto to proto.Response. Which agents, exactly? That’s what the next line of neatly arranged code does. Delegate distinguishes between types like Method, getter, and Access. The first two are easy to understand, methods and read-only properties, and the third one? It’s just a read-write property, it’s like proxying both the getter and the setter. So you’re actually accessing the CTx. redirect and you’re actually accessing the CTX.Redirect and so on. Note that request and Response are not native to NodeJS, but are wrapped by KOA.

Context.js is as simple as that.

$4.request.js & response.js

Request. js and Response. js encapsulate the Request and response received by the createServer callback, respectively.

See the request first. Js. Remember createContext? As we said, he made Request.req a native Request. So as you can see, many methods are essentially operating on this.req, which is similar to Response.js, but won’t be repeated later.

The header sets the getter and setter, respectively, for this.req.headers. Headers, like headers, are not used to distinguish between singular and plural numbers. There are also a lot of common properties, I won’t go into one, what url, method and so on, a little bit familiar with NodeJS students can implement it.

Note both Query and QueryString, one of which returns an object and the other a string.

You might ask what’s the difference between Search and QueryString. Difference, emMMMN… Maybe for completeness, after all, express has a search, koA also has to provide…

It should also be noted that many of these properties operate on HTTP content, such as fresh. HTTP is a big topic that I won’t cover. If you encounter code that you don’t understand, check out the HTTP protocol.

Also you can see it in idempotent!! What the hell is this?? The first time I saw it, I was confused. This is just a bit operation. We usually put!! As a group, its function is to turn arbitrary data into Boolean values. So this is a very simple operation, just check if it’s -1, if it’s -1, then it’s false; If not -1, then both are true. This operation is very clever. Explain a little bit.

We assume that the number is represented in eight bits, so the original code of -1 is 1000 0001, the inverse code is 1111 1110, and the complement is 1111 1111. The ~ operator is the inverse operator, so the inverse operator becomes 0000 0000. Computers store negative numbers as a complement (Google for that information), so they end up being -1.

There are a few accept-header functions that can be ignored, which check for compatibility with the specified type, language, and encoding, and which internally call a ACCEPTS library. This feature is rarely used, but involves more complex things like coding.

In the final code, request.js provides a get method, which essentially gets the header.

Let’s go to Response.js. At first glance, it is similar to Request.js, but the methods and attributes of knowledge encapsulation are different.

The first one is socket, this is socket, the bottom layer of HTTP module, not explained.

Header calls getHeaders() to get all the headers that have already been set. Status Sets the status code, such as 200,404. It’s worth noting that normally using NodeJS’s statusCode also requires you to set a statusMessage to tell the user what’s wrong, which KOA will do intelligently for you. For example, if you set the status to 404, the statusMessage will be automatically set to 404 not found. This is because KOA uses the statuses library, which returns the specified status information based on the status code you pass in.

And then the most important property of Response, which is the body. Operations on the body are reflected on the inner _body. The setter for the body does all sorts of things. For example, determine if the value passed to the body is null and do something if it is. What’s interesting is that the setter for the body decides what Type of data is being passed to the body if you don’t set the Content-Type.

  1. When a string is passed, it uses a regular: /^\s*

  2. When passing buffer, call the type setting bin. (Remember, type is a koA wrapped attribute, and it will automatically match the best Content-Type based on the type you set.) For example, if you set type to ‘json’, the final content-Type will actually be Application /json. Implementation methods will be described later).

  3. When a stream is passed (by checking whether it has a pipe function), bind the callback first and destroy the stream when the RES is sent to avoid wasting memory. Error handling follows. If not, remove the Content-Length and leave it to NodeJS to handle. (NodeJS doesn’t actually handle that either, why? The header must be sent before the body is sent, but the number of bytes of the Stream is not known until after it is sent. Finally, set type to bin because stream is a binary data stream.

  4. If it does not satisfy the above three criteria, it must be json (Boolean, symbol, etc.). . Remove the Content-Type (why, you may be asking? Since what you’re passing is actually an Object, you need to stringify to know its number of bytes, which will be dealt with later). Set type to JSON.

At this point, we’re done analyzing the setter for the body.

And then length, which essentially encapsulates the method of setting Content-Length. But its getter is a little complicated. We might as well take a closer look.

If the content-Length setting is set, return the number of bytes in the body. When the body is a stream, nothing is returned.

Here’s a nifty trick that can be used to convert strings into numbers. Why is that? ! I knew you were gonna ask! In fact, this thing to have a relatively high understanding of JS line, JS there is implicit conversion, when encountered some special operators, such as the bit operator, will convert the string into a number to calculate. The + symbol can be converted from a string to a number (STR + STR is not an implicit conversion), so why use ~~ instead of +? On second thought, I think the author may not understand. In practice, however, ~~ is safer than ‘+’, which returns NaN in the case of an unconverted expression, while ~~, which operates on bits, returns a safe 0.

Skip to Type, which, like Length, encapsulates the content-Type implementation. There is a reference to a library of MIME-types that is powerful enough to return a specified MIME type based on an passed parameter. For example, if we set type to json, we will call the contentType function of MIME-types and return the MIME of json type, i.e. Application /json.

Like Request.js, Response.js also encapsulates the set and get methods for setting and reading headers.

Inspect and toJSON come again…

Many attributes and methods of Response.js are not mentioned, because these attributes and methods are simply encapsulated, convenient to call and easy to understand.

Ok, so response.js has been analyzed.

$5. Principle analysis of KOA middleware

Koa’s middleware principles are powerful and not particularly complex to implement. Remember how to use KOA middleware? You only need to use a function! This function takes two arguments, one is context, which we’ve already analyzed. The other is next, and that’s the core of the middleware.

Let’s go back to the beginning and see how use is implemented. Instead of looking at error handling, here we make a judgment on FN. Judge what? Check whether the FN is a generator function. The official koA recommendation is to stop using Generator function and replace it with Async Function. If you are using a Generator function, the CO module will be called internally to handle this. Because the processing content is more obscure, and not related to the text, so do not explain. We assume that all middleware are async functions.

Application maintains a queue of middleware to which the use method pushes middleware, but does nothing else.

Remember the listen method? It calls the method callback. The final answer is here!

See the callback method. First, it calls the compose method on the Middleware queue. We open up the compose module with a few dozen lines of code.

Regardless of error handling, compose has only one return statement that returns a function. This function takes two arguments context and next, familiar? This is a middleware function! Don’t panic. Keep reading.

You declare an index cursor, define a dispatch function, and return Dispatch (0) by default.

The dispatch function is used to distribute middleware (much like distributing events). It receives a number that is the subscript of one of the middleware in the middleware queue. The first step is to determine if the index is out of bounds, i.e. the index is compared to the I passed in, and the cursor is not out of bounds to move to the currently distributed middleware. Then determine whether I has traversed the middleware queue, I === middleware.length If so, set fn to next as passed in. Then use promise.resolve and call the current middleware, notice

return Promise.resolve(fn(context, function next () {
    return dispatch(i + 1)}))Copy the code

The second argument passed to the middleware, next, is a function that distributes the next event!! The most important principle of middleware is here, why can use next to transfer control, logic is here!

Now that the compose function is analyzed, remember that the return value of compose is a middleware-like function.

Go back to the Application callback method. Defines a handleRequest function and returns it directly. HandleRequest is actually a callback to http.createServer. This callback first encapsulates createContext, as described above. It then calls the handleRequest method on application (not to be confused, this is the handleRequest method below).

If we look at the handleRequest method, it takes two arguments, the first is context, what is the second? This is the middleware queue that compose handles. Get rid of some ‘redundant’ code and simplify it to this:

  handleRequest(ctx, fnMiddleware) {
    const handleResponse = (a)= > respond(ctx);
    return fnMiddleware(ctx).then(handleResponse).catch(onerror);
  }
Copy the code

Remember what the return value of fnMiddleware is? Is dispatch (0). Remember what the return value of dispatch was? It’s a Promise. Let’s look at this Promise again

return Promise.resolve(fn(context, function next () {
    return dispatch(i + 1)}))Copy the code

If you think about it, fn is now the first middleware, it’s called first. The next function is called in this middleware, which is equivalent to calling Dispatch (I + 1), and so on. Isn’t that equivalent to calling the Dispatch function in turn?

Finally, the middleware is async function, do you see why Promise is used? Right, to await.

Finally, there is the respond method, which processes statusCode, header and body, and finally calls nodeJS to provide a method to send data to the client. Ctx.end (body) is called to end the HTTP request.

At this point, the KOA middleware is finished.

conclusion

Koa source code is not very complex, interested students can have a look. I hope this article will help you.

Promote your GitHub, my open source project DoxJS, interested can see, give a star and so on.