Artikel ini adalah ide terbaru yang telah saya kembangkan selama proses pembelajaran Node.js, dan saya akan membahasnya dengan Anda.
Node.js server http
Menggunakan Node.js dapat digunakan untuk menerapkan layanan HTTP dengan sangat mudah. Contoh paling sederhana adalah seperti contoh situs web resmi:
Salinan kode adalah sebagai berikut:
var http = membutuhkan ('http');
http.createServer (function (req, res) {
res.writeHead (200, {'tipe konten': 'teks/polos'});
res.end ('halo dunia/n');
}). Dengarkan (1337, '127.0.0.1');
Ini dengan cepat membangun layanan web yang mendengarkan semua permintaan HTTP di port 1337.
Namun, dalam lingkungan produksi nyata, kami biasanya jarang menggunakan Node.js secara langsung sebagai server web front-end untuk pengguna. Alasan utamanya adalah sebagai berikut:
1. Berdasarkan fitur tunggal Node.js, jaminan ketahanannya relatif tinggi untuk pengembang.
2. Layanan HTTP lainnya di server mungkin sudah menempati port 80, dan layanan web yang bukan port 80 jelas tidak cukup ramah pengguna kepada pengguna.
3.Node.js tidak memiliki banyak keuntungan dalam pemrosesan file IO. Misalnya, sebagai situs web reguler, mungkin mengharuskan Anda untuk menanggapi sumber daya file seperti gambar secara bersamaan.
4. Skenario muatan terdistribusi juga merupakan tantangan.
Oleh karena itu, menggunakan Node.js sebagai layanan web mungkin lebih cenderung menjadi antarmuka server game dan skenario serupa lainnya, sebagian besar untuk menangani layanan yang tidak memerlukan akses pengguna langsung dan hanya melakukan pertukaran data.
Layanan Web Node.js Berdasarkan Nginx sebagai mesin front-end
Berdasarkan alasan di atas, jika itu adalah produk berbentuk situs web yang dibangun dengan Node.js, cara konvensional untuk menggunakannya adalah dengan menempatkan server HTTP dewasa lainnya di ujung depan layanan web Node.js, seperti Nginx adalah yang paling umum digunakan.
Kemudian gunakan Nginx sebagai proxy terbalik untuk mengakses layanan web berbasis Node.js. menyukai:
Salinan kode adalah sebagai berikut:
server {
Dengarkan 80;
server_name yekai.me;
root/home/andy/wwwroot/yekai;
Lokasi / {
proxy_pass http://127.0.0.1:1337;
}
Lokasi ~ /.(GIF|JPG|PNG|SWF|Co|cs|js)$ {
root/home/andy/wwwroot/yekai/static;
}
}
Ini akan lebih baik menyelesaikan beberapa masalah yang diangkat di atas.
Komunikasi menggunakan protokol fastcgi
Namun, ada beberapa hal yang tidak terlalu baik tentang metode proxy di atas.
Salah satunya adalah skenario yang mungkin membutuhkan akses HTTP langsung ke layanan web Node.js yang perlu dikendalikan nanti. Namun, jika Anda ingin menyelesaikan masalah, Anda juga dapat menggunakan layanan Anda sendiri atau mengandalkan firewall untuk memblokirnya.
Alasan lain adalah bahwa metode proxy adalah solusi di lapisan aplikasi jaringan, dan tidak lebih mudah untuk secara langsung mendapatkan dan memproses data berinteraksi dengan HTTP klien, seperti pemrosesan tetap hidup, batang dan bahkan cookie. Tentu saja, ini juga terkait dengan kemampuan dan kesempurnaan fungsional server proxy itu sendiri.
Jadi, saya berpikir untuk mencoba cara lain untuk menghadapinya. Hal pertama yang saya pikirkan adalah metode FastCGI yang biasa digunakan dalam aplikasi Web PHP sekarang.
Apa itu fastcgi
Fast Common Gateway Interface (FastCGI) adalah protokol yang memungkinkan program interaktif untuk berkomunikasi dengan server web.
Latar belakang yang dihasilkan oleh FastCGI digunakan sebagai alternatif untuk aplikasi Web CGI. Salah satu fitur yang paling jelas adalah proses layanan FastCGI dapat digunakan untuk menangani serangkaian permintaan. Server web akan menghubungkan variabel lingkungan dan permintaan halaman ini ke server web melalui soket seperti proses FastCGI. Koneksi dapat dihubungkan ke server web dengan soket domain UNIX atau koneksi TCP/IP. Untuk pengetahuan latar belakang lebih lanjut, silakan merujuk ke entri Wikipedia.
Implementasi FastCGI dari Node.js
Jadi secara teori kita hanya perlu menggunakan Node.js untuk membuat proses FastCGI, dan kemudian menentukan bahwa permintaan mendengarkan Nginx dikirim ke proses ini. Karena Nginx dan Node.js keduanya adalah model layanan yang digerakkan oleh acara, mereka harus menjadi solusi "teoretis" untuk mencocokkan dunia. Mari kita lakukan sendiri.
Di Node.js, modul NET dapat digunakan untuk membuat layanan soket. Demi kenyamanan, kami memilih metode soket UNIX.
Dengan sedikit modifikasi konfigurasi Nginx:
Salinan kode adalah sebagai berikut:
...
Lokasi / {
fastcgi_pass unix: /tmp/node_fcgi.sock;
}
...
Buat file baru node_fcgi.js, dengan konten berikut:
Salinan kode adalah sebagai berikut:
var net = membutuhkan ('net');
var server = net.createServer ();
server.listen ('/tmp/node_fcgi.sock');
server.on ('connection', function (sock) {
console.log ('koneksi');
sock.on ('data', function (data) {
console.log (data);
});
});
Kemudian jalankan (karena izin, pastikan bahwa skrip nginx dan node dijalankan dengan pengguna atau akun yang sama dengan izin bersama, jika tidak, Anda akan mengalami masalah izin saat membaca dan menulis file kaus kaki):
node node_fcgi.js
Saat mengakses browser, kami melihat bahwa terminal yang menjalankan skrip simpul biasanya menerima konten data, seperti ini:
Salinan kode adalah sebagai berikut:
koneksi
<Buffer 01 01 00 01 00 08 00 00 00 01 00 00 00 00 00 00 01 04 00 01 01 87 01 ...>
Ini membuktikan bahwa landasan teoretis kami telah mencapai langkah pertama. Selanjutnya, kita hanya perlu mencari cara untuk menguraikan konten buffer ini.
Fastcgi Protocol Foundation
Catatan FastCGI terdiri dari awalan panjang tetap diikuti oleh jumlah konten dan byte empuk. Struktur catatan adalah sebagai berikut:
Salinan kode adalah sebagai berikut:
typedef struct {
versi char yang tidak ditandatangani;
jenis char yang tidak ditandatangani;
unsigned char requestIdb1;
unsigned char requestIdb0;
ContentLengthB1 char yang tidak ditandatangani;
ContentLengthB0 char yang tidak ditandatangani;
Paddinglength char yang tidak ditandatangani;
Char yang tidak ditandatangani dicadangkan;
ContentData Char Unsigned [ContentLength];
Paddingdata char yang tidak ditandatangani [Paddinglength];
} Fcgi_record;
Versi: Versi Protokol FastCGI, sekarang gunakan 1 secara default
Jenis: Jenis catatan sebenarnya dapat dianggap sebagai keadaan yang berbeda, dan itu akan dibahas secara rinci nanti
Permintaan: ID Minta, itu harus sesuai saat kembali. Jika bukan kasus konkurensi multiplexing, cukup gunakan 1 di sini
ContentLength: Panjang konten, panjang maksimum di sini adalah 65535
Paddinglength: Panjang bantalan digunakan untuk mengisi data panjang ke dalam kelipatan integer dari 8 byte penuh. Ini terutama digunakan untuk memproses data yang selaras lebih efektif, terutama untuk pertimbangan kinerja
Dicadangkan: byte yang dipesan untuk ekspansi berikutnya
ContentData: Data konten nyata, mari kita bicarakan secara detail nanti
Paddingdata: Isi data, itu adalah 0, abaikan saja secara langsung.
Untuk struktur dan deskripsi tertentu, silakan merujuk ke dokumen situs web resmi (http://www.fastcgi.com/devkit/doc/fcgi-pec.html#s3.3).
Meminta bagian
Tampaknya sangat sederhana, hanya menguraikan dan mendapatkan data dalam sekali jalan. Namun, ada lubang di sini, yaitu, apa yang didefinisikan di sini adalah struktur unit data (catatan), bukan seluruh struktur buffer. Seluruh buffer terdiri dari satu catatan dan satu catatan. Pada awalnya, mungkin tidak mudah bagi siswa yang terbiasa dengan pengembangan front-end, tetapi ini adalah dasar untuk memahami protokol FastCGI, dan kita akan melihat lebih banyak contoh nanti.
Oleh karena itu, kita perlu mengurai catatan dan membedakan catatan berdasarkan jenis yang kita peroleh sebelumnya. Berikut adalah fungsi sederhana untuk mendapatkan semua catatan:
Salinan kode adalah sebagai berikut:
fungsi getRCDS (data, cb) {
var rcds = [],
mulai = 0,
panjang = data.length;
return function () {
if (start> = length) {
CB && CB (RCDS);
rcds = null;
kembali;
}
var end = mulai + 8,
header = data.slice (start, end),
Versi = header [0],
type = header [1],
requestId = (header [2] << 8) + header [3],
contentLength = (header [4] << 8) + header [5],
paddinglength = header [6];
start = end + contentLength + Paddinglength;
var body = ContentLength? data.slice (end, contentLength): null;
rcds.push ([jenis, tubuh, permintaan]);
return argumen.callee ();
}
}
//menggunakan
sock.on ('data', function (data) {
getRCDS (data, fungsi (RCDS) {
}) ();
}
Perhatikan bahwa ini hanyalah proses yang sederhana. Jika ada situasi kompleks seperti mengunggah file, fungsi ini tidak cocok untuk demonstrasi paling sederhana. Pada saat yang sama, parameter permintaan juga diabaikan. Jika multiplexing, itu tidak dapat diabaikan, dan pemrosesan harus jauh lebih rumit.
Selanjutnya, catatan yang berbeda dapat diproses sesuai dengan jenisnya. Definisi tipe adalah sebagai berikut:
Salinan kode adalah sebagai berikut:
#define fcgi_begin_request 1
#define fcgi_abort_request 2
#define fcgi_end_request 3
#define fcgi_params 4
#define fcgi_stdin 5
#define fcgi_stdout 6
#define fcgi_stderr 7
#define fcgi_data 8
#define fcgi_get_values 9
#define fcgi_get_values_result 10
#define fcgi_unknown_type 11
#define fcgi_maxtype (fcgi_unknown_type)
Selanjutnya, Anda dapat mengurai data nyata sesuai dengan jenis yang direkam. Saya hanya akan menggunakan fcgi_params yang paling umum digunakan, fcgi_get_values, dan fcgi_get_values_result untuk diilustrasikan. Untungnya, metode analisis mereka konsisten. Parsing catatan jenis lain memiliki aturannya sendiri yang berbeda, dan Anda dapat merujuk pada definisi spesifikasi untuk mengimplementasikannya. Saya tidak akan membahas detailnya di sini.
FCGI_PARAMS, FCGI_GET_VALUES, FCGI_GET_VALUES_RESULT adalah semua jenis data "Value-Value" yang dikodekan. Format standar adalah: ditransmisikan dalam bentuk panjang nama, diikuti oleh panjang nilai, diikuti oleh nama, diikuti oleh nilainya, di mana 127 byte atau kurang dapat dikodekan dalam satu byte, sementara panjang yang lebih panjang selalu dikodekan dalam empat byte. Bit tinggi byte pertama dengan panjang menunjukkan bagaimana panjang dikodekan. Bit tinggi 0 berarti metode pengkodean byte, dan 1 berarti metode pengkodean empat-byte. Mari kita lihat contoh yang komprehensif, seperti kasus nama panjang dan nilai pendek:
Salinan kode adalah sebagai berikut:
typedef struct {
Char namelengthb3 yang tidak ditandatangani; / * naMeLengthB3 >> 7 == 1 */
unsigned char namelengthb2;
char namelengthb1 yang tidak ditandatangani;
char namelengthb0 yang tidak ditandatangani;
unsigned char valuelengthb0; / * valuelengthb0 >> 7 == 0 */
Char unsigned namedata [namelength
((B3 & 0x7f) << 24) + (B2 << 16) + (B1 << 8) + B0];
unsigned char hormata [valuelength];
} Fcgi_namevaluepair41;
Contoh Metode Implementasi JS yang sesuai:
Salinan kode adalah sebagai berikut:
fungsi parseparams (body) {
var j = 0,
params = {},
panjang = body.length;
while (j <length) {
nama var,
nilai,
namelength,
hal valuele;
if (body [j] >> 7 == 1) {
naMeLength = ((body [j ++] & 0x7f) << 24)+(body [j ++] << 16)+(tubuh [j ++] << 8)+tubuh [j ++];
} kalau tidak {
naMeLength = body [j ++];
}
if (body [j] >> 7 == 1) {
valuelength = ((body [j ++] & 0x7f) << 24)+(tubuh [j ++] << 16)+(tubuh [j ++] << 8)+tubuh [j ++];
} kalau tidak {
valuelength = body [j ++];
}
var ret = body.asciislice (j, j + namelength + valuelength);
name = ret.substring (0, namelength);
value = ret.substring (nameLength);
params [name] = value;
j + = (nameLength + valuelength);
}
mengembalikan params;
}
Ini mengimplementasikan metode sederhana untuk mendapatkan berbagai parameter dan variabel lingkungan. Tingkatkan kode sebelumnya dan tunjukkan bagaimana kami bisa mendapatkan IP klien:
Salinan kode adalah sebagai berikut:
sock.on ('data', function (data) {
getRCDS (data, fungsi (RCDS) {
untuk (var i = 0, l = rcds.length; i <l; i ++) {
var bodydata = rcds [i],
type = bodydata [0],
tubuh = bodydata [1];
if (body && (type === type.fcgi_params || type === type.fcgi_get_values || type === type.fcgi_get_values_result)) {
var params = parseparams (tubuh);
console.log (params.remote_addr);
}
}
}) ();
}
Sejauh ini kami telah memahami dasar -dasar bagian permintaan fastcgi, dan kemudian kami akan menerapkan bagian respons dan akhirnya menyelesaikan layanan balasan gema sederhana.
Bagian respons
Bagian respons relatif sederhana. Dalam kasus paling sederhana, Anda hanya perlu mengirim dua catatan, yaitu, FCGI_STDOUT dan FCGI_END_REQUEST.
Saya tidak akan menggambarkan konten spesifik entitas, lihat saja kode:
Salinan kode adalah sebagai berikut:
var res = (function () {
var maxlength = math.pow (2, 16);
function buffer0 (len) {
kembalikan buffer baru ((array baru (len + 1)). gabungkan ('/u0000'));
};
function writestdout (data) {
var rcdstdouthd = buffer baru (8),
contentLength = data.length,
paddinglength = 8 - ContentLength % 8;
rcdstdouthd [0] = 1;
rcdstdouthd [1] = type.fcgi_stdout;
rcdstdouthd [2] = 0;
rcdstdouthd [3] = 1;
rcdstdouthd [4] = contendlength >> 8;
rcdstdouthd [5] = contendlength;
rcdstdouthd [6] = paddinglength;
rcdstdouthd [7] = 0;
return buffer.concat ([rcdstdouthd, data, buffer0 (paddinglength)]);
};
fungsi writeHttphead () {
return writestdout (buffer baru ("http/1.1 200 ok/r/ncontent-tipe: teks/html; charset = utf-8/r/nconnection: tutup/r/n/r/n"));
}
fungsi writeHttpBody (bodystr) {
var Bodybuffer = [],
tubuh = buffer baru (bodystr);
untuk (var i = 0, l = body.length; i <l; i + = maxlength + 1) {
Bodybuffer.push (writestdout (body.slice (i, i + maxlength)));
}
return buffer.concat (Binybuffer);
}
fungsi writeend () {
var rcdendhd = buffer baru (8);
rcdendhd [0] = 1;
rcdendhd [1] = type.fcgi_end_request;
rcdendhd [2] = 0;
rcdendhd [3] = 1;
rcdendhd [4] = 0;
rcdendhd [5] = 8;
rcdendhd [6] = 0;
rcdendhd [7] = 0;
return buffer.concat ([rcdendhd, buffer0 (8)]);
}
return function (data) {
return buffer.concat ([writeHttphead (), writeHttpBody (data), writeend ()]);
};
}) ();
Dalam kasus paling sederhana, ini akan memungkinkan Anda untuk mengirim respons penuh. Ubah kode terakhir kami:
Salinan kode adalah sebagai berikut:
var pengunjung = 0;
server.on ('connection', function (sock) {
Pengunjung ++;
sock.on ('data', function (data) {
...
var kueri = queryString.parse (params.query_string);
var ret = res ('welcome,' + (querys.name || 'dear friend') + '! Anda adalah nomor' + pengunjung + 'dokumen ~');
sock.write (ret);
ret = null;
sock.end ();
...
});
Buka browser dan kunjungi: http: // domain/? Name = yekai, dan Anda dapat melihat sesuatu seperti "selamat datang, yekai! Anda adalah pengguna ke -7 dari situs ini ~".
Pada titik ini, kami berhasil menerapkan layanan FastCGI paling sederhana menggunakan Node.js. Jika perlu digunakan sebagai layanan nyata, kita hanya perlu membandingkan spesifikasi protokol untuk meningkatkan logika kita.
Tes komparatif
Akhirnya, pertanyaan yang perlu kita pertimbangkan adalah apakah solusi ini secara khusus layak? Beberapa siswa mungkin telah melihat masalahnya, jadi saya akan meletakkan hasil tes tekanan sederhana terlebih dahulu:
Salinan kode adalah sebagai berikut:
// Metode Fastcgi:
500 klien, berjalan 10 detik.
Kecepatan = 27678 halaman/mnt, 63277 byte/dtk.
Permintaan: 3295 Susceed, 1318 Gagal.
500 klien, berjalan 20 detik.
Kecepatan = 22131 halaman/mnt, 63359 byte/dtk.
Permintaan: 6523 Susceed, 854 Gagal.
// metode proxy:
500 klien, berjalan 10 detik.
Kecepatan = 28752 halaman/mnt, 73191 byte/dtk.
Permintaan: 3724 Susceed, 1068 Gagal.
500 klien, berjalan 20 detik.
Kecepatan = 26508 halaman/menit, 66267 byte/dtk.
Permintaan: 6716 Susceed, 2120 Gagal.
// Langsung mengakses metode layanan node.js:
500 klien, berjalan 10 detik.
Kecepatan = 101154 halaman/mnt, 264247 byte/dtk.
Permintaan: 15729 Susceed, 1130 gagal.
500 klien, berjalan 20 detik.
Kecepatan = 43791 halaman/mnt, 115962 byte/dtk.
Permintaan: 13898 Susceed, 699 Gagal.
Mengapa metode proxy lebih baik dari metode FastCGI? Itu karena di bawah skema proxy, layanan backend dijalankan langsung oleh modul asli Node.js, dan skema FastCGI diimplementasikan oleh diri kita sendiri menggunakan JavaScript. Namun, juga dapat dilihat bahwa tidak ada kesenjangan besar dalam efisiensi antara kedua solusi (tentu saja, perbandingan di sini hanyalah situasi sederhana. Jika kesenjangan lebih besar dalam skenario bisnis nyata), dan jika Node.js secara asli mendukung layanan FastCGI, efisiensinya harus lebih baik.
nota bene
Jika Anda tertarik untuk terus bermain, Anda dapat memeriksa kode sumber contoh yang saya terapkan dalam artikel ini. Saya telah mempelajari spesifikasi protokol dalam dua hari terakhir, tetapi tidak sulit.
Pada saat yang sama, saya akan kembali dan bermain dengan UWSGI, tetapi pejabat itu mengatakan bahwa V8 sudah siap untuk secara langsung mendukungnya.
Saya memiliki permainan yang sangat dangkal. Jika ada kesalahan, harap perbaiki saya dan berkomunikasi.