彭琪谈编程

Apache的rewrite要点

Apache的rewrite模块功能强大,但是想要把它用顺需要注意几点,不然你会很痛苦!

RewriteCond在RewriteRule条件之后判断

apache的rewrite规则是,先判断是否匹配RewriteRule的条件,成功后再判断是否匹配RewriteCond。假设我们想要把不存在的页面redirect到另外一个地址,我们可以用这样的规则:

1
2
3
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ http://www.example2.com/$1 [R,L]

这里首先用(.*)$这一正则表达式来匹配所有的url,然后再判断这个url指向的文件是否存在。

DocumentRoot设定后‘/’永远存在

继续上面的例子,假设我的一个域名没有首页,我希望把针对这个域名的访问也转发到另外一个地址,比如www.example.com跳转到www.example2.com,那么上面的规则就不够了,因为‘/’目录是存在的,无法通过条件

1
RewriteCond %{REQUEST_FILENAME} !-d

我们需要在后面再加一条规则

1
RewriteRule ^$ http://www.example2.com [R,L]

这样我们就能成功地把www.example.com下的首页和不存在的页面都转发到www.example2.com下了。

.htaccess里的规则不能直接放到httpd.conf或VirtualHost里

.htaccess的规则只是针对其文件所在目录下而言的,其效果和放在Directory里一样,然而当把这些规则挪到httpd.conf或VirtualHost里时,就需要做出调整了。继续上面的例子,假设我们把DocumentRoot设为”/var/www”,那么上面介绍的两个规则放在/var/www/.htaccess文件里,或者放在httpd.conf或VirtualHost的<Directory /var/www>中,效果一样。然而这两个规则其实都是Server级的,我们完全可以把它放到VirtualHost下,而不是<Directory /var/www>中,新的规则如下

1
2
3
4
5
RewriteCond %{LA-U:REQUEST_FILENAME} !-f
RewriteCond %{LA-U:REQUEST_FILENAME} !-d
RewriteRule ^/(.*)$ http://www.example2.com/$1 [R,L]

RewriteRule ^/$ http://local.sgj [R,L]

这里有两点区别需要注意,首先REQUEST_FILENAME前加了‘LA-U:’,这是因为当处理VirtualHost下的Rewrite规则时,apache还处在一个url对应文件的翻译阶段(URL-to-filename translation),apache这个时候还不知道该访问哪个文件或目录,自然也就无从得知这个文件或目录是否存在了,这里的‘LA-U:’就是让apache先去获取这个变量,知道自己该访问哪个文件或目录,然后才能判断其是否存在。 第二点是两个正则表达式里都多了一个‘/’,这是因为VirtualHost和<Directory /var/www>不同,它提供给RewriteRule判断的是完整的url,及www.example.com/index.html里的‘/index.html’,而<Directory /var/www>下提供的是目录‘/’之后的内容,及‘index.html’。以此类推,www.example.com/abc/index.html,在VirtualHost下判断的是‘/abc/index.html’,在<Directory /var/www>下则是‘abc/index.html’,而在<Directory /var/www/abc>下则是‘index.html’了。

Ruby与PHP的比较

最近在学Ruby,觉得这门语言有许多优美的特性,相对于PHP来说,代码更简洁,可读性更强,下面做些简单总结。

Ruby里所有的变量都是对象

整数、浮点数、字符串还有数组等基本变量类型都是对象

ruby
1
2
3
1.object_id    #3
1.5.to_s    #"1.5"
"abc".is_a?(Object)    #true

这样设计的一个好处就是,所有类型相关的函数都变成了对象的方法,使得代码更加直观易懂,而不是像PHP那样有成百上千个全局函数,各自的命名规则还不一致:

ruby
1
2
3
"a b c".split    #["a", "b", "c"]
"abc".length   #3
["a", "b", "c"].length
php
1
2
3
4
<?php
explode(" ", "a b c");
strlen('abc');
count(array('a', 'b', 'c'));

Ruby不需要用分号来结束每行代码

PHP以及很多其它的语言使用分号的目的其实是为了区分表达式。然而,为了代码的可读性,我想没有人会把几行代码写成一行,所以还使用分号来结束每一行代码就显得多余了。

同样的功能,Ruby让你写的代码更少

简单优美,这是Ruby的设计宗旨,首先体现在方法的命名和操作符上面:

ruby
1
2
3
array = [1, 2, 3, 4, 5]
last = array.last    #5
subarr = array[1,3]    #[2,3,4]
php
1
2
3
4
<?php
$array = array(1, 2, 3, 4, 5);
$last = $array[count($array)-1]
$subarr = array_slice($array, 1, 3);

也体现在变量的声明和互换上面:

ruby
1
2
a, b = 1, 2    #a=1, b=2
a, b = b, a    #a=2, b=1
php
1
2
3
4
5
6
7
<?php
$a = 1;
$b = 2;
$tmp = $a;
$a = $b;
$b = $a;
unset($tmp);

还体现在一些循环操作上面:

ruby
1
2
array = [1, 2, 3]
map = array.map { |item| item + 10 }    #[11, 12, 13]
php
1
2
3
4
5
6
<?php
$array = array(1, 2, 3);
function plusTen($v){
    return $v + 10;
}
$map = array_map('plusTen', $array);

以及一些常用的文件操作:

ruby
1
2
3
File.open('example.txt') do |file|
    capital_lines = file.map { |line| line.strip.upcase }
end
php
1
2
3
4
5
6
7
<?php
$handle = fopen("example.txt", "r");
$capital_lines = array();
while (($line = fgets($handle)) !== false) {
    $capital_lines[] = strtoupper($line);
}
fclose($handle);

GWO使用心得

GWO即google website optimizer,是一款用来给网页做A/B testing的工具。在我的一个项目里经常会用到这个工具来选择转化率最高的广告,然而在使用时我曾碰到一个很棘手的问题——抓不到数据。

这个问题通常只会在一种情况下发生,离开当前页面前(提交表单或点击链接时)计算转化。在抓耳挠腮了数周时间依然百思不得其解之后,我终于在一个google员工的博客里找到了答案

gwo的原理和google analytics类似,通过向google服务器请求一个图片来发送数据。然而当浏览器获得一个图片请求时,它并不是马上就会发送这个请求,而是会将这个请求放入一个队列,等上一段时间(可能就是几毫秒)后再请求。问题就出在这关键的几毫秒时间里!如果浏览器在这段时间里得知它需要加载一个新的页面,那么它会将队列里老页面残余的请求直接丢掉!这就是我的实验为何没有抓到数据的原因,我的图片请求直接被浏览器丢弃了!

问题终于找到了,那么该怎么解决呢?这篇博客的作者给出了一个很简单的办法,即等上100毫秒再请求新页面。

1
2
3
4
5
6
7
8
9
10
11
<script type="text/javascript">
function doGoal(that) {
  try {
    var pageTracker=_gat._getTracker("UA-123456-1");
    pageTracker._trackPageview("http://www.example.com");
    setTimeout('document.location = "' + that.href + '"', 100)
  }catch(err){}
}
</script>

<a href="www.example.htm" onclick='doGoal(this);return false;'>Click me</a>

100毫秒的时间足够浏览器将队列里的图片请求发出去,又不会让人感到任何等待时间上的差异,很好的解决了这个问题。我的实验终于能正常的抓到数据了,困扰我几周的难题终于搞定。

浅谈model, Orm, Dao和active Record的区别

在做web开发中,经常会碰到这样几个概念:

  • Model
  • DAO,data access object,数据访问对象
  • ORM,object-relational mapping,对象关系映射
  • Active Record

这些概念都是和数据相关的,然而他们之间有怎样的区别呢?

首先来看Model,模型。模型是MVC中的概念,指的是数据和改变数据的操作(业务逻辑)。模型通常指代现实生活中的某样实体。以订单为例,每个订单都包含许多数据,如客户、价格、明细等等,这些数据都叫做订单这个模型的属性,此外,和订单相关的一些列操作,比如当购买时,你可能需要先检查库存,给与一定的优惠,再更新账户余额和积分等等,这些就叫做业务逻辑,也是模型的一部分,从代码上来讲,是要放在模型中的。

当模型执行完业务逻辑后,我们便要把模型中的数据保存到数据库中。如果我们直接把和数据库相关的代码放在模型里,会使得以后的维护相当的麻烦。在我之前的一个项目中,我们用户的增长相当快,导致一台数据库无法支撑所有的访问,不得不使用分库来解决问题。然而前人把SQL语句直接写在了模型这一层里,这导致分库相当的麻烦,我们只能先把这些SQL语句抽出来,才能把分库进行下去。我们把这些抽出来的SQL代码放到单独的一层,这一层便是DAL,Data Access Layer,数据访问层,它由许多DAO组成,目的便是把和数据库相关的代码封装起来,这样当我们执行分库时,便只用调整DAO的代码了,模型根本不用关心它使用的数据是放在A库还是B库。

DAO其实是来源于J2EE的一个设计模式,当初的目的也是使得企业更换数据库时,不用影响模型层的代码。

与DAO类似,ORM也是一种封装数据访问的概念。然而ORM不像DAO只是一种软件设计的指导原则,强调的是系统应该层次分明。ORM更像是一种工具,有着成熟的产品,比如JAVA界非常有名的Hibernate,以及很多PHP框架里自带的ORM库。他们的好处在于能将你程序中的数据对象自动地转化为关系型数据库中对应的表和列,数据对象间的引用也可以通过这个工具转化为表之间的join,而Hibernate甚至提供一套他们自己的数据查询语言HQL来解决复杂的查询问题。

使用ORM的好处就是使得你的开发几乎不用接触到SQL语句。创建一张表,声明一个对应的类,然后你就只用和这个类的实例进行交互了,至于这个对象里的数据该怎么存储又该怎么获取,通通不用关心。

Active Record则是随着ruby on rails的流行而火起来的一种ORM模式,它是把负责持久化的代码也集成到数据对象中,即这个数据对象知道怎样把自己存到数据库里。这与以往的ORM有不同,传统的ORM会把数据对象和负责持久化的代码分开,数据对象只是一个单纯包含数据的结构体,在模型层和ORM层中传递。而在Active Record中,模型层集成了ORM的功能,他们既代表实体,包含业务逻辑,又是数据对象,并负责把自己存储到数据库中,当然,存储的这一部分代码是早已在模型的父类中实现好了的,属于框架的一部分,模型只需简单的调用父类的方法来完成持久化而已。