Nuxt3中间件及api请求的代理实现

文章介绍了在Nuxt3环境中,如何处理开发模式下Vue组件与后台交互的代理问题,包括使用nuxtjs/proxy、h3的内置方法如sendProxy、proxyRequest,http-proxy模块以及nuxt-proxy模块等不同方式。同时提到了Nuxt3的middleware在前端和后端请求拦截中的应用,以及对Nitro引擎的devProxy配置的提及。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

问题背景

在Nuxt3应用中,通常在开发模式下,vue组件与后台交互是经过nuxt的server层的,而实际生产环境是经过nginx转发的。但开发模式下,经nuxt的server层转发请求有多种实现方式,也会有形形色色的问题,如转发cookie,转发websocket请求等等。

本文梳理了一下目前常见的代理实现方式。在Nuxt 2中可以直接采用 @nuxtjs/proxy包。
了解代理的实现,能帮助我们进一步掌握Nuxt3中间件概念和一些API。

nuxt3的middleware使用

middleware可以拦截所有请求,并修改请求。
nuxt3项目下有两个middleware的预置目录,一个是/middleware,一个是/server/middleware。一个是拦截前端的请求,一个是拦截后端的请求。注意:/server目录的下的代码都是运行在服务端的,也就是Nodejs进程内。

在/server/middleware的代码中,是可以自动导入h3的函数的。

采用h3的sendProxy()

sendProxy是h3的内置方法。

import { sendProxy } from "h3";

const config = useRuntimeConfig();

export default defineEventHandler(async (event) => {
  const target = new URL(
    event.req.url.replace(/^\/proxy-api/, ""),
    config.proxyApiUrl
  );

  return await sendProxy(event, target.toString());
});

上述代码放到server/routes/proxy-api/[…].ts下即可。

采用h3的内置API实现转发

import {defineEventHandler, getCookie, getHeaders, getMethod, getQuery, readBody} from "h3";
const config = useRuntimeConfig()
const baseURL = config.public.apiBaseUrl
export default defineEventHandler(async (event) => {
    const method = getMethod(event)
    const params = getQuery(event)

    const headers = getHeaders(event)
    const authorization = headers.Authorization || getCookie(event, 'auth._token.local')

    const url = event.req.url as string

    const body = method === "GET" ? undefined : await readBody(event)

    return await $fetch(url, {
        headers: {
            "Content-Type": headers["content-type"] as string,
            Authorization: authorization as string,
        },
        baseURL,
        method,
        params,
        body,
    })
})

采用h3的proxyRequest()方法

/server/middleware/proxy.ts

export default defineEventHandler((event) => {
  // proxy only "/api" requests
  if (!event.node.req.url?.startsWith('/api/')) return

  const { apiBaseUrl } = useRuntimeConfig()
  const target = new URL(event.node.req.url, apiBaseUrl)

  if (should_be_proxied)
    return proxyRequest(event, target.toString(), {
      headers: {
        host: target.host // if you need to bypass host security
      }
    })
})

采用http-proxy模块

在server/middleware下创建proxy.ts文件:

import httpProxy from 'http-proxy';

const proxy = httpProxy.createProxyServer({
  target: 'http://localhost:7500/', // change to your backend api url
  changeOrigin: true,
});

export default defineEventHandler((event) => {
  return new Promise((resolve) => {
    const options = {};

    const origEnd = event.res.end;
    event.res.end = function() {
      resolve(null);
      return origEnd.call(event.res);
    }  
    const prefix = '/api'
  	if (req.url.startsWith(prefix)) {
      proxy.web(event.req, event.res, options); // proxy.web() works asynchronously
    } else {
      next()
    }
    
  });
});

采用Nitro引擎的devProxy选型

在nuxt.config.ts中配置nitro.devProxy选择,此方法我没有验证过。

// config:
export default defineNuxtConfig({
    nitro: {
        devProxy: {
            '/api/': {
                target: process.env.API_TARGET,
                changeOrigin: true
            }
        }
    }
})

// ======================
// later, on server side:
// ======================

const r = await (await fetch('/api/test')).json();
// => 500 Invalid URL

采用nuxt-proxy模块

老外基于http-proxy-middleware和h3写的一个nuxt3代理中间件。
地址:https://github.com/wobsoriano/nuxt-proxy

export default defineNuxtConfig({
  modules: ['nuxt-proxy'],
  // See options here https://github.com/chimurai/http-proxy-middleware#options
  proxy: {
    options: {
      target: 'https://jsonplaceholder.typicode.com',
      changeOrigin: true,
      pathRewrite: {
        '^/api/todos': '/todos',
        '^/api/users': '/users'
      },
      pathFilter: [
        '/api/todos',
        '/api/users'
      ]
    }
  },
  // OR
  // runtimeConfig: {
  //   proxy: {...}
  // }
})

// GET /api/todos -> https://jsonplaceholder.typicode.com/todos [304]
// GET /api/users -> https://jsonplaceholder.typicode.com/users [304]

基于http-proxy-middleware和http模块的实现

也是写一个nuxt3的中间件来实现。
server/middleware/api-proxy.ts

import type { IncomingMessage, ServerResponse } from 'http'
import { createProxyMiddleware } from 'http-proxy-middleware'
import config from '#config'

// Temporary dev proxy until @nuxtjs/proxy module is available.

const apiProxyMiddleware = createProxyMiddleware('/api-proxy/**', {
	target: config.API_URL as string,
	changeOrigin: true,
	pathRewrite: { '^/api-proxy/': '/' },
	logLevel: 'debug',
})

export default async (req: IncomingMessage, res: ServerResponse) => {
	// Workaround for h3 not awaiting next.
	await new Promise<void>((resolve, reject) => {
		const next = (err?: unknown) => {
			if (err) {
				reject(err)
			} else {
				resolve()
			}
		}

		// @ts-expect-error -- incompatible types express.Request and http.IncomingMessage. This still works though.
		apiProxyMiddleware(req, res, next)
	})
}

然后,你可以写一个useFetch的wrapper API, 例如叫callApi(),写法如下:

import { murmurHashV3 } from 'murmurhash-es'

type RequestOptions = {
	method?: string
	body?: Record<string, unknown>
	pick?: string[]
	params?: Record<string, unknown>
}

function getBaseURL() {
	const config = useRuntimeConfig() as { API_URL: string }
	return process.server ? config.API_URL : '/api-proxy'
}

export const useApi = async <Result = unknown>(
	endpoint: string,
	opts?: RequestOptions
) => {
	const baseURL = getBaseURL()
	const headers = useRequestHeaders(['cookie'])

	return useFetch<string, Result>(endpoint, {
		method: opts?.method,
		body: opts?.body,
		baseURL,
		headers,
		params: opts?.params,
		// The default key implementation includes the baseURL in the hasing process.
		// As this is different for server and client, the default implementation leads to different
		// keys, resulting in hydration errors.
		key: '$api-' + murmurHashV3(JSON.stringify({ endpoint, opts })).toString(),
	})
}

参考链接

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

北极象

如果觉得对您有帮助,鼓励一下

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值