📜 ⬆️ ⬇️

Its a web server on NodeJS, and not a single framework. Part 1

For many people, JavaScript is associatively associated with an abundance of diverse frameworks and libraries. Of course, the tools that help us every day are good, but it seems to me that we need to find some kind of balance between using the tools and procrastination, as well as know how the things you use work. Therefore, when I just sat down with NodeJS, I was particularly interested to write a full-fledged web server, which I could use myself.

Newbies in NodeJS can really be hard. JS is one of the languages ​​in which there is often no single correct solution to a specific task, and modules added to a node for working with the file system, http server and other things characteristic of working on the server make it difficult even for those who write good code for browsers. Nevertheless, I hope that you know the basics of this language and its work in the server environment, if not, I advise you to see a wonderful screencast , which will help to understand the basics. And finally - I do not pretend to some kind of exceptionally correct code and I will be glad to hear criticism - we all learn, and this is a great way to get knowledge.

Let's start with the file structure


The source nodejs folder is stored on the server at the path / var / www / html /. It will be our web server. Then everything is simple: create a routing directory in it, which will contain our index.js script, as well as 4 folders - dynamic, static, nopage and main - for dynamically generated pages, statics, page 404 and the main page. It looks like this:

nodejs
--routing
---- dynamic
---- nopage
---- static
---- main
---- index.js

We create our server


Great, the file structure is more or less defined. Now we create a server.js file with the following contents in the source folder:
')
// server.js //    . const http = require('http'); const routing = require('./routing'); let server = new http.Server(function(req, res) { // API     POST-   JSON,    //       jsonString var jsonString = ''; res.setHeader('Content-Type', 'application/json'); req.on('data', (data) => { //   - . jsonString += data; }); req.on('end', () => {//    -   . routing.define(req, res, jsonString); //  define    . }); }); server.listen(8000, 'localhost'); 

Great! Now our server will accept requests, write JSON-data, if there is one, but so far it will crash with an error, because we do not have a define function in /routing/index.js. Time to fix it.

 // /routing/index.js const define = function(req, res, postData) { res.end('Hello, Habrahabr!'); } exports.define = define; 

We start our server:

 node server.js 

We go where he listens to requests. If you did not change the code, it will be localhost: 8000. Hooray. The answer is.

image
Wonderful. Only this is not exactly what we need from the server, right?

We catch requests to our API


Yes, we have received the answer, but so far not too close to the final goal. It's time to write logic for our router.

 // /routing/index.js //    . const url = require('url'); const fs = require('fs'); const define = function(req, res, postData) { //    .     localhost:3000/test,  path  '/test' const urlParsed = url.parse(req.url, true); let path = urlParsed.pathname; //      server.js.    ,     //   systemd,  ,     ,  /etc/systemd/system/... prePath = __dirname; try { //       .     // localhost:8000/api,      /routing/dynamic/api, ,    // index.js,  .  ,    try/catch   ,   //   fs.readFile,       ,    //   . let dynPath = './dynamic/' + path; let routeDestination = require(dynPath); res.end('We have API!'); } catch (err) { //   api? . res.end("We don't have API!"); } }; exports.define = define; 

Is done. Now we can create /routing/dynamic/api , and test what we have. I will use for this purpose my ready-made script at / dm / shortenUrl.

image

Determine whether there is a page


We learned how to find scripts, now we need to learn how to find statics. First of all, let's go to /routing/nopage and create index.html there. Just create the backbone of the html page, and make a single h1 heading with the text: "404". After that, go back to /routing/index.js , but now we will focus on the catch block we’ve already written:

 // /routing/index.js:  catch catch (err) { //          . //    ,    '=>',       es6, //   . let filePath = prePath+'/static'+path+'/index.html'; fs.readFile(filePath, 'utf-8', (err, html) => { //    ,     404   . //   — ,        -. if(err) { let nopath = '/var/www/html/nodejs/routing/nopage/index.html'; fs.readFile(nopath, (err , html) => { if(!err) { res.writeHead(404, {'Content-Type': 'text/html'}); res.end(html); } //     -   ,  ,    //  -   ,    . else{ let text = "Something went wrong. Please contact webmaster@forgetable.ru"; res.writeHead(404, {'Content-Type': 'text/plain'}); res.end(text); } }); } else{ //  , ,  . res.writeHead(200, {'Content-Type': 'text/html'}); res.end(html); } }); } 

Inspiring. Now we can give page 404, as well as html-pages, which we add ourselves to /routing/static . In my case, page 404 looks like this:

image

A few words about the API


The way to organize scripts is a personal matter. At the moment, the code in the try block is:

 let dynPath = './dynamic/' + path; let routeDestination = require(dynPath); routeDestination.promise(res,postData,req).then( result => { res.writeHead(200); res.end(result); return; }, error => { let endMessage = {}; endMessage.error = 1; endMessage.errorName = error; res.end(JSON.stringify(endMessage)); return; } ); 

In fact, all my scripts are a function that passes parameters to closures and returns promises, and further logic on promises is tied. In this context, this approach seems to me very convenient, so catching errors becomes very easy. It is already possible, in principle, to rewrite this logic on async / await, but I don’t see much sense for myself in this.

Processing browser requests


Now we can use our server, and it will return pages. However, if you put the same page in /routing/static/somepage , which works great, for example, on Apache, you will encounter some problems.

First of all, for this web server, as well as, probably, for all of this kind, you need to set references to css / js / img / ... files differently. If you want to connect a css-file to page 404 and make it beautiful, then in the case of Apache we would create a style.css file in the same nopage folder and connect it by specifying the following in the link tag: 'href = "style.css "'. However, now we need to write the path differently, namely: "/routing/nopage/style.css".

Secondly, even if we connect everything correctly, then nothing will happen, and we will still have a bare html page. And here we come to the very last part of today's article - we add the script so that it catches and processes the requests that the browser sends itself, reading the html markup. Well, let's not forget about favicon - take a favicon and put it in the / routing directory of our server.

So, go back to /routing/index.js . Now we will write the code right before try / catch:

 //      path  prePath.   ,   //  .     ,        // , : style.css, test.js, song.mp3 if(/\./.test(path)) { if(path == 'favicon.ico') { //    -  ,      'favicon.ico' // ,     prePath, : '/var/www/html/nodejs/routing/favicon.ico'. //    return,        . let readStream = fs.createReadStream(prePath+path); readStream.pipe(res); return; } else{ //       ,    ,    ,    //   res.head,   ,     ,   . //      css, js  mp3  ,      //  , ,   ,      . if(/\.mp3$/gi.test(path)) { res.writeHead(200, { 'Content-Type': 'audio/mpeg' }); } else if(/\.css$/gi.test(path)) { res.writeHead(200, { 'Content-Type': 'text/css' }); } else if(/\.js$/gi.test(path)) { res.writeHead(200, { 'Content-Type': 'application/javascript' }); } //  -,      return,     . let readStream = fs.createReadStream(prePath+path); readStream.pipe(res); return; } } 

Fuh. All is ready. Now you can connect our css-file and see our 404 page with all the styles:

image

findings


Hooray! We have made a web server that works and works well. Of course, this is only the beginning of work on the application, but the most important thing is already done - on such a web server you can raise any pages, it can cope with both static and dynamic content, and routing, in my opinion, looks convenient - just put the corresponding a file in static or dynamic, and it will immediately pick up, and you should not write a routing for each specific case.

In general, I am very pleased with the work of the server, and now I have refused Apache towards this solution, and on the whole it was a very interesting experience. Thank you very much, you, the reader, shared it with me, having come to this point.

UPD: I do not encourage anyone to use this server on an ongoing basis. Despite the fact that it completely suits me, it does not have a large number of functions necessary for a regular web server and some ready-made functionality, such as a clear definition of mime types, is not optimized. This will all be in the following articles.

Source: https://habr.com/ru/post/327440/


All Articles