About
RSS

Bit Focus


简易配置 gunicorn

引子

    单纯 gevent 跟 nodejs 一样有个问题是如果服务器有大的同步计算 (比如压缩一张图片什么的) 需求时, 服务器会很卡. 这也不能怪它们, 因为本来它们的长处是 IO 异步化, 同步计算卡住是缺陷特性之一.
    然, 或荐基独搅受 gunicorn 以解此困. 只是其首页上例子意味不明, 各种文档文章都说要编写一些离奇复杂的配置文件, 然后跑个语焉不详的 hello world, 并没能明示重点问题.

正文

    嘛, 一番探索之后配了下面一个用例 (Flask)
import time
import flask

app = flask.Flask(__name__)

@app.route('/<int:n>')
def root(n):
    time.sleep(2)
    i = n / 2
    while 1 < i:
        if n % i == 0:
            return 'not prime'
        i -= 1
    return 'prime'

if __name__ == '__main__':
    app.run(port=8000)
    这个例子里面兼顾了长 IO (用睡眠去模拟) 跟大计算 (算请求的数是不是个素数). 把这货在控制台裸着启动起来, 然后用 apache benchmark 来一发 (如果觉得后面请求参数里那个素数不够大, 可以自行算一个大的替换)
ab -n 500 -c 50 localhost:8000/16785407
    当然了, -c 50 这个参数纯是卖萌的, 因为上面这代码自身根本异步不起来. 结果自然是惨不忍睹, 重点两行在测试机上表现如下
Time per request:       131417.472 [ms] (mean)
Time per request:       2628.349 [ms] (mean, across all concurrent requests)
    平均单个请求耗时 2.6 秒以上, 其中 2 秒是睡过去的, 剩下 0.6 秒是计算. 也就是说 IO 时间与计算时间大概的比例是 3:1.

    安装 gunicorn 可以直接通过 pip 安装, 简单容易, 就不废话了. 下面上 gunicorn 平装版, 把上面的文件保存为 test.py, 在控制台中执行
gunicorn -w 4 test:app
    这个是说, 开 4 个进程跑 test 模块下的 app (就是文件里全局定义的 app 变量啦). 现在再开 ab 来一炮 (参数完全相同), 结果是
Time per request:       33150.026 [ms] (mean)
Time per request:       663.001 [ms] (mean, across all concurrent requests)
    从结果上来看差不多就是裸跑的 1/4 了, 因为开了 4 个进程一起搅嘛.

    虽然有 4 个进程睡睡醒醒轮番搞, 但没有异步 IO 的支持, 进程睡着就不干事了. 作为要榨干 worker 进程以及 CPU 使用率的系统管理员来说这可不能忍, 于是继续折腾个 gevent 进去好了, 两者互补, 相得益彰.
    不过用 gunicorn 就不需要在文件最开始打猴子补丁了, gunicorn 有个参数直接让 gevent 嵌入进程
gunicorn -w 4 -k gevent test:app
    再来一发 ab, 结果是
Time per request:       9724.214 [ms] (mean)
Time per request:       194.484 [ms] (mean, across all concurrent requests)
    嘛, 算是还看得过去的数据了.

补充说明

绑定其它端口

Permanent Link: ?p=516 Load full text

Post tags:

 Python
 Flask
 Web Server
 Gevent
 Gunicorn

记一些 (没) 有意义的 reduce 用法

    在 Python 或 Javascript 等许多语言中都有 reduce 函数. 其中 Python 中 reduce 作为全局函数出现, 而 Javascript 中则是 Array 的成员函数. 大量的用 reduce 来做累加累乘之类的例子就不说了, 这里探讨一个特殊的用例.
    前端经常会需要将页面中用户填写的一些内容打包成 JSON 字典, 比如一个注册页面片段
<div>
    <input id='email' placeholder='Email'>
    <input id='password' placeholder='Password'>
    <input id='conform_password' placeholder='Confirm Password'>
    <input id='address' placeholder='Address'>
    <input id='phonenum' placeholder='Phone Number'>
    <button id='subm'>Submit</button>
</div>

<script>
document.getElementById('subm').onclick = function() {
    var inputValues = {
        email: document.getElementById('email').value,
        password: document.getElementById('password').value,
        address: document.getElementById('address').value,
        phonenum: document.getElementById('phonenum').value
    };
    /* process inputValues */
};
</script>
    以后每次这个表单多一项时, 构造 inputValues 时就会多一项, 代码维护会很烦.
    如果能这样写的话可能会好一些
var inputValues = {k: document.getElementById(k).value
                   for k in ['email', 'password', 'address', 'phonenum']};
    可惜 Javascript 里面没有温暖人心的 dict comprehension... 于是, 就有了下面这种 reduce 替代品 (终于正题了)
var inputValues = ['email', 'password', 'address', 'phonenum'].reduce(
    function(obj, item) {
        obj[item] = document.getElementById(item).value;
        return obj;
    }, {}));

Permanent Link: ?p=515 Load full text

Post tags:

 Javascript
 函数式程序设计
 Python

Flask / MongoDB 搭建简易图片服务器

前期准备

通过 pip 或 easy_install 安装了 pymongo 之后, 就能通过 Python 调教 mongodb 了.
接着安装个 flask 用来当 web 服务器.
当然 mongo 也是得安装的. 对于 Ubuntu 用户, 特别是使用 Server 12.04 的同学, 安装最新版要略费些周折, 具体说是

sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10
echo 'deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen' | sudo tee /etc/apt/sources.list.d/mongodb.list
sudo apt-get update
sudo apt-get install mongodb-10gen

如果你跟我一样觉得让通过上传文件名的后缀判别用户上传的什么文件完全是捏着山药当小黄瓜一样欺骗自己, 那么最好还准备个 Pillow 库

pip install Pillow

或 (更适合 Windows 用户)

easy_install Pillow

正片

Flask 文件上传

    Flask 官网上那个例子居然分了两截让人无从吐槽. 这里先弄个最简单的, 无论什么文件都先弄上来
import flask

app = flask.Flask(__name__)
app.debug = True

@app.route('/upload', methods=['POST'])
def upload():
    f = flask.request.files['uploaded_file']
    print f.read()
    return flask.redirect('/')

@app.route('/')
def index():
    return '''
    <!doctype html>
    <html>
    <body>
    <form action='/upload' method='post' enctype='multipart/form-data'>
         <input type='file' name='uploaded_file'>
         <input type='submit' value='Upload'>
    </form>
    '''

if __name__ == '__main__':
    app.run(port=7777)
  • 注: 在 upload 函数中, 使用 flask.request.files[KEY] 获取上传文件对象, KEY 为页面 form 中 input 的 name 值
    因为是在后台输出内容, 所以测试最好拿纯文本文件来测.

保存到 mongodb

    如果不那么讲究的话, 最快速基本的存储方案里只需要

Permanent Link: ?p=514 Load full text

Post tags:

 Python
 Web Server
 Flask
 Tutorial
 MongoDB

PLY 构造词法分析工具

    PLY 需要安装一份. 可以直接通过 pip 安装

# pip install ply

    这东西并非一个扩展的正则表达式工具, 而是一个完备的编译器构造工具, 不过这篇文章只打算讨论其词法分析器构造部分.

基本例子

    PLY 很魔法的一点是它使用到了模块内部反射. 也就是说在产生一个词法分析器时, 并不是把词法规则传递给 PLY 的接口, 而是依次将一些指定名字的变量或函数定义在 py 文件中.
    下面给出第一个例子, 从文本中抓出十进制数值.
import ply.lex

tokens = (
    'NUMBER',
)

t_NUMBER = r'\d*[.]?\d+'

def t_error(t):
    t.lexer.skip(1)

ply.lex.lex()
ply.lex.input('''
    The Chinese mathematician Zu Chongzhi, around 480 AD, calculated that
        pi ~= 355/113
    (a fraction that goes by the name Milv in Chinese), using Liu Hui's
    algorithm applied to a 12288-sided polygon. With a correct value for its
    seven first decimal digits, this value of 3.141592920... remained the most
    accurate approximation of pi available for the next 800 years.

    <From Wikipedia:Pi>
''')

for token in iter(ply.lex.token, None):
    print token.type, token.value
    输出

NUMBER 480
NUMBER 355
NUMBER 113
NUMBER 12288
NUMBER 3.141592920
NUMBER 800

    上面的代码有这么一些要点
  • 文件中定义了 tokens 表示可能的词元类型; 在官方例子中, 其中的取值通常以全大写的形式出现
  • 定义词元规则 t_NUMBER, 其名字是 token 变量中的成员 'NUMBER' 加上前缀 t_; 在构造词法分析器时, PLY 会将以 t_ 开头的所有定义收集起来
  • 词元规则 t_NUMBER 的取值是正则表达式, 用来匹配所有的数值
  • 定义 t_error 函数, 如果什么奇怪的东西混进来, 这个函数会被调用; 不过现在只是抓取数值, 无视其它符号, 所以实现只是跳过一个字符 (skip 的参数是字符数量)
  • 调用 ply.lex.lex 构造词法分析器
  • 调用 ply.lex.input 喂一些输入进去
  • ply.lex.token 获得词法分析的结果

利用分词输出

    从这个基本例子迈向一个用于简单表达式计算的词法分析器并不困难. 如果按照之前一篇文章里所演示的那个功能 (请将该文章中最后一段代码保存在 expr_eval.py 中以供这次使用), 需要至少以下这些词元类型
  • 二元算符 */^
  • 二元或一元算符 +-
  • 括号
  • 整数

Permanent Link: ?p=513 Load full text

Post tags:

 Compiler Construction
 Python
 Tutorial
 PLY

越俎代庖 - 文件系统权限设置与进程的用户身份

    首先提一个问题, 为何用 sudo 执行需要 root 权限的程序时, 输入的是当前用户的口令而不是 root 的?

    看一下 sudo 的 manpage, 描述是 "execute a command as another user". 注意并不一定是以 root 身份跑程序, 而是可以用任何其他身份. 这做好事不留名干坏事不怕差评利器真是太方便了. 比如, 偷偷输出某个用户的 SSH RSA 私钥之类的. 在桌面机上很少有人会建多个用户, 那么现在创建一个 (建议接下来的试验都新建用户进行, 避免遗留安全隐患; 另外, 当前用户必须已经被添加到 sudo 白名单中)
sudo useradd -m furude
sudo passwd furude # 这一步最好设置一个与当前用户不同的口令
su - furude
furude $
    这样切换到试验用户身份, 然后创建 ssh key, 并确认生成的密钥
ssh-keygen
Generating public/private rsa key pair.

Enter file in which to save the key (/home/furude/.ssh/id_rsa): Created directory '/home/furude/.ssh'.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/furude/.ssh/id_rsa.
Your public key has been saved in /home/furude/.ssh/id_rsa.pub.
The key fingerprint is:
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
The keys randomart image is:
+--[ RSA 2048]----+
| ............... |
+-----------------+
ls .ssh
id_rsa id_rsa.pub
    现在另开一个 shell, 以非 furude 身份登入, 执行
ls /home/furude/.ssh
会看到错误, 不允许列举该目录. 这是当然了, 要是连密钥都能随便看, 就没什么安全感了.

    下面就轮到 sudo 登场了. 还是当前用户身份登入, 执行
sudo ls /home/furude/.ssh
id_rsa id_rsa.pub
sudo -u furude ls /home/furude/.ssh
id_rsa id_rsa.pub
sudo -u `whoami` ls /home/furude/.ssh
ls: cannot open directory /home/furude/.ssh/: Permission denied
    第一个是以 root 身份执行, root 最大嘛, 所以当然可以毫无压力偷看一切文件或目录了; 接下来 -u furude 参数则是指定以 furude 这个用户身份执行, 自己当然可以看自己的东西了; 最后一个则仍然是以当前用户身份执行, 所以被驳回了.
    在上面试验中, 即使以 furude 的身份使用 sudo, 要求输入的口令仍然是当前用户的口令而不是 furude 用户的 (最好新开一个终端试试, 连续使用 sudo 时口令会被缓存下来). 那么, 开头的问题应该是这样的
  • 为何用 sudo 临时以另一用户身份执行程序时, 要求输入的是当前用户的口令, 而不是被指定的那个用户?

Permanent Link: ?p=512 Load full text

Post tags:

 C
 文件系统
 进程
 权限

ASCII Art 字体 Tukasans

Permanent Link: ?p=511 Load full text

Post tags:

 ASCII Art
 Font

多国语言环境下 Jinja2 与 strftime 互动异常退治纪要

    不少语言或者库或多或少都遭遇过乱码问题. 然而这一点在 Python 里面表现得比较另类, 一般来说只要贯彻 UTF-8 到底的思路, 不怎么可能遇到一大波乱码袭来的情况. 只是, 偶尔会直接因为字符串类型设置不对而直接抛运行期异常.
    常在河边走, 湿鞋是顺理成章的事情. 这次遇到的情况简报如下
# encoding=utf-8

import jinja2
import datetime

now = datetime.datetime.utcnow()

print jinja2.Template(u'''当前月份 {{ date.strftime('%Y 年 %m 月') }}''').render(date=now)
运行这一段代码, Python 直接扔出来一坨
Traceback (most recent call last):
  File "test.py", line 8, in <module>
    print jinja2.Template(u'''当前月份 {{ date.strftime('%Y 年 %m 月') }}''').render(date=now)
  File "/usr/local/lib/python2.7/dist-packages/Jinja2-2.6-py2.7.egg/jinja2/environment.py", line 894, in render
    return self.environment.handle_exception(exc_info, True)
  File "<template>", line 1, in top-level template code
UnicodeEncodeError: 'ascii' codec can't encode character u'\u5e74' in position 3: ordinal not in range(128)
    简单地说, 就是 Jinja2 在刷什么东西的过程中挂了, 因为字符串里面混入了什么奇怪的东西.

    一番尝试并寻找之后, 首先发现第一个问题, datetime.datetime.strftime 函数接受奇怪的字符串会出问题, 比如先来试试看这个
now = datetime.datetime.utcnow()
print now.strftime('%Y 年 %m 月')
    结果什么问题都没有, 突然有人品爆了的感觉, 这个好像很不科学的样子啊. 再仔细对比代码找找茬才发现要这样才能挂掉程序
now = datetime.datetime.utcnow()
print now.strftime(u'%Y 年 %m 月')
    这前缀 uunicode 类型怎么说也坑太深了的感觉吧. (别扯什么 Python strunicode 类型异与同 21 天从入门到精通什么的, 别的语言都没这问题 Python 确有, 这应该就是 Python 的问题了吧.) 在这种情况下如果要用 unicode 还得转一道
now = datetime.datetime.utcnow()
print now.strftime(u'%Y 年 %m 月'.encode('utf-8'))

Permanent Link: ?p=510 Load full text

Post tags:

 Python
 Jinja2
 strftime
 Unicode

Flask 出坑记

    Flask 是个 Python Web 框架. 网站上文档例子都很详尽, 这里就不废话了, 只是来扯两个使用中需要注意的地方.

获取 POST 请求体

    21 世纪的 Web 交互中服务器跟浏览器互相丢 JSON 已经成了司空见惯的事情. 在 Flask 框架作成的服务器上要搞到 JSON 数据当然是直接访问 POST 请求体了, 如
import flask
import functools

app = flask.Flask(__name__)

@app.route('/wtf', methods=['POST'])
def wtf():
    return 'Received: ' + flask.request.data

def main():
    app.run(port=7777)

if __name__ == '__main__': main()
    按文档的说法, flask.request.data 包含请求数据字符串. 但其实这也是个坑, 默认情况下根本取不到请求数据
curl -d "[1,1,2,3,5,8]" http://localhost:7777/wtf
Received:
    熊孩子你把拿到的字符串给吃了吧? 实际上如果去看看那文档会看到并不如上面所说的那样, 而是
  • Contains the incoming request data as string in case it came with a mimetype Flask does not handle.
    后面这个状语从句真是着急, 那到底什么 mimetype 会使得 Flask does not handle 呢? 根本没说清楚啊. 扫一眼文档后面, 还有个东西可以用: flask.request.json, 但这货一般是 None, 只有当请求 mimetype 被设置为 application/json 的时候才有用, Flask 你真心是跟 mimetype 过不去啊. 也就是说得这样发请求
curl -d "[1,1,2,3,5,8]" localhost:7777/wtf
Received: null
curl -d "[1,1,2,3,5,8]" -H "Content-Type:application/json" localhost:7777/wtf
Received: [1, 1, 2, 3, 5, 8]
# Server codes

import json
import flask
import functools

app = flask.Flask(__name__)

@app.route('/wtf', methods=['POST'])
def wtf():
    return 'Received: ' + json.dumps(flask.request.json)

def main():
    app.run(port=7777)

if __name__ == '__main__': main()
    问题是现在前端攻城狮都被浏览器兼容性折腾得满世界买表, 哪还有心情检查每个请求的 content-type 对不对. 况且这还只对 JSON 有效, 如果是山寨协议又怂了.
    好吧, 如果实在不行, 就挖到 WSGI 里面去好了, 比如这样
def request_data():
    d = flask.request.data
    if not d:
        return ''.join(flask.request.environ['wsgi.input'].readlines())
    return d

Permanent Link: ?p=509 Load full text

Post tags:

 Python
 Flask
 Web Server

0 Page 1 2 3 4 5 6 7 8 9 10 11 12


. Back to Bit Focus
NijiPress - Copyright (C) Neuron Teckid @ Bit Focus
About this site