作者归档:admin

关于Cookie

Cookie是什么

在wiki中Cookie的定义为: Cookie(复数形态Cookies),中文名称为小型文本文件或小甜饼(貌似这只是一个中文翻译,平时还是直接读的英文),指某些网站为了辨别用户身份而储存在用户本地终端上的数据。

Cookie是服务器在本地机器上存储的小段文本并随每一个请求发送至同一个服务器,是客户端与服务器保持会话的主要手段,其内容总是保存在客户端中,按在客户端中的存储位置,可分为内存Cookie和硬盘Cookie。内存Cookie由浏览器维护,保存在内存中,浏览器关闭后就消失了,其存在时间是短暂的。硬盘Cookie保存在硬盘里,有一个过期时间,除非用户手工清理或到了过期时间,硬盘Cookie不会被删除,其存在时间是长期的。所以,按存在时间,可分为非持久Cookie和持久Cookie。

Cookie被浏览器默认发送到服务器,通过HTTP协议,请求头中以Cookie字段存储客户端的Cookie值,应答头中以Set-Cookie字段应答,当服务器需要有多个cookie字段写到客户端,则在应答头中将包含多个Set-Cookie字段。 Cookie的使用非常简单,以PHP为例,在脚本中使用setcookie函数设置对应的key,value值,通过全局变量$_COOKIE直接读取客户端发送过来的Cookie值。

Cookie简单,但是存在一些问题:

  1. 安全,明文传输内容,容易被篡改。和HTTP一样,只能说看如何使用了,看你存储的是什么了
  2. 增加网络流量,加重整个网络的负载。默认浏览器在发送请求时会将本地Cookie的内容通过Cookie字段传输到服务器。所以经常我们会独立静态图片或资源的域名,使其Cookie为空。
  3. 大小限制。各浏览器对于单个cookie的大小限制为4096个字节左右,超过大小的内容将被忽略。每个域名下可以存储有30~50个cookie,不同的浏览器,不同的版本这些值不同。为什么会有大小限制,因为cookie会默认发送,当cookie太大时,可能会导致服务器响应出错等。

Cookie的历史

1993年3月,这样一个春光明媚,面朝大海,春暖花开的时节,现在的网景公司前雇员,当时的NB的网景公司员工Lou Montulli灵光一闪,Cookie华丽丽的出生了。 Cookie第一次被正式定义是在RFC2109,嗯,这是1997年2月的一天,也许那时还有些冷。在RFC中,Cookie被称为HTTP State Management Mechanism(HTTP 状态管理机制)。  RFC2109在2000年10月被RFC2965过时,而在2011年4月,最新的刚刚火热出炉的RFC6265将RFC2965过时,可谓是长江后浪推前浪,前浪死在沙滩上。另外,RFC2964记录了使用Cookie的最佳实践。

换句话说:Cookie经过了Netscape标准、RFC2109、RFC2965和RFC26265四个标准:

  • Netscape标准:Netscape是最原始的Cookies规范,同时也是RFC2109的基础。尽管如此,还是在很多重要的方面与RFC2109不同,可能需要特定服务器才可以兼容。
  • RFC2109: RFC2109是W3C组织第一次推出的官方Cookies标准。理论上,所有使用版本Cookies的服务端都应该使用此标准。HttpClient已经将此标准设定为默认。遗憾的是,许多服务端不正确的实现了标准或者仍然使用Netscape标准。所有有时感到此标准太多于严格。
  • RFC2965:RFC2965定义了版本2并且尝试去弥补在版本1中Cookie的RFC2109标准的缺点。RFC2965是,并规定RFC2965最终取代RFC2109. 发送RFC2965标准Cookies的服务端,将会使用Set-Cookie2 header添加到Set-Cookie Header信心中,RFC2965 Cookies是区分端口的。
  • RFC6265:RFC6265主要是干掉了RFC2965,在9.3和9.4小节。另外,增加了HttpOnly字段,指定HttpOnly的Cookie不能被客户端读写,仅供HTTP传输使用,或者就服务器可以读写,浏览器作为客户端需要确保其不能读写。

Cookie和Seesion

Cookie和Session都用来保存状态信息,做会话处理,都是保存客户端状态的机制,它们都是为了解决HTTP无状态的问题而所做的努力。 Session存储在服务器,一般通过Cookie来存储其生成的唯一ID(seesionID),当Cookie被禁用时,通常用URL回写的机制来替换Cookie。

Cookie和Session有一些不同:

  1. 存储位置的不同:Cookie将状态保存在客户端,Session将状态保存在服务器端;
  2. 与HTTP协议的关系不同:Cookie需要通过网络传输,依赖于HTTP协议,Session并没有在HTTP的协议中定 义;
  3. 可用性不同:相对于Cookie,Session在客户端禁用Cookie后还可以通过URL回写机制实现Session会话机制。
  4. 安全性不同:因为存储的位置不同,Cookie更容易被篡改,存储在服务器的Session相对来说则安全一些,客户不能随意读取这些内容,除非获到其它用户的了sessionID,这也是XSS攻击会关注的地方。

同源策略

说到WEB的安全问题就不得不提同源策略。浏览器的同源策略是 Web 安全的基础,所有的主流浏览器都会有相应的实现。同源策略中“源”是一个包含主机名、协议和端口号的三元组,则同源表示:同协议,同域名和同端口,三者都相同。同源策略的出发点是它认为自任何站点装载的信赖内容是不安全的。在同源策略的限制下,浏览器只允许网页中的脚本(如 JavaScript 或 VBScript)访问与之同源的 HTTP 请求和 Cookie。对于Cookie来说,同源策略就限制了网站间的Cookie读写操作。即使在服务器使用setcookie(PHP)函数对其它域名执行Cookie写操作也是无效的。 setcookie的域名是用来指向当前域名或根域名之类的用的,设置Cookie时,如果不指定domain的值,默认就是本域。

参考资料:

  1. http://wiki.apache.org/HttpComponents/ReferenceMaterials
  2. http://www.cnblogs.com/shepherd2012/archive/2012/08/03/2621797.html
  3. http://zh.wikipedia.org/wiki/Cookie
  4. http://curl.haxx.se/rfc/cookie_spec.html
  5. http://tools.ietf.org/html/rfc6265

Bash之命令历史的存储和记录

在Bash中我们可以使用 history 命令回顾,修改和重用之前使用过的历史命令。去掉信号机制、去掉作业控制、去掉各种参数调用,今天我们只看下Bash中如何记录命令,如何存储历史命令。

在 Bash 的源代码中,history 命令的定义代码为 builtins/history.def , builtins 目录下存放的是内部命令的源代码,每个内部命令是一个def文件,如history.def,cd.def等。 Makefile中DEFSRC声明了所有内部命令的def文件。由 mkbuiltins.c 生成编译时辅助工具 mkbuiltins,mkbuiltins 处理 *.def 文件,生成命令的 *.c 源程序以及 builtins.c 、 builtext.h。 builtins.c 和 builtext.h 相当于各个内部命令的索引。

history.def 的作用是解析命令并将不同的参数分发命令到不同的功能实现,如读取命令、覆盖命令等。一些功能实现代码文件在 bashhist.c/bashhist.h,但是关于历史记录的基础操作并不在这两个文件中,它们在 lib/readline 中。这是因为 Bash 采用的GNU Readline函数库中。 Readline提供了统一的行编辑和历史记录功能的命令行交互方式。

history命令在显示时会显示所有的历史记录,这里的所有历史记录包括最开始从文件中读取的历史记录,还包括当前会话产生的记录。假设你的历史记录中已经有了500条命令,如果你用其它文档编辑器将历史日志文件写到1000条,打开终端,你会发现显示的还只有500记录。这是因为在打开终端初始化时,不仅仅有历史记录列表的读操作,还会有关于文件记录数限制的初始化操作,确保文件中的记录条数不会大于设定的最大值。这个初始化操作在 load_history 函数中实现,函数最开始就做了两次历史记录文件的截取操作,一次是默认500,一次是设置的最大值: HISTFILESIZE 。

在历史记录中有一个会话的概念,不同会话中的命令在没有保存到文件前是不会互相冲突的。比如,打开终端A如果你删除 .bash_history 的前10行命令,保存,在命令行中输入history,你会发现输出的命令历史记录并不是从1开始,而是从11开始的。如果此时,你再开一个终端B,输入若干条命令后,再输入history,你会发现历史记录中没有在终端A输入的命令。这是由于在一个终端会话中,历史记录从固化存储位置的读取操作只有一次,写入操作也只有一次,即在打开终端时读取,在关闭终端时写入。

除了文件存储,历史记录也可以记录在MMAP,对应的宏定义为 HISTORY_USE_MMAP。

如果以文件存储方式,命令记录以一行一条命令的方式存储在.bash_history(默认,如果有设置 HISTFILE 则优先使用 HISTFILE 中的值),虽然我们使用 history 命令时看到每条命令前都会有一个标号,我们可以使用 !标号 的方式重新执行命令,这个标号并不是唯一不变的,标号是在初始化时在读取文件时在程序操作中标记的,后续有命令加入时,标号会自增,这个自增并不会受历史记录的最大值限制。

当一次会话退出时保存历史记录文件,将历史数据固化存储,并将会话统计清零。当存储到文件时,Bash 会将此前会话中的命令直接存储到文件末尾,如果文件的记录数大于定义的最大记录数,则清空旧的历史命令,并且当下次再存储时会重写此前文件。代码示例如下:

      if (history_lines_this_session <= where_history () || force_append_history)
        append_history (history_lines_this_session, hf);
      else
        write_history (hf);
 
      sv_histsize ("HISTFILESIZE");

存储操作最终还是归集于存储介质的读写操作,如对文件的读写,增加的只是对业务逻辑规则的各种限制。命令可以在执行命令时记录,也可以在命令刚输入,但已经识别的情况下记录,Bash 属于后者。 Bash 在 yacc 做语法分析时将用户输入的命令通过 maybe_add_history 函数写入到当前会话的命令历史记录表中。在做语法分析时就已经记录了用户输入的命令,此时记录就不用管命令最终的结果是怎样,也不用管如果执行过程出了异常会怎样处理。它只是如实的在执行前记录用户输入的什么命令,由此,我们可以定义 Bash 的命令历史记录的定义为用户输入的命令历史记录。

参考资料: http://files.linjian.org/articles/techreport/bash_study.tar.gz

Yii 框架的视图层实现

如果你想看看 Yii 框架的视图实现过程,请继续向下;如果你想看看胖子的碎碎念,请直接拉到文章最后;如果你只是路过,那也路过留名吧^_^。

Martin Flower 在《企业应用架构模式》中提到 MVC 模式的关键点在于两个分离:从模型中分离视图和从视图中分离控制器。视图的表现在很大程度上决定了此模式的使用,以及框架对 MVC 模式的各个层级的分离水平。 Yii 框架是一个基于 MVC 模式的框架,它在视图这块做了很多的工作,很清晰的实现了视图的功能。

Yii 框架的视图是一个包含了主要的用户交互元素的 PHP 脚本。每个视图有一个名字,当渲染( render )时,名字会被用于识别视图脚本文件。视图的名称与其视图脚本名称是一样的。例如:视图 edit 的名称出自一个名为 edit.php 的脚本文件。要渲染时,需通过传递视图的名称调用 CController::render()。这个方法将在 “protected/views/控制器 ID” 目录下寻找对应的视图文件,其寻找方法为 getViewFile。这里的 protected/views 只是默认的存储位置,我们可以通过 Yii::app()->setViewPath 方法改变此路径。

在视图脚本内部,我们可以通过 $this 来访问控制器实例。同时,我们也可以在视图里以“$this->属性名”的方式获取控制器的任何属性,这种调用方式是通过实现__get魔法方法实现的。

一次较为完整的视图渲染过程在 CController 类的 render 函数中体现得淋漓尽致。当控制器中从模型(model)中拿到数据后,一般会执行 render() 方法创建视图,其大概过程如下:

  1. 执行预留的 beforeRender 钩子。
  2. 查找渲染局部内容 $output,实际上这里是一个部分视图渲染的过程,它包括获取视图文件路径,渲染视图文件,处理输出三个部分。在渲染的过程中,通过 PHP 中的 extract() 方法把数组中将变量导入到当前的符号表,直接 require 视图文件,从而合并数据和表现。
  3. 如果存在布局,则将得到的内容放入以 content 为下标的的数组传递给父类的 renderFile() 方法中,重复执行渲染视图的过程。在布局中执行 ,输出局部内容$output,实现了局部和布局视图的合并。为了实现多级布局,在布局中还可以通过控制器的视图装饰方法加载。
  4. 执行预留的 afterRender 钩子。

在渲染视图的时候,如果参数中有传递对应的值,会执行 processOutput() 方法,此方法一般在渲染视图结束时才会调用,它实现了三个过程:

  1. 注册客户端脚本,具体由 ClientScript 组件管理。
  2. 如果存在,则执行动态内容输出。
  3. 页面内容 base64_encode 加密,如果存在 zlib 扩展,则会先压缩。

在 CController 类中对视图的渲染除了上面的render方法外,还有其它多种方法:

  • render方法: 和布局一起渲染 render($view,$data=null,$return=false)
  • renderPartial方法: 仅渲染视图内容,或者是渲染部分页面内容。它与 render() 方法的不同是它不会渲染布局,并且在 render() 方法中也会调用此方法。 renderPartial($view,$data=null,$return=false,$processOutput=false)
  • renderText方法:渲染静态内容和布局。renderText($text,$return=false)
  • renderDynamic方法:通过回调函数渲染动态内容,通常我们会在模板文件中中调用此方法。renderDynamic($callback)->renderDynamicInternal($callback,$params)
  • renderClip方法:渲染显示 CClipWidget 生成的内容,此处需要指定名字。renderClip($name,$params=array(),$return=false)

对于不同的页面中共用的内容,虽然可以通过 renderPartial 方法渲染部分页面视图,但是必然存在对于数据部分的重复,因为这些视图都需要调用控制提供的数据,从而产生耦合。因此 Yii 框架 提供了另一个独立的视图部件,官方称之为 Widget (小物件?小挂件?)。

小物件是 CWidget 或其子类的实例。它是一个主要用于表现数据的组件。小物件通常内嵌于一个视图来产生一些复杂而独立的用户界面。也算是一种界面的独立和松耦合的设计。如我们做WEB应用时常用的列表,翻页,日历等。这些 Widget 增加了界面的复用度,减少了代码量。

与前面视图部分不同的是,它没有布局文件支持,并且 Widget 视图中的 $this 指向 Widget 实例而不是控制器实例,这里实现了与控制器的分离。如果要实现一个自定义的 Widget ,我们仅需要继承 CWidget 并覆盖其 init() 和 run() 方法,可以定义一个新的 Widget 。

我们在视图中通过 $this->widget() 或 $this->beginWidget() 和 $this->endWidget() 调用 Widget,两者的区别在于第二个方法可以在显示的过程中添加 html 内容。添加内容的位置在 init() 方法和 run() 方法输出的内容之间。

除了布局、Widget 外, Yii 框架实现系统级的视图,用来显示 Yii 的错误和日志信息。

系统视图的命名遵从了一些规则。比如像“errorXXX”这样的名称就是用于渲染展示错误号 XXX 的 CHttpException 的视图。在 framework/views 下, Yii 提供了一系列默认的系统视图. 我们可以通过在 protected/views/system 下创建同名视图文件进行自定义。系统默认的 exception 视图非常赞,结合 Yii 本身的 traces 机制,当抛出异常或出错时就会很详细的定位出问题的代码所在。

以上只是胖子阅读 Yii 框架源码的笔记。结合《企业应用架构模式》这本书的内容,如页面控制器、前端控制器、活动记录等,胖子发现对框架的实现有更深入的理解。一方面印证了书上的理论,一方面为实现过程的原理找到了出处。不晓得 Yii 框架的作者是否对此书也有精读,或者是经验的积累?

参考资料: http://www.yiiframework.com/doc/guide/1.1/en/basics.view