源码链接
https://github.com/978543210/Frontend-01-Template/tree/master/week05/toy-browser
目标:
可以向服务器端发Request请求,并解析服务器返回的Response,将其中信息提取出来。
服务端程序
const http = require("http");const server = http.createServer((req,res)=>{console.log("request receved")res.setHeader('Content-Type','text/html');res.setHeader('X-Foo','bar');res.writeHead(200,{'Content-Type':'text=plain'});res.end('ok');});server.listen(8088);
客户端(浏览器)程序
Request
请求报文格式
封装
将method、host、port、URI、headers、body分别抽象出来。在使用的时候可以通过字符串模板灵活配置。
代码实现
class Request {constructor(options){this.method = options.method || "GET";this.host = options.host;this.port = options.port || 80;this.path = options.path || "/"this.body = options.body || {};this.headers = options.headers || {};if (!this.headers["Content-Type"]) {this.headers["Content-Type"] = "application/x-www-form-urlencoded";}if (this.headers["Content-Type"] === "application/json")this.bodyText = JSON.stringify(this.body);else if (this.headers["Content-Type"] === "application/x-www-form-urlencoded")this.bodyText = Object.keys(this.body).map(key => `${key}=${encodeURIComponent(this.body[key])}`).join('&')this.headers["Content-Length"] = this.bodyText.length;}toString(){return `${this.method} ${this.path} HTTP/1.1\r${Object.keys(this.headers).map(key=>`${key}: ${this.headers[key]}`).join('\r\n')}\r\r${this.bodyText}`}send(connection){return new Promise((resolve, reject) => {const parser = new ResponseParse;console.log(this.toString());if(connection){connection.write(this.toString());} else {connection = net.createConnection({host: this.host,port: this.port},() => {connection.write(this.toString());})}connection.on('data',(data)=>{parser.receive(data.toString());if(parser.isFinished){resolve(parser.response);}connection.end();});connection.on('error',(err)=>{reject(err);connection.end();});});}}
Response
相应报文格式
状态机
状态机的实现
receiveChar(char){if(this.current === this.WAITING_STATUS_LINE){if(char === '\r') {this.current = this.WAITING_STATUS_LINE_END;} else {this.statusLine += char;}} else if (this.current === this.WAITING_STATUS_LINE_END){if (char === '\n') {this.current = this.WAITING_HEADER_NAME;}} else if (this.current === this.WAITING_HEADER_NAME) {if (char === ':') {this.current = this.WAITING_HEADER_SPACE;} else if (char === '\r'){this.current = this.WAITING_HEADER_BLOCK_END;if (this.headers['Transfer-Encoding'] === 'chunked') {this.bodyParser = new TrunkedBodyParser();}} else {this.headerName += char;}} else if (this.current === this.WAITING_HEADER_SPACE){if (char === ' ') {this.current = this.WAITING_HEADER_VALUE;}} else if (this.current === this.WAITING_HEADER_VALUE){if (char === '\r') {this.current = this.WAITING_HEADER_LINE_END;this.headers[this.headerName] = this.headerValue;this.headerName = "";this.headerValue ="";} else {this.headerValue += char;}} else if (this.current === this.WAITING_HEADER_LINE_END) {if (char === '\n') {this.current = this.WAITING_HEADER_NAME;}} else if (this.current === this.WAITING_HEADER_BLOCK_END) {if (char === '\n') {this.current = this.WAITING_BODY;}} else if (this.current === this.WAITING_BODY) {this.bodyParser.receiveChar(char);}}receiveChar(char){if (this.current === this.WAITING_LENGTH) {if (char === '\r') {if(this.length === 0){this.isFinished = true;}this.current = this.WAITING_LENGTH_LINE_END;} else {this.length *= 16;this.length += char.charCodeAt(0) - '0'.charCodeAt(0);}} else if (this.current === this.WAITING_LENGTH_LINE_END) {if (char === '\n') {this.current = this.READING_TRUNK;}} else if (this.current === this.READING_TRUNK) {this.content.push(char);this.length --;if (this.length === 0) {this.current = this.WAITING_NEW_LINE;}} else if (this.current === this.WAITING_NEW_LINE) {if (char === '\r') {this.current = this.WAITING_NEW_LINE_END;}} else if (this.current === this.WAITING_NEW_LINE_END) {if (char === '\n') {this.current = this.WAITING_LENGTH;}}}
运行截图:

