跳至內容
返回

透過 AWS 的 Function URL 打造單向即時傳送訊息的 Server-Sent Events(SSE)應用

前陣子我在開發一個 AI 應用時,使用 AWS API Gateway 和 Lambda 作為 API,去接 AWS Bedrock 的 LLM 模型,由於思考過程以及中間可能還會去呼叫其他協作工具,使用者等待的時間有可能長達數十秒,只用一個 Loading 的訊息在前端提示,UX 顯然不是很好。

現在的 ChatGPT Like 的介面,都會顯示進度訊息,像是「正在搜尋網站」、「正在匯整資料」,我覺得對於使用者來說,等待的時間不會減少,但由於不斷有動態回傳,心理等待時間會縮短。

當我想實作這個功能時,發現既有的 API Gateway + Lambda 架構存在兩個無法解決的問題:

首先,API Gateway 不支援串流回應。它的 LAMBDA_PROXY 整合會緩衝 Lambda 的所有回應,等全部完成後才一次傳給客戶端。即使 Lambda 本身在 2023 年就已經支援 Response Streaming,透過 API Gateway 調用時這個能力也完全無法發揮。

其次,API Gateway 有 29 秒的超時限制。當 AI 需要呼叫多個工具處理複雜任務時,很容易超過這個時間,連線就會被中斷。

在和 Claude Code 幾輪問答後,我發現需要的是 AWS 的 Function URL 機制——它能繞過 API Gateway,讓 Lambda 的串流能力直接發揮作用。

邊做邊回報背後的 SSE 機制

在討論 Function URL 之前,我們需要先理解 SSE(Server-Sent Events)。

SSE 其實是個「老技術」——它在 HTML5 時代就存在了。簡單來說,SSE 是一種讓伺服器能夠「主動推送」資料給瀏覽器的技術。

傳統的 HTTP 請求是一去一回,你輸入網址,網頁就整頁回傳下載,非常單純。

SSE 的行為則有點像是訂閱電子報:你訂閱一次(建立連線),之後每當有新內容,電子報服務就會自動推送新的一期給你,直到你取消訂閱(關閉連線)。

SSE vs WebSocket

說到長時間連線,通常大家就會想到 WebSocket,為什麼不用 WebSocket?

WebSocket 支援雙向通訊——就像電話,雙方都能隨時說話。但對於我的 AI 應用場景,我其實不需要「對話」,我只需要接收「AI 說的話」。這種情境下,SSE 反而更適合:

  1. 實作簡單:瀏覽器內建 EventSource API,不需要額外的 library
  2. 自動重連:連線斷了會自動重新連接,不需要自己寫邏輯
  3. 基於 HTTP:可以利用現有的 HTTP 基礎設施

想像你看著台積電的股價今天又飆升到哪裡,你需要不斷接收最新價格(單向),但你不需要回傳任何資料給伺服器。

同樣的,AI 生成回應時,我只需要接收進度更新,不需要中途回傳資料。

SSE 剛剛好。

Lambda 的兩條路:API Gateway 還是 Function URL?

當我遇到實作問題時,才知道原來 Lambda 有兩種對外提供服務的方式。

API Gateway

API Gateway 可能是大家最熟悉的對外通路,它就像一個超級管家,可以處理各種雜事:

但管家有自己的做事方式:29 秒的超時限制,而且不支援串流回應

這不是缺陷,而是設計考量。API Gateway 的 LAMBDA_PROXY 整合會緩衝 Lambda 的回應,等到全部準備好才一次傳給客戶端。就像去餐廳吃套餐一樣,要等所有菜都擺好了才一起上桌。對很多應用來說,這樣做有它的道理。但對我需要的「邊做邊回報」,它就幫不上忙了。

(補充:AWS 在 2024 年 6 月開始允許 REST API 申請提高超時限制,但需要權衡 throttle quota,設定也較複雜)

Function URL

Lambda Function URL 是 AWS 在 2022 年 4 月推出的新功能,設計理念完全不同。

它就像個在快炒店吃飯一樣,哪道菜炒好了不管三七二十一就立刻上桌。

優勢:

限制:

對我的需求來說,Function URL 的優勢正是我需要的:支援串流,而且時間足夠

但它的限制也很明顯:沒有安全防護、沒有自訂網域。這些問題,就需要其他服務來補足。

Lambda Streaming:原來 Lambda 早就會「邊做邊說」

理解了為什麼要用 Function URL,接下來要搞懂 Lambda 是怎麼做到串流的。

Lambda 在 2023 年推出了 Response Streaming 功能,能夠部分回傳資料,不用等到全部完成。問題是,這個能力只能透過 Function URL 或 AWS SDK 的 InvokeWithResponseStream API 使用,透過 API Gateway 的 LAMBDA_PROXY 會卡住用不了。

這也是為什麼我們需要 Function URL,它讓 Lambda 的串流能力得以發揮。

實際運作方式

在程式碼層面,差異在於使用 awslambda.streamifyResponse() 這個特殊的包裝器:

// 傳統方式
export async function handler(event) {
  const result = await processAllData(); // 等待所有資料
  return { statusCode: 200, body: result }; // 一次回傳
}

// Streaming 方式
export const handler = awslambda.streamifyResponse(
  async (event, responseStream) => {
    responseStream.setContentType("text/event-stream");

    responseStream.write('data: {"status":"searching"}\n\n');
    await searchWeb();

    responseStream.write('data: {"status":"processing"}\n\n');
    await processData();

    responseStream.write('data: {"status":"complete"}\n\n');
    responseStream.end();
  }
);

重點是 responseStream 物件,每次呼叫 write() 時,資料會立即透過 HTTP Chunked Transfer Encoding 傳送給客戶端。

這個機制帶來兩個重要好處:

  1. 改善使用者體驗:TTFB(Time to First Byte)從可能的 30 秒降到 1 秒內
  2. 支援大型回應:最大回應從 6 MB 提升到 200 MB

對 AI 應用來說,這意味著使用者不用呆等 30 秒後才看到回應,而是立刻就能看到「AI 正在思考」的進度。

如果客戶端網速很慢,Lambda 會不會要一直等它傳完才能結束嗎?那不就會超時?

其實不會。Lambda Runtime 會進行緩衝,函數執行時間只計算到 responseStream.end() 呼叫為止。後續傳輸花多久時間,都不影響計費和超時。

用 CloudFront 和 WAF 補上缺失的功能與安全性

到目前為止,Function URL + Streaming 已經能解決我的核心需求了。但還有三個問題:

  1. Function URL 的網址很醜(隨機 ID)
  2. 沒有 DDoS 防護和速率限制
  3. 全球存取延遲可能很高

這就是為什麼要加上 CloudFront 和 WAF。

CloudFront:全球快遞網

CloudFront 是 AWS 的 CDN 服務,使用它有以下的好處:

WAF

WAF(Web Application Firewall)則像是門禁系統,幫你擋掉惡意流量:

完整的架構流程

最終的架構長這樣:

flowchart TD
    A["**使用者**"] -->|"① HTTPS 請求<br/>(自訂網域)"| B["**CloudFront + WAF**<br/>速率限制 / Bot 防護"]
    B -->|"② OAC 認證"| C["**Lambda Function URL**"]
    C -->|"③ 調用函數"| D["**Lambda 函數**"]
    D -.->|"④ 串流回應"| C
    C -.->|"⑤ 回傳資料"| B
    B -.->|"⑥ SSE 串流"| A

    style B fill:#FF9900,stroke:#FF9900,color:#ffffff
    style C fill:#FF9900,stroke:#FF9900,color:#ffffff
    style D fill:#FF9900,stroke:#FF9900,color:#ffffff

關鍵的是 Origin Access Control(OAC):這個機制確保只有 CloudFront 能存取你的 Function URL,外部無法直接訪問。這縮小了攻擊面,所有流量都必須經過 CloudFront 的安全檢查。

與 S3 SPA 的整合

如果前端是放在 S3 的單頁應用(SPA),這個架構還有個額外好處:你可以用同一個 CloudFront distribution 處理:

一個網域解決所有問題。

SSE 架構成本會不會很高?

多加了 CloudFront 和 WAF,成本會增加多少?我自己也很在意這點。

成本拆解

Lambda Function URL:

Response Streaming:

CloudFront:

WAF:

與 API Gateway 的對比

如果用 API Gateway:

假設每月 100 萬次請求:

看起來 Function URL 方案稍貴,但:

  1. API Gateway 不支援串流,無法滿足需求
  2. API Gateway 有 29 秒限制
  3. Function URL 方案支援到 200 MB 的回應

對於需要邊做邊回傳的串流訊息,Function URL 方案是唯一選擇,而且成本不高,主要的投資其實是在安全性上。

使用 Function URL 的意外好處

在實作過程中,我發現這個架構還有個意外好處:除錯變簡單了

因為 Function URL 是直接的 HTTPS 端點,我可以用 curl 或 Postman 直接測試,不需要透過 API Gateway 的複雜設定。開發階段可以直接存取 Function URL,上線後再套上 CloudFront。

Function URL 這個 2022 年才出現的功能,簡單解決了串流回應的需求,不過單靠它本身的限制也很明顯,只有配合 CloudFront 和 WAF,剛好能組成一個生產環境可用的架構。

如果你也在做類似的 AI 應用,或是需要處理即時進度更新,這個簡單就能組合上線的搭配是值得考慮一下。


分享文章至:

下一篇文章
AWS 借 AI 之力再進一步:用自動化評估框架協助 DynamoDB 資料塑模