student@aipi2015:~$ npm install npm -g
npm@3.5.0 /usr/local/lib/node_modules/npm
Operațiile pe care le pune la dispoziție utilitarul în linie de comandă sunt:
* **vizualizarea listei tuturor modulelor disponibile** se face prin intermediul comenzii ''npm ls'' (cu opțiunea ''-g'' dacă se dorește să se afișeze modulele instalate global);
* **instalarea unui modul** este realizată prin intermediul comenzii ''npm install
...
"dependencies": {
"express": "^4.10.6",
"mysql": "^2.5.4",
"ejs": "^1.0.0",
"body-parser": "1.8",
"serve-favicon": "2",
"express-session": "^1.7.6"
}
...
var result = syncMethod(param);
console.log(result);
console.log('finished');
O astfel de abordare presupune faptul că aplicația așteaptă ca metoda ''syncMethod()'' să se termine înainte de a-și putea continua execuția. În situația în care sunt implicate procesări de intrare/ieșire intensive, comunicație prin rețea sau acces la anumite resurse partajate, o astfel de aplicație poate dura o perioadă de timp considerabilă, interval în care aplicația nu poate prelua alte solicitări, dar nici nu realizează alte procesări. Rezultatul execuției codului sursă va fi rezultatul furnizat de metoda sincronă, urmat de afișarea șirului de caractere de control. Această variantă presupune execuția secvențială a instrucțiunilor.
O variantă asincronă (non-blocantă) ar putea fi:
asyncMethod(param, function(error, result) {
if (error) {
console.log('An error has occurred: %s', error);
}
console.log(result);
});
console.log('finished');
O astfel de abordare presupune faptul că aplicația invocă metoda ''asyncMethod()'' după care își continuă execuția. Metoda primește ca parametru o funcție de tip callback (anonimă), care va fi apelată în mod automat în momentul în care rezultatul este disponibil. În acest fel, aplicația poate gestiona alte solicitări în timp ce se așteaptă ca rezultatul să fie furnizat. Rezultatul execuției codului sursă va fi afișarea șirului de caractere de control urmat de rezultatul furnizat de metoda sincronă. O astfel de variantă presupune execuția paralelă a instrucțiunilor.
var events = require('events');
var eventEmitter = new events.EventEmitter();
Un eveniment este generat în momentul în care se apelează metoda [[https://nodejs.org/api/events.html#events_emitter_emit_event_arg1_arg2|emit()]]. Aceasta primește ca parametri denumirea evenimentului, precum și alte argumente care vor fi transmise funcției de tip callback, atașate ascultătorului specific evenimentului respectiv.
eventEmitter.emit('someEvent');
Metoda furnizează rezultatul ''true'' în situația în care au fost înregistrați în prealabil ascultători și ''false'', în caz contrar. Această operație este realizată prin metodele [[https://nodejs.org/api/events.html#events_emitter_on_event_listener|on()]], respectiv [[https://nodejs.org/api/events.html#events_emitter_addlistener_event_listener|addListener()]] (echivalente), fiecare dintre acestea primind ca parametri denumirea evenimentului precum și funcția de tip callback.
var someEventHandler = function process() {
console.log('processing...');
// do some processing
}
eventEmitter.on('someEvent', someEventHandler);
// or
eventEmitter.addListener('someEvent', someEventHandler);
În situația în care au fost apelate metodele ''on()'' sau ''addListener()'' pentru un tip de eveniment, funcțiile de tip callback definite de acestea vor fi apelate în mod automat în momentul în care se produce un astfel de eveniment, prin invocarea metodei ''emit()'' corespunzătoare.
Dacă nu mai este nevoie ca un eveniment să fie procesat, se poate înlătura ascultătorul respectiv prin intermediul metodei [[https://nodejs.org/api/events.html#events_event_removelistener|removeListener()]] care primește același tip de parametri. Similar, toți ascultătorii pot fi eliminați printr-un apel al metodei [[https://nodejs.org/api/events.html#events_emitter_removealllisteners_event|removeAllListeners()]].
for (var index = 1; index <= someList.length; index++) {
someAsyncMethod(parameter, function(error, result) {
// some processing involving the index value
});
}
Un astfel de comportament poate fi simulat folosind evenimente:
- se implementează o funcție de tip callback, care va fi invocată pentru procesarea fiecărui element al listei, primind ca parametru indexul (sau elementul) respectiv:
- în momentul în care s-a terminat de parcurs lista, se generează un eveniment prin care se anunță acest lucru;
- se realizează procesarea, iar la terminarea acesteia, se invocă metoda pentru elementul următor (prin generarea evenimentului corespunzător);
- se adaugă un ascultător pentru evenimentul de procesare a unui element al listei;
- se adaugă un ascultător pentru evenimentul de terminare a procesării tuturor elementelor listei;
- se generează un eveniment de procesare a primului element al listei.
var someEventHandler = function someProcessing(index) {
if (index > someList.length) {
eventEmitter.emit('listProcessingEnd');
return;
}
someAsyncMethod(parameter, function(error, result) {
// some processing involving the index value
eventEmitter.emit('listProcessingStart', index + 1);
});
};
eventEmitter.addListener('listProcessingStart', someEventHandler);
eventEmiter.addListener('listProcessingEnd', function() {
eventEmitter.removeAllListeners();
});
eventEmitter.emit('listProcessingStart', 1);
==== Fluxuri de Date ====
Un [[https://nodejs.org/api/stream.html|flux de date]] este un obiect care permite citirea, respectiv scrierea în / dintr-o sursă de date în mod continuu.
În Node.JS sunt definite următoarele tipuri de fluxuri de date:
- [[https://nodejs.org/api/stream.html#stream_class_stream_readable|stream.Readable]] - abstractizare a unei surse de date din care se citește;
- [[https://nodejs.org/api/stream.html#stream_class_stream_writable|stream.Writable]] - abstractizare a unei destinații de date în care se scrie;
- [[https://nodejs.org/api/stream.html#stream_class_stream_duplex|stream.Duplex]] - obiect care implementează ambele interfețe (''Readable'', respectiv ''Writable''), putând fi folosite atât pentru citire cât și pentru scriere;
- [[https://nodejs.org/api/stream.html#stream_class_stream_transform|stream.Transform]] - obiect care implementează interfața ''Duplex'' și în care ieșirea este determinată pe baza intrării.
Fiecare obiect de tip flux de date moștenește comportamentul clasei ''EventEmitter'', astfel încât generează o serie de evenimente:
**1**. Clasa ''stream.Readable''
^ **EVENIMENT** ^ **DESCRIERE** ^
| [[https://nodejs.org/api/stream.html#stream_event_close|close]] | generat în momentul în care fluxul de intrare și resursele asociate au fost închise, astfel încât nu se vor mai realiza alte procesări |
| [[https://nodejs.org/api/stream.html#stream_event_data|data]] | generat în momentul în care datele sunt disponibile, acestea fiind furnizate prin intermediul atributului ''chunk'' |
| [[https://nodejs.org/api/stream.html#stream_event_end|end]] | generat în momentul în care nu mai există informații care să fie citite (când sursa de date a fost complet epuizată) |
| [[https://nodejs.org/api/stream.html#stream_event_error|error]] | generat în momentul în care se produce o eroare |
| [[https://nodejs.org/api/stream.html#stream_event_readable|readable]] | generat în momentul în care datele sunt disponibile pentru a fi citite, prin invocarea metodei ''read()'' |
**2**. Clasa ''stream.Writable''
^ **EVENIMENT** ^ **DESCRIERE** ^
| [[https://nodejs.org/api/stream.html#stream_event_drain|drain]] | generat în momentul în care fluxul de ieșire este disponibil pentru a fi scris (dacă anterior, metoda ''write()'' a furnizat rezultatul ''false'') |
| [[https://nodejs.org/api/stream.html#stream_event_error_1|error]] | generat în momentul în care se produce o eroare |
| [[https://nodejs.org/api/stream.html#stream_event_finish|finish]] | generat în momentul în care nu mai există informații care să fie scrise (când destinația de date primit toate datele) |
| [[https://nodejs.org/api/stream.html#stream_event_pipe|pipe]] | generat în momentul în care se apelează metoda ''pipe()'' pe un flux de intrare, la care fluxul de ieșire este atașat; furnizează în atributul ''src'' fluxul de intrare respectiv |
| [[https://nodejs.org/api/stream.html#stream_event_unpipe|unpipe]] | generat în momentul în care se apelează metoda ''unpipe()'' pe un flux de intrare, la care fluxul de ieșire este detașat; furnizează în atributul ''src'' fluxul de intrare respectiv |
Utilizarea unui flux de intrare implică definirea unui comportament pentru evenimentele ''data'' și ''end'' (eventual ''error'').
var fs = require('fs');
var data = '';
var readerStream = fs.createReadStream(someResource);
readerStream.setEncoding('UTF8');
readerStream.on('data', function(chunk) {
data += chunk;
});
readerStream.on('end',function() {
console.log(data);
});
readerStream.on('error', function(error) {
console.log(error.stack);
});
console.log('finished');
Alternativ, se poate utiliza evenimentul ''readable'' împreună cu metoda [[https://nodejs.org/api/stream.html#stream_readable_read_size|read()]] care primește ca parametru opțional un număr indicând dimensiunea (exprimată în octeți) care se așteaptă a fi citită (în situația în care nu este disponibilă cantitatea respectivă se întoarce ''null'', cu excepția situației în care nu mai sunt alte date disponibile); în situația în care nu se furnizează nici un argument, se întoarce întreaga cantitate disponibilă.
var fs = require('fs');
var data = '';
var readerStream = fs.createReadStream(someResource);
readerStream.setEncoding('UTF8');
readerStream.on('readable', function() {
var chunk;
while (null !== (chunk = readerStream.read())) {
data += chunk;
}
});
readerStream.on('end', function() {
console.log(data);
});
readerStream.on('error', function(error) {
console.log(error.stack);
});
console.log('finished');
Utilizarea unui flux de ieșirea implică apelul metodelor [[https://nodejs.org/api/stream.html#stream_writable_write_chunk_encoding_callback|write()]] respectiv [[https://nodejs.org/api/stream.html#stream_writable_end_chunk_encoding_callback|end()]] precum și definirea unui comportament pentru evenimentul ''end'' (eventual ''error''). Ambele metode primesc ca parametri datele care se doresc a fi scrise, tipul de codificare folosit precum și o funcție de tip callback care este apelată în momentul în care datele au fost transmise complet. Apelul metodei ''write()'' după ce anterior s-a invocat metoda ''end()'' nu este permis și va genera o eroare.
var fs = require('fs');
var data = 'Some Data';
var writerStream = fs.createWriteStream(someResource);
writerStream.write(data, 'UTF8');
writerStream.end();
writerStream.on('finish', function() {
console.log('operation completed');
});
writerStream.on('error', function(error) {
console.log(error.stack);
});
console.log('finished');
Un flux de intrare poate fi atașat unui flux de ieșire prin intermediul metodei [[https://nodejs.org/api/stream.html#stream_readable_pipe_destination_options|pipe()]]. În acest mod, conținutul din sursa de date este transferată în mod automat în destinația de date. Metoda se apelează pe fluxul de intrare și primește ca parametru fluxul de ieșire, specificându-se eventual și un set de opțiuni (atributul ''end'', având implicit valoarea ''true'', indică faptul că fluxul de ieșire nu mai poate fi utilizat după ce datele din fluxul de intrare au fost transferate). O astfel de metodă este utilă pentru că adaptează transferul de date astfel încât să fie adecvate vitezelor de transfer ale fluxurilor de date de tip intrare, respectiv ieșire. Similar, se poate folosi metoda [[https://nodejs.org/api/stream.html#stream_readable_unpipe_destination|unpipe()]] care detașează fluxul de ieșire de la fluxul de intrare, în situația în care s-a produs o eroare sau a expirat un timp limită pentru transferul de date. Metoda se apelează pe fluxul de intrare și poate primi ca parametru opțional fluxul de ieșire (în caz contrar, fiind detașate toate fluxurile de ieșire).
var fs = require('fs');
var readerStream = fs.createReadStream(someResource);
var writerStream = fs.createWriteStream(someOtherResource);
readerStream.pipe(writerStream);
setTimeout(function() {
readerStream.unpipe(writerStream);
writerStream.end();
}, 1000);
console.log('finished');
Se pot crea lanțuri formate din mai multe fluxuri de intrare, respectiv fluxuri de ieșire, mai ales în situația în care este necesar să se realizeze procesări multiple pe setul de date respectiv. Un caz tipic este reprezentat de operația de compresie / decompresie a datelor.
var fs = require('fs');
var zlib = require('zlib');
var readerStream = fs.createReadStream(someResource);
var writerStream = fs.createWriteStream(someCompressedResource);
readerStream
.pipe(zlib.createGzip())
.pipe(writerStream);
console.log('file compressed');
var fs = require('fs');
var zlib = require('zlib');
var readerStream = fs.createReadStream(someCompressedResource);
var writerStream = fs.createWriteStream(someResource);
readerStream
.pipe(zlib.createGunzip())
.pipe(writerStream);
console.log('file uncompressed');
var fs = require('fs');
fs.open(someFile, 'r+', function(error, file_descriptor) {
if (error) {
return console.error(error);
}
console.log('file opened!');
});
Există și varianta sincronă a metodei [[https://nodejs.org/api/fs.html#fs_fs_opensync_path_flags_mode|fs.openSync(path, flags[, mode])]].
== Închiderea unui Fișier ==
Un fișier poate fi închis asincron folosind metoda [[https://nodejs.org/api/fs.html#fs_fs_close_fd_callback|fs.close(fd, callback)]].
Parametrii pe care îi primește metoda sunt:
* ''fd'' - descriptorul de fișier;
* ''callback'' - funcția de tip callback care este invocată în momentul în care fișierul este închis, primind ca argumente o eroare (în cazul în care se produce).
var fs = require('fs');
// ...
fs.close(file_descriptor, function(error) {
if (error) {
return console.error(error);
}
console.log('file closed!');
});
Există și varianta sincronă a metodei [[https://nodejs.org/api/fs.html#fs_fs_closesync_fd|fs.closeSync(fd)]].
== Obținerea de Informații cu privire la un Fișier ==
Obținerea de informații cu privire la un fișier poate fi realizată asincron folosind metoda [[https://nodejs.org/api/fs.html#fs_fs_stat_path_callback|fs.stat(path, callback)]].
Parametrii pe care îi primește metoda sunt:
* ''path'' - calea către resursa din sistemul local de fișiere pentru care se dorește să se obțină informații;
* ''callback'' - funcția de tip callback care este invocată în momentul în care s-au obținut informații cu privire la resursa din sistemul local de fișiere, primind ca argumente o eroare (în cazul în care se produce) și descriptorul pentru fișier și un obiect de tipul [[https://nodejs.org/api/fs.html#fs_class_fs_stats|fs.Stats]] prin intermediul căruia pot fi obținute diferite informații cu privire la resursa respectivă.
^ **OPȚIUNE** ^ **DESCRIERE** ^
| ''isFile()'' | întoarce ''true'' dacă resursa este un fișier obișnuit |
| ''isDirectory()'' | întoarce ''true'' dacă resursa este un director |
| ''isBlockDevice()'' | întoarce ''true'' dacă resursa este un dispozitiv de tip bloc |
| ''isCharacterDevice()'' | întoarce ''true'' dacă resursa este un dispozitiv de tip caracter |
| ''isSymbolicLink()'' | întoarce ''true'' dacă resursa este o legătură simbolică |
| ''isFIFO()'' | întoarce ''true'' dacă tipul resursei este FIFO |
| ''isSocket()'' | întoarce ''true'' dacă tipul resursei este un socket |
var fs = require('fs');
fs.stat(someFile, function(error, stats) {
if (error) {
return console.error(error);
}
console.log('isFile: ' + stats.isFile());
console.log('isDirectory: ' + stats.isDirectory());
});
Există și varianta sincronă a metodei [[https://nodejs.org/api/fs.html#fs_fs_statsync_path|fs.statSync(path)]].
== Citirea dintr-un Fișier ==
Un fișier poate fi citit în mod asincron folosind metoda [[https://nodejs.org/api/fs.html#fs_fs_read_fd_buffer_offset_length_position_callback|fs.read(fd, buffer, offset, length, position, callback)]].
Parametrii pe care îi primește metoda sunt:
* ''fd'' - descriptorul de fișier;
* ''buffer'' - un obiect de tip ''Buffer'' în care vor fi plasate datele citite;
* ''offset'' - poziția (în obiectul de tip ''Buffer'') la care vor fi plasate datele citite;
* ''length'' - dimensiunea (exprimată ca număr de octeți) care se dorește a fi citită;
* ''position'' - poziția de la care se dorește să se realizeze citirea (dacă nu este indicată, se folosește poziția curentă);
* ''callback'' - funcția de tip callback care este invocată în momentul în care s-a realizat o operație de citire din fișier, primind ca argumente o eroare (în cazul în care se produce), numărul de octeți care au fost citiți și un obiect de tip ''Buffer'' în care poate fi accesat conținutul respectiv.
var fs = require('fs');
var buffer = new Buffer(1024);
fs.open(someFile, 'r+', function(error, file_descriptor) {
if (error) {
return console.error(error);
}
fs.read(file_descriptor, buffer, 0, buffer.length, 0, function(error, bytes) {
if (error) {
console.log(error);
}
if (bytes > 0) {
console.log(buffer.slice(0, bytes).toString());
}
console.log('file has been read!');
});
});
Există și varianta sincronă a metodei [[https://nodejs.org/api/fs.html#fs_fs_readsync_fd_buffer_offset_length_position|fs.readSync(fd, buffer, offset, length, position)]].
== Scrierea într-un Fișier ==
Un fișier poate fi scris în mod asincron folosind metoda [[https://nodejs.org/api/fs.html#fs_fs_writefile_file_data_options_callback|fs.writeFile(file, data[, options], callback)]].
Parametrii pe care îi primește metoda sunt:
* ''file'' - calea către fișierul care se dorește a fi scris sau descriptorul de fișier;
* ''data'' - un obiect de tip șir de caractere sau ''Buffer'' care conține datele ce se doresc a fi scrise;
* ''options'' - un obiect care conține următoarele informații:
* ''encoding'' - mecanismul de codificare utilizat, implicit ''utf8'';
* ''mode'' - modul (permisiunile și biții murdari), implicit ''0666'';
* ''flag'' - valoare prin intermediul cărora se indică comportamentul fișierului care se dorește a fi scris, implicit ''w'';
* ''callback'' - funcția de tip callback care este invocată în momentul în care s-a realizat o operație de scriere în fișier, primind ca argumente o eroare (în cazul în care se produce).
var fs = require('fs');
fs.writeFile(someFile, someContent, function(error) {
if (error) {
return console.error(error);
}
console.log('file has been written!');
});
Există și varianta sincronă a metodei [[https://nodejs.org/api/fs.html#fs_fs_writefilesync_file_data_options|fs.writeFileSync(file, data[, options])]].
== Modificarea unui Fișier ==
Modificarea unui fișier implică reținerea primilor n octeți din cadrul său (funcționalitate de tip ''truncate''), putând fi realizată în mod asincron folosind metoda [[https://nodejs.org/api/fs.html#fs_fs_ftruncate_fd_len_callback|fs.ftruncate(fd, len, callback)]].
Parametrii pe care îi primește metoda sunt:
* ''fd'' - descriptorul de fișier;
* ''len'' - dimensiunea (exprimată în număr de octeți) după care conținutul respectiv va fi ignorat;
* ''callback'' - funcția de tip callback care este invocată în momentul în care s-a realizat o operația de modificare a fișierului, primind ca argumente o eroare (în cazul în care se produce).
var fs = require('fs');
var buffer = new Buffer(1024);
fs.open(someFile, 'r+', function(error, file_descriptor) {
if (error) {
return console.error(error);
}
fs.ftruncate(file_descriptor, 512, function(error) {
if (error) {
console.log(error);
}
fs.read(file_descriptor, buffer, 0, buffer.length, 0, function(error, bytes) {
if (error) {
console.log(error);
}
if(bytes > 0){
console.log(buffer.slice(0, bytes).toString());
}
fs.close(file_descriptor, function(error) {
if (error) {
console.log(error);
}
});
});
});
});
Există și varianta sincronă a metodei [[https://nodejs.org/api/fs.html#fs_fs_ftruncatesync_fd_len|fs.ftruncateSync(fd, len)]].
== Ștergerea unui Fișier ==
Ștergerea unui fișier poate fi realizată în mod asincron folosind metoda [[https://nodejs.org/api/fs.html#fs_fs_unlink_path_callback|fs.unlink(path, callback)]].
Parametrii pe care îî primește metoda sunt:
* ''path'' - calea către fișierul care se dorește a fi șters;
* ''callback'' - funcția de tip callback care este invocată în momentul în care s-a realizat o operație de scriere în fișier, primind ca argumente o eroare (în cazul în care se produce).
var fs = require('fs');
fs.unlink(someFile, function(error) {
if (error) {
return console.error(error);
}
console.log('file has been deleted!');
});
Există și varianta sincronă a metodei [[https://nodejs.org/api/fs.html#fs_fs_unlinksync_path|fs.unlinkSync(path)]].
=== Metode pentru Directoare ===
== Crearea unui Director ==
Crearea unui director poate fi realizată în mod asincron folosind metoda [[https://nodejs.org/api/fs.html#fs_fs_mkdir_path_mode_callback|fs.mkdir(path[, mode], callback)]].
Parametrii pe care îi primește metoda sunt:
* ''path'' - calea către directorul care se dorește a fi creat;
* ''mode'' - modul (permisiunile și biții murdari), implicit ''0777'';
* ''callback'' - funcția de tip callback care este invocată în momentul în care s-a realizat operația de creare a directorului, primind ca argument o eroare (în cazul în care se produce).
var fs = require('fs');
fs.mkdir(someDirectory, function(error) {
if (error) {
return console.error(error);
}
console.log('directory created!');
});
Există și varianta sincronă a metodei [[https://nodejs.org/api/fs.html#fs_fs_mkdirsync_path_mode|fs.mkdirSync(path[, mode])]].
== Ștergerea unui Director ==
Ștergerea unui director poate fi realizată în mod asincron folosind metoda [[https://nodejs.org/api/fs.html#fs_fs_rmdir_path_callback|fs.rmdir(path, callback)]].
Parametrii pe care îi primește metoda sunt:
* ''path'' - calea către directorul care se dorește a fi șters;
* ''callback'' - funcția de tip callback care este invocată în momentul în care s-a realizat operația de ștergere a directorului, primind ca argument o eroare (în cazul în care se produce).
var fs = require('fs');
fs.rmdir(someDirectory, function(error) {
if (error) {
return console.error(error);
}
console.log('directory removed!');
});
Există și varianta sincronă a metodei [[https://nodejs.org/api/fs.html#fs_fs_rmdirsync_path|fs.rmdirSync(path)]].
== Obținerea de Informații cu privire la un Director ==
Obținerea de informații cu privire la un director (lista de fișiere pe care le conține) poate fi realizată în mod asincron folosind metoda [[https://nodejs.org/api/fs.html#fs_fs_readdir_path_callback|fs.readdir(path, callback)]].
Parametrii pe care îi primește metoda sunt:
* ''path'' - calea către directorul pentru care se dorește să se obțină informații;
* ''callback'' - funcția de tip callback care este invocată în momentul în care s-au obținut informații cu privire la director, primind ca argument o eroare (în cazul în care se produce) și o listă de fișiere conținute în cadrul directorului (exclusiv ''.'' și ''..'').
var fs = require('fs');
fs.readdir(someDirectory, function(error, files) {
if (error) {
return console.error(error);
}
files.forEach(function(file) {
console.log(file);
});
});
Există și varianta sincronă a metodei [[https://nodejs.org/api/fs.html#fs_fs_readdirsync_path|fs.readdirSync(path)]].
==== Obiecte Globale ====
În Node.JS, [[https://nodejs.org/api/globals.html|obiectele globale]] sunt disponibile în toate modulele, fără a fi necesar să se încare explicit funcționalități expuse de alte componente.
Accesul la aceste obiecte este realizat nemijlocit, prin referirea acestora.
^ **OBIECT IMPLICIT** ^ **DESCRIERE** ^
| [[https://nodejs.org/api/globals.html#globals_filename|__filename]] | locația (cale absolută) la care se găsește fișierul care este executat în mod curent |
| [[https://nodejs.org/api/globals.html#globals_dirname|__dirname]] | locația (cale absolută) la care se găsește directorul din care face parte fișierul care este executat în mod curent |
| [[https://nodejs.org/api/globals.html#globals_settimeout_cb_ms|setTimeout(cb,ms)]] | utilizată pentru a invoca o funcție de tip callback după o anumită durată; momentul la care va fi executată metoda propriu-zis depinde de modul în care este configurat sistemul de operare precum și de încărcarea mașinii pe care acesta este rulat
var timeout = setTimeout(function() {
console.log('this is a message'_');
}, 1000);
|
| [[https://nodejs.org/api/globals.html#globals_cleartimeout_t|clearTimeout(t)]] | utilizată pentru a opri invocarea unei funcții de tip callback după o anumită durată;
clearTimeout(timeout);
|
| [[https://nodejs.org/api/globals.html#globals_setinterval_cb_ms|setInterval(cb,ms)]] | utilizată pentru a invoca o funcție de tip callback periodic, la un anumit interval; momentul la care va fi executată metoda propriu-zis depinde de modul în care este configurat sistemul de operare precum și de încărcarea mașinii pe care acesta este rulat
var interval = setInterval(function() {
console.log('this is a message'_');
}, 1000);
|
| [[https://nodejs.org/api/globals.html#globals_clearinterval_t|clearInterval(t)]] | utilizată pentru a opri invocarea unei funcții de tip callback periodic, la un anumit interval;
clearInterval(interval);
|
Alte obiecte globale sunt:
* [[https://nodejs.org/api/console.html|Console]] - pentru vizualizarea de informații la terminalele standard ''stdout'' și ''stderr'';
* [[https://nodejs.org/api/process.html|Process]] - pentru gestiunea operațiilor desfășurate în mod curent; este un obiect derivat din ''EventEmitter'' astfel încât pune la dispoziție numeroase evenimente legate de acestea (''beforeExit'', ''exit'', ''message'', ''rejectionHandled'', ''uncaughtException'', ''unhandledRejection'');
==== Module Utilitare ====
Node.JS pune la dispoziția utilizatorilor numeroase module utilitare ale căror funcționalități pot fi accesate pentru dezvoltarea de aplicații web:
* [[https://nodejs.org/api/os.html|Modulul OS]] - oferă informații furnizate de sistemul de operare;
* [[https://nodejs.org/api/path.html|Modulul Path]] - implementează operații pentru gestiunea căilor (absolute și relative);
* [[https://nodejs.org/api/net.html|Modulul Net]] - utilizat pentru funcții ce implică componente de rețea (atât server cât și client), tratete sub formă de fluxuri;
* [[https://nodejs.org/api/dns.html|Modulul DNS]] - folosit pentru regăsirea de informații pe baza unor operații DNS cât și pentru accesarea funcționalităților sistemului de operarare în vederea realizării operațiilor de rezolvare a numelor în adrese Internet;
* [[https://nodejs.org/api/domain.html|Modulul Domain]] - furnizează mecanisme pentru gestiunea simultană a unor operații de intrare/ieșire.
===== Platforma Express =====
[[http://expressjs.com/|Express]] reprezintă o platformă minimală și flexibilă care oferă un set de funcționalități pentru dezvoltarea facilă și rapidă de aplicații web:
* este integrat cu diferite middleware-uri către care delegă procesarea de cereri și de răspunsuri HTTP;
* definește un sistem de rute prin intermediul căruia se stabilește ce acțiune este realizată în funcție de resursa care este solicitată precum și de metoda folosită;
* permite generarea de pagini Internet pe baza unor șabloane către care sunt transmise diverse argumente;
* furnizează acces la informațiile stocate în diferite surse de date.
Platforma Express poate fi instalată global pentru a putea fi folosită împreună cu Node.JS în vederea dezvoltării de aplicații web.
student@aipi2015:~$ npm install express --save
var express = require('express');
var app = express();
app.get('/', function (request, response) {
res.send('Hello, Aipi2015!');
});
app.post('/', function (request, response) {
res.send('Hello, Aipi2015!');
});
var server = app.listen(8080, function () {
var host = server.address().address;
var port = server.address().port;
console.log('Application is available at http://%s:%s', host, port);
});
O astfel de aplicație nu va fi accesibilă decât în contextul rădăcină (http://localhost:8080/), prin metode de tip ''GET'' și ''POST'', pentru toate celelalte contexte / metode furnizându-se codul 404.
==== Accesul la Resurse Statice ====
Accesul la resurse statice (imagini, foi de stil, cod sursă JavaScript) poate fi realizat prin intermediul middleware-ului ''express.static'' care este integrat cu platforma Express.
Astfel, metoda ''use()'' va primi ca parametru rezultatul funcției ''express.static()'' căreia i se va transmite ca argument locația la care se găsesc resursele care vor putea fi referite direct din navigator (întrucât referirea se face relativ la denumirea directorului static, acesta nu va fi inclus în URL atunci când se încearcă regăsirea informațiilor respective).
var express = require('express');
var app = express();
var path = require('path');
app.use(express.static(path.join(__dirname, 'public')));
app.route('/')
.get(function (request, response) {
res.send('Hello, Aipi2015!');
})
.post(function (request, response) {
res.send('Hello, Aipi2015!');
});
De asemenea, un obiect de tipul ''express.Router'' permite definirea de route, care pot fi apoi exportate (devenind accesibile la nivelul întregii aplicații web, activarea acestora fiind realizată prin intermediul unui apel ''require()'' urmată de directiva ''use()''.
==== Gestiunea Obiectelor Cerere / Răspuns ====
Acțiunea asociată unei rute, pentru un anumit tip de cerere HTTP, este reprezentată printr-o metodă de tip callback, care primește ca argumente obiectele de tip cerere și răspuns.
var bodyParser = require('body-parser');
app.use(bodyParser.urlencoded({
extended: true
}));
* ''request.cookies'' - include obiectele de tip cookie din cadrul cererii; implicit, acest atribut are valoarea ''undefined'' și este activat doar în momentul în care se folosește un middleware pentru parsarea obiectelor de tip cookie, cum ar fi ''cookie-parser''
var cookieParser = require('cookie-parser');
app.use(cookieParser);
* ''request.fresh'' - precizează dacă cererea este nouă;
* ''request.hostname'' - conține valoarea câmpului ''Host'' din antetele HTTP;
* ''request.ip'' - stochează adresa Internet a cererii;
* ''request.ips'' - reține o listă de adrese Internet, așa cum apar în câmpul ''X-Forwarded-For'' din antetele HTTP în situația în care configurația referitoare la nivelul de încredere în privința proxy-urilor are valoarea ''true'';
* ''request.originalUrl'' - reține URL-ul cererii, permițând ca valoarea ''request.url'' să fie rescrisă pentru a deservi diferite scopuri legate de mecanismul de rutate;
* ''request.params'' - furnizează parametrii atașați rutei, accesul la aceștia fiind asigurat prin intermediul operatorului de dereferențiere (valoarea implicită este ''{}'');
* ''request.path'' - conține contextul din cadrul URL-ului;
* ''request.protocol'' - conține protocolul din cadrul URL-ului (''http'' sau ''https'');
* ''request.query'' - conține secțiunea de interogare din cadrul URL-ului;
* ''request.route'' - referință către ruta deservită în mod curent (un șir de caractere);
* ''request.secure'' - reprezintă o valoare care indică dacă s-a utilizat o conexiune TLS;
* ''request.signedCookies'' - furnizează o listă de obiecte de tip cookie semnate și transmise prin cerere, care pot fi utilizate;
* ''request.stale'' - precizează dacă cererea este veche;
* ''request.subdomains'' - reține o listă cu subdomenii din cadrul domeniului atașat cererii;
* ''request.xhr'' - include o valoare a cărei valoare este ''true'' în situația în care valoarea atributului ''X-Requested-With'' din antetele HTTP este ''XMLHttpRequest'', indicând faptul că cererea a fost realizată prin intermediul unei biblioteci de tip client.
* metode
* ''request.accepts(types)'' - verifică dacă tipurile de conținut specificate pot fi acceptate pe baza câmpului ''Accept'' din antetele HTTP;
* ''request.get(field)'' - furnizează valoarea asociată pentru un anumit câmp din antetele HTTP;
* ''request.is(type)'' - indică dacă tipul de conținut MIME, transmis ca parametru, este compatibil cu cel specificat în câmpul ''Content-Type'' din antetele HTTP;
* ''request.param(name[, defaultValue])'' - întoarce valoarea unui parametru identificat prin denumire, în situația în care acesta există.
Obiectul de tip ''response'' are următoarea structură:
* proprietăți
* ''res.app'' - referință către instanța aplicației express pe care o folosește middleware-ul;
* ''res.headersSent'' - indică dacă aplicația a transmis antetele HTTP corespunzătoare răspunsului;
* ''res.locals'' - conține variabilele locale aferente răspunsului.
* metode
* ''res.append(field[, value])'' - atașează o anumită valoare la un câmp din antetele HTTP corespunzătoare răspunsului;
* ''res.attachment([filename])'' - transmite un fișier ca atașament la un răspuns;
* ''res.cookie(name, value[, options])'' - adaugă un obiect de tip cookie (pereche de tip atribut, valoare - aceasta putând fi un șir de caractere sau un obiect în format JSON); opțiunile care pot fi precizate includ: ''domain'', ''path'', ''secure'';
* ''res.clearCookie(name[, options])'' - șterge un obiect de tip cookie;
* ''res.download(path[,filename][, fn])'' - transferă fișierul localizat într-o anumită cale sub formă de atașament, astfel încât navigatorul va solicita permisiunea utilizatorului pentru a descărca resursa respectivă;
* ''res.end([data][, encoding])'' - indică faptul că procesul de transmitere a răspunsului a fost încheiat;
* ''res.format(object)'' - realizează negocierea conținutului pe baza proprietății ''Accept'' din cadrul antetelor HTTP, atunci când aceasta este definită;
* ''res.get(field)'' - furnizează valoarea asociată unui câmp din cadrul antetelor HTTP;
* ''res.json([body])'' - folosită pentru transmiterea unui răspuns în format JSON;
* ''res.jsonp([body])'' - utilizată pentru transmiterea unui răspuns în format JSON, cu suport JSONP;
* ''res.links(links)'' - concatenează legăturile furnizate ca argumente și le transmite câmpului ''Links'' din antetele HTTP;
* ''res.location(path)'' - precizează valoarea câmpului ''Location'' din antetele HTTP pe baza căii transmise sub formă de parametru;
* ''res.redirect([status, ]path)'' - redirecționează contextul către URL-ul obținut din calea indicată, folosind un anumit cod de stare HTTP;
* ''res.render(view[, locals][, callback])'' - redă o anumită pagină (șablon), transmițând codul HTML generat pentru a fi vizualizat prin intermediul navigatorului;
* ''res.send([body])'' - transmite un răspuns având un anumit corp (conținut);
* ''res.sendFile(path[, options][, fn])'' - transferă un fișier la o anumită locație, stabilind valoarea câmpului ''Content-Type'' din antetele HTTP pe baza extensiei fișierului;
* ''res.sendStatus(statusCode)'' - trimite un cod de stare precum și reprezentarea sa sub formă de șir de caractere (conținând descrierea sa);
* ''res.set(field[, value])'' - stabilește valoarea unui câmp din antetele HTTP;
* ''res.status(code)'' - indică codul de stare pentru un răspuns;
* ''res.type(type)'' - completează câmpul ''Content-Type'' din antetele HTTP cu tipul MIME corespunzător unei anumite extensii.
==== Utilizarea Motoarelor pentru Șabloane ====
Pentru ca platforma Express să poată reda fișiere de tip șablon, este necesar să se specifice anumite configurații:
* ''views'' reprezintă directorul în care pot fi regăsite fișierele de tip șablon;
* ''view engine'' precizează motorul care va fi utilizat pentru redarea acestor fișiere.
var express = require('express');
var app = express();
var path = require('path');
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
Unul dintre cele mai utilizate motoare pentru redarea șabloanelor parametrizate este [[http://www.embeddedjs.com/|Embedded JavaScript Templates]], având o sintaxă similară cu JavaServer Pages.
Un document EJS conține etichete HTML, codul sursă JavaScript care referă atributele transmise de la nivelul de logică a aplicației fiind încadrat între delimitatorii ''<%'' și ''%>''.
Delimitatorii folosiți în cadrul documentelor EJS pentru a încadra codul sursă JavaScript sunt:
^ **DELIMITATOR** ^ **DESCRIERE** ^
| ''<%'' | utilizat pentru controlul fluxului de execuție al programului ca delimitator de sfârșit; nu produce nici un fel de rezultat la nivelul paginii HTML care este generată pe baza sa |
| ''<%='' | utilizat pentru evaluarea rezultatului expresiei conținute și plasarea acestuia în șablonul obținut, înlocuind caracterele speciale HTML prin codul lor |
| ''<%-'' | utilizat pentru evaluarea rezultatului expresiei conținute și plasarea acestuia în șablonul obținut, fără a înlocui caracterele speciale HTML prin codul lor |
| ''<%#'' | utilizat pentru comentarii, codul sursă JavaScript nu este executat și nici nu produce vreun rezultat |
| ''%%<%%%%'' | utilizat pentru a reda secvența de caractere ''<%'' la nivelul paginii HTML care este generată pe baza sa |
| ''%>'' | utilizat pentru controlul fluxului de execuție al programului ca delimitator de sfârșit |
| ''-%>'' | utilizat pentru eliminarea caracterelor '''\n''', în cazul în care acestea sunt conținute în codul sursă JavaScript |
Există posibilitatea ca acești delimitatori să fie suprascriși, fiind definiți de utilizator. În acest scop, se stabilește valoarea proprietății ''ejs.delimiter'' la valoarea care se dorește a fi utilizată sau se transmite această valoare ca parametru metodei ''ejs.render()'':
var ejs = require('ejs');
ejs.delimiter = '?';
ejs.render(' for (var index = 0; index < shoppingCart.length; index++) { ?>', { shoppingCart: request.session.shoppingCart });
var ejs = require('ejs');
ejs.render(' for (var index = 0; index < shoppingCart.length; index++) { ?>', { shoppingCart: request.session.shoppingCart }, { delimiter: '?' });
În cazul în care pentru o aplicație a fost precizat un director ''views'' care conține șabloanele parametrizate folosind Embedded JavaScript Templates precum și proprietatea corespunzătoare asociată proprietății ''view engine'', încărcarea automată a paginii respective se poate face prin metoda ''render()'' a obiectului ''http.ServerResponse'', prin care se transmit și valorile parametrilor care vor fi referiți.
response.render('login', {
error: ''
});
Alternativ, se pot folosi metodele ''compile()'' și ''template()'', respectiv ''render()'' ale obiectului ''ejs'' care primesc ca parametri șirul de caractere care se dorește a fi interpretat, datele (parametri) care vor fi incluse precum și un set de opțiuni.
var ejs = require('ejs');
var template = ejs.compile(code, options);
template(data);
var ejs = require('ejs');
ejs.render(code, data, options);
<%- include('header') -%>
<%- include('footer') -%>
var ejs = require('ejs');
var lru = require('lru-cache');
ejs.cache = lru(1024);
Dacă se dorește să se elimine conținutul zonei de memorie tampon, se poate utiliza metoda ''clearCache()'' a obiectului ''ejs''. De asemenea, în situația în care este necesar să se utilizeze o altă limită pentru zona de memorie tampon, se va atribui o nouă valoare atributului ''ejs.cache''.
===== Pachetul MySQL =====
Cele mai multe aplicații web își generează conținutul dinamic pe baza informațiilor furnizate prin intermediul unei baze de date. Node.JS este o platformă compatibilă cu numeroase sisteme de gestiune pentru baze de date, existând biblioteci dedicate în acest sens.
Pachetul [[https://www.npmjs.com/package/mysql|mysql]] este un driver Node.JS pentru MySQL, scris în JavaScript, care nu necesită compilare.
Pentru folosirea funcționalităților puse la dispoziție de acest pachet, este necesar să se creeze o conexiune la sistemul de gestiune pentru baze de date MySQL.
var mysql = require('mysql');
var connection = mysql.createConnection({
host: 'localhost',
user: 'root',
password: 'StudentAipi2015',
database: 'bookstore'
});
Parametrii care pot fi indicați pentru o conexiune sunt:
* ''host'' - denumirea gazdei pe care rulează sistemul de gestiune pentru baze de date MySQL la care se realizează conexiunea (implicit, ''localhost'');
* ''port'' - portul pe care sistemul de gestiune pentru baze de date MySQL poate fi invocat (implicit, 3306);
* ''localAddress'' - adresa Internet care va fi utilizată pentru o conexiune TCP (opțional);
* ''socketPath'' - calea către un socket de domeniu unix la care se realizează conexiunea; este ignorat atunci când se folosesc parametri ''host'' și ''port'';
* ''user'' - numele de utilizator cu care se realizează autentificarea;
* ''password'' - parola cu care se realizează autentificarea;
* ''database'' - denumirea bazei de date care va fi utilizată în cadrul conexiunii (opțional);
* ''charset'' - setul de caractere care va fi utilizat în cadrul conexiunii (corespondentul conceptului de //collation//); dacă este specificat un set de caractere la nivel SQL, se va utiliza valoarea implicită asociată acestuia la nivelul sistemului de gestiune pentru baze de date (implicit, ''UTF8_GENERAL_CI'');
* ''timezone'' - zona de timp utilizată pentru stocarea datelor calendaristice (implicit, ''local'');
* ''connectTimeout'' - durata de timp (exprimată în milisecunde) care trebuie să se scurgă înainte de a se genera un eveniment de tip timeout atunci când se realizează o conexiune către sistemul de gestiune pentru baze de date MySQL (implicit, ''10000'');
* ''stringifyObjects'' - transmiterea valorilor ca șiruri de caractere (implicit, ''false'');
* ''insecureAuth'' - determină politica de securitate cu privire la conectarea la instanțe ale sistemului de gestiune pentru baze de date MySQL care solicită o metodă de autentificare nesigură (implicit, ''false'');
* ''typeCast'' - determină dacă valorile din baza de date trebuie să fie convertite la tipuri de date JavaScript native (implicit, ''true'');
* ''queryFormat'' - permite definirea unei funcții pentru utilizarea unui format de interogări definit de utilizator;
* ''supportBigNumbers'' - atribut ce ar trebui utilizat în momentul în care în baza de date există atribute care rețin numere având valori foarte mari (implicit, ''false'');
* ''bigNumberStrings'' - pentru numere având valori foarte mari (numai dacă proprietatea ''supportBigNumbers'' are valoarea ''true'') determină dacă rezultatul preluat din baza de date este convertit sau nu automat la tipul de date șir de caractere:
* ''true'': se realizează întotdeauna conversia la tipul de date șir de caractere:
* ''false'': se realizează conversia la tipul de date șir de caractere numai în situația în care valoarea respectivă nu poate fi reprezentată adecvat, ca număr, în JavaScript (nu se află în intervalul [-253, +253]);
* ''dateStrings'' - determină dacă tipurile de date care rețin informații de tip dată calendaristică vor fi convertite la șiruri de caractere sau la obiecte de tip ''Date'' (implicit, ''false'');
* ''debug'' - determină afișarea unor detalii legate de protocol la consola de ieșire (implicit, ''false'');
* ''trace'' - permite reținerea stivei de execuție în cadrul obiectelor ''Error'' care să includă apelul metodei din cadrul bibliotecii, cu deprecieri ale performanțelor pentru majoritatea apelurilor (implicit, ''true'');
* ''multipleStatements'' - stabilește comportamentul în privința utilizării mai multor expresii MySQL în cadrul aceleiași interogări, ceea ce ar putea reprezenta o breșă de securitate în privința atacurilor prin injecția de SQL malițios (implicit, ''false'');
* ''flags'' - stabilește o listă cu valori care vor controla comportamentul conexiunii; aceștia sunt transmiși sub forma unui șir de caractere în care delimitatorul este caracterul '',''; un flag care este adăugat în plus față de cele implicite trebuie prefixat de caracterul ''+'' iar în situația în care se dorește ca un flag implicit să fie șters, acesta trebuie prefixat de caracterul ''-''; lista de flag-uri implicite este:
* ''CONNECT_WITH_DB'' - permite specificarea denumirii bazei de date în momentul în care este definită o conexiune;
* ''FOUND_ROWS'' - transmite atributul ''found_rows'' al argumentului ''result'' din cadrul metodei de tip callback transmisă prin metoda ''query()'', în loc de ''affected_rows'';
* ''IGNORE_SPACE'' - interpretorul SQL va ignora spațiile care preced caracterul ''('' în cadrul interogărilor;
* ''LOCAL_FILES'' - permite încărcarea înregistrărilor într-o bază de date folosind ''LOAD DATA LOCAL'';
* ''LONG_FLAG'';
* ''LONG_PASSWORD'' - folosește o versiune îmbunătățită a mecanismului de autentificare;
* ''MULTI_RESULTS'' - semnalează posibilitatea de gestiune concurentă a mai multe seturi de rezultate;
* ''PROTOCOL_41'' - folosește versiunea 4.1 a protocolului de conectare la baza de date;
* ''PS_MULTI_RESULTS'' - semnalează posibilitatea de gestiune concurentă a mai multe seturi de rezultate;
* ''SECURE_CONNECTION'' - indică faptul că este suportată mecanismul de autentificare nativ din versiunea 4.1 a protocolului de conectare la baza de date;
* ''TRANSACTIONS'' - solicită utilizarea flag-urilor care indică starea tranzacției;
* ''MULTI_STATEMENTS'' - transmis în mod automat în situația în care parametrul ''multipleStatements'' are valoarea ''true'';
* ''ssl'' - transmite un obiect care conține parametri SSL sau un șir de caracyere care conține denumirea unui profil SSL.
var connection = mysql.createConnection('mysql://root:StudentAipi2015@localhost/bookstore');
connection.connect(function(error) {
if (error) {
console.error('An error has occurred: ' + error.stack);
return;
}
console.log('The connection connect() method was successful!');
});
**Închiderea unei conexiuni** poate fi realizată în două moduri:
* metoda ''end()'' se asigură de faptul că toate interogările care au fost invocate dar pentru care nu s-a obținut încă un rezultat sunt terminate înainte ca legătura la sistemul de gestiune pentru baze de date să fie pierdută; în situația în care execuția uneia dintre interogări produce o eroare, aceasta este transmisă metodei de tip callback:
connection.end(function(error) {
if (error) {
console.error('An error has occurred: ' + error.stack);
return;
}
console.log('The connection end() method was successful!');
});
* metoda ''destroy()'' determină o terminare imediată a legăturii cu sistemul de gestiune pentru baze de date, garantând faptul că nu se vor mai produce alte evenimente și nici nu vor fi invocate metode de tip callback legate de conexiunea în cauză:
connection.destroy();
În situația în care se dorește folosirea unui grup de conexiuni, se poate folosi metoda ''createPool()'' a obiectului ''mysql'' astfel încât numărul de conexiuni este specificat prin intermediul proprietății ''connectionLimit''. Obținerea unei conexiuni se face ulterior prin apelul metodei ''getConnection()'', iar în momentul în care aceasta nu mai este necesară se poate apela metoda ''release()''. Un grup de conexiuni este terminat prin apelul metodei ''end()''.
**Realizarea unei interogări** este realizată de regulă prin intermediul metodei ''query()'' apelată pe obiectul de tip ''Connection'', care suportă următoarele forme:
* ''query(statement, callback)'' - este utilizată de regulă pentru acele interogări care nu folosesc parametri sau pentru care parametrii sunt incluși în interogare; metoda primește ca argument o funcție de tip callback, având ca parametri obiectul eroare (în situația în care aceasta se produce), obiectul care reține rezultatul, acesta având forma unui tablou (chiar dacă rezultatul conține o singură înregistrare) și un obiect în care se regăsesc informații cu privire la câmpurile pentru care s-a realizat interogarea respectivă:
connection.query('SELECT * FROM book', function(error, result, fields) {
if (error) {
console.log('An error has occurred: %s', error);
return;
}
for (var index = 0; index < result.length; index++) {
var book = {
"title": result[index].title,
"subtitle": result[index].subtitle,
"description": result[index].description,
"edition": result[index].edition,
"printing_year": result[index].printing_year,
"collection_id": result[index].collection_id
};
books.push(book);
}
});
În situația în care sunt incluși parametri direct în interogare, se recomandă să se utilizeze metoda ''escape()'' a obiectului ''connection'', ce transformă caracterele care ar putea să aibă un impact asupra interogării în codul corespunzător, care poate fi procesat de interpretorul SQL:
* valorile numerice nu sunt modificate sub nici o formă;
* obiectele ce rețin valori de adevăr sunt convertite la ''true'' / ''false'';
* valorile de tip dată calendaristică sunt transformate în șiruri de caractere având formatul ''YYYY-mm-dd HH:ii:ss'';
* obiectele care rețin informații binare (''Buffer'') sunt convertite la șiruri de caractere, reprezentate sub forma codului hexazecimal;
* pentru șirurile de caractere, sunt convertite caracterele speciale;
* tablourile sunt convertite în șiruri de caractere având forma unei liste, în care delimitatorul este caracterul '',''; în situația în care se folosesc tablouri imbricate, pe baza acestora se vor genera grupuri de liste, în care un grup este încadrat între caracterele ''('' și '')'';
* obiectele sunt transformate în perechi de tip ''atribut'' = ''valoare'' pentru fiecare atribut (enumerabil) al acestuia (funcțiile fiind ignorate), valorile fiind determinate prin apelarea metodei ''toString()''; valorile de tip ''undefined'' sau ''null'' sunt convertite automat la ''NULL''; această funcționalitate poate fi utilizată pentru adăugarea unei valori într-o tabelă direct dintr-un obiect JavaScript, fără a se mai preciza coloanele care sunt folosite;
* valorile de tip ''Nan'' / ''Infinity'' sunt lăsate ca atare, de vreme ce MySQL nu suportă astfel de valori, astfel că în acest caz va fi generată o eroare.
connection.statement('SELECT * FROM book b WHERE b.id = ' + connection.escape(bookId), function(error, result, fields) {
// ...
});
* ''query(statement, parameters, callback)'' - este utilizată de regulă pentru acele interogări care folosesc parametri; faptul că se utilizează un parametru este marcat prin caracterul ''?''; argumentul care indică parametrii are forma unei liste și trebuie să respecte exact ordinea în care aceștia sunt menționați în cadrul interogării
connection.query('SELECT * FROM book b WHERE b.id = ?', [bookId], function(error, result, fields) {
// ...
});
* ''query(options, callback)'' - este utilizat atunci când se dorește să se folosească anumite funcționalități pentru controlul modului în care vor fi furnizate rezultatele:
* ''nestTables'' permite stocarea valorilor corespunzătoare coloanelor având aceeași denumire în momentul în care sunt utilizate operații de asociere; se poate folosi valoarea ''true'' (situație în care denumirea coloanelor este prefixată de denumirea tabelei din care provin) sau ''_'' (caz în care valorile distincte aparținând unor coloane cu aceeași denumire sunt concatenate folosind acest delimitator);
* ''typeCast'' stabilește dacă operația de conversie de la tipurile de date MySQL la tipurile de date JavaScript, realizată automat în mod implicit; este recomandat ca o astfel de facilitate să nu fie dezactivată;
* ''timeout'' indică perioada după care se va genera o eroare dacă nu se furnizează un rezultat în intervalul respectiv;
* ''sql'' reprezintă interogarea care va fi executată;
* ''values'' conține valorile parametrilor, sub forma unei liste, în situația în care interogarea este parametrizată.
connection.query({
sql: 'SELECT * FROM books WHERE b.id = ?',
values: [bookId],
nestTables: true,
typeCast: false, // !!! NOT recommended
timeout: 10000
}, function (error, result, fields) {
// ...
});
* ''query(options, parameters, callback)'' - este utilizat pentru interogări parametrizate, atunci când valorile acestora sunt furnizate distinct, nu în cadrul atributului ''values'':
connection.query({
sql: 'SELECT * FROM books WHERE b.id = ?',
nestTables: true,
typeCast: false, // !!! NOT recommended
timeout: 10000
}, [bookId], function (error, result, fields) {
// ...
});
În cazul interogărilor parametrizate, în situația în care pe post de parametrii se folosesc anumite câmpuri pentru care nu se aplică regulile de transformare obișnuite, se folosește șirul de caractere ''??''. De asemenea, șirul de caractere poate fi pregătit local, înainte de a fi transmis către sistemul de gestiune pentru baze de date MySQL, prin intermediul metodei ''format()'':
var unformatted_statement = 'SELECT * FROM ?? WHERE ?? = ?';
var parameters = ['book', 'id', bookId];
var formatted_statement = mysql.format(unformatted_statement, parameters);
// ...
Pentru operațiile de **adăugare**, **modificare**, **ștergere**, se pot determina informații legate de identificatorul valorii care este adăugată, respectiv de numărul de înregistrări care au fost modificate / șterse, ca proprietăți ale obiectului ''result'' transmis ca parametru al funcției de tip callback în momentul în care este invocată metoda ''query()'':
* ''insertId'' - stochează identificatorul înregistrării care a fost adăugată în baza de date în urma unei interogări de tip ''INSERT'' transmisă ca parametru al metodei ''query()'':
var book = {
"title": title,
"subtitle": subtitle,
"description": description,
"edition": edition,
"printing_year": printing_year,
"collection_id": collection_id
};
var query = connection.query('INSERT INTO book SET ?', book, function(error, result) {
if (error) {
console.log('An error has occurred: %s', error);
return;
}
console.log('The record has been successfully inserted with id %d', result.insertId);
});
console.log(query.sql);
* ''changedRows'' - determină numărul de înregistrări care au fost modificate în urma unei interogări de tip ''UPDATE'' transmisă ca parametru al metodei ''query()'':
var query = connection.query('UPDATE book SET edition = edition + 1 WHERE printing_year > ?', [printingYear], function(error, result) {
if (error) {
console.log('An error has occurred: %s', error);
return;
}
console.log('%s records have been successfully updated', result.changedRows);
});
* ''affectedRows'' - determină numărul de înregistrări care au fost șterse în urma unei interogări de tip ''DELETE'' transmisă ca parametru al metodei ''query()'':
var query = connection.query('DELETE FROM book WHERE collection_id IN ?', [collectionId], function(error, result) {
if (error) {
console.log('An error has occurred: %s', error);
return;
}
console.log('%s records have been successfully deleted', result.affectedRows);
});
===== Activitate de Laborator =====
==== Structura aplicației web BookStore ====
Se doreşte implementarea unei interfeţe grafice cu utilizatorul (dezvoltată sub forma unei aplicaţii web, folosind platforma Node.JS) pentru gestiunea informaţiilor reţinute într-o bază de date, aceasta urmând a fi utilizată de un sistem ERP destinat unei librării care comercializează doar cărţi.
Sistemul informatic va putea fi accesat de:
* un utilizator tip **administrator**, care va manipula – prin intermediul aplicaţiei – informaţiile din baza de date; acesta are la dispoziție operații de adăugare, editare și ștergere pentru oricare dintre tabele;
* un utilizator tip **client**, care va formula o comandă după ce şi-a specificat un anumit coş de cumpărături ca urmare a consultării catalogului de produse; pe baza comenzii va fi emisă o factură (''invoice_header''), caracterizată printr-un număr de identificare (''identification_number''), data la care a fost emisă (''issue_date''), stare (''state'') și identificatorul utilizatorului către care va fi transmisă (''user_id''); aceasta conține la rândul ei mai multe înregistrări, corespunzătoare cărților care au fost cumpărate (''invoice_line''), definite de factura corespunzătoare (''invoice_header_id''), identificatorul formatului de prezentare al cărții (''book_presentation_id'') și cantitatea (''quantity'').
{{ :laboratoare:laborator06:bookstore_webapplication_structure.png?nolink&200 }}
Structura proiectului BookStore include următoarele componente:
* în rădăcină:
* fișierul ''main.js'' - reprezintă codul sursă care va fi executat (folosind executabilul ''node''), aici fiind realizate diferite configurări, indicându-se rutele prin intermediul cărora vor fi procesate anumite pagini și lansându-se în execuție serverul http (pe un anumit port), pe baza căruia vor fi gestionate cererile și răspunsurile:
* se indică faptul că aplicația va folosi framework-ul Express, precum și directorul în care se găsesc resursele care vor putea fi accesate (imagini, foi de stil, șabloane):
var express = require('express');
var app = express();
app.use(express.static(path.join(__dirname, 'public')));
* se precizează care este directorul în care se găsesc paginile care vor fi vizualizate precum și motorul care va realiza această operație (în cazul de față, [[https://www.npmjs.com/package/ejs|ejs (Embedded JavaScript Templates)]]):
var path = require('path');
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
* se specifică faptul că aplicația va folosi modulele:
* ''body-parser'' pentru prelucrarea parametrilor dintr-o cerere / răspuns:
var bodyParser = require('body-parser');
app.use(bodyParser.urlencoded({
extended: true
}));
* ''serve-favicon'' pentru folosirea unei pictograme pentru aplicația web:
var favicon = require('serve-favicon');
app.use(favicon(__dirname + '/public/images/favicon.ico'));
* ''express-session'' pentru gestiunea sesiunilor - parametrii ''saveUninitialized'' și ''resave'' sunt folosiți pentru a indica transmiterea în cadrul sesiunii a variabilelor neinițializate și pentru persistența lor de-a lungul mai multor cicluri cerere / răspuns:
var session = require('express-session');
app.use(session({
secret: 'StudentAipi2015',
saveUninitialized: true,
resave: true
}));
* ''events'' pentru folosirea de evenimente - se construiește un obiect de tip EventsEmitter prin intermediul căruia vor fi gestionate evenimentele (acesta va fi transmis ca parametru paginilor, pentru a se evita obținerea de noi instanțe în momentul în care pagina este încărcată din nou)
var events = require('events');
var EventEmitter = new events.EventEmitter();
* se indică rutele (codul sursă) care va fi executat pentru fiecare pagină în parte, precum și parametrii care sunt transmiși acestora:
require('./routes/login')(app, EventEmitter);
require('./routes/administrator')(app, EventEmitter);
require('./routes/client')(app, EventEmitter);
* se lansează în execuție serverul HTTP pe un anumit port:
app.listen(8080);
* fișierul ''package.json'' - conține informații cu privire la aplicația web (denumire, versiune, cuvinte-cheie, autor precum și modulele folosite, descrise sub formă de dependențe - pentru fiecare atribut se indică denumirea și versiunea care se dorește a fi utilizată)
module.exports = function(app, EventEmitter) {
app.route('/')
.get(function(request, response) {
processLogin(request, response, EventEmitter);
})
.post(function(request, response) {
processLogin(request, response, EventEmitter);
});
};
* directorul ''views'' - conține șabloanele pentru paginile web care vor fi vizualizate, în care urmează să fie plasate informațiile provenite în urma procesării datelor în modulele de logică a aplicației.
Pentru fiecare funcționalitate există o pagină dedicată, gestionată de câte o rută distinctă, prin intermediul căreia este implementată logica aplicației (în directorul ''routes'') și un document prin intermediul căruia sunt vizualizate informațiile (în directorul ''views'').
Astfel, există o pagină de autentificare (''login'') de unde, în funcţie de rolul utilizatorului se trece la pagina de tip administrator (''administrator'') respectiv pagina de tip client (''client''). Pentru fiecare pagină, cererea primită din navigator este preluată de către modulul corespunzător, unde sunt realizate procesările ce țin de logica aplicației, inclusiv accesarea informațiilor din baza de date. Prezentarea informațiilor astfel obținute este delegată către documentul aferent, către care se transferă contextul. Datele care se doresc a fi vizualizate sunt transmise dinspre modul către document sub formă de atribute. În acest mod se construiește răspunsul care va fi trimis către navigator.
Atributele care sunt transmise între module și documente sunt:
- login.js → login.ejs
* ''error'' - furnizează informații cu privire la rezultatul operației de autentificare a unui utilizator în cadrul aplicației web (succes, eșec);
- administrator.js → administrator.ejs
* ''display'' - identificatorul utilizatorului autentificat în cadrul aplicației web (format din prenume și nume);
* ''currentTable'' - denumirea tabelei vizualizată la un moment dat de timp;
* ''databaseStructure'' - structura bazei de date, formată din lista tuturor tabelelor;
* ''attributes'' - structura tabelei vizualizată la un moment dat de timp, conținând lista tuturor atributelor;
* ''identifier'' - denumirea identificatorului în lista de atribute;
* ''identifierNextValue'' - valoarea identificatorului unei înregistrări pentru adăugare;
* ''tableContent'' - conținutul tabelei vizualizate la un moment dat de timp, conținând lista tuturor înregistrărilor, pentru fiecare înregistrare fiind reținute valorile tuturor atributelor;
* ''identifierRecordToBeUpdated'' - valoarea identificatorului unei înregistrări selectate pentru modificare sau ștergere, în situația în care aceasta a fost selectată;
- client.js → client.ejs
* ''display'' - identificatorul utilizatorului autentificat în cadrul aplicației web (format din prenume și nume);
* ''recordsPerPageValue'' - numărul maxim de înregistrări care poate fi vizualizat pe o pagină la un moment dat;
* ''previousRecordsPerPageValue'' - valoarea anterioară pentru numărul maxim de înregistrări care poate fi vizualizat pe o pagină la un moment dat; această informație este necesară pentru a se determina dacă este necesar ca valoarea paginii curente să fie reinițializată;
* ''recordsPerPageValues'' - lista cu valorile posibile pentru numărul maxim de înregistrări care poate fi vizualizat pe o pagină la un moment dat;
* ''pageValue'' - pagina vizualizată la un moment dat;
* ''formatsList'' - lista cu toate formatele de prezentare disponibile;
* ''formatsFilter'' - lista cu criteriile de sortare în funcție de formatul de prezentare;
* ''languagesList'' - lista cu toate limbile disponibile;
* ''languagesFilter'' - lista cu criteriile de sortare în funcție de limbă;
* ''categoriesList'' - lista cu toate categoriile disponibile;
* ''categoriesFilter'' - lista cu criteriile de sortare în funcție de categorie;
* ''books'' - lista cărților care vor fi vizualizate, împreună cu toate atributele, în funcție de criteriile de filtrare care au fost precizate;
* ''errorMessage'' - mesaj de eroare legat de procesarea coșului de cumpărături;
* ''shoppingCart'' - conținutul coșului de cumpărături (identificatorul formatului de prezentare și cantitatea solicitată);
{{ :laboratoare:laborator04:bookstore_webapplication_loginservlet.png?nolink&300 }}
student@aipi2015:~$ git clone https://www.github.com/aipi2015/Laborator06.git
**1**. Să se ruleze, folosind MySQL Workbench (sau alt utilitar similar), scriptul ''Laborator06l.sql'', localizat în directorul ''scripts''. Acesta instalează baza de date ''bookstore''.
student@aipi2015:~/06-BookStore-NodeJS$ npm install
npm WARN package.json 06-BookStore-NodeJS@1.0.0 No description
npm WARN package.json 06-BookStore-NodeJS@1.0.0 No repository field.
npm WARN package.json 06-BookStore-NodeJS@1.0.0 No README data
npm WARN package.json 06-BookStore-NodeJS@1.0.0 No license field.
ejs@1.0.0 node_modules/ejs
serve-favicon@2.3.0 node_modules/serve-favicon
├── fresh@0.3.0
├── etag@1.7.0
├── ms@0.7.1
└── parseurl@1.3.0
mysql@2.9.0 node_modules/mysql
├── bignumber.js@2.0.7
└── readable-stream@1.1.13 (string_decoder@0.10.31, isarray@0.0.1, inherits@2.0.1, core-util-is@1.0.2)
express-session@1.12.1 node_modules/express-session
├── cookie-signature@1.0.6
├── utils-merge@1.0.0
├── on-headers@1.0.1
├── cookie@0.2.3
├── parseurl@1.3.0
├── depd@1.1.0
├── crc@3.3.0
├── debug@2.2.0 (ms@0.7.1)
└── uid-safe@2.0.0 (base64-url@1.2.1)
body-parser@1.8.4 node_modules/body-parser
├── media-typer@0.3.0
├── raw-body@1.3.0
├── bytes@1.0.0
├── depd@0.4.5
├── qs@2.2.4
├── iconv-lite@0.4.4
├── on-finished@2.1.0 (ee-first@1.0.5)
└── type-is@1.5.7 (mime-types@2.0.14)
express@4.13.3 node_modules/express
├── escape-html@1.0.2
├── merge-descriptors@1.0.0
├── cookie@0.1.3
├── array-flatten@1.1.1
├── cookie-signature@1.0.6
├── utils-merge@1.0.0
├── methods@1.1.1
├── content-type@1.0.1
├── vary@1.0.1
├── fresh@0.3.0
├── etag@1.7.0
├── range-parser@1.0.3
├── serve-static@1.10.0
├── path-to-regexp@0.1.7
├── content-disposition@0.5.0
├── parseurl@1.3.0
├── depd@1.0.1
├── qs@4.0.0
├── debug@2.2.0 (ms@0.7.1)
├── on-finished@2.3.0 (ee-first@1.1.1)
├── finalhandler@0.4.0 (unpipe@1.0.0)
├── proxy-addr@1.0.8 (forwarded@0.1.0, ipaddr.js@1.0.1)
├── send@0.13.0 (destroy@1.0.3, ms@0.7.1, statuses@1.2.1, mime@1.3.4, http-errors@1.3.1)
├── accepts@1.2.13 (negotiator@0.5.3, mime-types@2.1.7)
└── type-is@1.6.9 (media-typer@0.3.0, mime-types@2.1.7)
**3.** Modificați în fișierele ''login.js'', ''administrator.js'', ''client.js'' din directorul ''routes'' informațiile necesare obținerii drepturilor de acces la baza de date (nume de utilizator, parola).
**4.** Să se configureze mediul integrat de dezvoltare NetBeans 8.1 astfel încât acesta să fie interfațat cu platforma Node.JS.
student@aipi2015:/usr/local/nodejs$ npm install express-generator -g
/usr/local/nodejs/bin/express -> /usr/local/nodejs/lib/node_modules/express-generator/bin/express
express-generator@4.13.1 /usr/local/nodejs/lib/node_modules/express-generator
├── sorted-object@1.0.0
├── commander@2.7.1 (graceful-readlink@1.0.1)
└── mkdirp@0.5.1 (minimist@0.0.8)
Calea către platforma Express trebuie să fie referită în cadrul câmpului //Express Path//.
{{ :laboratoare:laborator06:netbeans03.png?nolink }}
**5.** Să se testeze aplicaţia prin accesarea adresei [[http://localhost:8080/|]].
Se poate consulta script-ul ''Laborator06l.sql'' există exemple de utilizatori care pot fi folosite pentru accesarea paginilor de administrator, respectiv de client.
* administrator: mary.smith / -
* client: linda.williams / -
**6.** Să se acceseze pagina de tip administrator şi să se testeze funcţionalităţile implementate în cadrul acesteia (adăugare, editare, ştergere).
**7.** Să se acceseze pagina de tip client. Să se implementeze funcționalitatea corespunzătoare filtrării în funcție de limbile în care sunt disponibile cărțile din librărie. Un utilizator poate adăuga o valoare din lista de limbi disponibile. Filtrarea se face în funcție de limbile selectate, pentru acele formate de prezentare a cărții care conțin cel puțin una dintre aceste valori. În situația în care nu s-a specificat nici o valoare, sunt incluse toate volumele comercializate. Un utilizator poate șterge una sau mai multe dintre limbile selectate anterior.
{{ :laboratoare:laborator04:activitate_de_laborator_exercitiu07.png?nolink }}
<% for (var index = 0; index < languagesFilter.length; index++) {
// ...
} %>
Valoarea unui element al listei poate fi obținută prin evaluarea unei expresii, încadrată între elementele ''<%= ... %>'': ''<%= languagesFilter[index] %>''.
În fișierul ''client.js'' localizat în directorul ''routes'', se tratează acțiunile corespunzătoare evenimentelor de tip apăsare a butoanelor ''insert'', respectiv ''delete'' corespunzătoare filtrului în funcție de limba în care a fost selectată cartea.
* pentru parametrii având denumirea de tipul ''insert_language'', se obține denumirea limbii (ca valoare atașată parametrului ''currentLanguage'', inclus în corpul cererii ''request.body['currentLanguage]''), se verifică dacă aceasta nu este conținută în lista de limbi în funcție de care se face filtrarea și, în caz afirmativ, este adăugată la listă, marcându-se faptul că lista de filtre a fost modificată (parametrul ''filterChange'' de tip ''boolean'');
* pentru parametrii având denumirea de tipul ''delete_language_{language_name}'', se obține denumirea limbii (prin parsarea denumirii parametrului), se verifică dacă aceasta este conținută în lista de limbi în funcție de care se face filtrarea și, în caz afirmativ, este ștearsă din listă, marcându-se faptul că lista de filtre a fost modificată (parametrul ''filterChange'' de tip ''boolean'').
În cazul butonului de tip ''insert'', se poate verifica direct faptul că acesta este inclus în corpul cererii întrucât acesta este unic:
if (request.body['insert_language.x']) {
// ..
}
În cazul butonului de tip ''delete'', se iterează asupra tuturor parametrilor incluși în corpul cererii, verificându-se dacă aceștia au formatul indicat, situație în care se parsează parametrul respectiv pentru a se determina denumirea limbii:
var delete_language;
for (var parameter in request.body) {
if (parameter.startsWith('delete_language_') && parameter.endsWith('.x')) {
delete_language = parameter.substring(parameter.lastIndexOf('_') + 1, parameter.indexOf('.x'));
}
}
Verificarea faptului că un element aparține unui tablou se face prin intermediul metodei [[http://www.w3schools.com/jsref/jsref_indexof.asp|indexOf()]] prin care se determină poziția elementului în tablou, dacă este cazul. O valoare -1 indică faptul că elementul nu aparține tabloului.
var position = languagesFilter.indexOf(language);
if (position === -1) {
// ...
}
if (position !== -1) {
// ...
}
Adăugarea într-un tablou se face prin metoda [[http://www.w3schools.com/jsref/jsref_push.asp|push()]], care primește ca parametru elementul respectiv.
Ștergerea dintr-un tablou se face prin metoda [[http://www.w3schools.com/jsref/jsref_slice_array.asp|slice()]] care primește ca parametri poziția de la care se realizează operația precum și numărul de elemente care se doresc a fi eliminate.
if (parameter.startsWith('insert_shoppingcart') && parameter.endsWith('.x')) {
bookPresentationId = parameter.substring(parameter.lastIndexOf('_') + 1, parameter.indexOf('.x'));
}
if (parameter.startsWith('insert_shoppingcart') && parameter.endsWith('.x')) {
quantity = request.body['copies_shoppingcart_' + bookPresentationId];
}
connection.query(statement, [parameters], function(error, result) {
if (error) {
console.log('An error has occurred: %s', error);
}
// ...
});
Interogarea care va fi folosită este:
SELECT bp.price AS price, b.title AS title, f.value AS value, l.name AS name
FROM book_presentation bp, book b, format f, language
WHERE bp.id = ? AND b.id = bp.book_id AND f.id = bp.format_id AND l.id = bp.language_id
și va primi ca parametru identificatorul formatului de prezentare.
Obiectul care va fi plasat în coșul de cumpărături are următoarea formă:
var record = {
bookPresentationId: bookPresentationId,
quantity: quantity,
price: result[0].price,
title: result[0].title,
format: result[0].value,
language: result[0].name
};
<% if (shoppingCart.length !== 0) { %>
<% } else { %>
The shopping cart is empty!
<% } %>
Se iterează asupra conținutului coșului de cumpărături (obiectul ''shoppingCart''), determinându-se, pentru fiecare element, cantitatea, prețul, titlul, valoarea, formatul de prezentare și limba. Este necesar să se calculeze și prețul total al coșului de cumpărături, într-o variabilă, prin însumarea tuturor valorilor corespunzătoare volumelor care se doresc a fi achiziționate.
<% for (var index = 0; index < shoppingCart.length; index++) {
var currentBookValue = shoppingCart[index].quantity * shoppingCart[index].price; %>
<% shoppingCartValue += currentBookValue;
} %>
Prezentarea se va face în cadrul unui tabel (element de tip [[http://www.w3schools.com/tags/tag_table.asp|| ... |