将!将!将!

  1. 汇报一下近况,最近仍然是6*12小时的工作,这是第二个礼拜了。我们的进度还算是顺利,不过架不住产品那里经常会有另他们自己拍案的新点子,这可苦了开发。可怜我们开发在产品面前基本没有什么话语权,连老大都只能说我们尽量支持。因为我负责的部分跟业务关系不是特别紧密,之前也算是有先见之明,坚决解耦,所以策划的变化对我影响不大,只是听见两旁的同事不断地重复“我悲剧了”。
  2. 10.04来了,迫不及待了又。结果升级的过程中遇到了无数依赖问题和冲突,apt几乎都无能为力,前天搞到1点多才睡,早晨7点起床又继续折腾。到了昨天中午终于可以进入桌面了,累得够呛,晚上又发烧了。
  3. 早晨起床忽然想起winter,现在的4square这么火,昨天又看到了比较mood的微博产品。哎,我们做的时候可是07年啊,也许是太超前了吧。
  4. 加班路上哼张楚的《将将将》,我真想再见张楚一回。
  5. 看见@dearaprilfool同学抄的《致橡树》,很有共鸣,女生们应该读一下。小时候读不算,这个年纪读才好。
  6. 新的一天开始了。

Sunng's Canvas based Heatmap API

Glad to announce my works this morning: A simple heatmap API based on HTML5 canvas.
canvas

The programming interface is rather simple now. To create such a heatmap, you just new a heatmap object by:

<canvas width="300" height="215" id="canv"></canvas>
heatmap = new HeatMap("canv");

Then read your dataset and push data into the heatmap:

heatmap.push(x, y, value);

At last, spread the data and get the canvas rendered:

heatmap.spread();
heatmap.render();

Now you got it.

For advanced usage, you can specify the resolution of heatmap by px:

heatmap = new HeatMap("canv", 2);

Large value will gain performance with low image quality.

Also, you can specify a value to define the attenuation value of each px.

heatmap.spread(5);

Finally, there is an option to use your own color schema for heatmap generation.

heatmap.render(function(value, maxValue){
    var light = value / maxValue * 100;
    return "hsl(20, 75%, "+light+"%)";
});

The parameters passed in are current pixel value and max value in whole context.

That’s all the toolkit. More functionality and options might be added in future. Grab it from my bitbucket page:
http://bitbucket.org/sunng/daily-coding/src/tip/canvas-heatmap/

There is a demo page which you can test the api by clicking canvas:
canvas-raw
then click heatmap button!
canvas2

This is the new year gift for my readers and my dear friends !

All new SUNNG.INFO

今早短路,在godaddy上注册了我的新域名sunng.info。更换域名主要出于几方面的考虑:

  1. 新域名1年的注册费用相当于原先的一半;
  2. 众所周知的原因,域名放在国内存在一定的风险;
  3. 原先域名过长不方便记忆和交流;
  4. .com后缀和我一穷二白地状况不符
  5. classicning.com曾被加入stopbadware.org,至今没有从twitter的malcious数据库里移除

原先的classicning.com会在今年6月到期,如果没有意外情况我不准备续费了,届时这个使用了四年的域名就要废弃了(当时客服大嫂还在电话里娇嗔地问我为啥不续十年呢,你说为啥呢)。

麻烦大家利用这半年的时间逐步改变习惯,逐步用sunng.info了。

OAuth Step by Step

最近琢磨OAuth认证方式。OAuth的优点主要在于

  • 用户不需要直接提供用户名密码给第三方应用,就可以让第三方应用访问受限资源;
  • 资源提供方对第三方应用有更细粒度的控制。

在整个OAuth协议里,生成signature的base string是最容易出错的部分。它由HTTP方法名、URL编码的请求路径和请求的参数表组成。
请求的参数表是除去oauth_signature以外的所有参数,按参数名排序,并进行url转义

def to_signature_key(method, url, data):
	keys = list(data.keys())
	keys.sort()
	encoded = urllib.quote("&".join([key+"="+data[key] for key in keys]))
	return "&".join([method, urllib.quote(url, safe="~"), encoded])

有了这个通用的生成signature base string的方法,以后就可以根据OAuth协议规范按步骤进行。

首先获取Request Token。这一步通常使用资源提供方注册的API Key和API Key Secret

def request_token_params(consumer_key, consumer_secret, path, method='GET'):
	data={}
	data['oauth_consumer_key']=consumer_key
	data['oauth_signature_method']='HMAC-SHA1'
	data['oauth_timestamp']=str(int(time.time()))
	data['oauth_nonce']=''.join([str(random.randint(0,9)) for i in range(10)])
	print data

	msg = to_signature_key(method, path, data)
	print msg

	signed = base64.b64encode(hmac.new(consumer_secret+"&", msg, hashlib.sha1).digest())
	print signed
	data['oauth_signature']=signed
	return data

def result2dict(result_string):
	d = {}
	params = res.split('&')
	for p in params:
		d[p.split('=')[0]] = p.split('=')[1]
	return d

conn = httplib.HTTPConnection("www.douban.com", 80)

params = request_token_params(consumer_key, consumer_secret, request_token_path)
conn.request('GET', request_token_path+"?"+urllib.urlencode(params))
res = conn.getresponse().read()
print res
request_token = result2dict(res)

这一步可以获得未经认证的Request Token和Request Token Secret。需要注意的细节是在计算hmac签名的时候,即使只有一个Token Secret,仍然需要加上”&”

第二步要求用户授权该Request Token,打开浏览器,将用户定向到相应的授权页面,参数为上一步获得的Request Token

第三步,用授权过的Request Token换取Access Token。这一步类似第一步,只是用于签名的token包括API Key Secret和Request Token

def access_token_params(consumer_key, consumer_secret, oauth_token, oauth_secret, path, method='GET'):
	data={}
	data['oauth_consumer_key']=consumer_key
	data['oauth_signature_method']='HMAC-SHA1'
	data['oauth_timestamp']=str(int(time.time()))
	data['oauth_nonce']=''.join([str(random.randint(0,9)) for i in range(10)])
	data['oauth_token'] = oauth_token

	msg = to_signature_key(method, path, data)
	print msg

	signed = base64.b64encode(hmac.new(consumer_secret+"&"+oauth_secret, msg, hashlib.sha1).digest())
	print signed
	data['oauth_signature']=signed
	return data

params = access_token_params(consumer_key, consumer_secret, request_token['oauth_token'],
	request_token['oauth_token_secret'], access_token_path)
conn.request('GET', access_token_path+"?"+urllib.urlencode(params))
res = conn.getresponse().read()
print res
access_token = result2dict(res)

这一步将至少返回Access Token和Access Token Secret,是最终用于访问受限资源的Token。以豆瓣的实现为例,OAuth的相关参数应放在HTTP头里随请求进行发送。

def oauth_header(consumer_key, consumer_secret, oauth_token, oauth_secret, path, realm):
	data = access_token_params(consumer_key, consumer_secret, oauth_token, oauth_secret, path, method="POST")
	header_string = ','.join([key+'="'+data[key]+'"' for key in data.keys()])
	return 'OAuth realm="'+realm+'",'+header_string

posturl = 'http://api.douban.com/miniblog/saying'

content = """<?xml version='1.0' encoding='UTF-8'?>
<entry xmlns:ns0="http://www.w3.org/2005/Atom" xmlns:db="http://www.douban.com/xmlns/">
<content>li lei ju le han mei mei</content>
</entry>
"""

header = {}
header['Authorization'] = oauth_header(consumer_key, consumer_secret,
		access_token['oauth_token'], access_token['oauth_token_secret'],
		posturl, "http://api.douban.com")
header['Content-Type'] = 'application/atom+xml'
print header

conn.request('POST', posturl, content, header)
res = conn.getresponse().read()

print res

conn.close()

在这一步中,用于生成signature base string的url是要访问的受限资源地址,而签名的参数表依然是oauth相关的参数。
生成的Authorization头如下

Authorization: OAuth realm="http://api.douban.com",
    oauth_nonce="8735717688",
    oauth_timestamp="1262613619",
    oauth_consumer_key="0bc081a01168b263234184e0343a1729",
    oauth_signature_method="HMAC-SHA1",
    oauth_token="5fb836c37543ad691f28a44a5fcb083b",
    oauth_signature="jk6p5qaXVPrGQctSzpO5jjYHfDk="

用这个头就可以在一定的时间内访问所有授权范围内的受限资源。

代码是ugly了一些,不过应该相对易于理解吧。