django模板自动转义(autoescape)机制

用过php的人都头疼过那个magic quotes(特殊字符的转义)问题。经常需要判断哪个情况下自动了转义,那个情况下没有自动转义。不仅要防止忘记了转义,还要防止那些转义之后又被转义的情况。总之很头疼。django模板也有自动转义(html特殊字符)功能,根据文档和阅读源码,发现这个机制很有趣,它能巧妙的避免那些转义多次的问题。

一、状态机制

django把通过模板过滤器(filter)的数据分为3种状态:

1、原始态(Raw string)
即普通的str或unicode类型。如果开启了自动转义,这种数据会被自动转义;如果关闭了自动转义,那么这种数据则原样输出,不做任何改变。

2、安全态(Safe string)
这是已经做过了所有必要的转义。在它再次被修改前,是安全的,输出时不会再被转义。如果处于这个状态的数据经又过了某个过滤器,状态可能会发生改变。主要是SafeString和SafeUnicode,它们是SafeData的子类型。

3、需转义态(Strings marked as "needing escaping")
被标记为“需转义态”的数据,在输出时都会被转义。主要是EscapeString和EscapeUnicode,它们是EscapeData的子类型。

在开启自动转义的情况下,django模板数据过滤器链的尾端,都会根据数据状态进行转义,凡是处于“安全态”的数据都原样输出,凡是处于“原始态”或“需转义态”的数据都会被转义,并使之转换为“安全态”。最后只有处于“安全态”的数据才会最终将其内容输出至html文档中。在数据通过过滤器时,过滤器根据所提供的数据状态判断是否进行转义或其他操作,并根据转义或操作的结果决定是否改变数据状态。


二、内部处理流程

假设有源数据(data),要通过过滤器。
  1. 如果过滤器的 needs_autoescape 属性为True,则给该过滤器准备一个 autoescape 参数。
  2. 运行过滤器,得到一个新数据(new_data)。如果提供了autoescape参数,过滤器就可以根据autoescape的值(True/False)做必要的处理。
  3. 如果过滤器的is_safe属性为True,并且源数据(data)是SafeData实例,则将新数据(new_data)转换成“安全态”。否则,如果源数据(data)是EscapeData,则将新数据转换为“需转义态”。
  4. 返回处理好的新数据(new_data),即下一个过滤器的源数据。
  5. 进入下一个过滤器,重复步骤1-4 直至过滤器链中最后一个过滤器。
  6. 判断过滤器链条最终输出数据(final_data)的状态,如果final_data为“安全态”,则原样渲染到网页,如果final_data为其他状态,则会对final_data进行转义,并渲染到网页。

三、过滤属性的作用及用法

1、is_safe
仅当过滤器中未引入任何不安全数据时使用。但要注意如果替换或删除某些字符也会导致不安全,例如,将已经处于安全状态的数据中的链接<a>去掉了一个大于号变成<a。因此使用这个属性必须要保证真的不会对已经处于安全态的数据造成破坏。
过滤器定义:
@register.filter
def safe_filter(value):
    '%s <a href="#">x</a>' % value
    return nv
safe_filter.is_safe=True
模板:

<!--
python code:
data='<a href="http://www.google.com">Google</a>'
-->
{% autoescape on %}
<ul>
	<li>{{ data }}</li>
	<li>{{ data|safe_filter}}</li>
	<li>{{ data|safe}}</li>
	<li>{{ data|safe|safe_filter }}</li>
</ul>
{% endautoescape %}
显示为:

  • <a href="http://www.google.com">Google</a>
  • <a href="http://www.google.com">Google</a> <a href="#">x</a>
  • Google
  • Google  x

2、needs_autoescape
凡是带有这个属性的过滤器,必须提供一个名为autoescape的参数。值为True代表开启了自动转义,False代表关闭了自动转义。过滤器中一般会用conditional_escape与mark_safe等方法来辅助处理各类状况。
conditional_escape(html),根据状态转义,如果html状态为“安全态”,就直接返回html,否则就对html进行转义。
mark_safe(s),将s转换成“安全态”。

from django.utils.html import conditional_escape
from django.utils.safestring import mark_safe

def initial_letter_filter(text, autoescape=None):
    first, other = text[0], text[1:]
    
    #如果开启了自动转义,就根据text的状态进行转义
    if autoescape:
        esc = conditional_escape
    else:
        esc = lambda x: x
    result = '<strong>%s</strong>%s' % (esc(first), esc(other))
    return mark_safe(result)
initial_letter_filter.needs_autoescape = True


参考:
  1. http://docs.djangoproject.com/en/1.2/howto/custom-template-tags/#filters-and-auto-escaping
More

在GAE中迁移 django 0.96到1.2时遇到的模板问题

django 0.96 与 django 1.2 之间的细微差别造成很多麻烦事。


一、自动转换(autoescape)的问题

autoescape 主要是为了防止模板变量中含有标签等html元素,它会自动将html标签等元素转换实体。
例如:把
<script>
转换成
&lt;script&gt;

但是 django 0.96 没有autoescape,而django 1.2默认会自动对模板变量进行过滤。从0.96更换至1.2的时候,会发现很多不想被过滤的标签全都被过滤了,可以使用safe标记成安全字符。
如果使用了escape过滤器,则不管它出现在过滤链条的哪个位置,一律只会在链条的最后执行。并且如果变量已经标记为safe,那么escape则不会做任何事情。
例如:
{{ val|safe }}

解决办法有两种,一种是将原来模板中的escape去掉,再把不需要过滤的挨个修改成safe。另一种就是全局关闭autoescape:

{% autoescape off %}
....模板其他部分...
{% endautoescape %}


二、{% spaceless %}标签默认行为差别

在 django 0.96 中,去掉所有标签间的空白符号,仅标签间留一个空格。
在 django 1.2 中,去掉所有标签间的空白符号,标签间什么都不留。

举例:
<a href="#">This</a> <a href="#">is</a> <a href="#">mine</a>.
django 0.96 显示为:
This is mine.
django 1.2 显示成:
Thisismine.

这个解决办法很简单,不用这个标签即可,用与不用一般没什么区别。


三、{% include %}与{% base %}标签的路径问题

django 1.2 的标签中,不能使用上层路径,只能从模板根目录向下找。
在 django 0.96 中,可以这样使用:

{% include "../home.html" %}
而在 django 1.2 你必须这样:

{% include "path/to/home.html" %}

对比一下0.96与1.2的 django.template.loaders.file_system 模块,就会发现 get_template_sources 方法的文件路径结合方式不同。0.96 用的是一般的 os.path.join 方法,而 1.2 用的是 django.utils._os.safe_join 方法,后者限制不允许使用 base path 外的路径。而 include 和 extends 标签会先从base path开始寻找模板(这也许是bug?),如果路径里边有 “../” 可能会直接跳出这个 base path ,于是会触发异常:
>>> from django.utils._os import safe_join 
>>> safe_join('templates','../bbb.html') 
Traceback (most recent call last): 
File "<stdin>", line 1, in <module> 
File "C:\Python26\lib\site-packages\django\utils\_os.py", line 44, in safe_joi 
n 
raise ValueError('the joined path is located outside of the base path' 
ValueError: the joined path is located outside of the base path component 
>>> 

另外一个麻烦事就是在django 1.2中必须使用一个空app进行初始化设置才能正确找到模板。
举例,
目录结构:

project 
|--templates 
| |--outter.html
| |--bbb
| | |--in_b.html 
| |--aaa
| | |--in_a.html 
outter.html :
{% include "bbb/in_b.html"%}
in_a.html :
{% include "bbb/in_b.html"%}
main.py:

from google.appengine.dist import use_library
use_library('django', '1.2')

from django.conf import settings
#必须找个空文件(nothing.py)作为引子才能正确找到模板文件
settings.configure(INSTALLED_APPS=('nothing',))

from google.appengine.ext import webapp
from google.appengine.ext.webapp import util, template
import os

class MainHandler(webapp.RequestHandler):
    def get(self):
        #不论settings是否设置INSTALLED_APPS=('nothing',)
        #在outter.html中总能找到bbb/in_b.html或aaa/in_b.html
        self.render('outter.html', debug=True)

        #在aaa/in_a.html中,只有在settings中设置了
        #INSTALLED_APPS=('nothing',)时
        #才能正确找到 bbb/in_b.html ,反过来也是一样。
        self.render('aaa/in_a.html', debug=True)

    def render(self, tpl, vals={},debug=False):
        directory=os.path.dirname(__file__)
        path=os.path.join(directory, os.path.join('templates',tpl))
        self.response.out.write(template.render(path, vals, debug))

def main():
    application = webapp.WSGIApplication([('/', MainHandler)],
                                         debug=True)
    util.run_wsgi_app(application)


if __name__ == '__main__':
    main()


参考:
1、https://groups.google.com/forum/#!msg/google-appengine-python/YaqfeygoiaI/RtDh9pL6XJQJ

More

破解 Twitter Search API 的请求限制

You have been rate limited. Enhance your calm.

在Google App Engine上使用过Twitter Search API 的人可能会经常遇到这个头疼的问题,就是API的请求限额经常被刷爆。

出现这个问题有两个原因:首先要从urlfetch的实现原理来看。因为GAE是个云平台,它将资源整合分配。每次通过urlfetch发出一个请求时,首先GAE会在IP地址池里取得一个IP,然后用这个IP访问网络。而IP地址池中的IP都是给所有应用共享的,你也用,我也用,大家都用。另外一个原因,就是Twitter Search API的请求策略是按照 每IP*小时(基于IP的请求限制) 计算,而不是REST API的按照 每用户*小时(基于认证的请求限制)计算。由于GAE发出请求的IP是所有应用共享的,所以给这些IP的请求限额会必然会被海量的应用连续不断的请求刷爆。

有人提出过给search api加入认证(authenticate)就可以实现 每(用户*小时) 的请求策略。但经过实践证明这样做是不行的。

这个问题困扰了我很久,搜索各种资料都没找到什么有用的结果。后来发现了一个一直没有注意到的现象,那就是twitter官网的新界面搜索功能特别好用,而且只要修改了hosts,就可以用https在墙内使用。可Search API所用的域名search.twitter.com没有https支持,而且我在hosts中也没有设置任何search.twitter.com这个域名的IP。那这个搜索结果是怎么传过来的呢? 如果twitter官网使用的是 search.twitter.com 这个域名作为搜索api的地址,那肯定search.twitter.com也支持https,但事实上是不行的。显然,twitter官网肯定使用了别twitter.com域名来搜索!

接下来就简单了,用Firefox/chrome打开https://twitter.com,观察网络活动。于是抓到了一个名叫 pheonix_search 的隐藏API。大概是这样的:


一、API地址
https://twitter.com/phoenix_search.phoenix


二、参数
q - 必须,要搜索的关键字。
since_id - 可选,返回结果的id应全都大于since_id。
page - 可选,这是一个很奇怪的参数,twitter用它来获取搜索结果的下一页。它本身又包含了很多其他参数。
include_entities - 可选,是否包含推文的“实体”。
contributor_details - 未知,在Httpfox中观察它的值一直是:true
domain - 未知,在Httpfox中观察它的值一直是:https://twitter.com
format - 未知,在Httpfox中观察它的值一直是:phoenix

详解page参数:
page是个很奇怪的参数,它本身又包含了很多其他参数(大致)。
   page - 这个不是上边说的page而是真正的页码。
   max_id - 返回结果应全都小于max_id
   rpp - 每页返回的推文个数
   q - 请求关键字
举例(注意这里page参数必须是urlencode过的):
page=%3Fpage%3D2%26max_id%3D44034426279165952%26rpp%3D20%26q%3Dtwitdao
解码后:
?page=2&max_id=44034426279165952&rpp=20&q=twitdao


三、返回数据
json格式,
{
	"statuses":[...], //推文实体数组
	"next_page":false, //下一页,也就是翻页时的page参数
	"error":null //出错信息
}


四、用法
假设要搜索 "twitdao" ,
第一次请求:
https://twitter.com/phoenix_search.phoenix?q=twitdao&format=phoenix
刷新(最新符合关键字的推文):
https://twitter.com/phoenix_search.phoenix?q=twitdao&since_id=43865302966075392&format=phoenix
翻页(更早符合关键字的推文):
https://twitter.com/phoenix_search.phoenix?q=twitdao&page=%3Fpage%3D2%26max_id%3D44034426279165952%26rpp%3D20%26q%3Dtwitdao&format=phoenix

实例代码:
http://code.google.com/p/twitdao/source/browse/trunk/twitter.py?spec=svn62&r=61#238


五、一些特性
1、可以通过加入认证提升Rate Limit
这也是本文主要的目的,就是破解 Twitter Search API 的请求限制。经过一番测试,在pheonix_search的请求中加入OAuth header后,果然可以从“基于IP的请求限制”转化成“基于认证的请求限制”!
2、返回真正的推文实体列表
pheonix_search API 的另外一个优点就是它返回的推文实体是完整的,与home_timline方法返回的推文实体相同。而普通Search API返回的推文实体并不是完整的,并且有些id数据可能会不正确。
Twitter Search API文档中的警告:
Warning: The user ids in the Search API are different from those in the REST API (about the two APIs). This defect is being tracked by Issue 214. This means that the to_user_id and from_user_id field vary from the actualy user id on Twitter.com. Applications will have to perform a screen name-based lookup with the users/show method to get the correct user id if necessary.


六、测试办法
如何知道一个API是“基于IP的请求限制”还是“基于认证的请求限制”呢?每次请求 twitter api 的时候都会返回几个有关请求限制(Rate Limit)的http首部,主要有:
X-RateLimit-Limit - 每小时限制请求数
X-RateLimit-Remaining - 剩余的请求数
X-RateLimit-Reset - 请求限制重置时刻

假设所有请求都用同一个IP。

1、首先,对api发出请求,假设返回:
X-RateLimit-Limit: 150
X-RateLimit-Remaining: 149
X-RateLimit-Reset: 1299344165

2、然后,换一个用户,再次对该api发出请求,
--若返回:
X-RateLimit-Limit: 150
X-RateLimit-Remaining: 148 (仍然在减少)
X-RateLimit-Reset: 1299344165 (注意这里不能变,如果变了要重新开始)
继续更换用户并发出请求,如果X-RateLimit-Remaining持续减少,并且X-RateLimit-Reset一直未变。
则该api请求限制是“基于IP的请求限制”。
--若返回:
X-RateLimit-Limit: 150
X-RateLimit-Remaining: 149 (重新开始计数)
X-RateLimit-Reset: 1299399168 (变了!)
继续更换用户并发出请求,如果X-RateLimit-Remaining按照的减少是基于用户的,并且每个用户都有自己独有的X-RateLimit-Reset。
则该api请求限制是“基于认证的请求限制”。


参考:
  1. https://dev.twitter.com/pages/rate-limiting
  2. https://groups.google.com/d/msg/google-appengine/okY7XSO3EFQ/37AvPss4K88J
  3. http://codeleaks.net/2010/08/19/%E8%AF%B4%E8%AF%B4-twitter-for-iphone-%E7%9A%84-n-%E5%AE%97-%E2%80%9C%E7%BD%AA%E2%80%9D/
  4. http://dev.twitter.com/pages/tweet_entities
More

[转] 在淘宝被诈骗5.46万始末

这个很多人可能都看了,不过这个过程还是值得注意的。。

原文:http://blog.rexsong.com/?p=12451

今早只睡了三个小时,下午从Betacafe北京店开业现场回来,带几位朋友在和平街附近刚吃完饭到家,回了北京市公安局的电话,来好好梳理下今天混乱的思绪。 事情得回到昨晚在淘宝上买邮票...

今早只睡了三个小时,下午从Betacafe北京店开业现场回来,带几位朋友在和平街附近刚吃完饭到家,回了北京市公安局的电话,来好好梳理下今天混乱的思绪。

事情得回到昨晚在淘宝上买邮票,通过搜索找到目标货物,东西暂时不便透露,但量是100件。当时客服回话说“加下我们老板吧,要的多,价格得他订。”我加了这位QQ号为442165303的骗子,聊了会儿感觉还不错。但我发现他们这个是新店,并且仅有的十几个信用都是刷的,于是提出质疑,他答复说是新开的店。我想想现在确实很多卖家这么做,没深究。

后来我要求发实物图看看,对方发了个rar压缩包,看到大小只有126K也没提出质疑,因为可能手机拍的。解压后是个类似“图片”的文件,于是我点了,然后就报错(典型的木马病毒)。我当时就问对方“为什么发.exe可执行文件?”并同步迅速检查了系统管理设置,还有进程服务,没发现异常。对方也回答“发成店铺装修工具了”,然后重新又给了份。虽然意识到可能已经中木马,但想到淘宝、支付宝是双重验证机制,并且有证书支持,应该不会这么容易出问题。所以,又没深究。

最后我们确定了一笔¥39,000的订单,下单时间是0点10分。付款时间大概是0点30分,印象中顺利付款成功,因为这个过程用好几年淘宝从来没在意过。为预防钓鱼网站,我每一步都仔细验证了是否真正的淘宝链接,确认都没问题;为预防键盘记录,我在输入密码采用了交叉和混淆的安全措施,应该也比较保险。过了约20分钟,骗子告诉我说还有20件,一起给我算了。我想想也合适,于是在0点58分下了第二个¥7,800的订单,随后马上进行了付款操作。但奇怪的是,网银支付成功后最后返回淘宝确认的页面居然出现错误!如下图

我第一反应从没见过这种提示,第二反应可能淘宝出问题,于是毫不犹豫点击了“请您重新支付”,返回“已买到的宝贝”看到状态也确实“等待买家付款”,于是我马上又重新支付了一次(这个循环目地在于把钱骗干净)。这回我特地留意了每个步骤,原来网银确实提示支付成功,但只是返回淘宝后出现“错误”,而返回“已买到的宝贝”列表看到也确实还都是“等待买家付款”状态。我开始感觉有点不对,但又找不到具体原因。骗子一直说“是有网络问题,我以前也碰到过。”并劝我早点休息,不断打乱我思路,很没头绪。

而后我在招行网银活期交易记录里,看到三笔订单都已经支付,于是我怀疑可能是网银或者淘宝出问题,这不是没有可能。最终问题的关键点,我发现在订单号上。之前支付宝交易订单号都是10位,而新操作的三笔订单都是6位,说明很有可能这几笔订单根本没支付给支付宝。如下图(第四笔订单为后来测试)

想到这里不仅倒抽一口冷气,基本确认被诈骗,三笔订单总计达到¥54,600!。联想骗子不断“拖延时间”的托词,可能是在等待提现或支付。于是马上打95555客服咨询资金去向,当时已差不多凌晨1点30分,居然还有人工客服。结果确认了我的猜想,三笔订单根本没支付给淘宝,而是到了上海的“汇付天下有限公司”,并且提供了4008202819这个客服电话。接着打到上海,同样也还有人工客服,对方告知现在正在“结算”,无法查询这三笔订单到了哪里,需要等到今早6点才可以。并且对方男性客服提供了很多参考建议,包括提醒我到支付宝后台尝试给自己“充值”1毛钱,看能否顺利到帐。我测试了1块钱,支付宝余额果然没变化,也就是上边截图中的第四笔订单。

此时已过凌晨2点,算下到6点还有四小时,本打算“坐等”,但后来实在熬不住3点睡了。

5点50被闹钟吵醒,爬起来等刚过6点再给上海打电话。接电话还是那哥们,很顺利告知钱已打到“网之易信息技术有限公司”,三笔订单号分别是:185511506, 185517495, 185517748,也提供了客服电话020-83918160,说公司的游戏部门在广州。这会我才反应过来,这不就是广州网易吗?难怪眼熟呢。马上打客服电话,没有这种业务选项,我随便挑了个游戏进人工服务。客服先说他们查不了,我表示非常严重后,他好像在记录,又说只有“订单号”无法查询,需要“流水号”,然后需上报处理等等。

其间,我不断在网络上搜索资料,终于发现了“支付宝大盗”这类木马病毒。已有不少淘友有投诉,而且我看了有类似经历,只是金额太小,公安机关不予立案,但媒体已经在关注。如果说我们这样的互联网从业者都会上当受骗,难以想象普通老百姓会如何。

这条路走不通,我选择打广州110,此时快早上7点了。广州110的意思,需要在事发当地立案,他们才可以受邀协查。于是我直接打北京110,接线员大致了解了案情和金额,让我在家等。大概10分钟,民警给我打电话到了楼下来接我,我们一起去了和平街派出所。一位民警同志先了解了情况,然后做了个临时立案,因为金额较大,需要刑警过来处理。大概8点,我抽空给网易公司的朋友彭毅打了个电话,希望网易公司能先主动冻结资金。电话陆续反馈过来,第一确实有这回事,第二钱已经全消费了,第三买了1万多的游戏点卡和4万的手机充值卡(已充给100多位用户)。

刑警大概9点过来,重新做了遍笔录,这位同志对这种事儿了解还比较多,沟通很顺畅。基本都认同,高科技团伙作案,分工明细效率极高。最后他的意思,往后警方会找网易(消费出口)和其他公司调查,但结果很可能和目前通过朋友得到的消息没什么差别。

出派出所后与彭毅联系了下,正好网易支付技术团队在北京,打车去五道口清华科技园D座25层网易公司,我们和技术人员简单碰了下。然后我就先回家了,13号线上不仅坐过柳芳到了东直门,更悲剧还是被列车员叫醒的。迷迷糊糊那会儿,心情真是跌到了冰点,主要挫败感所致。

这个事情我倒没怎么着急,来回反复考虑,自己肯定有责任,但淘宝、支付宝、网易也都脱不了干系。首先淘宝店铺是实名认证的,骗子不使用旺旺而是用QQ沟通应该有原因。其次支付宝我认为应该是被“劫持”了会话,伪装很好,但好像也太容易了吧?第三网易游戏平台居然几小时内就支持能把几万块洗干净,而且还是在后半夜,显然不合理的。

其实也认识很多淘宝、支付宝、网易公司的朋友,而且我们公司还是淘宝合作伙伴。说来也巧,正好下午去参加Betacafe北京店开业酒会,可以碰到很多淘宝、支付宝、网易的家伙。截止上午11点,网易方面除处理了帐号,也已单方面报警,并且对交易操作及时做了限制。

骗子车轻驾熟,分寸把握的很好,很多细节都是对后来的铺垫。整个过程精心策划,充分利用了各平台的弱点。资金要倒好几手,正好我这边又是网银支付而不是余额支付,查询资金去向浪费了不少时间。现在其实资金能否顺利追回已经不重要,关键怎样规避这种问题再次发生,净化电子商务环境。希望更多淘友看到避免上当,也包括各家有干系的公司从中得到教训迅速改进。

好吧,还是说点轻松的事儿,本来咱们UCDChina的朋友约好今天早上十点在Betacafe北京店碰头。我在八点电话把Angela老师吵醒之后,正在与警官讨论时,收到条群发短信“周董开会,鸟哥又进公安局了,姐要被小猪安排去应酬……”

2月27日 上午11:27
网易广州客服来电,主要表示这个事情会全力配合警方,另告知三笔订单确实是充值到了“网易宝”某账户上,并已对账户做了相关处理(昨天已知)。在我表示他们也有责任的时候,客服说“我们也是无辜的,也不知道淘宝购物的钱怎么会充值到网易宝上边。”

本文可以随意转载。
More

使用W3C Geolocation API

一、浏览器支持状况
IE 9.0+
Firefox 3.5+
Safari 5.0+
Chrome 5.0+
Opera 10.6+
iPhone 3.0+
Android 2.0+

二、geolocation.getCurrentPosition()的用法

/* 获取信息成功时的处理函数 */
function successHandler(pos){
	/*
	* pos是这样的一个对象:
	* {
	* 	coords:{
	*		latitude:纬度
	* 		longitude:经度
	* 		altitude:海拔
	* 		accuracy:精度
	* 		altitudeAccuracy:海拔精度
	* 		heading:设备的移动方向(0°≤heading<360°)
	* 		speed:设备的移动速度
	*	},
	* 	timestamp:1298477342721500
	* }
	*/
}

/* 错误处理函数 */
function errorHandler(e){
	switch(e.code){
		case e.PERMISSION_DENIED:
			//用户或浏览器拒绝提供地理位置时
			break;
		case e.POSITION_UNAVAILABLE:
			//获取位置失败时
			break;
		case e.TIMEOUT:
			//获取位置超时
			break;
	}
	// e.message 是出错信息文字。
}

/* 选项 */
var options = {
	//超时时间(毫秒)
	timeout:5000,
	//位置信息的最长缓存时间(毫秒)
	maximumAge:9999,
	//是否启用高精度定位
	enableHighAccuracy:true
}

//判断浏览器是否支持Geolocation API
if(navigator.geolocation){
	//getCurrentPosition接受3个参数,其中后边两个是可选的
	navigator.geolocation.getCurrentPosition(
		successHandler,
		errorHandler,
		options
	);
}

三、geolocation.watchPosition()与clearWatch()的用法
watchPosition(),它一般用在移动设备上,用于连续追踪位置信息,每次位置更新时都会调用一次successHandler。

if(navigator.geolocation){
	//watchPosition()接受3个参数,其中后边两个是可选的,与getCurrentPosition()相同。
	//调用完成后会立即返回一个数字id,与setTimeout()相似。
	var watch_id = navigator.geolocation.watchPosition(
		successHandler,
		errorHandler,
		options
	);

	//解除追踪,watch_id就是watchPosition()所返回的那个数字。
	clearWatch(watch_id);
}

参考:
  1. http://www.w3.org/TR/geolocation-API/
  2. http://diveintohtml5.org/geolocation.html
  3. http://www.jsmix.com/html5/try-out-geolocation.html
More

使用Instagram API

这里描述的是官方公布的API,而不是通过hack得到的api。
有兴趣可以去这里注册 http://instagr.am/developer/ 。可能会有一个审核激活的过程,大约要几天的时间。
完成后会收到一封邮件,然后就能登录查看文档了。但这个文档似乎暂时并不是很丰富,因为我没找到用于上传的api。

这里主要写了一些使用上的注意事项。

1、外链图片
GET http://instagr.am/p/{shortcode}/media
它接受一个参数size,t表示缩略图 m表示中等, l表示最大,默认为m。
假设有链接 http://instagr.am/p/BSJRn/ 则可以:
<img src="http://instagr.am/p/BSJRn/media?size=m" />
效果:


2、shortcode 转换成 media-id
shortcode 就是 http://instagr.am/p/BSJRn/ 里边的 'BSJRn'
截止目前,我在那个文档中并没有找到有关如何将shortcode 转化成 media-id 的描述。但是通过一些猜测和验证,很容易就会得出正确的算法。
很简单,实际上shortcode就是一个64进制数字,解码的过程就是64进制转化成10进制的过程。
代码:

#A代表0,B代表1,..., _代表63
_chars='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'
_chars_num=len(_chars)

def deshortcode(t):
    t=str(t)
    n,i=0,len(t)-1
    for c in t:
        if c not in _chars: return 0
        n+=(_chars.index(c)*pow(_chars_num, i))
        i-=1
    return n

print deshortcode('BSJRn')
# 21533799

3、参数中的 redirect_uri 必须与注册中的 CALLBACK URL 相同
这一点是跟Twitter API 不同的地方,在注册Twitter客户端时,那个CALLBACK URL可以随便写;而Instagram 则必须跟注册时的相同,否则会硬邦邦的告诉你这样不行。
你可以点击 这里 可以注册一个 Instagram 客户端,不过你必须首先通过之前所说的那个验证才行。

4、验证过程比Twitter API简单得多
不用什么OAuth库,也不用什么参数签名(大概这就是OAuth2.0吧?)。直接通过https获得access_token,然后用这个access_token就可以做任何事情了。特别简单。文档中都有描述这里不多说了。 More

用GAE访问fanfou API,支持发图

可下载版本在这里:https://gist.github.com/789880

# -*- coding: utf-8 -*-
from django.utils import simplejson as json
from google.appengine.api import urlfetch

import urllib
import mimetypes
import base64
import random

_api_url = 'http://api.fanfou.com/'

_http_methods={
    'GET':urlfetch.GET,
    'POST':urlfetch.POST,
    'HEAD':urlfetch.HEAD,
    'PUT':urlfetch.PUT,
    'DELETE':urlfetch.DELETE
}

def _generate_boundary(length=16):
    s = '1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_'
    a = []
    for i in range(length):
        a.append(random.choice(s))
    return ''.join(a)

def _get_content_type(filename):
    return mimetypes.guess_type(filename)[0] or 'application/octet-stream'

def _encode_multipart_formdata(fields, files=[]):
    """
    fields is a sequence of (name, value) elements for regular form fields.
    files is a sequence of (name, filename, value) elements for data to be uploaded as files
    Return (boundary, body)
    """
    boundary = _generate_boundary()
    crlf = '\r\n'

    l = []
    for k, v in fields:
        l.append('--' + boundary)
        l.append('Content-Disposition: form-data; name="%s"' % k)
        l.append('')
        l.append(str(v))
    for (k, f, v) in files:
        l.append('--' + boundary)
        l.append('Content-Disposition: form-data; name="%s"; filename="%s"' % (k, f))
        l.append('Content-Type: %s' % _get_content_type(f))
        l.append('')
        l.append(str(v))
    l.append('--' + boundary + '--')
    l.append('')
    body = crlf.join(l)
    return boundary, body


class Fanfou:
    def __init__(self, username, password):
        self.username=username
        self.password=password
        self.http_status=0
        self.http_headers={}
        self.http_body=''

    def _fetch(self, method, url, params={}, headers={}, files=None):
        payload=None
        if method.upper() in ['POST','PUT']:
            if files and type(files) == list:
                boundary, payload = _encode_multipart_formdata(params.items(), files)
                headers['Content-Type']='multipart/form-data; boundary=%s' % boundary
            else:
                payload=urllib.urlencode(params)
        res=urlfetch.fetch(url, payload, _http_methods[method.upper()], headers)
        self.http_status=res.status_code
        self.http_headers=res.headers
        self.http_body=res.content
        return res.content

    def api_call(self, http_method, api_method, params={}, files=None):
        raw_content=self._fetch(
            http_method,
            ''.join([_api_url,api_method,'.json']),
            params,
            {'Authorization':'Basic %s' % base64.b64encode('%s:%s' % (self.username, self.password))},
            files
        )
        return json.loads(raw_content)

用法:

from fanfou import Fanfou 

fanfou=Fanfou('name','pass') 

#时间线
fanfou.api_call('GET', 'statuses/public_timeline') 

#传图 
fanfou.api_call('POST', 'photos/upload', {'status':'test...'}, files=[('photo','aaa.jpg', photo_data )])
More

给 urlfetch 加上自动重试功能

在使用Google App Engine 的 urlfetch 请求其他服务时。时不时会遇到因目标服务出现错误(如超时、500 等)导致自己的程序出现错误。

例如 Twitter API 不稳定时,经常会返回一个500错误,如果自己的程序不加处理,一定会跟着出错。即便加入错误处理,也会给用户一个错误页面,这个体验很不好。实际上这样的错误通常只是暂时的随机出现的。只要重试几次就能获得一个满意的结果。

原理:
把urlfetch.fetch放入一个循环中。若请求成功,则返回数据;若抛出 DownloadError 异常,则开始新一轮循环。如果出错次数大于一定数值,那么意味着对方服务器差不多已经死彻底了,不想出错都不行了,就退出这个循环并抛出异常。

代码:

# xfetch.py
from google.appengine.api import urlfetch

_max_fetch_count = 5

def fetch(url, payload=None, method=urlfetch.GET,
headers={}, allow_truncated=False, follow_redirects=True,
deadline=None, validate_certificate=None):
    http_body=''
    for count in range(_max_fetch_count):
        try:
            http_body = urlfetch.fetch(url, payload, method, headers, allow_truncated, follow_redirects, deadline, validate_certificate)    
            return http_body
        except urlfetch.DownloadError, e:
            continue
    raise Exception('Max fetch count exceeded.')

注意事项:
  • _max_fetch_count*deadline 必须小于等于 request duration
  • 必须考虑到请求次数的限额,在个人负担允许范围内使用。

More

咱也开个博

写点啥呢?
想到再说。
More