tencent cloud

文档反馈

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

最后更新时间:2023-12-05 17:49:33
    通过 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时,即可触发边缘函数进行动态打包。
    说明:
    
    联系我们

    联系我们,为您的业务提供专属服务。

    技术支持

    如果你想寻求进一步的帮助,通过工单与我们进行联络。我们提供7x24的工单服务。

    7x24 电话支持