背景
趁着服务器迁移的时候,对自己的一些网站做了重新的整理。有两个问题:
域名1和域名2,我都想继续持有并使用。这两个域名我都做了备案,但是域名如果不提供服务,云服务厂商就会经常打电话给我问,说这个域名还用不用啊?如果不用的话要取消备案之类之类。
域名1,我同时在多家云服务厂商做了域名的绑定。比如腾讯云+百度云。域名1解析到了腾讯云,但是百度云也需要至少有一个2级域名提供服务,并做绑定。
针对这两个问题,解决的方式倒是简单,就是需要针对域名2,以及域名1的某个2级域名,都做一些服务的部署,保持网页可以打开的状态。
我原先的做法,就是分别再搭建一套博客系统,也不用放什么内容,就纯放着,网页能打开就行。但是,毕竟不优雅嘛,而且也要分别搞个数据库。所以我想着干脆,都基于我这个博客网站,做一下反向代理。
即类似于:打开https://domain2.com 和 https://sub.domain1.com 都显示 https://domain1.com 的内容。
方案1
最简单的方式其实是不做反向代理,而是通过DNS服务来做一些事情。
我的网站域名是通过DNSPod进行管理的,而它有个很有意思的功能,就是做url的跳转。
这个功能有两种形式,一个叫做显性url,一个叫做隐性url。显性url很简单,就是类似301、302跳转的效果一样,当我们在浏览器地址栏中输入https://domain2.com 的地址之后,自动跳转到https://domain1.com 这个地址,而地址栏中的地址也同样会发生变化。这样其实不太符合我的预期。
至于隐性url,其实是通过iframe来实现的。也就是说,在页面内点击各种链接跳转,url地址都不会发生任何变化,一直停留在 domain2.com 这个地址上。对于我想保留domain2.com 域名这个初衷来说,其实基本算是满足需要了。
但是稍微有一点小问题,就是这个domain2.com只能通过http访问,而不能通过https协议来访问,这样的话,在浏览器的地址栏中,就会出现网站非安全的小图标提示,看起来不是很爽。再加上iframe技术的应用,对网页搜索引擎的收录自然也不是很友好。
方案2
我们知道,apache、nginx这样的服务器,本身就提供了反向代理的功能。一般来说,我们是基于它做一些服务器隐藏、负载均衡等事情,但是用来做域名的停靠访问也是可以的。
以nginx为例,我们可以针对 domain2.com 去提供一个虚拟主机配置,参考下面的配置代码:
server { // 其他各种配置 location / { proxy_pass https://www.domain1.com; #sub_filter '第一个站点的名字' '第二个站点的名字'; #sub_filter_types text/html; #sub_filter_once on; } }
即,我们针对所有的url地址,全部通过代理转发到 https://www.domain1.com 这个域名下,类似于https://www.domain2.com/a/b/c 这样的路径,也同样会实际请求到 https://www.domain1.com/a/b/c 这个实际路径。
其中,如果我们想对页面中的一些文字进行替换,也可以通过sub_filter*这样的一些配置来实现。例如访问 domain1.com 的时候,显示站点名称为“站点1”,换成 domain2.com 访问的时候,显示站点名称为“站点2”。虽然这个替换配置不复杂,但是对于一些基础场景还是适用的。
这个方案比较简单,只需要有一台能够提供服务的nginx即可操作。但是我在使用的时候,又遇到了一点问题。
前面提到,我有的域名是想在百度云上停靠访问的,我百度云的服务器并不是完全可配置的nginx,而是BCH这样一个虚拟主机。百度云在虚拟主机的配置上做了很多阉割、封装,典型的就是proxy_pass这个功能无法使用!
这就有点无语了,还得另想办法。
方案3
百度云的虚拟主机,其实就等同于一个阉割版nginx+php+mysql,php和mysql的功能其实是相对完整的。那么,我干脆就基于php来实现反向代理的逻辑也可以啊!
思路也很简单,总共2个主功能:通过curl访问被代理网站的对应路径,其中path、header、body等信息,都原封不动带过去;并把响应的内容,header、body等都直接输出出来。代码可以直接参考:
$rule = [// 反向代理的目标 'upstream' => 'https://www.poisonbian.com', // 设置header中的host 'host' => 'www.poisonbian.com', // 改写规则 'rewrite' => [ // 将字符串aa替换成bb,最多改写N次。如果N=null,则表示所有的全部改写 ['aaaaaaaaaa', 'bbbbbbbbbb', null], ] ]; $host = $_SERVER['HTTP_HOST']; $timeout = 5; // seconds $request_method = $_SERVER['REQUEST_METHOD']; $request_uri = $_SERVER['REQUEST_URI']; $request_uri = str_replace('proxy_pass/index.php', '', $request_uri); $request_uri = str_replace('proxy_pass/', '', $request_uri); $request_headers = getallheaders(); $request_body = file_get_contents('php://input'); // 转发请求 $url = $rule['upstream'] . $request_uri; $headers = array(); foreach ($request_headers as $key => $value) { if ($key === 'Host') { $value = $rule['host']; } $headers[] = $key . ': ' . $value; } $ch = curl_init(); curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $request_method); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); curl_setopt($ch, CURLOPT_POSTFIELDS, $request_body); curl_setopt($ch, CURLOPT_HEADER, true); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_ENCODING, ''); // 关闭 gzip $response = curl_exec($ch); // 反向代理失败 if ($response === false) { header('HTTP/1.1 502 Bad Gateway'); header('Content-Type: text/plain'); echo 'Upstream host did not respond.'; curl_close($ch); exit(0); } $header_length = curl_getinfo($ch, CURLINFO_HEADER_SIZE); $response_headers = explode("\n", substr($response, 0, $header_length)); $response_body = substr($response, $header_length); foreach ($response_headers as $header) { $header = trim($header); if ($header === '') {continue;} if (stripos($header, 'gzip') !== false) { continue; } header($header); } foreach ($rule['rewrite'] as $rewrite) { $response_body = str_replace($rewrite[0], $rewrite[1], $response_body, $rewrite[2]); } echo $response_body; curl_close($ch);
可以看到这段代码中,除了主功能之外,也增加了str_replace的字符串替换功能,类似nginx的sub_filter相关配置。
不过在做文本替换的过程中,发现了一个小坑。刚开始的时候,无论怎么写替换函数,都没有生效。我把response打出来一看,发现是一坨乱码。仔细分析之后,发现这个是做了gzip压缩后的效果。这样的话,文本内容变得面目全非,自然也没法做替换了。
因此,我在代码中做了调整,请求被代理网站、输出页面内容的时候,都把gzip的对应header直接去掉,这样虽然牺牲了一些网页传输效率,但字符串的替换就能够正常生效了。
最后,还有一个小问题遗留下来。例如我在 https://domain1.com/a/b/c.js 这样一个静态js的地址,如果通过https://domain2.com/ 这个页面来加载,就产生了跨域请求。在原先的nginx配置中,服务器会拒绝这样的访问请求,从而导致https://domain2.com 页面中的js、css、图片、字体等各种静态资源都无法加载生效。
这样,我们该如何解决呢?留下悬念,下篇文章再讲。
本文链接:https://www.poisonbian.com/post/5056.html 转载需授权!