Cara membuat server web sederhana hanya menggunakan instruksi nodejs standar
Seringkali, server web sederhana diperlukan untuk mengembangkan aplikasi MPA / SPA / PWA. Suatu hari, pada rapat umum besar menanggapi pertanyaan: "Apa yang kamu lakukan?", Saya mengatakan bahwa saya sedang meningkatkan server web untuk hosting aplikasi PWA. Kami semua tertawa untuk waktu yang lama dan ya, omong-omong, PWA bukanlah lem. Seperti SPA, ini bukan salon kecantikan. Ini semua jenis aplikasi web. Dan SSR bukan negara :-). Jika Anda meluncurkan aplikasi seperti itu hanya dengan membuka halaman awal index.html melalui browser, itu tidak akan berfungsi sebagaimana mestinya, dalam skenario kasus terbaik kita akan mendapatkan versi offline. Saya suka JavaScript dan akan menyelesaikan masalah hanya dengan menggunakan sarana yang tersedia untuk saya, jadi untuk berbicara di luar kotak.
Mari kita mulai dengan rencananya:
- Jika tidak ada NodeJS, unduh LTS, instal, jangan ubah pengaturan, klik berikutnya
- Di tempat terpencil kami, tempat semua proyek dikumpulkan, buat folder server web sederhana
- Dalam folder proyek, jalankan perintah npm init --yes // tanpa --yes, initializer akan menanyakan banyak pertanyaan
- Dalam file package.json di bagian skrip , tambahkan properti dan nilainya - "main": "index.js" - sehingga kami dapat dengan cepat memulai server kami menggunakan perintah run npm
- Buat folder lib Disarankan untuk memasukkan semua kode Anda ke dalamnya, yang tidak memerlukan perakitan dan langkah tambahan untuk operasinya
- Buat file index.js di folder lib . Ini adalah server kami di masa depan.
- Buat folder dist - ini akan menjadi folder di mana akan ada file yang dapat diakses publik, termasuk index.html, dengan kata lain, statis yang akan didistribusikan server kami
- Buka file /index.js
- Tulis beberapa kode
Jadi apa yang kita ketahui tentang apa yang harus dilakukan server kita?
- Tangani permintaan
- Baca file
- Balas untuk meminta dengan konten file
Pertama, buat server kami, impor ke file idex.js
const {createServer} = require('http');
Instruksi ini merusak objek modul http dan memberikan ekspresi, fungsi createServer , ke pengenal variabel createServer .
Buat server baru menggunakan pernyataan berikut
const server = createServer();
Ketika Anda pertama kali pergi ke host server, browser mengirim permintaan untuk menerima dokumen. Oleh karena itu, untuk memproses acara ini, kita perlu mendengarkan permintaan tersebut. Server yang kami buat memiliki metode mendengarkan , sebagai parameter kami melewati nomor port 3000.
const eventsEmitter = server.listen(3000);
Ekspresi metode ini akan menjadi objek EventEmitter yang akan disimpan dalam variabel dengan identifier eventsEmitter . Objek ini bisa diamati. Kami berlangganan aktivitasnya menggunakan pemanggilan metode on / addEventListener dengan dua parameter fungsi string yang diperlukan. Parameter pertama menunjukkan acara mana yang menarik bagi kami; permintaan yang kedua adalah fungsi yang akan memproses acara ini.
eventsEmitter.on('request', (req, res) => { debugger; });
Buka tautan di browser

Jadi, kami sepakat dengan instruksi debugger. Kita melihat bahwa sebagai parameter kita mendapatkan dua objek req , res . Objek ini adalah instance dari objek tipe stream, oleh karena itu req adalah stream read, dan res adalah stream tulis.
Kami memproses permintaan itu dan kami dapat mengatakan bahwa "masalahnya ada di topi." Yang tersisa adalah membaca dan mengembalikan file. Pertama, Anda perlu memahami jenis file apa yang kami butuhkan. Di debugger, setelah mempelajari semua properti dari parameter req , saya melihat bahwa ia memiliki properti url . Tetapi hanya tidak ada yang seperti index.html di dalamnya .
Mari kita lihat lagi browser kita: kita melihat bahwa kita belum mengindikasikan ini secara eksplisit. Mari kita coba lagi, tetapi kita sudah secara eksplisit menentukan index.html .

Sekarang jelas bahwa nama file datang dalam permintaan di properti url dan tidak lebih dari ini, kami tidak perlu membaca file dari permintaan. Kami merusaknya menggunakan sepasang kurung kurawal, tentukan nama properti url dan, melalui operator :, tetapkan nama arbitrer menggunakan pengenal variabel yang valid, dalam kasus saya requestUrl .
eventsEmitter.addListener('request', ({url: requestUrl}, res) => { debugger });
Hebat, selanjutnya apa? Sebenarnya, saya tidak terlalu suka fakta bahwa index.html akan selalu perlu ditentukan secara eksplisit, jadi mari kita selesaikan masalah ini segera. Saya memutuskan bahwa cara termudah untuk melakukan ini adalah dengan menggunakan fungsi extname standar , termasuk dalam paket standar
NodeJS dari modul path yang kami impor menggunakan pernyataan berikut.
const {extname} = require('path');
Sekarang Anda dapat memanggilnya dengan mengirimkan ekspresi requestUrl identifier sebagai parameter dan mendapatkan ekspresi untuk string dari format perkiraan '.extension'
. Jika permintaan tidak secara spesifik menentukan file, string kosong akan dikembalikan. Dengan menggunakan prinsip ini, kami akan menambahkan nilai default 'index.html' . Kami menulis instruksi berikut
const url = extname(requestUrl) === '' ? DEFAULT_FILE_NAME : requestUrl;
Saya yakin bahwa pengguna server akan ingin menimpa nama ini dan juga mengatur variabel lingkungan menggunakan pernyataan berikut
const {env: {DEFAULT_FILE_NAME = '/index.html'}} = process;`
dalam proses variabel global banyak informasi yang berguna, saya hanya mengubur sebagian darinya, khususnya, properti env yang berisi semua properti dari lingkungan pengguna dan kami akan mencari DEFAULT_FILE_NAME di dalamnya jika pengguna tidak menentukannya - kami menggunakan index.html secara default.
PENTING: jika nilai properti lingkungan DEFAULT_FILE_NAME sama sekali tidak terdefinisi, menetapkan nilai default tidak akan berfungsi. Ini patut diingat, tetapi tidak sekarang, kami melakukan semuanya seminimal mungkin :-)

Sekarang kita memiliki tautan relatif ke file, kita perlu mendapatkan path absolut ke file di sistem file server kita. Kami memutuskan bahwa semua file publik akan disimpan di folder dist , jadi untuk mendapatkan path absolut ke file tersebut , kami akan menggunakan fungsi tekad lain dari modul yang sudah kita ketahui
path cukup mengindikasikannya dalam instruksi yang dibuat sebelumnya pada baris 5
const {resolve, extname} = require('path');
Selanjutnya pada baris 10, kita menulis instruksi yang akan menerima dan menyimpan path absolut ke variabel filePath. Saya juga "wang" sebelumnya bahwa nama folder ini dapat didefinisikan ulang untuk fleksibilitas. Oleh karena itu, saya memperluas instruksi pada baris 6, menambahkan nama
variabel lingkungan DIST_FOLDER !

Sekarang semuanya siap untuk membaca file. Anda dapat membaca file dengan berbagai cara Asinkron, Sinkron, atau Anda dapat menggunakan stream . Saya akan menggunakan stream :-) itu indah dan lebih efektif, dari sudut pandang sumber daya yang dikeluarkan. Pertama, buat file uji di folder dist sehingga ada sesuatu untuk dibaca :-)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> TESTING 1,2,3... </body> </html>
Sekarang kita membutuhkan fungsi yang akan membuat aliran pembacaan file, itu juga termasuk dalam distribusi NodeJS standar, kita mengekstraknya dari modul fs menggunakan instruksi berikut
const {createReadStream} = require('fs');
dan pada baris 12 di badan prosesor permintaan kami menggunakan pernyataan berikut
createReadStream(filePath)
sebagai hasilnya, instance objek read-stream akan dikembalikan menggunakannya
kita dapat mengalihkan aliran baca ke aliran tulis, kita juga dapat mengubah aliran dan banyak hal berguna lainnya. Jadi parameter res adalah aliran baca, kan?
Mari kita coba untuk segera beralih aliran baca file yang kami buat ke aliran res tulis
dari ini, pada baris 12 kami melanjutkan instruksi dengan memanggil metode pipa , dan sebagai parameter kami melewati res stream stream kami
createReadStream(filePath).pipe(res);

Hanya itu semua Tidaaaak Dan siapa yang akan menangani kesalahan? Kesalahan macam apa? Mari mencoba mengunggah file css ke file index.html, tetapi kami tidak akan membuatnya dan melihat apa yang terjadi :-)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="index1.css"> </head> <body> TESTING 1,2,3... </body> </html>

Diharapkan, tetapi server macet! Ini tidak terjadi sama sekali. Faktanya adalah bahwa secara default kesalahan tidak terperangkap dalam aliran dan Anda perlu melakukan ini sendiri :-) createReadStream mengembalikan aliran di mana kesalahan terjadi. Oleh karena itu, kami menambahkan penangan kesalahan. Menggunakan panggilan ke metode aktif Menentukan nama acara kesalahan dan pawang fungsi. Ini akan menghentikan aliran baca baca dengan kode respons 404.
createReadStream(filePath) .on('error', error => res.writeHead(404).end()) .pipe(res);
periksa!

Satu hal lagi. Ngomong-ngomong, server belum siap dan jika kami mencoba membukanya di browser lain, halaman tidak akan berfungsi dengan benar :-) siapa yang menebaknya, silakan tulis di komentar, apa yang Anda lupa lakukan? Faktanya adalah ketika server menjawab permintaan server dengan file, satu ekstensi tidak cukup bagi browser untuk memahami jenis file ini dan browser lainnya: baik chrome maupun versi yang lebih lama tidak akan bekerja dengan file yang diunduh tanpa menentukan header respons Tipe-Konten. memproses file, antara lain, server kami harus menentukan jenis MIME. Untuk melakukan ini, kami akan membuat variabel terpisah dengan semua jenis Mei yang umum. Kami juga memberikan kesempatan untuk memperluas mereka dengan melewati sebagai variabel lingkungan
const {env: {DEFAULT_FILE_NAME = '/index.html', DIST_FOLDER = 'dist', DEFAULT_MIME_TYPES = '{}'}} = process; const {text} = mimeTypes = { 'html': 'text/html', 'jpeg': 'image/jpeg', 'jpg': 'image/jpeg', 'png': 'image/png', 'js': 'text/javascript', 'css': 'text/css', 'text': 'plain/text', 'json': 'application/json', ...JSON.parse(DEFAULT_MIME_TYPES) };
Nah, sekarang Anda perlu menentukan tipe MIME sebelum mengalihkan aliran baca ke aliran tulis. Saya menggunakan nama ekstensi file sebagai kunci, jadi kita akan mendapatkan ekstensi file dengan fungsi extname yang sudah dikenal
const fileExtension = extname(url).split('.').pop();
dan dengan bantuan pengendali acara pipa , kami mengatur jenis MIME yang diinginkan
res.on('pipe', () => res.setHeader(contentType, mimeTypes[fileExtension] || text));
Periksa

Itu saja - server sudah siap. Tentu saja itu tidak sempurna, tetapi untuk permulaan yang cepat, itu saja. Jika Anda tertarik mengembangkan ide ini, silakan tulis di komentar :-)
Kode proyek lengkap