对于cheater-node的总结——异步篇

之前也说了,开始玩儿nodejs,然后拿cheater下手。做得差不多了,总结一下。会有几篇单独的文章,省得看起来乱。

nodejs的基本思想就是异步,这对传统编程模型是彻底的颠覆。刚开始很不习惯,死得很惨,程序怎么都不按我想的跑。一个简单的例子就是http的request,调用不会等待response而是立即返回,然后通过回调函数处理response。

npm安装东西时候的困惑

不知道npm是什么的同学,自己google,提示一下,跟node相关。

npm安装分为两种,local和global,有什么区别这里说得已经很详细了:http://blog.nodejs.org/2011/03/23/npm-1-0-global-vs-local-installation/,我也就不再废话了。

只是有些东西还要解释一下。

刚装好npm的时候,我惊异地发现它所有的安装目录全是777的权限,表示不理解(而且terminal里面看过去,非常不好看),手工改成755。这样带来的问题是如果做global安装的话,普通用户则无法安装;ok没问题,我可以sudo来搞,但是sudo之后新的问题又来了,~/.npm是做cache用的,sudo安装的话这里面东西可都属于root了。这个设计比较土,理想的方案是local安装,~/.npm作为cache,global安装,cache在npm自己的安装路径下。

还有local这种安装方式看起来隔离性挺好,但是对版本管理工具可不怎么友好,不知道有没有办法。

其实根本没有object,所有的东西都是function

我是意思是js里压根儿就没有object,全是浮云;其实根本就是function。

越看越糊涂,这语言设计得真是够可以的。都说是OO,其实就是个屁,所有东西都是用function模拟的。没有任何东西能跟传统意义上的OO对应起来,然后就说这门语言比较深奥,受过传统程序设计教育的人不容易理解。操蛋也不能操成这样吧?如果你说我就是不支持OO,OO算个屁,那也好很多。

只是发发牢骚,不能这么让人忽悠了,但是该学还是要好好学,既然它这么设计了,就咬牙跟进吧。

还是抓不着?

??

果然js给我上的第一课就是this

this在js里设计得还真是够复杂,或者确切地说根本就是设计错了(这不是我说的,是写那本the good parts的老大说的)。

一个例子:

1
2
3
4
5
6
7
myObject.double = function () {
    var helper = function () {
        this.value = add(this.value, this.value);
    };
 
    helper();
};

这个方法要做的事情很简单,就是把自己的value属性double一下。一切看起来都很完美,直到真正跑起来,你才会发现报错说value没定义。

原因在于那个内部匿名函数里的this并不是myObject,而是这个函数本身!诡异得很。

解决起来倒是简单:

1
2
3
4
5
6
7
8
9
myObject.double = function () {
    var that = this;
 
    var helper = function () {
        that.value = add(that.value, that.value);
    };
 
    helper();
};

习惯上大家都用that。怪不得之前总是看到that这that那的,还以为是js的保留字。

给自己建一个Nexus

我不是做CM的,但是家里有两台工作机器、一台二奶机,所以弄个Nexus还是有点意义的。

过程相当容易。

1. 这里下:http://nexus.sonatype.org/

2. 解包

3. bin/jsw/<arch>/nexus start(Windows底下不是这个,我不想写,自己研究吧)

4. http://<server>:8081/nexus

5. 在Repositories里面加自己需要的repository,注意三种类型:hosted、proxy、virtual;鼠标移动到问号上有提示,这里就不多说了

6. 修改$HOME/.m2/settings.xml

1
2
3
4
5
6
    <mirror>
      <id>id</id>
      <mirrorOf>*</mirrorOf>
      <name>xxx</name>
      <url>http://<server>:8081/nexus/content/groups/public/</url>
    </mirror>

这里把所有的repository都mirror到Nexus去。

7. 还有复杂的权限控制,我暂时用不到,以后需要再说吧。

关于node.js的第一贴

node.js是一个server端js的运行时。

想研究一下,就拿cheater来练手。(注:cheater是在公司里对付cc zone的实现自动登录的小工具)

首先想到的是用来做管理、监控的socket端口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var net = require('net');
var server = net.createServer(function (client) { // 有client连上来的时候调用此回调函数
  client.setEncoding('ascii'); // 设置编码,否则下面的data默认使用Buffer
  client.write('> ');
  client.on('data', function (data) { // client发数据过来,触发'data'事件,然后调用此回调函数
    switch (data.trim()) {
      case 'status':
        client.write('heartbeating...\r\n');
        client.write('> ');
        break;
      case 'quit':
        client.end(); // 关掉client
        break;
      case 'shutdown':
        client.end();
        server.close(); // 关掉server
        break;
      default:
        client.write('> ');
        break;
    }
  });
});
server.listen(44050, 'localhost');

值得注意的是,这里只有一个线程,所有异步的事情都靠事件驱动。这也就是node.js一个最核心的思想:用单个线程充分压榨CPU资源,能异步的全都异步了。

刚开始玩儿,哪里不对的,请多指正。顺便帮cnodejs做个广告。

最近都没时间更新

就忙着调性能了……

如何给当前页面的菜单加上URL

默认情况下,Lift的菜单系统对于当前页面是没有<a/>这个tag的,也就是说假如你处在页面A上,菜单里的A是没有URL的。但是有些时候确实又需要,enable的方法很简单:

1
<pre lang="html" colla="+"><span class="lift:Menu.builder?linkToSelf=true"></span>

Lift的REST支持

跟所有Lift的feature一样,支持REST也是非常非常得简单。

Mixin这个trait:

1
object AdminAPI extends RestHelper

Boot.scala里面加上:

1
LiftRules.dispatch.append(AdminAPI)

如果不需要创建session(不创建著名的S对象),还可以:

1
LiftRules.statelessDispatchTable.append(AdminAPI)

然后就是URL匹配:

1
2
3
4
5
6
serve {
  case "api" :: "add" :: Nil JsonGet _ => add
  case "api" :: "delete" :: Nil JsonGet _ => delete
  case "api" :: "edit" :: Nil JsonGet _ => edit
  case "api" :: "get" :: Nil JsonGet _ => get
}

以上都是GET的JSON接口。JsonGet会检查HTTP头里面的Accept以确认client是否支持JSON,这里需要注意的是“*/*”表示所有都接受(这个应该已经在2.3里面被修复掉了);当然如果Accept里面不支持JSON,JsonGet还会去查询URL是否是.json结尾。

具体解释一下上面的例子。

对于URL为 http://server/api/add.json 的请求,第一条匹配规则命中,最后那个“_”是Req,然后调用add方法,add的返回值需要是LiftResponse类型;其实LiftReponse是个啥都不干的trait,有很多实现,JsonResponse、XmlResponse等等,总之几乎不用特别在意,Lift里面能作为response返回给client的都能用。

其他的匹配类似处理。

给个add方法的实现:

1
2
3
4
5
6
7
8
9
10
11
12
private def add = {
  val linkId = param(ShortenedUrl.originUrl.name).map(x =&gt; {
    shortenedUrl.find(ShortenedUrl.originUrl.name -> x).map(_.linkId.value) or {
      val tmp = (DependencyFactory.inject[NextIdGenerator].open_! !? 'id).toString
      shortenedUrl.createRecord.linkId(tmp).originUrl(x).shortUrl(Props.get(Site).open_! + "/" + tmp).date(new Date).
              ip(containerRequest.map(_.remoteAddress).toString).clickCount(0).save
      Full(tmp)
    }
  })
  (StatusField -> linkId.map(_.map(x => SuccessStatus)).openOr(Full(FailedStatus)).openOr(FailedStatus)) ~
          (ShortenedUrl.linkId.name -> linkId.openOr(Full("")).openOr(""))
}

返回一个JSON对象{status: “successful”, linkId: “1″}。

Lift利用Scala对DSL的支持把JSON整得很舒服,几乎跟js原生的用起来没区别:

1
2
3
("name" -> "honnix") ~
  ("address" -> "somewhere") ~
    ("phone" -> List("111", "222", "333"))

等价于:

1
2
3
{name: "honnix",
 address: "somewhere",
 phone: ["111", "222", "333"]}

具爽吧?