搜狐闪电邮是如何基于 webpy 框架开发的 - 火魔网

前言:应 Python-CN 社区之邀,我们这里公开一些技术上的小秘密,描述搜狐邮件中心是怎样基于 webpy 进行 webmail 的开发和部署的。要理解我们自己山寨出来的这个框架——姑且命名为 webpy+ 吧——您首先得熟悉 webpy 程序的开发模式。

本文档采用协议发布

Q: 搜狐邮件中心为什么选择 web.py?
A: 轻便,这意味可以更容易的去定制它。这对于我们这种需要部署几百台服务器的应用非常重要。

==================================
搜狐邮件中心webpy项目的目录结构
proj\runctl
proj\run.py
proj\app1\
proj\app2\
...

我们有一个 /opt/sohumc 的目录,为便于理解你可以把它认为是 /usr/local.
该目录就是我们的运行环境。编译一次,到处部署

python 以及 webpy 等都安装在 /opt/sohumc 里。

run.py和runctl是我们这个webpy+ framework的核心,下面只介绍这两个文件

==================================
runctl 是从 shell 启动应用的入口
实质上它也是一个 python 脚本,但由于我们架构师的趣味,去掉了 .py 后缀,可能是为了让它看起来更像一个可执行程序。
它的开头是这样:

#!/opt/sohumc/bin/python

import sys, os, os.path, signal, zdaemon.zdctl, zdaemon.zdrun,shutil

CONF_FILE = '''<runner>
program /opt/sohumc/bin/python run.py %(run_as)s %(port)d
socket-name /opt/work/log/run.%(port)d.sock
%(log_files)s
forever true
user postfix
umask 022
default-to-interactive false
</runner>
'''

1. 我们利用了 zope 里的 zdaemon 组件来控制进程. 这样停止应用不需要使用 killall 或者额外写什么脚本了.
2. 因为我们主要是文件系统的操作,所以要保证正确的 user 和 mask. zdaemon 确实很合用.

runctl 最终调用 zdaemon.zdctl.main() 来启动 run.py

==================================
run.py 是启动 webpy 的入口,格式为 run.py app_dir port
换言之就是把某个应用启动在某端口上(然后前端nginx反向代理至该端口)
我们一个应用就是一个目录

最开始webpy之旅的时候,首要解决的问题就是如何把app分割到多个文件里,然后把 url/class 的映射关系自动组装起来。

1. 我们遍历 app_dir 目录,里面的 .py 文件都是需要导入的模块

    modules = [(x[:-3], os.path.join(app_dir, x)) for x in os.listdir(app_dir) if x.endswith('.py')]

得到一个 list,每个元素是一个 tuple,(模块名, 文件路径名)

2. 然后我们去获得 webpy 所需的 url/class 映射关系并生成 app

    urls = import_modules(modules)

    app = web.application(urls, globals(), False)

这里得先介绍一下 app_dir 目录下每个 .py 文件的规范:

a. 必须有一个名为 urls 的 tuple,里面声明这个 py 文件想处理哪些 url

b. 必须有一个名为 handler 的 class,就是 webpy 里的 GET/POST 请求处理单元

比如写个 demo.py:

urls = ('/demo', '/hello')

class handler():

    def GET(self):

        return "Hello, From Demo!"

3. 重头戏来了,看看 demo.py 如何被集成

def import_modules(modules):

    urls = []

    g = globals()

    from imp import load_source

    def import_module(name, file):

        m = load_source(name, file)         g[name] = m

        #寻找文件中是否有handler和urls的声明

        v = dir(m)

        h, u = 'handler' in v, 'urls' in v

        if h or u:

            if not (h and u):

                #handler和urls必须成对出现,handler是一个类,而urls是一个元素是字符串的tuple或列表

                raise ImportError('module %s doesn\'t have matched handler and urls' % name)

            #有handler和urls的声明,就记下来加入全局的urls表中

            urls.extend(reduce(list.__add__, [[x, '%s.handler' % name] for x in m.urls]))

    for (name, file) in modules:         if name not in g:
import_module(name, file)
return urls

现在还比较山寨,所有的名字都在 globals() 空间里. 总之这样执行完以后,返回的 urls 是

    ('/demo', 'demo.handler', '/hello', 'demo.handler')

同时 'demo.handler' 是可以被访问的类资源了

run.py 的最后是启动 FastCGI

    app.add_processor(web.loadhook(prototype_patch))

    # 我们使用了 loadhook 完成一些入口清理工作

    func = app.wsgifunc(access_log, )                

    # access_log 我们是利用 WSGI 中间件来做的

    from flup.server.fcgi_fork import WSGIServer      

    # fork 是 python 王道

    WSGIServer(func, .../*flup提供了一堆参数, 值得了解*/...).run()

::...
免责声明:
当前网页内容, 由 大妈 ZoomQuiet 使用工具: ScrapBook :: Firefox Extension 人工从互联网中收集并分享;
内容版权归原作者所有;
本人对内容的有效性/合法性不承担任何强制性责任.
若有不妥, 欢迎评注提醒:

或是邮件反馈可也:
askdama[AT]googlegroups.com


订阅 substack 体验古早写作:


点击注册~> 获得 100$ 体验券: DigitalOcean Referral Badge

关注公众号, 持续获得相关各种嗯哼:
zoomquiet


自怼圈/年度番新

DU22.4
关于 ~ DebugUself with DAMA ;-)
粤ICP备18025058号-1
公安备案号: 44049002000656 ...::