Angular应用调用GitHub REST API下载仓库压缩包遇CORS错误
问题背景
我在部署于GitHub Pages(绑定自定义已验证域名)的Angular应用中,尝试获取公开仓库的Zip压缩包,用于后续JSZip处理。初始调用GitHub API的代码如下:
public getTemplate() { return this.http.get( 'https://api.github.com/repos/crystal-nest/cobweb-mod-template/zipball/1.20.4', { headers: { 'X-GitHub-Api-Version': '2022-11-28' } } ); }
该URL在浏览器直接访问可正常下载,但代码执行时触发CORS拦截:
Access to XMLHttpRequest at 'https://codeload.github.com/Crystal-Nest/cobweb-mod-template/legacy.zip/refs/heads/1.20.4' (redirected from 'https://api.github.com/repos/crystal-nest/cobweb-mod-template/zipball/1.20.4') from origin 'https://crystalnest.it' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
尝试改用JSONP请求:
return this.http.jsonp('https://api.github.com/repos/crystal-nest/cobweb-mod-template/zipball/1.20.4', 'callback');
仍失败,收到JSONP错误:
{ "headers": { "normalizedNames": {}, "lazyUpdate": null, "headers": {} }, "status": 0, "statusText": "JSONP Error", "url": "https://api.github.com/repos/crystal-nest/cobweb-mod-template/zipball/1.20.4?callback=ng_jsonp_callback_0", "ok": false, "name": "HttpErrorResponse", "message": "Http failure response for https://api.github.com/repos/crystal-nest/cobweb-mod-template/zipball/1.20.4?callback=ng_jsonp_callback_0: 0 JSONP Error", "error": { "message": "JSONP injected script did not invoke callback.", "stack": "Error: JSONP injected script did not invoke callback.\n stacktrace omitted for the sake of brevity" } }
另外,我更偏好使用https://github.com/crystal-nest/cobweb-mod-template/archive/refs/heads/1.20.4.zip这个URL,它返回的压缩包命名规整,不会附加随机代码,更便于JSZip处理。
可行解决方案
1. 后端代理(生产环境首选)
由于GitHub的codeload域名不允许跨域请求,最可靠的方式是通过后端服务代理请求:
- 搭建简单后端接口或使用无服务器函数(如Cloudflare Workers、Vercel Edge Functions),由后端请求GitHub的Zip URL,再将文件内容返回给前端。
- 前端调用同域名的代理接口,避免CORS拦截。
示例Cloudflare Workers代理代码(简化版):
export default { async fetch(request) { const githubUrl = 'https://github.com/crystal-nest/cobweb-mod-template/archive/refs/heads/1.20.4.zip'; const response = await fetch(githubUrl); return new Response(response.body, { headers: { 'Access-Control-Allow-Origin': 'https://crystalnest.it', // 指定你的自定义域名 'Content-Type': response.headers.get('Content-Type') } }); } };
2. 直接触发浏览器下载(无需前端处理内容时使用)
如果不需要在前端用JSZip处理文件,只是让用户下载,可直接创建a标签跳转,绕开AJAX的CORS限制:
public downloadTemplate() { const url = 'https://github.com/crystal-nest/cobweb-mod-template/archive/refs/heads/1.20.4.zip'; const link = document.createElement('a'); link.href = url; link.download = 'cobweb-mod-template-1.20.4.zip'; document.body.appendChild(link); link.click(); document.body.removeChild(link); }
3. 临时使用公开CORS代理(仅测试用)
公开CORS代理服务可临时解决问题,但存在流量限制和安全风险,不建议生产环境使用:
将请求URL包装为代理地址,例如:
const proxyUrl = 'https://cors-anywhere.herokuapp.com/'; const targetUrl = 'https://github.com/crystal-nest/cobweb-mod-template/archive/refs/heads/1.20.4.zip'; return this.http.get(proxyUrl + targetUrl, { responseType: 'arraybuffer' });
内容的提问来源于stack exchange,提问作者CrystalSpider

