代理可以解决浏览器跨域请求的问题。
服务器之间是不存在跨域的,可以使用 NodeJS 创建一个客户端代理,由它代替浏览器客户端直接向服务端发送请求。
浏览器客户端也可以将发送给服务端的请求发送给客户端代理,由客户端代理转为发送,解决跨域问题。
// 服务端
const http = require('http')
const url = require('url')
const querystring = require('querystring')
const server = http.createServer((req, res) => {
// const { pathname, query } = url.parse(req.url, true)
// console.log(pathname, query)
const arr = []
req.on('data', chunk => {
arr.push(chunk)
})
req.on('end', () => {
const body = Buffer.concat(arr).toString()
// console.log(body)
const contentType = req.headers['content-type']
// 根据不同格式进行解析 处理JSON格式和form表单
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')
// 直接发送 get 请求
// http.get(
// {
// host: 'localhost',
// port: 1234,
// path: '/?a=1'
// },
// res => {}
// )
const options = {
host: 'localhost',
port: 1234,
path: '/?a=1',
method: 'POST',
headers: {
// 'Content-Type': 'application/json' // json 格式
'Content-type': 'application/x-www-form-urlencoded' // form 表单格式
}
}
// 创建一个可以发送请求的客户端
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('你好张三')
// json 格式
// req.end('{"name": "张三"}')
// form 表单
req.end('a=1&b=2')
客户端代理解决跨域
浏览器客户端访问代理的客户端 localhost:1000 即可。
// 服务端
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('本地服务端启动了')
})
HTTP 静态服务
使用 http 模块开启一个服务端,由浏览器充当客户端,按照一定的路径访问目标服务器提供的静态资源。
初始化
- 安装工具:npm install mime
示例文件
├─ www
│ └─ index.html # 测试用 html 文件
├─ index.css # 测试用 css 文件
├─ index.html # 测试用 html 文件
└─ server.js
示例代码
```javascript 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) => { // 1 路径处理 let { pathname, query } = url.parse(req.url) // 处理中文路径 pathname = decodeURIComponent(pathname) // 拼接绝对路径 const absPath = path.join(__dirname, pathname)
// 2 目标资源状态处理 fs.stat(absPath, (err, statObj) => { if (err) { res.statusCode = 404 res.end(‘Not Found’) return }
if (statObj.isFile()) {
// 路径对应的目标是一个文件,可以直接读取然后回写
fs.readFile(absPath, (err, data) => {
// mime 包可以通过传入文件名获取对应的 mime 类型
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…’) })
<a name="Lez1s"></a>
# 静态服务工具
使用 NodeJS 内置模块和一些第三方工具包实现类似 serve 的命令行工具,调用相应的命令可以在指定的目录下开启一个 web 服务。
<a name="lC0zI"></a>
## 初始化项目
创建目录 myserver。<br />在目录下创建文件 bin/www.js(处理命令行选项和执行主要逻辑) 和 main.js(存放主要逻辑)<br />npm init -y 初始化 package.json,并修改可执行文件路径:
```javascript
{
"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
// bin/www.js
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
// bin/www.js
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)
启动 web 服务
#! /usr/bin/env node
// bin/www.js
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()
// main.js
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
处理文件资源
// 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')
// 合并配置
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)
// 响应404
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')
// 创建可读流读取文件,写入到 res(可写流)
createReadStream(absPath).pipe(res)
}
}
module.exports = Server
测试:
- 运行 myserver
- 访问 http://localhost:1234/main.js
处理目录资源
```javascript // 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’) // promisify 用于将异步回调方法改成返回 Pormise 实例的方法 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) // 响应404 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’) // 创建可读流读取文件,写入到 res(可写流) createReadStream(absPath).pipe(res) } }
module.exports = Server
创建模板文件 template.html:
```javascript
<!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