代理可以解决浏览器跨域请求的问题。
服务器之间是不存在跨域的,可以使用 NodeJS 创建一个客户端代理,由它代替浏览器客户端直接向服务端发送请求。
浏览器客户端也可以将发送给服务端的请求发送给客户端代理,由客户端代理转为发送,解决跨域问题。

  1. // 服务端
  2. const http = require('http')
  3. const url = require('url')
  4. const querystring = require('querystring')
  5. const server = http.createServer((req, res) => {
  6. // const { pathname, query } = url.parse(req.url, true)
  7. // console.log(pathname, query)
  8. const arr = []
  9. req.on('data', chunk => {
  10. arr.push(chunk)
  11. })
  12. req.on('end', () => {
  13. const body = Buffer.concat(arr).toString()
  14. // console.log(body)
  15. const contentType = req.headers['content-type']
  16. // 根据不同格式进行解析 处理JSON格式和form表单
  17. if (contentType === 'application/json') {
  18. const obj = JSON.parse(body)
  19. obj.age = 20
  20. res.end(JSON.stringify(obj))
  21. } else if (contentType === 'application/x-www-form-urlencoded') {
  22. const obj = querystring.parse(body)
  23. res.end(JSON.stringify(obj))
  24. }
  25. })
  26. })
  27. server.listen(1234, () => {
  28. console.log('server is running...')
  29. })
  1. // 客户端代理
  2. const http = require('http')
  3. // 直接发送 get 请求
  4. // http.get(
  5. // {
  6. // host: 'localhost',
  7. // port: 1234,
  8. // path: '/?a=1'
  9. // },
  10. // res => {}
  11. // )
  12. const options = {
  13. host: 'localhost',
  14. port: 1234,
  15. path: '/?a=1',
  16. method: 'POST',
  17. headers: {
  18. // 'Content-Type': 'application/json' // json 格式
  19. 'Content-type': 'application/x-www-form-urlencoded' // form 表单格式
  20. }
  21. }
  22. // 创建一个可以发送请求的客户端
  23. const req = http.request(options, res => {
  24. const arr = []
  25. res.on('data', chunk => {
  26. arr.push(chunk)
  27. })
  28. res.on('end', () => {
  29. console.log(Buffer.concat(arr).toString())
  30. })
  31. })
  32. // 发送请求
  33. // 字符串
  34. // req.end('你好张三')
  35. // json 格式
  36. // req.end('{"name": "张三"}')
  37. // form 表单
  38. req.end('a=1&b=2')

客户端代理解决跨域

浏览器客户端访问代理的客户端 localhost:1000 即可。

  1. // 服务端
  2. const http = require('http')
  3. const server = http.createServer((req, res) => {
  4. const arr = []
  5. req.on('data', chunk => {
  6. arr.push(chunk)
  7. })
  8. req.on('end', () => {
  9. console.log(Buffer.concat(arr).toString())
  10. res.end('获取到了客户端的数据')
  11. })
  12. })
  13. server.listen(1234, () => {
  14. console.log('外部服务端启动了')
  15. })
  1. // 客户端代理
  2. const http = require('http')
  3. const options = {
  4. host: 'localhost',
  5. port: 1234,
  6. path: '/',
  7. method: 'POST'
  8. }
  9. const server = http.createServer((request, response) => {
  10. // 向服务端发送请求
  11. const req = http.request(options, res => {
  12. const arr = []
  13. res.on('data', chunk => {
  14. arr.push(chunk)
  15. })
  16. res.on('end', () => {
  17. const ret = Buffer.concat(arr).toString()
  18. response.setHeader('content-type', 'text/html;charset=utf-8')
  19. response.end(ret)
  20. })
  21. })
  22. req.end('你好张三')
  23. })
  24. server.listen(1000, () => {
  25. console.log('本地服务端启动了')
  26. })

HTTP 静态服务

使用 http 模块开启一个服务端,由浏览器充当客户端,按照一定的路径访问目标服务器提供的静态资源。

初始化

  • 安装工具:npm install mime

    示例文件

    1. ├─ www
    2. └─ index.html # 测试用 html 文件
    3. ├─ index.css # 测试用 css 文件
    4. ├─ index.html # 测试用 html 文件
    5. └─ 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 }

  1. if (statObj.isFile()) {
  2. // 路径对应的目标是一个文件,可以直接读取然后回写
  3. fs.readFile(absPath, (err, data) => {
  4. // mime 包可以通过传入文件名获取对应的 mime 类型
  5. res.setHeader('Content-type', mime.getType(absPath) + ';charset=utf-8')
  6. res.end(data)
  7. })
  8. } else {
  9. // 目录
  10. fs.readFile(path.join(absPath, 'index.html'), (err, data) => {
  11. res.setHeader('Content-type', mime.getType(absPath) + ';charset=utf-8')
  12. res.end(data)
  13. })
  14. }

}) })

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)

测试运行 myserver -p 3000 -d e:

启动 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>

测试: