HTTP 模块
基于 NodeJS 中的 http 模块,掌握 HTTP 协议中一些必要的内容组成以及一些常见功能实现的原理,如缓存、编码、断点续传、防盗链等。
快速使用
net 模块创建服务:
const net = require('net')
const server = net.createServer()
server.listen(1234, () => {
console.log('服务端启动,访问 localhost:1234')
})
server.on('connection', socket => {
socket.on('data', data => {
console.log(data.toString())
})
socket.end('test http request')
})
net 模块主要用于创建 TCP 服务器或客户端,对应传输层。
http 模块依赖 net 模块,用于实现 HTTP 服务器或客户端,对应应用层。
http 模块创建服务:
const http = require('http')
const server = http.createServer((req, res) => {
console.log('接收到请求')
})
server.listen(1234, () => {
console.log('服务端启动,访问 localhost:1234')
})
获取 http 请求信息
const http = require('http')
const url = require('url')
const server = http.createServer((req, res) => {
const { pathname, query } = url.parse(req.url, true)
console.log(pathname, query)
console.log(req.method)
console.log(req.httpVersion)
const arr = []
req.on('data', chunk => {
arr.push(chunk)
})
req.on('end', () => {
console.log(Buffer.concat(arr).toString())
})
})
server.listen(1234, () => {
console.log('server is running...')
})
设置 http 响应
const http = require('http')
const server = http.createServer((req, res) => {
res.statusCode = 302
res.setHeader('Content-Type', 'text/html;charset=utf-8')
res.end('你好张三')
})
server.listen(1234, () => {
console.log('server is running...')
})
客户端代理
代理可以解决浏览器跨域请求的问题。
服务器之间是不存在跨域的,可以使用 NodeJS 创建一个客户端代理,由它代替浏览器客户端直接向服务端发送请求。
浏览器客户端也可以将发送给服务端的请求发送给客户端代理,由客户端代理转为发送,解决跨域问题。
const http = require('http')
const url = require('url')
const querystring = require('querystring')
const server = http.createServer((req, res) => {
const arr = []
req.on('data', chunk => {
arr.push(chunk)
})
req.on('end', () => {
const body = Buffer.concat(arr).toString()
const contentType = req.headers['content-type']
if (contentType === 'application/json') {
const obj = JSON.parse(body)
obj.age = 20
res.end(JSON.stringify(obj))
} else if (contentType === 'application/x-www-form-urlencoded') {
const obj = querystring.parse(body)
res.end(JSON.stringify(obj))
}
})
})
server.listen(1234, () => {
console.log('server is running...')
})
const http = require('http')
const options = {
host: 'localhost',
port: 1234,
path: '/?a=1',
method: 'POST',
headers: {
'Content-type': 'application/x-www-form-urlencoded'
}
}
const req = http.request(options, res => {
const arr = []
res.on('data', chunk => {
arr.push(chunk)
})
res.on('end', () => {
console.log(Buffer.concat(arr).toString())
})
})
req.end('a=1&b=2')
客户端代理解决跨域
const http = require('http')
const server = http.createServer((req, res) => {
const arr = []
req.on('data', chunk => {
arr.push(chunk)
})
req.on('end', () => {
console.log(Buffer.concat(arr).toString())
res.end('获取到了客户端的数据')
})
})
server.listen(1234, () => {
console.log('外部服务端启动了')
})
const http = require('http')
const options = {
host: 'localhost',
port: 1234,
path: '/',
method: 'POST'
}
const server = http.createServer((request, response) => {
const req = http.request(options, res => {
const arr = []
res.on('data', chunk => {
arr.push(chunk)
})
res.on('end', () => {
const ret = Buffer.concat(arr).toString()
response.setHeader('content-type', 'text/html;charset=utf-8')
response.end(ret)
})
})
req.end('你好张三')
})
server.listen(1000, () => {
console.log('本地服务端启动了')
})
浏览器客户端访问代理的客户端 localhost:1000 即可。
HTTP 静态服务
使用 http 模块开启一个服务端,由浏览器充当客户端,按照一定的路径访问目标服务器提供的静态资源。
初始化
安装工具:npm install mime
示例文件:
├─ www
│ └─ index.html
├─ index.css
├─ index.html
└─ server.js
示例代码
www/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Document</title>
<link rel="stylesheet" href="../index.css" />
</head>
<body>
<h2>www/index.html</h2>
</body>
</html>
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Document</title>
<link rel="stylesheet" href="./index.css" />
</head>
<body>
<h2>root/index.html</h2>
</body>
</html>
index.css
body {
background-color: lightblue;
}
server.js
const http = require('http')
const url = require('url')
const path = require('path')
const fs = require('fs')
const mime = require('mime')
const server = http.createServer((req, res) => {
let { pathname, query } = url.parse(req.url)
pathname = decodeURIComponent(pathname)
const absPath = path.join(__dirname, pathname)
fs.stat(absPath, (err, statObj) => {
if (err) {
res.statusCode = 404
res.end('Not Found')
return
}
if (statObj.isFile()) {
fs.readFile(absPath, (err, data) => {
res.setHeader('Content-type', mime.getType(absPath) + ';charset=utf-8')
res.end(data)
})
} else {
fs.readFile(path.join(absPath, 'index.html'), (err, data) => {
res.setHeader('Content-type', mime.getType(absPath) + ';charset=utf-8')
res.end(data)
})
}
})
})
server.listen(1234, () => {
console.log('server is running...')
})
测试
- 访问
localhost:1234/index.html - 访问
localhost:1234/www
静态服务工具
使用 NodeJS 内置模块和一些第三方工具包实现类似 serve 的命令行工具,调用相应的命令可以在指定的目录下开启一个 web 服务。
初始化项目
创建目录 myserver。
在目录下创建文件 bin/www.js (处理命令行选项和执行主要逻辑) 和 main.js (存放主要逻辑)
npm init -y 初始化 package.json ,并修改可执行文件路径:
{
"name": "myserver",
"version": "1.0.0",
"description": "",
"main": "main.js",
"bin": {
"myserver": "bin/www.js"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
安装工具包:
- commander 用于处理命令行选项
- mime 用于自动获取文件 MIME 类型
- ejs JavaScript 模板引擎,方便渲染数据
编写脚本 bin/www.js :
#! /usr/bin/env node
const { program } = require('commander')
console.log('test')
program.option('-p --port', 'set server port')
program.parse(process.argv)
将模块链接到全局 npm link (在 myserver 目录下执行)。
测试模块,运行 myserver 。
查看命令选项,运行 myserver --help
commander 使用
#! /usr/bin/env node
const { program } = require('commander')
const options = {
'-p --port <dir>': {
description: 'init server port',
example: 'myserver -p 3306'
},
'-d --directory <dir>': {
description: 'init server directory',
example: 'myserver -d c:'
}
}
function formatConfig(configs, cb) {
Object.entries(configs).forEach(([key, val]) => {
cb(key, val)
})
}
formatConfig(options, (cmd, val) => {
program.option(cmd, val.description)
})
program.on('--help', () => {
console.log('\nExamples: ')
formatConfig(options, (cmd, val) => {
console.log(val.example)
})
})
program.name('myserver')
const version = require('../package.json').version
program.version(version)
program.parse(process.argv)
const cmdConfig = program.opts()
console.log(cmdConfig)
测试运行 myserver -p 3000 -d e:
启动 web 服务
#! /usr/bin/env node
const { program } = require('commander')
const options = {
'-p --port <dir>': {
description: 'init server port',
example: 'myserver -p 3306'
},
'-d --directory <dir>': {
description: 'init server directory',
example: 'myserver -d c:'
}
}
function formatConfig(configs, cb) {
Object.entries(configs).forEach(([key, val]) => {
cb(key, val)
})
}
formatConfig(options, (cmd, val) => {
program.option(cmd, val.description)
})
program.on('--help', () => {
console.log('\nExamples: ')
formatConfig(options, (cmd, val) => {
console.log(val.example)
})
})
program.name('myserver')
const version = require('../package.json').version
program.version(version)
program.parse(process.argv)
const cmdConfig = program.opts()
const Server = require('../main.js')
new Server(cmdConfig).start()
const http = require('http')
function mergeConfig(config) {
return {
port: 1234,
directory: process.cwd(),
...config
}
}
class Server {
constructor(config) {
this.config = mergeConfig(config)
}
start() {
const server = http.createServer(this.serverHandle.bind(this))
server.listen(this.config.port, () => {
console.log(`服务已经启动,地址:http://localhost:${this.config.port}`)
})
}
serverHandle(req, res) {
console.log('接收到请求')
}
}
module.exports = Server
运行 myserver 启动 web 服务器,访问 http://localhost:1234
处理文件资源
const http = require('http')
const url = require('url')
const path = require('path')
const fs = require('fs').promises
const { createReadStream } = require('fs')
const mime = require('mime')
function mergeConfig(config) {
return {
port: 1234,
directory: process.cwd(),
...config
}
}
class Server {
constructor(config) {
this.config = mergeConfig(config)
}
start() {
const server = http.createServer(this.serverHandle.bind(this))
server.listen(this.config.port, () => {
console.log(`服务已经启动,地址:http://localhost:${this.config.port}`)
})
}
async serverHandle(req, res) {
let { pathname } = url.parse(req.url)
pathname = decodeURIComponent(pathname)
const absPath = path.join(this.config.directory, pathname)
try {
const statObj = await fs.stat(absPath)
if (statObj.isFile()) {
this.fileHandle(req, res, absPath)
} else {
}
} catch (err) {
this.errorHandle(req, res, err)
}
}
errorHandle(req, res, err) {
console.log(err)
res.stateCode = 404
res.setHeader('Content-type', 'text/html;charset=utf-8')
res.end('Not Found')
}
fileHandle(req, res, absPath) {
res.statusCode = 200
res.setHeader('Content-type', mime.getType(absPath) + ';charset=utf-8')
createReadStream(absPath).pipe(res)
}
}
module.exports = Server
测试:
- 运行
myserver - 访问 http://localhost:1234/main.js
处理目录资源
const http = require('http')
const url = require('url')
const path = require('path')
const fs = require('fs').promises
const { createReadStream } = require('fs')
const mime = require('mime')
const ejs = require('ejs')
const { promisify } = require('util')
function mergeConfig(config) {
return {
port: 1234,
directory: process.cwd(),
...config
}
}
class Server {
constructor(config) {
this.config = mergeConfig(config)
}
start() {
const server = http.createServer(this.serverHandle.bind(this))
server.listen(this.config.port, () => {
console.log(`服务已经启动,地址:http://localhost:${this.config.port}`)
})
}
async serverHandle(req, res) {
let { pathname } = url.parse(req.url)
pathname = decodeURIComponent(pathname)
const absPath = path.join(this.config.directory, pathname)
try {
const statObj = await fs.stat(absPath)
if (statObj.isFile()) {
this.fileHandle(req, res, absPath)
} else {
let dirs = await fs.readdir(absPath)
dirs = dirs.map(item => ({
path: path.join(pathname, item),
dirs: item
}))
const renderFile = promisify(ejs.renderFile)
const ret = await renderFile(path.resolve(__dirname, 'template.html'), {
arr: dirs,
parent: pathname !== '/',
parentPath: path.dirname(pathname),
title: path.basename(absPath)
})
res.end(ret)
}
} catch (err) {
this.errorHandle(req, res, err)
}
}
errorHandle(req, res, err) {
console.log(err)
res.stateCode = 404
res.setHeader('Content-type', 'text/html;charset=utf-8')
res.end('Not Found')
}
fileHandle(req, res, absPath) {
res.statusCode = 200
res.setHeader('Content-type', mime.getType(absPath) + ';charset=utf-8')
createReadStream(absPath).pipe(res)
}
}
module.exports = Server
创建模板文件 template.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<h3>indexOf <%=title%></h3>
<ul>
<%if(parent) {%>
<li><a href="<%=parentPath%>">../</a></li>
<%}%>
<%for(let i = 0; i < arr.length;i++) {%>
<li><a href="<%=arr[i].path%>"><%=arr[i].dirs%></a></li>
<%}%>
</ul>
</body>
</html>
测试:
- 运行
myserver - 访问 http://localhost:1234
|