开发AEM Edge功能

IMPORTANT
AEM Edge函数当前为测试版。 功能和文档可能会更改。 如需反馈,请联系aemcs-edgecompute-feedback@adobe.com

我们的目标是生成动态Edge Delivery Services块,该块调用AEM Edge函数以从第三方API获取动态数据。

第一步是开发AEM Edge函数,该函数公开一个端点,将两个上游API调用组合为一个响应,并启用CORS以进行本地开发。 有关请求和响应合同、端点匹配以及出站fetch()的基础知识,请参阅使用Edge函数构建API端点。 此处仅介绍本教程的特定部分。

从样板开始

您的AEM Edge Functions项目(克隆在在Edge Delivery Services上设置AEM Edge Functions ​中)附带两个示例路径: /hello-world/weather,因此您从第一天开始就有测试需要的工作了。 两者都不属于实际项目,因此请先查看样板并删除不需要的内容。

定义API合同

在编写处理程序之前,请定义请求和响应形状,以便可以根据合同构建Edge Delivery Services块或任何其他调用方,而无需读取实现。

终结点: GET /api/frescopa/estimated-delivery

GET /api/frescopa/estimated-delivery?sku=house-blend-medium-roast&postcode=90210

postcode为必填项。 sku是可选的,默认为house-blend-medium-roast

响应正文:

成功的响应将返回此形状:

// 200 OK
{
  "sku": "house-blend-medium-roast",
  "productName": "House Blend - Medium Roast",
  "inventoryStatus": "in-stock", // or "low-stock", "out-of-stock"
  "qtyLeft": 12,
  "fulfillmentRegion": "West Coast Fulfillment",
  "deliveryEta": "tomorrow",
  "cutoffMessage": "Order by 2:00 PM for same-day dispatch.",
  "message": "Arrives tomorrow in West Coast Fulfillment"
}

错误响应共享此形状,每个失败原因的code各不相同:

// 400 missing postcode, 404 unknown sku, 405 wrong method: same shape, different code
{
  "error": "Missing required parameter",
  "code": "MISSING_POSTCODE", // or "UNKNOWN_SKU", "METHOD_NOT_ALLOWED"
  "message": "The postcode query parameter is required"
}

每个错误正文都包含一个code字段,因此调用方可以根据失败原因进行分支,而不是解析messageOPTIONS请求获取针对CORS预检的单独响应,包含在下面的为本地开发启用CORS中。

实施业务逻辑

index.js限制为路由。 业务逻辑位于src/handlers/下,每个端点一个文件夹。

您将添加的文件

src/
├── index.js                              # routes to a handler, nothing else
├── handlers/estimated-delivery/
│   ├── handler.js                        # orchestrates the two upstream calls
│   ├── responses.js                      # builds the success and error JSON payloads
│   └── constants.js                      # route path and query-param defaults
├── lib/
│   ├── api-client.js                     # shared per-API token loading
│   └── cors.js                           # origin allow-list, applied to every response
└── mocks/                                # tutorial stand-ins for the two upstream APIs
    ├── catalog/product-api.js
    └── delivery/delivery-api.js

处理程序和上游调用

handler.js中的处理程序采用skupostcode,调用这两个上游服务,并将其结果合并到一个响应中:

// src/handlers/estimated-delivery/handler.js
async function estimatedDeliveryBySkuAndPostcodeHandler(req) {
  const url = new URL(req.url);
  const sku = (url.searchParams.get("sku") ?? DEFAULT_SKU).trim();
  const postcode = (url.searchParams.get("postcode") ?? "").trim();

  if (!postcode) {
    return missingPostcodeError(sku);
  }

  // API 1: catalog lookup
  const product = await getProductBySku(sku);
  if (!product) {
    return unknownSkuError(sku, postcode);
  }

  // API 2: fulfillment lookup
  const fulfillment = await getEstimatedDeliveryByPostcode(postcode);

  return json(buildSuccessResponse(product, fulfillment));
}

responses.js将错误和成功JSON形状保留在处理程序之外,而constants.js保留路由路径和默认SKU,因此handler.js将焦点放在两次调用序列上。 src/mocks/中的每个模拟在返回数据之前加载其自身的令牌,形状与实际API客户端使用的形状相同:

// src/mocks/catalog/product-api.js
export async function getProductBySku(sku) {
  await getApiToken(SECRETS.CATALOG);

  // Real API: uncomment and replace with your catalog endpoint.
  // const response = await authorizedFetch(
  //   `https://api.example.com/products?sku=${encodeURIComponent(sku)}`,
  //   SECRETS.CATALOG,
  // );
  // return response.ok ? response.json() : null;

  return PRODUCTS[sku] || null;
}

getApiToken()authorizedFetch()lib/api-client.js中生活。 product-api.jsdelivery-api.js都共享此帮助程序,因此每个上游API会保留自己的密钥存储密钥(CATALOG_API_TOKENDELIVERY_API_TOKEN),而不会复制身份验证逻辑。 当您准备好使用实际API替换模拟调用时,请按照使用Edge函数中的配置和密钥在已部署的网站上进行密钥设置,然后取消对上述块的注释。

每个平台限制只调用​32个出站获取呼叫,因此此处有两个呼叫留有充足的空间。

为本地开发启用CORS

Edge Delivery Services块的本地开发服务器在http://localhost:3000上运行。 AEM Edge函数的本地开发服务器在http://127.0.0.1:7676上运行。 由于它们是两个不同的源,因此如果没有CORS标头,浏览器会在您的代码看到请求之前阻止该请求。

根据cors.js中的允许列表检查请求的来源,并对浏览器首先发送的OPTIONS预检请求应用相同的检查:

// src/lib/cors.js
const ALLOWED_ORIGIN_EXACT = new Set([
  "http://localhost:3000",
  "http://127.0.0.1:3000",
]);

function isAllowedOrigin(origin) {
  return Boolean(origin) && ALLOWED_ORIGIN_EXACT.has(origin);
}

function applyCors(request, response) {
  const origin = request.headers.get("Origin");
  if (!isAllowedOrigin(origin)) {
    return response;
  }

  const headers = new Headers(response.headers);
  headers.set("access-control-allow-origin", origin);
  headers.set("vary", "Origin");
  return new Response(response.body, { status: response.status, headers });
}

function corsPreflightResponse(request) {
  const origin = request.headers.get("Origin");
  if (!isAllowedOrigin(origin)) {
    return new Response(null, { status: 403 });
  }

  return new Response(null, {
    status: 204,
    headers: {
      "access-control-allow-origin": origin,
      "access-control-allow-methods": "GET, OPTIONS",
      "access-control-allow-headers": "Content-Type",
      vary: "Origin",
    },
  });
}

export { applyCors, corsPreflightResponse };
// src/index.js
if (url.pathname === "/api/frescopa/estimated-delivery") {
  if (req.method === "OPTIONS") {
    finalResponse = corsPreflightResponse(req);
  } else if (req.method === "GET") {
    finalResponse = await estimatedDeliveryBySkuAndPostcodeHandler(req);
  }
}

finalResponse = applyCors(req, finalResponse);

引用实现使用正则表达式扩展ALLOWED_ORIGIN_EXACT,以允许部署的开发、暂存和生产域,因为对这些域的请求是通过CDN的同源域,并且在技术上不需要CORS标头,但是显式匹配它们会使允许列表自文档化。 部署后,浏览器绝不会为相同源请求发送CORS检查,因此额外的标头无害。 您可以保留CORS帮助程序;它仅在请求源与允许列表匹配时添加标头。

fastly.toml中配置本地开发服务器

fastly.toml配置由aio aem edge-functions serve启动的本地开发服务器。 对于本教程,其[local_server.secret_stores]块为每个上游API分配一个本地令牌,因此处理程序可以在没有实际凭据的情况下调用SecretStoreManager.getSecret()

# fastly.toml
[local_server.secret_stores]
  [[local_server.secret_stores.secret_default]]
    key = "CATALOG_API_TOKEN"
    data = "catalog-tutorial-token"
  [[local_server.secret_stores.secret_default]]
    key = "DELIVERY_API_TOKEN"
    data = "delivery-tutorial-token"

样板还声明了天气样本的Open-Meteo API的[local_server.backends]条目。 由于本教程将模拟两个上游调用而不是调用实际的主机,因此请删除该条目而不是重新指向该条目。

有关[local_server]选项的完整集,请参阅Fastly.toml引用

在本地测试端点

此时,您仅测试API协定:端点的请求和响应形状,这与稍后将调用它的Edge Delivery Services块无关。

在Edge Delivery Services上设置AEM Edge功能中启动本地开发服务器:

$ aio aem edge-functions serve

使用curl调用快乐路径或在浏览器中打开URL:

$ curl "http://127.0.0.1:7676/api/frescopa/estimated-delivery?sku=house-blend-medium-roast&postcode=90210"

确认响应将合并来自上游调用的数据,例如来自目录查找的产品名称和来自同一JSON正文中的履行查找的投放估计。

本地开发服务器响应估计的投放终结点

然后确认错误路径responses.js生成:

# missing postcode
$ curl "http://127.0.0.1:7676/api/frescopa/estimated-delivery?sku=house-blend-medium-roast"

# unknown SKU
$ curl "http://127.0.0.1:7676/api/frescopa/estimated-delivery?sku=not-a-real-product&postcode=90210"

每个应返回包含code字段(MISSING_POSTCODEUNKNOWN_SKU)的JSON错误正文而不是通用500,以确认处理程序在输入到达上游调用之前验证输入。

添加端点

两个配置文件按照生成API终结点中的模式声明路由。

config/edgeFunctions.yaml声明了AEM Edge函数本身,与样板中声明的名称相同:

# config/edgeFunctions.yaml
kind: "EdgeFunctions"
version: "1"
data:
  functions:
    - name: my-edge-function

config/cdn.yaml添加了一个原点选择器规则,该规则将本教程的路径转发到该函数:

# config/cdn.yaml
- name: route-estimated-delivery-to-edge-function
  when: { reqProperty: path, equals: "/api/frescopa/estimated-delivery" }
  action:
    type: selectAemOrigin
    originName: edgefunction-my-edge-function
    skipCache: true

查看引用实现中的完整文件: config/edgeFunctions.yamlconfig/cdn.yaml

后续步骤

开发Edge Delivery Services块中,您对调用此端点的块进行了基架,然后在通用编辑器中创作它。

其他资源

recommendation-more-help
experience-manager-learn-help-cloud-service