本文代码:本文代码
1.TCP/IP参考模型
2. GET请求
2.1 使用get
2.1.1 服务端
2.1.2 客户端
2.1.3 请求和响应的格式
2.2 实现get
2.2.1 tcp-get-client.js
2.2.2 tcp-get-sever
3. POST请求
3.1 使用post
3.1.1 服务端
3.1.2 客户端
3.1.3 请求和响应的格式
3.2 实现get
3.2.1 客户端
3.2.2 服务端
3.2.3 Parser.js
- TCP/IP协议被称为传输控制协议/互联网协议,又称网络通讯协议
2. GET请求
2.1 使用
2.1.1 服务端
**http-get-server.js**
const http = require('http');
const fs = require('fs');
const path = require('path');
const server = http.createServer(function(req,res){
if(['/get.html'].includes(req.url)){
res.writeHead(200,{'Context-type':"text-html"});
res.end(fs.readFileSync(path.join(__dirname,'static',req.url.slice(1))));
}else if(req.url === '/get'){
res.writeHead(200,{'Context-type':"text-plain"});
res.end('get');
}
});
server.listen(8080);
2.1.2 客户端
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>get</title>
</head>
<body>
<script>
let xhr = new XMLHttpRequest();
xhr.onreadystatechange = ()=>{
console.log('onreadystatechange',xhr.readyState);
}
xhr.open("GET", "http://127.0.0.1:8080/get");
xhr.responseType="text";
xhr.setRequestHeader('name', 'zhufeng');
xhr.setRequestHeader('age', '10');
xhr.onload = () => {
console.log('readyState',xhr.readyState);
console.log('status',xhr.status);
console.log('statusText',xhr.statusText);
console.log('getAllResponseHeaders',xhr.getAllResponseHeaders());
console.log('response',xhr.response);
};
xhr.send();
</script>
</body>
</html>
在浏览器打开http://127.0.0.1:8080/get.html
2.1.3 请求和响应的格式
- 一个请求消息是从客户端到服务器端的,在消息首行里包含方法,资源指标符,协议版本。
请求头格式:
Request=Request-Line;
*((general-header|request-header|entity-header)CRLF)
CRLF
[message-body]
解释:
请求行
请求头多个,每个后面带回车换行(CRLF,即\r\n)
回车换行
请求体(可选)
2.1.3.1 请求
GET /get HTTP/1.1
Host: 127.0.0.1:8080
Connection: keep-alive
name: zhufeng
age: 10
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36
Accept: '*/*' (注意这里其实没单引号,为了不引起代码注释才加的)
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
wireshark截图:
2.1.3.2 响应
响应头格式:
Response=Status-Line;
*((general-header|response-header|entity-header)CRLF)
CRLF
[message-body]
HTTP/1.1 200 OK
Context-type: text-plain
Date: Fri, 14 Aug 2020 03:58:41 GMT
Connection: keep-alive
Transfer-Encoding: chunked
3
get(服务器返回的’get’字符串)
0
wireshark截图:
2.2 实现get请求
2.2.1 客户端
**tcp-get-client.js,用node或者nodemon执行**
let net = require("net");
const ReadyState = {
UNSENT: 0, //(代理被创建,但尚未调用 open() 方法。
OPENED: 1, //open() 方法已经被调用
HEADERS_RECEIVED: 2, //send() 方法已经被调用,并且响应头部和状态已经可获得。
LOADING: 3, //(交互)正在解析请求体内容
DONE: 4, //(完成)请求体内容解析完成,可以在客户端调用了
};
class XMLHttpRequest {
constructor() {
this.ReadyState = ReadyState.UNSENT;
this.headers = {};
}
open(method, url) {
this.method = method || "GET";
this.url = url;
let { hostname, port, path } = require("url").parse(url);
this.hostname = hostname;
this.port = port;
this.path = path;
this.headers.Host = `${hostname}:${port}`;
const socket = (this.socket = net.createConnection(
{ port: this.port, hostname: this.hostname },
() => {
socket.on("data", (data) => {
// 服务端的响应数据
data = data.toString();
let [response, bodyRows] = data.split("\r\n\r\n");
let [statusLine, ...headerRows] = response.split("\r\n");
let [, status, statusText] = statusLine.split(" ");
this.status = status;
this.statusText = statusText;
this.responseHeaders = headerRows.reduce((memo, row) => {
let [key, value] = row.split(": ");
memo[key] = value;
return memo;
}, {});
this.readyState = ReadyState.HEADERS_RECEIVED;
xhr.onreadystatechange && xhr.onreadystatechange();
this.readyState = ReadyState.LOADING;
xhr.onreadystatechange && xhr.onreadystatechange();
let [, body] = bodyRows.split("\r\n");
this.response = this.responseText = body;
this.readyState = ReadyState.DONE;
xhr.onreadystatechange && xhr.onreadystatechange();
this.onload && this.onload();
});
socket.on("error", (err) => {
this.onerror() && this.onerror(err);
});
}
));
this.readyState = ReadyState.OPENED;
xhr.onreadystatechange && xhr.onreadystatechange();
}
getAllResponseHeaders() {
let allResponseHeaders = "";
for (let key in this.responseHeaders) {
allResponseHeaders += `${key}: ${this.responseHeaders[key]}\r\n`;
}
return allResponseHeaders;
}
setRequestHeader(header, value) {
this.headers[header] = value;
}
send() {
let rows = [];
rows.push(`${this.method} ${this.path} HTTP/1.1`);
rows.push(
...Object.keys(this.headers).map((key) => `${key}: ${this.headers[key]}`)
);
this.socket.write(rows.join("\r\n") + "\r\n\r\n");
}
}
let xhr = new XMLHttpRequest();
xhr.onreadystatechange = () => {
console.log("onreadystatechange", xhr.readyState);
};
xhr.open("GET", "http://127.0.0.1:8080/get");
xhr.responseType = "text";
xhr.setRequestHeader("name", "zhufeng");
xhr.setRequestHeader("age", "10");
xhr.onload = () => {
console.log("readyState", xhr.readyState);
console.log("status", xhr.status);
console.log("statusText", xhr.statusText);
console.log("getAllResponseHeaders", xhr.getAllResponseHeaders());
console.log("response", xhr.response);
};
xhr.send();
运行输出内容:
2.2.2 服务端
**tcp-get-server.js,用node或者nodemon执行**
const net = require("net");
const server = net.createServer((socket) => {
socket.on("data", (data) => {
let request = data.toString();
let [requestLine, ...headerRows] = request.split("\r\n");
let [method, path] = requestLine.split(" ");
let headers = headerRows.slice(0, -2).reduce((memo, row) => {
let [key, value] = row.split(": ");
memo[key] = value;
return memo;
}, {});
console.log("method", method);
console.log("path", path);
console.log("headers", headers);
let rows = [];
rows.push(`HTTP/1.1 200 OK`);
rows.push(`Content-Type: text/plain`);
rows.push(`Date: ${new Date().toGMTString()}`);
rows.push(`Connection: keep-alive`);
rows.push(`Transfer-Encoding: chunked`);
let responseBody = "get";
rows.push(
`\r\n${Buffer.byteLength(responseBody).toString(
16
)}\r\n${responseBody}\r\n0`
);
let response = rows.join("\r\n");
socket.end(response);
});
});
server.on("error", (err) => {
console.log(err);
});
server.listen(8080, () => {
console.log("服务器已经启动", server.address());
});
运行输出内容:
3.1 使用
3.1.1 服务端
**http-post-server.js**
const http = require('http');
const fs = require('fs');
const path = require('path');
const server = http.createServer(function(req,res){
+ if(['/get.html','/post.html'].includes(req.url)){
res.writeHead(200,{'Context-type':"text-html"});
res.end(fs.readFileSync(path.join(__dirname,'static',req.url.slice(1))));
}else if(req.url === '/get'){
res.writeHead(200,{'Context-type':"text-plain"});
res.end('get');
+ }else if(req.url === '/post'){
+ let buffers = [];
+ req.on('data',(data)=>{
+ buffers.push(data);
+ });
+ req.on('end',()=>{
+ console.log('method',req.method);
+ console.log('url',req.url);
+ console.log('headers',req.headers);
+ console.log('buffers',buffers);
+ let body = Buffer.concat(buffers);
+ console.log('body',body.toString());
+ res.statusCode = 200;
+ res.setHeader('Context-type',"text-plain");
+ res.write(body);
+ res.end();
+ });
}
});
server.listen(8080);
运行结果:
3.1.2 客户端
**post.html**
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>get</title>
</head>
<body>
<script>
let xhr = new XMLHttpRequest();
xhr.onreadystatechange = ()=>{
console.log('onreadystatechange',xhr.readyState);
}
+ xhr.open("POST", "http://127.0.0.1:8080/post");
xhr.responseType="text";
+ xhr.setRequestHeader('Content-Type','application/json');
xhr.onload = () => {
console.log('readyState',xhr.readyState);
console.log('status',xhr.status);
console.log('statusText',xhr.statusText);
console.log('getAllResponseHeaders',xhr.getAllResponseHeaders());
console.log('response',xhr.response);
};
+ xhr.send(JSON.stringify({name:'zhufeng',age:10}));
</script>
</body>
</html>
在浏览器打开http://127.0.0.1:8080/post.html
运行结果:
3.2 实现
3.2.1 客户端
**tcp-post-client.js**
let net = require('net');
const ReadyState = {
UNSENT:0,//(代理被创建,但尚未调用 open() 方法。
OPENED:1,//open() 方法已经被调用
HEADERS_RECEIVED:2,//send() 方法已经被调用,并且头部和状态已经可获得。
LOADING:3,//(交互)正在解析响应内容
DONE:4 //(完成)响应内容解析完成,可以在客户端调用了
}
class XMLHttpRequest {
constructor(){
this.readyState = ReadyState.UNSENT;
this.headers = {};
}
open(method, url) {
this.method = method||'GET';
this.url = url;
let {hostname,port,path} = require('url').parse(url);
this.hostname = hostname;
this.port = port;
this.path = path;
this.headers.Host=`${hostname}:${port}`;
+ this.headers.Connection=`keep-alive`;
const socket = this.socket = net.createConnection({port: this.port,hostname:this.hostname},()=>{
socket.on('data', (data) => {
data = data.toString();
console.log(data);
let [response,bodyRows] = data.split('\r\n\r\n');
let [statusLine,...headerRows] = response.split('\r\n');
let [,status,statusText] = statusLine.split(' ');
this.status = status;
this.statusText = statusText;
this.responseHeaders = headerRows.reduce((memo,row)=>{
let [key,value] = row.split(': ');
memo[key]= value;
return memo;
},{});
this.readyState = ReadyState.HEADERS_RECEIVED;
xhr.onreadystatechange&&xhr.onreadystatechange();
this.readyState = ReadyState.LOADING;
xhr.onreadystatechange&&xhr.onreadystatechange();
let [,body,] = bodyRows.split('\r\n');
this.response = this.responseText = body;
this.readyState = ReadyState.DONE;
xhr.onreadystatechange&&xhr.onreadystatechange();
this.onload&&this.onload();
});
socket.on('error', (err) => {
this.onerror&&this.onerror(err);
});
});
this.readyState = ReadyState.OPENED;
xhr.onreadystatechange&&xhr.onreadystatechange();
}
getAllResponseHeaders(){
let allResponseHeaders='';
for(let key in this.responseHeaders){
allResponseHeaders+=`${key}: ${this.responseHeaders[key]}\r\n`;
}
return allResponseHeaders;
}
setRequestHeader(header,value){
this.headers[header]= value;
}
send(body) {
let rows = [];
rows.push(`${this.method} ${this.path} HTTP/1.1`);
+ this.headers["Content-Length"]=Buffer.byteLength(body);
rows.push(...Object.keys(this.headers).map(key=>`${key}: ${this.headers[key]}`));
+ let request = rows.join('\r\n')+'\r\n\r\n'+body;
console.log(request);
this.socket.write(request);
}
}
let xhr = new XMLHttpRequest();
xhr.onreadystatechange = ()=>{
console.log('onreadystatechange',xhr.readyState);
}
xhr.open("POST", "http://127.0.0.1:8080/post");
xhr.responseType="text";
xhr.setRequestHeader('Content-Type','application/json');
xhr.onload = () => {
console.log('readyState',xhr.readyState);
console.log('status',xhr.status);
console.log('statusText',xhr.statusText);
console.log('getAllResponseHeaders',xhr.getAllResponseHeaders());
console.log('response',xhr.response);
};
xhr.send(`{"name":"zf"}`);
3.2.2 服务端
**tcp-post-server.js**
const net = require('net');
const Parer = require('./Parser');
const server = net.createServer((socket) => {
socket.on('data',(data)=>{
+ let parser = new Parer();
+ let {method,url,headers,body} = parser.parse(data); // parser用来解决body太大,客户端是一部分一部分发过来的情况
+ console.log('method',method);
+ console.log('url',url);
+ console.log('headers',headers);
+ console.log('body',body);
let rows = [];
rows.push(`HTTP/1.1 200 OK`);
rows.push(`Context-type: text-plain`);
rows.push(`Date: ${new Date().toGMTString()}`);
rows.push(`Connection: keep-alive`);
rows.push(`Transfer-Encoding: chunked`);
rows.push(`\r\n${Buffer.byteLength(body).toString(16)}\r\n${body}\r\n0`);
let response = rows.join('\r\n');
socket.end(response);
});
})
server.on('error', (err) => {
console.error(err);
});
server.listen(8080,() => {
console.log('服务器已经启动', server.address());
});
**Parser.js 状态机算法**
let LF = 10, //换行 line feed
CR = 13, //回车 carriage return
SPACE = 32, //空格
COLON = 58; //冒号
let PARSER_UNINITIALIZED = 0, //未解析
START = 1, //开始解析
REQUEST_LINE = 2, // 开始解析请求行
HEADER_FIELD_START = 3, // 开始解析头字段
HEADER_FIELD = 4, // 解析头字段
HEADER_VALUE_START = 5, // 开始解析头值
HEADER_VALUE = 6, // 解析头值
READING_BODY_START = 7; // 读取body
READING_BODY = 8; // 读取body
class Parser {
constructor() {
this.state = START;
this.requestLine = "";
this.headers = {};
this.body = "";
this.headerField = "";
this.headerValue = "";
this.requestLineMark = 0;
this.headerFieldMark = 0;
this.headerValueMark = 0;
this.bodyMark = 0;
}
parse(buffer) {
let self = this,
i = 0,
char,
state = this.state;
this.bodyMark = 0;
for (i = 0; i < buffer.length; i++) {
char = buffer[i];
switch (state) {
case START:
state = REQUEST_LINE;
self.requestLineMark = i;
case REQUEST_LINE:
if (char == CR) {
//换行
self.requestLine = buffer.toString("utf8", self.requestLineMark, i);
break;
} else if (char == LF) {
//回车
state = HEADER_FIELD_START;
}
break;
case HEADER_FIELD_START:
if (char === CR) {
state = READING_BODY_START;
// 确保在发送空行后进入 READING_BODY_START 状态
if (i < buffer.length - 1 && buffer[i + 1] === LF) {
self.bodyMark = i + 2; // 设置 bodyMark 为下一个字符的位置
}
break;
} else {
state = HEADER_FIELD;
self.headerFieldMark = i;
}
case HEADER_FIELD:
if (char == COLON) {
self.headerField = buffer.toString("utf8", self.headerFieldMark, i);
state = HEADER_VALUE_START;
}
break;
case HEADER_VALUE_START:
if (char == SPACE) {
break;
}
self.headerValueMark = i;
state = HEADER_VALUE;
case HEADER_VALUE:
if (char === CR) {
self.headerValue = buffer.toString("utf8", self.headerValueMark, i);
self.headers[self.headerField] = self.headerValue;
self.headerField = "";
self.headerValue = "";
} else if (char === LF) {
state = HEADER_FIELD_START;
}
break;
case READING_BODY_START:
state = READING_BODY;
case READING_BODY:
self.body += buffer.toString("utf8", self.bodyMark, i + 1); // 加上当前字符
self.bodyMark = i + 1;
break;
default:
break;
}
}
this.state = state;
let [method, url] = self.requestLine.split(" ");
return {
method,
url,
headers: self.headers,
body: self.body,
state: self.state,
};
}
}
module.exports = Parser;
// 创建一个 Parser 实例
// const parser = new Parser();
// 分多次发送数据
// const buffer1 = Buffer.from("GET /abc HTTP/1.1\r\n");
// const buffer2 = Buffer.from("Host: example.com\r\n");
// const buffer3 = Buffer.from("Content-Length: 5\r\n");
// const buffer4 = Buffer.from("\r\n");
// const buffer5 = Buffer.from("h");
// const buffer6 = Buffer.from("e");
// const buffer7 = Buffer.from("llo");
// const buffer8 = Buffer.from(" adele");
// // 第一次调用 parse,发送请求行
// const result1 = parser.parse(buffer1);
// console.log(result1); // 应该为空
// // 第二次调用 parse,发送 Host 头部
// const result2 = parser.parse(buffer2);
// console.log(result2); // 应该为空
// // 第三次调用 parse,发送 Content-Length 头部
// const result3 = parser.parse(buffer3);
// console.log(result3); // 应该为空
// // 第四次调用 parse,发送空行,表示头部结束
// const result4 = parser.parse(buffer4);
// console.log(result4); // 应该为空
// // 第五次调用 parse,发送 body 的第一部分 'h'
// const result5 = parser.parse(buffer5);
// console.log(result5); // 应该为 "h"
// // 第六次调用 parse,发送 body 的第二部分 'e'
// const result6 = parser.parse(buffer6);
// console.log(result6); // 应该为 "he"
// // 第七次调用 parse,发送 body 的剩余部分 'llo'
// const result7 = parser.parse(buffer7);
// console.log(result7); // 应该为 "hello"
// // 第八次调用 parse,发送 body 的剩余部分 ' adele'
// const result8 = parser.parse(buffer8);
// console.log(result8); // 应该为 "hello adele"