基于TCP实现HTTP的get、post请求(前后端)

本文代码:本文代码

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

1. TCP/IP参考模型

  • 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. POST请求

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"

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值