EdgeOne Logo
Documentation
请选择
请选择
Overview
Menu

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

通过 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;

/** 1. request 前置校验,不需要处理的 request 直接 fullback */
if (!this.checkRequest(request)) {
return;
}

/** 2. fetch 获取源文件 */
let response = null;
try {
response = await fetch(request);
} catch (err) {
const error = {
code: 'FETCH_ORIGIN_ERROR',
message: err?.message,
};
response = new Response(JSON.stringify(error), {
status: 590,
});
}

/** 3. response 校验,不需要处理的 response 直接响应客户端 */
if (!this.checkResponse(response)) {
return event.respondWith(response);
}

/** 4. 处理 apk 文件,并响应客户端 */
const { readable, writable } = new TransformStream();
this.handleStream(response, writable);

response.headers.set('Cache-Control', 'max-age=0');
const streamResponse = new Response(readable, response);

event.respondWith(streamResponse);
}

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

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

/** ATTENTION:默认取 comment 参数,如需修改参数名,请修改此处 */
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;
}

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];
}

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

const responseBody = response.body;
const encoder = new TextEncoder();

const section = encoder.encode(comment);
const writer = writable.getWriter();
const reader = responseBody.getReader();

try {
let handledBytes = 0;
while (true) {
const result = await reader.read();

if (result.done) {
console.log('WRITE_COMMENT_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);
}

await writer.ready;
await writer.write(buffer);
}
} catch (err) {
console.error('WRITE_COMMENT_ERROR: ', err);
}

try {
await writer.ready;
await writer.close();
} catch (err) {
console.error('CLOSE_WRITER_ERROR: ', err);
} finally {
writer.releaseLock();
}
}
}

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

addEventListener('fetch', handleEvent);

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

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