XAuth.org 提供一个新的轻量级认证机制,他的机制可以参考:
http://xauth.org/spec/
OpenAddresses.org是上个月O’Reilly Where 2.0上发布的开放的Geocoding服务,类似OpenStreetMaps
http://openaddresses.org/
XAuth.org 提供一个新的轻量级认证机制,他的机制可以参考:
http://xauth.org/spec/
OpenAddresses.org是上个月O’Reilly Where 2.0上发布的开放的Geocoding服务,类似OpenStreetMaps
http://openaddresses.org/
Glad to announce my works this morning: A simple heatmap API based on HTML5 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:

then click heatmap button!

This is the new year gift for my readers and my dear friends !
今早短路,在godaddy上注册了我的新域名sunng.info。更换域名主要出于几方面的考虑:
原先的classicning.com会在今年6月到期,如果没有意外情况我不准备续费了,届时这个使用了四年的域名就要废弃了(当时客服大嫂还在电话里娇嗔地问我为啥不续十年呢,你说为啥呢)。
麻烦大家利用这半年的时间逐步改变习惯,逐步用sunng.info了。
最近琢磨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了一些,不过应该相对易于理解吧。
I will show you the usage of Yan captcha service. In this tutorial, it’s based on a simple ruby web application of the Sinatra web framework.
Before we start to use the service, it is necesary to get Yan running. Download the code from the project page, then build and run it with maven:
mvn jetty:run
To enable the application to use Yan, we have to register our application to get an API Key. If you use Yan 0.3, there is a secret registration page at http://localhost:8080/yan/reg.jsp The page is protected by HTTP Basic Authentication, the username and password are store in ‘realm.properties’ which is considered to locate in the root directory. Open the file you can see the plain text username and password. If you are running the latest development version, there is no long any UI for API Key creation, but restful interface. This won’t be hard to you, pickup your tools such as curl or poster (a firefox extension) to send a HTTP request. Take curl as example, do it like this:
curl -X PUT “http://localhost:8080/yan/apikey/” -d “SinatraTestApp” -u “username:password”
If it works, you will get a line of json:
{“apikey”:”b251b0dc2eed31cac38555b61d4fa6a453923bfd”,”appName”:”SinatraTestApp”}
Save this apikey.
Sinatra is generally considered to be the world’s lightest and smallest web framework. And our application is rather simple. Just check the code:
require "rubygems"
require "sinatra"
require "net/http"
require "yaml"
apikey='b251b0dc2eed31cac38555b61d4fa6a453923bfd'
get '/' do
conn = Net::HTTP.new('localhost', 8080)
q = "ip=#{@env['REMOTE_ADDR']}&apikey=#{apikey}&alt=yaml&mode=0"
resp, data = conn.get("/yan/ticket?#{q}")
@ticket = YAML::load(data)
haml :sinatra_captcha
end
post '/' do
conn = Net::HTTP.new('localhost', 8080)
q = "ip=#{@env['REMOTE_ADDR']}&apikey=#{apikey}&key=#{params['key']}&code=#{params['captcha']}"
resp, data = conn.get("/yan/validate?#{q}")
data
end
use_in_file_templates!
__END__
@@ sinatra_captcha
%html
%head
%title Yan Captcha on Sinatra
%body
%form{:action=>"/", :method=>"post"}
%p
Username:
%input{:name=>"username", :type=>"text"}
%p
Password:
%input{:name=>"password", :type=>"password"}
%p
Captcha:
%img{:src=>@ticket['url']}
%br
%input{:name=>"captcha", :type=>"text"}
%input{:name=>"key", :type=>"hidden", :value=>@ticket['key']}
%input{:type=>'submit'}
There are two parts of this application: ruby code and haml. I just use in-file-template for convenience. We define a get handler and a post handler on the path ‘/’. The get handler will request a ticket from Yan which contains captcha image url and ticket key. The post handler will extract user input and submit the Yan’s validator and return user the result. And the HAML code is template for page rendering after GET request.
Maybe you need to install sinatra and some dependency:
sudo gem install sinatra haml
Run the code with a build-in WEBrick
ruby sinatra-yan.rb
Browse to the default url, test it:

For another similar tutorial using python, check Yan’s wiki page:
http://bitbucket.org/sunng/yan/wiki/SampleCode
Thank you for your support. btw, today is my dear girl friend’s birthday, I just wish her happy everyday.
前些天看到一个Nginx的Module,用来是实现Comet,今天简单试了一下功能。作者名叫Leo Ponomarev,项目地址:http://pushmodule.slact.net/
Module需要在编译时加入nginx,同时下载nginx和nginx-push-module,在nginx configure时增加一个参数:
./configure –add-module=path/to/nginx_http_push_module
编写一个非常基本的nginx配置文件:
events{
worker_connections 1024;
}
http{
server {
listen 80;
server_name localhost;
location /publish {
set $push_channel_id $arg_id;
push_publisher;
push_store_messages on;
push_message_timeout 2h;
push_max_message_buffer_length 10;
push_min_message_recipients 0;
}
location /subscribe{
push_subscriber;
push_subscriber_concurrency broadcast;
set $push_channel_id $arg_id;
default_type text/plain;
}
}
}
一个简单的Server定义了两个路径分别用于publish和subscribe。所有相关的配置项可以在项目主页找到解释,不作赘述。
启动nginx
nginx -c /home/sun/nginxpush/nginx-push.conf
打开一个终端访问subscribe
curl -X GET http://localhost/subscribe?id=0
可以看到HTTP请求被阻塞
打开另一个终端访问publish
curl -X POST http://localhost/publish?id=0 -d “Hello World”
此时subscriber收到字符串”Hello World” ,完成HTTP请求。
subscriber可以通过设置HTTP头来对消息进行过滤,如
curl -X POST http://localhost/publish?id=0 -d “Hello World”
curl -X GET http://localhost/subscribe?id=0 –verbose
HelloWorld
从响应的头部可以看到Last-Modified: Thu, 26 Nov 2009 09:44:59 GMT的时间是上一次publish的时间,并且通过Vary字段提示了两个选项
RFC中对Vary头是这样解释的:
The Vary field value indicates the set of request-header fields that fully determines, while the response is fresh, whether a cache is permitted to use the response to reply to a subsequent request without revalidation.
即可以通过发送If-Modified-Since来获取指定时间之后的数据
curl -X GET -H “If-Modified-Since: Thu, 26 Nov 2009 09:44:50 GMT” http://localhost/subscribe?id=0 –verbose
这时subscribe会重新被阻塞而不是接收上次publish的数据,充分利用了HTTP的语义。
这样用push module来做Web-IM、聊天室的思路就非常清晰了:每个浏览器保持一个subscriber连接,在接收到消息后连接关闭。把消息打印出来,并根据消息响应的头部Last-Modified请求重新subscribe。
项目的下一阶段,需要做一些数据展现方面的工作,处于对Adobe产品的抵触情绪,我个人还是倾向于用JavaScript来完成。今天找了几个画Chart的库,比较一下功能和编程接口的使用,为接下来的开发做一些准备。
(BSD License)
没什么悬念,第一个想到的就是曾经用过的dojo。之前实习时候用dojo画chart也算是积累了一些心得。dojox.charting的最主要优点是编程接口完善、全面,可以配置的项目很多,接口易于编程,易于动态地生成、操作Chart。而另一方面最主要的问题就是文档比较匮乏,不了解的人可能问了,doc.dojotoolkit.org好强大,怎么会文档匮乏呢。其实是dojo和mootools类似,都很喜欢用option object来传递可选参数,但是这些option object在文档中没有任何涉及,很多关键的属性都在其中但是却没有办法查到,算是美中一大不足。
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<script src="dojo-release-1.3.2/dojo/dojo.js" djConfig="isDebug: true"></script>
<title>Dojo</title>
<script type="text/javascript">
dojo.require("dojox.charting.Chart2D");
</script>
</head>
<body>
<h1>Dojox.charting</h1>
<div id="charting" style="width:400px; height:250px;">
</div>
<script type="text/javascript">
(function charting(){
dojo.empty("charting");
var data = [2,4,5,7,7,4,4,7];
var chart = new dojox.charting.Chart2D("charting");
chart.addPlot("default", {type: "Lines", markers: true, shadows: {dw:2, dy:2, dx:2}});
chart.addPlot("additional", {type: "Areas"});
chart.addPlot("other", {type: "ClusteredColumns"})
chart.addAxis("x");
chart.addAxis("y", {vertical: true, min:0, max: 10});
chart.addSeries("Testing Data 1", data);
chart.addSeries("Average", dojo.map(data, function(it){return (Math.sin(it)+Math.random())*4}), {
fill: "rgba(145, 213, 100, 0.5)", plot:"additional"
});
chart.addSeries("All Random", dojo.map(data, function(it){return (Math.random()*5)}), {
plot: "other", fill: "#AEC6E2", stroke:{color:"#333", width:1}
});
chart.render();
})();
</script>
</body>
</html>
(BSD License)
Protovis,看域名就知道是斯坦福的项目,Protovis是在prototype基础上,不仅有charting的功能,还包括其他可视化方法的实现。看了Demo会发现它真的很不错,不过Protovis的本意似乎只是借助浏览器的图形功能实现可视化,它并没有提供超出可视化范畴的其他东西,甚至看过实例的代码你会发现,作者是不折不扣的函数式编程和链式方法的爱好者,Protovis的代码算是出神入化了,如果你想看看学术的JavaScript是如何写的,那就看看这个发表了论文的JavaScript库吧。
看起来是很出神入化,可是这样的编程接口如果要用在动态的数据展现上,实在是难以想象。况且,pv.Panel居然没有传入dom对象的接口,无从控制chart的生成位置。说白了,Protovis并不是一个满足项目需要的Library,不过它的功能和代码还是值得多看几眼的。
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<script src="protovis-3.1/protovis-r3.1.js"></script>
</head>
<body>
<div id="page">
<h1>Protovis</h1>
<div style="border:1px #CCC solid;">
<script type="text/javascript">
var width = 300;
var height = 225;
var data = [1,2,3,4,6,7,5,8,9];
var dataMax = data[0];
for(var i=1; i<data.length; i++){
dataMax = dataMax > data[i] ? dataMax : data[i];
}
var x = pv.Scale.linear(0, dataMax).range(0, width);
var y = pv.Scale.ordinal(pv.range(10)).splitBanded(0, height, 0.9);
var charting = new pv.Panel();
charting.width(width).height(height).left(20).top(20);
var bar = charting.add(pv.Bar);
bar.data(data).top(function() y(this.index)).height(y.range().band).left(0).width(x);
bar.anchor("center").add(pv.Label)
.textStyle("white")
.textAlign("center")
.text(function(d) d.toFixed(1));
charting.add(pv.Rule)
.data(x.ticks())
.left(function(d) Math.round(x(d)) - .5)
.strokeStyle(function(d) d ? "rgba(255,255,255,.3)" : "#000")
.add(pv.Rule)
.bottom(0)
.height(5)
.strokeStyle("#C00")
.anchor("bottom").add(pv.Label)
.text(function(d) d);
charting.render();
</script>
</div>
</div>
</body>
</html>
(MIT License)
不同于上面两个库通过SVG绘图,Flotr是用canvas api来完成绘制的。与dojo相比,Flotr的API比较简单,主要只有一个Flotr.draw方法,可以传入三个参数:目标DOM对象,数据和绘图选项。和dojo不同,Flotr的文档里关于这个option object有非常明确的文档。另外强大的是Flotr还提供了一些交互的功能,支持事件绑定。
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<script src="flotr-0.2.0-alpha/flotr/lib/prototype-1.6.0.2.js"></script>
<script src="flotr-0.2.0-alpha/flotr/flotr-0.2.0-alpha.js"></script>
<title>Flotr</title>
<script type="text/javascript">
</script>
</head>
<body>
<h1>Flotr</h1>
<div id="charting" style="width:400px; height:250px;">
</div>
<script type="text/javascript">
(function d(){
var series1 = {data: (function(){
var d = [];
for(var i=0; i<10; i+=0.5){
d.push([i, Math.random()-0.5+Math.sin(i)*3]);
}
return d;
})(), label:"d1", lines:{fill:true}};
var series2 = {data: (function(){
var d = [];
for(var i=0; i<10; i+=0.5){
d.push([i, (Math.random()-0.5)*2+Math.cos(i)*2]);
}
return d;
})(), label: "s2", points:{show:true}, lines:{show:true}};
Flotr.draw($("charting"), [series1, series2], {
legend: {position :"se"},
yaxis: {min: -3.5, max:3.5}
});
})();
</script>
</body>
</html>
此外,还有一个与Flotr很类似的Protochart,后者的网站上写自己是motivated by Flotr,不过我很难想象功能几乎相同是如何motivated的。
(Apache License 2.0)
MilkChart是一个建立在Mootools上的Charting库,采用canvas api绘图。它的特点是把HTML Table的数据绘制成Chart,如果场景允许,倒是省去了不少构建数组的麻烦。这个项目目前规模还不大,并且在作者积极的开发中,可以参考它的Wiki获得详细的使用方法。
今天又讨论了一种验证码服务的机制,这种机制相对前两天说的简化的验证码生成的部分,由两步生成变成了一步生成,当然由于生成图片的接口直接暴露给用户,存在被刷的可能。

优点:
固定链接,简化了接入,便于接入静态页面;
缺点:
写cookie受到域的限制,只能在相同的域中使用该服务;
验证码接口暴露给用户,可能被穷举
百度和腾讯使用的都是这种方式。