源码链接

https://github.com/978543210/Frontend-01-Template/tree/master/week05/toy-browser

目标:

可以向服务器端发Request请求,并解析服务器返回的Response,将其中信息提取出来。

服务端程序

  1. const http = require("http");
  2. const server = http.createServer((req,res)=>{
  3. console.log("request receved")
  4. res.setHeader('Content-Type','text/html');
  5. res.setHeader('X-Foo','bar');
  6. res.writeHead(200,{'Content-Type':'text=plain'});
  7. res.end('ok');
  8. });
  9. server.listen(8088);

客户端(浏览器)程序

Request

请求报文格式

image.png

封装

将method、host、port、URI、headers、body分别抽象出来。在使用的时候可以通过字符串模板灵活配置。

代码实现

  1. class Request {
  2. constructor(options){
  3. this.method = options.method || "GET";
  4. this.host = options.host;
  5. this.port = options.port || 80;
  6. this.path = options.path || "/"
  7. this.body = options.body || {};
  8. this.headers = options.headers || {};
  9. if (!this.headers["Content-Type"]) {
  10. this.headers["Content-Type"] = "application/x-www-form-urlencoded";
  11. }
  12. if (this.headers["Content-Type"] === "application/json")
  13. this.bodyText = JSON.stringify(this.body);
  14. else if (this.headers["Content-Type"] === "application/x-www-form-urlencoded")
  15. this.bodyText = Object.keys(this.body).map(key => `${key}=${encodeURIComponent(this.body[key])}`).join('&')
  16. this.headers["Content-Length"] = this.bodyText.length;
  17. }
  18. toString(){
  19. return `${this.method} ${this.path} HTTP/1.1\r
  20. ${Object.keys(this.headers).map(key=>`${key}: ${this.headers[key]}`).join('\r\n')}\r
  21. \r
  22. ${this.bodyText}`
  23. }
  24. send(connection){
  25. return new Promise((resolve, reject) => {
  26. const parser = new ResponseParse;
  27. console.log(this.toString());
  28. if(connection){
  29. connection.write(this.toString());
  30. } else {
  31. connection = net.createConnection({
  32. host: this.host,
  33. port: this.port
  34. },() => {
  35. connection.write(this.toString());
  36. })
  37. }
  38. connection.on('data',(data)=>{
  39. parser.receive(data.toString());
  40. if(parser.isFinished){
  41. resolve(parser.response);
  42. }
  43. connection.end();
  44. });
  45. connection.on('error',(err)=>{
  46. reject(err);
  47. connection.end();
  48. });
  49. });
  50. }
  51. }

Response

相应报文格式

image.png

状态机

W05-H1 ToyBrowser(1):Request与Response - 图3

状态机的实现

  1. receiveChar(char){
  2. if(this.current === this.WAITING_STATUS_LINE){
  3. if(char === '\r') {
  4. this.current = this.WAITING_STATUS_LINE_END;
  5. } else {
  6. this.statusLine += char;
  7. }
  8. } else if (this.current === this.WAITING_STATUS_LINE_END){
  9. if (char === '\n') {
  10. this.current = this.WAITING_HEADER_NAME;
  11. }
  12. } else if (this.current === this.WAITING_HEADER_NAME) {
  13. if (char === ':') {
  14. this.current = this.WAITING_HEADER_SPACE;
  15. } else if (char === '\r'){
  16. this.current = this.WAITING_HEADER_BLOCK_END;
  17. if (this.headers['Transfer-Encoding'] === 'chunked') {
  18. this.bodyParser = new TrunkedBodyParser();
  19. }
  20. } else {
  21. this.headerName += char;
  22. }
  23. } else if (this.current === this.WAITING_HEADER_SPACE){
  24. if (char === ' ') {
  25. this.current = this.WAITING_HEADER_VALUE;
  26. }
  27. } else if (this.current === this.WAITING_HEADER_VALUE){
  28. if (char === '\r') {
  29. this.current = this.WAITING_HEADER_LINE_END;
  30. this.headers[this.headerName] = this.headerValue;
  31. this.headerName = "";
  32. this.headerValue ="";
  33. } else {
  34. this.headerValue += char;
  35. }
  36. } else if (this.current === this.WAITING_HEADER_LINE_END) {
  37. if (char === '\n') {
  38. this.current = this.WAITING_HEADER_NAME;
  39. }
  40. } else if (this.current === this.WAITING_HEADER_BLOCK_END) {
  41. if (char === '\n') {
  42. this.current = this.WAITING_BODY;
  43. }
  44. } else if (this.current === this.WAITING_BODY) {
  45. this.bodyParser.receiveChar(char);
  46. }
  47. }
  48. receiveChar(char){
  49. if (this.current === this.WAITING_LENGTH) {
  50. if (char === '\r') {
  51. if(this.length === 0){
  52. this.isFinished = true;
  53. }
  54. this.current = this.WAITING_LENGTH_LINE_END;
  55. } else {
  56. this.length *= 16;
  57. this.length += char.charCodeAt(0) - '0'.charCodeAt(0);
  58. }
  59. } else if (this.current === this.WAITING_LENGTH_LINE_END) {
  60. if (char === '\n') {
  61. this.current = this.READING_TRUNK;
  62. }
  63. } else if (this.current === this.READING_TRUNK) {
  64. this.content.push(char);
  65. this.length --;
  66. if (this.length === 0) {
  67. this.current = this.WAITING_NEW_LINE;
  68. }
  69. } else if (this.current === this.WAITING_NEW_LINE) {
  70. if (char === '\r') {
  71. this.current = this.WAITING_NEW_LINE_END;
  72. }
  73. } else if (this.current === this.WAITING_NEW_LINE_END) {
  74. if (char === '\n') {
  75. this.current = this.WAITING_LENGTH;
  76. }
  77. }
  78. }

运行截图:

image.png