node的出現,真是讓用慣js的前端工程師碰見了愛情,進而大踏步的走向了後端,儘管有時候會被質疑,不被理解。那又有什麼關係。javascript
本文是《一站到底 ---前端基礎之網絡》 代碼的整理。但也是一篇獨立的node零基礎學習筆記。 首先你須要安裝node環境。你們本身去看教程 就好。本文和函數式編程那篇文章是同樣的思路。咱們先用先實現。若是有機會咱們回過頭再來補理論,其實API也沒啥須要補,有時間咱們寫寫node異步隊列和DC的算法,但你有什麼不明白的能夠隨着查看文檔 。好的,老規矩,咱們看看,本文都完成了那些內容。css
本文代碼在github html
const net = require('net');
let server = net.createServer((socket)=>{
socket.on('data',function (res) {
console.log(res.toString())
});
});
server.listen({
host: 'localhost',
port: 8080
});
複製代碼
這裏咱們講一下node的事件機制:前端
//events 模塊只提供了一個對象: events.EventEmitter
//EventEmitter 的核心就是事件觸發與事件監聽器功能的封裝。
var EventEmitter = require('events').EventEmitter;
//一個socket對象
var socket = new EventEmitter();
//咱們在socket對象上綁定data事件,若是是多個函數會被前後調用
socket.on('data', function(res) {
console.log(res);
});
socket.on('data', function(res) {
console.log(res + '111');
});
//咱們用emit的方法去觸發事件,在1秒後咱們出發,咱們觸發事件時,能夠傳遞參數。
setTimeout(function() {
socket.emit('data' , "hello" );
}, 1000);
複製代碼
咱們會在控制檯看到下面的信息。java
這時咱們會過頭來看,瀏覽器左下角,是否是一直在顯示等待響應,是由於咱們尚未返回數據啊,那咱們給 它返回一些數據。咱們知道要符合http格式。node
咱們將一段符合http格式的數據用socket.write(responseDataTpl)去返回數據git
let responseDataTpl = `HTTP/1.1 200 OK
Connection:keep-alive
Date: ${new Date()}
Content-Length: 12
Content-Type: text/plain
Hello world!
`;
複製代碼
問題:咱們已經發現了寫出固定格式的http響應報文杯仍是比較麻煩的,咱們爲何不能封裝一層呢?github
const http = require('http');
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('hello world'); // 發送響應數據
})
server.on('clientError', (err, socket) => {
socket.end('HTTP/1.1 400 Bad Request\r\n\r\n')
})
server.listen(10080)
複製代碼
問題:那麼這個時候,若是我但願傳進去是一個文件而不是字符串,改怎麼辦呢?ajax
node的文件模塊是很是強大的,能夠對文件進行讀取,增刪改查。這裏咱們先講如何讀取的。讀取分兩種一種同步,一種異步。算法
const fs = require('fs');
const http = require('http');
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/html' });
// 同步
// let data = fs.readFileSync('index.html');
// res.write(data);
// res.end(); // 發送響應數據
// 異步
fs.readFile('index.html', function (err, data) {
res.write(data);
res.end(); // 發送響應數據
})
})
server.on('clientError', (err, socket) => {
socket.end('HTTP/1.1 400 Bad Request\r\n\r\n')
})
server.listen(8088)
複製代碼
問題:那麼如今有一個問題,不管是同步仍是異步,咱們都須要先讀文件,再寫入,那麼文件很大時,對內存的壓力就會很是大。咱們有沒有什麼辦法,邊讀取邊寫入?
Stream 是一個抽象接口,做用就是能把文件,讀一點寫一點。這樣不就不用佔很大內存了。咱們來看看怎麼實現的?
const fs = require('fs');
const http = require('http');
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/html' });
// let resStream = fs.createReadStream('index.html');
// resStream.pipe(res);
//流是能夠支持鏈式操做的
fs.createReadStream('index.html').pipe(res)
})
server.on('clientError', (err, socket) => {
socket.end('HTTP/1.1 400 Bad Request\r\n\r\n')
})
server.listen(10080)
複製代碼
問題:在咱們解決了內存問題後,你會發現,咱們index.html中是有一張圖片沒有加載出來的。緣由很簡單。由於不管發送什麼請求,咱們都只返回一樣的操做。那麼咱們能如何區分不一樣的請求呢?
咱們知道在應用成協議中用URL來表示文件的位置。區分不一樣請求的一個重要任務就是區分路徑。那麼對路徑的處理node中提供了一個url模塊,讓咱們來看看吧。
const fs = require('fs');
const http = require('http');
const url = require("url");
const server = http.createServer((req, res) => {
//pathname是取到端口號後面的地址
let pathname = url.parse(req.url).pathname;
if(pathname === '/') pathname = '/index.html';
let resPath = '.' + pathname;
//判斷路徑是否存在
if(!fs.existsSync(resPath)){
res.writeHead(404, {'Content-Type': 'text/html'});
return res.end('<h1>404 Not Found</h1>');
}
//若是存在,將在路徑下的文件返回給頁面
res.writeHead(200, { 'Content-Type': 'text/html' });
fs.createReadStream(resPath).pipe(res)
})
server.on('clientError', (err, socket) => {
socket.end('HTTP/1.1 400 Bad Request\r\n\r\n')
})
server.listen(10080)
複製代碼
問題:那麼如今,咱們就能在瀏覽器上看見咱們美麗的大娟的圖片了,可是咱們在學http的時候知道Content-Type是處理文件類型的,那麼圖片類型確定不會是'text/html' ,雖然瀏覽器很智能幫我顯示出來了,可是咱們仍是要把這樣的錯誤改過來。
咱們知道,只要改變 'Content-Type'的文件類型便可。
function getFileType(resPath){
const EXT_FILE_TYPES = {
'default': 'text/html',
'.js': 'text/javascript',
'.css': 'text/css',
'.json': 'text/json',
'.jpeg': 'image/jpeg',
'.jpg': 'image/jpg',
'.png': 'image/png',
//...
}
let path = require('path');
let mime_type = EXT_FILE_TYPES[path.extname(resPath)] || EXT_FILE_TYPES['default'];
return mime_type;
}
複製代碼
sudo npm install supervisor -g
supervisor 02-http-fs-url.js
複製代碼
問題:咱們大娟的圖片才只有一百多K,若是是圖片很大咱們是能夠先壓縮再傳輸的
(1)咱們先取出請求頭中的accept-encoding參數,若是參數不存在,咱們賦值成''
let acceptEncoding = req.headers['accept-encoding'];
if (!acceptEncoding) { acceptEncoding = '';};
複製代碼
(2)而後咱們用正則去判斷acceptEncoding是否用了gzip壓縮,固然這裏能夠有多個判斷壓縮格式。這裏咱們只寫一個。
if(/\bgzip\b/.test(acceptEncoding)){
//執行壓縮,並在響應頭中告訴瀏覽器壓縮的格式
}else{
//不執行壓縮
}
複製代碼
(3)咱們須要引用zlib模塊對文件進行壓縮。這裏咱們用Gzip,就調用Gzip的方法。 而後咱們對文件流先進行一步壓縮,在寫到響應體中。
const zlib = require('zlib');
let raw = fs.createReadStream(resPath);
raw.pipe(zlib.createGzip()).pipe(res);
複製代碼
(4)最後咱們還須要在響應頭中告訴瀏覽器個人文件已經給你壓縮成什麼格式啦
'Content-Encoding': gzip
複製代碼
而後咱們開兩個終端分別用啓動有gzip和沒有gzip壓縮的
home文件中放了一張我在頤和園用相機拍的5M的圖片
你能夠打開多個瀏覽器窗口,分別先訪問兩個文件,能夠多測幾遍,你會發現有gzip壓縮的明顯要慢。
爲何會這樣呢,道理很簡單,由於咱們的服務器和瀏覽器都在同一臺電腦上,傳輸速度很快。因此壓縮和解壓的時間就被放大啦。這也告訴咱們並非什麼場景都適合對文件進行壓縮的。
這一節沒有node的新知識,咱們對http瀏覽器緩存協議進行一個實現。咱們也不須要進行壓縮,因此上一節壓縮的內容不會加。
**(1)強緩存 ** 強緩存咱們在響應頭中給一個一週的過時時間 參考代碼cache.js
Cache-Control : max-age = 604800' 複製代碼
**(2)弱緩存 ** 參考代碼cache2.js
etag須要一個雙引號的字符串,而後咱們把它寫入響應頭中
let etagStr = "dajuan"; //etag 要加雙引號
res.writeHead(200, {
'Content-Type': getFileType(resPath),
'etag' : etagStr
});
複製代碼
當再次訪問的時候咱們須要判斷一下,if-none-match帶的值於如今etagStr值是否一致。若是一致直接返回304,不用在返回文件。瀏覽器看到304,就知道了要從緩存中拿。
let etagStr = "dajuan"; //etag 要加雙引號
if(req.headers['if-none-match'] === etagStr){
res.writeHead(304, {
'Content-Type': getFileType(resPath),
'etag' : etagStr
});
res.end();
}
複製代碼
(1)咱們首先分別用get 和 post 寫一個表單提交,讓其點擊都跳轉到form_result.html,有一行你好,name
//form.html
<form action="form_result.html" method="get">
<p> get: <input type="text" name="name" /></p>
<input type="submit" value="Submit" />
</form>
<form action="form_result.html" method="post">
<p> post: <input type="text" name="name" /></p>
<input type="submit" value="Submit" />
</form>
//form_result.html
<div>你好,name</div>
複製代碼
(2)get方法去處理 參考代碼method.js
let pathAll = url.parse(req.url);
let getArgument = pathAll.query; //取出參數 name=XXX
if(pathname === '/form_result.html' && getArgument != undefined){
let text = fs.readFileSync('form_result.html').toString().replace(/name/, getArgument)
fs.writeFileSync('form_result.html',text)
}
複製代碼
這時候get提交的表單能夠去處理啦,可是post的參數並無在URL中,因此對post沒有影響
(3)post方法去處理 參考代碼method2.js
req.on('data',(data)=>{
let text = fs.readFileSync('form_result.html').toString().replace(/name/, 'post'+ data)
fs.writeFileSync('form_result.html',text)
})
複製代碼
這裏咱們留一個問題,咱們在處理文件的時候是同步處理的,若是異步處理咱們改怎麼作?
參考代碼:cors.js cors2.js
if(req.headers['origin'] ) {
res.writeHead(200, {
'Access-Control-Allow-Origin': 'http://localhost:5000',
'Content-Type': 'text/html'
});
return fs.createReadStream(resPath).pipe(res)
};
複製代碼
注: 這裏仍是有點小問題,第一我只在第一次訪問時,若是端口不符合提示報錯了。我懷疑是否是瀏覽器給服務器地址加入白名單了。第二爲何不是書上寫的兩次求情啊。我第一次即便不寫數據,也不會發起第二次請求。不過跨域的效果仍是實現了的。
知道了原理後,咱們在終端生成證書和私鑰吧。
(1)openssl genrsa -out server.key 1024 //生成服務器私鑰
(2)openssl rsa -in server.key -pubout -out server.pem // 生成公鑰
//本身扮演CA機構,給本身服務器頒發證書,CA機構也須要本身私鑰,CSR文件(證書籤名請求文件),和證書
(3) openssl genrsa -out ca.key 1024 //生成CA 私鑰
openssl req -new -key ca.key -out ca.csr //生成CA CSR文件
openssl x509 -req -in ca.csr -signkey ca.key -out ca.crt //生成CA 證書
//生成證書籤名請求文件
(4) openssl req -new -key server.key -out server.csr //生成server CSR文件
//向本身的機構請求生成證書
(5) openssl x509 -req -CA ca.crt -CAkey ca.key -CAcreateserial -in server.csr -out server.crt //生成server 證書
複製代碼
注意:信息隨便填,但提示裏有格式要注意啊,寶寶們。。。
const https = require('https');
const fs = require('fs');
const options = {
key: fs.readFileSync('./key/server.key'),
cert: fs.readFileSync('./key/server.crt')
};
https.createServer(options, (req, res) => {
res.writeHead(200);
res.end('hello world\n');
}).listen(8000);
複製代碼
服務器訪問: https://localhost:8000/
node的http2是試驗的API。若是node版本比較低,請先升級。個人是v8.11.3
const http2 = require('http2');
const fs = require('fs');
const server = http2.createSecureServer({
key: fs.readFileSync('./key/server.key'),
cert: fs.readFileSync('./key/server.crt')
});
server.on('error', (err) => console.error(err));
server.on('stream', (stream, headers) => {
// stream is a Duplex
stream.respond({
'content-type': 'text/html',
':status': 200
});
stream.end('<h1>Hello World</h1>');
});
server.listen(8443);
複製代碼
這樣咱們就完成了一個最簡單的http2的訪問啦。