论在用PHP写反向代理/CDN时遇到的坑

最近有人说我站点打开速度慢,防御还差,我就忍不住了,当场就准备写一个可以部署在空间的CDN程序

当然,在此之前,我也上GayHub搜了一下有没有现成的CDN软件,找到了一个,有兴趣的朋友可以去搜搜LayerCDN。但是为什么不用呢,因为很多功能如传递POST参数和COOKIE都没有(粗略的看了下源码看到的),于是了解了一下原理(cURL)后开始自己捣鼓

既然知道了原理,那实现起来就没有太大的难度了
打开百度,搜索php curl,开始摸索
说实话这也还是我第一次用cURL来写应用,之前有遇到要请求httpapi的项目用的都是file_get_contents,当时已经满足了条件。但是现在要做的可是CDN,单纯的获取已经满足不了需求了,何况FGC不会缓存dns,再次访问还需要解析,因此直接放弃FGC

一开始使curl的时候发现无法输出,网上查阅很久没找到解决方案,于是暂时改用FGC,这是遇到的第一个坑。后来直接用了他人的实例便可以了,如下(已化简到最简)

<?php
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL,'https://www.mcplugin.cn'.$_SERVER['REQUEST_URI']);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
echo curl_exec($ch);
curl_close($ch);

使用FGC也遇到了问题,便是没有设置Content-type,浏览器默认把css/js/图片等文件全作为html来解析,一开始想的解决方案比较麻烦,但是确实解决了

<?php
switch(end(explode('.',$_SERVER['REDIRECT_URL']))){
    case 'js': header('Content-type: text/javascript'); break;
    case 'css': header('Content-type: text/css'); break;
    case 'png': header('Content-type: image/png'); break;
    case 'gif': header('Content-type: image/gif'); break;
    case 'jpg': case 'jpeg': header('Content-type: image/jpeg'); break;
    case 'svg': header('Content-type: image/svg+xml'); break;
    case 'mp3': header('Content-type: audio/mpeg'); break;
    case 'txt': header('Content-type: text/plain'); break;
    //case '': header('Content-type: '); break;
    default: header('Content-Type: text/html');
}

后来因为遇到了PHP动态生成的验证码,做适配不太容易,因此去找了一下获取Mine-type的curl方法,还真有,于是上面的代码就注释掉了

header('Content-type: '.curl_getinfo($ch, CURLINFO_CONTENT_TYPE));

由于header需要在传输内容前设置,所以第一个curl实例需要改改

$r=curl_exec($ch);
header('Content-type: '.curl_getinfo($ch, CURLINFO_CONTENT_TYPE));
echo $r;
curl_close($ch);

但是这也解决不了我说遇到的问题,还在研究,但相对穷举这个方法无疑更加简便,效率更高


复制COOKIE也是一个重要功能,很多程序依赖于COOKIE,比如cloudflare,和PHP的SESSION,所以这个功能也是不可或缺的

下一个遇到的坑便是获取curl后源站给cdn服务器设置的cookie,一开始想用header获取的,直接设置header即可,这个方法确实一劳永逸,但是发现找到的很臃肿,于是继续摸索,找到了将cookie写进文件的方法,也就是COOKIEJAR这可是个大坑,不仅无法实现功能,而且还获取不到COOKIE
于是换个关键词继续百度php curl获取cookie
找到

curl_getinfo($ch, CURLINFO_COOKIELIST);

返回的是一个数组,每个cookie为一个元素,处理一下,并设置cookie

foreach(curl_getinfo($ch, CURLINFO_COOKIELIST) as $scookie){
    $scookie=explode("\t",$scookie);
    setcookie($scookie[5],$scookie[6],$scookie[4]);
}

有兴趣想看看是什么的话可以print_r查看

获取到并给用户设置完以后就很简单了
在用户访问cdn节点的时候判断有没有设置cookie,有就写进curl

if($_COOKIE){
    $ucookie;
    foreach($_COOKIE as $utcn=>$utcv){
        $ucookie.=$utcn.'='.$utcv.';';
    }
    curl_setopt($ch, CURLOPT_COOKIE, $ucookie);
}

至此COOKIE复制就可以了


其实POST转发应该放前面的,毕竟比较重要,但是并没有踩到什么坑,因此这样安排

if($_POST){
    curl_setopt($ch, CURLOPT_POST, 1);
    curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($_POST));
}

这就没啥好说的了,这样就可以


在实践中还碰到了资源是绝对链接的情况,也有特殊的如dz使用了base标签(一开始还搞得我一头雾水,明明源码是相对链接,点开却是绝对的),对于这种,用以下方法可以解决。
原理:将所有该站点的绝对链接换成相对

$r=str_replace('//www.mcplugin.cn','',str_replace('https://www.mcplugin.cn','',curl_exec($ch)));
echo $r;

是否采用https具体情况具体考虑


此外,不验证SSL可以提高回源速度

curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);

完整的源码待开发完后会贴出,也会开源,敬请关注

《论在用PHP写反向代理/CDN时遇到的坑》有1个想法

发表评论

电子邮件地址不会被公开。 必填项已用*标注

Captcha Code