You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Serverless React从S3获取HTML:异步调用同步代码问题排查

问题与解决方案

部署在AWS Lambda的Serverless+React项目,尝试从S3存储桶获取HTML内容并通过dangerouslySetInnerHTML渲染,但await s3.getObject().promise()无返回,关键日志未出现在CloudWatch,页面无法正常显示内容。已配置IAM角色允许s3:GetObject操作。

关键问题排查与修复

1. 异步函数未执行(最直接原因)

你的React代码中,调用createPageBody的匿名async函数仅被定义,未执行:

// 错误写法:仅定义函数,未调用
(async() => {
    await createPageBody(); 
})
// 正确写法:加上()执行函数
(async() => {
    await createPageBody(); 
})();

这直接导致createPageBody从未运行,所以看不到function createPageBody、before await s3.getObject.promise等关键日志。

2. React组件中异步操作的正确写法

在React组件函数体中直接调用异步函数,会导致组件每次渲染时重复执行该操作,且可能引发状态更新的竞态问题。应该用useEffect钩子包裹异步操作,控制执行时机:

import "./App.css";
import * as React from "react";
import useConfig from "./components/useConfig";
import logo from "./logo.svg";
import logoPsych from "./psych_mind_logo.jpg"

export default function App() {
  console.log("Start: 'function App()'");

  const [html, setHtml] = React.useState<{ __html: string}>({ __html: '<h1>Blog goes here</h1>'})
  const config = useConfig();

  async function createPageBody() {
      console.log("function createPageBody");
      const AWS = require('aws-sdk');

      const params = {
        Bucket: 'mybucket',
        Key: 'myObjectFileName.html'
      }
      let s3 = new AWS.S3();

      console.log("before await s3.getObject.promise");
      try {
          const output = await s3.getObject(params).promise();
          console.log("after await s3.getObject.promise");
          const fileContents = output.Body?.toString() || "<h1>Failed to get S3 File/Body</h1>";
          console.log("S3 fileContents=");
          console.log(fileContents);
          setHtml({ __html: fileContents})      
      } catch (e) {
          console.log("s3.getObject: error=" + e.message); 
          setHtml({ __html: e.message})      
      }
  }         

  // 使用useEffect控制异步操作仅在组件挂载时执行一次
  React.useEffect(() => {
    const fetchData = async () => {
      await createPageBody();
    };
    fetchData();
    // 空依赖数组确保仅执行一次
  }, []);

  return (
    <div className="App">
      <header className="App-header">
         <img src={logoPsych} className="App-logo" alt="logo" />
         <h1 className="App-title">CognitivePsychology.com</h1>
      </header>
      <p className="App-intro">
            <div dangerouslySetInnerHTML={html} />
      </p>
    </div>
  );
}

3. 前端直接调用S3的注意事项

如果选择在React前端(浏览器环境)直接调用S3,需要额外配置:

  • S3桶CORS配置:确保桶的CORS规则允许你的前端域名(或Lambda分配的域名)发起GET请求:
    <?xml version="1.0" encoding="UTF-8"?>
    <CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
    <CORSRule>
        <AllowedOrigin>*</AllowedOrigin> <!-- 生产环境建议限制为具体域名 -->
        <AllowedMethod>GET</AllowedMethod>
        <AllowedHeader>*</AllowedHeader>
    </CORSRule>
    </CORSConfiguration>
    
  • AWS凭证配置:浏览器环境中不能直接使用IAM用户密钥,需要通过AWS Cognito身份池或Lambda代理提供临时凭证。否则会出现权限验证失败的问题(即使Lambda的IAM角色有权限,前端的AWS SDK是独立上下文)。

4. 更安全的方案:通过Lambda代理访问S3

避免前端直接操作S3,改为前端调用你的Lambda API,由Lambda后端去获取S3内容并返回给前端:

  1. 修改Lambda handler(handler.serve),添加获取S3内容的逻辑:
    const AWS = require('aws-sdk');
    const s3 = new AWS.S3();
    
    module.exports.serve = async (event) => {
      // 处理静态资源请求(React打包后的文件)
      if (event.path === '/api/get-s3-html') {
        try {
          const params = {
            Bucket: 'mybucket',
            Key: 'myObjectFileName.html'
          };
          const output = await s3.getObject(params).promise();
          const htmlContent = output.Body.toString();
          return {
            statusCode: 200,
            headers: {
              'Content-Type': 'text/html',
              'Access-Control-Allow-Origin': '*' // 生产环境限制域名
            },
            body: htmlContent
          };
        } catch (err) {
          return {
            statusCode: 500,
            headers: {
              'Access-Control-Allow-Origin': '*'
            },
            body: JSON.stringify({ error: err.message })
          };
        }
      }
      // 原有React静态资源服务逻辑...
    };
    
  2. React前端改为调用Lambda API获取内容:
    async function createPageBody() {
        try {
            const response = await fetch('/api/get-s3-html');
            const fileContents = await response.text();
            setHtml({ __html: fileContents});
        } catch (e) {
            console.log("Fetch error: " + e.message);
            setHtml({ __html: e.message});
        }
    }
    

这种方式更安全,所有S3操作都在后端完成,无需在前端暴露AWS凭证,同时Lambda的IAM权限配置已经生效。

5. 日志排查补充

  • 确保Lambda的日志级别正确,CloudWatch日志组中能看到Lambda的输出(如果用代理方案,前端的日志在浏览器控制台,后端日志在CloudWatch)。
  • 如果前端直接调用S3,浏览器控制台的Network和Console标签会显示请求的错误信息(比如CORS错误、权限错误),这比CloudWatch日志更直接。

内容的提问来源于stack exchange,提问作者NealWalters

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 22:45:37