2013年7月31日星期三

测试 IRC 网址机器人代码

最近在优化 IRC 链接机器人。这是一种在 IRC 频道中自动识别用户发出的网址并获取对应页面的标题或对应图片的尺寸等信息的机器人。
有一个要求很重要,就是发一些奇怪的网址也能正确识别,并且能妥善处理超时等问题。
下面列出一段代码,使用 Tornado 技术:
#!/usr/bin/env python

import time

import tornado.gen
import tornado.httpserver
import tornado.ioloop
import tornado.options
import tornado.web


tornado.options.define('port', default=10489,
                       help='run on the given port', type=int)


class CountHandler(tornado.web.RequestHandler):
    @tornado.web.asynchronous
    @tornado.gen.coroutine
    def get(self):
        for i in range(1, 101):
            yield tornado.gen.Task(
                tornado.ioloop.IOLoop.instance().add_timeout, time.time()+1
            )
            self.write(('%s\n') % i)
            self.flush()
        self.finish()


class BombHandler(tornado.web.RequestHandler):
    def get(self):
        self.set_header('Content-Encoding', 'gzip')
        self.set_header('Content-Type', 'text/html')
        self.set_header('Content-Length', '2147483647')
        with open('bomb.gz', 'rb') as f:
            self.write(f.read())


def main():
    tornado.options.parse_command_line()
    application = tornado.web.Application([
        ('/count', CountHandler),
        ('/bomb', BombHandler),
    ])
    http_server = tornado.httpserver.HTTPServer(application)
    http_server.listen(tornado.options.options.port)
    tornado.ioloop.IOLoop.instance().start()


if __name__ == '__main__':
    main()
然后我们还需要生成 bomb.gz。使用如下命令:
dd if=/dev/zero bs=4M count=1k | gzip -9 >bomb.gz
这将一个 4 GiB 大小的空文件压缩成 gzip 格式,生成文件大小约 4 MiB。
我们将测试 http://localhost:10489/count 和 http://localhost:10489/bomb 两个链接。

某些机器人有超时设定,而 /count 测试会分批传输数据,某些具有 bug 的机器人会一直等待下去。此时如果发送很多这个链接,在机器人等待的时候中断服务器的 Tornado 程序,可能会导致机器人不断高速发送错误信息而被踢出。
由于某些站点,如哔哩哔哩,会不顾浏览器发送的 Accept-Encoding 请求而始终返回 gzip 压缩的数据。导致有的机器人需要解压返回的数据。/bomb 就是测试机器人是否能够控制解压的数据量而不占用过多内存。
最后,提醒一下:如果你打算测试某个机器人,最好征得其主人同意哦。

2013年7月27日星期六

多人服务器的软件包和服务管理

现维护一台服务器,有很多人要登录,需要管理好软件包和服务。
为了避免服务器重启之后挨个通知大家的问题,我写了一个简单的服务管理脚本。有一些简陋(直接用 rc.local 而不是服务),也有一些 bug(比如关机脚本在某些发行版上不执行),但是安全性是有保障的。
那么软件包呢?服务器是 Debian 的,很多软件包在源里没有,或者版本太旧。这就需要用户自己编译。于是在 .bashrc 中写:
export PATH="$HOME/.local/bin:$HOME/.local/sbin:$PATH"
export LD_LIBRARY_PATH="$HOME/.local/lib"
export LD_RUN_PATH="$HOME/.local/lib"
export PREFIX="$HOME/.local"
export LDFLAGS="-L$HOME/.local/lib"
export CFLAGS="-I$HOME/.local/include"
export CPPFLAGS="-I$HOME/.local/include"
不对所有软件包生效(比如 cmake 生成的软件包,但是有解决方法),但是大部分的都可以这么做了。
可能读者朋友已经使用类似的方法了,这篇文章是个备忘。

2013年7月9日星期二

Nginx alias try_files php-fpm work together

There is such a configuration on my Nginx 1.2.1 server:
server {
 listen 80 default_server;
 listen [::]:80 default_server ipv6only=on;
 root /var/www;
 index index.php index.html;
 autoindex on;
 autoindex_localtime on;
 try_files $uri $uri/ =404;
 location ~ ^/~([^/]+)(/.*)?\.php(/.*)?$ {
  alias /home/$1/public_html$2.php;
  fastcgi_pass unix:/var/run/php5-fpm.sock;
  fastcgi_index index.php;
  include fastcgi_params;
  fastcgi_param SCRIPT_FILENAME /home/$1/public_html$2.php;
  fastcgi_param PATH_INFO $3;
 }
 location ~ ^/~([^/]+)(/.*)?$ {
  alias /home/$1/public_html$2;
 }
}
It aims to map /~username/ to /home/username/public_html/ so that users on my server can access and manage their own files.
However, this brought a problem. If one tried to access a non-exist .php file, such as /~username/asdfasdf.php, php-fpm would complain about “Invalid Argument” instead of displaying the “404 Not Found” page of Nginx.
I have browsed Nginx Wiki and found:
Note that there is a longstanding bug that alias and try_files don't work together.
But I would never give up!
I observed the environment variables that Nginx passed to PHP and found that $_SERVER["DOCUMENT_ROOT"] became the same value as $_SERVER["SCRIPT_FILENAME"] instead of /var/www.
try_files directive tries files from $document_root. For example, try_files $uri =404; tests the existence of the file named $document_root$uri.
It is clear after I noticed this. The workaround is just append try_files "" =404; after the line of alias.

Nginx alias try_files php-fpm 共存

Nginx 1.2.1 服务器有这么一个配置:
server {
 listen 80 default_server;
 listen [::]:80 default_server ipv6only=on;
 root /var/www;
 index index.php index.html;
 autoindex on;
 autoindex_localtime on;
 try_files $uri $uri/ =404;
 location ~ ^/~([^/]+)(/.*)?\.php(/.*)?$ {
  alias /home/$1/public_html$2.php;
  fastcgi_pass unix:/var/run/php5-fpm.sock;
  fastcgi_index index.php;
  include fastcgi_params;
  fastcgi_param SCRIPT_FILENAME /home/$1/public_html$2.php;
  fastcgi_param PATH_INFO $3;
 }
 location ~ ^/~([^/]+)(/.*)?$ {
  alias /home/$1/public_html$2;
 }
}
目的是把所有的 /~username/ 映射到 /home/username/public_html/,让服务器的用户能够存放自己的文件。
但这样带来了一个问题,如果访问一个不存在的 .php 文件,比如 /~username/asdfasdf.php,php-fpm 就会报错,显示“Invalid Argument”而不是 Nginx 的“404 Not Found”页面。
查看了 Nginx 的 Wiki,有这么一句话:
Note that there is a longstanding bug that alias and try_files don't work together.
但是我不会就此罢休的!
于是我观察了 Nginx 传递给 PHP 的环境变量,发现 $_SERVER["DOCUMENT_ROOT"] 变成了和 $_SERVER["SCRIPT_FILENAME"] 一样的值,而不是 /var/www 了。
try_files 指令是从 $document_root 算起的,比如 try_files $uri =404; 就是检测 $document_root$uri 是否存在。
弄明白了这个,就豁然开朗了。在 alias 那一行后面加上 try_files "" =404; 即可。