type
status
date
slug
summary
tags
category
titleIcon
password
icon
calloutIcon
有用小项目(x) 奥卡姆剃刀(√) 计网专题实践(√)
关键词:Cloudflare Worker;DNS;Cloudns;Huggingface;CDN
需求分析
应用功能
- 在原本博客的APlayer播放器中使用自建音乐源,而非在给定平台中选取。


直接依赖分析
- 定位回博客的相关播放器代码,依赖为meting-js,可以通过更改api参数的方式更换输入(平台,歌单)。

- 定位相关源码,将api返回的json结果放入APlayer作为audio构建参数实现。

主要思路
- 实现上尽量不修改依赖的相关代码,可以通过替换api请求的响应结果实现,回到博客,查看meting-js发出的网络请求

- 可以看到返回的json中关键属性:name歌曲名,artist作曲,url歌曲来源,pic歌曲封面,lrc歌词

实现要点
- 目前为止,需要实现的如下:
- 一个存储音乐与封面的存储端
- 能够返回元信息(歌曲名,音乐url等)的后端
- 能够在内陆大部分地区DNS解析的域名
- 承载图片与歌曲文件的CDN缓存
系统设计
技术选型
存储端
- 一个存储音乐与封面的存储端,考虑大概两类音乐文件:有损mp3(约7M左右单首)与无损wav/flac(约40-50M单首),封面图只考虑avif格式,其他格式转换后再上传,暂不考虑浏览器不兼容avif下走png或jpg的fallback。参考caniuse的结果,兼容性应该不是太离谱。

乐曲大小参考,随机挑选一首在网易云查看大小

- 音乐与封面需要直接存储文件,一般可以用网盘如OneDrive,但OneDrive api并不好用。在上回部署博客加速链接时,发现一个用法Huggingface Dataset,通过将文件上传到自定义数据集中存储使用,私有存储大约100G总和上限,公开不设限,可以通过Huggingface hub的python库进行文件读取,然后结合fastapi文件响应返回回去。


后端
- 在适合白嫖(不收费+不绑信用卡+不易跑路)的所有部署方式里,了解到只有Huggingface Space比较好用(Docker部署,不限制部署语言,不会部署过程变成黑盒,2核CPU+16G内存,性能优异,Space数量好像也不限制),所以选择Huggingface Space做后端部署,可以设置私有Space然后通过Cloudflare Worker进行反代。也就是Huggingface Space + Cloudflare Worker。
域名
- 域名选择上需要确保能被Cloudflare进行托管,且在大陆地区都可访问,速度不能太离谱。
- 能被CF托管便于:
- 嫖Cloudflare的CDN缓存响应与资源
- 方便HTTPS相关:CA证书、设置TLS1.3、强制HTTPS
- 数据监控
直接只用Cloudflare Worker域名的连通情况参考(博客的评论系统挂在Vercel上,非代理非腾讯云DNS应该加载不出来):
CDN缓存
- Cloudflare有慷慨又免费的CDN提供
- 但使用前有一些注意事项:
CF Worker使用CDN必须绑自有域名
Workers deployed to custom domains have access to functionalcache
operations. So do Pages functions, whether attached to custom domains or*.pages.dev
domains.
在Playground界面不能通过预览测试,需要在实际请求中测试
However, any Cache API operations in the Cloudflare Workers dashboard editor and Playground previews will have no impact. For Workers fronted by Cloudflare Access ↗, the Cache API is not currently available.
在实现部分再进一步补充CF CDN缓存设置不成功的详细问题与解决方案
流程实现
存储端
数据预处理
- 歌曲封面直接使用png,jpeg(非xl)等格式可能大小相对比较大,而压缩前后的图片效果差别不一定太大,再者作为歌曲封面其展示在APlayer里本身也比较小,视觉效果差别不大,所以对存储的图片可以先做一次格式转换压缩。

- 在上传歌曲封面图片前,先进行avif格式转换,进行图片压缩
- 参考代码
- 实际效果:图片压缩2M → 62KB

- 音频上目前用到的均来源于油管下载,得到webm文件,通过ffmpeg转换为音频文件。
- 参考代码
- 因为下载的webm文件音频质量限制,转为flac与mp3并没有那么大差距,选择文件占用更小的mp3格式
文件大小占用效果:
从9M视频转换出个46M的音频实在过于抽象

MP3好一些,视频本身单张静态图,压缩后也不比音频占用大太多

以下比较音频图
webm视频

flac

mp3

部署实现
- 为了便于一会后端调Hub API,将元信息存放在一个文件meta.json里,包含name与artist,像lrc、url、pic需要替换为实际部署的反代服务器地址的内容,等在后端里面动态生成后再一并组装返回响应
- 数据存储可以依托Huggingface Dataset,开个数据集自行拖拽上传文件
- 如果需要实现编程上传可以开一个写token用,不再赘述
- 参考效果:


后端
- 由于需要使用Huggingface Hub API,后端部分使用fastapi实现。
- 核心思路就是调hf_hub_download下载文件到本地(Space),可以加上应用内的缓存(16G可用内存),不用每次都下载,作为FileResponse返回,然后将对应路径拼接回元信息响应中去。
- 核心思路部分参考代码
域名
- 自行选择托管到CF的域名(双向解析域名可以参考闲谈-DNS设置部分附录Cloudns为例的双向解析设置),将域名挂在Worker上。
CDN缓存
- Cloudflare CDN对带自有域名的Worker本身会按照默认规则做CDN缓存,但由于Worker作反代时,会带上Authorization认证头,按默认规则CF是不会认定这些请求(在本APP中是所有的请求)需要被cache的。实际会显示cf-cache-status: DYNAMIC,参见:

如果是别的原因导致DYNAMIC,在域名界面设置Cache Rules为Cache Everything可以解决,但Authorization认证头的请求不行。

使用Cache Everything后会显示BYPASS,仍然不能自行缓存。

- 于是转而使用Cache API,自行进行缓存
- 使用上有一些注意事项,cache api不缓存:非GET,206,Vary:*,后面的主要是对于大文件的部分内容响应,如果文件大小控制得当一般不会触发,对于会触发的情况,一会再进一步讨论
Invalid parameterscache.put
will throw an error if:
- The
request
passed is a method other thanGET
.
- The
response
passed has astatus
of206 Partial Content
↗.
- The
response
passed contains the headerVary: *
. The value of theVary
header is an asterisk (). Refer to the Cache API specification ↗ for more information.
- CF Worker核心思路部分代码
删除原本的cache-control(pragma为cache-control的旧版替补)行为,重新构造新请求作为cache key
按Request匹配响应,如果cache命中,加上自定义响应头直接返回,如果没有命中再重新请求并构造新返回响应,设置响应头MISS值与缓存相关设置
直连情况下CDN命中与MISS效果


实现效果
API得到正确的元信息响应,文件下载功能与速率均正常


API https响应速度参考

APlayer出现预期效果,正确加载音乐/封面/元信息,不过第二个歌曲名太长,作曲没有显示出来

可能改进
- 由于没有什么数据一致性的要求,目前缓存完全可以直接等待过期再重新写入实现更新,实际在后端与CDN都可以通过刷新部署的方式重置缓存,但也可以加入后端手动失效缓存,并实现从Space发送请求到Worker清空缓存。
- 可以优化缓存key,目前是按请求缓存(不同Origin,Referer属于不同请求也就是不同key)换为按URL缓存。
- 修改meting-js内部的fetch请求头改协商缓存(no-cache)为强制缓存
(注:no-cache为协商缓存,no-store才是不缓存)
Theno-cache
response directive indicates that the response can be stored in caches, but the response must be validated with the origin server before each reuse, even when the cache is disconnected from the origin server.
音乐播放器获取音乐的实际api请求发生在meting-js内部

- 支持缓存206与Vary:*,可以通过构造新的响应,删除Vary:*,改原本的状态码206为200绕开限制进行缓存部分响应,或调整Range与设置Content-Length缓存完整响应。
替换方式
闲谈
AI编程体感
- 本次代码设计上,CF Worker除开最原始的反代代码,也就是一个fetch带上Authorization认证头,里面填Huggingface的token。其他的缓存功能实现(CF Cache API)均由Gemini2.5Pro实现,实测效果>>Claude 3.7 Sonnet,要么不用调一次过,要么理解很到位,一两次迭代就能完成。
- 后端部分(Huggingface Space调用Huggingface Hub API访问Dataset,FastAPI返回元信息与文件响应)由Claude3.7 Sonnet配合人工实现,体验并不理想,Claude首先用的是Dataset的API,对数据限制太大,完全没有Hub API的自由度大,比如有train等split和按照大部分文件格式读取其他文件(比如大部分都是.flac就不好读.wav),需要明确制定使用Hub API,并且其中对于库依赖,代码逻辑不存在一次过,在没有人工干预情况下至少三轮迭代才能在不报错的情况下实现功能,上下文窗还小。
DNS设置
- 以往网站访问5开头的访问不通,在印象里是“服务器错误”,理解上也就是实际的“服务器”出现错误了,比如Java Springboot异常没有全局异常兜底服务挂掉,但其实DNS也是“服务器“,而进一步的,代理服务也是“服务器“,所以5开头的错误可能出现这样的:DNS无法解析时报 500 Internal Server Error,代理服务无法解析时报502 Bad Gateway,解析超时时保504 Gateway Timeout(这两均可能是代理服务器的DNS设置问题)。本次实践实际就遇到了上述问题,500,502,504走个遍。
- 契机是两个cloudns域名都出现过间歇性无法访问的情况,由于本身是要双向解析的域名,以前没碰过相关设置,觉得可能是设置问题或DNS传播时延,但实际上DNS传播几乎没有花什么时间,CF和Cloudns两边几乎都是设置完成就立竿见影,实际测试发现是校园网DNS阻断了河北与江苏全境的访问,更换DNS后问题不复存在。
附:校园网DNS解析结果

- 以下附Windows10DNS设置流程
注:修改DNS设置是针对本地(不使用代理)某一适配器(修改有线以太网不影响无线WLAN)生效,使用代理时是与代理服务器的DNS有关
比如使用国内DNS的代理/校园网DNS直连无法访问

使用国外DNS的代理/CF DNS直连正常访问

流程:


在联网的适配器上选择属性


默认应该是自动获得DNS服务器地址,这里已经修改过

可以上itdog找dns地址加入,也可以自行测试对应dns对对应网站的解析情况

可以自行在WLAN查看修改效果


- 以下附Cloudns双向解析域名CF托管参考流程
ALIAS比较重要,很奇特的是新注册的好像已经没有ALIAS的使用权,可以点个升级的试用就可以加ALIAS了,不需要信用卡,没有自动续费,可以观察下过期会不会撤走这条Record,以前的可以正常使用。
ALIAS写Worker的域名
NS写两条CF给的Record
_acme的是证书相关



Huggingface Space + Dataset
之前想过部署sqlite,sqlite单文件存储方便,直接备份在huggingface dataset(免费用户硬盘是不保证数据持久存储),以后可以试一试
缓存
如果在meting里做强制缓存的修改,最后可能会有三个部分的缓存,从客户端开始,浏览器缓存,CDN缓存,Space后端缓存,缓存命中与不命中的时间差别可能比较大
- 作者:CamelliaV
- 链接:https://camelliav.netlify.app/article/self-music?target=comment
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。