打開(kāi)chrome的調(diào)試面板俐芯,切換到network面板晒屎,搞清楚該頁(yè)上所有的顯示項(xiàng)喘蟆、菜單、彈窗等下級(jí)功能的含義
- 什么是TTFB鼓鲁?TTFB包含了哪些部分蕴轨?
Time to first byte,Timing里面的waiting骇吭,從http請(qǐng)求發(fā)送到服務(wù)器接收第一個(gè)字節(jié)的時(shí)間花費(fèi)橙弱。
包含了TCP連接時(shí)間,http請(qǐng)求發(fā)送時(shí)間和服務(wù)器接收到第一個(gè)字節(jié)的時(shí)間燥狰。
- network面板里看到一個(gè)請(qǐng)求棘脐,怎么判斷這個(gè)請(qǐng)求真的發(fā)送到了服務(wù)器那里?
根據(jù)狀態(tài)碼來(lái)看龙致,502和504是沒(méi)有發(fā)送到服務(wù)器蛀缝,304從瀏覽器讀取緩存。
- 一條請(qǐng)求變紅了可能是因?yàn)槟男┰颍?/li>
4目代,5開(kāi)頭的狀態(tài)碼屈梁,插件阻攔嗤练,瀏覽器的一些約定和協(xié)議
HTTP協(xié)議的特征:
- 兩方通信
- 非對(duì)等的通信雙方(server/client)
- 通信的基本單元(request和response)
- 請(qǐng)求與返回的對(duì)應(yīng)關(guān)系
請(qǐng)寫出一個(gè)簡(jiǎn)單的http請(qǐng)求:(method放前面,協(xié)議放后面)
初始行:method + path + 協(xié)議(GET /index.html HTTP/1.1\r\n HOST: www.maxtropy.com\r\n\r\n)
注:CRLF
- 回車 = CR = \r
- 換行 = LF = \n
HTTP基于TCP運(yùn)行
TCP:兩河模型
怎么知道一條請(qǐng)求已經(jīng)結(jié)束了在讶?答:\r\n
怎么區(qū)分前一條請(qǐng)求和后一條請(qǐng)求煞抬?答:\r\n\r\n
const str = "GET /index.html HTTP/1.1\r\nHost: www.maxtropy.com\r\n\r\nGET /main.js HTTP/1.1\r\nHost: www.maxtropy.com\r\n\r\n"
function parseRequest(str) {
const result = [];
const arr = str.split('\r\n\r\n');
arr.forEach(item => {
const data = item.split('\r\n');
result.push({
requestLine: data[0],
headers: data[1].split()
})
})
}
寫出一個(gè)帶有body的http POST請(qǐng)求:
POST /index.html HTTP/1.1\r\n
HOST: www.maxtropy.com\r\n
Content-Length: 14\r\n\r\n
body: {json}
interface RequestObject {
requestLine: string;
headers: string[];
body?: string;
}
class RequestProcessor {
feed(rawData: string): void {
}
poll():RequestObject | undefined {
return undefined;
}
}
const str0 = 'POST /user/';
const str1 = 'create HTTP/1.1\r\nHost: www.';
const str2 = 'maxtropy.com\r\nqwee: Content-Length: 19\r\nContent';
const str3 = '-Length: 19\r\n\r\n';
const str4 = '{"name":"';
const str5 = 'tom","\r\n"}POST';
const str6 = ' /user/';
const str7 = 'create HTTP/1.1\r\nHost: www.';
const str8 = 'maxtropy.com\r\nContent';
const str9 = '-Length: 21\r\n\r\n';
const str10 = '{"name":"';
const str11 = 'tom","\r\n\r\n"}';
const test0 = "POST /index.js HTTP/1.1\r\nHost: www.test.com\r\nContent-Length:15\r\n\r\n{'name': 'tom'}GET /main.js HTTP/1.1\r\nHost: www.test.com\r\n\r\n";
const test1 = "POST /index.js HTTP/1.1\r\nHost: www.test.com\r\nContent-Length: 17\r\n\r\nContent-Length=16GET /main.js HTTP/1.1\r\nHost: www.test.com\r\n\r\n";
const test2 = "POST /index.js HTTP/1.1\r\nHost: www.test.com\r\nContent-Length: 9\r\n\r\nGET HOST:GET /main.js HTTP/1.1\r\nHost: www.test.com\r\n\r\n";
const test3 = "POST /index.js HTTP/1.1\r\nHost: www.test.com\r\nContent-Length: 6\r\n\r\n\r\n\r\n\r\nGET /main.js HTTP/1.1\r\nHost: www.test.com\r\n\r\n";
const test4 = "GET /main.js HTTP/1.1\r\nHost: www.test.com\r\n\r\nPOST /index.js HTTP/1.1\r\nHost: www.test.com\r\nContent-Length: 15\r\n\r\n{'name': 'tom'}";
const test5 = "GET /main.js HTTP/1.1\r\nHost: www.test";
const test6 = "POST /index.js HTTP/1.1\r\nHost: www.test.com\r\nContent-Length: 6\r\n\r\n\r\ntestGET /main.js HTTP/1.1\r\nHost: www.test.com\r\n\r\n";
const test7 = "POST /index.js HTTP/1.1\r\nHost: www.test.com\r\nContent-Length: 15\r\n\r\n{'name': 'tom'}GET /main.js HTTP";
const test8 = "POST /index.js HTTP/1.1\r\nHost: www.test.com\r\nContent-Length: 15\r\n\r\n{'name': 'tom'}GET /main.js HTTP/1.1\r\nHost: www.test.com";
const test9 = "POST /index.js HTTP/1.1\r\nHost: www.test.com\r\nContent-Length: 15\r\n\r\n{'name': 'tom'}GET /main.js HTTP/1.1\r\nHost: www.test.com\r\n\r\nGET /main.js HTTP/1.1\r\nHost: www.test.com\r\n\r\n";
const test10 = "POST /index.js HTTP/1.1\r\nHost: www.test.com\r\nContent-Length: 15\r\n\r\n{'name': GET GET /main.js HTTP/1.1\r\nHost: www.test.com\r\n\r\nGET /main.js HTTP/1.1\r\nHost: www.test.com\r\n\r\n";
let result = [];
let newUrl = '';
function feed(rawData){
newUrl = newUrl + rawData;
let requestUrl = '';
let indexArr = [];
let firstIndex = -1;
for (let i = 0;i < newUrl.length;i++) {
if(String(newUrl[i] + newUrl[i+1] + newUrl[i+2] + newUrl[i+3]) === '\r\n\r\n') {
requestUrl = newUrl.substring(0,i);
firstIndex = i;
break;
}
}
for (let i = 0; i < requestUrl.length; i++) {
if (String(requestUrl[i] + requestUrl[i + 1]) === '\r\n') {
indexArr.push(i)
}
}
if (firstIndex !== -1) {
if (requestUrl.toLocaleUpperCase().indexOf('CONTENT-LENGTH:') === -1) { // 不帶body
result.push({
requestLine: requestUrl.substring(0, indexArr[0]),
headers: requestUrl.substring(indexArr[0]+2)
});
newUrl = newUrl.substring(requestUrl.length).trim();
feed('')
} else {
const contentLength = Number(requestUrl.toLocaleUpperCase().trim().split('CONTENT-LENGTH:')[1]);
//---- body尚不完整的情況
if (newUrl.length >= requestUrl.length + contentLength + 4) {
result.push({
requestLine: requestUrl.substring(0, indexArr[0]),
headers: requestUrl.substring(indexArr[0] + 2, firstIndex).split('\r\n'),
body: newUrl.substr(firstIndex+4, contentLength)
});
newUrl = newUrl.substring(requestUrl.length + contentLength + 4);
feed('')
}
}
}
}
function poll() {
if(result.length) {
const first = result[0];
result.shift();
return first
}else {
return undefined;
}
}
// feed(str0);
// console.log(poll());
// feed(str1);
// console.log(poll());
// feed(str2);
// console.log(poll());
// feed(str3);
// console.log(poll());
// feed(str4);
// console.log(poll());
// feed(str5);
// console.log(poll());
// feed(str6);
// console.log(poll());
// feed(str7);
// console.log(poll());
// feed(str8);
// console.log(poll());
// feed(str9);
// console.log(poll());
// feed(str10);
// console.log(poll());
// feed(str11);
// console.log(poll());
feed(test3);
console.log(poll(),poll(),poll());
分別寫出一個(gè)帶body和不帶body的response:(協(xié)議放在前面)
不帶body
HTTP/1.1 200 OK
max-age: 0
帶body
HTTP/1.1 200 OK
max-age: 0
Content-Length: 14
{json}
請(qǐng)求及相應(yīng)結(jié)構(gòu):
start-line + *(header-field CRLF) + CRLF + [message-body]
- HTTP為什么不能是多方通信?
request和response不能一一對(duì)應(yīng)
- 怎么區(qū)分client和server构哺?
通過(guò)發(fā)送的信息的結(jié)構(gòu)
- server怎么向client主動(dòng)發(fā)送數(shù)據(jù)此疹?
webSocket,SSE遮婶,HTTP是不能實(shí)現(xiàn)的,因?yàn)閏lient會(huì)把請(qǐng)求當(dāng)響應(yīng)處理報(bào)錯(cuò)
https://tools.ietf.org/html/rfc2119
延伸:
- 了解HTTP chunked編碼及其使用場(chǎng)景
服務(wù)器返回的消息的長(zhǎng)度湖笨,像前臺(tái)給后臺(tái)的Content-Length一樣旗扑;transfer-coding的閾值為chunked時(shí)表示將用chunked編碼傳輸內(nèi)容;
使用場(chǎng)景:前臺(tái)下載大文件
- 了解WebSocket的協(xié)議細(xì)節(jié)慈省,與HTTP協(xié)議的不同之處
WebSocket 是基于TCP/IP協(xié)議臀防,獨(dú)立于HTTP協(xié)議的通信協(xié)議;雙向通訊边败,有狀態(tài)袱衷,客戶端一(多)個(gè)與服務(wù)端一(多)雙向?qū)崟r(shí)響應(yīng)(客戶端 ? 服務(wù)端);持久化協(xié)議笑窜;
GET /webfin/websocket/ HTTP/1.1
Host: localhost
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: xqBt3ImNzJbYqRINxEFlkg==
Origin: http://localhost:8080
Sec-WebSocket-Version: 13
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: K7DJLdLooIwIG/MOpvWFB3y3FE8=
- HTTP/1.1隊(duì)頭阻塞的問(wèn)題
由于http一發(fā)一收的應(yīng)答機(jī)制(請(qǐng)求-應(yīng)答模型)致燥,如果隊(duì)首的請(qǐng)求處理太慢就會(huì)阻塞后面的進(jìn)程。
解決辦法:并發(fā)連接排截,客戶端對(duì)一個(gè)域名同時(shí)發(fā)起多個(gè)長(zhǎng)連接嫌蚤,用數(shù)量來(lái)解決質(zhì)量問(wèn)題,但是如果并發(fā)數(shù)太大断傲,服務(wù)器會(huì)認(rèn)為是惡意攻擊脱吱,拒絕請(qǐng)求,對(duì)客戶端發(fā)起并發(fā)請(qǐng)求的數(shù)量限制在6-8认罩;域名分片箱蝠,增加域名的數(shù)量,多個(gè)域名指向同一個(gè)服務(wù)器垦垂。
長(zhǎng)連接:不進(jìn)行四次握手宦搬,數(shù)據(jù)傳輸完成TCP連接不斷開(kāi),同域名下繼續(xù)使用這個(gè)通道乔外,有過(guò)期時(shí)間限制(keep-alive:timeout)
- 如果GET或DELETE請(qǐng)求床三,發(fā)送者帶上了body,服務(wù)器應(yīng)該怎樣處理杨幼?
直接忽略body的內(nèi)容(可能會(huì)導(dǎo)致下一個(gè)接口報(bào)錯(cuò))撇簿;報(bào)400聂渊,參數(shù)錯(cuò)誤;接受處理四瘫,返回空數(shù)據(jù)汉嗽;丟棄Content-Length和body
- 撰寫測(cè)試用例