创世神域

Nothing is impossible.

@Puteulanus1年前

06/2
22:30
学习心得

在 DO 的 Centos 7 上运行 OpenVZ 容器

首先安装 EPEL,openvz-release 里的有些软件包的依赖默认源没有:

yum install epel-release -y

然后安装 openvz-release:

yum localinstall -y https://download.openvz.org/virtuozzo/releases/openvz-7.0.3-479/x86_64/os/Packages/o/openvz-release-7.0.3-3.vz7.x86_64.rpm

导入 openvz-release 的 GPG key:

rpm --import http://repo.virtuozzo.com/vzlinux/security/VZLINUX_GPG_KEY

升级一下软件包,这一步很重要,特别 openvz-release 的版本得更新:

yum update -y

然后安装 OpenVZ 的内核和基础软件包:

yum install -y vzkernel prlctl prl-disp-service

装完 reboot 重启让系统用新内核跑起来。

OpenVZ 7(Virtuozzo) 要求 /vz 必须是 ext3 或者 ext4 的,重启之后首先把服务停下来:

systemctl stop vz

将原来 /vz 的东西备份:

mv /vz /vz_bak

建立挂载用的 ext4 镜像:

dd if=/dev/zero of=disk.img bs=10M count=1024

建立文件系统:

mkfs.ext4 -F disk.img

创建挂载点:

mkdir /vz

挂载镜像到 /vz:

mount -t ext4 -o loop disk.img /vz

复制原 /vz 目录内的文件到新目录:

cp -r /vz_bak/* /vz/

启动服务:

systemctl start vz

创建容器磁盘的时候需要一些内核模块,将他们加载:

modprobe ploop
modprobe pfmt_ploop1
modprobe pfmt_raw
modprobe pio_direct

查看一下哪些系统模版可用:

vzpkg list --available

安装 Centos 7 的系统模版:

vzpkg install template centos-7-x86_64

创建容器:

prlctl create test --vmtype ct

启动容器:

prlctl start test

在 DO 的 Centos 7 上运行 OpenVZ 容器

@Puteulanus1年前

05/24
14:53
学习心得

OSX 使用 Parallels Desktop 单机 LEDE 软路由

用了一段时间了,顺便记录一下。

跟 VRouter 其实是一个意思,但 VRouter 我死活启动不了,只能自己动手搞个了。

首先下载 LEDE x86 的镜像,解压(系统自带的解压双击就行,命令行下是用 gunzip),后缀名改为 hdd。

然后在 PD 里新建一个空白虚拟机,系统类型选择其他 Linux。

配置可以视情况分配,我是给的 256M 内存。记得将启动和关机设为“始终在后台准备就绪”,这样即使 PD 软件关闭之后或者电脑重启之后,虚拟机也会在后台运行。

删除默认的硬盘、CD驱动器、USB 等设备,添加一个新硬盘,选择刚刚解压出来的 hdd 文件。PD 会提示需要转换格式,确认转换。

保存之后创建虚拟机。

关闭虚拟机,然后在 PD 的软件设置中(注意不是虚拟机的设置)新建一个 host only 的网络,配置如图:

因为 LEDE x86 默认设置的 IP 地址是 192.168.1.1,如果家里网络是 192.168.1.x 网段的可能需要把 WiFi 先断开来避免冲突。

保存设置后进入新建的虚拟机的设置,把网络模式改为刚刚创建的网络。

保存后再次启动虚拟机,然后就可以在浏览器中访问 192.168.1.1 看到 LEDE 的网页了,默认密码为 koolshare。

首先修改网卡的地址,避免跟主机 IP 产生冲突。

保存之后关闭虚拟机,修改刚才新建的 host only 的网络中的 DHCP 设置为对应的网段,并且勾选“在系统偏好设置中显示”。

然后进入虚拟机设置,为虚拟机新增一张网卡,模式选择为桥接 WiFi 网卡。

然后重新打开虚拟机,访问新的 IP 地址进入管理界面。

修改默认 LAN 口的设置,在物理设置中取消 eth0 以外的所有勾选。

然后新建一个 WAN 接口,使用 DHCP 客户端模式,勾选 eth1 接口。

保存后确认 WAN 口获取到了上级路由器分配的 IP 地址:

在 Mac 的 系统偏好设置-网络 中,找到虚拟机的网络,在空白的路由器栏填上 LEDE 虚拟机的 IP 地址。

然后只需要在 Terminal 中执行 sudo route change default 192.168.66.1 ,将虚拟机设为默认网关,就可以临时以虚拟机为软路由访问网络了。

不过网关设置在网络发生变化(比如 WiFi 断开和重连之后)都会被更新,如果希望长期默认使用虚拟机软路由作为网关的话,可以修改系统网络设置中的服务顺序:

将创建的网络接口拖至第一位,即可将它作为默认网关。

OSX 使用 Parallels Desktop 单机 LEDE 软路由

@Puteulanus4年前

02/18
02:39
学习心得

Windows下搭建带有缓存与广告过滤功能的分流代理

之前折腾了一下基友送的软路由,把代理和DNS防污染查询都移到了上边。代理部分经过几天摸索踩了不少坑,包括Polipo莫名其妙的卡,adbyby和Privoxy过滤广告卡网等等。下边是已经使用了一段时间的解决方案。

首先,下载TcpRoute2

TcpRoute2是类似COW的分流代理,这里用它来完成核心的代理客户端及分流功能。

把配置文件末尾的example去掉,然后按照提示修改配置文件。

我的配置是:

addr="127.0.0.1:7070"
PreHttpPorts=[0,]
PreHttpsPorts=[0,]

[[UpStreams]]
Name="direct"
ProxyUrl="direct://0.0.0.0:0000"
DnsResolve=true
[[UpStreams.Whitelist]]
Path="https://raw.githubusercontent.com/GameXG/TcpRoute2/master/direct.txt"
UpdateInterval="24h"
Type="suffix"
[[UpStreams.Blacklist]]
Path="direct/blacklist.txt"
UpdateInterval="24h"
Type="suffix"

[[UpStreams]]
Name="SS-HK"
ProxyUrl="ss://aes-256-cfb:password@ip:port"
DnsResolve=false
Credit=0
Sleep=200
CorrectDelay=75
[[UpStreams.Whitelist]]
Path="ss-hk/whitelist.txt"
UpdateInterval="24h"
Type="suffix"
[[UpStreams.Blacklist]]
Path="ss-hk/blacklist.txt"
UpdateInterval="24h"
Type="suffix"

[[UpStreams]]
Name="SS-SG"
ProxyUrl="ss://aes-256-cfb:password@ip:port"
DnsResolve=false
Credit=0
Sleep=200
CorrectDelay=75
[[UpStreams.Whitelist]]
Path="ss-sg/whitelist.txt"
UpdateInterval="24h"
Type="suffix"
[[UpStreams.Blacklist]]
Path="ss-sg/blacklist.txt"
UpdateInterval="24h"
Type="suffix"

[[Hosts]]
Path="hosts.txt"
Type="base"

[[Hosts]]
Path="http://pgl.yoyo.org/adservers/serverlist.php?hostformat=hosts&showintro=0&mimetype=plaintext"
Type="base"
UpdateInterval="24h"

因为是5.0版的时候写的配置,可能会有一些缺少的新特性的参数。我为直连、SS-HK以及SS-SG各自建立了一个文件夹,方便在需要时对黑白名单进行配置。yoyo的这个hosts是在源头对一些广告服务器进行屏蔽,这样这些广告的请求压根不会产生网络流量。

配置完成之后启动TcpRoute2Windows.exe并把它设为开机自动启动,TcpRoute2就开始监听7777端口了。

TcpRoute2和SS一样提供socks5代理,不过一些程序,特别是OSX上对socks5支持没HTTP代理那么好。所以在下面可以用Privoxy来做一层转化。

下载Privoxy,安装。

修改配置文件为:

listen-address 127.0.0.1:8117
forward-socks5 / localhost:7070 .

保存。之后运行Privoxy,这样Privoxy就开始监听8117端口提供HTTP代理了。

Privoxy是一个轻量级的代理软件,虽然可以使用规则对代理内容进行过滤(比如adbyby就是它的延伸版本),但实际测试中会有很严重的性能问题。所以这里只是单纯将它作为协议转换来使用,更加复杂的功能交给squid来完成。

下载squid for windows(XP比较方便的就只有预编译的2.7了,Win7或以上64位的可以下3.5的安装包),同时下载SoftPerfect RAM Disk。因为要加缓存,会对储存介质造成比较频繁的读写。用SoftPerfect RAM Disk建立一个RAM Disk可以防止长期读写对硬盘造成伤害,如果觉得无所谓的可以就用硬盘来做缓存目录。。

将squid文件夹移到C盘下,进入etc目录去掉配置文件的后缀。编辑squid.ini,找到:

#Default:
http_access deny all

改完:

#Default:
acl ads dstdom_regex "C:\squid\share\ads.txt"
http_access deny ads
http_access allow all

然后在配置文件尾部加上

header_access Via deny all
header_access X-Forwarded-For deny all
http_port 8118
cache_peer 127.0.0.1 parent 8117 0 no-query default
never_direct allow all
cache_dir ufs r:\ 120 16 256

保存,下载 广告过滤规则 并存为C:\squid\share\ads.txt。

header_access的两条作用是阻止squid将你的内网IP放在HTTP头中发送出去,cache_peer是上级代理,也就是Privoxy的HTTP代理的地址和端口,cache_peer后面的r:\和120是缓存目录的路径与大小。

使用cmd运行 C:\squid\sbin\squid -i 来安装squid的服务。然后使用SoftPerfect RAM Disk新建一个开机自动挂载的RAM Disk,大小130M,盘符为r。

新建一个批处理,内容为:

C:\squid\sbin\squid.exe -z
sc start Squid

并在SoftPerfect RAM Disk的工具→应用程序中添加这个批处理。因为缓存目录是RAM Disk,squid必须在内存盘挂载之后才可以启动。这个批处理负责在SoftPerfect RAM Disk挂载r盘之后,对它进行初始化,并启动squid服务。

最后,将 127.0.0.1:8118 设为IE浏览器的代理,大部分支持HTTP代理的软件就会自动读取IE浏览器的代理设置来使用代理了。

TcpRoute2会选择所有线路中最快的一条(或者直连)来进行连接,可以是多台上级SS选择最优线路,也可以是亚洲的主代理和搬瓦工的应急备用代理做故障转移。默认的直连白名单中包含了大量使用海外IP无法正常访问的国内网站(比如网易云音乐),如果在使用中出现不需要走代理的网站被走了代理,只需要将它添加进直连的白名单中就可以了。

yoyo的squid规则比hosts范围要大很多,使用代理时包括大部分国内网站的广告都会被过滤掉。而squid强大的缓存功能在内网会压根感觉不到延迟,开过一次的网站再次打开时加载速度会非常的快。

广告过滤也可以使用Adguard来完成,它支持设置上级代理以及开启自身的HTTP代理,只需要在squid与Privoxy之前再加一层HTTP代理转发就可以了。理论上它应该是比yoyo的规则更好用,不过我那台x86的软路由实在装不下.NET,只能作罢。

在目标网页无法显示的时候,会显示Privoxy的错误页。和我一样强迫症感觉那个页面丑Privoxy\templates\no-server-data 改完自己喜欢的萌化错误页模版就可以了。

Windows下搭建带有缓存与广告过滤功能的分流代理

@Puteulanus4年前

07/16
01:28
学习心得

dtcp——tcp转发调试工具

虚拟机因为分辨率的问题,开sokit字体显示极度蛋疼,搜了一下又没找到合适的替代品,干脆写了一个。

dtcp是debug tcp的缩写,功能类似于sokit的转发器,可以在客户端与服务器之间做中转,将通信内容详细的展示出来。

显示方式参照了sokit原文+16进制的形式,在多线程连接时可以使用-v详细模式显示每条信息所属的隧道,还有-t选项显示每条信息的时间。

使用-h或–help参数查看所有参数及其作用,其中-s或–srv【本地监听地址及端口】、-d或–dst【远程地址和端口】为必填项,本地不指定地址时默认监听0.0.0.0。

还有个并没有什么卵用的-b参数可以设定两头socket的buff大小。

项目放在Github,设计的基本功能已经实现完了,现在正在各种修bug,欢迎反馈问题。

dtcp——tcp转发调试工具

@Puteulanus5年前

04/5
22:26
学习心得

PHP监控Phantasien是否补货

才不是我要买=。=

之前获取两个领结的补货情况,直接用file_get_contents获取的网页。这次7个,感觉阻塞获取网页有点没效率了。。

Socket非阻塞模式代码参考的 这里 ,做了一些更改。

通知部分准备直接用现有的监控服务。网页会在检测到补货的时候404。只要在这个页面上加上监控宝或者D监控一类的就可以在补货的时候得到邮件/短信/微信的通知了。

知更鸟的代码高亮悄无声息的没了什么鬼,直接贴上来缩进全挂了。。

代码:

<?php
// 设定监控地址
$url_list = array(
'http://p-bandai.jp/phantasien/PHItem/PHTieRibbon/item-1000081400/',
'http://p-bandai.jp/phantasien/PHItem/PHTieRibbon/item-1000079639/',
'http://p-bandai.jp/phantasien/PHItem/PHTieRibbon/item-1000079779/',
'http://p-bandai.jp/phantasien/PHItem/PHTieRibbon/item-1000079643/',
'http://p-bandai.jp/phantasien/PHItem/PHTieRibbon/item-1000079644/',
'http://p-bandai.jp/phantasien/PHItem/PHTieRibbon/item-1000078613/',
'http://p-bandai.jp/phantasien/PHItem/PHTieRibbon/item-1000015407/',
);
// 发送请求
$timeout = 10;
$socket = array();
foreach($url_list as $url){
$url = parse_url($url);
$ip = gethostbyname($url['host']);
$port = ($url['scheme'] == 'https') ? 443 : 80 ;
$sh = stream_socket_client("${ip}:${port}", $errno, $errstr, $timeout, STREAM_CLIENT_ASYNC_CONNECT|STREAM_CLIENT_CONNECT);
usleep(10);
if ($sh) {
$sockets[] = $sh;
$http_header = "GET ${url['path']} HTTP/1.1\n";
$http_header .= "Host: ${url['host']}\n";
$http_header .= "Accept: */*\n";
$http_header .= "Accept-Charset: *\n";
$http_header .= "\n";
fwrite($sh, $http_header);
} else {
header('HTTP/1.1 404 Not Found');
header("status: 404 Not Found");
echo "Stream failed to open correctly.";
exit;
}
}
// 接收返回
$result = array();
$read_block_size = 16384;

while (count($sockets))
{
$read = $sockets;
$n = stream_select($read, $w=null, $e=null, $timeout);
if (count($read))
{
foreach ($read as $r)
{
$id = array_search($r, $sockets);
$data = fread($r, $read_block_size);
if (strlen($data) == 0)
{
//echo "Stream {$id} closes at " . date('h:i:s') . ".";
fclose($r);
unset($sockets[$id]);
} else {
if (!isset($result[$id])) $result[$id] = '';
$data = explode("\n\r",$data,2);
$result[$id] .= $data[1];
}
}
} else {
echo "Time-out!";
break;
}
}
// 判断可购买数量
$can_buy = false;
$list = array();
foreach($result as $id => $html){
$html = explode('</head>',$html,2);
$html = $html[0];
preg_match('/ordermax_list = {(.|\n)+?"\w+":(?<n>\d+)/',$html,$n);
$n = $n['n'];
if((bool)$n){
$can_buy = true;
$list[] = $url_list[$id];
}
}

if($can_buy){
header('HTTP/1.1 404 Not Found');
header("status: 404 Not Found");
var_dump($list);
}

话说这个和上一个Heroku空间不能一起使用,或者说监控地址不能使用ngrok的,否则无法输出404的头。

PHP监控Phantasien是否补货

@Puteulanus5年前

04/2
19:27
学习心得

在Heroku上运行签到站

2015年5月22日更新:

1.使用www.tunnel.org.cn新加坡节点作为ngrok服务器

2.增加cron脚本使用的搜图服务避免抽风

3.在每轮循环中访问Heroku地址,避免搜图抽风时需要执行的程序不执行

4.运行脚本增加每小时重启ngrok的功能,增强稳定性

5.替换需要登录的OSC仓库为Github

注意:

由于之前的ngrok服务已经失效,ngrok地址改为 https://名字.phiui.com

脚本只是辅助防休眠,自己给Heroku地址加个云监控更靠谱

由于自动休眠的存在,算是非稳定服务,而且本身Heroku自己的地址有些地方访问很慢甚至被墙,不建议作为提供公众服务的网站使用

 

最近啃了几个Heroku的buildpack,对它的环境稍稍有了些了解,试着改了改官方的buildpack-php,加上了ngrok和防休眠。

Heroku使用git部署代码,最方便的解决方案是在Cloud9上部署应用。

先来说说主要的坑吧,看教程的可以跳到下面去。

Heroku因为不限流量和原生支持https,早年貌似被大量挂过代理,导致现在一些地方访问和Openshift一样是被墙的。我修改了官方的 heroku-buildpack-php ,在安装脚本中加入了ngrok(参考的 heroku-buildpack-ngrok ),使用 Tunnel 的服务让网站出口变为国内。这样好处是,国内访问基本没问题了,而且不使用Heroku自己的Web服务可以顺便避开官方对每个IP在一段时间第一次访问投放的广告。坏处也有,没法绑定自己的域名了,只能使用Tunnel的二级域名。

Heroku不像Openshift可以直接把代码传到空间里去,代码的储存和运行是分离的。Web应用没有访问的话1小时会自动休眠,可以靠Web访问唤醒。休眠的时候实际上运行环境是已经被删除掉了的,在唤醒时会从代码储存空间拉取代码重新部署。所以Heroku实际上是介于本地可写和本地不可写之间,一方面临时文件什么的存在本地完全没有问题,另一方面如果是重要的配置文件存在运行空间中的,一旦应用休眠或者后期有更改重新git push了代码的话,文件就丢失了。

为了防止应用休眠,我在buildpack的服务脚本加了一段循环,每隔三分钟以应用的Url为参数访问一次Bing搜图。这样主要是避免Heroku过滤掉自己访问自己的流量。Bing搜图会把应用主页当成原图去请求,用heroku logs可以看到每次访问的IP还不一样。

应用创建完可以用heroku run bash登录到运行空间查看上面的文件,不过这也算是个坑,实际上查看的应该是类似运行空间的镜像的东西,你做的修改同样不会被保存。其实还有貌似buildpack在部署的时候貌似和运行也不是一个环境,这个是在啃别人的buildpack的时候学着写的。

嗯还有个,MySQL的Add-on要绑定信用卡才能用。这个没办法靠应用内搭建的方法来解决,毕竟运行空间的数据终究是临时的,要是在应用内自己运行MySQL服务一旦应用重启整个数据库就没了。。
Read More →

在Heroku上运行签到站

@Puteulanus5年前

02/1
23:15
学习心得

10.10解决WiFi不稳定问题

更新10.10之后,开始频繁出现盒盖休眠之后开盖搜不到WiFi、只搜到我的WiFi连上了却上不了网一类的情况,刚开始以为是个例没在意,后来才知道WiFi不稳定是很多人更新10.10之后都遇到了的。

再后来看到 这篇帖子 ,因为自己用不到AirDrop所以毫不犹豫的禁用了awdl0,然后就把这事忘了。

直到这两天又开始出现这问题,这才想起之前设置之后好像WiFi问题的确没出现过了。至于为啥现在又出现了,怎么想也只有一个可能——前几天更新10.10.2又给我打开了。。开terminal一看,果然awdl0又是active状态。

果断执行

sudo ifconfig awdl0 down

又给禁用回去了。

网上说10.10.2已经解决了WiFi的问题,我是完全没觉得,不知道是不是个例。如果和我症状一样的朋友可以试试这个方法。

要重新启用awdl0也很简单,执行

sudo ifconfig awdl0 up

就行了。

10.10解决WiFi不稳定问题

@Puteulanus5年前

11/28
19:46
学习心得

抓取多看每日免费书籍的相关信息

那天看到V2EX上推荐一本限免的Linux相关的书籍,跑去收了之后想到做这个。免费的书收太多会把好书淹没在里面,反而不会去看了,而且每天跑去收也麻烦。既然如此,那干脆做个程序每天抓取免费书籍的信息然后发自己邮件好了,喜欢的话就就去多看收,不喜欢的话就不管了。

<?php
// 载入PHPMailer类 
require_once('class.phpmailer.php'); 
require_once('class.smtp.php');
// 抓取多看免费图书信息
$content = file_get_contents('http://www.duokan.com/');
preg_match('/<a .*href\s*="(.+?)".+alt="限时免费".+<\/a>/',$content,$result);
$content = file_get_contents('http://www.duokan.com'.$result[1]);
preg_match('/<div class="desc">(([\s]|.)+?)<\/table>/',$content,$result);
preg_match('/<h3>(.+)<\/h3>/',$result[1],$name);
$name = $name[1];
preg_match_all('/<tr>(([\s]|.)+?)<\/tr>/',$result[1],$result);
foreach($result[1] as $key){
$info .= preg_replace('/(<.+?>)|\s/','',$key).'<br/>';
}
preg_match('/<article.+id="book-content"(([\s]|.)+?)<\/article>/',$content,$result);
$content = preg_replace('/(<.+?>)/','',$result[0]);

// 调用PHPMailer类发送邮件
$mail = new PHPMailer(); //实例化 
$mail->IsSMTP(); // 启用SMTP 
$mail->Host = "smtp.exmail.qq.com"; //SMTP服务器 
$mail->Port = 25;  //邮件发送端口 
$mail->SMTPAuth   = true;  //启用SMTP认证 
 
$mail->CharSet  = "UTF-8"; //字符集 
$mail->Encoding = "base64"; //编码方式 
 
$mail->Username = "ex@mple.com";  //你的邮箱 
$mail->Password = "passwprd";  //你的密码 
$mail->Subject = "多看免费书:{$name}"; //邮件标题 
 
$mail->From = "ex@mple.com";  //发件人地址(也就是你的邮箱) 
$mail->FromName = "送书女仆";  //发件人姓名 
 
$address = "my@email.com";//收件人email 
$mail->AddAddress($address, "主人");//添加收件人(地址,昵称) 

$mail->IsHTML(true); //支持html格式内容 
$mail->Body = "亲爱的主人,今天的免费图书是酱紫的哦~<br/><br/>{$name}<br/>{$info}<br/>{$content}"; //邮件主体内容 
//发送 
if(!$mail->Send()) { 
  echo "Mailer Error: " . $mail->ErrorInfo; 
} else { 
  echo "Message sent!"; 
} 

设定成每天一次Cron就行。

PHP发邮件部分的代码直接用了 这里的 。调用了PHPMailer的类来使用SMTP发送邮件,关于PHPMailer详见 其github页

抓取多看每日免费书籍的相关信息

@Puteulanus5年前

11/25
03:46
学习心得

WordPress使用Redis的全站刷新

最近发现采集站访问速度慢的坑爹,排查了一下排除了本地性能瓶颈,最后确定是跟数据库服务器之间的延迟太大了。。。

对付这问题没啥好办法,只能上缓存,缓存期间访问页面直接返回不经过数据库。参照 免费资源部落 的教程给俩采集站加上了Redis缓存,效果拔群。不过对于教程中缓存刷新的问题,总觉得有点问题。

教程中是使用 */10 * * * * curl http://www.freehao123.com/?r=y 来进行首页的缓存刷新,但是首页刷新了,第二页不是应该还缓存着的?这样一旦有了新日志,从首页被挤到了第二页,而第二页的缓存又不刷新,岂不是消失了?

清除所有缓存的功能只在登陆后的后台可用,无法做到自动全部清理,这点对采集站来说无疑是不方便的。我读了下 优化版本的前端PHP脚本 的代码,把清理全部缓存的功能剥离了出来,单独做了个文件放在Wordpress根目录下。【不得不说作者注释详细得相当良心】

<?php
// init predis
include("predis.php");
$redis = new Predis\Client('');

$domain = $_SERVER['HTTP_HOST'];
$dkey = md5($domain);

if ($redis->exists($dkey)) {
$redis->del($dkey);
echo 'domain cache flushed';
} else {
echo 'no cache to flush';
}

?>

只要在crontab里设定定时用curl访问这个页面,就能做到定时清理整站缓存了。

WordPress使用Redis的全站刷新

@Puteulanus5年前

11/12
13:53
学习心得

TPRSS——看图没有国界

TPRSS,即Twitter Picture RSS,是一个能将指定用户的推文转化为RSS,并反代其中的图片的网站。

项目起源于一次聊天,我给群里的朋友发了一些Tumblr的RSS地址,顺便推荐了几个推特的发图机器人。和Tumblr可以直接用RSS阅读器看图相比,Twitter的就略不方便了。上网搜了一下,找到了twitrss.me,可以简单的把推文转化为RSS,使用Perl写成并开源。twitrss.me的一个问题是它不会输出图片,订阅之后也得点链接才能看到图,因为对Perl完全不熟,于是用PHP写了一个介于twitrss.me和用户之间的“中间层”,负责从twitrss.me获取信息,并将所有链接转化为真实图片地址,加上标签直接输出。这是TPRSS的雏形。

最先遇到的问题是有时订阅会失败,排查后发现原因是RSS阅读器的获取是有时间限制的。按最初的设计,阅读器在访问一个全图订阅时会依次抓取每条推文中网址的页面,然后通过正则表达式匹配得到真实图片的地址。也就是说,阅读器每次刷新,需要等待服务器依次下载10个网页并对其内容进行匹配。这样做非常耗时,导致阅读器在获取订阅内容时超时。为了解决这个问题,我增加了数据库部分,在受到访问时,会把用户推文中的地址与真实图片地址存入数据库中,再次访问时从数据库中调取记录,存在记录的直接读取,不存在的记录才会抓取网页获取。比起最初的设计,这样大幅度降低了重复访问时的响应时间。只有在新订阅用户或长时间没有对某用户的数据库进行更新时,才会出现耗时过长的问题。

然后遇到的是很无奈的问题——即使把推文中图片的真实地址加标签输出了,还是有看不到图的情况,因为推特全站连带图片资源站都是在墙外的。对于这个问题,最初直接给生成订阅的页面加了功能,将真实图片地址作为参数进行访问时,会从推特下载图片并返回给用户。这样,由绑定域名后的Openshift空间作为一层代理,输出RSS时输出带有真实图片地址参数的Openshift空间地址,大部分使用的人都能看到图片了。但实际上,Openshift在国内的访问效果并不理想,即使绑定域名之后,也会有访问速度慢或者无法访问的地方。为了进一步增强服务的稳定性,写了一个单独的图片反代作为TPRSS的子项目。反代托管在Coding上,使用演示页的方式部署。因为选用的是Coding的北京服务器,国内访问基本不会有问题。但因为Coding本身也是墙内服务器,和用户一样无法直接获取到推特的图片资源,所以在设计上使用了Openshift的空间作为跳转,即用户在访问一张图片时,先请求Coding上的图片反代,反代将参数传递给Openshift上的本体,由本体从推特获取图片资源后,传递给Coding的反代,再由反代返回给用户。这样的两层结构保证了用户看图的稳定性。同时,主站上的程序在输出RSS之前会检查反代的可用性,如果不可用即全部转为输出Openshift空间地址,这样即使在反代鼓掌时,用户的订阅也不会受到影响。不过双层结构也存在坏处,逐层请求带来的是巨大的延迟,即使在网络畅通的情况下,用户访问一张图片也需要等待1到2秒才能看见图片。对此的解决办法是,在Coding上利用演示页自带的1G存储空间建立了图片缓存。用户第一次访问的图片在从本体传回Coding时会被储存在本地,当再次有访问时,反代会从本地直接获取图片返回,实测中几乎没有延时。因为PHP貌似不好对文件夹大小进行统计,而演示空间存在每次部署文件重置的特性,在设计中放弃了使用数据库索引,转而使用文件名来标明大小。储存到本地的缓存图片会在文件末尾加上文件大小,在统计大小时,直接遍历目录用正则获取所有文件尾部的数字加在一起即为文件夹大小。缓存机制使用两个文件夹储存图片,在检测到pic-1超过250M时,会删除pic-2、将pic-1重命名为pic-2并新建空的pic-1,在读取缓存时,先尝试获取pic-1中的图片,如果失败再尝试获取pic-2中的图片,如果还是失败,则会经由Openshift从推特获取图片,缓存并返回。这样保证了不会因为清空缓存而造成大面积啊卡顿,所有缓存图片都有一定的生存周期。

之后计划尝试的优化方向是,利用ppython将抓取页面匹配图片真实地址的工作交给Python多线程完成,使即使在第一次访问时也能尽量减少延迟时间。

可惜,这时候出现了变故。由于twitrss.me使用了缓存,从那里获取到的信息不具有及时性,而在Openshift上新建Perl应用部署他的源代码又受阻,我只好寻找新的RSS生成程序作为它的替代品。在这个过程中,我找到了功能和TPRSS部分重合的Twitter to RSS proxy。

在阅读了他的代码并查阅资料之后,我发现原来使用OAuth协议可以直接从推特获取某人的时间线,而且官方API传回的信息中竟然是包括了图片真实地址的。这意味着之前设计的数据库功能完全丧失,因为无论从哪方面来讲,使用官方API获取信息都具有绝对的优势。于是我新建了TPRSS2项目,借鉴之前自己写的代码和Twitter to RSS proxy中使用的thmOAuth,从头开始写。现在TPRSS网站使用的即依靠OAuth协议获取时间线的TPRSS2。吸取上个版本的经验,TPRSS2在编写过程中更多的考虑了变量命名、代码可读性等问题,在细节方面尽量做到人性化,比如使用字符串截取函数保证了在遇到较长的中英混合退推文时RSS标题的易读性、清除了图片推末尾的本页链接等。

现在和TPRSS经过重新编写,用户体验高于上个版本。不仅适合使用RSS阅读器看图,也适合不方便挂代理、希望持续关注某人推特的情况。

项目一路走来,我也学到了很多。感谢对项目提建议的朋友,你们对我的帮助是巨大的。

最后用百度的招聘信息结尾吧。

一张网页,要经历怎样的过程,才能抵达用户面前?一位新人,要经历怎样的成长,才能站在技术之巅?

求学不易,且行且珍惜。

TPRSS——看图没有国界