72松的访问问题

•2009年01月18日 • No Comments

最近本blog的服务商72松在折腾服务器升级等事宜,导致目前从国内访问本blog及其困难,预计要到春节后才有可能恢复正常,到时候会启用国内服务器,从国内访问速度的问题可能得到解决。

总的来说72松的免费wp服务用到现在还是不错的,最主要是比自己host一个wp要省时省心。但目前这样的服务质量真有些让人受不了了,先等一下看服务器搞定后的效果吧,还是不行的话就搬yo2.cn外加买绑定域名服务算了。

Google Quick Search Bar

•2009年01月14日 • No Comments

作为Google Mac Team的一员,Mac必备工具QuickSilver的作者Nicholas Jitkoff发布了Google Quick Search Bar for Mac!话说QuickSilver很久没什么动静了,原来Nicholas忙这个呢。
马上在我的mbp上装上开始用了,毕竟是同一个作者,QSB深得QS的精髓,但又不和QS功能上重叠太多。值得一提的是集成Spotlight的功能,这点用起来很爽,只是目前的早期版本打开Spotlight集成以后性能就下降了,用了半天碰到几次CPU占满的情况。另外方面,和Google服务的集成(Search, GCal, GMail),输入计算表达式直接给出结果等功能也非常使用,也是QS欠缺的,这些功能倒是让我想起Win/Linux上的Launchy了。
虽然目前的版本还支持早期原型,但是绝对值得Mac上的敲键盘爱好者们期待!现在其我就QS和QSB双管齐下了。对了,还有FF下的Ubiquity等着我呢。

Quick Search Bar

Quick Search Bar

‘09 blog重组计划

•2009年01月13日 • No Comments

不知不觉我的blog已经有3处了,这里的本人个人地盘,feedlr日志,还有marsbug日志。地盘多了维护起来精力就不够了,内容也分散了,不是个好的办法,于是决定今年来一次重组。
首先,本blog改用新的主域名damienh.org,继续定位在本人的原创技术经验,感想,和其他个人内容,会花更多时间在这里。同时,准备把其他两处blog的一些有价值的原创文章也搬过来,分类并集中在一起。其他两处的更新会减少,并引导读者到这里来集中阅读原创内容。
趁着72松升级到wp 2.7,本blog也换了主题和标题,2009,以一个新面貌开始吧!

在Django程序中如何用好logging模块

•2008年12月30日 • No Comments

logging是Python 2.3起自带的标准模块,可以用来从运行状态的程序中记录日志。logging模块的功能非常强大,可以非常灵活的向各种预定或者自定的目标输出日志。而利用标准的logging模块,Django程序就可以轻松实现运行环境下的日志输出,这对于开发以及部署环境下程序运行具体情况的监控和调试都是不可或缺的,所以我在这里总结一下自己的一些经验。

Django程序使用logging输出的基本设置

要让Django程序正确得利用logging模块输出日志,首先需要在settings.py中配置好logging参数:


logging.basicConfig(
  level = logging.DEBUG, #设定DEBUG级别输出
  format = '%(asctime)s %(levelname)s %(module)s.%(funcName)s Line:%(lineno)d %(message)s',
)

logging.basicConfig是logging模块提供的简便配置logging参数的方法。经过以上的配置,在Django程序中只需要通过logging.debug,logging.info等方法就可以输出日志了。logging.DEBUG及以上级别的日志都会直接输出到django运行时当前命令窗口,而在生产环境下,只需要相应的提高logging输出级别就可以控制日志输出的内容,避免输出过多日志内容。(关于logging级别和logging的基本知识请参考pydoc) 在本地调试使用manage.py runserver的时候,logging内容就会直接出现在console里。

输出日志到文件

以上的基本设置只能让日志直接输出到命令行窗口。需要把日志输出到文件保存的话最简便的方法是这样


logging.basicConfig(
  level = logging.DEBUG, #设定DEBUG级别输出
  format = '%(asctime)s %(levelname)s %(module)s.%(funcName)s Line:%(lineno)d %(message)s',
  filename = '/path/to/logfile/filelog.log',
)

在logging.basicConfig方法中,只要指定了filename,那么日志就会直接输出到指定的文件了。

按日期循环保存日志文件

在生产环境下,不仅需要把日志写到文件,通常还需要把日志文件按日期分割保存。这样的任务用logging模块也很容易做到。在生产环境的settings.py里使用如下设置:


root = logging.getLogger()
if len(root.handlers) == 0: #避免重复设置handler,否则日志内容可能被重复输出
  level = logging.INFO
  filename = '/path/to/logfile/filelog.log'
  format = '%(asctime)s %(levelname)s %(module)s.%(funcName)s Line:%(lineno)d %(message)s'
  hdlr = TimedRotatingFileHandler(filename, "midnight", 1, 5)
  fmt = Formatter(format)
  hdlr.setFormatter(fmt)
  root.addHandler(hdlr)
root.setLevel(level)

在这里使用了logging模块预定义的TimedRotatingFileHandler类,在每天半夜滚动日志文件,而最多保留5个以往的日志文件。由于需要指定特殊的Handler,所以这里不能使用logging.basicConfig的简便方法。

使用logging模块的更多好处

用好日志功能可以对开发和调试起到很多帮助作用。使用了logging模块可以通过日志级别非常方便的控制输出,不需要增减任何程序代码,只需要在settings.py中更改logging级别一行代码就行了。所以在开发过程中,可以尽量多用logging.debug输出对调试有帮助的内容。而在生产环境下,通过日志也可以方便的分析真实运行环境下的一些问题,便于调试修改bug。

如果用在Google App Engine,不需要指定输出文件,程序输出的日志都会直接保存在App Engine运行日志中,还可以通过正则表达式来搜索以往日志。

另外,通过Django debug toolbar,可以方便的直接在调试环境下直接在每个页面上查看输出的日志,非常好用。

所以,如果你在写Django程序,就不要再用原始的print了,用好logging可以事半功倍。

如何在Django+Nginx fcgi方式下不间断服务地部署新代码

•2008年12月22日 • No Comments

极品座驾“上线以来,用户访问量一直运行在高位,上周末创下了单日pv 139万的记录。在这样的情况下,保持服务不间断是最重要的事情。目前我们支撑这样访问量的架构,是使用Nginx作为web服务前端,用fcgi方式运行django的python进程,再和前端Nginx连接起来。fcgi模式下,python进程把字节码装入内存,来提供最佳的执行速度。但是这样带来的问题就是更新了python代码以后,服务器没法动态装入新代码,而是需要杀掉现有fcgi进程然后spawn新进程才能达到更新代码的目的。虽然重启django fcgi进程的速度很快,但是不可避免的会导致用户服务的中断,在访问量大的情况下更是很危险的方式。为了达到不中断服务的目的,我们采用了这样的方法:

  1. 更新python代码
  2. 在另一个端口spawn一组新的fcgi进程
  3. 更改Nginx配置,把proxy_pass转发端口指向新fcgi进程的端口
  4. 动态重载Nginx配置
  5. 过一段时间等到原fcgi端口不再有未完成的用户请求,再把原端口上的进程全部杀掉

由于Nginx可以在运行状态下不间断的重载配置改动,所以在重载配置以后所有的访问请求都被转发到新的fcgi端口了。而在用户操作频繁的时候,访问请求从原fcgi端口被转到新端口也只需要很短的时间(可以借助netstat命令来观察)。这样一来,就可以实现在不影响用户操作的情况下不间断的切换到新代码了。

平台不是一天建成的-从开发者角度看国内SNS平台技术

•2008年10月23日 • 3 Comments

题外先说一句,最近太忙,欠着的GAE+Django开发SNS应用的文章一直没写好,很抱歉。之前给康盛的《站长》杂志写过一篇简单的稿子,准备整理和丰富下具体的代码例子再发布到本Blog上。

上周末把测试完毕的Py51放上了google code,至此我已经为校内UCHome Manyou,以及51这3个目前国内最主要和最先开放的SNS平台都发布了各自的Python SDK。通过这些工作我对这几个SNS平台技术有了比较详细的了解,有不少经验想分享。今天写下来的主要是一些技术细节,可能不是你感兴趣的。但是目前还没看到有从细节方面总结的各平台的开发经验,所以觉得还是值得写一下。

魔鬼藏在细节中

网站面向的是所有普通用户,而开发者平台面向的是有专业技术背景的开发者。设计和实现API,是难度最高的软件工程,需要出色的技术和经验。

国内SNS的开放平台,主流都是模仿Facebook F8平台的,所以可以称作F8系平台。但虽然从表面看和F8平台的设计基本一致,仔细接触的话就会发现,技术方面的差距其实很大。

  • 校内平台

校内是国内最早开放的F8系平台,我从开放不久就开始在校内平台上做开发。在校内经过一段时间的开发,明显感到校内平台的仓促上阵,和技术实力的捉襟见肘。刚开放的时候,整个平台功能极其单一。XNML只提供非常有限的重用组件标签(现在也没什么改善),不支持除了inline方式以外的css,不支持js(这两个最近已经支持了)。最严重的是校内明显对API服务器的负荷缺少准备,API调用经常堵塞超时,还动不动发生整个应用服务器长时间无法正常工作,所有应用都没法打开的问题。而这样的问题持续了不少时间都没有明显改善。

不过最夸张的还是校内API不设防的安全机制。校内平台对来自第三方服务器的API调用从刚开放到目前一直是不做签名检查的。记得你在新建校内应用时候得到的API Secret字串吗?其实这个Secret根本就用不到,就是摆样子的。做过Facebook开发的同学肯定知道Facebook App也是有一个唯一的secret字串,校内看起来和Facebook平台也一样,到底有什么区别呢?

Facebook平台要求第三方应用调用其API的时候除了调用参数还要提交一个签名字串,而这个签名字串就是用调用参数外加应用的secret做的md5 hash。由于你的secret是唯一而不公开的,那么平台就能通过你的签名字串来唯一验证这个API调用是不是来自真正的应用本身。

而校内平台在接受第三方应用调用API的时候并不需要和检查签名字串,这样,只要得到了某个应用的API Key,那么任何应用就可以在调用API的时候冒充其他某个应用了。而得到一个应用的API Key也是举手之劳,因为API Key是明文POST到校内服务器的,任何人只要用一下某个应用,截取应用发出的POST就能得到它的API Key了。于是,就在大赛期间出现了某名列前茅的第三方应用利用校内自己“特权”应用的API Key突破平台限制获利的事件。

  • Manyou平台

Manyou平台在国内来看是目前技术方面最严谨功能也最强的一个F8系平台,从功能和稳定性来看都做的不错。这点和康盛拥有若干PHP大牛是分不开的。但奇怪的问题还是有的。

首先是有些过分神经质的签名算法。具体细节现在在Manyou开发者wiki上应该有更详细的解释了,但是我在写PyManyou的时候在签名问题上着实费了不少周折。不过,有这么神经质的签名算法可见Manyou对平台安全的重视,所以还是应该赞一下的。

另外有一个诡异的细节问题,通过MYML的表单POST到应用服务器地址的多值参数,会让django无法解析成list。

比如,我在一个form里有一批多选框,input的name是"select[]"。在正常情况下,包括facebook,校内和51.com,这个form在post过来以后,从django的request.POST里可以通过getlist直接得到"selectp[]"值的数组。但是在Manyou上,从django得到的POST数据确是"select[0]", "select[1]"这样的变量名,也无法通过getlist得到多值参数数组。select[n]这样的变量名对PHP的处理来说是正常的,但是我不理解的是为什么其他平台都没问题,唯独Manyou要设计成这样,对非PHP的语言缺乏考虑了。

  • 51平台

实话说,51.com就算是网站本身,也是几个里面技术上最落后的。不说别的,在utf8编码早就成为广泛接受的标准的今天,51.com还用的gbk编码。这也造成了51平台混乱而毫无意义的编码问题,就连用51官方的PHP SDK也需要为编码折腾。

提交到51应用服务器的非英文参数值,如果编码和51期望的不一致,就会发生签名错误。经过和51技术人员的沟通和自己的摸索,最后终于发现,尽管声称一切用UTF8,但实际上对非英文数据,在计算签名字串的时候必需用encode(’GBK’)把unicode字串编码成GBK二进制格式,而在向51服务器发出POST的时候又要用UTF8。另外,在发布中文feed内容时候,也需要把中文编码成GBK。

平台不是一天建成的

国内SNS平台到目前为止给人的感觉是,为了让手上的投资有地方用,抓着开放平台和第三方应用的概念炒一把。从校内51平台的开发协议就可以看出,两个SNS的管理层在理念上和它们竞相模仿的Facebook只是貌合神离。这方面Manyou确实做的最正宗,只是Manyou的模式太独特了,还需要发展和完善。

不过值得赞扬的是,虽然校内和51第一版协议都让人非常无语,但是在听到了开发者的骂声以后,两家还都是用开放的心态确实在改进。而随着年底前更多开放平台的加入,相信国内的SNS平台在明年能逐步发展到脱离幼稚期。只是,其实一开始肯定能料到开发者的抗议,为什么还要来侥幸一把呢?

用Python+Django+Google App Engine开发SNS应用(一):校内和PyXn

•2008年09月14日 • 4 Comments

大家好!我是Marsbug团队的Damien。今天起,我会在本blog陆续发表一系列关于Python, Django, Google App Engine以及SNS应用开发的文章。

SNS应用可以说是web2.0时代mashup概念的一个非常成功的例子。从Facebook F8平台开始,SNS应用经过1年的发展已经深入人心。而在F8开放一年之际,国内的SNS领域也开始了一场开放平台大战,这也是我们开发者一展身手的好机会。今天,让我先从开发校内应用谈起吧。

开发技术的选择

目前Marsbug已经在校内Manyou51.com上开发了多款应用。我们使用的技术,主要包括Python+Django+Google App Engine,以及RoR+Heroku。在服务器方面,我们选择的都是新兴的云计算服务,是因为它们非常适合我们团队的需要:从零成本起步,按需付费,即使访问量上去了,也不必过多操心性能问题。

那么,怎么使用Google App Engine开发校内应用呢?

首先,GAE目前只支持Python作为开发语言,所以我选择了Python+Django来开发应用。本质上来讲,SNS应用的开发和网站开发没有本质区别。最大的不同在于SNS应用的Mashup性质:需要调用SNS平台的API接口来完成一些交互功能。

校内平台官方提供的是Java API开发包,并没有Python开发包。为此,我在PyFacebook基础上为校内平台定制了PyXn开发包,提供和PyFacebook一致的使用方式,维持了良好的可扩展性和轻量封装的理念,也已经经过了我们多款应用的实战检验。PyXn的代码可以从Google Code上得到:http://pyxn.googlecode.com 请直接checkout svn最新版,目前没有提供打包下载。

Django+PyXn的使用

PyXn对校内API调用进行了轻量封装,对Django和GAE做了特别支持。所以,使用PyXn在Django+GAE上进行开发是一件很轻松的事。由于PyXn的GAE支持对开发者是透明的,本文就以Django+PyXn的组合作为例子。GAE的开发细节留待下一篇文章说明。

首先,建立一个新的Django site

django-admin.py startproject mysite

然后,建立一个app

django-admin.py startapp myapp

把PyXn文件包整个复制到django site根目录下面,和django app目录同级。也就是:

mysite
>myapp
>pyxn
manage.py
settings.py
urls.py

在settings.py里,配置好PyXn需要的参数

MIDDLEWARE_CLASSES = [
    ...
    'pyxn.djangoxn.XiaoneiMiddleware',
    ...
]
XIAONEI_API_KEY = 'api_key'
XIAONEI_SECRET_KEY = 'api_secret'
XIAONEI_APP_NAME = 'my_app'
XIAONEI_CALLBACK_PATH = "/xn/" #相对于url根的callback_path

这样,就能开始利用PyXn进行校内应用开发了。注意到我们添加了PyXn提供的’pyxn.djangoxn.XiaoneiMiddleware’,这个Middleware的作用是再每个request中自动加入一个pyxn.Xiaonei的实例,调用校内API就是通过直接调用这个Xiaonei实例中定义的函数来做的。同时,djangoxn模块里还提供了require_add()这个decorator,对于对应于一个XNML页面的view函数,需要用require_add()来修饰,从而得到一个直接可用的xiaonei实例。看下面这个例子。

import pyxn.djangoxn as xn
from django.utils import simplejson

@xn.require_add()
def foo(request):
    xn = request.xiaonei
    #make api calls now
    friends = xn.friends.getFriends()
    xn.profile.setXNML(profile='some xnml')
    xn.feed.publishTemplatizedAction(template_id='1', title_data=simplejson.dumps({'foo':'bar'}))

在上面这段代码中,@xn.require_add()这步decorator操作实际上做的事情是检查request里从校内服务器传过来的参数,如果必须的用户登录信息都存在,那么就自动建立一个完整的xiaonei实例;否则,自动重定向到app安装页面,要求用户安装后才能访问这个页面。

调用校内API的时候需要在POST数据里提供一些必须的app和用户数据,包括预定义的api_key, api_secret,以及从request里得到的session_key,uid。而xn.require_add()这步检查做的事情就是把这些必须的参数填入新建的Xiaonei实例,而通过xiaonei实例调用api的时候会自动把必须的参数封装整理到POST数据里。也就是说,只有一个定义了必须参数值的xiaonei实例才能成功调用api,而xn.require_add()对于需要调用api的view函数是必须的。

以上的例子适用于XNML页面对应的view函数,因为用于重定向到安装页面的方法实际上是返回<xn:redirect>给校内服务器。在开发校内应用的时候,还经常会需要用到iframe页面,比较多的情况是xnml页面上嵌入iframe页面。iframe页面对应的view函数就不能直接利用@require_add来修饰了,而是通过xiaonei.check_session()函数来显式的检查request参数,并自动构造一个xiaonei实例,并填入调用api必须的参数。实际上,xn.require_add()这个decorator也是通过check_session()函数来从构造完整的Xiaonei实例的。我们在iframe的情形下,只不过是不需要redirect这一步,而是直接check_session()就行了。

def iframe_view(request):
    xn = request.xiaonei
    xn.check_session(request)
    #make api calls now
    friends = xn.friends.getFriends()

你可能会问,那么这种情况下,用户没有安装就访问这个页面的时候怎么把用户重定向到安装地址呢?这就可以通过给包含着这个iframe的xnml页面对应的view加上xn.require_add()这个decorator就行了。(不推荐纯iframe形式的app)

小提示

所有校内API的调用,都是通过Xiaonei类的实例进行的。而调用格式完全可以参照校内api的定义来推测。比如,friends.getFriends()这个api,就是用xiaonei.friends.getFriends()。而api需要的参数,也按照api定义的类型来提供。

推荐大家略读一下pyxn.__init__.py的代码,从114行开始定义的METHODS dict就能方便的看出具体api调用函数的格式。例如这个定义:

'feed': {
        'publishTemplatizedAction': [
            ('template_id', int, []),
            ('title_data', json, ['optional']),
            ('body_data', json, ['optional']),
            ('resource_id', int, ['optional']),
        ],
    },

这就是定义了xiaonei.feed.publishTemplatizedAction这个api方法。其中,template_id参数是必填的,类型是int,其余都是可选参数,类型分别是json和int。具体调用例子请参考上文实例。

总结

好了,在Django环境下使用PyXn今天就介绍到这里。下次会详细谈一下我们是如何在Google App Engine上,运用Django+PyXn这套工具来开发应用的。

如何从Ubuntu Gutsy顺利升级到Hardy

•2008年05月26日 • No Comments

前两天终于决定把机器上的Gutsy升级到Hardy了。没有第一时间升级就是因为从Gutsy发布开始已经把全部工作环境搬到了Gutsy上,用了半年的环境要折腾一下是需要一定决心的。况且,从Dapper开始,每次升级都没有成功过,只是因为Ubuntu下没什么重要数据,后来都是重新安装的。好在之前咨询的一些尝鲜的朋友都没什么问题的升级成功,所以决定在备份了/home以后开始升级。
果然,Hardy的升级过程非常顺利。除了若干小问题,升级完成以后重启一下马上就回到了熟悉的工作环境,终于松了一口气。看来Ubuntu经过这么多版本的改进,确实已经相当成熟了。那这次升级值得写一下的就只有碰到的个别问题了。

Firefox 2和Firefox 3共存

Hardy默认安装了Firefox 3,但是很多插件还没法装,所以很多人都希望用回Firefox 2。装Firefox 2本身其实很简单,源里就有firefox-2的包。但是升级前如果用的是firefox 2,就一定要先备份好数据,推荐用FEBE完整备份profile。因为升级后Firefox 3会直接使用升级前Firefox 2默认的profile,这样再装回Firefox 2的话,原来的profile在ff2下也不能用了。所以推荐的做法是升级完成后为ff2和ff3各自新建一个profile,然后把ff2备份数据导入,就恢复原样了。

Sun JVM 1.6在64位Hardy下造成Eclipse经常崩溃

对这个问题已经开出了bug报告,而且据说用了OpenJDK 7同样有问题。目前我用的解决办法是在eclipse.ini里添加以下一行内容:

-XX:CompileCommand=exclude,org/eclipse/core/internal/dtree/DataTreeNode,forwardDeltaWith

这样改完以后都没再崩溃过了。另外还可以改用IBM JDK,据说是没有问题的。

无法注销/重启/关机的问题

这个现象可能只有少数人才能碰到,注销/重启/关机到一半就没反应了,需要重启一下x,然后才会继续下去。问题是出在KeyTouch上。把KeyTouch拆了就好了。这个Bug也已经在修复中了,暂时不用KeyTouch也没什么问题。

其他

其实值得提的基本就这些了^^ 升级以后,本来坏了总无法启动的OOo恢复正常了,有时候莫名死机的问题也没有了。总之,这是一次非常成功的升级。Ubuntu向着完美桌面的目标又进了结实的一步。

又搬了,开始72松

•2008年04月4日 • No Comments

恩,又搬了。今天把本Blog从feedlrvps服务器搬了出来,是为了节约有点紧张的服务器资源。而且,72松确实也是一个不可多得的好服务,在使用中能感觉到这是一个真的理解网络,在乎用户的团队。要支持的!

搬过来以后,感觉唯一比不上自建blog的只是不能编辑模板。预装的插件和主题非常多,我原来用的基本都有。如果能以某种方式自助添加插件就完美了。总之,对于国内的wordpress爱好者来说,在wp.com和谐的情况下能有72松真的很幸福。

Feedlr公开上线咯

•2008年03月2日 • 5 Comments

Feedlr是我用Grails做的一个pet project(宠物项目?)现在已经公开上线咯~

Feedlr是一个类似twitterfeed的miniblog mashup。但它除了twitter同时还支持国内的叽歪饭否,并且能让用户浏览所有在feedlr上创建的bot,就像个bot超市,找自己感兴趣的就能直接follow/关注了。

如果你经常用miniblog,特别是国内的叽歪或者饭否用户的话,不妨去feedlr找找看有趣的bot吧。

或者,如果你有自己的博客,希望更多的人能随时随地收到你博客的更新的话,就可以用feedlr把自己的blog广播到miniblog上。现在不用再羡慕英文blog圈流行的twitterfeed了,用feedlr把你的博客直接广播到国内的叽歪和饭否吧~

另外,你也可以把任何你感兴趣别人也可能感兴趣的feed用feedlr建立bot,让大家都能随时随地收到新鲜消息,比如我建立的cnBeta botGoogle新闻 bot,就有很多叽歪的朋友关注着咯。

你还可以访问Feedlr的叽歪留言板,关注和讨论最新的Feedlr功能和用法,还可以给我提意见~我直接就能通过叽歪收到你的留言呢:))