Ubiquity命令:收藏到Evernote和Friendfeed
Ubiquity太好玩了,又写了几个有用的命令(我为什么要说又呢),可以直接敲键盘来收藏当前选中的内容到Evernote,以及分享到Friendfeed。外加一个,在当前页面上调出Jash,一个Javascript命令行调试工具。从下面的地址就可以安装了。
“clip-to-evernote”命令
“share-on-friendfeed”命令
“jash”命令
当然,首先需要安装Ubiquity插件噢!
Ubiquity太好玩了,又写了几个有用的命令(我为什么要说又呢),可以直接敲键盘来收藏当前选中的内容到Evernote,以及分享到Friendfeed。外加一个,在当前页面上调出Jash,一个Javascript命令行调试工具。从下面的地址就可以安装了。
“clip-to-evernote”命令
“share-on-friendfeed”命令
“jash”命令
当然,首先需要安装Ubiquity插件噢!
今天又看到了code_swarm的Python视频,不禁贴出来共赏。外加code_swarm Eclipse作为Bonus。
code_swarm – Python from Michael Ogawa on Vimeo.
code_swarm – Eclipse (short ver.) from Michael Ogawa on Vimeo.
找一个国内能访问又免费功能又强大的wordpress宿主服务实在不容易。去年4月把本blog从自己的vps主机搬到了72松,当时确实是一个很不错的免费wp服务,插件模板都很丰富,也省得占用我自己服务器资源,还有省得自己折腾了。但是年初以来72松的升级后遗症实在太夸张了,访问速度慢不说,直至最近一个多礼拜来由于不确切原因,国内一直根本无法访问72松上的博客,用户对访问问题的反馈也没有得到任何对应措施。虽然要怪罪的是GFW,但是72松管理团队的不作为让人心寒,看来他们只是把这个wp宿主服务当成业余项目来玩的(看看yo2.cn吧,至少还专业不少)。
本来有考虑搬到yo2.cn,但是最后决定还是不要对免费服务侥幸了,还是自己折腾吧。于是我通过自己的国外vps做了个ssh通道爬上了72松上的管理后台,导出了备份数据,转移到了现在的另一个国外空间上,访问速度看起来也不错。既然打算更认真的做一个blog,那就还是自己动手更好吧,就这样了!
最近本blog的服务商72松在折腾服务器升级等事宜,导致目前从国内访问本blog及其困难,预计要到春节后才有可能恢复正常,到时候会启用国内服务器,从国内访问速度的问题可能得到解决。
总的来说72松的免费wp服务用到现在还是不错的,最主要是比自己host一个wp要省时省心。但目前这样的服务质量真有些让人受不了了,先等一下看服务器搞定后的效果吧,还是不行的话就搬yo2.cn外加买绑定域名服务算了。
作为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
logging是Python 2.3起自带的标准模块,可以用来从运行状态的程序中记录日志。logging模块的功能非常强大,可以非常灵活的向各种预定或者自定的目标输出日志。而利用标准的logging模块,Django程序就可以轻松实现运行环境下的日志输出,这对于开发以及部署环境下程序运行具体情况的监控和调试都是不可或缺的,所以我在这里总结一下自己的一些经验。
要让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模块可以通过日志级别非常方便的控制输出,不需要增减任何程序代码,只需要在settings.py中更改logging级别一行代码就行了。所以在开发过程中,可以尽量多用logging.debug输出对调试有帮助的内容。而在生产环境下,通过日志也可以方便的分析真实运行环境下的一些问题,便于调试修改bug。
如果用在Google App Engine,不需要指定输出文件,程序输出的日志都会直接保存在App Engine运行日志中,还可以通过正则表达式来搜索以往日志。
另外,通过Django debug toolbar,可以方便的直接在调试环境下直接在每个页面上查看输出的日志,非常好用。
所以,如果你在写Django程序,就不要再用原始的print了,用好logging可以事半功倍。
“极品座驾“上线以来,用户访问量一直运行在高位,上周末创下了单日pv 139万的记录。在这样的情况下,保持服务不间断是最重要的事情。目前我们支撑这样访问量的架构,是使用Nginx作为web服务前端,用fcgi方式运行django的python进程,再和前端Nginx连接起来。fcgi模式下,python进程把字节码装入内存,来提供最佳的执行速度。但是这样带来的问题就是更新了python代码以后,服务器没法动态装入新代码,而是需要杀掉现有fcgi进程然后spawn新进程才能达到更新代码的目的。虽然重启django fcgi进程的速度很快,但是不可避免的会导致用户服务的中断,在访问量大的情况下更是很危险的方式。为了达到不中断服务的目的,我们采用了这样的方法:
由于Nginx可以在运行状态下不间断的重载配置改动,所以在重载配置以后所有的访问请求都被转发到新的fcgi端口了。而在用户操作频繁的时候,访问请求从原fcgi端口被转到新端口也只需要很短的时间(可以借助netstat命令来观察)。这样一来,就可以实现在不影响用户操作的情况下不间断的切换到新代码了。
题外先说一句,最近太忙,欠着的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平台在国内来看是目前技术方面最严谨功能也最强的一个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.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平台在明年能逐步发展到脱离幼稚期。只是,其实一开始肯定能料到开发者的抗议,为什么还要来侥幸一把呢?
大家好!我是Marsbug团队的Damien。今天起,我会在本blog陆续发表一系列关于Python, Django, Google App Engine以及SNS应用开发的文章。
SNS应用可以说是web2.0时代mashup概念的一个非常成功的例子。从Facebook F8平台开始,SNS应用经过1年的发展已经深入人心。而在F8开放一年之际,国内的SNS领域也开始了一场开放平台大战,这也是我们开发者一展身手的好机会。今天,让我先从开发校内应用谈起吧。
目前Marsbug已经在校内、Manyou、51.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最新版,目前没有提供打包下载。
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这套工具来开发应用的。