koa + http-proxy-middleware 搭建一个带转发的静态服务器

背景

由于工作中碰到写普通页面(未使用脚手架),需要发起接口请求,但普通页面又无法对接口发起正常请求,故编写一个Koa搭建的带转发功能的静态服务器。

起步

  1. 新建一个文件夹,在文件夹下打开 cmd 或者 git 之类的窗口,输入 pnpm init ,然后输入一路输入各种基础信息后,再次输入pnpm add koa --save ,等待安装完成后,就可以创建简单的koa服务器了。

  2. 在这里,我使用的是esmodule的形式,所以,js 文件名都是以 mjs 结尾的。文件目录结构如下:
    在这里插入图片描述

  3. 要让 koa 能够展示普通html文件,就需要 pnpm add koa-static --save,这个是 koa 文件服务器的中间件,有了它就可以很方便的读取服务器目录下的文件了
    到这一步,koa(文件名我命名为koaServer.mjs)的代码如下:

import Koa from 'koa'
import path from 'path'
import { fileURLToPath } from 'node:url'  // fileURLToPath 作用是将文件 URL 转换为平台特定的本地文件路径
import staticServer from 'koa-static'

const PORT = 3001
const app = new Koa()

// import.meta: 为 import 命令添加了一个元属性import.meta,返回当前模块的元信息
const __fileUrl = fileURLToPath(import.meta.url) // 返回当前模块(js)的 URL 路径
const __dirname = path.dirname(__fileUrl) // 组装当前静态文件所在的目录

// staticServer传入当前静态目录路径
app.use(staticServer(path.join(__dirname, './static')))

app.listen(PORT, () => {
    console.log('listen: ' + PORT)
})

在根目录下,创建static文件夹,写一个简单的index.html文件夹,然后在控制台输入 node koaServer.mjs 启动服务,然后浏览器输入localhost:3001/index.html 可以看到页面被打开
DEMO

  1. 页面已经能打开,接下来要做的就是加入请求转发功能,一般说到转发中间件,常用的就是就是 http-proxy-middleware这个插件。pnpm 安装此插件,并引入到koaServer.js文件。根据npmjs上面的文档,将文档上面的示例复制过来
import Koa from 'koa'
import path from 'path'
import { fileURLToPath } from 'node:url'  // fileURLToPath 作用是将文件 URL 转换为平台特定的本地文件路径
import staticServer from 'koa-static'

import { createProxyMiddleware } from 'http-proxy-middleware'

const PORT = 3001
const app = new Koa()

// import.meta: 为 import 命令添加了一个元属性import.meta,返回当前模块的元信息
const __fileUrl = fileURLToPath(import.meta.url) // 返回当前模块(js)的 URL 路径
const __dirname = path.dirname(__fileUrl) // 组装当前静态文件所在的目录

// staticServer传入当前静态目录路径
app.use(staticServer(path.join(__dirname, './static')))

const exampleProxy = createProxyMiddleware({
  pathFilter: '/api', // 匹配以/api开头的路径
  pathRewrite: {
    '/api': ''
  }, // 把/api去除掉
  target: 'https://jsonplaceholder.typicode.com', // target host with the same base path
  changeOrigin: true, // needed for virtual hosted sites
})

app.use(exampleProxy)

app.listen(PORT, () => {
    console.log('listen: ' + PORT)
})

然后 node koaServer.mjs 运行起来,直接就报错
在这里插入图片描述
然后经过搜索得知,http-proxy-middleware 是 express 社区的插件,所以需要再安装 koa2-connect 这个依赖包,koa-connect作用就是在 Koa2 中可以使用 Express 社区的中间件,起到了一个中转或者适配的作用。执行 pnpm add koa-connect --save,安装成功后,在 koaServer.mjs 中引用进来。修改后的代码如下

import Koa from 'koa'
import path from 'path'
import { fileURLToPath } from 'node:url'  // fileURLToPath 作用是将文件 URL 转换为平台特定的本地文件路径
import staticServer from 'koa-static'

import { createProxyMiddleware } from 'http-proxy-middleware'
import Koa2Connect from 'koa2-connect'

const PORT = 3001
const app = new Koa()

// import.meta: 为 import 命令添加了一个元属性import.meta,返回当前模块的元信息
const __fileUrl = fileURLToPath(import.meta.url) // 返回当前模块(js)的 URL 路径
const __dirname = path.dirname(__fileUrl) // 组装当前静态文件所在的目录

// staticServer传入当前静态目录路径
app.use(staticServer(path.join(__dirname, './static')))

const exampleProxy = createProxyMiddleware({
  pathFilter: '/api', // 匹配以/api开头的路径
  pathRewrite: {
    '/api': ''
  }, // 把/api去除掉
  target: 'https://jsonplaceholder.typicode.com', // target host with the same base path
  changeOrigin: true, // needed for virtual hosted sites
})


app.use(Koa2Connect(exampleProxy))

app.listen(PORT, () => {
    console.log('listen: ' + PORT)
})

再次启动脚本运行,然后在 static/index.html 中,写接口请求
在这里插入图片描述

在控制台中就可以看到打印了请求的响应了在这里插入图片描述

改进

到目前为止,这个 koa2 搭建的简易服务器就可以使用了,但是在日常工作中,转发的请求可能不止一个,并且想要在控制台打印一些信息(响应结果等),就需要做进一步的修改了。

  1. 将转发请求的使用独立出来 proxy.mjs,变成可配置的,也就是批量增加多个 createProxyMiddleware 。http-proxy-middleware 的 createProxyMiddleware 方法每次只能转发到一个目标,故需要安装 koa-compose 来整合多个 createProxyMiddleware ,安装此插件,代码修改如下
import { createProxyMiddleware } from 'http-proxy-middleware'
import Koa2Connect from 'koa2-connect'
import koaCompose from 'koa-compose'

const proxies = [
  {
    pathFilter: '/api1', // 匹配以/api1开头的路径
    pathRewrite: {
      '/api1': ''
    }, // 把/api去除掉
    target: 'https://jsonplaceholder.typicode.com', // target host with the same base path
    changeOrigin: true, // needed for virtual hosted sites
  },
  {
    pathFilter: '/api2',
    pathRewrite: {
      '/api2': ''
    },
    target: 'https://jsonplaceholder.typicode.com', // target host with the same base path
    changeOrigin: true, // needed for virtual hosted site
  },
]

// 导出所有多个转发中间件
export default koaCompose(
  proxies.map(proxy => Koa2Connect(createProxyMiddleware(proxy)))
)

然后在 koaServer.mjs 中引入 proxy.mjs,app.use() 一下就可以了,经过页面的请求测试,均可以将接口转发到配置的目标。

  1. 要查看到转发的响应结果,就需要查看 http-proxy-middleware 的文档,使用到的事件有 proxyReq 和 proxyRes
    在这里插入图片描述
    给代理配置批量增加事件监听,代码如下:
// 给代理配置增加监听
proxies.forEach((proxy) => {
  proxy.on = (() => {
    return {
      // 打印请求转发
      proxyReq: function onProxyReq(proxyReq, req, res) {
        proxyReq.setHeader('Cache-Control', 'no-cache')
        console.log(' 🚀 ', req.method, req.url, '->', proxyReq.host + proxyReq.path)
      },
      proxyRes: async function onProxyRes(proxyRes, req, res) {
        const responseBody = await getBody(proxyRes)
        console.log(' 🌠 ', req.method, proxyRes.statusCode, req.url, '->', responseBody)
      },
      error: (err) => {
        console.log(chalk.red('error'), ' 😱 ', err.code)
      }
    }
  })()
})

// 解析获取转发返回响应内容
function getBody(proxyRes) {
  return new Promise((resolve) => {
    let body = []
    proxyRes.on('data', function (chunk) {
      body.push(chunk)
    })
    proxyRes.on('end', async function (val) {
      body = Buffer.concat(body)
      try {
        // 判断响应是否有使用了gzip
        if (proxyRes.headers['content-encoding']?.toLowerCase() === 'gzip') {
          const ungzip = await zlib.gunzipSync(body) // 同步解压缩数据
          resolve(JSON.parse(ungzip.toString()))
        } else {
          resolve(JSON.parse(body.toString()))
        }
      } catch (error) {
        // JSON.parse 报错直接返回body
        resolve(body)
      }
    })
  })
}

在上面的代码中,有使用到了头部判断。但在ctx 里面的 headers 变量中,它里面的 key 都被 nodejs 转换成小写。在日常的一般业务中,我们对大小写敏感,所以需要对每个到 koa 的请求,进行头部字段的大小写还原。ctx.req.rawHeaders这个变量内部存储着原始字段,需要转换,代码如下,然后引入到 koa 中。

// 设置头部字段为原始内容,而不是node转换后的都是小写的头部字段
const setRawHeaders = async (ctx, next) => {
  const originHeaders = convertRawHeadersToObject(ctx.req.rawHeaders)
  ctx.request.headers = originHeaders
  await next()
}

// ['requestId', '1234565', 'clienntId', '123456'] 转换成 {requestId: '123456', clienntId: '123456'}
const convertRawHeadersToObject = (rawHeaders) => {
  const headers = {}
  for (let i = 0; i < rawHeaders.length; i += 2) {
    headers[rawHeaders[i]] = rawHeaders[i + 1]
  }
  return headers
}

export default setRawHeaders

到此,我们将这个简易服务器搭建起完成,能够实现对普通html页面的接口进行转发,以及在控制台查看相关参数内容。
如果有什么问题,欢迎指教,完整的代码在这里 https://github.com/402931261/simple-koa2-server

相关推荐

  1. 华纳云:如何一个简易文件服务器?

    2024-07-09 17:18:06       46 阅读
  2. 如何一个nginxweb服务器

    2024-07-09 17:18:06       38 阅读
  3. 如何使用EMQX一个私有MQTT服务器

    2024-07-09 17:18:06       32 阅读
  4. 多版本管理Ubuntu软件源服务器

    2024-07-09 17:18:06       61 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-09 17:18:06       67 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-09 17:18:06       71 阅读
  3. 在Django里面运行非项目文件

    2024-07-09 17:18:06       58 阅读
  4. Python语言-面向对象

    2024-07-09 17:18:06       69 阅读

热门阅读

  1. ChatGPT-4 对比 ChatGPT-3.5:有哪些优势

    2024-07-09 17:18:06       28 阅读
  2. GitHub:现代软件开发的协作平台

    2024-07-09 17:18:06       30 阅读
  3. 河北有机农业的元宇宙探索:科技赋能绿色农业

    2024-07-09 17:18:06       23 阅读
  4. eval和new Function构造函数时的区别

    2024-07-09 17:18:06       31 阅读
  5. Python 获取数组中连续数据的组数

    2024-07-09 17:18:06       27 阅读
  6. 大二暑假 + 大三上

    2024-07-09 17:18:06       27 阅读
  7. Git 常用命令及其作用

    2024-07-09 17:18:06       28 阅读