源码链接
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;
}
}
}