A tentative study of Nodejs building a server like Apache

Source: Internet
Author: User
Tags readfile browser cache

#针对于项目而言
What we need to understand is that

* Most of the files in the project are static files, only the data part has the dynamic request.
* Data section requests are presented as restful features.
* So the project consists of two parts that are static servers and restful servers.
# # #section One: Create a static server


1. Create a file with App.js as the portal

App.js

<pre>
var PORT = 8000;
var http = require (' http ');
var server = Http.createserver (function (request, response) {
Todo
});
Server.listen (PORT);
Console.log ("Server runing at Port:" + Port + ".");
</pre>
# # #静态文件服务器的功能:

* The browser sends the URL, the service side resolves the URL, corresponds to the file on the hard disk.
* If the file exists, return the 200 status code and send the file to the browser side; If the file does not exist, return the 404 status Code and send a 404 file to the browser side.

# # # section: the ability to implement routing


* Add URL module is necessary, then parse pathname.

Here is the implementation code:

<pre>
var server = Http.createserver (function (request, response) {
var pathname = Url.parse (request.url). Pathname;
Response.Write (pathname);
Response.End ();
});

</pre>

# # #读取静态文件

In order not to let the user in the browser side through request/app.js to see our code, we set the user to request only the file under the assets directory.
The server maps the path information to the assets directory.

File reads are related to the FS (file system) module.
Similarly, path processing is involved, and the path module is needed.

We use the Path.exists method of the path module to determine if a static file exists on disk.
Join does not exist we respond directly to the client 404 error.

If the file exists, call the Fs.readfile method to read the file.
If an error occurs, we respond to a client 500 error indicating an internal error.
The normal state sends the read file to the client, indicating the 200 status.

<pre>
var server = Http.createserver (function (request, response) {
var pathname = Url.parse (request.url). Pathname;
var Realpath = "assets" + pathname;
Path.exists (Realpath, function (exists) {
if (!exists) {
Response.writehead (404, {
' Content-type ': ' Text/plain '
});

Response.Write ("This request URL" + pathname + "is not found on the this server.");
Response.End ();
} else {
Fs.readfile (Realpath, "binary", function (err, file) {
if (err) {
Response.writehead (500, {
' Content-type ': ' Text/plain '
});

Response.End (ERR);
} else {
Response.writehead (200, {
' Content-type ': ' text/html '
});

Response.Write (File, "binary");

Response.End ();
}
});
}
});
});
</pre>

The above simple code together with a assets directory, constitutes our most basic static file server.

So sharp-eyed you and see, what are the problems with this most basic static file server?
The answer is MIME type support. Because our server also has to store HTML, CSS, JS, PNG, GIF, JPG and so on files.
Not every file has a MIME type of text/html.

# # #MIME类型支持

Like other servers, MIME is supported by a mapping table.
<pre>

Exports.types = {
"css": "Text/css",
"gif": "Image/gif",
"html": "Text/html",
"ico": "Image/x-icon", br> "JPEG": "Image/jpeg",
"jpg": "Image/jpeg",
"JS": "Text/javascript",
"JSON": "Application/json",
" PDF ":" Application/pdf ",
" png ":" Image/png ",
" svg ":" Image/svg+xml ",
" swf ":" application/ X-shockwave-flash ",
" TIFF ":" Image/tiff ",
" TXT ":" Text/plain ",
" wav ":" Audio/x-wav ",
" wma ":" audio/ X-ms-wma ",
" wmv ":" Video/x-ms-wmv ",
" xml ":" Text/xml "
};i</pre>
The above code exists in the Mime.js file.

What we're going to do is introduce this mime.js file.
< pre>
var mime = require ("./mime"). Types;
</pre>
We get the suffix name of the file by Path.extname. Because the Extname return value contains ".", the slice method is used to remove ".", and for files without a suffix, we are all considered to be unknown.
<pre>
var ext = path.extname (Realpath);
Ext = ext? Ext.slice (1): ' Unknown ';
</pre>
Next, it's easy to get a real MIME type.
<pre>
var contentType = Mime[ext] | | "Text/plain";
Response.writehead ($, {' Content-type ': ContentType});
Response.Write (File, "binary");
Response.End ();
</pre>
For unknown types, we return the Text/plain type.

# # #缓存支持/control

After MIME support, the static file server looks perfect.
Any static file will be ready to go if it is dropped into the assets directory.
It appears that Apache has achieved the same effect as a static file server.

* However, we find that every time the user requests, the server calls the Fs.readfile method every time to read the files on the hard disk. When the server's request volume increases, hard disk IO will be unbearable. *
Before solving this problem,
# # #我们有必要了解一番前端浏览器缓存的一些机制和提高性能的方案.

* Gzip compressed files can reduce the size of the response and can achieve the purpose of saving bandwidth.
* A conditional GET request is generated when a copy of the file is stored in the browser cache and cannot be determined when it is valid.
* If-modified-since is included in the header of the request.
* If the server-side file has been modified after this time, the entire file is sent to the front end.
* If not modified, 304 status code is returned. Does not send the entire file to the front end.
* If the copy is valid, this GET request will be omitted. The most important method of judging effectiveness is to take the expires head when the server responds.
* The browser will determine the expires header until the established date expires before a new request is initiated.
* Another way to achieve the same goal is to return cache-control:max-age=xxxx. *


To simplify the problem, here are just a few things we do:

For files that specify several suffixes, add the Expires header and the Cache-control:max-age header in response. The timeout date is set to 1 years.
Because this is a static file server, the last-modified header is returned for all requests, in response.
For the request header with If-modified-since, make a date check, and if not modified, return 304. If modified, the file is returned.
For the above static file server, the response header given by node is very simple:
<pre>
Connection:keep-alive
Content-type:text/html
Transfer-encoding:chunked
<pre>
For the specified suffix file and expiration date, in order to ensure that it is configurable. Then it should be the case to build a config.js file.
<pre>
Exports. Expires = {
Filematch:/^ (gif|png|jpg|js|css) $/ig,
MAXAGE:60 * 60 * 24 * 365
};
</pre>
Introduce the Config.js file.

<pre>var config = require ("./config");</pre>
We determine if the suffix matches the condition we want to add the expiration time header to.
<pre>
var ext = path.extname (Realpath);
Ext = ext? Ext.slice (1): ' Unknown ';
if (Ext.match (config. Expires.filematch)) {
var expires = new Date ();
Expires.settime (expires.gettime () + CONFIG. Expires.maxage * 1000);
Response.setheader ("Expires", expires.toutcstring ());
Response.setheader ("Cache-control", "max-age=" + CONFIG.) Expires.maxage);
}
</pre>
There are two more headers in the response header.
<pre>
cache-control:max-age=31536000
Connection:keep-alive
Content-type:image/png
Expires:fri, 12:55:41 GMT
Transfer-encoding:chunked
<pre>
The browser detects Cache-control and expires before sending the request
* (Cache-control priority is higher than expires, but some browsers do not support Cache-control, this takes expires) *, and if not, the request is not sent and the file is read directly from the cache.

Next we add last-modified headers for all the requested responses.

The last modification time of the read file is implemented through the Fs.stat () method of the FS module.


<pre>
Fs.stat (Realpath, function (err, stat) {
var lastmodified = stat.mtime.toUTCString ();
Response.setheader ("last-modified", lastmodified);
});
</pre>
We also want to detect if the browser is sending a if-modified-since request header. If it is sent and the file is modified at the same time, we return to the 304 status.

if (request.headers[ifmodifiedsince] && lastmodified = = Request.headers[ifmodifiedsince]) {
Response.writehead (304, "not Modified");
Response.End ();
}
If it is not sent or does not match the file modification time on the disk, it is sent back to the most recent file on the disk.

With the expires and last-modified two scenarios and co-operation with the browser, a significant portion of the network traffic can be saved, while some hard drive IO requests are reduced. If there was a CDN before that, the whole solution would be perfect.

Since both expires and Max-age are judged by the browser, if successful, HTTP requests are not sent to the server, which can only be tested by fiddler and browser mates. But last-modified can be tested by curl.
<pre>
Curl--header "If-modified-since:fri, 19:14:51 GMT"-I http://localhost:8000

http/1.1 304 Not Modified
Content-type:text/html
Last-modified:fri, 19:14:51 GMT
Connection:keep-alive
</pre>
* Note that we see that the response of this 304 request is not with the body information. So, meet our bandwidth-saving needs. With just a few lines of code, you can save a lot of bandwidth costs. *


# # #GZip启用
* Can reduce traffic and bandwidth *
##### #要用到gzip, the zlib module is required, and the module starts native support in node 0.5.8.

<pre>var zlib = require ("zlib");</pre>

Gzip compression is not required for files of the image class, so we configure a compression-enabled list in Config.js.
<pre>
Exports.compress = {
Match:/css|js|html/ig
};
</pre>
In order to prevent large files, and in order to satisfy the call mode of the Zlib module, the read file is read in the form of stream.
<pre>
var raw = Fs.createreadstream (Realpath);
var acceptencoding = request.headers[' accept-encoding ' | | "";
var matched = Ext.match (Config.Compress.match);
if (matched && acceptencoding.match (/\bgzip\b/)) {
Response.writehead (k, "OK", {
' content-encoding ': ' gzip '
});
Raw.pipe (Zlib.creategzip ()). pipe (response);
} else if (matched && acceptencoding.match (/\bdeflate\b/)) {
Response.writehead (k, "OK", {
' content-encoding ': ' Deflate '
});
Raw.pipe (Zlib.createdeflate ()). pipe (response);
} else {
Response.writehead (k, "OK");
Raw.pipe (response);
}
</pre>

A tentative study of Nodejs building a server like Apache

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.