Astro:如何代理服务调用

问题描述 投票:0回答:3

我正在设置一个 Astro 站点,它将显示从运行在同一主机但不同端口上的简单服务获取的数据。

该服务是一个简单的 Express 应用程序。

server.js

const express = require('express')
const app = express()
const port = 3010

const response = {
  message: "hello"
}
app.get('/api/all', (_req, res) => {
  res.send(JSON.stringify(response))
})

app.listen(port, () => {
  console.log(`listening on port ${port}`)
})

由于该服务运行在端口 3010 上,这与 Astro 站点不同,因此我在 Vite 级别配置了一个 服务器代理

astro.config.mjs

import { defineConfig } from 'astro/config';
import react from '@astrojs/react';

export default defineConfig({
  integrations: [react()],
  vite: {
    optimizeDeps: {
      esbuildOptions: {
        define: {
          global: 'globalThis'
        }
      }
    },
    server: {
      proxy: {
        '/api/all': 'http://localhost:3010'
      }
    }
  },
});

这是我尝试调用服务的地方。

index.astro

---
const response = await fetch('/api/all');
const data = await response.json();
console.log(data);
---

当我运行

yarn dev
时,我得到以下控制台输出:

Response {
  size: 0,
  [Symbol(Body internals)]: {
    body: Readable {
      _readableState: [ReadableState],
      _events: [Object: null prototype],
      _eventsCount: 1,
      _maxListeners: undefined,
      _read: [Function (anonymous)],
      [Symbol(kCapture)]: false
    },
    stream: Readable {
      _readableState: [ReadableState],
      _events: [Object: null prototype],
      _eventsCount: 1,
      _maxListeners: undefined,
      _read: [Function (anonymous)],
      [Symbol(kCapture)]: false
    },
    boundary: null,
    disturbed: false,
    error: null
  },
  [Symbol(Response internals)]: {
    type: 'default',
    url: undefined,
    status: 404,
    statusText: '',
    headers: { date: 'Tue, 02 Aug 2022 19:41:02 GMT' },
    counter: undefined,
    highWaterMark: undefined
  }
}

网络请求似乎返回 404。

我在doc中没有看到更多有关服务器配置的信息。 我以正确的方式处理这件事吗?

我可以在普通 Vite 应用程序和相同的配置/设置下正常工作。

如何代理 Astro 应用程序的本地服务调用?

node.js http-status-code-404 vite astrojs
3个回答
2
投票

您可以使用

astro:server:setup
钩子添加自己的代理中间件。

例如在服务器设置挂钩中使用 http-proxy-middleware

// plugins/proxy-middleware.mjs
import { createProxyMiddleware } from "http-proxy-middleware"

export default (context, options) => {
  const apiProxy = createProxyMiddleware(context, options)

  return {
    name: 'proxy',
    hooks: {
      'astro:server:setup': ({ server }) => {
        server.middlewares.use(apiProxy)
      }
    }
  }
}

用途:

// astro.config.mjs
import { defineConfig } from 'astro/config';
import proxyMiddleware from './plugins/proxy-middleware.mjs';

// https://astro.build/config
export default defineConfig({
  integrations: [
    proxyMiddleware("/api/all", {
      target: "http://localhost:3010",
      changeOrigin: true,
    }),
  ],
});

0
投票

简短回答

您无法使用 Astro 代理服务调用,但您也不必这样做

有关直接解析答案,请参阅无需代理的功能测试部分

详情

  • Astro 不会将 server.proxy 配置转发到 Vite(除非您修补自己版本的 Astro),Astro Vite 服务器配置可以看到为空
proxy: {
        // add proxies here
     },

参考https://github.com/withastro/astro/blob/8c100a6fe6cc652c3799d1622e12c2c969f30510/packages/astro/src/core/create-vite.ts#L125

  • Astro 服务器与 Astro vite.server 配置合并,但它不采用代理参数。从代码中看出来这一点并不明显,请参阅稍后的测试。
let result = commonConfig;
    result = vite.mergeConfig(result, settings.config.vite || {});
    result = vite.mergeConfig(result, commandConfig);

参考https://github.com/withastro/astro/blob/8c100a6fe6cc652c3799d1622e12c2c969f30510/packages/astro/src/core/create-vite.ts#L167

测试

配置测试

我尝试了如何将配置输入 Astro 的所有可能组合,并在每个位置使用不同的端口号来显示哪个端口被覆盖

  • 根目录下的 vite.config.js 文件,带有
export default {
    server: {
      port:6000,
        proxy: {
          '/api': 'http://localhost:4000'
        }
      }
}
  • 根文件 astro.config.mjs 中的两个位置
    • 服务器
    • vite.服务器
export default defineConfig({
  server:{
    port: 3000,
    proxy: {
      '/api': 'http://localhost:4000'
    }
},
  integrations: [int_test()],
  vite: {
    optimizeDeps: {
      esbuildOptions: {
        define: {
          global: 'globalThis'
        }
      }
    },
    server: {
      port:5000,
      proxy: {
        '/api': 'http://localhost:4000'
      }
    }
  }
});
  • Astro 集成

Astro 有一个所谓的集成,可以帮助更新配置(类似于 Astro 插件),该集成有助于识别最终保留在配置中的内容,并且还提供了更新配置的最后机会

集成测试.js

async function config_setup({ updateConfig, config, addPageExtension, command }) {
    green_log(`astro:config:setup> running (${command})`)
    updateConfig({
        server:{proxy : {'/api': 'http://localhost:4000'}},
        vite:{server:{proxy : {'/api': 'http://localhost:4000'}}}
    })
    console.log(config.server)
    console.log(config.vite)
    green_log(`astro:config:setup> end`)
}

这是输出日志

 astro:config:setup> running (dev)
{ host: false, port: 3000, streaming: true }
{
  optimizeDeps: { esbuildOptions: { define: [Object] } },
  server: { port: 5000, proxy: { '/api': 'http://localhost:4000' } }
}
 astro:config:setup> end

代理参数从astro服务器配置中删除,vite配置可见,但没有任何效果,因为它被覆盖,并且没有转发到Vite

测试结果

  • 开发服务器在来自 Astro 配置的端口 3000 上运行
    server
    所有其他配置都被覆盖
  • 获取 api 失败并出现错误
 error   Failed to parse URL from /api
  File:
    D:\dev\astro\astro-examples\24_api-proxy\D:\dev\astro\astro-examples\24_api-proxy\src\pages\index.astro:15:20
  Stacktrace:
TypeError: Failed to parse URL from /api
    at Object.fetch (node:internal/deps/undici/undici:11118:11)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)

无需代理的功能测试

考虑到 Astro front Matter 运行在服务器端,构建期间处于 SSG 模式,服务器上页面加载时处于 SSR 模式,然后服务器发送结果 html,Astro 可以访问所有主机端口,并且可以直接使用服务端口,例如这个

const response = await fetch('http://localhost:4000/api');
const data = await response.json();
console.log(data);

上面的代码按预期运行,没有错误

参考示例

上述所有测试和文件都可以在参考示例 github 存储库中找到:https://github.com/MicroWebStacks/astro-examples/tree/main/24_api-proxy


0
投票

nwellis 的答案非常接近,但仅适用于开发服务器。如果你在生产中,那是行不通的。

这是一种在生产中可行的方法,但有一个警告。它需要 SSR 模式。您可以在here阅读有关动态路由的更多信息。另外,这仅限于节点版本 18 或更高版本,因为我在服务器上使用 fetch。如果您还在使用 Node 版本 < 18, fetch can be swapped with axios.

步骤1.

将 astro 配置输出值更改为“服务器”以允许 api 端点使用。

import { defineConfig } from "astro/config";

// https://astro.build/config
export default defineConfig({
  output: "server",
  // rest of config
});

第2步:

使用扩展语法创建一个名为

[...proxy].ts
的文件,位于 src > Pages > api > [...proxy].ts

这会动态匹配

api/*
处的任何 url。无论路径如何嵌套,它都将适用。

第三步:

添加以下内容或类似内容。这将捕获所有请求并转发到您想要的任何地方。

import type { APIRoute } from "astro";

const getProxyUrl = (request: Request) => {
  const proxyUrl = new URL("http://localhost:3000");
  const requestUrl = new URL(request.url);

  return new URL(requestUrl.pathname, proxyUrl);
};

export const ALL: APIRoute = async ({ request }) => {
  const proxyUrl = getProxyUrl(request);
  const response = await fetch(proxyUrl.href, request);
  return new Response(response.body);
};
© www.soinside.com 2019 - 2024. All rights reserved.