請求響應原理及HTTP協議學習筆記

1. 服務器端基礎概念

1.1網站組成

  • 網站應用程序主要分爲兩大部分:客戶端和服務器端。
  • 客戶端:在瀏覽器中運行的部分,就是用戶看到並與之交互的界面程序。使用HTML、CSS、JavaScript構建。
  • 服務器端:在服務器中運行的部分,負責存儲數據和處理應用邏輯。
    在這裏插入圖片描述

1.2Node網站服務器

能夠提供網站訪問服務的機器就是網站服務器,它能夠接收客戶端的請求(request),能夠對請求做出響應(respond)

1.3 IP地址、域名和端口

IP地址:是指互聯網中設備的唯一標識。這裏不多敘述,大家應該都瞭解一些~

域名:是指平時上網所使用的網址。如:www.baidu.com,雖然在地址欄中輸入的是網址, 但是最終還是會將域名轉換爲ip才能訪問到指定的網站服務器。
例如:http://www.baidu.com => http://220.181.38.148/

端口:是指計算機與外界通訊交流的出口,用來區分服務器電腦中提供的不同的服務。
在這裏插入圖片描述
因爲大多數網站應用使用的是80端口號,如果輸入域名或IP訪問網站時沒有輸入端口號,瀏覽器在請求的時候默認在輸入的域名或IP地址後面接的是80端口號,如果是非80端口號,在IP地址或域名後面加上端口號即可。

1.4 URL
統一資源定位符,又叫URL(Uniform Resource Locator),是專爲標識Internet網上資源位置而設的一種編址方式,我們平時所說的網頁地址指的即是URL。在URL中標註了要請求的服務器地址,提供服務的端口及要請求的資源位置。

URL的組成
傳輸協議://服務器IP或域名:端口/資源所在位置標識
https://editor.csdn.net/md?articleId=106287460
http:超文本傳輸協議,提供了一種發佈和接收HTML頁面的方法。

1.5開發過程中客戶端和服務器端說明
在開發階段,客戶端和服務器端使用同一臺電腦,即開發人員電腦。
在這裏插入圖片描述
我們自己電腦的服務器(本地)可以通過一組特殊的IP或域名來訪問
本機域名:localhost
本機IP地址:127.0.0.1

2. 創建web服務器

我們使用nodejs當中創建網站服務器。做服務器端開發如果沒有網站服務器,一切都無從談起。
在電腦當中,我們要先安裝好node軟件,參考node學習筆記,再用nodejs創建軟件層面上的服務器,得到請求對象和響應對象。

創建網站服務器實例代碼如下圖:

//引入http創建網站服務器系統模塊
const http = require('http');

//創建網站服務器對象
const app = http.createServer();

//當客戶端有請求來的時候
app.on('request',(req,res) => {   
	//響應
    res.end('hello');
    
});

//監聽的端口號
app.listen(3000);
console.log('網站服務器啓動成功');

保存代碼,打開PowerShell窗口,切換到當前目錄,使用nodemon xxx.js(當前文件名),這裏也可以用node命令,但是建議用nodemon,這樣每次修改保存文件後可以自動運行,無需手動再去運行一遍

nodemon test.js

在這裏插入圖片描述
打開瀏覽器,在網址欄輸入localhost:3000,測試成功,響應內容成功顯示出來。(如果你響應的內容是中文字符,此時代碼還不夠完善所以顯示出來的不是想要的效果,這個問題後面會提到)
在這裏插入圖片描述

3. HTTP協議

3.1 HTTP協議的概念
超文本傳輸協議(英文:HyperText Transfer Protocol,縮寫:HTTP)規定了如何從網站服務器傳輸超文本到本地瀏覽器,它基於客戶端服務器架構工作,是客戶端(用戶)和服務器端(網站)請求和應答的標準。
在這裏插入圖片描述
3.2 報文
在HTTP請求和響應的過程中傳遞的數據塊就叫報文,包括要傳送的數據和一些附加信息,並且要遵守規定好的格式。
在這裏插入圖片描述
拿博客首頁來看看
在這裏插入圖片描述
在這裏插入圖片描述
信息量挺大的,可以瞭解一些

3.3 請求報文
請求方式 (Request Method)

  • GET 請求數據
  • POST 發送數據

最常見的get請求就是瀏覽器通過地址欄中輸入的網址的方式。如果既不是獲取數據也不是添加數據的請求,比如登陸操作,一般是post方式,因爲使用post相對於get 更安全一些。
實例代碼如下圖,保存文件,

//引入http,創建網站服務器模塊
const http = require('http');
//app對象就是網站服務器對象
const app = http.createServer();

//當客戶端有請求來的時候
app.on('request',(req,res) => {
	//獲取請求方式
    // console.log(req.method);
    if(req.method=='POST'){
        res.end('post');
    }else if(req.method=='GET'){
        res.end('get');
    }else {}
});

//監聽的端口號
app.listen(3000);
console.log('網站服務器啓動成功');

在這裏插入圖片描述
編寫一個表單html文件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <!-- method:指定當前表單的提交方式
    action:指定當前表單提交的地址 -->
    <form method="POST" action="http://localhost:3000/">
    <input type="submit" name="提交">
    </form>
</body>
</html>

保存後運行,點擊提交按鈕,可以看到此時的請求方式就是post。

請求地址 (Request URL)

app.on('request', (req, res) => {
     req.headers  // 獲取請求報文
     req.url      // 獲取請求地址
     req.method   // 獲取請求方法
 });

3.4 響應報文

HTTP狀態碼

  • 200 請求成功
  • 404 請求的資源沒有被找到
  • 500 服務器端錯誤
  • 400 客戶端請求有語法錯誤

內容類型

  • text/html
  • text/css
  • application/javascript
  • image/jpeg
  • application/json
app.on('request', (req, res) => {
     // 設置響應報文
     res.writeHead(200, {
         'Content-Type': 'text/html;charset=utf8‘
     });
 });

綜合請求地址及響應報文應用實例代碼如下圖:

//引入http創建網站服務器系統模塊
const http = require('http');
//創建網站服務器對象
const app = http.createServer();
//當客戶端有請求來的時候
app.on('request',(req,res) => {   
    //獲取請求地址
    // res.end(req.url);
    //響應報文
    res.writeHead(200,{
      //設置了響應報文的文本類型和字符編碼格式後,中文以及html標籤的格式就可以在瀏覽器渲染出來了
        'content-type':'text/html;charset=utf8'
    });
    if(req.url=='/index' || req.url=='/'){
        res.end('<h2>這是index界面</h2>');     
    }else if(req.url=='/list'){
        res.end('<h2>這是list界面</h2>');    
    }else {
        res.end('<h3>404</h3>');
    }
});
//監聽的端口號
app.listen(3000);
console.log('網站服務器啓動成功');

在這裏插入圖片描述

4. HTTP請求與響應處理

4.1 請求參數
客戶端向服務器端發送請求時,有時需要攜帶一些客戶信息,客戶信息需要通過請求參數的形式傳遞到服務器端,比如登錄操作。

4.2 GET請求參數
參數被放置在瀏覽器地址欄中,例如:http://localhost:3000/?name=zhangsan&age=20

參數獲取需要藉助系統模塊url,url模塊用來處理url地址

const http = require('http');
 // 導入url系統模塊 用於處理url地址
 const url = require('url');
 const app = http.createServer();
 app.on('request', (req, res) => {
	//響應報文 		
 	 res.writeHead(200,{
        'content-type':'text/html;charset=utf8'
    });
    //獲取請求地址
    // console.log(req.url);
    
	// 將url路徑的各個部分解析出來並返回對象
    //獲取url相應對象方式,true 代表將參數解析爲對象格式
    // let query = url.parse(req.url).query;
    // let paras = url.parse(req.url).query;
    // console.log(paras);
    // console.log(paras.name);
    // console.log(paras.age);
    
    // let {query} = url.parse(req.url,true);
    //let {pathname,query} = url.parse(req.url,true);
    
	let {pathname,query} = url.parse(req.url,true);
    if(pathname=='/index' || pathname=='/'){
        res.end('<h2>這是index界面</h2>');
        console.log(query);        
    }else if(pathname=='/list'){
        res.end('<h2>這是list界面</h2>');
        console.log(query);       
    }else {
        res.end('<h3>404</h3>');
    }
 });
 app.listen(3000);

在瀏覽器地址欄輸入:http://localhost:3000/index?name=zahngsan&age=18
在這裏插入圖片描述
4.3 POST請求參數

  • 參數被放置在請求體中進行傳輸
  • 獲取POST參數需要使用data事件和end事件
  • 使用querystring系統模塊將參數轉換爲對象格式
// 導入系統模塊querystring 用於將HTTP參數轉換爲對象格式
 const querystring = require('querystring');
 app.on('request', (req, res) => {
     let postData = '';
     // 監聽參數傳輸事件
     req.on('data', (chunk) => postData += chunk;);
     // 監聽參數傳輸完畢事件
     req.on('end', () => { 
         console.log(querystring.parse(postData)); 
     }); 
     res.end('hello');
 });

通過表單文件點擊按鈕形式請求參數
在這裏插入圖片描述
4.4 路由
http://localhost:3000/index
http://localhost:3000/login
路由是指客戶端請求地址與服務器端程序代碼的對應關係。簡單的說,就是請求什麼響應什麼。
在這裏插入圖片描述
實例代碼:

// 當客戶端發來請求的時候
 app.on('request', (req, res) => {
 	 //響應報文,設置文本類型和字符編碼		
 	 res.writeHead(200,{
        'content-type':'text/html;charset=utf8'
     });
     //獲取請求方式
    let method = req.method.toLowerCase();
    //獲取請求地址的pathname對象和query參數對象
    let {pathname,query} = url.parse(req.url,true);
    if(method == 'get'){
        if(pathname=='/index' || pathname=='/'){
            res.end('<h2>這是index界面</h2>');
            console.log(query);            
        }else if(pathname=='/list'){
            res.end('<h2>這是list界面</h2>');
            console.log(query);         
        }else {
            res.end('<h3>404</h3>');
        }
    }else if(method == 'post'){
    }else {}
 });

4.5 靜態資源和動態資源
靜態資源:服務器端不需要處理,可以直接響應給客戶端的資源就是靜態資源,例如CSS、JavaScript、image文件。如:http://www.itcast.cn/images/logo.png
動態資源:相同的請求地址不同的響應資源,這種資源就是動態資源。
如:http://www.itcast.cn/article?id=1
http://www.itcast.cn/article?id=2

//創建網站服務器模塊
const http = require('http');
//請求地址模塊
const url = require('url');
//路徑模塊
const path = require('path');
//文件模塊
const fs = require('fs');
//mime模塊,引用前需要下載mime模塊
const mime = require('mime');
//創建服務器對象
const app = http.createServer();
//請求響應服務器對象
app.on('request',(req,res) => {
    //獲取請求路徑
    let pathname = url.parse(req.url).pathname;
    pathname = pathname == '/'? '/default.html' : pathname;
    //轉行成物理地址
    let realPath = path.join(__dirname,'public'+pathname);
    //獲取文件類型
    let type = mime.getType(realPath);
    //讀取文件
    fs.readFile(realPath,(err,result) => {
        if(err != null){
            res.writeHead(404,{'content-type':'text/html;charset=utf8'});
            res.end('文件讀取失敗!');
            return ;
        }
        //響應報文
        res.writeHead(200,{'content-type':type});
        res.end(result);
    });   
});
//監聽端口號
app.listen(3000);
console.log("網站服務器啓動成功");

下載mime模塊命令

npm install mime

4.6 客戶端請求途徑
GET方式

  • 瀏覽器地址欄
  • link標籤的 href屬性
  • script標籤的src屬性
  • img標籤的src屬性
  • Form表單提交

POST方式

  • Form表單提交

5.Node.js異步編程

5.1 同步API, 異步API

同步API:只有當前API執行完成後,才能繼續執行下一個API

console.log('before'); 
console.log('after');

異步API:當前API的執行不會阻塞後續代碼的執行

//輸出順序:before after last
console.log('before');
setTimeout(
   () => { console.log('last');
}, 2000);
console.log('after');

5.2 同步API, 異步API的區別( 獲取返回值 )
同步API可以從返回值中拿到API執行的結果, 但是異步API是不可以的

    // 同步
  function sum (n1, n2) { 
      return n1 + n2;
  } 
  const result = sum (10, 20);

    // 異步,下面代碼
  function getMsg () { 
      setTimeout(function () { 
          return { msg: 'Hello Node.js' }
      }, 2000);
  }
  //結果undefined
  console.log(getMsg());

5.3 回調函數
自己定義函數讓別人去調用。

function getData (callback) {
	callback('123')
}
getData(function (n) {
	console.log('callback函數被調用了')
	console.log(n)
});

5.4 使用回調函數獲取異步API執行結果

function getMsg (callback) {
    setTimeout(function () {
        callback ({ msg: 'Hello Node.js' })
    }, 2000);
}
getMsg (function (msg) { 
    console.log(msg);
});

5.5 同步API, 異步API的區別(代碼執行順序)

同步API從上到下依次執行,前面代碼會阻塞後面代碼的執行

for (var i = 0; i < 100000; i++) { 
    console.log(i);
}
console.log('for循環後面的代碼');

異步API不會等待API執行完成後再向下執行代碼

console.log('代碼開始執行'); 
setTimeout(() => { console.log('2秒後執行的代碼')}, 2000);
setTimeout(() => { console.log('0秒後執行的代碼')}, 0); 
console.log('代碼結束執行');

5.6 代碼執行順序分析

//下列代碼執行結果
//代碼開始執行
//代碼結束執行
//0秒後執行的代碼
//2秒後執行的代碼
console.log('代碼開始執行');
setTimeout(() => {
    console.log('2秒後執行的代碼');
}, 2000); 
setTimeout(() => {
    console.log('0秒後執行的代碼');
}, 0);
console.log('代碼結束執行');

在這裏插入圖片描述

5.7 Node.js中的異步API
前面學過的讀取文件和事件監聽屬於常見的異步API

//讀取文件
fs.readFile('./demo.txt', (err, result) => {});
//事件監聽
var server = http.createServer();
 server.on('request', (req, res) => {});

如果異步API後面代碼的執行依賴當前異步API的執行結果,但實際上後續代碼在執行的時候異步API還沒有返回結果,這個問題要怎麼解決呢?

fs.readFile('./demo.txt', (err, result) => {});
console.log('文件讀取結果');

需求:依次讀取A文件、B文件、C文件

方法一:套娃方式,倘若讀取的文件n個,顯然用這種方法需要“套娃”n-1遍,造成回調地獄。這種方法顯然不適合用來讀取過多的文件。

const fs = require('fs');

fs.readFile('./1.txt', 'utf8', (err, result1) => {
	console.log(result1)
	fs.readFile('./2.txt', 'utf8', (err, result2) => {
		console.log(result2)
		fs.readFile('./3.txt', 'utf8', (err, result3) => {
			console.log(result3)
		})
	})
});

5.8 Promise
方法二:使用Promise,Promise出現的目的是解決Node.js異步編程中回調地獄的問題。

//resolve執行成功的返回結果,結果以對象形式存儲
//reject執行失敗的返回結果
//resolve和reject本質是函數
let promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        if (true) {
            resolve({name: '張三'})
        }else {
            reject('失敗了') 
        } 
    }, 2000);
});
promise.then(result => console.log(result); // {name: '張三'})
       .catch(error => console.log(error); // 失敗了);

利用Promise方式讀取單個文件

const fs = require('fs');
let promise = new Promise((resolve, reject) => {
	fs.readFile('./100.txt', 'utf8', (err, result) => {
		if (err != null) {//讀取文件失敗
			reject(err);
		}else {//成功讀取文件
			//用Promise執行成功的返回函數resolve返回結果
			resolve(result);
		}
	});
});
//用then接收成功的返回結果
promise.then((result) => {
	 console.log(result);
})
//catch捕捉執行異常
.catch((err)=> {
	console.log(err);
})

利用Promise方式讀取多個文件

const fs = require('fs');
function p1() {
    return new Promise((resolve,reject) => {
        fs.readFile('1.txt','utf8',(err,result) => {
            if(err != null){
                reject(err);
            }else {
                resolve(result);
            }
        });
    });
}
function p2() {
    return new Promise((resolve,reject) => {
        fs.readFile('2.txt','utf8',(err,result) => {
            if(err != null){
                reject(err);
            }else {
                resolve(result);
            }
        });
    });
}
function p3() {
    return new Promise((resolve,reject) => {
        fs.readFile('3.txt','utf8',(err,result) => {
            if(err != null){
                reject(err);
            }else {
                resolve(result);
            }
        });
    });
}
p1().then((r1) => {
    console.log(r1);
    return p2();
})
.then((r2) => {
    console.log(r2);
    return p3();
})
.then((r3) => {
    console.log(r3);
})

雖然這個方式解決了回調地獄問題,但是代碼過於繁瑣,怪嚇人的~

5.9 異步函數
方法三:使用async關鍵字
異步函數是異步編程語法的終極解決方案,它可以讓我們將異步代碼寫成同步的形式,讓代碼不再有回調函數嵌套,使代碼變得清晰明瞭。

const fn = async () => {};
async function fn () {};
//可以測試一下,使用async關鍵字的函數,其返回結果爲promise對象
async function fn () {
	throw '發生了一些錯誤';
	return 123;
}
// console.log(fn ())
fn ().then(function (data) {
	console.log(data);
}).catch(function (err){
	console.log(err);
})

async關鍵字

  • 普通函數定義前加async關鍵字 普通函數變成異步函數
  • 異步函數默認返回promise對象
  • 在異步函數內部使用return關鍵字進行結果返回 結果會被包裹的promise對象中 return關鍵字代替了resolve方法
  • 在異步函數內部使用throw關鍵字拋出程序異常
  • 調用異步函數再鏈式調用then方法獲取異步函數執行結果
  • 調用異步函數再鏈式調用catch方法獲取異步函數執行的錯誤信息

await關鍵字

  • await關鍵字只能出現在異步函數中
  • await promise await後面只能寫promise對象 寫其他類型的API是不不可以的
  • await關鍵字可是暫停異步函數向下執行 直到promise返回結果

使用async關鍵字函數,讀取多個文件,解決了上述的回調地獄問題以及代繁瑣問題,從而使代碼簡潔易懂。

const fs = require('fs');
//改造現有的異步API,讓其返回promise對象,從而支持異步函數語法
const readFile = require('util').promisify(fs.readFile);

async function run() {
    let f1 = await readFile('1.txt','utf8');
    let f2 = await readFile('2.txt','utf8');
    let f3 = await readFile('3.txt','utf8');
    console.log(f1);
    console.log(f2);
    console.log(f3);
}
run();

util模塊

const fs = require('fs');
//獲取util模塊下的promisify方法,
const promisify = require('util').promisify
//promisify方法可以改造異步API,讓其返回promise對象
const readFile = promisify(fs.readFile);
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章