tencent cloud

边缘安全加速平台 EO

动态与公告
产品动态
安全公告
产品公告
产品简介
产品概述
产品优势
应用场景
EdgeOne 与 CDN 等产品功能对比
使用限制
购买指南
试用套餐体验权益说明
免费版套餐使用说明
计费概述
计费项目
购买指引
续费指引
欠费与退款说明
套餐选型对比
关于“干净流量”计费说明
DDoS 防护容量说明
快速入门
选择业务场景
快速接入网站安全加速
通过 Pages 快速部署网站
域名服务与源站配置
域名服务
HTTPS 证书
源站配置
站点加速
概述
访问控制
智能加速
缓存配置
文件优化
网络优化
URL 重写
修改头部
修改应答内容
规则引擎
图片与视频处理
单连接下载限速
DDoS 与 Web 防护
概述
DDoS 防护
Web 防护
Bot 管理
API 资产识别(Beta)
边缘函数
概述
快速指引
操作指引
Runtime APIs
示例函数
实践教程
Pages
四层代理
概述
新建四层代理实例
修改四层代理实例配置
停用/删除四层代理实例
批量配置转发规则
获取客户端真实IP
数据分析与日志服务
日志服务
数据分析
告警服务
站点与计费管理
计费管理
站点管理
版本管理
通用策略
通用参考
配置语法
请求与响应行为
国家/地区及对应代码枚举
Terraform
Terraform 简介
安装和配置 Terraform
实践教程
自动预热/清除缓存
防盗刷/盗链实践
HTTPS 相关实践
加速优化
流量调度
数据分析与告警
第三方日志平台集成实践
对象存储类源站(例如:COS)配置实践
跨域响应配置
API 文档
History
Introduction
API Category
Making API Requests
Site APIs
Acceleration Domain Management APIs
Site Acceleration Configuration APIs
Edge Function APIs
Alias Domain APIs
Security Configuration APIs
Layer 4 Application Proxy APIs
Content Management APIs
Data Analysis APIs
Log Service APIs
Billing APIs
Certificate APIs
Origin Protection APIs
Load Balancing APIs
Diagnostic Tool APIs
Custom Response Page APIs
API Security APIs
DNS Record APIs
Content Identifier APIs
Legacy APIs
Ownership APIs
Image and Video Processing APIs
Multi-Channel Security Gateway APIs
Version Management APIs
Data Types
Error Codes
常见问题
产品特性相关问题
DNS 记录相关问题
域名配置相关问题
站点加速相关问题
数据与日志相关问题
安全防护相关问题
源站配置相关问题
排障指南
异常状态码参考
EdgeOne 4XX/5XX 状态码排障指南
520/524状态码排障指南
521/522 状态码排障指南
工具指南
相关协议
Service Level Agreement
源站防护启用特别约定
TEO 政策
隐私协议
数据处理和安全协议
联系我们
词汇表

步骤二:EdgeOne 边缘函数写入渠道信息到 APK 包

PDF
聚焦模式
字号
最后更新时间: 2025-12-26 15:32:18
通过 EdgeOne 边缘函数,我们可以动态地将渠道信息写入到 APK 包内。用户只需访问与边缘函数绑定的域名并进行触发配置,就可以触发该边缘函数,从而实现 APK 的动态打包和加速分发。

步骤1:添加用于加速分发的加速域名

请根据 添加加速域名 指引添加加速域名,例如:www.example.com 且源站配置为 Android APK 母包所在的对象存储 COS 自定义域名,详情可参见 存储桶切换自定义域名 。配置如下图所示:

说明:
该域名将用于访问下载 APK 安装包。

步骤2:创建用于触发渠道信息写入的边缘函数

1. 根据 函数管理 指引创建一个边缘函数,将如下代码复制到函数代码内。
const CUSTOM_BLOCK_VALUE_LENGTH = 10240;
const APK_SIGNING_BLOCK_MAGIC_LENGTH = 16;
const APK_SIGNING_BLOCK_OFFSET_LENGTH = 8;

const APK_COMMENT_LENGTH = 512;

class EdgePack {
totalSize;
signVersion;
centralDirectoryOffset;
customBlockValueStart;
customBlockValueEnd;
rangeRelativeOffset;
customInfo;

constructor() {
this.totalSize = null;
this.signVersion = null;
this.centralDirectoryOffset = null;
this.customBlockValueStart = null;
this.customBlockValueEnd = null;
this.rangeRelativeOffset = null;
this.customInfo = null;
}

async handle(event) {
const { request } = event;

const headers = new Headers(request.headers);

const modifiedRequest = new Request(request, { headers });

if (!this.checkRequest(modifiedRequest)) {
return;
}

let response = null;
try {
const headRequest = new Request(modifiedRequest.url, {
method: 'HEAD',
headers: modifiedRequest.headers,
});
response = await fetch(headRequest);
} catch (err) {
const error = {
code: 'FETCH_ORIGIN_ERROR',
message: err?.message,
};
response = new Response(JSON.stringify(error), {
status: 590,
});
}

if (!this.checkResponse(response)) {
return event.respondWith(response);
}

response.headers.set('Cache-Control', 'max-age=0');

const streamResponse = new Response(
await this.combineStreams(modifiedRequest),
response
);

event.respondWith(streamResponse);
}

getRelativeOffset(response) {
const start = this.customBlockValueStart;
const end = this.customBlockValueEnd;

const range = response.headers.get('Content-Range');

if (!range) return start;

const match = range.match(/bytes\\s*(\\d*)-(\\d*)/i);
if (!match || match?.length < 2) {
return start;
}

if (+match[2] < start || +match[1] > end) {
return null;
}

return start - +match[1];
}

checkRequest(request) {
if (request.method !== 'GET') {
return false;
}

if (request.headers.has('Range')) {
return false;
}

const { pathname, searchParams } = new URL(request.url);

const comment = searchParams?.get('comment');

if (!pathname.endsWith('.apk') || !comment) {
return false;
}

this.customInfo = comment;
return true;
}

checkResponse(response) {
if (response.status !== 200 && response.status !== 206) {
return false;
}

const contentLength = response.headers.get('Content-Length');

if (response.body === null || contentLength === null) {
return false;
}

this.totalSize = Number(contentLength);

const cosOffsetHeader = response.headers.get('x-cos-meta-edgepack-offset');
const cosTypeHeader = response.headers.get('x-cos-meta-edgepack-type');

if (!cosOffsetHeader || !cosTypeHeader) {
return false;
}

this.signVersion = cosTypeHeader;
this.centralDirectoryOffset = Number(cosOffsetHeader);

if (this.signVersion === 'v1') {
this.customBlockValueStart = this.totalSize - APK_COMMENT_LENGTH;
this.customBlockValueEnd = this.totalSize;
} else {
this.customBlockValueStart =
this.centralDirectoryOffset -
CUSTOM_BLOCK_VALUE_LENGTH -
APK_SIGNING_BLOCK_MAGIC_LENGTH -
APK_SIGNING_BLOCK_OFFSET_LENGTH;
this.customBlockValueEnd = this.centralDirectoryOffset;
}

this.rangeRelativeOffset = this.getRelativeOffset(response);

if (this.rangeRelativeOffset === null) {
return false;
}

return true;
}

async combineStreams(request) {
const { readable, writable } = new FixedLengthStream(this.totalSize);
this.handleStream(request, writable);
return readable;
}

async handleStream(request, writable) {
const comment = this.customInfo;
const relativeOffset = this.rangeRelativeOffset;

const encoder = new TextEncoder();
const section = encoder.encode(comment);

try {
const apkHeader = await this.apkHeaderStream(request);

try {
await apkHeader.pipeTo(writable, {
preventClose: true,
});
} catch (e) {
console.error('HEADER_STREAM_ERROR: ', e);
}

// 返回的是Blob数据
const apkBody = await this.apkBodyStream(
request,
section,
relativeOffset
);

const apkBodyStream = apkBody.stream();

try {
await apkBodyStream.pipeTo(writable, {
preventClose: true,
});
} catch (e) {
console.error('BODY_STREAM_ERROR: ', e);
}

const apkTail = await this.apkTailStream(request);

try {
await apkTail.pipeTo(writable, {
preventClose: true,
});
} catch (e) {
console.error('TAIL_STREAM_ERROR: ', e);
}
} catch (err) {
console.error('HANDLE_STREAM_ERROR: ', err);
} finally {
let writer = writable.getWriter();
writer.close();
writer.releaseLock();
}
}

async apkHeaderStream(request) {
const headers = new Headers(request.headers);
headers.set('Range', `bytes=0-${this.customBlockValueStart - 1}`);

// 获取签名块之前的部分
const headResponse = await fetch(request, {
headers: headers,
});

return headResponse.body;
}

async apkBodyStream(request, section = null, relativeOffset = 0) {
const headers = new Headers(request.headers);
headers.set(
'Range',
`bytes=${this.customBlockValueStart}-${this.customBlockValueEnd - 1}`
);

const middleResponse = await fetch(request, {
headers: headers,
});

const reader = middleResponse.body.getReader();

let outputBuffers = [];
try {
let handledBytes = this.customBlockValueStart;
while (true) {
const result = await reader.read();

if (result.done) {
console.log('APK_BODY_STREAM_DONE');
break;
}

const startByteOffset = handledBytes;
const buffer = result.value;
handledBytes += buffer.byteLength;

const min = Math.max(startByteOffset, relativeOffset);
const max = Math.min(relativeOffset + section.byteLength, handledBytes);

if (min < max) {
const bufferStart = min - startByteOffset;
const sectionStart = min - relativeOffset;
const sectionEnd = max - relativeOffset;

const replacement = section.subarray(sectionStart, sectionEnd);

new Uint8Array(buffer).set(replacement, bufferStart);
}

outputBuffers.push(buffer);
}
} catch (err) {
console.error('APK_BODY_STREAM_ERROR: ', err);
}
return new Blob(outputBuffers);
}

async apkTailStream(request) {
const headers = new Headers(request.headers);
headers.set(
'Range',
`bytes=${this.customBlockValueEnd}-${this.totalSize - 1}`
);

const tailResponse = await fetch(request, {
headers: headers,
});

return tailResponse.body;
}
}

async function handleEvent(event) {
const edgepack = new EdgePack();
await edgepack.handle(event);
}

addEventListener('fetch', handleEvent);
2. 完成部署函数后,根据 函数管理 指引配置触发规则,其 HOST 值为 步骤1 创建的加速域名,如下所示:

3. 单击确定,即可完成触发规则的创建。用户访问域名 www.example.com 且文件后缀为.apk时,即可触发边缘函数进行动态打包。

步骤3:验证 EdgeOne 边缘函数是否已将渠道信息写入到 Android APK 包

在浏览器中输入带有渠道信息的 URL,例如: http://www.example.com/v2_src.apk?comment=test 即可触发边缘函数,动态地将渠道信息注入到指定位置,其中 comment 为您在 创建用于触发渠道信息写入的边缘函数 中定义的渠道参数。以 v2-VasDolly 方式为例,可以使用 VasDolly 多渠道打包工具来读取动态注入的渠道信息:





帮助和支持

本页内容是否解决了您的问题?

填写满意度调查问卷,共创更好文档体验。

文档反馈