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

问题背景

在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(),
	})
}

参考链接

猜你喜欢

转载自blog.csdn.net/jgku/article/details/130005887