
Alicloud Network Esa
- 180 installs
- 396 repo stars
- Updated July 18, 2026
- cinience/alicloud-skills
Configure Alibaba Cloud ESA edge security and acceleration for apps needing DDoS protection, WAF rules, and global traffic routing before production cutover.
About
Guides agents through Alibaba Cloud ESA (Edge Security Acceleration) setup: domain onboarding, origin configuration, WAF and DDoS policies, TLS, and traffic routing so SaaS and API workloads get edge protection and faster global delivery.
- Edge Security Acceleration setup
- DDoS and WAF policy configuration
- DNS and origin routing integration
- Certificate and domain binding guidance
- Pre-production network hardening checklist
Alicloud Network Esa by the numbers
- 180 all-time installs (skills.sh)
- Ranked #475 of 1,039 Cloud & Infrastructure skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/cinience/alicloud-skills --skill alicloud-network-esaAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 180 |
|---|---|
| repo stars | ★ 396 |
| Last updated | July 18, 2026 |
| Repository | cinience/alicloud-skills ↗ |
What it does
Configure Alibaba Cloud ESA edge security and acceleration for apps needing DDoS protection, WAF rules, and global traffic routing before production cutover.
Files
Category: service
Edge Security Acceleration (ESA) - Pages, Edge Routine, KV, Site Management, Analytics & More
Use Alibaba Cloud OpenAPI (RPC) with official Python SDK to manage all ESA capabilities.
Alibaba Cloud ESA provides five core capabilities:
- Pages — Deploy HTML or static directories to edge nodes (quick deployment flow based on Edge Routine)
- Edge Routine (ER) — Full lifecycle management of serverless edge functions
- Edge KV — Distributed edge key-value storage with Namespace/Key/Value management
- Site Management — Site management, DNS records, cache rules, certificates, etc.
- Analytics — Traffic analysis, time-series trends, Top-N rankings, bandwidth statistics, request metrics
Use Python SDK uniformly to call ESA OpenAPI.
Prerequisites
- Prepare AccessKey (RAM user/role with least privilege).
- Install Python SDK:
pip install alibabacloud_esa20240910 alibabacloud_tea_openapi alibabacloud_credentials - ESA OpenAPI is RPC style; prefer SDK or OpenAPI Explorer to avoid manual signing.
SDK quickstart
from alibabacloud_esa20240910.client import Client as Esa20240910Client
from alibabacloud_esa20240910 import models as esa_models
from alibabacloud_tea_openapi import models as open_api_models
def create_client(region_id: str = "cn-hangzhou") -> Esa20240910Client:
config = open_api_models.Config(
region_id=region_id,
endpoint="esa.cn-hangzhou.aliyuncs.com",
)
return Esa20240910Client(config)Pages — Edge Page Deployment
Pages is a quick deployment flow based on Edge Routine, deploying HTML or static directories to the edge.
HTML Page Deployment Flow
CreateRoutine → GetRoutineStagingCodeUploadInfo → Upload code to OSS
→ CommitRoutineStagingCode → PublishRoutineCodeVersion(staging)
→ PublishRoutineCodeVersion(production) → GetRoutine(get access URL)Static Directory Deployment Flow
CreateRoutine → CreateRoutineWithAssetsCodeVersion → Package zip and upload to OSS
→ Poll GetRoutineCodeVersionInfo(wait for available)
→ CreateRoutineCodeDeployment(staging) → CreateRoutineCodeDeployment(production)
→ GetRoutine(get access URL)Zip Package Structure
The zip package structure depends on EDGE_ROUTINE_TYPE (automatically determined by checkEdgeRoutineType based on whether entry file and assets directory exist):
- JS_ONLY:
routine/index.js(bundled with esbuild or--no-bundleto read source files directly) - ASSETS_ONLY: All static files under
assets/, maintaining original directory structure - JS_AND_ASSETS:
routine/index.js+assets/static resources (most common)
The assets/ path is relative to assets.directory in configuration. Configuration priority: CLI args > esa.jsonc / esa.toml.
Key Notes
- Function name rules: lowercase letters/numbers/hyphens, start with lowercase letter, length >= 2
- Same name function: Reuse if exists, deploy new version code
- Deploy to both staging and production by default
- After successful deployment, get
defaultRelatedRecordviaGetRoutineas access domain
Detailed reference: references/pages.md
Edge Routine (ER) — Edge Functions
Manage the complete lifecycle of serverless edge functions via Python SDK.
Core Workflow
CreateRoutine → GetRoutineStagingCodeUploadInfo → Upload code to OSS
→ CommitRoutineStagingCode → PublishRoutineCodeVersion
→ (CreateRoutineRoute) → GetRoutineAPI Summary
- Function Management:
CreateRoutine,DeleteRoutine,GetRoutine,GetRoutineUserInfo,ListUserRoutines - Code Version:
GetRoutineStagingCodeUploadInfo,CommitRoutineStagingCode,PublishRoutineCodeVersion,DeleteRoutineCodeVersion - Routes:
CreateRoutineRoute,UpdateRoutineRoute,DeleteRoutineRoute,GetRoutineRoute,ListRoutineRoutes,ListSiteRoutes - Related Records:
CreateRoutineRelatedRecord,DeleteRoutineRelatedRecord,ListRoutineRelatedRecords
ER Code Format
export default {
async fetch(request) {
return new Response("Hello", {
headers: { "content-type": "text/html;charset=UTF-8" },
});
},
};Detailed reference: references/er.md
Edge KV — Edge Key-Value Storage
Distributed edge key-value storage, readable and writable in Edge Routine, also manageable via OpenAPI/SDK.
Core Concepts
- Namespace: Isolation container for KV data, Key max 512 chars, Value max 2MB (high capacity 25MB)
- Supports TTL expiration:
Expiration(Unix timestamp) orExpirationTtl(seconds)
API Summary
- Namespace:
CreateKvNamespace,DeleteKvNamespace,GetKvNamespace,GetKvAccount,DescribeKvAccountStatus - Single Key Operations:
PutKv,GetKv,GetKvDetail,DeleteKv,PutKvWithHighCapacity - Batch Operations:
BatchPutKv,BatchDeleteKv,BatchPutKvWithHighCapacity,BatchDeleteKvWithHighCapacity,ListKvs
Quick Start
client = create_client()
# Create namespace
client.create_kv_namespace(esa_models.CreateKvNamespaceRequest(namespace="my-ns"))
# Write
client.put_kv(esa_models.PutKvRequest(namespace="my-ns", key="k1", value="v1"))
# Read
resp = client.get_kv(esa_models.GetKvRequest(namespace="my-ns", key="k1"))Detailed reference: references/kv.md
Site Management — Site Management
Use Python SDK to manage ESA sites, DNS records, cache rules, etc.
API behavior notes
- Most list APIs support pagination via
PageNumber+PageSize. ListSitesreturns sites across all regions; no need to iterate regions.- Newly created sites start as
pending; complete access verification viaVerifySiteto activate. - Deleting a site removes all associated configuration.
UpdateSiteAccessTypecan switch between CNAME and NS, but switching to CNAME may fail if incompatible DNS records exist.- DNS record APIs (
CreateRecord,ListRecords, etc.) work for both NS and CNAME connected sites. CNAME sites are limited toCNAMEandA/AAAAtypes only, and records cannot disable acceleration (proxy must stay enabled). - DNS record
Typeparameter must be exact: useA/AAAA(notA),CNAME,MX,TXT,NS,SRV,CAA. CreateCacheRulesupports two config types:global(site-wide default) andrule(conditional rule with match expression).
Workflow
1) Confirm target site ID, access type (CNAME/NS), and desired action. 2) Find API group and exact operation name in references/api_overview.md. 3) Call API with Python SDK (preferred) or OpenAPI Explorer. 4) Verify results with describe/list APIs. 5) If you need repeatable inventory or summaries, use scripts/ and write outputs under output/alicloud-network-esa/.
SDK priority
1) Python SDK (preferred) 2) OpenAPI Explorer 3) Other SDKs (only if Python is not feasible)
Python SDK scripts (recommended for inventory)
- List all ESA sites:
scripts/list_sites.py - Summarize sites by plan:
scripts/summary_sites_by_plan.py - Check site status:
scripts/check_site_status.py - List DNS records for a site:
scripts/list_dns_records.py
Analytics — Traffic Analysis
Query and analyze ESA site traffic data using DescribeSiteTimeSeriesData and DescribeSiteTopData APIs.
Core Features
- Time-Series Data: Query traffic trends with configurable time granularity
- Top-N Rankings: Get rankings by country/IP/host/path/status code dimensions
- Multiple Metrics: Traffic, Requests, RequestTraffic, PageView
- Rich Dimensions: Country, province, ISP, browser, device, host, path, status code, etc.
Two Main APIs
1. DescribeSiteTimeSeriesData - Time-Series Trends
Query traffic trends over time, returning aggregated data points.
Time Granularity Rules:
| Time Range | Interval | Interval Value |
|---|---|---|
| <= 3 hours | 1 minute | 60 |
| 3-12 hours | 5 minutes | 300 |
| 12 hours - 1 day | 15 minutes | 900 |
| 1-10 days | 1 hour | 3600 |
| 10-31 days | 1 day | 86400 |
2. DescribeSiteTopData - Top-N Rankings
Query Top-N ranking data by various dimensions.
Limit Options: 5, 10, 150
Available Metrics (FieldName)
| Field | Type | Description |
|---|---|---|
Traffic | int | Response traffic from ESA to client (bytes) |
Requests | int | Number of requests |
RequestTraffic | int | Client request traffic (bytes) |
PageView | int | Page views |
Available Dimensions
Geographic Dimensions: ClientCountryCode (country), ClientProvinceCode (province), ClientISP (ISP), ClientASN
Client Info: ClientIP, ClientIPVersion, ClientBrowser, ClientDevice, ClientOS
Request Details: ClientRequestHost, ClientRequestMethod, ClientRequestPath, ClientRequestProtocol, ClientRequestQuery, ClientRequestReferer, ClientRequestUserAgent
Response/Cache: EdgeCacheStatus, EdgeResponseStatusCode, EdgeResponseContentType, OriginResponseStatusCode
Others: ALL (aggregated), SiteId (account-level query), Version, ClientSSLProtocol, ClientXRequestedWith
Error Handling
| HTTP Code | Error Code | Description |
|---|---|---|
| 400 | InvalidParameter.TimeRange | Time range exceeded (max 31 days) |
| 400 | InvalidEndTime.Mismatch | EndTime earlier than StartTime |
| 400 | InvalidParameter.Field | Invalid field name |
| 400 | InvalidParameter.Dimension | Invalid dimension |
| 400 | InvalidTime.Malformed | Time format error (use yyyy-MM-ddTHH:mm:ssZ) |
Detailed reference: references/time-series.md, references/top-data.md, references/fields.md
Common operation mapping
Site Management
- Create site:
CreateSite - List sites:
ListSites(supportsSiteName,Status,AccessType,Coveragefilters) - Get site details:
GetSite - Delete site:
DeleteSite - Check site name availability:
CheckSiteName - Verify site ownership:
VerifySite - Update access type:
UpdateSiteAccessType - Update coverage:
UpdateSiteCoverage - Get current nameservers:
GetSiteCurrentNS - Update custom nameservers:
UpdateSiteVanityNS - Pause/resume site:
UpdateSitePause,GetSitePause - Site exclusivity:
UpdateSiteNameExclusive,GetSiteNameExclusive - Version management:
ActivateVersionManagement,DeactivateVersionManagement
Site Configuration
- IPv6:
GetIPv6,UpdateIPv6
DNS Records
NS access: full record type support. CNAME access: only CNAME and A/AAAA, proxy must stay enabled.
- Create record:
CreateRecord - List records:
ListRecords(supportsType,RecordName,Proxiedfilters) - Get record:
GetRecord - Update record:
UpdateRecord - Delete record:
DeleteRecord - Batch create:
BatchCreateRecords - Export records:
ExportRecords
Cache Rules
- Create cache rule:
CreateCacheRule - List cache rules:
ListCacheRules - Get cache rule:
GetCacheRule - Update cache rule:
UpdateCacheRule - Delete cache rule:
DeleteCacheRule
Cache rule expression notes (important):
CreateCacheRuleparameters are flat, not a nested JSONRuleobject.- The
Ruleparameter is a match condition expression string. See Rule Expression Syntax section below. - Quick reminders:
ends_with()/starts_with()must use function-call style;matches(regex) requires standard plan or above. - Set edge cache TTL with
--EdgeCacheMode override_origin --EdgeCacheTtl <seconds>.
Rule Expression Syntax
ESA uses a unified rule engine expression syntax across multiple features (cache rules, WAF custom rules, rate limiting, URL rewrite, header modification, etc.).
When to use
Use this syntax for the Rule parameter in any ESA API that accepts a match condition expression:
CreateCacheRule/UpdateCacheRule- Cache rulesCreateWafRule/UpdateWafRule- WAF custom rulesCreateRatePlanRule- Rate limiting rulesCreateRewriteUrlRule/UpdateRewriteUrlRule- URL rewrite rules- Origin rules, redirect rules, header modification rules, etc.
Expression format
(condition)
(condition1 and condition2)
(condition1) or (condition2)Max nesting depth: 2 levels.
Operator syntax - two styles
Infix style (operator between field and value):
(field eq "value")
(field ne "value")
(field contains "value")
(field in {"value1" "value2"})
(field matches "regex")Function style (operator wraps field):
(starts_with(field, "value"))
(ends_with(field, "value"))
(exists(field))
(len(field) gt 100)
(lower(field) eq "value")Common patterns
# Match file extension
--Rule '(http.request.uri.path.extension eq "html")'
# Match multiple extensions
--Rule '(http.request.uri.path.extension in {"js" "css" "png" "jpg"})'
# Match URL prefix
--Rule '(starts_with(http.request.uri, "/api/"))'
# Match URL suffix
--Rule '(ends_with(http.request.uri, ".html"))'
# Match URL containing substring (value MUST start with /)
--Rule '(http.request.uri contains "/test")'
# Match specific host
--Rule '(http.host eq "www.example.com")'
# Combined conditions
--Rule '(http.request.uri contains "/test" and ip.geoip.country eq "CN")'
# Match by country
--Rule '(ip.geoip.country eq "CN")'
# Exclude path
--Rule '(not starts_with(http.request.uri, "/admin/"))'
# Negating set membership
--Rule '(not http.host in {"a.com" "b.com"})'Key Gotchas
1. ends_with and starts_with must use function-call syntax, NOT infix. 2. matches (regex) requires standard plan or above; basic plan returns RuleRegexQuotaCheckFailed. 3. contains with URI must include path separator: "/test" is correct; "test" alone causes CompileRuleError. 4. List values in in operator are space-separated inside braces: {"a.com" "b.com"}. 5. Outer parentheses are optional for single conditions. 6. Use ne for "not equal", never use not...eq. 7. Use not...in for negating set membership (not before field), not not in.
Plan Limitations
| Plan | eq/ne/in/starts_with/ends_with | contains | matches (regex) |
|---|---|---|---|
| Basic | Supported | Supported | Not supported |
| Standard | Supported | Supported | Supported |
| Enterprise | Supported | Supported | Supported |
AccessKey priority (must follow, align with README)
1) Environment variables: ALICLOUD_ACCESS_KEY_ID / ALICLOUD_ACCESS_KEY_SECRET / ALICLOUD_REGION_ID Region policy: ALICLOUD_REGION_ID is an optional default. If unset, decide the most reasonable region for the task; if unclear, ask the user. 2) Shared config file: ~/.alibabacloud/credentials (region still from env)
Auth setup (README-aligned)
Environment variables:
export ALICLOUD_ACCESS_KEY_ID="your-ak"
export ALICLOUD_ACCESS_KEY_SECRET="your-sk"
export ALICLOUD_REGION_ID="cn-hangzhou"Also supported by the Alibaba Cloud SDKs:
export ALIBABA_CLOUD_ACCESS_KEY_ID="your-ak"
export ALIBABA_CLOUD_ACCESS_KEY_SECRET="your-sk"Shared config file:
~/.alibabacloud/credentials
[default]
type = access_key
access_key_id = your-ak
access_key_secret = your-skAPI discovery
- Product code:
ESA - Default API version:
2024-09-10 - Metadata endpoint:
https://api.aliyun.com/meta/v1/products/ESA/versions/2024-09-10/api-docs.json - Use OpenAPI metadata endpoints to list APIs and get schemas (see references).
Output policy
If you need to save responses or generated artifacts, write them under: output/alicloud-network-esa/
References
Pages, ER & KV
- Pages Deployment Reference:
references/pages.md - Edge Routine Reference:
references/er.md - Edge KV Storage Reference:
references/kv.md
Site Management
- API overview:
references/api_overview.md - Endpoints:
references/endpoints.md - Sites:
references/sites.md - DNS records:
references/dns-records.md - Cache:
references/cache.md - Sources:
references/sources.md - Rule expression - generation guide:
references/rule-generation-guide.md - Rule expression - match fields:
references/rule-match-fields.md - Rule expression - operators:
references/rule-operators.md - Rule expression - examples:
references/rule-examples.md
Analytics
- Time-Series Data API:
references/time-series.md - Top-N Data API:
references/top-data.md - Metrics and Dimensions Reference:
references/fields.md
interface:
display_name: "Alibaba Cloud Network ESA"
short_description: "Edge security acceleration workflows"
default_prompt: "Use $alicloud-network-esa to complete this network/esa task on Alibaba Cloud."
ESA OpenAPI overview (2024-09-10) - Pages, ER, KV, Site Management, DNS & Cache
API index for Pages deployment, Edge Routine, Edge KV, site management, configuration, DNS records, and cache rules. All APIs via Python SDK (alibabacloud_esa20240910).
Pages (Based on Edge Routine)
Underlying ER API calls, see references/pages.md for complete flow.
HTML Deployment Core APIs
- CreateRoutine - Create edge function
- GetRoutineStagingCodeUploadInfo - Get code upload OSS signature
- CommitRoutineStagingCode - Submit code version
- PublishRoutineCodeVersion - Publish to staging/production
- GetRoutine - Get access URL
Static Directory Deployment Core APIs
- CreateRoutine - Create edge function
- CreateRoutineWithAssetsCodeVersion - Create code version with assets
- GetRoutineCodeVersionInfo - Query version build status
- CreateRoutineCodeDeployment - Deploy by percentage to specified environment
- GetRoutine - Get access URL
Edge Routine (ER)
Function Management
- CreateRoutine - Create edge function
- DeleteRoutine - Delete edge function
- GetRoutine - Get edge function details
- GetRoutineUserInfo - Get user edge function info
- ListUserRoutines - Paginate all edge functions
Code Version
- GetRoutineStagingCodeUploadInfo - Get code upload info
- CommitRoutineStagingCode - Submit staging code version
- PublishRoutineCodeVersion - Publish code version to staging/production
- DeleteRoutineCodeVersion - Delete code version
- CreateRoutineWithAssetsCodeVersion - Create code version with assets (for static directory deployment)
- GetRoutineCodeVersionInfo - Get code version status
- CreateRoutineCodeDeployment - Create code deployment (for assets deployment)
- ListRoutineCodeVersions - Paginate code versions
- GetRoutineCodeVersion - Query single code version details
- UpdateRoutineConfigDescription - Update function description
Route Management
- CreateRoutineRoute - Create function route
- DeleteRoutineRoute - Delete function route
- GetRoutineRoute - Get route details
- UpdateRoutineRoute - Update function route
- ListRoutineRoutes - List function routes
- ListSiteRoutes - List site routes
Related Record Management
- CreateRoutineRelatedRecord - Create function related record (domain)
- DeleteRoutineRelatedRecord - Delete function related record
- ListRoutineRelatedRecords - List function related records
Edge KV
Edge key-value storage, supports Namespace and Key-Value management.
Namespace Management
- CreateKvNamespace - Create KV storage space
- DeleteKvNamespace - Delete KV storage space
- GetKvNamespace - Query single namespace info
- GetKvAccount - Query account KV usage info and all namespaces
- DescribeKvAccountStatus - Query if Edge KV is enabled
Single Key Operations
- PutKv - Write key-value pair (≤2MB)
- PutKvWithHighCapacity - Write large capacity key-value pair (≤25MB)
- GetKv - Read key's value
- GetKvDetail - Read key-value and TTL info
- DeleteKv - Delete key-value pair
Batch Operations
- BatchPutKv - Batch write key-value pairs (≤2MB)
- BatchPutKvWithHighCapacity - Batch write large capacity (≤100MB)
- BatchDeleteKv - Batch delete key-value pairs (≤10000)
- BatchDeleteKvWithHighCapacity - Batch delete large capacity (≤100MB)
- ListKvs - List all keys under namespace (supports prefix filter and pagination)
Site Management
- CreateSite - Add site
- ListSites - List sites (supports pagination and filters)
- GetSite - Get site details
- DeleteSite - Delete site
- CheckSiteName - Check site name availability
- VerifySite - Verify site ownership
- UpdateSiteAccessType - Update access type (CNAME/NS)
- UpdateSiteCoverage - Update coverage area
- GetSiteCurrentNS - Get current NS servers
- UpdateSiteVanityNS - Update custom NS
- UpdateSitePause - Pause/resume site proxy
- GetSitePause - Get site proxy status
- UpdateSiteNameExclusive - Set site exclusive
- GetSiteNameExclusive - Get site exclusive status
- ActivateVersionManagement - Enable version management
- DeactivateVersionManagement - Disable version management
Site Configuration
- GetIPv6 - Get IPv6 config
- UpdateIPv6 - Update IPv6 config
DNS Records
NS access: supports all record types. CNAME access: only CNAME and A/AAAA, and proxy (acceleration) cannot be disabled.
- CreateRecord - Create DNS record
- ListRecords - List DNS records (supports Type, RecordName, Proxied filters)
- GetRecord - Get DNS record details
- UpdateRecord - Update DNS record
- DeleteRecord - Delete DNS record
- BatchCreateRecords - Batch create DNS records
- ExportRecords - Export DNS records
Cache Rules
- CreateCacheRule - Create cache rule
- ListCacheRules - List cache rules
- GetCacheRule - Get cache rule details
- UpdateCacheRule - Update cache rule
- DeleteCacheRule - Delete cache rule
References
- Official API list: https://next.api.aliyun.com/document/ESA/2024-09-10/overview
- API metadata: https://api.aliyun.com/meta/v1/products/ESA/versions/2024-09-10/api-docs.json
ESA Cache
Cache rules for ESA sites.
Cache Rules
Cache rules control how content is cached at ESA edge nodes.
Common operations
- Create:
CreateCacheRule - List:
ListCacheRules - Query:
GetCacheRule - Update:
UpdateCacheRule - Delete:
DeleteCacheRule
Rule types
global: Site-wide default cache configurationrule: Conditional rule with match expression
CreateCacheRule parameters
Parameters are flat (not a nested JSON object). Key parameters:
Required:
SiteId: Site ID (Long)Rule: Match condition expression (String). For rule expression syntax, see `rule-generation-guide.md` and related rule reference files in this directory.
Optional:
RuleName: Rule name (not required forglobaltype)RuleEnable:on/off(not required forglobaltype)EdgeCacheMode: Edge cache modefollow_origin: Follow origin cache policy (default)no_cache: Do not cacheoverride_origin: Override origin cache policyfollow_origin_bypass: Follow origin if exists, otherwise no cacheEdgeCacheTtl: Edge cache TTL in seconds (e.g. 864000 = 10 days)BrowserCacheMode: Browser cache mode (no_cache,follow_origin,override_origin)BrowserCacheTtl: Browser cache TTL in secondsQueryStringMode: Query string handling (ignore_all,exclude_query_string,reserve_all,include_query_string)BypassCache: Bypass cache mode (cache_all,bypass_all)ServeStale: Serve stale cache when origin unavailable (on/off)SortQueryStringForCache: Sort query string for cache key (on/off)
Rule expression syntax
See rule expression reference files in this directory for complete documentation:
rule-generation-guide.md- Generation guide from natural languagerule-match-fields.md- All match fields (HTTP, IP/Geo, Map fields)rule-operators.md- All operators (string/numeric comparison, function-style, negation, logical)rule-examples.md- Scenario examples and common mistakes
Quick reminder for cache rules:
- Parameters are flat (not a nested JSON
Ruleobject). ends_with/starts_withuse function-call syntax:(ends_with(http.request.uri, ".html")).matches(regex) requires standard plan or above.
Cache TTL options
- Override origin: set
EdgeCacheModetooverride_origin+EdgeCacheTtlin seconds - Follow origin: set
EdgeCacheModetofollow_origin - Browser cache: set
BrowserCacheMode+BrowserCacheTtl - Common TTL values: 3600 (1h), 86400 (1d), 604800 (7d), 864000 (10d), 2592000 (30d)
Tiered Cache
Multi-level cache architecture configuration.
GetTieredCache: Get current tiered cache configUpdateTieredCache: Update tiered cache config
Cache architecture modes:
edge_regional: Regional edge cachingedge_smart: Smart edge caching
Cache Tag
Cache tag configuration for granular cache control.
GetCacheTag: Get cache tag configUpdateCacheTag: Update cache tag config
References
- CreateCacheRule: https://help.aliyun.com/zh/esa/developer-reference/api-esa-2024-09-10-createcacherule
- ListCacheRules: https://help.aliyun.com/zh/esa/developer-reference/api-esa-2024-09-10-listcacherules
ESA DNS Records
DNS records management for ESA sites.
Access Type & Record Restrictions
- NS access: Full DNS record management via API, supports all record types (A/AAAA, CNAME, MX, TXT, NS, SRV, CAA).
- CNAME access: DNS record APIs are available, but with restrictions:
- Only `CNAME` and `A/AAAA` record types are allowed. Other types (MX, TXT, NS, SRV, CAA) will fail.
- Records cannot disable acceleration (proxy must remain enabled, i.e.
Proxiedmust betrue).
Common operations
- Create:
CreateRecord - List:
ListRecords(supports pagination and filters) - Query:
GetRecord - Update:
UpdateRecord - Delete:
DeleteRecord - Batch create:
BatchCreateRecords - Export:
ExportRecords
Supported record types
| Type | NS access | CNAME access |
|---|---|---|
A/AAAA | Supported | Supported (must enable proxy) |
CNAME | Supported | Supported (must enable proxy) |
MX | Supported | Not supported |
TXT | Supported | Not supported |
NS | Supported | Not supported |
SRV | Supported | Not supported |
CAA | Supported | Not supported |
Note: Record type must be exact: use A/AAAA (not just A).
ListRecords filters
Type: Record type (e.g.,A/AAAA,CNAME)RecordName: Record name (fuzzy match)Proxied: Whether proxied through ESA (true/false)
CreateRecord parameters
Required:
SiteId: Site IDRecordName: Must be the full domain name (e.g.,www.example.com), not just the subdomain prefix (e.g.,www). The suffix must match the site name, otherwise returnsInvalidParameter.InvalidRecordNameSuffix.Type: Record typeData: Record value in JSON format (e.g.,{"Value":"1.2.3.4"}for A/AAAA,{"Value":"target.com"}for CNAME)Ttl: Time to live (seconds). Set to1for system-determined TTL.
Conditionally required:
BizName: Required when `Proxied` is `true` (i.e., acceleration enabled). Valid values:web,api,image_video. Omitting it when proxy is on causesInvalidParameter.InvalidBiz.SourceType: Required for CNAME records. Valid values:OSS,S3,LB,OP,Domain. Defaults toDomainif omitted.
Optional:
Proxied: Enable ESA proxy/acceleration (default: false). CNAME-access sites must set this totrue.Priority: Priority for MX/SRV records.HostPolicy: Origin host policy for CNAME records:follow_hostnameorfollow_origin_domain.Comment: Record comment (max 100 characters).
Key gotchas
1. RecordName must be full domain: qodertest.qoder.weiyigirl.top, not qodertest. 2. BizName is required when Proxied=true: Without it, API returns InvalidParameter.InvalidBiz with misleading message "business type is empty or incorrect". 3. CNAME-access sites must enable proxy: Setting Proxied=false on CNAME-access sites will fail.
Behavioral notes
- Record names must be unique within the same type
- Deleting a record that doesn't exist returns success (idempotent)
BatchCreateRecordscan create multiple records of different types at onceExportRecordsreturns all DNS records in BIND zone file format
References
- CreateRecord: https://help.aliyun.com/zh/esa/developer-reference/api-esa-2024-09-10-createrecord
- ListRecords: https://help.aliyun.com/zh/esa/developer-reference/api-esa-2024-09-10-listrecords
ESA OpenAPI endpoints
ESA uses RPC-style OpenAPI. Prefer SDK or OpenAPI Explorer.
Public endpoint
- Default:
esa.cn-hangzhou.aliyuncs.com - Pattern:
esa.<region-id>.aliyuncs.com
Common endpoints
| Region | Endpoint |
|---|---|
| cn-hangzhou | esa.cn-hangzhou.aliyuncs.com |
| cn-shanghai | esa.cn-shanghai.aliyuncs.com |
| cn-beijing | esa.cn-beijing.aliyuncs.com |
| ap-southeast-1 | esa.ap-southeast-1.aliyuncs.com |
Notes
- API version:
2024-09-10 - ESA is a global service; most operations work from any endpoint.
- Use
cn-hangzhouas default if region is unspecified.
References
- Endpoint list: https://www.alibabacloud.com/help/en/esa/developer-reference/endpoints
Edge Routine (ER) — Edge Function Reference
ESA Edge Routine is a serverless edge function service where code runs on global edge nodes. Supports full lifecycle management: creation, code submission, deployment, route configuration, and record management.
Manage Edge Routine via Python SDK calling ESA OpenAPI.
API List
Function Management
| API | Description | Key Parameters |
|---|---|---|
CreateRoutine | Create edge function | Name(required, lowercase letters/numbers/hyphens, >=2 chars), Description(optional) |
DeleteRoutine | Delete edge function | Name(required) |
GetRoutine | Get edge function details, including code version list, related records, default access domain | Name(required) |
GetRoutineUserInfo | Get user edge function overview info | No parameters |
ListUserRoutines | Paginate all edge functions under account | PageNumber, PageSize |
Code Version Management
| API | Description | Key Parameters |
|---|---|---|
GetRoutineStagingCodeUploadInfo | Get signature info for code upload to OSS | Name(required) |
CommitRoutineStagingCode | Submit staging code, generate formal code version | Name(required), CodeDescription(optional) |
PublishRoutineCodeVersion | Publish code version to staging/production | Name(required), Env(required, "staging"/"production"), CodeVersion(required) |
DeleteRoutineCodeVersion | Delete code version | Name(required), CodeVersion(required) |
CreateRoutineWithAssetsCodeVersion | Create code version with assets (for static file deployment) | Name(required), CodeDescription(optional) |
GetRoutineCodeVersionInfo | Get code version status (init/available/failed) | Name(required), CodeVersion(required) |
CreateRoutineCodeDeployment | Deploy code version to specified environment by percentage (for assets deployment) | Name(required), Env(required), Strategy(required), CodeVersions(required, JSON) |
ListRoutineCodeVersions | Paginate function's code versions | Name(required), PageNumber, PageSize |
GetRoutineCodeVersion | Query single code version details | Name(required), CodeVersion(required) |
Route Management
| API | Description | Key Parameters |
|---|---|---|
CreateRoutineRoute | Create route | SiteId(required), Route(path, e.g. test.example.com/*), RoutineName(required), RouteName(required), RouteEnable("on"/"off"), Bypass("on"/"off") |
UpdateRoutineRoute | Update route configuration | SiteId(required), ConfigId(required), RouteName(required), RouteEnable(required), Rule(required), RoutineName(required), Bypass(required) |
DeleteRoutineRoute | Delete route | SiteId(required), ConfigId(required) |
GetRoutineRoute | Get route details | SiteId(required), ConfigId(required) |
ListRoutineRoutes | List all routes for a function | RoutineName(required), RouteName(optional filter), PageNumber, PageSize |
ListSiteRoutes | List all routes for a site | SiteId(required), RouteName(optional filter), PageNumber, PageSize |
Related Record Management
| API | Description | Key Parameters |
|---|---|---|
CreateRoutineRelatedRecord | Create function related record (domain), triggers function execution | Name(required), SiteId(required), RecordName(required) |
DeleteRoutineRelatedRecord | Delete related record | Name(required), SiteId(required), RecordName(required), RecordId(optional) |
ListRoutineRelatedRecords | List all related records for a function | Name(required), PageNumber, PageSize, SearchKeyWord(optional) |
Standard Workflow
Create and Deploy Edge Function (Complete Flow)
1. CreateRoutine → Create function
2. GetRoutineStagingCodeUploadInfo → Get upload signature
3. Upload code to OSS (POST with signature) → Code upload
4. CommitRoutineStagingCode → Submit code version
5. PublishRoutineCodeVersion(env=staging) → Deploy to staging
6. PublishRoutineCodeVersion(env=production)→ Deploy to production
7. (Optional) CreateRoutineRoute → Bind custom domain route
8. (Optional) CreateRoutineRelatedRecord → Create related record
9. GetRoutine → Get details, obtain default access URLCode Format Requirements
Edge Routine code must export fetch handler:
async function handleRequest(request) {
return new Response("Hello World", {
headers: { "content-type": "text/html;charset=UTF-8" },
});
}
export default {
async fetch(request) {
return handleRequest(request);
},
};Route Pattern Explanation
Route's Rule field uses ESA rule expression, for example:
(http.host eq "test.example.com" and starts_with(http.request.uri.path, "/"))
Simplified path format (e.g. test.example.com/*) needs to be converted to rule expression:
- Domain prefix
*=ends_with(http.host, ".example.com") - Path suffix
*=starts_with(http.request.uri.path, "/")
Python SDK Usage
from alibabacloud_esa20240910.client import Client as Esa20240910Client
from alibabacloud_esa20240910 import models as esa_models
from alibabacloud_tea_openapi import models as open_api_models
import requests
def create_client(region_id: str = "cn-hangzhou") -> Esa20240910Client:
config = open_api_models.Config(
region_id=region_id,
endpoint="esa.cn-hangzhou.aliyuncs.com",
)
return Esa20240910Client(config)
# Create edge function
def create_routine(name: str, description: str = ""):
client = create_client()
request = esa_models.CreateRoutineRequest(name=name, description=description)
return client.create_routine(request)
# List edge functions
def list_routines():
client = create_client()
resp = client.get_routine_user_info()
return resp.body
# Get function details
def get_routine(name: str):
client = create_client()
request = esa_models.GetRoutineRequest(name=name)
return client.get_routine(request)
# Delete edge function
def delete_routine(name: str):
client = create_client()
request = esa_models.DeleteRoutineRequest(name=name)
return client.delete_routine_with_options(request)
# Upload code and deploy (complete flow)
def deploy_code(name: str, code: str, env: str = "production"):
client = create_client()
# 1. Get upload signature
upload_info = client.get_routine_staging_code_upload_info(
esa_models.GetRoutineStagingCodeUploadInfoRequest(name=name)
)
oss_config = upload_info.body.oss_post_config
# 2. Upload code to OSS
form_data = {
"OSSAccessKeyId": oss_config.ossaccess_key_id,
"Signature": oss_config.signature,
"callback": oss_config.callback,
"x:codeDescription": oss_config.x_code_description,
"policy": oss_config.policy,
"key": oss_config.key,
}
requests.post(oss_config.url, data=form_data, files={"file": code.encode()})
# 3. Submit code version
commit_resp = client.commit_routine_staging_code(
esa_models.CommitRoutineStagingCodeRequest(name=name)
)
code_version = commit_resp.body.code_version
# 4. Deploy
client.publish_routine_code_version(
esa_models.PublishRoutineCodeVersionRequest(
name=name, env=env, code_version=code_version
)
)
return code_versionRoute Management
# Create route
def create_route(site_id: int, routine_name: str, route_name: str, rule: str):
client = create_client()
request = esa_models.CreateRoutineRouteRequest(
site_id=site_id,
routine_name=routine_name,
route_name=route_name,
rule=rule,
route_enable="on",
bypass="off",
)
return client.create_routine_route(request)
# List function routes
def list_routine_routes(routine_name: str):
client = create_client()
request = esa_models.ListRoutineRoutesRequest(routine_name=routine_name)
return client.list_routine_routes(request)Related Record Management
# Create related record
def create_related_record(name: str, site_id: int, record_name: str):
client = create_client()
request = esa_models.CreateRoutineRelatedRecordRequest(
name=name, site_id=site_id, record_name=record_name
)
return client.create_routine_related_record(request)This document introduces the field names and their meanings returned when calling ESA data analysis APIs. These fields are data analysis results obtained through API calls, used to support deeper data analysis and business insights. Through these fields, you can get detailed traffic, requests, cache status, and other information to comprehensively understand business performance and system operation status.
Metrics
Data analysis provides you with rich data metric values, including response traffic, request count, request traffic, page views, etc. These metrics can comprehensively show your business performance and help you deeply understand business activity, traffic distribution, user behavior patterns, and system performance. Through these detailed data analyses, you can more accurately evaluate business operation status, timely discover potential issues, and formulate corresponding optimization strategies to improve user experience and business efficiency.
| Field Name | Data Type | Description |
|---|---|---|
| Traffic | int | Size of ESA node response returned to client, unit: Byte |
| Requests | int | Number of requests |
| RequestTraffic | int | Size of client request, unit: Byte |
| PageView | int | Page views |
Dimensions
Data analysis provides multiple dimensions for data metrics, helping you analyze business performance from different angles. Helps you comprehensively understand traffic geographic distribution, user behavior patterns, request details, cache status, and system performance. Multi-dimensional data analysis not only helps optimize business processes and improve user experience, but also helps you quickly locate issues and formulate targeted solutions to better manage business operations.
| Field Name | Description |
|---|---|
| ALL | User dimension full data |
| ClientASN | Autonomous System Number (ASN) information parsed from client IP address |
| ClientBrowser | Client browser type |
| ClientCountryCode | ISO-3166 Alpha-2 Code parsed from client IP address |
| ClientDevice | Client device type |
| ClientIP | Client IP that established connection with ESA node |
| ClientIPVersion | Client IP version that established connection with ESA node |
| ClientISP | ISP information parsed from client IP address |
| ClientOS | Client system model |
| ClientProvinceCode | China mainland province information parsed from client IP address |
| ClientRequestHost | Client request Host information |
| ClientRequestMethod | Client request HTTP Method information |
| ClientRequestPath | Client request path information |
| ClientRequestProtocol | Client request protocol information |
| ClientRequestQuery | Client request Query information |
| ClientRequestReferer | Client request Referer information |
| ClientRequestUserAgent | Client request User-Agent information |
| ClientSSLProtocol | Client SSL protocol version, - indicates no SSL used |
| ClientXRequestedWith | Client X-Requested-With request header |
| EdgeCacheStatus | Cache status of client request |
| EdgeResponseContentType | Content-Type information of ESA node response |
| EdgeResponseStatusCode | Status code returned by ESA node response to client |
| OriginResponseStatusCode | Origin response status code |
| SiteId | Current site ID |
| Version | Version management version number |
Edge KV — Edge Key-Value Storage Reference
ESA Edge KV is a distributed edge key-value storage service, readable and writable in Edge Routine, also manageable via OpenAPI. Suitable for edge configuration distribution, feature flags, A/B testing, and other scenarios.
Core Concepts
- Namespace (Storage Space): Isolation container for KV data, each account can create multiple namespaces
- Key: Key name, max 512 characters, cannot contain spaces or backslashes
- Value: Value, standard API max 2MB, high capacity API max 25MB
- TTL: Optional expiration time, supports absolute timestamp (Expiration) or relative seconds (ExpirationTtl)
Limits
| Limit Item | Value |
|---|---|
| Max Key length | 512 characters |
| Single Value max (PutKv/BatchPutKv) | 2 MB |
| Single Value max (PutKvWithHighCapacity) | 25 MB |
| Batch request body max (BatchPutKvWithHighCapacity/BatchDeleteKvWithHighCapacity) | 100 MB |
| BatchDeleteKv max keys per request | 10,000 |
| Single Namespace max capacity | 1 GB |
| ListKvs pagination limit | PageNumber × PageSize ≤ 50,000 |
API List
Namespace Management
| API | Description | Key Parameters |
|---|---|---|
CreateKvNamespace | Create KV storage space | Namespace(required, string), Description(optional) |
DeleteKvNamespace | Delete KV storage space | Namespace(required, string) |
GetKvNamespace | Query single namespace info | Namespace(required, string) |
GetKvAccount | Query account KV usage info and all namespaces | No parameters |
DescribeKvAccountStatus | Query if Edge KV is enabled | No parameters |
Single Key Operations
| API | Description | Key Parameters |
|---|---|---|
PutKv | Write key-value pair (≤2MB) | Namespace(required), Key(required), Value(body, required), Expiration(optional, Unix timestamp), ExpirationTtl(optional, seconds), Base64(optional, bool) |
PutKvWithHighCapacity | Write large capacity key-value pair (≤25MB) | Same as PutKv, but via SDK body method |
GetKv | Read key's value | Namespace(required), Key(required), Base64(optional, bool) |
GetKvDetail | Read key-value and TTL | Namespace(required), Key(required) |
DeleteKv | Delete key-value pair | Namespace(required), Key(required) |
Batch Operations
| API | Description | Key Parameters |
|---|---|---|
BatchPutKv | Batch write key-value pairs (≤2MB) | Namespace(required), body is JSON array [{Key, Value, Expiration?, ExpirationTtl?}] |
BatchPutKvWithHighCapacity | Batch write large capacity (≤100MB) | Same as above, via SDK body method |
BatchDeleteKv | Batch delete key-value pairs (≤10000) | Namespace(required), body is JSON array ["key1", "key2", ...] |
BatchDeleteKvWithHighCapacity | Batch delete large capacity (≤100MB) | Same as above, via SDK body method |
ListKvs | List all keys in namespace | Namespace(required), Prefix(optional), PageNumber(optional), PageSize(optional, default 20, max 100) |
Python SDK Usage
Installation
pip install alibabacloud_esa20240910 alibabacloud_tea_openapi alibabacloud_credentialsNamespace Management
from alibabacloud_esa20240910.client import Client as Esa20240910Client
from alibabacloud_esa20240910 import models as esa_models
from alibabacloud_tea_openapi import models as open_api_models
def create_client(region_id: str = "cn-hangzhou") -> Esa20240910Client:
config = open_api_models.Config(
region_id=region_id,
endpoint="esa.cn-hangzhou.aliyuncs.com",
)
return Esa20240910Client(config)
# Create namespace
def create_namespace(name: str, description: str = ""):
client = create_client()
request = esa_models.CreateKvNamespaceRequest(
namespace=name,
description=description,
)
return client.create_kv_namespace(request)
# List all namespaces (via GetKvAccount)
def list_namespaces():
client = create_client()
request = esa_models.GetKvAccountRequest()
resp = client.get_kv_account(request)
return resp.body
# Delete namespace
def delete_namespace(name: str):
client = create_client()
request = esa_models.DeleteKvNamespaceRequest(namespace=name)
return client.delete_kv_namespace(request)Key-Value Operations
# Write key-value pair
def put_kv(namespace: str, key: str, value: str, ttl: int = None):
client = create_client()
request = esa_models.PutKvRequest(
namespace=namespace,
key=key,
value=value,
)
if ttl:
request.expiration_ttl = ttl
return client.put_kv(request)
# Read key's value
def get_kv(namespace: str, key: str):
client = create_client()
request = esa_models.GetKvRequest(
namespace=namespace,
key=key,
)
return client.get_kv(request)
# Delete key-value pair
def delete_kv(namespace: str, key: str):
client = create_client()
request = esa_models.DeleteKvRequest(
namespace=namespace,
key=key,
)
return client.delete_kv(request)
# List keys
def list_kvs(namespace: str, prefix: str = None):
client = create_client()
request = esa_models.ListKvsRequest(
namespace=namespace,
prefix=prefix,
)
return client.list_kvs(request)Batch Operations
import json
# Batch write
def batch_put_kv(namespace: str, items: list):
"""items: [{"Key": "k1", "Value": "v1", "ExpirationTtl": 3600}, ...]"""
client = create_client()
request = esa_models.BatchPutKvRequest(
namespace=namespace,
)
# body is JSON string
request.body = json.dumps(items).encode("utf-8")
return client.batch_put_kv(request)
# Batch delete
def batch_delete_kv(namespace: str, keys: list):
"""keys: ["key1", "key2", ...]"""
client = create_client()
request = esa_models.BatchDeleteKvRequest(
namespace=namespace,
)
request.body = json.dumps(keys).encode("utf-8")
return client.batch_delete_kv(request)Using KV in Edge Routine
In Edge Routine code, you need to create an instance via new EdgeKV({namespace: "..."}) to access KV storage (no global instance, must be explicitly created each time):
export default {
async fetch(request) {
const kv = new EdgeKV({ namespace: "my-namespace" });
// Write
await kv.put("key1", "value1");
// Read
const value = await kv.get("key1");
// Delete
await kv.delete("key1");
return new Response(value || "not found");
},
};Common Workflows
1. Initialize KV Storage
DescribeKvAccountStatus → (Enable if not enabled)
CreateKvNamespace → PutKv / BatchPutKv → ListKvs verify2. Configuration Distribution (Edge Config Hot Update)
1. Write config via OpenAPI: PutKv(namespace="config", key="feature-flags", value=json)
2. Edge Routine reads config: new EdgeKV({namespace: "config"}).get("feature-flags")
3. Update config by calling PutKv again, edge nodes sync automatically3. Data Cleanup
ListKvs(prefix="temp-") → Filter keys to delete → BatchDeleteKvCommon Error Codes
| HTTP | Error Code | Description |
|---|---|---|
| 400 | InvalidNameSpace.Malformed | Invalid namespace name (e.g. empty string) |
| 400 | InvalidKey.Malformed | Invalid key name (e.g. empty string) |
| 400 | InvalidKey.ExceedsMaximum | Key length exceeds 512 bytes |
| 400 | InvalidValue.ExceedsMaximum | Value exceeds 2MB (or 25MB) |
| 404 | InvalidNameSpace.NotFound | Namespace does not exist |
| 404 | InvalidKey.NotFound | Key does not exist |
| 406 | InvalidNameSpace.Duplicate | Namespace already exists |
| 406 | InvalidNameSpace.QuotaFull | Namespace quota exceeded |
| 403 | InvalidKey.ExceedsCapacity | Namespace capacity full |
| 429 | TooQuickRequests | Modify/delete operations too frequent |
ESA Pages — Edge Page Deployment Reference
ESA Pages provides the ability to quickly deploy HTML pages or static file directories to edge nodes. Built on Edge Routine, deployments are completed via Python SDK calling ESA OpenAPI.
Deploy HTML Pages
Flow
1. CreateRoutine(name) → Create routine (skip if exists)
2. GetRoutineStagingCodeUploadInfo(name) → Get OSS upload signature
3. POST code to OSS → Upload code file
4. CommitRoutineStagingCode(name) → Commit code version
5. PublishRoutineCodeVersion(staging) → Deploy to staging
6. PublishRoutineCodeVersion(production) → Deploy to production
7. GetRoutine(name) → Get defaultRelatedRecord as access URLCode Template
HTML content needs to be wrapped as Edge Routine code:
const html = `<html><body>Hello World</body></html>`;
async function handleRequest(request) {
return new Response(html, {
headers: { "content-type": "text/html;charset=UTF-8" },
});
}
export default {
async fetch(request) {
return handleRequest(request);
},
};Python SDK Example
from alibabacloud_esa20240910.client import Client as Esa20240910Client
from alibabacloud_esa20240910 import models as esa_models
from alibabacloud_tea_openapi import models as open_api_models
import requests
def create_client() -> Esa20240910Client:
config = open_api_models.Config(
region_id="cn-hangzhou",
endpoint="esa.cn-hangzhou.aliyuncs.com",
)
return Esa20240910Client(config)
def deploy_html(name: str, html: str):
"""Deploy HTML page to ESA Pages"""
client = create_client()
# Escape special characters in template string
escaped_html = html.replace("`", "\\`").replace("$", "\\$")
code = f'''const html = `{escaped_html}`;
async function handleRequest(request) {{
return new Response(html, {{
headers: {{ "content-type": "text/html;charset=UTF-8" }},
}});
}}
export default {{
async fetch(request) {{
return handleRequest(request);
}},
}};'''
# 1. Create routine (skip if exists)
try:
client.create_routine(esa_models.CreateRoutineRequest(name=name))
except Exception as e:
if "RoutineNameAlreadyExist" not in str(e):
raise
# 2. Get upload signature
upload_info = client.get_routine_staging_code_upload_info(
esa_models.GetRoutineStagingCodeUploadInfoRequest(name=name)
)
oss = upload_info.body.oss_post_config
# 3. Upload code to OSS
form_data = {
"OSSAccessKeyId": oss.ossaccess_key_id,
"Signature": oss.signature,
"callback": oss.callback,
"x:codeDescription": oss.x_code_description,
"policy": oss.policy,
"key": oss.key,
}
requests.post(oss.url, data=form_data, files={"file": code.encode()})
# 4. Commit code version
commit = client.commit_routine_staging_code(
esa_models.CommitRoutineStagingCodeRequest(name=name)
)
version = commit.body.code_version
# 5-6. Deploy to staging and production
for env in ["staging", "production"]:
client.publish_routine_code_version(
esa_models.PublishRoutineCodeVersionRequest(
name=name, env=env, code_version=version
)
)
# 7. Get access URL
routine = client.get_routine(esa_models.GetRoutineRequest(name=name))
domain = routine.body.default_related_record
return f"https://{domain}" if domain else NoneDeploy Static File Directory
Flow
1. CreateRoutine(name) → Create routine (skip if exists)
2. CreateRoutineWithAssetsCodeVersion(name) → Create assets code version, get OSS signature
3. Package directory as zip → POST zip to OSS → Upload assets
4. Poll GetRoutineCodeVersionInfo(name, version) → Wait for available status
5. CreateRoutineCodeDeployment(staging, 100%) → Deploy to staging
6. CreateRoutineCodeDeployment(production, 100%) → Deploy to production
7. GetRoutine(name) → Get access URLZip Package Structure
The zip package structure created during deployment depends on the project's EDGE_ROUTINE_TYPE, with three cases:
1. JS_ONLY (entry file only)
your-project.zip
└── routine/
└── index.js ← Code bundled by esbuild (or source file directly when --no-bundle)2. ASSETS_ONLY (static resources only)
your-project.zip
└── assets/
├── image.png
├── style.css
└── subdir/
└── data.json ← All files under assets directory, maintaining original structure3. JS_AND_ASSETS (entry file + static resources, most common)
your-project.zip
├── routine/
│ └── index.js ← Dynamic code (bundled JS)
└── assets/
├── image.png
└── ... ← Static resources, maintaining original structureKey Details
index.jscontent source: By default, produced by prodBuild (esbuild) bundling the entry file; if--no-bundleis passed, reads source file directly- Paths under
assets/are relative toassets.directoryin configuration, recursively traversing all subdirectories and files - Zip package is converted to Buffer via
zip.toBuffer()and uploaded to OSS (first get OSS temporary credentials via API, then POST upload), with max 3 retries - Project type determination logic is in
checkEdgeRoutineType, based on whether entry file and assets directory actually exist - Configuration source priority: CLI args >
esa.jsonc/esa.tomlconfig file
Python SDK Example
import os
import zipfile
import io
import time
import json
import requests
def deploy_folder(name: str, folder_path: str, description: str = ""):
"""Deploy static directory to ESA Pages"""
client = create_client()
# 1. Create routine
try:
client.create_routine(
esa_models.CreateRoutineRequest(name=name, description=description)
)
except Exception as e:
if "RoutineNameAlreadyExist" not in str(e):
raise
# 2. Create assets code version
# Note: This API needs to be called via callApi method
from alibabacloud_tea_openapi import models as api_models
params = api_models.Params(
action="CreateRoutineWithAssetsCodeVersion",
version="2024-09-10", protocol="https", method="POST",
auth_type="AK", body_type="json", req_body_type="json",
style="RPC", pathname="/",
)
body = {"Name": name, "CodeDescription": description}
request = api_models.OpenApiRequest(body=body)
runtime = {}
result = client._client.call_api(params, request, runtime)
oss_config = result.get("body", {}).get("OssPostConfig", {})
code_version = result.get("body", {}).get("CodeVersion")
# 3. Package and upload zip
buf = io.BytesIO()
with zipfile.ZipFile(buf, "w", zipfile.ZIP_DEFLATED) as zf:
for root, dirs, files in os.walk(folder_path):
for f in files:
full = os.path.join(root, f)
rel = os.path.relpath(full, folder_path).replace(os.sep, "/")
zf.write(full, f"assets/{rel}")
buf.seek(0)
form_data = {
"OSSAccessKeyId": oss_config["OSSAccessKeyId"],
"Signature": oss_config["Signature"],
"policy": oss_config["Policy"],
"key": oss_config["Key"],
}
if oss_config.get("XOssSecurityToken"):
form_data["x-oss-security-token"] = oss_config["XOssSecurityToken"]
requests.post(oss_config["Url"], data=form_data, files={"file": buf.getvalue()})
# 4. Wait for version ready
for _ in range(300):
info = client._client.call_api(
api_models.Params(
action="GetRoutineCodeVersionInfo", version="2024-09-10",
protocol="https", method="GET", auth_type="AK",
body_type="json", req_body_type="json", style="RPC", pathname="/",
),
api_models.OpenApiRequest(query={"Name": name, "CodeVersion": code_version}),
{},
)
status = info.get("body", {}).get("Status", "").lower()
if status == "available":
break
if status not in ("", "init"):
raise RuntimeError(f"Build failed: {status}")
time.sleep(1)
# 5-6. Deploy
for env in ["staging", "production"]:
client._client.call_api(
api_models.Params(
action="CreateRoutineCodeDeployment", version="2024-09-10",
protocol="https", method="POST", auth_type="AK",
body_type="json", req_body_type="json", style="RPC", pathname="/",
),
api_models.OpenApiRequest(query={
"Name": name, "Env": env, "Strategy": "percentage",
"CodeVersions": json.dumps([{"Percentage": 100, "CodeVersion": code_version}]),
}),
{},
)
# 7. Get access URL
routine = client.get_routine(esa_models.GetRoutineRequest(name=name))
domain = routine.body.default_related_record
return f"https://{domain}" if domain else NoneCommon Use Cases
1. Deploy Single HTML Page
Suitable for quick prototypes, games, demo pages:
url = deploy_html("game-2048", "<html><body>...</body></html>")
print(f"Access URL: {url}")2. Deploy Frontend Build Output
Suitable for React/Vue/Angular frontend projects' dist/build directories:
url = deploy_folder("my-app", "/path/to/dist")
print(f"Access URL: {url}")Notes
1. Function name rules: Only lowercase letters, numbers, hyphens; must start with lowercase letter; length >= 2 2. Same name function: If function exists, reuses existing function and deploys new version code 3. Deployment environments: Deploys to both staging and production by default 4. Access URL: After successful deployment, get default access domain via defaultRelatedRecord from GetRoutine 5. Static directory deployment: Directory cannot be empty; files in zip are placed under assets/ prefix 6. HTML escaping: When wrapping as ER code, escape backticks and $ symbols 7. Assets deployment: CreateRoutineWithAssetsCodeVersion and CreateRoutineCodeDeployment need to be called via callApi method (not directly wrapped by SDK)
ESA Rule Expression - Scenario Examples
Practical examples for common ESA rule expression scenarios.
Static Resource Caching
# Cache all images (single extension)
--Rule '(http.request.uri.path.extension eq "jpg")'
# Cache multiple static resource types
--Rule '(http.request.uri.path.extension in {"js" "css" "png" "jpg" "gif" "ico" "woff2" "svg" "webp"})'
# Cache specific file
--Rule '(http.request.uri.path.full_file_name eq "index.html")'
# Cache all HTML pages
--Rule '(ends_with(http.request.uri, ".html"))'URL Path Matching
# Match URL prefix (API path)
--Rule '(starts_with(http.request.uri, "/api/"))'
# Match URL suffix
--Rule '(ends_with(http.request.uri, ".json"))'
# Match URL containing substring (MUST start with /)
--Rule '(http.request.uri contains "/test")'
--Rule '(http.request.uri.path contains "/dynamic")'
# Exclude admin path from caching
--Rule '(not starts_with(http.request.uri, "/admin/"))'
# Exclude multiple paths
--Rule '(not starts_with(http.request.uri, "/admin/") and not starts_with(http.request.uri, "/internal/"))'Domain / Host Matching
# Match specific hostname
--Rule '(http.host eq "static.example.com")'
# Match multiple domains
--Rule '(http.host in {"cdn1.example.com" "cdn2.example.com" "cdn3.example.com"})'
# Match domain suffix (e.g. all .cn domains) - requires standard plan
--Rule '(ends_with(http.host, ".cn"))'
# Match domain prefix
--Rule '(starts_with(http.host, "api."))'
# Exclude specific domain
--Rule '(http.host ne "internal.example.com")'Geo / IP Matching
# Match by country
--Rule '(ip.geoip.country eq "CN")'
# Match multiple countries
--Rule '(ip.geoip.country in {"CN" "JP" "KR"})'
# Match by continent (Asia)
--Rule '(ip.geoip.continent eq "AS")'
# Match by AS number
--Rule '(ip.geoip.asnum eq 45104)'
# Match IPv6 traffic
--Rule '(ip.src.version eq "IPv6")'Request Method Matching
# Match GET requests only
--Rule '(http.request.method eq "GET")'
# Match POST requests
--Rule '(http.request.method eq "POST")'
# Match non-GET requests
--Rule '(http.request.method ne "GET")'Header / Cookie / Query Matching
# Match if authorization header exists
--Rule '(exists(http.request.headers["authorization"]))'
# Match if specific cookie exists
--Rule '(exists(http.request.cookies["session"]))'
# Match if query parameter exists
--Rule '(exists(http.request.uri.args["nocache"]))'
# Match by cookie length
--Rule '(len(http.request.cookies["session"]) gt 1024)'
# Match if no authorization header (unauthenticated)
--Rule '(not exists(http.request.headers["authorization"]))'Combined Conditions
# Specific domain + static resources
--Rule '(http.host eq "www.example.com" and http.request.uri.path.extension in {"js" "css" "png"})'
# Specific domain + path prefix
--Rule '(http.host eq "www.example.com" and starts_with(http.request.uri, "/static/"))'
# Static resources but exclude admin
--Rule '(http.request.uri.path.extension in {"js" "css" "png" "jpg"} and not starts_with(http.request.uri, "/admin/"))'
# GET requests from China to HTML pages
--Rule '(http.request.method eq "GET" and ip.geoip.country eq "CN" and ends_with(http.request.uri, ".html"))'
# HTTPS only + specific domain
--Rule '(ssl eq true and http.host eq "secure.example.com")'OR Conditions (Multiple Rule Groups)
# Match either domain
--Rule '(http.host eq "a.example.com") or (http.host eq "b.example.com")'
# Match API path OR static resources
--Rule '(starts_with(http.request.uri, "/api/")) or (http.request.uri.path.extension in {"js" "css" "png"})'
# Match China OR Japan traffic
--Rule '(ip.geoip.country eq "CN") or (ip.geoip.country eq "JP")'Case-Insensitive Matching
# Case-insensitive URL match (use lower() wrapper)
--Rule '(lower(http.request.uri) eq "/readme.html")'
# Case-insensitive host match
--Rule '(lower(http.host) eq "www.example.com")'Regex Examples (Standard Plan and Above)
# Match versioned API paths
--Rule '(http.request.uri matches "^/api/v[0-9]+")'
# Match image files by extension
--Rule '(http.request.uri matches "\\.(jpg|png|gif|webp)$")'
# Match subdomains pattern
--Rule '(http.host matches "^(www|blog|docs)\\.example\\.com$")'ESA Rule Expression - Generation Guide
This guide provides detailed rules for generating correct ESA rule expressions from natural language requirements.
Operator Usage Rules (CRITICAL)
Forbidden Operator Formats
These formats will cause errors - NEVER USE THEM:
| Forbidden Format | Correct Alternative |
|---|---|
not...eq | Use ne instead |
not...ne | Use eq instead |
not in | Use not...in (with space) |
not contains | Use not...contains (with space) |
not matches | Use not...matches (with space) |
Operator Selection by User Expression
| User Expression | Operator | Example |
|---|---|---|
| "equals/is/为" | eq | http.host eq "example.com" |
| "not equals/is not/不等于" | ne | http.host ne "test.com" |
| "contains the following/包含以下各项" | in + set | http.host in {"a.com" "b.com"} |
| "does not contain the following/不包含以下各项" | not...in + set | not http.host in {"a.com" "b.com"} |
| "contains/包含" (substring) | contains | http.request.uri contains "/test" |
| "does not contain/不包含" (substring) | not...contains | not http.request.uri contains "/test" |
| "starts with/以...开头" | starts_with | starts_with(http.request.uri, "/api") |
| "does not start with/不以...开头" | not starts_with | not starts_with(http.request.uri, "/api") |
| "ends with/以...结尾" | ends_with | ends_with(http.request.uri, ".html") |
| "does not end with/不以...结尾" | not ends_with | not ends_with(http.request.uri, ".html") |
Rule Type Recognition
Priority 1: Explicit Context
If user mentions rule type explicitly ("cache rule", "HTTPS rule", "version management"), use that type.
Priority 2: Keywords Inference
| Keywords | Rule Type |
|---|---|
| cache, 缓存, expire, ttl | Cache Rules |
| compress, 压缩, gzip, brotli | Compression Rules |
| HTTPS, SSL/TLS, certificate | HTTPS Rules |
| redirect, 重定向, 301, 302 | Redirect Rules |
| request header, 请求头 | Request Header Modification |
| response header, 响应头 | Response Header Modification |
| rewrite, 重写, url rewrite | URL Rewrite Rules |
| load balance, 负载均衡 | Load Balancing Rules |
| version, 版本, A/B test, 灰度 | Version Management |
| origin, 回源 | Origin Rules |
Priority 3: Default
If unable to determine, default to Node Rules (most versatile).
Rule Type Field Restrictions
| Rule Type | Allowed Fields |
|---|---|
| Node Rules | All common fields |
| Load Balancing | Node fields + ip.src.region_code + http.request.timestamp.sec |
| HTTPS Rules | Only http.host (only eq/ne supported) |
| Version Management | Node fields, but no in_list/not in_list; matches quota-limited |
URI Field Selection Rule
Choose the correct URI field based on match value format:
| Match Value Format | Use Field |
|---|---|
Starts with http:// or https:// (full URL) | http.request.full_uri |
Starts with / (path only) | http.request.uri |
| Path without protocol | http.request.uri |
Examples:
# Full URL matching
http.request.full_uri eq "http://example.com/api/v1/users"
# Path only matching
http.request.uri eq "/api/v1/users"
http.request.uri contains "/test"IMPORTANT: When using contains with http.request.uri, the value MUST start with `/`:
- CORRECT:
http.request.uri contains "/test" - WRONG:
http.request.uri contains "test"(will causeCompileRuleError)
Value Types and Literals
| Type | Format | Example |
|---|---|---|
| String | Double quotes, escape \\ \" | "example.com" |
| Integer | No quotes | 30, 45104 |
| Boolean | Direct use | ssl, not ssl |
| IP | Single or CIDR | 192.168.1.1, 192.168.0.0/24 |
| Set | Space-separated in braces | {"a.com" "b.com" "c.com"} |
| Object | Subscript access | http.request.headers["x-header"] |
Special Values
- Match all requests: Return
true(boolean, not string) - HTTP version values:
HTTP/1.0,HTTP/1.1,HTTP/2.0,HTTP/3.0 - `ip.geoip.asnum`: Integer, no quotes
Logical Operators
- Priority:
not>and>or - Use parentheses to eliminate ambiguity
- Max nesting: 2 levels
Combination examples:
# AND combination
(http.host eq "example.com" and starts_with(http.request.uri, "/api"))
# OR combination
(http.host eq "a.com") or (http.host eq "b.com")
# Complex combination
(http.request.uri contains "/test" and ip.geoip.country eq "CN")
# Negation with set
(http.host contains "example.com") and (not http.host in {"sub1.example.com" "sub2.example.com"})Error Examples and Corrections
Error 1: Using not...eq
# Input: Requests that are NOT m3u8 files
# WRONG: (not http.request.uri.path.extension eq "m3u8")
# CORRECT: http.request.uri.path.extension ne "m3u8"Error 2: Using not in (wrong syntax)
# Input: Exclude jpg and png files
# WRONG: http.request.uri.path.extension not in {"jpg" "png"}
# CORRECT: not http.request.uri.path.extension in {"jpg" "png"}Error 3: Using not contains (wrong syntax)
# Input: User-Agent not containing "bot"
# WRONG: http.user_agent not contains "bot"
# CORRECT: not http.user_agent contains "bot"Generation Flow
1. Preprocess input: Extract match conditions, ignore configuration operations 2. Check "match all": If "all requests", return true 3. Identify rule type: Determine type by keywords and context 4. Filter allowed fields: Based on rule type 5. Select field and operator: Based on user expression and field support 6. Apply URI field rule: Choose http.request.uri or http.request.full_uri 7. Type matching: String with quotes, integer without quotes 8. Combine expression: Use parentheses, prefer function-style for prefix/suffix 9. Error check: Field range, operator restrictions, type constraints
Final Checklist
Before output, verify:
1. ✅ Correct operator format (no not...eq, not in, not contains) 2. ✅ Correct field names and syntax 3. ✅ Correct value types (strings quoted, integers unquoted) 4. ✅ Correct set syntax {value1 value2} 5. ✅ Correct parentheses grouping and logical operators 6. ✅ Correct URI field selection based on value format
ESA Rule Expression - Match Fields
Complete list of match fields available in ESA rule expressions.
Standard Fields (HTTP Request)
| Field | Type | Supported Operators | Example |
|---|---|---|---|
http.host | String | in, eq, ne, contains, matches, starts_with, ends_with, in_list | http.host in {"example.com"} |
http.cookie | String | in, eq, ne, contains, matches | http.cookie contains "PHPSESSID" |
http.referer | String | in, eq, ne, contains, matches | http.referer contains "google.com" |
http.request.method | String | in, eq, ne | http.request.method in {"GET" "POST"} |
http.request.uri | String | in, eq, ne, contains, matches, starts_with, ends_with | http.request.uri contains "/test" |
http.request.uri.path | String | in, eq, ne, contains, matches, starts_with, ends_with | starts_with(http.request.uri.path, "/api") |
http.request.uri.path.extension | String | in, eq, ne | http.request.uri.path.extension in {"jpg" "png"} |
http.request.uri.path.file_name | String | in, eq, ne | http.request.uri.path.file_name eq "index" |
http.request.uri.path.full_file_name | String | in, eq, ne | http.request.uri.path.full_file_name eq "index.html" |
http.request.uri.query | String | in, eq, ne, contains, matches, starts_with, ends_with | http.request.uri.query contains "utm_source" |
http.request.full_uri | String | in, eq, ne, contains, matches, starts_with, ends_with | http.request.full_uri eq "http://example.com/api" |
http.request.version | String | in, eq, ne | http.request.version eq "HTTP/2.0" |
http.request.scheme | String | eq | http.request.scheme eq "https" |
http.user_agent | String | in, eq, ne, contains, matches | http.user_agent contains "bot" |
http.x_forwarded_for | String | in, eq, ne, contains, matches | http.x_forwarded_for eq "192.168.1.1" |
http.request.body.mime | String | eq, ne | http.request.body.mime eq "application/json" |
http.request.timestamp.sec | Integer | eq, ne, le, ge, lt, gt | http.request.timestamp.sec gt 1735019278 |
HTTP version values: HTTP/1.0, HTTP/1.1, HTTP/2.0, HTTP/3.0
Map Fields (support key access with ["key"])
| Field | Type | Description | Access example |
|---|---|---|---|
http.request.headers | Object | Request headers | http.request.headers["accept"] |
http.request.cookies | Map | Individual cookies | http.request.cookies["session"] |
http.request.uri.args | Map | Query parameters | http.request.uri.args["page"] |
http.request.body.form | Map | Form body fields | http.request.body.form["username"] |
IP / Geo Fields
| Field | Type | Supported Operators | Example |
|---|---|---|---|
ip.src | IP | in, eq, ne, in_list | ip.src in {192.168.1.1 10.0.0.5} |
ip.geoip.country | String | in, eq, ne | ip.geoip.country eq "CN" |
ip.geoip.continent | String | in, eq, ne | ip.geoip.continent eq "AS" |
ip.geoip.asnum | Integer | in, eq, ne, in_list | ip.geoip.asnum eq 45104 |
ip.src.isp | String | in, eq, ne | ip.src.isp contains "China Telecom" |
ip.src.version | String | eq, ne | ip.src.version eq "IPv4" |
ip.src.subdivision_1_iso_code | String | in, eq, ne | ip.src.subdivision_1_iso_code in {"CN-ZJ" "CN-GD"} |
ip.src.region_code | String | in, eq, ne | ip.src.region_code eq "us-west-1" |
Note: ip.geoip.asnum is an integer, do not use quotes.
Boolean Fields
| Field | Type | Description |
|---|---|---|
ssl | Boolean | Whether the request is HTTPS |
Extended Fields (advanced, may require specific plan)
| Field | Type | Description |
|---|---|---|
ali.ja3_hash | String | JA3 TLS fingerprint hash |
ali.ja4 | String | JA4 TLS fingerprint |
ali.js_detection.passed | Boolean | Whether JS challenge passed |
ali.static_resource | Boolean | Whether request is for a static resource |
ali.tls_client_auth.cert_verified | Boolean | Whether client TLS cert is verified |
ali.tls_hash | String | TLS hash |
Field selection guidance
| Scenario | Recommended field |
|---|---|
| Match by domain | http.host |
| Match URL path or suffix | http.request.uri |
| Match file type | http.request.uri.path.extension |
| Match specific file | http.request.uri.path.full_file_name |
| Match query parameter | http.request.uri.args["key"] |
| Match by country | ip.geoip.country |
| Match by IP | ip.src |
| Match by request method | http.request.method |
| Match by header | http.request.headers["header-name"] |
| Match by cookie | http.request.cookies["cookie-name"] |
Notes
- Not all fields are available for all rule types. If a field causes
CompileRuleError, try a related field (e.g. usehttp.request.uriinstead ofhttp.request.uri.path). - Map fields accessed with
["key"]return the value for that key as a string. - Extended fields (
ali.*) may require specific plan levels or feature activation.
ESA Rule Expression - Operators
Complete list of operators available in ESA rule expressions.
Forbidden Operator Formats (CRITICAL)
These formats are ABSOLUTELY FORBIDDEN and will cause errors:
| Forbidden Format | Why | Correct Alternative |
|---|---|---|
not...eq | Invalid syntax | Use ne |
not...ne | Invalid syntax | Use eq |
not in | Wrong position | Use not...in (not before field) |
not contains | Wrong position | Use not...contains (not before field) |
not matches | Wrong position | Use not...matches (not before field) |
field ends_with "value" | Wrong syntax | Use ends_with(field, "value") |
field starts_with "value" | Wrong syntax | Use starts_with(field, "value") |
Correct negation patterns:
# Negating equality
WRONG: not http.host eq "a.com"
RIGHT: http.host ne "a.com"
# Negating set membership
WRONG: http.host not in {"a.com" "b.com"}
RIGHT: not http.host in {"a.com" "b.com"}
# Negating contains
WRONG: http.request.uri not contains "/test"
RIGHT: not http.request.uri contains "/test"String Comparison (Infix Style)
Syntax: (field operator "value")
| Operator | Description | Syntax | Example |
|---|---|---|---|
eq | Equal to | (field eq "value") | (http.host eq "www.example.com") |
ne | Not equal to | (field ne "value") | (http.host ne "test.example.com") |
contains | Contains substring | (field contains "value") | (http.request.uri contains "/test") |
in | In a set of values | (field in {"v1" "v2"}) | (http.host in {"a.com" "b.com"}) |
matches | Regex match (PCRE) | (field matches "regex") | (http.request.uri matches "^/api/v[0-9]+") |
Important notes:
inuses space-separated values in braces, NOT comma-separated:{"a" "b" "c"}matchesrequires standard plan or above; basic plan returnsRuleRegexQuotaCheckFailedcontainsworks withhttp.request.uriandhttp.host. When matching URI, include the path separator (e.g."/test"not"test")
String Comparison (Function Style)
Syntax: (function(field, "value"))
| Operator | Description | Syntax | Example |
|---|---|---|---|
starts_with | Starts with prefix | (starts_with(field, "value")) | (starts_with(http.request.uri, "/api/")) |
ends_with | Ends with suffix | (ends_with(field, "value")) | (ends_with(http.request.uri, ".html")) |
Critical: starts_with and ends_with MUST use function-call syntax. Infix syntax like (field ends_with "value") will cause CompileRuleError.
Numeric Comparison (Infix Style)
Syntax: (field operator number)
| Operator | Description | Syntax | Example |
|---|---|---|---|
eq | Equal to | (field eq num) | (ip.geoip.asnum eq 45104) |
ne | Not equal to | (field ne num) | (ip.geoip.asnum ne 45104) |
lt | Less than | (field lt num) | (ip.geoip.asnum lt 45104) |
le | Less than or equal | (field le num) | (ip.geoip.asnum le 45104) |
gt | Greater than | (field gt num) | (ip.geoip.asnum gt 45104) |
ge | Greater than or equal | (field ge num) | (ip.geoip.asnum ge 45104) |
Length Check (Function Style)
Syntax: (len(field) operator number)
| Operator | Description | Syntax | Example |
|---|---|---|---|
len eq | Length equals | (len(field) eq num) | (len(http.cookie) eq 100) |
len gt | Length greater than | (len(field) gt num) | (len(http.cookie) gt 1024) |
len lt | Length less than | (len(field) lt num) | (len(http.request.uri) lt 2048) |
Existence Check (Function Style)
| Operator | Description | Syntax | Example |
|---|---|---|---|
exists | Field exists | (exists(field)) | (exists(http.request.headers["authorization"])) |
not exists | Field not exists | (not exists(field)) | (not exists(http.request.headers["authorization"])) |
Transformation (Function Style)
| Function | Description | Syntax | Example |
|---|---|---|---|
lower | Convert to lowercase | lower(field) | (lower(http.request.uri) contains "api") |
Use lower() for case-insensitive matching by wrapping the field.
Negation
Any expression can be negated with not:
| Operator | Syntax | Example |
|---|---|---|
not | (not expr) | (not http.host contains "test") |
not...contains | (not field contains "value") | (not http.host contains "staging") |
not...in | (not field in {...}) | (not http.host in {"a.com" "b.com"}) |
not...matches | (not field matches "regex") | (not http.request.uri matches "^/internal") |
not starts_with | (not starts_with(field, "v")) | (not starts_with(http.request.uri, "/admin")) |
not ends_with | (not ends_with(field, "v")) | (not ends_with(http.request.uri, ".json")) |
Logical Operators
| Operator | Description | Syntax | Example |
|---|---|---|---|
and | Both conditions | (expr1 and expr2) | (http.host eq "a.com" and starts_with(http.request.uri, "/api")) |
or | Either condition | (expr1) or (expr2) | (http.host eq "a.com") or (http.host eq "b.com") |
Nesting rules:
- Max nesting depth: 2 levels
andconditions go inside the same parentheses:(A and B and C)orconditions separate parenthesized groups:(A) or (B) or (C)- Mixed:
(A and B) or (C and D)
Plan Limitations
| Plan | eq/ne/in/starts_with/ends_with | contains | matches (regex) |
|---|---|---|---|
| Basic | Supported | Supported | Not supported |
| Standard | Supported | Supported | Supported |
| Enterprise | Supported | Supported | Supported |
Common CompileRuleError causes
1. Using ends_with/starts_with as infix operators instead of function syntax 2. Using wildcard (not a valid ESA operator) 3. Using contains with URI but missing path separator (use "/test" not "test") 4. Using unsupported match fields for the specific rule type 5. Missing parentheses around the expression 6. Using comma-separated values in in instead of space-separated
ESA Sites
Sites are the basic management unit of ESA. Each site corresponds to a domain (or subdomain).
Common operations
- Create:
CreateSite - List:
ListSites(supports pagination and filters) - Query:
GetSite - Delete:
DeleteSite - Check name:
CheckSiteName - Change access type:
UpdateSiteAccessType - Change coverage:
UpdateSiteCoverage
Access types
- CNAME: Keep original DNS, add CNAME record pointing to ESA edge node.
- Pros: Simple setup, no DNS migration needed.
- Limitation: DNS record APIs are NOT available (CreateRecord, ListRecords, etc. will fail).
- NS: Delegate nameservers to ESA, ESA takes over DNS resolution.
- Pros: Full DNS management via API.
- Limitation: Must change NS records at domain registrar.
Switching access type
Use UpdateSiteAccessType to switch between CNAME and NS.
- NS -> CNAME: Will fail if incompatible DNS records exist. Delete A/AAAA records first.
- CNAME -> NS: Works directly. After switching, manage DNS via ESA API.
Coverage options
domestic: China mainland onlyoverseas: Outside China mainlandglobal: Worldwide
Plan types
basicplan/standardplan/advancedplan/enterpriseplan
ListSites filters
SiteName: Fuzzy matchStatus: Site status (active, pending, etc.)AccessType: NS or CNAMECoverage: domestic, overseas, globalPlanSubscribeType: Plan type
Behavioral notes
- Newly created sites start as
pending; complete access verification to activate. - Deleting a site removes all associated configuration (DNS records, cache rules, WAF rules, certificates, etc.).
ListSitesreturns sites across all regions; no need to iterate regions.
References
- CreateSite: https://help.aliyun.com/zh/esa/developer-reference/api-esa-2024-09-10-createsite
- ListSites: https://help.aliyun.com/zh/esa/developer-reference/api-esa-2024-09-10-listsites
- Access types: https://help.aliyun.com/zh/esa/user-guide/add-site
Official documentation sources (for future updates) ====================================================
Product pages
- OpenAPI product page: https://api.aliyun.com/product/ESA
- API metadata: https://api.aliyun.com/meta/v1/products/ESA/versions/2024-09-10/api-docs.json
- API overview: https://www.alibabacloud.com/help/en/esa/developer-reference/api-esa-2024-09-10-overview
- Endpoints: https://www.alibabacloud.com/help/en/esa/developer-reference/endpoints
- Product home: https://www.alibabacloud.com/help/en/esa/
- Quick start: https://www.alibabacloud.com/help/en/esa/getting-started/
Key operation docs
- ListSites: https://help.aliyun.com/zh/esa/developer-reference/api-esa-2024-09-10-listsites
- CreateSite: https://help.aliyun.com/zh/esa/developer-reference/api-esa-2024-09-10-createsite
- CreateRecord: https://help.aliyun.com/zh/esa/developer-reference/api-esa-2024-09-10-createrecord
- ListRecords: https://help.aliyun.com/zh/esa/developer-reference/api-esa-2024-09-10-listrecords
- PurgeCaches: https://help.aliyun.com/zh/esa/developer-reference/api-esa-2024-09-10-purgecaches
- PreloadCaches: https://help.aliyun.com/zh/esa/developer-reference/api-esa-2024-09-10-preloadcaches
- ApplyCertificate: https://help.aliyun.com/zh/esa/developer-reference/api-esa-2024-09-10-applycertificate
- CreateWafRule: https://help.aliyun.com/zh/esa/developer-reference/api-esa-2024-09-10-createwafrule
- ListWafRulesets: https://help.aliyun.com/zh/esa/developer-reference/api-esa-2024-09-10-listwafrulesets
- CreateOriginPool: https://help.aliyun.com/zh/esa/developer-reference/api-esa-2024-09-10-createoriginpool
- CreateCacheRule: https://help.aliyun.com/zh/esa/developer-reference/api-esa-2024-09-10-createcacherule
Access type docs
- CNAME vs NS: https://help.aliyun.com/zh/esa/user-guide/add-site
DescribeSiteTimeSeriesData API Reference
Query account-level or site-level traffic analysis time-series data.
Official docs: https://api.aliyun.com/document/ESA/2024-09-10/DescribeSiteTimeSeriesData
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
SiteId | string | No | Site ID. Empty = account-level query |
StartTime | string | No | Start time (ISO8601 UTC+0) |
EndTime | string | No | End time (ISO8601 UTC+0) |
Interval | string | No | Time granularity in seconds |
Fields | array | Yes | Query metrics array |
Fields Structure
| Parameter | Type | Required | Description |
|---|---|---|---|
FieldName | string | No | Metric name (e.g., Traffic, Request) |
Dimension | array | No | Dimension array (e.g., ["SiteId"]) |
Response Parameters
| Parameter | Type | Description |
|---|---|---|
Data | array | Time-series data array |
SummarizedData | array | Aggregated summary data |
StartTime | string | Query start time |
EndTime | string | Query end time |
Interval | long | Data granularity (seconds) |
SamplingRate | float | Sampling rate (%) |
RequestId | string | Request ID |
Data Structure
| Parameter | Type | Description |
|---|---|---|
FieldName | string | Metric name |
DimensionName | string | Dimension name |
DimensionValue | string | Dimension value |
DimensionValueAlias | string | Dimension alias (e.g., site name) |
DetailData | array | Time-series points |
DetailData Structure
| Parameter | Type | Description |
|---|---|---|
TimeStamp | string | Time point (ISO8601) |
Value | any | Metric value |
SummarizedData Structure
| Parameter | Type | Description |
|---|---|---|
FieldName | string | Metric name |
DimensionName | string | Dimension name |
DimensionValue | string | Dimension value |
AggMethod | string | Aggregation method (sum, avg) |
Value | any | Aggregated value |
Time Granularity Rules
| Time Range | Interval | Parameter Value |
|---|---|---|
| <= 3 hours | 1 minute | 60 |
| 3-12 hours | 5 minutes | 300 |
| 12 hours - 1 day | 15 minutes | 900 |
| 1-10 days | 1 hour | 3600 |
| 10-31 days | 1 day | 86400 |
Default: If StartTime and EndTime not specified, returns last 24 hours.
Note: Large time ranges may use sampling.
Code Examples
Query site traffic (hourly)
from alibabacloud_esa20240910.client import Client as EsaClient
from alibabacloud_esa20240910 import models as esa_models
from alibabacloud_tea_openapi import models as open_api_models
from datetime import datetime, timedelta, timezone
def create_client():
config = open_api_models.Config(
region_id="cn-hangzhou",
endpoint="esa.cn-hangzhou.aliyuncs.com",
)
return EsaClient(config)
def query_traffic(site_id: str, hours: int = 24):
client = create_client()
end_time = datetime.now(timezone.utc)
start_time = end_time - timedelta(hours=hours)
resp = client.describe_site_time_series_data(
esa_models.DescribeSiteTimeSeriesDataRequest(
site_id=site_id,
start_time=start_time.strftime("%Y-%m-%dT%H:%M:%SZ"),
end_time=end_time.strftime("%Y-%m-%dT%H:%M:%SZ"),
interval="3600", # 1 hour
fields=[
esa_models.DescribeSiteTimeSeriesDataRequestFields(
field_name="Traffic",
dimension=["SiteId"]
)
]
)
)
return resp.body
# Usage
result = query_traffic("974351557069296", hours=24)
for item in result.data:
print(f"Site: {item.dimension_value_alias}")
for point in item.detail_data:
print(f" {point.time_stamp}: {point.value} bytes")Query multiple metrics
def query_multiple_metrics(site_id: str):
client = create_client()
resp = client.describe_site_time_series_data(
esa_models.DescribeSiteTimeSeriesDataRequest(
site_id=site_id,
start_time="2026-03-09T00:00:00Z",
end_time="2026-03-10T00:00:00Z",
interval="3600",
fields=[
esa_models.DescribeSiteTimeSeriesDataRequestFields(
field_name="Traffic",
dimension=["ALL"]
),
esa_models.DescribeSiteTimeSeriesDataRequestFields(
field_name="Request",
dimension=["ALL"]
),
esa_models.DescribeSiteTimeSeriesDataRequestFields(
field_name="HitRate",
dimension=["ALL"]
)
]
)
)
return resp.bodyQuery by country
def query_traffic_by_country(site_id: str):
client = create_client()
resp = client.describe_site_time_series_data(
esa_models.DescribeSiteTimeSeriesDataRequest(
site_id=site_id,
start_time="2026-03-09T00:00:00Z",
end_time="2026-03-10T00:00:00Z",
interval="3600",
fields=[
esa_models.DescribeSiteTimeSeriesDataRequestFields(
field_name="Traffic",
dimension=["ClientCountryCode"]
)
]
)
)
# Group by country
for item in resp.body.data:
country = item.dimension_value
print(f"Country: {country}")
for point in item.detail_data:
print(f" {point.time_stamp}: {point.value}")Response Example
{
"Data": [
{
"FieldName": "Traffic",
"DimensionName": "SiteId",
"DimensionValue": "974351557069296",
"DimensionValueAlias": "lwcwiki.fun",
"DetailData": [
{"TimeStamp": "2026-03-09T16:00:00Z", "Value": 38428},
{"TimeStamp": "2026-03-09T17:00:00Z", "Value": 1042},
{"TimeStamp": "2026-03-09T18:00:00Z", "Value": 629}
]
}
],
"SummarizedData": [
{
"FieldName": "Traffic",
"DimensionName": "SiteId",
"DimensionValue": "974351557069296",
"DimensionValueAlias": "lwcwiki.fun",
"AggMethod": "sum",
"Value": 27317844
}
],
"StartTime": "2026-03-09T16:00:00Z",
"EndTime": "2026-03-10T15:59:00Z",
"Interval": 3600,
"SamplingRate": 100
}Error Codes
| HTTP Code | Error Code | Description |
|---|---|---|
| 400 | InvalidParameter.TimeRange | Time range exceeds limit |
| 400 | InvalidEndTime.Mismatch | EndTime earlier than StartTime |
| 400 | InvalidParameter.Field | Invalid field name |
| 400 | InvalidParameter.Dimension | Invalid dimension |
| 400 | InvalidTime.Malformed | Wrong time format |
| 400 | TooManyDimensions | Too many query dimensions |
| 400 | TooManyRequests | Rate limited |
DescribeSiteTopData API Reference
Query account-level or site-level traffic analysis Top-N ranking data.
Official docs: https://api.aliyun.com/document/ESA/2024-09-10/DescribeSiteTopData
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
SiteId | string | No | Site ID. Empty = account-level query |
StartTime | string | No | Start time (ISO8601 UTC+0) |
EndTime | string | No | End time (ISO8601 UTC+0) |
Interval | string | No | Time granularity in seconds |
Fields | array | Yes | Query metrics array |
Limit | string | No | Top-N count. Values: 5, 10, 150 |
Fields Structure
| Parameter | Type | Required | Description |
|---|---|---|---|
FieldName | string | No | Metric name (e.g., Traffic, Request) |
Dimension | array | No | Dimension array (e.g., ["ClientCountryCode"]) |
Response Parameters
| Parameter | Type | Description |
|---|---|---|
Data | array | Top-N data array |
StartTime | string | Query start time |
EndTime | string | Query end time |
SamplingRate | float | Sampling rate (%) |
RequestId | string | Request ID |
Data Structure
| Parameter | Type | Description |
|---|---|---|
FieldName | string | Metric name |
DimensionName | string | Dimension name |
DetailData | array | Top-N ranking data |
DetailData Structure
| Parameter | Type | Description |
|---|---|---|
DimensionValue | string | Dimension value (e.g., country code) |
Value | any | Metric value |
Code Examples
Query top 10 countries by traffic
from alibabacloud_esa20240910.client import Client as EsaClient
from alibabacloud_esa20240910 import models as esa_models
from alibabacloud_tea_openapi import models as open_api_models
from datetime import datetime, timedelta, timezone
def create_client():
config = open_api_models.Config(
region_id="cn-hangzhou",
endpoint="esa.cn-hangzhou.aliyuncs.com",
)
return EsaClient(config)
def query_top_countries(site_id: str, limit: int = 10):
client = create_client()
end_time = datetime.now(timezone.utc)
start_time = end_time - timedelta(hours=24)
resp = client.describe_site_top_data(
esa_models.DescribeSiteTopDataRequest(
site_id=site_id,
start_time=start_time.strftime("%Y-%m-%dT%H:%M:%SZ"),
end_time=end_time.strftime("%Y-%m-%dT%H:%M:%SZ"),
fields=[
esa_models.DescribeSiteTopDataRequestFields(
field_name="Traffic",
dimension=["ClientCountryCode"]
)
],
limit=str(limit)
)
)
return resp.body
# Usage
result = query_top_countries("974351557069296", limit=10)
for item in result.data:
print(f"Metric: {item.field_name} by {item.dimension_name}")
for i, rank in enumerate(item.detail_data, 1):
print(f" #{i} {rank.dimension_value}: {rank.value} bytes")Query top hosts by requests
def query_top_hosts(site_id: str):
client = create_client()
resp = client.describe_site_top_data(
esa_models.DescribeSiteTopDataRequest(
site_id=site_id,
start_time="2026-03-10T00:00:00Z",
end_time="2026-03-11T00:00:00Z",
fields=[
esa_models.DescribeSiteTopDataRequestFields(
field_name="Request",
dimension=["Host"]
)
],
limit="10"
)
)
return resp.bodyQuery top URLs by traffic
def query_top_urls(site_id: str):
client = create_client()
resp = client.describe_site_top_data(
esa_models.DescribeSiteTopDataRequest(
site_id=site_id,
start_time="2026-03-10T00:00:00Z",
end_time="2026-03-11T00:00:00Z",
fields=[
esa_models.DescribeSiteTopDataRequestFields(
field_name="Traffic",
dimension=["RequestUri"]
)
],
limit="150"
)
)
return resp.bodyQuery account-level top sites
def query_top_sites():
"""Query top sites at account level (no SiteId)"""
client = create_client()
resp = client.describe_site_top_data(
esa_models.DescribeSiteTopDataRequest(
# No site_id = account level
start_time="2026-03-10T00:00:00Z",
end_time="2026-03-11T00:00:00Z",
fields=[
esa_models.DescribeSiteTopDataRequestFields(
field_name="Traffic",
dimension=["SiteId"]
)
],
limit="10"
)
)
return resp.bodyResponse Example
{
"Data": [
{
"FieldName": "Traffic",
"DimensionName": "ClientCountryCode",
"DetailData": [
{"DimensionValue": "HK", "Value": 9132354},
{"DimensionValue": "CN", "Value": 703820},
{"DimensionValue": "CA", "Value": 64679},
{"DimensionValue": "US", "Value": 33979},
{"DimensionValue": "SG", "Value": 27666}
]
}
],
"StartTime": "2026-03-10T02:47:00Z",
"EndTime": "2026-03-11T02:47:00Z",
"SamplingRate": 100
}Common Use Cases
| Use Case | FieldName | Dimension |
|---|---|---|
| Top countries by traffic | Traffic | ClientCountryCode |
| Top provinces (China) | Traffic | ClientProvinceCode |
| Top ISPs | Traffic | ClientISP |
| Top hosts | Request | Host |
| Top URLs | Traffic | RequestUri |
| Top sites (account level) | Traffic | SiteId |
| Top status codes | Request | Status |
Error Codes
| HTTP Code | Error Code | Description |
|---|---|---|
| 400 | InvalidParameter.TimeRange | Time range exceeds limit |
| 400 | InvalidEndTime.Mismatch | EndTime earlier than StartTime |
| 400 | InvalidParameter.Field | Invalid field name |
| 400 | InvalidParameter.Dimension | Invalid dimension |
| 400 | InvalidTime.Malformed | Wrong time format |
| 400 | TooManyDimensions | Too many query dimensions |
| 400 | TooManyRequests | Rate limited |
Differences from Time-Series API
| Feature | Time-Series | Top-N |
|---|---|---|
| Purpose | Trend over time | Ranking at a point |
| Returns | Time-stamped values | Ranked values |
| Limit | N/A | 5, 10, or 150 |
| SummarizedData | Yes | No |
#!/usr/bin/env python3
"""Check ESA site status and configuration overview.
Displays site info and configuration settings (IPv6, dev mode, tiered cache, etc.).
"""
from __future__ import annotations
import argparse
import os
from alibabacloud_esa20240910.client import Client as Esa20240910Client
from alibabacloud_esa20240910 import models as esa_models
from alibabacloud_tea_openapi import models as open_api_models
def create_client() -> Esa20240910Client:
config = open_api_models.Config(
region_id="cn-hangzhou",
endpoint="esa.cn-hangzhou.aliyuncs.com",
)
ak = os.getenv("ALICLOUD_ACCESS_KEY_ID") or os.getenv("ALIBABA_CLOUD_ACCESS_KEY_ID")
sk = os.getenv("ALICLOUD_ACCESS_KEY_SECRET") or os.getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET")
token = os.getenv("ALICLOUD_SECURITY_TOKEN") or os.getenv("ALIBABA_CLOUD_SECURITY_TOKEN")
if ak and sk:
config.access_key_id = ak
config.access_key_secret = sk
if token:
config.security_token = token
return Esa20240910Client(config)
def main() -> int:
parser = argparse.ArgumentParser(description="Check ESA site status and configuration")
parser.add_argument("--site-id", type=int, required=True, help="ESA site ID")
args = parser.parse_args()
client = create_client()
site_id = args.site_id
# 1. Site info
site_resp = client.get_site(esa_models.GetSiteRequest(site_id=site_id))
site = site_resp.body.site_model # GetSite returns nested site_model object
print("=== Site Info ===")
print(f" Site Name: {getattr(site, 'site_name', 'N/A')}")
print(f" Site ID: {site_id}")
print(f" Status: {getattr(site, 'status', 'N/A')}")
print(f" Access Type: {getattr(site, 'access_type', 'N/A')}")
print(f" Plan: {getattr(site, 'plan_name', 'N/A')}")
print(f" Coverage: {getattr(site, 'coverage', 'N/A')}")
print(f" CNAME Zone: {getattr(site, 'cname_zone', 'N/A')}")
print()
# 2. Tiered cache config
try:
cache_resp = client.get_tiered_cache(esa_models.GetTieredCacheRequest(site_id=site_id))
print("=== Tiered Cache ===")
print(f" Architecture: {getattr(cache_resp.body, 'cache_architecture_mode', 'N/A')}")
except Exception as e:
print(f"=== Tiered Cache ===\n Error: {e}")
print()
# 3. IPv6 config
try:
ipv6_resp = client.get_ipv6(esa_models.GetIPv6Request(site_id=site_id))
print("=== IPv6 ===")
print(f" Enabled: {getattr(ipv6_resp.body, 'enable', 'N/A')}")
except Exception as e:
print(f"=== IPv6 ===\n Error: {e}")
print()
# 4. Development mode
try:
dev_resp = client.get_development_mode(esa_models.GetDevelopmentModeRequest(site_id=site_id))
print("=== Development Mode ===")
print(f" Enabled: {getattr(dev_resp.body, 'enable', 'N/A')}")
except Exception as e:
print(f"=== Development Mode ===\n Error: {e}")
print()
# 5. SEO bypass
try:
seo_resp = client.get_seo_bypass(esa_models.GetSeoBypassRequest(site_id=site_id))
print("=== SEO Bypass ===")
print(f" Enabled: {getattr(seo_resp.body, 'enable', 'N/A')}")
except Exception as e:
print(f"=== SEO Bypass ===\n Error: {e}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
#!/usr/bin/env python3
"""List DNS records for an ESA site.
Only works for NS-connected sites. CNAME sites will return an error.
"""
from __future__ import annotations
import argparse
import json
import os
import sys
from alibabacloud_esa20240910.client import Client as Esa20240910Client
from alibabacloud_esa20240910 import models as esa_models
from alibabacloud_tea_openapi import models as open_api_models
def create_client() -> Esa20240910Client:
config = open_api_models.Config(
region_id="cn-hangzhou",
endpoint="esa.cn-hangzhou.aliyuncs.com",
)
ak = os.getenv("ALICLOUD_ACCESS_KEY_ID") or os.getenv("ALIBABA_CLOUD_ACCESS_KEY_ID")
sk = os.getenv("ALICLOUD_ACCESS_KEY_SECRET") or os.getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET")
token = os.getenv("ALICLOUD_SECURITY_TOKEN") or os.getenv("ALIBABA_CLOUD_SECURITY_TOKEN")
if ak and sk:
config.access_key_id = ak
config.access_key_secret = sk
if token:
config.security_token = token
return Esa20240910Client(config)
def iter_records(client: Esa20240910Client, site_id: int, record_type: str | None = None):
"""Iterate over all DNS records for a site with pagination."""
page_number = 1
page_size = 100
while True:
req = esa_models.ListRecordsRequest(
site_id=site_id,
page_number=page_number,
page_size=page_size,
)
if record_type:
req.type = record_type
resp = client.list_records(req)
records = resp.body.records or []
for rec in records:
yield rec
total = resp.body.total_count or 0
if page_number * page_size >= total:
break
page_number += 1
def to_record(rec) -> dict:
"""Convert a record object to a dict."""
return {
"record_id": rec.record_id,
"record_name": rec.record_name,
"type": rec.type,
"data": getattr(rec, "data", None) or getattr(rec, "record_data", None),
"ttl": rec.ttl,
"proxied": rec.proxied,
"create_time": rec.create_time,
"update_time": rec.update_time,
}
def main() -> int:
parser = argparse.ArgumentParser(description="List DNS records for an ESA site")
parser.add_argument("--site-id", type=int, required=True, help="ESA site ID")
parser.add_argument("--type", dest="record_type", help="Filter by record type (e.g., A/AAAA, CNAME)")
parser.add_argument("--json", action="store_true", help="Output as JSON")
args = parser.parse_args()
client = create_client()
try:
records = [to_record(rec) for rec in iter_records(client, args.site_id, args.record_type)]
except Exception as e:
if "CnameSiteRecordUnsupport" in str(e):
print("Error: DNS record APIs are not available for CNAME-connected sites.", file=sys.stderr)
print("This site uses CNAME access type. Switch to NS access to manage DNS records.", file=sys.stderr)
return 1
raise
if args.json:
print(json.dumps({"records": records, "total": len(records)}, indent=2, ensure_ascii=False))
else:
if not records:
print("No DNS records found.")
return 0
# TSV output
print("record_name\ttype\tdata\tttl\tproxied")
for rec in records:
print(f"{rec['record_name']}\t{rec['type']}\t{rec['data']}\t{rec['ttl']}\t{rec['proxied']}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
#!/usr/bin/env python3
"""List all ESA sites.
Outputs TSV by default. Use --json for JSON output.
"""
from __future__ import annotations
import argparse
import json
import os
from typing import Iterable
from alibabacloud_esa20240910.client import Client as Esa20240910Client
from alibabacloud_esa20240910 import models as esa_models
from alibabacloud_tea_openapi import models as open_api_models
def create_client() -> Esa20240910Client:
config = open_api_models.Config(
region_id="cn-hangzhou",
endpoint="esa.cn-hangzhou.aliyuncs.com",
)
ak = os.getenv("ALICLOUD_ACCESS_KEY_ID") or os.getenv("ALIBABA_CLOUD_ACCESS_KEY_ID")
sk = os.getenv("ALICLOUD_ACCESS_KEY_SECRET") or os.getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET")
token = os.getenv("ALICLOUD_SECURITY_TOKEN") or os.getenv("ALIBABA_CLOUD_SECURITY_TOKEN")
if ak and sk:
config.access_key_id = ak
config.access_key_secret = sk
if token:
config.security_token = token
return Esa20240910Client(config)
def iter_sites(client: Esa20240910Client) -> Iterable:
page_number = 1
page_size = 50
while True:
resp = client.list_sites(esa_models.ListSitesRequest(
page_number=page_number,
page_size=page_size,
))
for site in resp.body.sites:
yield site
total = resp.body.total_count
if page_number * page_size >= total:
break
page_number += 1
def to_record(site) -> dict:
return {
"site_id": site.site_id,
"site_name": site.site_name,
"status": site.status,
"access_type": site.access_type,
"plan_name": site.plan_name,
"coverage": site.coverage,
"cname_zone": getattr(site, "cname_zone", None),
"create_time": site.create_time,
}
def main() -> int:
parser = argparse.ArgumentParser(description="List all ESA sites")
parser.add_argument("--json", action="store_true", help="Output JSON array")
parser.add_argument("--output", help="Write output to file")
args = parser.parse_args()
client = create_client()
records = [to_record(site) for site in iter_sites(client)]
if args.json:
output = json.dumps(records, ensure_ascii=False, indent=2)
else:
lines = [
"site_id\tsite_name\tstatus\taccess_type\tplan_name\tcoverage\tcname_zone\tcreate_time"
]
for r in records:
lines.append(
"\t".join(
str(r.get(k) or "")
for k in [
"site_id", "site_name", "status", "access_type",
"plan_name", "coverage", "cname_zone", "create_time",
]
)
)
output = "\n".join(lines)
if args.output:
with open(args.output, "w", encoding="utf-8") as f:
f.write(output)
else:
print(output)
return 0
if __name__ == "__main__":
raise SystemExit(main())
#!/usr/bin/env python3
"""
ESA 数据分析统一查询脚本
支持所有维度和指标的灵活查询。
使用前请配置凭证:
export ALIBABA_CLOUD_ACCESS_KEY_ID="your-ak"
export ALIBABA_CLOUD_ACCESS_KEY_SECRET="your-sk"
示例:
# 查询 Top 维度数据
python query_analytics.py --dimension ClientCountryCode --metric Traffic
python query_analytics.py --dimension EdgeResponseStatusCode --metric Requests
python query_analytics.py --dimension ClientRequestHost --metric Traffic,Requests
# 查询时序数据
python query_analytics.py --time-series --metric Traffic,Requests
# 指定时间范围
python query_analytics.py --dimension ClientIP --metric Requests --hours 72
# 列出可用维度和指标
python query_analytics.py --list-dimensions
python query_analytics.py --list-metrics
"""
import os
import sys
import json
import argparse
import configparser
from datetime import datetime, timedelta, timezone
from typing import List, Optional, Dict, Any
from alibabacloud_esa20240910.client import Client as EsaClient
from alibabacloud_esa20240910 import models as esa_models
from alibabacloud_tea_openapi import models as open_api_models
# ============================================================================
# 维度和指标定义 (来自 field.md)
# ============================================================================
METRICS = {
"Traffic": "ESA 节点响应返回给客户端的大小,单位:Byte",
"Requests": "请求数",
"RequestTraffic": "客户端请求的大小,单位:Byte",
"PageView": "页面浏览量",
}
DIMENSIONS = {
"ALL": "用户维度全量数据",
"ClientASN": "从客户端 IP 地址解析出的自治系统编号(ASN)信息",
"ClientBrowser": "客户端浏览器类型",
"ClientCountryCode": "从客户端 IP 地址解析出的 ISO-3166 Alpha-2 Code",
"ClientDevice": "客户端设备类型",
"ClientIP": "与 ESA 节点建立连接的客户端 IP",
"ClientIPVersion": "与 ESA 节点建立连接的客户端 IP 版本",
"ClientISP": "从客户端 IP 地址解析出的运营商信息",
"ClientOS": "客户端系统型号",
"ClientProvinceCode": "从客户端 IP 地址解析出的中国内地省份信息",
"ClientRequestHost": "客户端请求的 Host 信息",
"ClientRequestMethod": "客户端请求的 HTTP Method 信息",
"ClientRequestPath": "客户端请求的路径信息",
"ClientRequestProtocol": "客户端请求的协议信息",
"ClientRequestQuery": "客户端请求的 Query 信息",
"ClientRequestReferer": "客户端请求的 Referer 信息",
"ClientRequestUserAgent": "客户端请求的 User-Agent 信息",
"ClientSSLProtocol": "客户端的 SSL 协议版本,- 表示没有使用 SSL",
"ClientXRequestedWith": "客户端携带的 X-Requested-With 请求头",
"EdgeCacheStatus": "客户端请求的缓存状态",
"EdgeResponseContentType": "ESA 节点响应的 Content-Type 信息",
"EdgeResponseStatusCode": "ESA 节点响应返回给客户端的状态码",
"OriginResponseStatusCode": "源站响应状态码",
"SiteId": "当前站点的 ID",
"Version": "版本管理的版本号",
}
# 缓存状态说明
CACHE_STATUS_INFO = {
"HIT": "缓存命中 - 请求直接从 ESA 边缘节点缓存返回",
"MISS": "缓存未命中 - 请求需要回源获取内容",
"STALE": "过期缓存 - 返回过期缓存内容(源站不可用时)",
"EXPIRED": "缓存过期 - 缓存已过期,需要重新验证",
"BYPASS": "绕过缓存 - 请求绕过缓存",
"UPDATING": "更新中 - 缓存正在后台更新",
"REVALIDATED": "重新验证 - 源站确认缓存仍然有效",
"DYNAMIC": "动态内容 - 不进行缓存",
"NONE": "无缓存状态 - 请求未触发缓存逻辑",
}
# HTTP 状态码说明
STATUS_CODE_INFO = {
200: "OK - 请求成功",
201: "Created - 资源创建成功",
204: "No Content - 无内容返回",
206: "Partial Content - 部分内容",
301: "Moved Permanently - 永久重定向",
302: "Found - 临时重定向",
304: "Not Modified - 未修改",
307: "Temporary Redirect - 临时重定向",
308: "Permanent Redirect - 永久重定向",
400: "Bad Request - 请求格式错误",
401: "Unauthorized - 未授权",
403: "Forbidden - 禁止访问",
404: "Not Found - 资源未找到",
405: "Method Not Allowed - 方法不允许",
408: "Request Timeout - 请求超时",
429: "Too Many Requests - 请求过多",
500: "Internal Server Error - 服务器内部错误",
502: "Bad Gateway - 网关错误",
503: "Service Unavailable - 服务不可用",
504: "Gateway Timeout - 网关超时",
522: "Connection Timed Out - 连接超时",
524: "A Timeout Occurred - 发生超时",
}
# ============================================================================
# ESA 客户端
# ============================================================================
def create_client() -> EsaClient:
"""创建 ESA 客户端"""
access_key_id = os.environ.get("ALIBABA_CLOUD_ACCESS_KEY_ID")
access_key_secret = os.environ.get("ALIBABA_CLOUD_ACCESS_KEY_SECRET")
if not access_key_id or not access_key_secret:
cred_path = os.path.expanduser("~/.alibabacloud/credentials")
if os.path.exists(cred_path):
parser = configparser.ConfigParser()
parser.read(cred_path)
if "default" in parser:
access_key_id = parser.get("default", "access_key_id", fallback=None)
access_key_secret = parser.get("default", "access_key_secret", fallback=None)
if not access_key_id or not access_key_secret:
print("错误: 请配置凭证", file=sys.stderr)
print(" export ALIBABA_CLOUD_ACCESS_KEY_ID='your-ak'", file=sys.stderr)
print(" export ALIBABA_CLOUD_ACCESS_KEY_SECRET='your-sk'", file=sys.stderr)
sys.exit(1)
config = open_api_models.Config(
access_key_id=access_key_id,
access_key_secret=access_key_secret,
region_id=os.environ.get("ALIBABA_CLOUD_REGION_ID", "cn-hangzhou"),
endpoint="esa.cn-hangzhou.aliyuncs.com",
)
return EsaClient(config)
def list_sites(client: EsaClient) -> List:
"""列出所有站点"""
resp = client.list_sites(esa_models.ListSitesRequest(
page_number=1,
page_size=50,
))
return resp.body.sites or []
def select_site(sites: List, site_id: Optional[str] = None) -> Any:
"""选择站点"""
if site_id:
for site in sites:
if str(site.site_id) == site_id:
return site
print(f"错误: 未找到站点 ID: {site_id}", file=sys.stderr)
sys.exit(1)
if len(sites) == 1:
return sites[0]
print("\n站点列表:")
for i, site in enumerate(sites, 1):
print(f" {i}. {site.site_name} (ID: {site.site_id}, 状态: {site.status})")
try:
choice = int(input("\n请选择站点编号: "))
if 1 <= choice <= len(sites):
return sites[choice - 1]
except (ValueError, EOFError):
pass
print("错误: 无效选择", file=sys.stderr)
sys.exit(1)
# ============================================================================
# 查询函数
# ============================================================================
def query_top_data(
client: EsaClient,
site_id: str,
dimension: str,
metrics: List[str],
hours: int = 24,
limit: int = 150
):
"""查询 Top 数据"""
end_time = datetime.now(timezone.utc)
start_time = end_time - timedelta(hours=hours)
fields = [
esa_models.DescribeSiteTopDataRequestFields(
field_name=metric,
dimension=[dimension]
)
for metric in metrics
]
resp = client.describe_site_top_data(
esa_models.DescribeSiteTopDataRequest(
site_id=site_id,
start_time=start_time.strftime("%Y-%m-%dT%H:%M:%SZ"),
end_time=end_time.strftime("%Y-%m-%dT%H:%M:%SZ"),
fields=fields,
limit=str(limit)
)
)
return resp.body, start_time, end_time
def query_time_series(
client: EsaClient,
site_id: str,
metrics: List[str],
dimension: str = "ALL",
hours: int = 24
):
"""查询时序数据"""
end_time = datetime.now(timezone.utc)
start_time = end_time - timedelta(hours=hours)
# 根据时间范围选择时间粒度
if hours <= 3:
interval = "60"
elif hours <= 12:
interval = "300"
elif hours <= 24:
interval = "900"
else:
interval = "3600"
fields = [
esa_models.DescribeSiteTimeSeriesDataRequestFields(
field_name=metric,
dimension=[dimension]
)
for metric in metrics
]
resp = client.describe_site_time_series_data(
esa_models.DescribeSiteTimeSeriesDataRequest(
site_id=site_id,
start_time=start_time.strftime("%Y-%m-%dT%H:%M:%SZ"),
end_time=end_time.strftime("%Y-%m-%dT%H:%M:%SZ"),
interval=interval,
fields=fields
)
)
return resp.body, start_time, end_time
# ============================================================================
# 格式化输出
# ============================================================================
def format_bytes(value: int) -> str:
"""格式化字节数"""
if value >= 1024 * 1024 * 1024:
return f"{value / (1024*1024*1024):.2f} GB"
elif value >= 1024 * 1024:
return f"{value / (1024*1024):.2f} MB"
elif value >= 1024:
return f"{value / 1024:.2f} KB"
return f"{value} B"
def format_number(value: int) -> str:
"""格式化数字"""
return f"{value:,}"
def get_dimension_display(dimension: str, value: str) -> str:
"""获取维度值的显示文本"""
if dimension == "EdgeCacheStatus":
return f"{value} - {CACHE_STATUS_INFO.get(value.upper(), '')}"
if dimension == "EdgeResponseStatusCode":
try:
code = int(value)
return f"{value} - {STATUS_CODE_INFO.get(code, '')}"
except ValueError:
return value
return value
# ============================================================================
# 打印结果
# ============================================================================
def print_top_data(result, dimension: str, metrics: List[str], start_time, end_time, top_n: int = 20):
"""打印 Top 数据"""
print(f"\n{'='*80}")
print(f"Top {DIMENSIONS.get(dimension, dimension)} 分析")
print(f"时间范围: {start_time.strftime('%Y-%m-%d %H:%M')} - {end_time.strftime('%Y-%m-%d %H:%M')} UTC")
print(f"采样率: {result.sampling_rate}%")
print(f"{'='*80}\n")
if not result.data:
print("暂无数据")
return {}
# 解析数据
data_by_dimension = {}
for item in result.data:
if not item.detail_data:
continue
for p in item.detail_data:
key = p.dimension_value
if key not in data_by_dimension:
data_by_dimension[key] = {}
data_by_dimension[key][item.field_name] = p.value
if not data_by_dimension:
print("暂无数据")
return {}
# 计算总数用于百分比
totals = {metric: 0 for metric in metrics}
for values in data_by_dimension.values():
for metric in metrics:
totals[metric] += values.get(metric, 0)
# 按第一个指标排序
primary_metric = metrics[0]
sorted_data = sorted(
data_by_dimension.items(),
key=lambda x: x[1].get(primary_metric, 0),
reverse=True
)
# 打印表头
header = f"{'Rank':<6}{dimension[:30]:<32}"
for metric in metrics:
if metric == "Traffic":
header += f"{metric:>14}"
else:
header += f"{metric:>12}"
header += f"{'%':>8}"
print(header)
print("-" * 80)
# 打印数据
for i, (key, values) in enumerate(sorted_data[:top_n], 1):
# 显示值
display_key = key[:30] + ".." if len(key) > 32 else key
row = f"#{i:<5}{display_key:<32}"
for metric in metrics:
val = values.get(metric, 0)
if metric == "Traffic":
row += f"{format_bytes(val):>14}"
else:
row += f"{format_number(val):>12}"
# 百分比(基于第一个指标)
primary_val = values.get(primary_metric, 0)
total = totals.get(primary_metric, 1)
percentage = (primary_val / total * 100) if total > 0 else 0
row += f"{percentage:>7.1f}%"
print(row)
if len(sorted_data) > top_n:
print(f"\n ... 还有 {len(sorted_data) - top_n} 条数据")
# 打印汇总
print(f"\n汇总:")
for metric in metrics:
val = totals.get(metric, 0)
if metric == "Traffic":
print(f" 总{metric}: {format_bytes(val)}")
else:
print(f" 总{metric}: {format_number(val)}")
return data_by_dimension
def print_time_series(result, metrics: List[str], start_time, end_time):
"""打印时序数据"""
print(f"\n{'='*80}")
print(f"时序数据分析")
print(f"时间范围: {start_time.strftime('%Y-%m-%d %H:%M')} - {end_time.strftime('%Y-%m-%d %H:%M')} UTC")
print(f"采样率: {result.sampling_rate}%")
print(f"{'='*80}\n")
if not result.data:
print("暂无数据")
return
for item in result.data:
if not item.detail_data:
continue
metric = item.field_name
values = [p.value for p in item.detail_data if p.value is not None]
if not values:
continue
print(f"\n{metric} 趋势:")
print("-" * 50)
if metric == "Traffic":
print(f" 总计: {format_bytes(sum(values))}")
print(f" 平均: {format_bytes(sum(values)/len(values))}")
print(f" 最大: {format_bytes(max(values))}")
print(f" 最小: {format_bytes(min(values))}")
else:
print(f" 总计: {format_number(sum(values))}")
print(f" 平均: {format_number(int(sum(values)/len(values)))}")
print(f" 最大: {format_number(max(values))}")
print(f" 最小: {format_number(min(values))}")
# 打印最近数据点
print(f"\n 最近数据点:")
for point in item.detail_data[-10:]:
ts = point.time_stamp.replace("T", " ").replace("Z", "")
if metric == "Traffic":
print(f" {ts} {format_bytes(point.value)}")
else:
print(f" {ts} {format_number(point.value)}")
# ============================================================================
# 导出数据
# ============================================================================
def export_data(data: Dict, site_info: Dict, args, filename_prefix: str):
"""导出数据到 JSON 文件"""
output_dir = "output/esa-analytics"
os.makedirs(output_dir, exist_ok=True)
output_file = f"{output_dir}/{filename_prefix}_{site_info['site_id']}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
export_data = {
"site_id": site_info["site_id"],
"site_name": site_info["site_name"],
"query": {
"dimension": args.dimension,
"metrics": args.metric.split(","),
"hours": args.hours,
},
"start_time": site_info.get("start_time"),
"end_time": site_info.get("end_time"),
"data": data
}
with open(output_file, "w") as f:
json.dump(export_data, f, indent=2, ensure_ascii=False)
print(f"\n结果已保存到: {output_file}")
# ============================================================================
# 主函数
# ============================================================================
def main():
parser = argparse.ArgumentParser(
description="ESA 数据分析统一查询脚本",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
示例:
%(prog)s --dimension ClientCountryCode --metric Traffic
%(prog)s --dimension EdgeResponseStatusCode --metric Requests
%(prog)s --dimension ClientRequestHost --metric Traffic,Requests
%(prog)s --time-series --metric Traffic,Requests
%(prog)s --list-dimensions
%(prog)s --list-metrics
"""
)
# 查询类型
parser.add_argument("--time-series", action="store_true",
help="查询时序数据(默认查询 Top 数据)")
# 查询参数
parser.add_argument("-d", "--dimension", type=str,
help=f"查询维度,可选: {', '.join(DIMENSIONS.keys())}")
parser.add_argument("-m", "--metric", type=str, default="Traffic",
help="查询指标,多个用逗号分隔,默认: Traffic")
parser.add_argument("--hours", type=int, default=24,
help="查询时间范围(小时),默认: 24")
parser.add_argument("--limit", type=int, default=150,
help="Top 数据返回数量,默认: 150")
parser.add_argument("--top-n", type=int, default=20,
help="显示 Top N 条数据,默认: 20")
parser.add_argument("--site-id", type=str,
help="站点 ID,不指定则交互选择")
# 列出可用选项
parser.add_argument("--list-dimensions", action="store_true",
help="列出所有可用维度")
parser.add_argument("--list-metrics", action="store_true",
help="列出所有可用指标")
args = parser.parse_args()
# 列出维度
if args.list_dimensions:
print("可用维度:")
for dim, desc in DIMENSIONS.items():
print(f" {dim:<30} {desc}")
return
# 列出指标
if args.list_metrics:
print("可用指标:")
for metric, desc in METRICS.items():
print(f" {metric:<20} {desc}")
return
# 验证参数
if not args.time_series and not args.dimension:
parser.error("查询 Top 数据需要指定 --dimension 参数")
if args.dimension and args.dimension not in DIMENSIONS:
print(f"错误: 无效的维度 '{args.dimension}'", file=sys.stderr)
print(f"可用维度: {', '.join(DIMENSIONS.keys())}", file=sys.stderr)
sys.exit(1)
metrics = [m.strip() for m in args.metric.split(",")]
for m in metrics:
if m not in METRICS:
print(f"错误: 无效的指标 '{m}'", file=sys.stderr)
print(f"可用指标: {', '.join(METRICS.keys())}", file=sys.stderr)
sys.exit(1)
# 创建客户端
client = create_client()
# 获取站点
print("正在获取站点列表...")
sites = list_sites(client)
if not sites:
print("未找到任何站点", file=sys.stderr)
sys.exit(1)
site = select_site(sites, args.site_id)
print(f"已选择站点: {site.site_name}")
# 查询数据
if args.time_series:
print(f"\n正在查询时序数据 ({args.metric})...")
result, start_time, end_time = query_time_series(
client, site.site_id, metrics, hours=args.hours
)
print_time_series(result, metrics, start_time, end_time)
# 导出
result_data = {
"metrics": {},
}
for item in result.data:
if item.detail_data:
result_data["metrics"][item.field_name] = [
{"timestamp": p.time_stamp, "value": p.value}
for p in item.detail_data
]
export_data(result_data, {
"site_id": str(site.site_id),
"site_name": site.site_name,
"start_time": start_time.strftime("%Y-%m-%dT%H:%M:%SZ"),
"end_time": end_time.strftime("%Y-%m-%dT%H:%M:%SZ"),
}, args, "time_series")
else:
print(f"\n正在查询 Top {args.dimension} ({args.metric})...")
result, start_time, end_time = query_top_data(
client, site.site_id, args.dimension, metrics,
hours=args.hours, limit=args.limit
)
data = print_top_data(result, args.dimension, metrics, start_time, end_time, top_n=args.top_n)
# 导出
export_list = [
{"dimension_value": k, **v}
for k, v in data.items()
]
export_data(export_list, {
"site_id": str(site.site_id),
"site_name": site.site_name,
"start_time": start_time.strftime("%Y-%m-%dT%H:%M:%SZ"),
"end_time": end_time.strftime("%Y-%m-%dT%H:%M:%SZ"),
}, args, f"top_{args.dimension.lower()}")
if __name__ == "__main__":
main()