Skip to content

server.js

js
const fs = require('fs')
const path = require('path')
const http = require('http')

const server = http.createServer((req, res) => {

  // 返回 HTML 字符串
  res.end(fs.readFileSync(path.resolve(__dirname, 'index.html')))
})

server.listen(3000)

client.js

js
const net = require('net')
const css = require('css')
const htmlparser2 = require("htmlparser2")

class HTTP {
  constructor (options = {}) {
    this.host = options.host
    this.port = options.port
    this.path = options.path
    this.method = options.method
    this.headers = options.headers
  }

  send () {
    // 模拟 HTTP 请求
    return new Promise((resolve, reject) => {
      const rows = []
      const { path, host, port, method, headers } = this

      // 请求行
      rows.push(`${ method } ${ path } HTTP/1.1`)
      
      // 请求头
      const keys = Object.keys(headers)
      for (let i = 0, l = keys.length; i < l; i++) {
        const key = keys[i]
        rows.push(`${ key}: ${ headers[key] }`)
      }

      // 空行
      rows.push('\r\n')
      // 请求体
      rows.push('\r\n')

      const requestMsg = rows.join('\r\n')

      // 创建 TCP 连接,传输数据。
      const socket = net.createConnection({
        host,
        port
      }, () => {
        socket.write(requestMsg)
      })

      socket.on('data', chunk => {
        const rows = Buffer.from(chunk).toString().split('\r\n')
        const line = `${ rows[0] }\r\n`
        const blankLine = rows.indexOf('')
        const headers = rows.slice(1, blankLine).join('\r\n')
        const body = rows.slice(blankLine + 1).join('\r\n')
        const res = {
          line,
          headers,
          body
        }

        resolve(res)
      })
    })
  }
}

const htmlParser = html => {
  // html => html-parser(词法分析) => dom tree
  const stack = [{
    type: 'document',
    children: []
  }]
  let style = null

  const parser = new htmlparser2.Parser({
    onopentag(tagName, attributes) {
      const parent = stack[stack.length - 1]
      const element = {
        tagName,
        attributes,
        children: []
      }

      parent.children.push(element)
      element.parent = parent
      stack.push(element)
    },

    ontext(text) {
      const parent = stack[stack.length - 1]
      
      parent.children.push({
        type: 'text',
        text
      })
    },

    onclosetag(tagName) {
      const parent = stack[stack.length - 1]

      if (tagName === 'style') {

        // 解析样式
        style = parent.children[0].text
      }
      stack.pop()
    },
  })

  parser.write(html)

  return {
    stack,
    style
  }
}

const cssParser = style => {
  const ast = css.parse(style)
  console.log(ast)
}

const request = async (options = {}) => {
  const req = new HTTP(options)
  const { line, headers, body } = await req.send()
  
  // 浏览器根据响应类型去解析 Content-Type: 'text/html'
  const { stack, style } = htmlParser(body)
  cssParser(style)
}

const data = request({
  method: 'GET',
  host: '127.0.0.1',
  port: 3000,
  path: '/',
  headers: {
    Foo: 'foo'
  }
})

console.log(data)