Webszerverek üzemeltetése: Forever, Statikus webszerverek, Express static.

Webszerverek és típusaik


A webszerverek tulajdonképpen számítógépek, melyeket összekötve adatközpontot képeznek, melyeket szolgáltató cégek üzemeltetnek. Ezen számítógépek egyik célja a weboldalak (mint fájlok) tárolása és szolgáltatása akár több ezer végfelhasználók részére. Az, hogy egyszerre hány kérést tud kielégíteni, szoftveres beállítások és hardver konfiguráció (processzor, memória, router, stb) függvénye. A végfelhasználói oldalon a böngészők (például Google Chrome, Opera, Firefox) kapcsolatot tartanak fenn a szerverek között, a kettő oldal között pedig adatcsere zajlik. Ezeket a weboldalakat a böngészők dolgozzák fel és jelenítik meg. Egy átlagos webszerveren az alábbi operációs rendszerek közül az egyiket futtatják: Linux vagy Windows. Arányaiban véve Több webszerveren futattnak Linuxot, mint Windows-t. Csak hogy teljesen összezavarjuk a kedves olvasókat, ezen számítógépeken futó szoftverek összeségét is szervereknek hívjuk. Nem lenne jó, ha bárki hozzá tudna férni a webszervereken tárolt adatokhoz, ezért felhasználói jogosultságokat szokás definiálni, melyeknek különböző szintjei vannak. Az egyes tartalmakhoz való hozzáférést jelszavas védelemmel is szokták korlátozni. Ezen számítógépekre feltelepített és a háttérben futtatott szoftverek határozzák meg a funkcionalitásukat. Eszerint megkülönböztetünk:
  • HTTP szerver- weboldalakat szolgáltat (HTTP protokoll segítségével)
  • FTP szerver - fájlokat tölthetünk fel és le (FTP protokoll segítségével)
  • Email szerver - továbbítja és tárolja az email üzeneteket
  • Adatbázis szerver - az adott weboldallal kapcsolatban (például webshop) egy előre meghatározott struktúrában tárolja és szolgáltatja az adatokat (például felhasználói profil és vásárolt termékek)

Ha az adott tartalom időben nem változik, akkor statikus tartalomról beszélünk, viszont ha a tartalom egy bejövő kérés következtében jön létre / változik meg, akkor pedig dinamikus tartalomról beszélhetünk. A dinamikus tartalom generálás nemcsak a felhasználói oldalon történhet, hanem a szerver oldalon is. A koncepció megvalósításához és fenntartásához az alábbi programozási környezeteket szokták használni: Node.js, PHP, Python. A funkcionalitások hatékony implementálására találták ki a keretrendszereket. A keretrendszereket használva (például Express.js, Laravel. Django) egyrészt nem kell mindent magadtól kitalálni (koncentrálni lehet az üzleti logikára), másrészt a használatával jóval kisebb támadási felületet adhatunk a programunknak. Egy példa keretén belül létrehozunk és futattunk egy egyszerű szervert NodeJs használatával. Első lépésként a https://nodejs.org weboldalról töltsük le a számunkra megfelelő verziót és telepítsük a számítógépünkre. Egy parancssort megnyitva a node -v parancsot lefuttatva lekérdezhetjük a NodeJs aktuálisan telepített verzióját. Egy tetszőleges útvonalon (projekt_neve mappa) futtassuk le a parancssoron az npm init parancsot és kövessük végig a projekt létrehozásának menetét:
> package name: (projekt_neve)

A következő lépésként importáljuk be a http modult és adjuk értékül a http változónak. Hozzunk létre egy port nevű változót és állítsuk be neki a 3000 értéket. Hozzunk létre egy server nevű változót, hívjuk meg a http modul createServer() metódusát, melynek segítségével létrehozhatunk egy szervert. A szerver a bejövő "request" kérésre figyelje az on metódus meghívására. Erre reagáljon. Az argumentumba definiáljunk egy függvényt, request és response paraméterekkel. A request és response a bejövő kérésnek és a válasznak szánt objektumok. Egy ilyen válasz objektum többek között áll egy fej és egy törzs részből. A fej részében definiálhatjuk a válaszunk természetét (hibakód, formátum, stb...). A törzs szekcióban pedig a tényleges választ deklarálhatjuk. A response objektum fej részében ezen kívűl allítsuk be a karakterkódolást UTF-8-ra. Amennyiben készen állunk a válasz definiálásával, az end() metódust meghívva elküldhetjük azt. Legyen az üzenet jellege szöveges és maga az üzenet legyen az, hogy Válasz a szervertől. Visszatérve a parancssorhoz, futtassuk le a node server.js parancs segítségével a szerverünket. Nyissük meg egy böngészőt és írjuk a keresőbe azt, hogy http://localhost:3000 és várjunk az eredményre. Leállítani a ctrl + c billentyűkombinációval tehetjük meg. A példa itt próbálható ki.

//  server.js
const http = require("http");
const port = 3000;

const server = http.createServer();
server.on("request", (request, response) => {
  request.writeHead(200, { "Content-Type": "text/plain; charset=utf-8" });
  response.write("Válasz a szervertől");
  response.end();
});

server.listen(port);




Forever


Egészen eddig ha a szerver futása közben bezárjuk a parancssort, maga a szerver is leáll. Sokkal praktikusabb lenne úgymond háttérben futtatni a szerverünket, mint a legtöbb folyamatot. Erre találták ki a forever könyvtárat. Amint valamilyen nem várt hiba következik be, a foreverrel indított szerver automatikusan újraindul a háttérben. Telepítsük fel a könyvtárat és indítsuk el a szerverünket a forever start server.js paranccsal. Leállítani (az összes foreverrel indított folyamatot) pedig a forever stopall paranccsal tehetjük meg.

Express static


Ahhoz, hogy az Express keretrendszerrel közzé tehessünk bármilyen statikus tartalmat, mint képek, CSS stílusfájlok és JavaScript fájlok, az express.static beépített middleware függvény megoldást nyújthat a kérdésünkre. Az argumentumában két paramétert vár: express.static(root, [options]). Az első paraméterben definiálhatjuk relatív formában azon könyvtár útvonalát, melynek tartalmát közzé szeretnénk tenni. a weboldal címét kiegészítve ezen relatív útvonallal, hozzáférhetünk a közzé tett könyvtár tartalmát. Több mappa esetén annyiszor kell implementálni ezt a middleware függvényt, ahány mappáról van szó. A könyvtár elérési útvonala egzakt információt közöl a szerver könyvtár struktúrájáról, ami biztonsági kockázatot is jelenthet. Lehetőségünk van virtuális útvonalakat definiálni, így megkerülhetjük a problémát. A példa itt próbálható ki.
//  server.js
const express = require('express');
const app = express();
const port = 3000;

//  egy példa a public mappa tartalmának közzé tételére
app.use(express.static('public'));

// egy példa több mappa tartalmának közzé tételére
app.use(express.static('files1'));
app.use(express.static('files2'));

//  egy példa virtuális útvonal implementálására, abszolút útvonallal
app.use('/static', express.static(path.join(__dirname, 'public')));

app.listen(port,(req,res)=>{
    console.log(`a szerverünk a  ${port} porton fut`);
});
> npm install --save express




MVC


Az MVC a Model View Controller szavakból összetevődő kifejezés egy rendkívül népszerű szoftvertervezési mintára. Egy webfejlesztő szemszögéből nézve az eltérő nyelvek esetén nincs két egyforma megvalósítási forma, sok programozó szerint igazi MVC nem is értelmezhető a webfejlesztésben. Az általános koncepciója az, hogy elválasszuk egymástól a funkcionalitást, a vezérlést, a felhasználói felületet, stb. az alkalmazásunkban. Ez megadja a lehetőséget, hogy fejlesztőket csoportosítsunk egyetlen problémára. Az egyetlen hátránya az adatok megfelelő struktúrájának tervezésének és implementálásának időigényességéből adódik. A Model az adatok manipulásásáért felel, alapvetően az alkalmazásunk agyaként funkcionál (az üzleti logikát megvalósító réteg). A Model feladata az, hogy és leírjuk és definiáljuk azokat a szabályokat, melyek alapján elérhetjük azokat. Általában kapcsolatban áll valamilyen típusú adatbázissal (NoSQL vagy SQL) és intézi a lekérdezéseket. Továbbá kapcsolatot tart fent a Controller-el azon keresztül, hogy az is lekérhet adatokat a Model-en keresztül, hogy frissítse a View-t. Az MVC View része az alkalmazásunk kinézetéért / felhasználói felületéért felel. A View definíció szerint HTML, CSS a Controller-től származó dinamikusan változó adatok összessége. Elhatárolódik egymástól a megjelenítés az adatszerkezetektől. Az adatokat kizárólag a Model osztályainak példányain keresztül (az objektum-orientált programozási környezetben) érhetjük el. Az összes View irányítását egy Modellel is meg lehet oldani . Az éppen használt keretrendszertől függően, a template engine eltérhet, ami a dinamikus átvitelért és megjelenítésért felel. Színtiszta HTML-el dolgozva a felhasználói oldalon a megjelenítésünk jellege statikus, dinamikusan változó adatokat nem lehet megjeleníteni. Template engine használatával megoldódik ez a problémánk. Végezetül maradt a Controller, ami összefoglalóan a bemeneti eszközök és csatornák felügyeletéért felel, lényegében összeköti őket. Ez azt az esetet is takarhatja, amikor egy felhasználó egy oldalt meglátogat és rákattintva egy linkre kérést indítványoz. Természetesen egy böngészőből nem indítható akármilyen kérés. Mint már tudjuk, a Controller egyfajta köztes állomásként viselkedik a Model és a View között. A Controller megkéri a Model-t arra, hogy adatot kérdezzen le az adatbázisból, ezt követően fogja az adatokat és betöltjük a View-be a template engine számára.

  • Model
    • Adat vezérelt logika
    • Interakció az adatbázissal (SELECT, INSERT, UPDATE, DELETE)
    • Kommunikáció a Controller-el
    • Keretrendszertől függően képes frissíteni a View-t
  • View
    • A felület, amivel a felhasználó találkozik
    • Általában HTML és CSS elemekből épül fel
    • Kapcsolatot tart fenn a Controller-el
    • A Controller-ből dinamikus adatok tölthetőek be
    • Template engine
  • Controller
    • Bemenet
    • Bejövő kéréseket dolgoz fel (GET, POST, PUT, DELETE)
    • A Model-től kér le adatokat
    • A View számára adatokat továbbít
MVC tervezési minta

Middleware függvények, hibakezelés


A Middleware függvények definíció szerint olyan függvények, amik hozzáférnek a request (kérés - req) és response (válasz - res) objektumokhoz és kezelik azokat. Az egyes kérésekhez feltételeket is köthetünk, ekkor jön aképbe a next() middleware függvény. Amennyiben a definiált feltétel teljesül az egyik feltételre, átugrik a soron következő kérésre. Akkor is szokás használni, ha egy idő után nem hajlandó leállni a kérés-válasz ciklus. Továbbá rendelkezik egy harmadik argumentummal, ahova függvény vár ami meghívódik amint lefut a Middleware kódunk. Ez azt jelenti, hogy megvárja amíg mondjuk egy aszinkron művelet (adat lekérése az adatbázisból) megtörténik és csak aztán teszi meg a következő lépést. Az Express middleware-ek közül a legfontosabbakat kiemelve:

  • Application level (alkalmazás szintű) middleware: app.use
  • Router level (útvonal szintű) middleware: router.use
  • Built-in (beépített) middleware: express.static
  • Error-handling (hibakezelés): app.use(err,req,res,next)
  • Third-party (harmadik féltől származó) middleware: bodyparser, cors
middleware függvények

Az Express-ben lehetőségünk van a hibakezelő middleware függvéynek implementálására. A szokásos middleware függvényekkel ellentétben nem három, hanem négy paramétert várnak az argumentumukban.

app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).send('Valamilyen hiba történt a szerverrel!');
});

Routing (get, post, put, del, all)


Tegyük fel, hogy számos útvonallal rendelkezünk és minden egyes útvonallal valamilyen erőforrást és / vagy nézetet kívánnánk elérni. Továbbá arról is meg szeretnénk győzödni arról, hogy az egyes útvonalakhoz csakis a megfelelő személyek férjenek hozzá. Ebben az esetben egy autentikációs middleware-t kell megvalósítanunk. Amint az autentikáció feltétele teljesül, az erőforráshoz való hozzáférést biztosítsuk a next() meghívásával. Amennyiben az autentikáció sikertelen, a next() meghívása helyett hibával térjünk vissza. Valósítsuk meg az elképzelésünket és lássuk mi lesz az eredmény! A példa itt próbálható ki.

útvonalkezelés
//  server.js
const express = require('express');
const app = express();
const port = 3000;

const bejelentkezés = (req,res,next) =>{
    console.log(`Bejelentkezve  ${req.url}  ${req.method}`);
    next();
};

app.use(bejelentkezés);

app.get('/felhasznalok',(req,res)=>{
    res.json({
        'status':true
    })
})

app.post('/mentes',(req,res)=>{
    res.json({
        'status':true
    })
})

app.listen(port,(req,res)=>{
    console.log('a szerverünk a ${port} porton fut');
});
> npm install --save express





A Router-level (útvonal szintű) middleware függvények hasonlóképpen működnek, mint a application-level (alkalmazás szintű) párjaik azzal a különbséggel, hogy a express.Router() példányához kötődnek. Ezeket a middleware-ket úgy indíthatjuk el, hogy először a router.use() metódust meghívjuk, aztán pedig magát a kívánt router.metodus() metódust/függvényt. A példa itt próbálható ki.
//  server.js
const express = require('express');
const app = express();
const port = 3000;
const router = express.Router();

router.use((req,res,next)=>{
    console.log("Idő:",new Date());
    next();
});

router.get("/user/:id",(req,res,next)=>{
    console.log('URL cím:', req.originalUrl);
    next();
},(req,res,next)=>{
    console.log('Kérés típusa:', req.method);
    next();
},(req,res)=>{
    res.json({
        status:true,
        id:req.params.id
    })
})

app.use('/',router);

app.listen(port,(req,res)=>{
    console.log(`a szerverünk a ${port} porton fut`);
});
> npm install --save express




Példa alkalmazás


Az eddig tanultakat felhasználva Írjunk egy egyszerű szerver által szolgáltatott alkalmazást Express segítségével. A feladat az lenne, hogy nyilván tartsunk néhány hallgató nevét és az általuk felvett tárgyait. Az egyszerűség kedvéért az adatok nyilván tartását reprezentáljuk egy objektummal, melyek tulajdonságaihoz egy-egy objektum van rendelve. Ezek egy-egy hallgatót reprezentálnak, melyekben el van tárolva a név, illetve a felvett tárgyak listája. A feladat lényegében az lenne, hogy a böngészőnkön annyi nézet jelenítődhessen meg, ahágy hallgató van és minden egyes nézetben írjuk ki a hallgató nevét, azonosítóját, illetve a felvett tárgyait. A példa itt próbálható ki.

//  server.js
const express = require('express');
const app = express();
const port = 3000;
const bodyParser = require('body-parser');
app.use(bodyParser.urlencoded({extended:false}));
app.use(bodyParser.json());
app.set('view engine', 'ejs');
app.set("views", __dirname + "/views");

const hallgatok = {
	1: {
		nev: 'Gábor Tibor',
		felvett_targyak: ['c++', 'java', 'python']
	   },
	2: {
		nev: 'Lajkó Dénes',
		felvett_targyak: ['c', 'javascript', 'python']
	   },
	3: {
		nev: 'Nagy János',
		felvett_targyak: ['mysql', 'java', 'mongodb']
	   }
};

app.get('/hallgatok/:id', 
	(req, res) => {	
		res.render(
			'hallgatok', 
			{
				nev: hallgatok[Number(req.params.id)].nev, 
				id: req.params.id, 
				felvett_targyak: hallgatok[Number(req.params.id)].felvett_targyak
			}
		)
	}
);

app.listen(port, () => { console.log(`a szerverünk a ${port} porton fut`)});
server.js
package.json
package-lock.json
> node_modules
> views
      |_ hallgatok.ejs
//  hallgatok.ejs

<!DOCTYPE html>
<html lang="hu">
	<head>
		<meta charset="utf-8">
		<title>Hallgatók listája</title>
		<style>
		</style>
	</head>
	<body>
		<p>Hallgató neve: <%= nev %></p>
		<p>Azonosito: <%= id %></p>
		<p>Felvett tárgyak:</p>
		<ul>
			<% felvett_targyak.forEach((targy) => { %>
				<li> <%= targy %></li>
			<%});%>
		</ul>
	</body>
<html>


Irodalomjegyzék

[1]
Vue.js core team. Vue.js: The Progressive JavaScript Framework. https://vuejs.org, Legutóbb megtekintve: 2019. április 22.
[2]
Vue.js core team. API, Global Config, directive. https://vuex.vuejs.org/vuex.png, Legutóbb megtekintve: 2019. április 22.
[3]
Vue.js core team. API, Global Config, computed. https://vuejs.org/v2/api/#computed, Legutóbb megtekintve: 2019. április 22.
[4]
Vue.js core team. API, Global Config, components. https://vuejs.org/v2/api/#components, Legutóbb megtekintve: 2019. április 22.
[5]
Vue.js core team. Vue-CLI. https://cli.vuejs.org/guide/creating-a-project.html#vue-create, Legutóbb megtekintve: 2019. április 22.
[6]
Vue.js core team. Vue-router. https://router.vuejs.org/guide/#html, Legutóbb megtekintve: 2019. április 22.
[7]
Vue.js core team. Render Functions and JSX. https://vuejs.org/v2/guide/render-function.html, Legutóbb megtekintve: 2019. április 22.
[8]
w3schools core team. JavaScript RegExp Reference. https://www.w3schools.com/jsref/jsref_obj_regexp.asp, Legutóbb megtekintve: 2019. április 22.
[9]
Google Chrome DevTools core team. Chrome DevTools. https://developers.google.com/web/tools/chrome-devtools, Legutóbb megtekintve: 2019. április 22.
[10]
Node.js core team. Node.js® is a JavaScript runtime built on Chrome's V8 JavaScript engine. https://nodejs.org/en/, Legutóbb megtekintve: 2019. április 22.
[11]
NPMJS core team. NPMJS. https://www.npmjs.com, Legutóbb megtekintve: 2019. április 22.
[12]
ExpressJS core team. Express Fast, unopinionated, minimalist web framework for Node.js. https://expressjs.com, Legutóbb megtekintve: 2019. április 22.
[13]
EJS core team. EJS: Embedded JavaScript templating. https://ejs.co, Legutóbb megtekintve: 2019. április 22.
[14]
Olga Filipova. Learning Vue.js 2. Packt Publishing Ltd., Livery Place, 35 Livery Street, Birmingham, B3 2PB, UK, 3, 2016.
[15]
E.F. Codd. A relational Model of Data for Large Shared Data Banks. Communications of the ACM, 13 (6) 1970.
[16]
Papp Edit. Adatbáziskezelés. Booklands 2000 Könyvkiadó Kft., 5600 Békéscsaba, Dr. Becsey Oszkár u. 42., 1, 2004.
[17]
Nemzeti Szakképzési és Felnőttképzési hivatal. Szoftverfejlesztő tanfolyam. https://www.nive.hu, Legutóbb megtekintve: 2019. április 22.
[18]
JavaScript.info core team. JavaScript.info. https://javascript.info/promise-basics, Legutóbb megtekintve: 2019. április 22.
[19]
Craig Buckler. Sitepoint (JavaScript) - Understanding ES6 Modules. https://www.sitepoint.com/understanding-es6-modules/, Legutóbb megtekintve: 2019. április 22.