老韩
4十一/091

使用Flash Cookie技术在客户端永久保存HTTP Cookie

  前言:
  在我负责的一个项目中,为了实现一个特殊的需求,要求在客户端的Cookie中长久保存一份数据,但是我们知道在客户端Cookie里保存数据是不稳定的,因为用户可能随时会清除掉浏览器的Cookie,在这种情况下,一般的解决方案是重新向服务器端发送一个请求,以获得一个新的HTTP Cookie数据,并将其保存--就一般的交互需求而言,这是没有问题的。但是,倘若我的需求是:要求恢复到原来的Cookie里保存数据呢?呵呵,这种情况,倘若服务器端没有做特殊的处理的话,显然是很难实现的。在尝试了许多方法之后,我们最后选择使用FlashCookie技术来做。

  一、什么是Flash Cookie?
  下面我首先来介绍一下FlashCookie。
  FlashCookie是由FlashPlayer控制的客户端共享存储技术,它具备以下特点:1、类似HTTPCookie,FlashCookie利用SharedObject类实现本地存储信息,SharedObject类用于在用户计算机上读取和存储有限的数据量,共享对象提供永久贮存在用户计算机上的对象之间的实时数据共享;2、本地共享对象是作为一些单独的文件来存储的,它们的文件扩展名为.SOL。默认时,它们的尺寸为不超过100kB,并且不会过期——这一点与传统的HTTP Cookie不同(4KB);3、本地共享对象并不是基于浏览器的,所以普通的用户不容易删除它们。如果要删掉它们的话,首先要知道这些文件所在的具体位置。这使得本地共享对象能够长时间的保留在本地系统上。
  从上面可以看出,FlashCookie具有可操作性、比普通HTTPCookies有着更大存储空间、更好的隐蔽性等优点。加上现在FlashPlayer已经成为互联网用户标配之一,不存在兼容性的问题,因此它非常适合用来保护客户端数据、收集用户行为等。
  根据加利福尼亚大学伯克利分校(University of California, Berkeley)的一项调查表明,目前全球Top100的网站中,至少有54家在使用Flash Cookie技术在进行用户行为收集,有兴趣的同学可以看看这篇文章:《Top websites using Flash cookies to track user behavior

  二、使用Flash Cookie永久存储HTTP Cookie流程
  要实现Flash Cookie永远存储的功能,显然,首先要实现Flash Cookie与Http Cookie的互通,所以,在技术上使用JavaScript与ActionScript的来进行沟通显然是最好的选择,因为在这两种语言之间,除了语法上相近,从沟通上也有着完美的实现。下面我们来看看实现流程(如图所示):
  使用FlashCookies来永远保存HTTPCookies流程图

  三、ActionScript实现:
  在明确了实现流程之后,那么接下来就是具体的实现了,首先是基础建设,在Flash端,我们使用的是ActionScript3来编程,下面实现了一个基本的Flash Cookies存储的类,在接下来的过程当中,我们将通过这个类来实现对Flash Cookies的操作。当然,这里只是把流程以及关键的技术讲清楚,涉及到具体的操作,则需要您自己来实现。

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
/**
 * Package laohan.flashcookie
 * Author:handaoliang
 * Create Date:2009.11.03
 * Last Mofied Date:2009.11.03
 * CopyRights:Handaoliang.com All Rights Reserved.
**/
package laohan.flashcookie {
    //注:由于我没有装Flex,所以只好使用Flash CS3来嵌入。
    //如果使用Flash Developer,则MovieClip这个包可以不包含。
    import flash.display.MovieClip;
    import flash.net.SharedObject;
    import flash.external.ExternalInterface;
 
    public class myFlashCookie extends MovieClip {//如果使用Flex,可以不继承这个类。
        private var cookieTimeOut:uint;
        private var cookieName:String;
        private var cookieSharedObj:SharedObject;
		private var currentCookie:Object;
        private var cookieValue:String;
 
        //构造函数。
        public function myFlashCookie(cName:String = "handaoliang", timeOut:uint=3600) {
            cookieName = cName;
            cookieTimeOut = timeOut;
            cookieSharedObj = SharedObject.getLocal(cName, "/" );	
		    if(isCookieExist(cName)){//如果FlashCookies存在。
		        currentCookie = getCookies(cName);
                //调用JavaScript里的jsSetCookies方法来设置HTTPCookie
                ExternalInterface.call("jsSetCookies",{fcookie:currentCookie});
                //trace(currentCookie);
		    }
        }
        //到期删除Cookies
        public function clearTimeOut():void {
            var obj:* = cookieSharedObj.data.cookie;
            if(obj == undefined){
                return;
            }
            for(var key in obj){
                if(obj[key] == undefined || obj[key].time == undefined || isTimeOut(obj[key].time)){
                    delete obj[key];
                }
            }
            cookieSharedObj.data.cookie = obj;
            cookieSharedObj.flush();
        }
        //添加Cookies( key-value )
        public function saveCookies(key:String, value:*):void {
            var today:Date = new Date();
            key = "key_"+key;
            value.time = today.getTime();
            if(cookieSharedObj.data.cookie == undefined){
                var obj:Object = {};
                obj[key] = value;
                cookieSharedObj.data.cookie = obj;
            }else{
                cookieSharedObj.data.cookie[key] = value;
            }
            cookieSharedObj.flush();
        }
        //删除当前Cookies
        public function removeCookies(key:String):void {
            if (isCookieExist(key)) {
                delete cookieSharedObj.data.cookie["key_" + key];
                cookieSharedObj.flush();
            }
        }
        //通过Key来获取Cookies值。
        public function getCookies(key:String):Object{
            return isCookieExist(key)?cookieSharedObj.data.cookie["key_"+key]:null;
        }
        //检查Cookies是否存在。
        public function isCookieExist(key:String):Boolean{
            key = "key_" + key;
            return cookieSharedObj.data.cookie != undefined && cookieSharedObj.data.cookie[key] != undefined;
        }
        //检查Cookies的到期时间
        private function isTimeOut(time:uint):Boolean {
            var today:Date = new Date();
            return time + cookieTimeOut * 1000 < today.getTime();
        }
        //取得Cookies的到期时间;
        public function getTimeOut():uint {
            return cookieTimeOut;
        }
        //取得Cookies名称。
        public function getName():String {
            return cookieName;
        }
        //清除所有的Cookies值。
        public function clearCookies():void {
            cookieSharedObj.clear();
        }
    }
}

  将所有的模块编译成SWF文件,我们将其命名为:hdl.swf,在上面的代码中,我们首先在构造函数里检查了Flash Cookie是否存在,如果存在,则会调用一个叫jsSetCookies的JavaScript方法来设置HTTP Cookie。那么,我们接下来要使用JavaScript来实现此SWF的调用以及互动。

  四、JavaScript的实现:
  1)首先要预定义一个供ActionScript3调用的方法,即上面说过的jsSetCookies方法。

1
2
3
4
5
6
7
//先定义一个全局变量
var flashCookiesValue = Null;
var jsSetCookies = function(flahCookieValue){
    var o = arguments[0];
    flashCookiesValue = o.fcookie;//在SWF执行时传递过来的值。
    document.cookie="handaoliang="+flashCookiesValue+";path=/;expires=Fri, 04-Dec-2009 08:44:07 GMT;domain=handaoliang.com";
};

  2)检查特定的HTTPCookies是否存在,如果不存在,则在页面上生成Object Dom节点,把hdl.swf加载进来。此时SWF会去检查Flash Cookies是否存在,如果存在,则去取得Flash Cookies,并且通过调用jsSetCookies来设置HTTP Cookies:

  1. if(document.cookie.indexOf("handaoliang=")<0){
  2.     //先去load hdl.swf,即在页面上生成Object对象:
  3.     var jsLoadFlash = function(){
  4.         var myFlashURL = "hdl.swf";
  5.         var myFlashObj = document.createElement("object");
  6.         myFlashObj.setAttribute("id","myFlash");
  7.         myFlashObj.setAttribute("classid","clsid:D27CDB6E-AE6D-11cf-96B8-444553540000");
  8.         myFlashObj.setAttribute("width",0);
  9.         myFlashObj.setAttribute("height",0);
  10.         var flahParamObj = document.createElement("param");
  11.         flahParamObj.setAttribute("name","movie");
  12.         flahParamObj.setAttribute("value",myFlashURL);
  13.  
  14.         var subMyFlashObj = document.createElement("object");
  15.         subMyFlashObj.setAttribute("type","application/x-shockwave-flash");
  16.         subMyFlashObj.setAttribute("data",myFlashURL);
  17.         subMyFlashObj.setAttribute("width",0);
  18.         subMyFlashObj.setAttribute("height",0);
  19.  
  20.         myFlashObj.appendChild(flahParamObj);
  21.         myFlashObj.appendChild(subMyFlashObj);
  22.  
  23.         var myDHTMLBody = document.body;
  24.         if(myDHTMLBody){
  25.             myDHTMLBody.appendChild(myFlashObj);
  26.         }
  27.     };
  28.     jsLoadFlash();
  29. }

  倘若Flash Cookies里也没有存储我们想要的数据呢?那么,此时就必须通过JS去请求特定的URL,然后使用JavaScript去调用AS3的方法来设置Flash Cookies吧。关于这一步,就由大家自己去实现吧。呵呵。

  五、结语:
  那么到这里,基本上就实现了使用Flash Cookies来永久保存客户端数据的全过程。最后顺便说一下就是,其实这种方式也只能用来对付非计算机专业人士,因为Flash Cookie本身也是可以删除的,一般情况下,flash cookie保存在系统的如下位置:C:\Documents and Settings\yourusername\Application Data\Macromedia\Flash Player\#SharedObjects\,如下图所示,一般情况下,在这个文件夹下可以看到很多的网站目录,里面有他们的Flash程序留下的痕迹:
Flash Cookies的保存位置
  当然,我这是删除过的,呵呵。

  --EOF--

2十一/099

NodeJS探索

一、什么是NodeJS?
第一眼看到这个词,估计你和我一样,以为它不过是一个JavaScript框架吧?如果是,那么首先恭喜你,说明你的思维是正常的。其次鄙视你:凭什么带JS的就一定是JavaScript框架?你丫做技术的吧?最看不起你们这帮做技术的了,跟姓韩的那谁似的,没一点文化。
生活经验告诉我们,牛人往往都不怎么正常,正常人往往都成不了牛人。比如说NodeJS的发明者,这家伙就曾叫嚣说:其实JavaScript更适合做服务器端的并发编程。估计很多人听到这话都笑了,嗯,原谅我的可耻,我也笑了。令人欣慰的是上帝没有笑。当年上帝说要光,于是就有人脱了个精光。今天,上帝说我相信这个人能成,于是这个人还真成了,他弄了这么个东西:NodeJS--基于V8引擎的,使用事件驱动模型而不是复杂的多线程来获得可伸缩性,类似Ruby的EventMachine和Python的Twisted的轻量级WEBServer。
是不是很有意思?这意味着什么我想大伙都清楚吧?嗯,如果这东东真的可用,那就是说,以后前段后端都可以都用JavaScript来编程了,这对于JS程序员来说,真是一个莫大的福音,因为你们再也不用看着那帮做后台的孙子的脸色干活了,可以翻身做后端了。
好,废话到此,那接下来就请跟着我,开始NodeJS的探索之旅。

二、安装!
安装前先从NodeJS的官方网站下载其源代码:

  1. [root@www source]# wget http://s3.amazonaws.com/four.livejournal/20091028/node-v0.1.15.tar.gz
  2. [root@www source]# tar xzvf node-v0.1.15.tar.gz
  3. [root@www source]# cd node-v0.1.15

安装NodeJS很简单,一般情况下,Linux下常用的三步走即可。但需要注意的是:NodeJS需要2.3以上版本Python的支持,否则安装无法继续。

打开tools/waf-light,看到如下代码:

  1. #!/usr/bin/env python
  2. # encoding: utf-8
  3. # Thomas Nagy, 2005-2009
  4.  
  5. # ...
  6.  
  7. import os, sys
  8. if sys.hexversion<0x203000f: raise ImportError("Waf requires Python >= 2.3")

从以上代码我们知道,在安装脚本中,在Python脚本的头部指定了#!/usr/bin/env python,这就使得如果你的系统安装了自带的Python程序,则有可能由于版本不够高而无法安装。比如我的系统,是把Python2.5安装在了/usr/local/python252,同时未删除系统自带的低版本Python,此时如果configure,则会出错。

好吧,那我们建一个软连接:

  1. [root@www source]# sh
  2. [root@www source]# cd /usr/local/sbin/
  3. [root@www source]# ln -s /usr/local/python252/bin/python2.5 python
  4. [root@www source]# python
  5. [root@www source]# exit

此时执行的/usr/bin/env python,即可使用我们自己编译的Python。
PS.直接建软连接就可以了,这里之所以到sh环境下,是为了看一下sh环境。

执行configure,安装nodejs:

  1. [root@www source]# ./configure --prefix=/var/iapps/nodejs
  2. [root@www source]# make && make install

三、初次试用:
一切顺利,安装完成,写个脚本试试:

  1. #!/var/iapps/nodejs/bin/node
  2.  
  3. var sys = require("/sys.js"), http = require("/http.js");
  4. http.createServer(function (req, res) {
  5.     setTimeout(function () {
  6.             res.sendHeader(200, {"Content-Type": "text/plain"});
  7.             res.sendBody("Hello,This is Laohan(www.handaoliang.com)!");
  8.             res.finish();
  9.         }, 2000);
  10.     }).listen(8000);
  11. sys.puts("Server running at http://127.0.0.1:8000/");

我相信,对于JavaScript程序员来说,这语法真是太熟悉了,除了第一、第二行之外。无论如何,我们要开始我们的梦想之旅了,那么接下来我们将example.js设置成可执行并运行它:

  1. [root@www source]# chmod +x example.js
  2. [root@www source]# ./example.js

此时屏幕显示:Server running at http://127.0.0.1:8000/
访问:http://127.0.0.1:8000/,如果看到页面输出:Hello,This is Laohan(www.handaoliang.com)!,那么恭喜你,安装成功了,接下来就需要发挥你自己的聪明才智,去实现各种强大的功能了。很激动人心吧?

后面将为大家深入分析NodeJS,敬请期待。

原创文章,转载请注明出处!

22十/092

关于JS前端添加RSH会多出一个history的解决方案

  在我负责的某一个项目当中,由于使用了大量的Ajax技术,而Ajax技术由于是异步请求,所以使得浏览器无法记录相应的History,这就意味着用户无法通过点击“前进”、“后退”按钮来进行操作,显然,从用户体验的角度来说,由于点击前进后退按钮是用户的习惯性操作,因此这是很不人性化的。
  那么,为了规避用户体验不佳的问题,我们使用了Google的一个开源项目RSH来做为最终解决方案。

  先介绍一下RSH,RSH是ReallySimpleHistory,是一个用于处理Ajax/DHTML应用程序的书签以及浏览器历史记录的轻量级的JavaScript库(Really Simple History is a lightweight JavaScript library for the management of bookmarking and browser history in Ajax/DHTML applications.)。它的原理是通过在URL上添加一串经过格式化的字符串,以使得每一次Ajax操作看起来都像是点击了不同的URL(实际上就是模拟点击URL的操作)。以此来达到用户可以通过点浏览器按钮来前进或者后退的目的。
  RSH目前的最新版本是:0.6Final。项目主页在这里:http://code.google.com/p/reallysimplehistory/

  但是由于RSH在初始化的时候就会往URL后添加一个格式化的字符串,所以这就导致当用户进入到某一个页面时,明明是第一次访问,此时浏览器也会有一个history出现,此时点击“后退”按钮是会退回到一个空白页或者重复刷新一次。为了规避这个问题,通读了RSH的代码之后,在结合前端和后端的基础之上做了一点调整,最后问题完美解决。我这里后端用的是Python+Webpy,所以后端代码以Python代码为例。

  现在的解决方案是,后台location到新的URL里,即往header里插入跳转的URL。然后客户端init的时候把这个事件插入hash表,这样就不会多出一个history来,且又能实现插入history的效果。

  实现步骤:
  1、首先是后台需要location到新的URL。
  比如我现在开放给用户的访问链接是:http://www.youcompany.com,在webpy获取到用户请求的时候(GET),判断如果为首页(第一次访问,此时我这边是根据请求特性来判断的,即如果请求为空,则意味着是第一次访问),则跳转:

  1. def do_request(self,call_name,method):
  2.     if not call_name:
  3.         cur_urls = web.ctx.env.get("HTTP_X_FORWARDED_SERVER",None)
  4.         self.urls = "".join(["http://",cur_urls,"/main/#my-index-page"])
  5.         web.redirect(self.urls)

  此时新的URL为:http://www.youcompany.com/main/#my-index-page,之后所有的请求都将发送到:http://www.youcompany.com/main/下面。此是history的记录为0。

  2、客户端监听。
  由于用户访问首页时,必须要添加一个监听才能使用RSH的方法,而RSH在添加监听函数的时候,相应的也会改变URL,这样还是会产生一个垃圾history,这就需要我们对RSH做一个修改。

在add方法里,加一个形参:is_hidden_location

  1. //---------------------原来:--------------------
  2. add: function(newLocation, historyData) {
  3. //---------------------修改后:--------------------
  4. add: function(newLocation, historyData, is_hidden_location) {

查找:window.location.hash,加一个判断:

  1. if(!is_hidden_location){
  2.     window.location.hash = newLocation;
  3. }

  在首页init添加监听的时候,为add方法加上一个参数:True,这样就不会添加路径到URL上,但不会影响RSH的history事件hash表。

20十/090

自己写的一个JS遮罩类

  在一些Ajax的页面中,我们经常能看到一些类似于Windows关机的效果,即是把界面上所有元素都灰度化,同时在最上面弹出一个带有颜色的对话框。貌似很酷的样子。
  其实这在页面上很好实现,实际上只要初始化一个浮动层,通过JavaScript把这个浮动层拉到符合页面的宽度和高度,当需要弹出对话框时,只要使用JS去Display这个图层即可以了。当然,如果纯粹都用JavaScript来实现,必然会出现很多冗余的代码,因此在这个我基于Prototype写了一个遮罩类,用它可以有效的减化生成的过程:

  1. /**
  2. * Package:        resource.web.javascript
  3. * FileName:        cover_effect.js
  4. * Description:        show cover effect.
  5. * Author:            handaoliang<handaoliang@gmail.com>
  6. * Date:            Oct 24,2008
  7. **/
  8. var cover_effect_conv_t = {
  9.     version: "cover_effect Ver 1.0",
  10.     create_date: "2008-10-24 15:12:00"
  11. };
  12.  
  13. var readerCoverEffect = new Class.create();
  14. Object.extend(readerCoverEffect.prototype, {
  15.     initialize: function(cover_element_id, bgcolor, color_deep){
  16.         if (cover_element_id) {
  17.             this.cover_eid = cover_element_id;
  18.         }
  19.         else {
  20.             this.cover_eid = createRandElementID();
  21.         }
  22.         if (typeof bgcolor == "undefined") {
  23.             this.bgcolor = "#333";
  24.         }
  25.         else {
  26.             this.bgcolor = bgcolor;
  27.         }
  28.         if (typeof color_deep == "undefined") {
  29.             this.color_deep = 4;
  30.         }
  31.         else {
  32.             this.color_deep = color_deep;
  33.         }
  34.     },
  35.     createCover: function(){
  36.         //如果存在遮罩层,返回NULL。
  37.         if ($(this.cover_eid)) {
  38.             return;
  39.         }
  40.         //创建一个DIV,即遮罩层。
  41.         this.cover_element = document.createElement("div");
  42.         //为这个DIV设置一个ID。
  43.         this.cover_element.setAttribute("id", this.cover_eid);
  44.         Element.insert(document.body, {
  45.             bottom: this.cover_element
  46.         });
  47.         Element.setStyle(this.cover_element, {
  48.             position: "absolute",
  49.             zIndex: 800,
  50.             // "-moz-opacity":parseInt(this.color_deep)/10,
  51.             // "FILTER": "alpha(opacity=40)",
  52.             filter: "Alpha(opacity=" + parseInt(this.color_deep) * 10 + ")",
  53.             opacity: parseInt(this.color_deep) / 10,
  54.             // "-khtml-opacity": parseInt(this.color_deep)/10,
  55.             // backgroundColor:this.bgcolor,
  56.             backgroundColor: "#333333",
  57.             left: "0px",
  58.             top: "0px"
  59.         });
  60.         this.resizeConver();
  61.         // 注册onresize事件
  62.         Element.observe(window, "resize", this.resizeConver.bindAsEventListener(this));
  63.     },
  64.     destroyCover: function(){
  65.         try {
  66.             //找到元素
  67.             var cover_element_obj = $(this.cover_eid);
  68.             //如果此元素存在,则把它删除。
  69.             if (cover_element_obj) {
  70.                 document.body.removeChild(cover_element_obj);
  71.             }
  72.             // 注销onresize事件
  73.             Element.stopObserving(window, "resize", this.resizeConver.bindAsEventListener(this));
  74.             /*
  75.              //如果是IE,则需要恢复select和flash的显示。
  76.              if(myBrowser.is_ie)
  77.              {
  78.              this.displayPeekElement();
  79.              }
  80.              */
  81.         } 
  82.         catch (e) {
  83.             dmsg(e);
  84.         }
  85.     },
  86.     resizeConver: function(){
  87.         var widow_size = getWindowDimensions();
  88.         this.win_width = widow_size.win_width;
  89.         this.win_height = widow_size.win_height;
  90.         //设置CSS,使之具有遮罩的样式。
  91.         Element.setStyle(this.cover_element, {
  92.             width: this.win_width + "px",
  93.             height: this.win_height + "px"
  94.         });
  95.     },
  96.     //隐藏掉所有的select框
  97.     hiddenPeekElement: function(){
  98.         selects = document.getElementsByTagName("select");
  99.         for (i = 0; i != selects.length; i++) {
  100.             selects[i].style.display = "none";
  101.         }
  102.     },
  103.     //显示所有的select框
  104.     displayPeekElement: function(){
  105.         //display select element
  106.         selects = document.getElementsByTagName("select");
  107.         for (i = 0; i != selects.length; i++) {
  108.             selects[i].style.display = "inline";
  109.         }
  110.     }
  111. });
11十二/080

一点经验总结

  写这样一段话,首先是因为看了这篇文章:http://eishn.blog.163.com/blog/static/65231820072574234114/
  出于和这篇文章作者相同的心理,本来想找一个zDaemon的替代品(之前大概读了一下zDaemon的代码,读得我实在晕),结果在这篇文章在发现一些值得我们学习的经验,尤其是:

  1、Python 的语法决定了 Python 能够编写出目前最复杂的程序。代码量失控是 Python 的最大敌人, 这说明我们可能正在用 Java 写 Python。
  2、过度向对象实际上就是完全无法重用, 这是一个典型的例子。

  看到这些非常有感触,因为这些问题我都碰到过并且深深为之头疼,不仅仅是Python,任何面向对象的语言都不可避免的有此诟病,比如我之前曾经接手过一个项目,这个项目的极品之处就在于,把所有的操作都封闭在了一个JS文件中,最后导致这个JS文件,无论是代码的深度还是行数,都极为可观。而且之前封装的大部分看起来结构严谨的方法和类,基本上都无法重用,这主要原因就在于:当初在设计这些方法时,只考虑到了部分共性而没有考虑到大部分的无法共通的属性。加上命名空间的杂乱无章、规划无序,都导致维护以及后续开发的困难重重。

  严重建议以后写代码(无论是JS还是Python):
  1、尽量拆分(尤其是JS。Python还好办,按模块来做,个人觉得尽量不要一个文件里写多个类,行数太多非常不易于阅读理解。)。
  2、除非是经验丰富,否则请尽量少用继承,个人觉得继承这个东西,如果没有完美的规划设计,只会添乱,尤其是在快速开发某个项目时,饥不择食的情况下根本没法考虑周全。
  3、变量的命名尽量做得可区分。命名混乱会给维护造成很大的困难,经常会让你在读代码的过程当中,不知道这到底是一个字符串变量还是一个对象。

   下一页