安装Node.js

Linux

1
2
curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash -;
sudo apt-get install -y nodejs;

Mac OSX

1
2
curl "https://nodejs.org/dist/latest/node-${VERSION:-$(wget -qO- https://nodejs.org/dist/latest/ | sed -nE 's|.*>node-(.*)\.pkg</a>.*|\1|p')}.pkg" > "$HOME/Downloads/node-latest.pkg" && sudo installer -store -pkg "$HOME/Downloads/node-latest.pkg" -target "/";
brew install node;

参考

旅行,写作,编程

花了10个月的时间做世界环游,途经非洲,东南亚,澳洲,中南美洲里的17个国家和地区。这次旅行的主题就是冲浪和摄影。
出席在香港,日本,美国和伦敦举行的会议
启程时给O’Reilly出版公司写了一本书,书名叫做《JavaScript Web Applications》
另外写了一本关于CoffeeScript的书,很快就会由O’Reilly公司出版。
写了大量的开源库,例如Spine, Spine.Mobile, GFX, 和 Juggernaut.
筹划了一个创业公司的框架
出席伦敦2011FOWA会议
最后,我在Twitter公司找到了一份工作
那么,让我从一年前开始,那是2010年9月,我刚好从一个我合作创办的公司里出来,尽管这段经历是很有价值的,但无休无止的长时间苦干让我精疲力 尽。我回到了英格兰,需要对未来做一些思考。我一直有一个梦想——移居美国(几年就好),所以,我在Google记事本上写了下面的话:

人生的选择:
去纽约哥伦比亚大学深造
坏处 - 非常昂贵,并不一定能学到什么真正有用的东西,无聊?
好处 - 那是一个纽约的大学!
写一本书,申请 01 签证
坏处 - 需要大量的时间,有风险
好处 - 对事业有好处,有趣
等待。去纽约度一次假(3个月)。等待创业签证。
很容易 - 不是那么有趣

也许选第二个,不行就选3?
最终我选择了2,我已经对JavaScript web应用研究了很久,我要写一本这方面的书,为什么不边做环游世界的旅行、边写书呢?这也是我一个梦想呀。我从oneworld买了一份环游世界的机票(比你们想象的要便宜),决定下周去我的第一站,南非。

如果你从来没有到过非洲,你应该去一次。那里的景色原始而美丽,对那些没有体验过这种景色的人,你很难用言语描绘明白。几年前我就喜欢上了南 方,那时我在东海岸做了一个为期3个月的冲浪旅行。这次,我只有一个月的时间,穿越特兰斯凯,从开普敦到德班。当我在南非旅行时,我的写作也开始了,把早 期向O’Reilly提交的书的框架里的数章填充了材料。

特兰斯凯是南非非常具有乡野特色的地方,到处是连绵的小山,一些小村庄和土堆的茅屋。他们仍然沿袭着酋长制度,有一个首领,大多数的当地人靠捕 鱼为生。我们在高低不平的土路上颠了两天才到达我心仪的地方,一个美丽的海湾,叫做咖啡湾(Coffee Bay)。在那里,我休整了一下,从网上下载了一些相关资料,为更远的海湾远征做准备。

我还清晰的记得我们走了数里地来到那个未开垦的海滩,我们从那些一个个被黄沙和小丘孤立的村庄穿行而过。有一个地方,我们要过一条大河,我们需 要游过去,我把背包举过头顶,以免里面的相机和iPod遇到水。非洲是一个让你脱离尘世的地方,解放你的思想,重新认识人生最重要的东西是什么。

下一站是香港,在那里,我度过了我的21岁生日,接着,我从陆路由新加坡到越南河内。很多人不相信香港70%的面积由自然公园覆盖,我徒步走了几条精彩的景观路线,非常的精彩壮观,比如:香港龙脊。有几天,我在boot.hk这个网站上闲逛,这是一个协作工作的网站,我顺便教了一个同行的游客如何使用ruby。然后,到了夜里,我跟Soho里的一些冲浪爱好者狂欢到凌晨。

从泰国到柬埔寨到越南是我这次旅行中做喜欢的部分,如果你从没有到过亚洲,你绝对应该去一次。这些国家非常的漂亮,气候非常的好,食物美味可口,人们非常友善。吴哥窟是世上最神奇的地方之一,每个人都应该去看看。是Trey Ratcliff的照片把我吸引到了那里,我的很多其它旅游目的地也是受了他的影响。那个家伙是很多旅游地的第一宣传者。

在一些无名的小博客中,我听有人说过一个很远的美丽的小岛,在柬埔寨的海边。说小岛的Sihanoukville这个地方有个酒吧,说只能坐小 渔船到那里。我,还有几个非常好的朋友,乘坐晚上的大巴,开始寻找这个传说中的酒吧。搜索差不多进行了一整天,每一个问过的酒吧都把我们指向另外一个酒 吧。最终,我们问了出来,并在第二天早晨做短程巴士去了那个地方。

上面的照片上是海岸边一个10美元一晚的小木屋。从当地居民区离开后,我们的队伍像小岛上唯一的人,我们随性自由的奔跑。白天我们懒懒的躺在海滩上,吃着岛上厨师准备的鲜美可口的水果沙拉,在夜晚,我们在到处是浮游生物的海里游泳。

下一站是越南,我们沿着湄公河支流来到一个边界上的小镇,我们是这里唯一的西方人,交流成了最大的问题。幸运的是,我们发现一个也许是镇上唯一会说英语的人,他骑车当我们的向导。当我的信用卡被那里的一个自动取款机吞掉了后,他提供了我很大的帮助!

我们的队伍分成了几路,在我到达越南时,我的书正在按计划完成,进行的非常顺利。此时,我在西贡多待了几周,让我在书的好几章上有了重大的进展,正好是中国旧历新年,气氛非常的壮观热闹。

接着是日本,澳大利亚,新西兰和夏威夷。我很难把我所有的感受都在这篇文章里写出来,但说这是此生难忘的一段历程是不为过的。把如此多的美景都 放到一个国家里,太让人赞叹了,我说的正是新西兰。我最喜爱的一段记忆是沿着Wanaka的一个湖边在阳光下跑步,还有就是背着食物和生活用品,徒步数天 穿越Routeburn的大山。在这个国家的旅途中,我结识了好几个值得一生相伴的好友。这是一个真正的天堂。

就在我环绕新西兰的南部岛屿时,我的书终于完成了,提交给了技术编辑校对。

接下来是纽约和旧金山,这两个神奇的地方到处是天才的程序员,有些人我很幸运的认识。Techcrunch Disrupt办的很精彩(我高度推荐hackathon)。

在从纽约到旧金山的中途停留期间,我在各种公司了进行了不少的求职面试,最终在Twitter公司找到了一份做前端开发的工作。要在那里和杰出的团队一起工作,我不能不高兴的颤抖,而去旧金山,同样也是我此生的一个梦想。

当签证的事办下来了后,我去了中、南美洲旅行,同时开发了我的一个小工程:一个JavaScript MVC框架库,叫做Spine。我到了哥斯达黎加,巴拿马,秘鲁,Bolvia,和阿根廷。 秘鲁是我的最爱,尽管那里的海拔给我带来了不少麻烦,我大部分的时间都在探险。下面的图片是哥斯达黎加传说中神奇猎鹰,是在我爬下世界最深的峡谷时拍到的。

当我在哥斯达黎加时,微博上有个叫Roberto的家伙给我发了条信息,说他读了我的书,问我是否有兴趣一起冲浪。我欣然同意,坐上去圣何塞的 汽车,在几天后和他会了面。那天我们一起在他海边的公寓里开发Spine和Ruby项目,使用移动硬盘,用汽车电源给笔记本充电。当电量不足后,让太阳能 板补充能量,我们去冲浪。

我推荐大家去写一本书,特别是边旅游边写书。可以想象,如果我不去旧金山去看一看,我可能还在旅途中,做顾问,去创业。当作家并不能让你直接的 挣到很多钱,但它绝对能提升你的身份地位,给你带来很多潜在的机会。事实上,写作过程让我真正享受的是,我可以认真深入的研究一个题目。

这一年是我这辈子目前为止最好的一年,而我感觉今后的一年会更好。当我如今定居下来后,我并没有感觉旅行对我的吸引力减少了;我始终把签证放到一个口袋里,而另一个口袋里装着钱包,当召唤降临,随时准备离开。

可是,这篇文章并不是关于我的旅行,它是要发送一个信号:

对于程序员来说,有个得天独厚的条件,就是这种职业可以远程工作或边旅游边工作,这是其它职业办不到的。当然,也不都是这样,在我的旅途中,我 没有碰到第二个跟我的做法相似的程序员。这种情况让人悲哀。我想向程序员们送出的信息是,不要再找借口了,行动起来,你可以做到。一个人只有一生,我可以 向你保证,这样的生活才不枉世间走这一遭。

就像我,我感到极度的幸运,能这样的生活,去发现我的热情所在,去做每天我喜欢做的事情。你可以看出,大部分我现在的境遇并非偶然或侥幸,这是计划,追求,工作的结果。

一份汗水,一份收成。

这篇文章的目标不是做一些自我陶醉似的炫耀和大话,而是向大家演示如何立下目标,鼓励大家去做相似的事情。想清楚你现在的处境,这一年内你想得到什么,制定出一系列具体的能让你到达这些目标的步骤。追随你的梦想。

原文地址:https://www.oschina.net/news/23952/traveling_writing_programming

Django QuerySet

字段查找过滤

操作符 含义
__exact 精确等于 like ‘aaa’
__iexact 精确等于 忽略大小写 ilike ‘aaa’
__contains 包含 like ‘%aaa%’
__icontains 包含 忽略大小写 ilike ‘%aaa%’,但是对于sqlite来说,contains的作用效果等同于icontains。
__gt 大于
__gte 大于等于
__lt 小于
__lte 小于等于
__in 存在于一个list范围内
__startswith 以…开头
__istartswith 以…开头 忽略大小写
__endswith 以…结尾
__iendswith 以…结尾,忽略大小写
__range 在…范围内
__year 日期字段的年份
__month 日期字段的月份
__day 日期字段的日

Reference

当我跑步时,我谈些什么

在肉体上时痛苦的,在精神上,令人沮丧的局面有时也会出现。不过,“痛苦”对于这一运动,乃是前提条件的东西。不伴随着痛苦,还有谁来挑战铁人三项赛事和全程马拉松这种费时耗力的运动呢?正因为痛苦,正因为刻意经历者这种痛苦,我才从这个过程中发现自己还活着的感觉,至少是发现了一部分。我现在意识到:生存的质量并非成绩、数字、名次之类固定的东西,而是含于运动中流动性的东西。

HTTP缓存

缓存是某个资源(或文档)的副本。对于私有缓存,可以服务同一个客户端的对同一个资源多次的请求,比如浏览器缓存。对于共享缓存(或代理缓存),可以服务多个客户端对于同一个资源的请求,比如CDN。为什么需要缓存,缓存有哪些优点:

  • 减少网络延迟,更快的加载内容,提高用户体验
  • 减少冗余数据传输,降低网络负载
  • 降低服务器负载,服务器可以更快的处理请求

不同类型的缓存

  • 私有缓存 - 服务于单个用户
  • 共享缓存 -存储响应被很多用户复用

其他缓存,gateway cache, CDN, reverse proxy cache 和 部署在服务器上的负载均衡器,以获得更好的可靠性和性能.

HTTP Cache TypeHTTP Cache Type

私有浏览器缓存(Private browser cache)

私有缓存服务于单个用户.

共享代理缓存(Shared proxy caches)

共享缓存服务于多个用户.比如ISP或者企业可以配置一个web代理,作为本地网络基础架构,服务于多个用户.热点资源可以被多个用户复用,减少网络流量和延迟. 

常用的缓存条目的形式:

  • 成功的检索请求结果 - 200 OK响应,比如HTML 文档,图片或者文件
  • 永久重定向 - 301(Moved Permanently)响应
  • 错误响应 - 404(Not Found)结果页
  • 未完成的结果 - 206(Partial Content)响应
  • 不仅仅是GET请求的响应 - 定义一个合适的cache key

缓存处理步骤

对于一个HTTP GET请求报文,基本缓存处理包含7个步骤:

  1. 接收-缓存从网络中读取请求报文
  2. 解析-缓存对报文进行解析,提取HTTP首部信息
  3. 查询-缓存查询是否命中,如果没有,则从源服务器获取,并缓存到本地
  4. 新鲜度检测-检查副本是否新鲜,如果不新鲜,则与源服务器进行验证
  5. 响应-缓存用新的首部和缓存主体创建响应报文
  6. 发送-缓存通过网络将响应发送给客户端
  7. 日志-缓存记录这次请求的日志

缓存处理流程图:
HTTP_CACHE_GET_FLOW_CHARTHTTP_CACHE_GET_FLOW_CHART

缓存控制

服务器可以通过HTTP定义文档过期之前可以将其缓存多长时间。

  • Cache-Control: no-store
  • Cache-Control: no-cache
  • Cache-Control: must-revalidate
  • Cache-Control: max-age
  • Expires

no-store与no-cache

HTTP/1.1提供了几种限制对象缓存方式。 no-store和no-cache首部可以防止缓存未经证实的已缓存对象:

1
2
3
Pragma: no-cache
Cache-Control: no-store
Cache-Control: no-cache

标识为no-store的响应回禁止缓存对响应进行复制。标识为no-cache的响应可以在本地缓存中,只是在与服务器进行新鲜度再验证之前,缓存不能提供给客户端使用。

Cache-Control: max-age=3600表示收到服务器响应文档处于新鲜状态的秒数。max-age=0表示不缓存文档。

Expires: Fri, 05 Jul 2017, 12:00:00 GMT表示文档绝对过期时间。不推荐使用Expires,HTTP设计者后来任务,由于服务器之间的时间不同步或不正确,会导致文档新鲜度计算错误。

如果源服务器希望缓存严格遵守过期时间,可以在加Cache-Control: must-revalidate的HTTP首部。Cache-Control: must-revalidate响应告诉缓存,在事先没有跟源服务器再验证之前,不能提供这个对象的过期副本。缓存仍然可以提供新鲜的副本。如果缓存进行must-revalidate新鲜度是,源服务器不可用,缓存必须返回一条Gateway Timeout从错误。

缓存命中

缓存命中、未命中和再验证:
HTTP Cache HitHTTP Cache Hit

缓存命中率

由缓存缓存提供服务的请求所占的比例成为称为缓存命中率(cache hit rate)。命中率在0到1之间,0表示缓存全部未命中,1表示缓存全部命中。缓存服务提供者希望缓存的命中是100%,而实际的缓存命中率与缓存大小,缓存内容变化,请求者兴趣相似度等因素相关。

缓存新鲜度

文档过期

就像牛奶过期一样,文档也有过期时间。

1
2
3
HTTP/1.1 200 OK
Content-Type: text/plain
Cache-Control: max-age=484200
1
2
3
HTTP/1.1 200 OK
Content-Type: text/plain
Expires: Fri, 05 Jul 2017, 12:00:00 GMT

Cache-Control: max-age=484200是一个相对过期时间,max-age定义了文档的最大使用期,从第一次生成文档到文档不再新鲜为止,以秒为单位。

Expires:Fri, 05 Jul 2017, 12:00:00 GMT是一个绝对过期时间,如果过期时间已经过了,则文档不再新鲜。该首部要时钟同步

服务器再验证

缓存文档过期并不意味着该副本与服务器文档不一致,只是意味着要与源服务器进行再验证。

  • 如果再验证内容发生了变化,缓存获取新的副本,替换过期副本
  • 如果再验证内容没有发生变化,缓存只需要获取新的首部,对缓存的副本的首部进行更新

条件再验证HTTP首部:

Header Description
If-Modify-Since: 如果从指定日期之后文档被修改过了,就执行该请求。可以与Last-Modified服务器响应首部配合使用,只有在内容被修改后,才去获取新的内容
If-None-Match: 服务器可以为文档提供特殊的标签,而不是将其与最近修改时间相匹配,这些标签就像序列号一样

If-Modify-Since: Date再验证:

  • 如果自指定日期后,文档被修改了,If-Modify-Since条件为真,源服务器会返回成功的响应,包含新的过期首部和新文档实体
  • 如果自指定日期后,文档未被修改,If-Modify-Since条件为假,源服务器会返回一个304 Not Modified的响应,不包含文档实体内容

img

If-None-Match: Tags

有些情况下,仅使用最后修改时间是不够的。

  • 文档被周期性的重写,最后修改时间发生变化,而内容未改变
  • 服务器无法判定最后修改时间

HTTP允许用户对实体打标签,进行标识。

img

什么时候使用最后修改时间和标签验证?

如果服务器返回了ETag首部,客户端必须使用标签验证。如果服务器只返回了Last-Modified首部,客户端可以使用最后修改时间验证。

新鲜度计算算法

为了分辨文档是否新鲜,需要计算两个值,文档的使用期(age)和文档的新鲜生存期(freshness lifetime)。如果文档使用期小于文档新鲜生存期,则文档是新鲜的。

1
is_fresh_enough = True if age < freshness_lifetime

使用期

使用期包含了网络传输时间和文档在缓存的停留时间。

1
2
3
4
5
6
7
apparent_time = time_got_response - date_header_value
corrent_apparent_time = max(0, apparent_time)
age_when_document_arrived_at_our_cache = corrent_apparent_time

how_long_copy_has_been_in_our_cache = current_time - got_response_time

age = age_when_document_arrived_at_our_cache + how_long_copy_has_been_in_our_cache

基于Date首部计算apparent使用期

apparen时间:

apparent时间等于获得响应时间减去服务器发送文档时间:

1
apparent_time = time_got_response - date_header_value

为了防止由于服务器时间不同步导致apparent_time为负,进行时间修正:

1
2
apparent_time = max(0, apparent_time)
age_when_document_arrived_at_our_cache = apparent_time

对网络时延对补偿:

如果文档在网络或服务器中阻塞了很长时间,相对使用期的计算可能会极大的低估文档使用期。缓存知道文档请求时间,以及文档到达时间。HTTP/1.1会在这些网络延迟上加上整个网络时延,一遍对其进行保守校正。这个从缓存到服务器到缓存高估了服务器到缓存延迟,它是保守的。如果出错,只会使文档看起来比实际使用期要老,并会引发不必要的验证。

1
2
response_delay_estimate = time_got_response - time_issued_request
age_when_document_arrived_at_our_cache = apparent_time + response_delay_estimate

Note:该时延补偿会导致最后计算文档使用期大于实际的文档使用期。apparent_time是包含网络时延的,对网络时延补偿是否必要?在服务器负载较高,对服务器的处理时间进行补偿倒是很有必要。

缓存停留时间:

1
how_long_copy_has_been_in_our_cache = current_time - got_response_time

img

新鲜生存期

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def calculate_server_freshness_limit(**kwargs):
heuristic = False
server_freshness_limit = default_cache_min_lifetime
if max_age_value_set:
server_freshness_limit = max_age_value_set
elif expires_value_set:
server_freshness_limit = expires_value_set - date_value
elif last_modified_value_set:
time_since_last_modify = max(0, date_value - last_modified_value)
server_freshness_limit = int(time_since_last_modify*lm_factor)
heuristic = True
else:
server_freshness_limit = default_cache_min_lifetime
heuristic = True

if heuristic:
if server_freshness_limit > default_cache_max_lifetime:
server_freshness_limit = default_cache_max_lifetime
if server_freshness_limit < default_cache_min_lifetime:
server_freshness_limit = default_cache_min_lifetime

return server_freshness_limit

LM-factor算法计算新鲜周期

  • 如果已缓存文档最后一次修改发生在很久以前,它可能是一份稳定的文档,不会突然发生变化,因此将其汲取保存在缓存中比较安全
  • 如果已缓存的文档最近被修改过,就说明它很可能会频繁发生变化,因此在与服务器再验证之前,只应该将其缓存很短一段时间
1
2
time_since_modify = max(0, date_value - server_last_modified)
server_freshness_limit = time_since_modify * lm_factor

img

其他

缓存相关的HTTP头部:

Header Description
Cache-Control 缓存控制
Expires 过期绝对时间
If-Modify-Since 从某个时间开始文档是否发生改变
If-None-Match 文档的标签是否发生改变
Last-Modified 最后修改时间
ETag 文档标签

参考

OAuth

OAuth客户端使用一个访问令牌(access token)来访问受保护的资源,其中访问令牌包含了特殊作用域(specific scope), 生命周期,和其他的访问属性。访问令牌由授权服务器在资源拥有者授权之后颁发(issue)。客户端通过访问令牌资源服务器上受保护资源。

比如,一个终端用户(resource-owner)可以授权打印服务器(client)访问他的存储在图片分享服务(resource server)上的受保护的图片,但不用与打印服务器分享他的用户名和密码。他可以授权图片分享服务(authorization server)颁发一个特殊的证书(access token).

Introduction

Roles

Name Description
resource owner 一个能授权访问受保护资源的实体
resource server 托管受保护资源的服务器,能处理通过访问令牌访问受保护资源的请求
client 代表资源拥有者发起访问受保护资源的请求的应用程序
authorization server 在资源拥有者认证、授权之后,能够颁发访问令牌给客户端的服务器

Protocol Flow

Authorization Grant

授权grant是一个证书表示资源拥有者已经授权,客户端可以用授权grant获取访问令牌。该规范定义4种授权类型:

  • authorization code
  • implicit
  • resource owner password credentials
  • client credentials

Access Token

访问令牌是一种证书用来访问受保护的资源。一个访问令牌就是认证服务器颁发给客户端的字符串。令牌包含了特殊作用域,过期时间,授权的资源拥有者等信息。

Refresh Token

刷新令牌也是一个证书用来获取访问令牌。刷新令牌是认证服务器发放给客户端的,在当访问令牌不可用或过期时,用来获取新的访问令牌。

刷新令牌是资源资源拥有者授权给客户端认证码。与访问令牌不同,刷新令牌只能与认证服务器通信,不能从资源服务器获取资源。

Refreshing an Expired Access TokenRefreshing an Expired Access Token

Client Registration

在使用OAuth协议之前,客户端需要在认证服务器注册应用。

客户端注册是让认证服务器能够识别、信任客户端,其中包括客户端类型,重定向URI等。

Client Types

OAuth定义了两种客户端类型

  • confidential - 客户端能够管理证书的机密性
  • public - 客户端不能管理证书的机密性

Client Identifier

认证服务器给已注册的客户端颁发一个客户端标识。对于认证服务器,客户端标识是唯一的。

Client Authentication

如果客户端类型是confidential, 客户端和认证服务器建立一个客户端认证方法。Confidential客户端会向认证服务器声明它支持证书,比如密码或公私钥对。

Obtaining Authorization

在请求访问令牌之前,客户端先要获取资源拥有者的授权。OAuth定义了4种授权类型: authorization code, implicit, resource owner password credential, 和client credentials.

Authorization Code Grant

授权码授权类型用来获取访问令牌和刷新令牌。

认证码获取流程:

Authorization Request

Content-Type: application/x-www-form-urlencoded

Parameter Description
response_type REQUIRED该值必须设置为”code”
client_id REQUIRED注册的应用的id
redirect_uri OPTIONAL重定向URI,成功获取授权码后重定向到oauth客户端的URI
scope OPTIONAL作用域
state RECOMMENDED客户端用来管理请求、回调状态的值。认证服务器的重定向到UA时,会包含这个值。这个参数应该用来防止扩展请求伪造
1
2
3
4
5
6
7
8
GET /o/authorize/?response_type=code&client_id=wt6Pvm2s3vbb8RPE7nlPlugwaMnj58UhFpk8bCPp&redirect_uri=http%3A%2F%2Flocalhost%3A8000%2Foauth%2Fauthorized%2F HTTP/1.0
Host: localhost:8000
Connection: close
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.98 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Encoding: gzip, deflate, sdch, br
Accept-Language: en-US,en;q=0.8

认证服务器会验证这个请求,保证请求中的所有参数都是有效的。如果请求是有效的,认证服务器需要资源拥有者授权。如果资源拥有者授权成功,授权服务器根据redirect_uri返回给UA一个重定向响应。

Authorization Response

Content-Type: application/x-www-form-urlencoded

Parameter Description
code REQUIRED认证服务器生成的授权码,为了防止泄露的危险,授权码必须在很短的时间过期。授权码生存期推荐为10min.客户端只能使用一次授权码。授权码与客户端id,重定向URI绑定
state REQUIRED如果客户端请求中包含state,授权服务器必须要返回该值
1
2
3
4
5
6
7
8
HTTP/1.0 302 Found
Date: Tue, 18 Jul 2017 04:12:00 GMT
Server: WSGIServer/0.1 Python/2.7.9
X-Frame-Options: SAMEORIGIN
Access-Control-Allow-Origin: *
Content-Type: text/html; charset=utf-8
Location: http://localhost:8000/oauth/authorized/?code=x1yxsYhAJ23pXNMXej4tB13dvMFTov
Vary: Cookie

Error Response

Conent-Type: application/x-www-form-urlencoded

Parameter Description
error REQUIRED
error_description OPTIONAL
error_uri OPTIONAL
state REQUIRED
1
2
HTTP/1.1 302 Found
Location: https://client.example.com/cb?error=access_denied&state=xyz

Access Token Request

Conent-Type: application/x-www-form-urlencoded

Parameter Description
grant_type REQUIRED该值必须设置“authorization_code”
code REQUIRED授权服务器返回的code
redirect_uri REQUIRED重定向URI
client_id REQUIRED注册应用的ID
1
2
3
4
5
6
7
8
9
10
POST /o/token/ HTTP/1.0
Host: localhost:8000
Connection: close
Content-Length: 183
Accept-Encoding: gzip, deflate
Accept: */*
User-Agent: python-requests/2.18.1
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&code=x1yxsYhAJ23pXNMXej4tB13dvMFTov&redirect_uri=http%3A%2F%2Flocalhost%3A8000%2Foauth%2Fauthorized%2F&client_id=wt6Pvm2s3vbb8RPE7nlPlugwaMnj58UhFpk8bCPp

认证服务器必须要验证客户端的请求

  • 对于confidential客户端,需要客户端认证
  • 如果客户端包含认证信息,认证客户端
  • 验证授权码是否有效
  • 验证redirect_uri是否证券

Access Token Response

如果获取访问令牌的请求是有效的而且认证通过,认证服务器需要发放访问令牌和可选的刷新令牌。如果请求是无效的或认证失败,需要返回一个错误响应。

1
2
3
4
5
6
7
8
9
HTTP/1.0 200 OK
Date: Tue, 18 Jul 2017 04:12:00 GMT
Server: WSGIServer/0.1 Python/2.7.9
X-Frame-Options: SAMEORIGIN
Content-Type: application/json
Pragma: no-cache
Cache-Control: no-store

{"access_token": "4C1gRp2TfYKN0tO45fqa6BkFZxTJNU", "token_type": "Bearer", "expires_in": 36000, "refresh_token": "oZX6XtV2OGSVRnvyOoa7qerWddOPKw", "scope": "read write"}

Refreshing an Access Token

如果认证服务器向客户端颁发了刷新令牌,客户端可以使用刷新令牌更新访问令牌。

Content-Type: application/x-www-form-urlencoded

Parameter Description
grant_type REQUIRED该值必须设置为”refresh_token”
refresh_token REQUIRED认证服务器颁发的刷新令牌
scope OPTIONAL

刷新令牌是长期存在证书用来更新访问令牌,刷新令牌必须要与客户端id绑定。此外,客户端必须向认证服务器提供认证信息。

1
2
3
4
5
6
7
8
9
10
11
POST /o/token/ HTTP/1.0
Host: localhost:8000
Connection: close
Content-Length: 69
Accept-Encoding: gzip, deflate
Accept: */*
User-Agent: python-requests/2.18.1
Content-Type: application/x-www-form-urlencoded
Authorization: Basic d3Q2UHZtMnMzdmJiOFJQRTdubFBsdWd3YU1uajU4VWhGcGs4YkNQcDpWcUF3NDlwb3VXc0lUVU1OcWFrdzczQ0NSRVZzclMyRWd5SG9WeVJnekpWNWRlWnNKQUNBeVdkeEFJMXZRc2RaZk1JQmlCRlRFeUR5YUkyeTJJcU1vR3JEbkFNWE5ITWd4TFRJUDIxRXhIR0tkUmNTVlJ5RjRxOHA5STR5OWhqVQ==

grant_type=refresh_token&refresh_token=oZX6XtV2OGSVRnvyOoa7qerWddOPKw

Response:

1
2
3
4
5
6
7
8
9
HTTP/1.0 200 OK
Date: Tue, 18 Jul 2017 06:47:50 GMT
Server: WSGIServer/0.1 Python/2.7.9
X-Frame-Options: SAMEORIGIN
Content-Type: application/json
Pragma: no-cache
Cache-Control: no-store

{"access_token": "gebkQX71vIwmksTsvleRKMzpdysLHY", "token_type": "Bearer", "expires_in": 36000, "refresh_token": "CbpaNXAqyexTQOuCvQf1RUoEN2EZeX", "scope": "read write"}

Accessing Protected Resources

客户端可以同访问访问资源服务器受保护的资源。资源服务器必须要验证访问令牌,保证访问令牌没有过期,并且其作用域能覆盖此资源。

Access Token Types

“bearer”令牌类型,参考RFC6750

1
2
3
GET /resource/1 HTTP/1.1
Host: example.com
Authorization: Bearer mF_9.B5f-4.1JqM

“mac”令牌类型

1
2
3
4
5
GET /resource/1 HTTP/1.1
Host: example.com
Authorization: MAC id="h480djs93hd8",
nonce="274312:dj83hs9s",
mac="kDZvddkndxvhGRXZhvuDjEWhGeE="

References

Python元类

[Metaclasses] are deeper magic than 99% of users should ever worry about. If you wonder whether you need them, you don’t (the people who actually need them know with certainty that they need them, and don’t need an explanation about why). The Python core developer Time Peters said.

元类是创建类的类。新风格(new-style)类是type类的实例。元类是type类的派生类,通过重载type类的__new____init__方法,重新定义类创建协议,来实现类创建的定制化。

元类模型

(对象)实例是通过类创建,类是通过type类创建,元类是type类的派生类。

  • 类型(自定义)是通过type类或type派生元类创建
  • 元类是type类的派生类
  • 用户自定义类是type类的实例
  • 用户自定义类可以生成自己的实例

类声明协议

Python解释器在类声明语句结束时,调用type类型创建,class = type(classname, superclasses, attributedict)

1
2
type.__new__(meta, classname, superclasses, attributedict)
type.__init__(cls, classname, superclasses, attributedict)

声明元类

Py3与Py2声明元类的方式不一样:

Py3声明元类

1
2
3
4
5
6
7
8
9
>>> class Metaclass(type):
... def __new__(meta, classname, superclasses, attributedict):
... return type(classname, superclasses, attributedict)
... def __init__(cls, classname, superclasses, attributedict):
... pass
...
>>> class Dummy(object, metaclass=Metaclass):
... pass
...

Py2声明元类

1
2
3
4
5
6
7
8
9
>>> class Metaclass(type):
... def __new__(meta, classname, superclasses, attributedict):
... return type(classname, superclasses, attributedict)
... def __init__(cls, classname, superclasses, attributedict):
... pass
...
>>> class Dummy(object):
... __metaclass__ = Metaclass
...

继承和实例

  • 元类继承于type类
  • 元类的声明可以被派生类继承
  • 元类的属性不能被类的实例继承
  • 元类的属性可以被类获取

元类继承于type类

元类重载type类的__new____init__方法,定制类的创建和初始化。

元类的声明可以被派生类继承

元类的属性不能被类实例继承

类是元类的实例,元类的行为可以被类访问,当类不能被类的实例访问。

元类的属性可以被类获取

继承

Python继承算法

  1. 对于一个实例,先搜索这个实例,再搜索实例的类,再搜索超类
    a. 先搜索实例的__dict__
    b. 再搜索该实例的类的__mro__对应类的__dict__
  2. 对于一个类,先搜索类,再搜索超类,再搜索元类
    a. 根据__mro__搜索类的__dict__
    b. 再搜索元类的__dict__
  3. 规则1和2中,再b阶段中,数据描述的优先级高
  4. 规则1和2中,对于内置的运算符,跳过a阶段

数据描述符继承算法

对于定义了__set__拦截赋值的描述符就是数据描述符。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> class D(object):
... def __get__(self, ins, _type):
... print('call D.__get__')
... def __set__(self, ins, value):
... print('call D.__set__')
...
>>>
>>> class Dummy(object):
... d = D()
...
>>> ins = Dummy()
>>> ins.d
call D.__get__
>>> ins.d = 'spam'
call D.__set__

未定义__set__的描述符

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> class D(object):
... def __get__(self, ins, value):
... print('call D.__get__')
...
>>> class Dummy(object):
... d = D()
...
>>> ins = Dummy()
>>> ins.d
call D.__get__
>>> ins.d = 'spam'
>>> ins.d
'spam

Python 的继承算法

  1. 对于实例I,先搜索实例,再搜索类,再搜索超类
    a. 根据类的__mro__搜索超类的__dict__
    b. If 如果在a阶段发现了数据描述,调用该数据描述,完成后退出
    c. Else 返回该实例__dict__中的值
    d. Else 调用非数据描述符,并放回结果
  2. 对于类C,搜索类,再搜索超类,再搜索元类
    a. 搜索类的__mro__依次搜索类的__dict__
    b. If 如果在a阶段发现了数据描述符,调用该数据描述符,完成后退出
    c. Else 返回该类__dict__中值g
    d. Else 调用非数据描述符,返回结果

Note, 数据描述符的优先级 > 普通属性 > 非数据描述符

元类与类装饰器

TODO

示例

django ORM

ripozo API

参考

  • Learning Python 5th Edition

Python描述符

如果一个对象定义了以下任意方法,这个对象就是一个描述符。给描述符下个定义,描述符就是绑定了行为属性的对象。

object.__get__(self, instance, owner)

object.__set__(self, instance, value)

object.__delete__(self, instance)

属性访问的默认行为就是从一个对象字典中获取、设置和删除属性。比如,a.x首先会搜索a.__dict__['x'],其次type(a).__dict__['x'],最后所有type(a)的元类。如果要查找的值一个包含描述器方法的对象,Python会用调用描述器方法代替默认行为。

Note:只有new-style class会调用描述符的对象的方法。

描述符是一个强大的通用协议。Python内建的property, staticmethod, classmethod, super背后的实现机制都是描述符协议。

Descriptor Protocol

object.__get__(self, ins, _type=None)

object.__set__(self, ins, value)

object.__del__(self, ins)

如果一个对象包含上面任意一个方法,就可以被看作是一个描述符。如果一个对象定义了__get____set__两个方法,该对象可以被看作一个数据描述符,如果一个对象只定义了__get__,该对象就是non-data描述符。

数据描述符与非数据描述符的区别在于,描述符与对象实例entry调用优先级。如果一个实例的字典有一个entry和数据描述符的名字相同,数据描述符的调用的优先级高。如果一个实例有一个entry和非数据描述符的名字相同个,实例entry的调用的优先级高。

Invoking Descriptors

obj.d查找obj的字典是否包含d,如果d定义了__get__方法,d.__get__(obj, type(obj))就会被调用。

1
2
3
4
5
6
def __getattribute__(self, key):
"Emulate type_getattro() in Objects/typeobject.c"
v = object.__getattribute__(self, key)
if hasattr(v, '__get__'):
return v.__get__(None, self)
return v

super()返回的对象有一个定制化的__getattribute__方法,用于调用描述符。super(B, self).m先会搜索obj.__class__.__mro__查找基类A,如果是一个数据描述符,则会调用A.__dict__['m'].__get__(obj, B),如果是一个非数据描述符,返回结果不会改变。

Built-in Descriptors

Property

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class C(object):
def getx(self): return self.__x
def setx(self, value): self.__x = value
def delx(self): del self.__x
x = property(getx, setx, delx, "I'm the 'x' property.")


class Property(object):
"Emulate PyProperty_Type() in Objects/descrobject.c"

def __init__(self, fget=None, fset=None, fdel=None, doc=None):
self.fget = fget
self.fset = fset
self.fdel = fdel
if doc is None and fget is not None:
doc = fget.__doc__
self.__doc__ = doc

def __get__(self, obj, objtype=None):
if obj is None:
return self
if self.fget is None:
raise AttributeError("unreadable attribute")
return self.fget(obj)

def __set__(self, obj, value):
if self.fset is None:
raise AttributeError("can't set attribute")
self.fset(obj, value)

def __delete__(self, obj):
if self.fdel is None:
raise AttributeError("can't delete attribute")
self.fdel(obj)

def getter(self, fget):
return type(self)(fget, self.fset, self.fdel, self.__doc__)

def setter(self, fset):
return type(self)(self.fget, fset, self.fdel, self.__doc__)

def deleter(self, fdel):
return type(self)(self.fget, self.fset, fdel, self.__doc__)

Staticmethod

1
2
3
4
5
6
7
8
class StaticMethod(object):
"Emulate PyStaticMethod_Type() in Objects/funcobject.c"

def __init__(self, f):
self.f = f

def __get__(self, obj, objtype=None):
return self.f

Classmethod

1
2
3
4
5
6
7
8
9
10
11
12
class ClassMethod(object):
"Emulate PyClassMethod_Type() in Objects/funcobject.c"

def __init__(self, f):
self.f = f

def __get__(self, obj, klass=None):
if klass is None:
klass = type(obj)
def newfunc(*args):
return self.f(klass, *args)
return newfunc

References

Python上下文管理器

上下管理器是一个对象,定义了执行with语句时需要创建的上下文。context manager的__enter__()__exit__()方法分别在进入、退出with语句时被调用。

object.__enter__(self)

object.__exit__(self, exc_type, exc_value, traceback)

with statement

1
2
with_stmt ::=  "with" with_item ("," with_item)* ":" suite
with_item ::= expression ["as" target]

with语句执行数据流:

  1. 评估上下文表达式是否包含上下文管理器
  2. 加载上下文管理器的__exit__方法
  3. 调用上下文管理的__enter__方法
  4. 如果target包含在with语句中,将__enter__方法的返回值赋给target
  5. 执行suite
  6. 调用__exit__()方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
mgr = (EXPR)
exit = type(mgr).__exit__ # Not calling it yet
value = type(mgr).__enter__(mgr)
exc = True
try:
VAR = value # Only if "as VAR" is present
BLOCK
except:
# The exceptional case is handled here
exc = False
if not exit(mgr, *sys.exc_info()):
raise
# The exception is swallowed if exit() returns true
finally:
# The normal and non-local-goto cases are handled here
if exc:
exit(mgr, None, None, None)

contextlib

contextlib提供了快速定义支持上下文管理器的函数对象。

定义一个生成器函数,contextmanager装饰后就变成一个支持上下文管理器的函数对象。yield之前语句子在代码块之前被执行,yield之后语句在代码执行完之后被执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> from contextlib import contextmanager
>>>
>>> @contextmanager
... def tag(name):
... print('<%s>' % name)
... yield
... print('</%s>' % name)
...
>>> with tag('h1'):
... print('hotbaby')
...
<h1>
hotbaby
</h1>

context decorator

contextmanager是一个函数装饰器,装饰只包含一个yield语句的生成器函数,返回一个支持上下文管理器的函数对象。

1
2
3
4
5
def contextmanager(func):
@wraps(func)
def helper(*args, **kwds):
return GeneratorContextManager(func(*args, **kwds))
return helper

Note: 被装饰的生成器函数变成生成器作为参数传递到GeneratorContextManager对象中。

生成器上下文管理器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class GeneratorContextManager(object):
"""Helper for @contextmanager decorator."""

def __init__(self, gen):
self.gen = gen

def __enter__(self):
try:
return self.gen.next()
except StopIteration:
raise RuntimeError("generator didn't yield")

def __exit__(self, type, value, traceback):
if type is None:
try:
self.gen.next()
except StopIteration:
return
else:
raise RuntimeError("generator didn't stop")
else:
if value is None:
# Need to force instantiation so we can reliably
# tell if we get the same exception back
value = type()
try:
self.gen.throw(type, value, traceback)
raise RuntimeError("generator didn't stop after throw()")
except StopIteration, exc:
# Suppress the exception *unless* it's the same exception that
# was passed to throw(). This prevents a StopIteration
# raised inside the "with" statement from being suppressed
return exc is not value
except:
# only re-raise if it's *not* the exception that was
# passed to throw(), because __exit__() must not raise
# an exception unless __exit__() itself failed. But throw()
# has to raise the exception to signal propagation, so this
# fixes the impedance mismatch between the throw() protocol
# and the __exit__() protocol.
#
if sys.exc_info()[1] is not value:
raise

references

Python Itertools

itertools.imap()

imap(func, *iterables) 和iter和map混合体。

工作原理

1
2
3
4
5
6
7
8
9
>>> def myimap(func, *iterables):
iterables = map(iter,iterables)
while True:
args = [next(it) for it in iterables]
yield func(*args)


>>> [item for item in myimap(pow, (2,2), (3,3))]
[8, 8]

itertools.chain()

chain(*iterables) 返回一个迭代器,该迭代器依次返回可迭代对象中没一个元素。

工作原理

1
2
3
4
5
6
7
>>> def mychain(*iterables):
for iter_ in iterables:
for item in iter_:
yield item

>>> [item for item in mychain('abc', 'def')]
['a', 'b', 'c', 'd', 'e', 'f']

References