Appearance
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)