Author:Alex Gu <gusg AT livedoor.cn>
MSN:gsging000 AT hotmail.com
转载请保留作者信息.谢谢.
前言
笔者在"Sledge学习 -- Sledge入门"中为大家初步介绍了什么是Sledge、Sledge的安装/配置以及一个最简单的Sledge开发示例.
本文中笔者将与大家一起深入分析Sledge的运行流程,从接收请求,到处理请求,最后到返回结果的过程.
笔者愿意为Perl、愿意为Sledge的推广作贡献.由于本人水平有限,文章内容也是在项目中慢慢总结出来的.如有错误之处,请指出,我会立即修改.
零、准备
前文提到Sledge基于Apache运行,同时支持mod_perl和CGI方式.本文将以mod_perl方式为例.
首先在Apache配置mod_perl方式运行的Sledge.
PerlRequire /path/to/Sledge/etc/startup.pl
<Location /sledge_www>
SetHandler perl-script
PerlHandler Sledge::Dispatcher::Properties
PerlSetVar SledgeMapFile /path/to/Sledge/etc/map.properties
AddAcceptForwarder All
</Location>
一、接收请求
看到以上配置首先我们知道应该到@INC中寻找Sledge/Dispatcher/Properties.pm模块.
根据mod_perl知识我们知道PerlHandler中必须有一个handler()方法来处理Request.Sledge::Dispatch::Porperties中并没有handler(),让我们到它的父类Sledge::Dispatcher中看看.
在Sledge::Dispatcher中我们看到了handler方法.我们来看一看它都处理什么内容:
1、解析Request
handler()调用当前模块的determine()来分析一个Request.Sledge允许用户在Apache配置文件中定义许多PerlSetVar.举例如下:
SledgeExtension
SledgeStaticExtension
SledgeDispatchStatic
SledgeDirectoryIndex
SledgeMapFile
等
determine()根据这些变量返回了4个变量:
$loadclass #需要加载的业务类模块名称
$page #对应页面名称
$is_static #判断是否请求的是静态页面的标志位
$slash #判断uri格式是否是以/结尾
2、处理Request,确定使用哪个$loadclass的dispatch_$page方法处理
$loadclass与$page的对应关系定义在SledgeMapFile变量中定义的.
确定当前请求对应的$loadclass后进行加载.以下对部分代码进行解释:
my @indexes = $r->dir_config('SledgeDirectoryIndex') ? split(/\s+/, $r->dir_config('SledgeDirectoryIndex')) : 'index';
for my $index (@indexes) {
if ($loadclass->can("dispatch_$index")) {
$page = $index;
last;
}
}
$page ||= $indexes[0];
3、接续以上代码解释,由此开始准备跳转到实际的业务类中进行处理
if (!$loadclass->can("dispatch_$page")) {
if ($no_static) { #如果设定了SledgeDispatchStatic为关闭则同样不能使用dispatch_$page处理,返回"拒绝"
debug($r, "access to slash, but no_static is on");
return DECLINED;
}
$class->_generate_method($r, $loadclass, $page); #如果没有设定SledgeDispatchStatic为关闭,则生成$loadclass和$page的对应关系
}
$loadclass->new->dispatch($page); #开始使用$loadclass的dispatch_$page方法进行处理.从此出跳转到业务类进行处理.
根据以上代码我们可以推断出,在进行二次开发的业务类中,必须有一个dispatch_$page()的方法.
二、实际业务类处理请求
1、下面是笔者写的一段示例业务类代码:
package Sample::Pages::Index;
use strict;
use base qw(Sample::Pages);
use Data::Dumper;
use Sample::Data::EventsMember;
__PACKAGE__->tmpl_dirname('.');
sub dispatch_index {
my $self = shift;
my $entry = Sample::Data::EventsMember->retrieve('1');
$self->load_fillin_form;
$self->fillin_form->fdat($entry);
$self->tmpl->param(entry => $entry);
$self->tmpl->param(title => 'Powered By Sledge');
}
sub post_dispatch_index {
my $self = shift;
my $events_id = $self->r->param('events_id');
my $member_id = $self->r->param('member_id');
my $id = $self->r->param('id');
my $entry = Sample::Data::EventsMember->retrieve($id);
$entry->events_id($events_id);
$entry->member_id($member_id);
$entry->update;
}
1;
__END__
本模块由Sledge::Dispatcher::Properties调用Sledge::Dispatcher的handler方法跳转而来,这里面的new方法和dispatch方法需要往父类中查找,首先查找Sample::Pages.
Sample::Pages部分代码如下:
package Sample::Pages;
use strict;
#use base qw(Sledge::Pages::Apache);
use Sledge::Pages::Compat;
use Sledge::Authorizer::Null;
use Sledge::Charset::Default;
use Sledge::SessionManager::Cookie;
use Sledge::Session::MySQL;
use Sledge::Template::TT;
use Sample::Config;
sub create_authorizer {
my $self = shift;
return Sledge::Authorizer::Null->new($self);
}
sub create_charset {
my $self = shift;
return Sledge::Charset::Default->new($self);
}
sub create_config {
my $self = shift;
return Sample::Config->instance;
}
sub create_manager {
my $self = shift;
return Sledge::SessionManager::Cookie->new($self);
}
sub create_session {
my($self, $sid) = @_;
return Sledge::Session::MySQL->new($self, $sid);
}
1;
__END__
Sample::Pages原本直接继承Sledge::Pages::Apache,现在加入了一个Sledge::Pages::Compat模块,这个模块用来动态加载Sample::Pages的父类.Sledge::Pages::Compat代码如下:
use constant MOD_PERL => defined $ENV{MOD_PERL};
sub import {
my $base = MOD_PERL ? 'Sledge::Pages::Apache' : 'Sledge::Pages::CGI';
eval qq{require $base};
{
my $pkg = caller;
no strict 'refs';
unshift @{"$pkg\::ISA"}, $base;
}
}
1;
以上代码实现了区别对待CGI和mod_perl环境,应对了笔者在前面所述的"同时支持mod_perl和CGI方式".根据本文环境,Sample::Pages继承于Sledge::Pages::Apache.经过向上追溯我们来到了Sledge处理pages的老巢:Sledge::Pages::Base.
2、下面挑重点讲解一下Sledge如果通过Sledge::Pages::Base来生成一个Sledge环境内容的request对象.
Sledge::Pages::Base完成以下功能:
1) 通过Class::Accessor定义了该模块的对象的结构:实际结构包括如下内容
__PACKAGE__->mk_accessors(
'r', # Apache::Request or Sledge::Request::CGI
'session', # Sledge::Session
'manager', # Sledge::SessionManager
'authorizer', # Sledge::Authorizer
'charset', # Sledge::Charset
'tmpl', # Sledge::Template
'fillin_form', # Sledge::FillInForm
'finished', # flag whether request is finished
'page', # page name (arg to dispatch())
'filters', # filter subs
);
2) 初始化一个page对象:根据上一步对page对象的定义,该步骤调用如下方法进行初始化
sub create_authorizer { Sledge::Exception::AbstractMethod->throw }
sub create_manager { Sledge::Exception::AbstractMethod->throw }
sub create_charset { Sledge::Exception::AbstractMethod->throw }
sub create_session { Sledge::Exception::AbstractMethod->throw }
sub create_config { Sledge::Exception::AbstractMethod->throw }
sub create_request { Sledge::Exception::AbstractMethod->throw }
也许读者还记得笔者在前文提到过Sledge为开发者提供了大量的二次开发接口,在这里就是一个很好的体现.Sledge的这些初始化对象的操作需要开发者根据自己的项目情况进行定制.
从以上代码可以看出authorizer、charset等属性都是必须存在的,如果开发者没有在自己的业务类中重写这些属性,Sledge会调用Sledge::Exception抛出各种情况的错误(异常)提示.
page对象另一个重要的属性为Template Toolkits编写的页面服务的:
sub create_template {
my($self, $file) = @_;
return Sledge::Template->new($file, $self);
}
当然,开发者也可以对create_template进行重写.
3) new一个page对象:经过初始化,我们可以bless一个page对象了,以笔者测试机情况举例,我们看看page对象长什么样子:
warn Dumper $self;
#$VAR1 = bless( {
# 'r' => bless( do{\(my $o = 142571316)}, 'Apache::Request' ),
# 'charset' => bless( {}, 'Sledge::Charset::Default' ),
# 'manager' => bless( {}, 'Sledge::SessionManager::Cookie' ),
# 'authorizer' => bless( {}, 'Sledge::Authorizer::Null' )
# }, 'Sample::Pages::Index' );
4) 对page对象进行实际的dispatch处理:dispatch是一个重要的过程,这部分在原来Sledge寻找dispatch_$page()的基础上增加了对一个请求的method的判断.
if ($self->is_post_request && ! $self->finished) { #如果请求是post方式则调用post_dispatch_$page方法
my $postmeth = 'post_dispatch_' . $page;
$self->$postmeth() if $self->can($postmeth);
}
is_post_request()方法代码如下:
sub is_post_request {
my $self = shift;
return $self->r->method eq 'POST';
}
此处$self->r是一个基于mod_perl的Apache Request.
从以上代码我们可以知道:如果你的page中有型如如下代码:
<form name="form1" action="./index" method="post">
events_id is <input type="text" name="events_id" size="20"><br>
member_id is <input type="text" name="member_id" size="20"><br>
<input type="hidden" name="id" value=[% entry.id %]>
<input type="submit" name="submit" value="enter"><br>
</form>
您就需要在该页面对应的pm文件中包含post_dispatch_index()方法来处理这个POST请求.
那么如果是GET方法呢?请继续往下看:
unless ($self->finished) {
my $method = 'dispatch_' . $page;
$self->$method();
$self->invoke_hook('AFTER_DISPATCH');
}
如果不是post方法,并且request没有被打上finished标签,则调用dispatch_$page方法.
好了剩下的部分就该您写得(post_)dispatch_$page代码发挥作用了.
Sledge处理请求的部分到此告一段落了.
三、返回结果
返回结果这部分相对前面提到的过程就非常简单了.让我们继续看Sledge::Pages::Base的dispatch代码吧:
$self->output_content unless $self->finished;
sub output_content {
my $self = shift;
$self->r->content_type($self->charset->content_type); # set
my $content = $self->make_content;
$self->set_content_length(length $content);
$self->send_http_header;
$self->r->print($content);
$self->invoke_hook('AFTER_OUTPUT');
$self->finished(1);
}
到了这里可能再不用笔者赘述了.呵呵.
以上就可以在页面中看到您写的业务类的处理结果了.具体业务类的示例请参考笔者在前面贴出的Sample::Pages::Index代码.
您累了么?别急马上结束.
Sledge做事有始有终:
$self->_destroy_me;
sub _destroy_me {
my $self = shift;
# paranoia: guard against cyclic reference
delete $self->{$_} for keys %$self; #删掉$self对象中的每一对键值.
# don't use me after dispatch()
bless $self, 'Sledge::Pages::LivingDead';
}
package Sledge::Pages::LivingDead;
sub AUTOLOAD { }
四、结尾
以上笔者相对完整地向大家展示了Sledge的工作流程.
在这其中还有很多细节处理问题,由于篇幅有限,不能都讲到,有兴趣的读者请自行详细阅读Sledge代码.相信您会收获颇丰.
笔者期待有更多人与Sledge的亲密接触,期待Sledge为更多人了解,为更多人使用.
[ 本帖最后由 gsging 于 2006-9-4 11:56 编辑 ]
gsging 回复于:2006-09-04 11:42:17
辛苦一上午,自己UP一下,呵呵.
gsging 回复于:2006-09-05 08:57:46
谢谢仙子。:D
helbreathszw 回复于:2006-09-10 17:03:53
哦,没有兼顾到新手,哈哈,不过最好能结合一个例子就好多了
=head1 NAME
Sledge::Doc::Install - Sledge フレームワークの導入に必要な作業 (懂日文的可以看看sledge框将的安装说明和helloworld说明,但是我感觉它没有详细说明一些细节。)
=head1 DESCRIPTION
Sledge の初期インストールに必要な作業を解説しています。
=head1 INSTALL SLEDGE
Sledge フレームワークに必要とされる CPAN モジュールをインストールする
スクリプトが cpan_install.pl としておいてありますので、root で実行して
ください。
# ./eg/cpan_install.pl (自动安装很多时候出现缺失某某模块,我后来在redhat9的系统下装成功了,fc4,debian都没装成功,低版本的系统比较适用,因为sledge最后的更新是在2003年。没有用到apache2.)
それ以外に、mod_perl がインストールされていることが必要です。
次に、Sledge モジュールをインストールします。
% perl Makefile.PL
% make
% make test
# make install
=head1 SET UP YOUR PROJECT
プロジェクト用のスケルトンモジュールを作成します。ここではプロジェクト
名を HelloWorld とします。
ウィザードツール sledge-setup を利用してスケルトンを作成します。
(適宜 rehash などしてパスを読み込み直してください。)
% rehash
% sledge-setup HelloWorld
mkdir HelloWorld
mkdir HelloWorld/Config
Initial setup for HelloWorld is now finished.
Remember:
1) Edit HelloWorld/Config/_common.pm
2) Make session table.
Thanks for using Sledge!
- Sledge development team
=head2 EDIT CONFIG FILE
表示されているとおり、_common.pm をまず編集する必要があります。
$C{TMPL_PATH} = '/path/to/tmpl_dir';
テンプレートのパスを指定します。
$C{DATASOURCE} = [ 'dbi:mysql:sledge','root', '' ];
セッション管理に必要なDSN を Anonymous Array で指定します。
$C{COOKIE_NAME} = 'sledge_sid';
$C{COOKIE_PATH} = '/';
セッション Cookie の名前、パスを設定します。いじらなくてもOKです。
$C{COOKIE_DOMAIN} = undef;
Cookie のドメインを指定します。undef の場合、ドメインなしの Cookie が
はかれます。なるべく設定した方が良いでしょう。
また、デフォルトでは /etc/HelloWorld-conf.pl というファイルから Config
ファイルの設定を読み込むようになっています。このファイルを
$ENV{SLEDGE_CONFIG_NAME} = 'staging';
1;
のように記述すると、_common.pm に *加えて*、staging.pm の内容も設定と
して読み込まれます。この /etc/HelloWorld-conf.pl の内容を、開発/プロダ
クション環境で入れかえることによって、設定変数を切り替えることが出来ま
す。
このファイルを使用せず、httpd.conf に
PerlSetEnv SLEDGE_CONFIG_NAME production
のように設定することも可能です。
=head2 SETUP DATABASE
セッション管理にMySQL を採用する場合(デフォルト)は、セッション用のテー
ブルが必要です。Config に指定したデータベース内に、sessions テーブルを
作成してください。eg/sessions.sql にサンプルがあります。
> mysql -uroot
mysql> create database sledge;
mysql> use sledge
mysql> create table sessions (
id char(32) not null primary key,
a_session text,
timestamp timestamp
);
=head1 AUTHOR
Tatsuhiko Miyagawa <[email]miyagawa@edge.co.jp[/email]> with Sledge Developpers.
=cut
-------------------------------------------------------------------------------------------------
下面这个是我找到的不错的例子,我也总算helloworld出来了,不敢独享
Sledgeを使用するには、コンテンツ、テンプレート、モジュールという 3つのディレクトリが必要です。
ここでは、/var/www/www1.i-e-c.co.jp/の下にサブディレクトリを作ることにします。 ディレクトリ名 用途
htdocs コンテンツ、CGIトリガファイル
view テンプレート
Www1 Sledgeモジュール
Apache設定ファイルのhttpd.confもこれに合わせてDocumentRootを變更します。
httpd.confの拔粹
<VirtualHost *>
ServerName www1.i-e-c.co.jp
ServerAdmin [email]webmaster@www1.i-e-c.co.jp[/email]
ErrorLog /var/log/apache/www1.i-e-c.co.jp/error.log
CustomLog /var/log/apache/www1.i-e-c.co.jp/access.log combined
DocumentRoot /var/www/www1.i-e-c.co.jp/htdocs
<Location />
Order deny,allow
Allow from all
Options +ExecCGI
</Location>
<Files ~ "\.cgi$">
SetHandler perl-script
PerlHandler Apache::Registry
PerlSendHeader On
PerlInitHandler Apache::StatINC
<Perl>
use lib "/var/www/www1.i-e-c.co.jp";
</Perl>
</Files>
</VirtualHost>
Apacheをリロードしておきます。また、コンテンツやモジュールは tarokunが自由に設定できるように、/var/www/www1.i-e-c.co.jp以下のファイルのオーナとグループを變更します。
hanachan@server1:/var/www/www1.i-e-c.co.jp$ sudo /etc/init.d/apache reload
hanachan@server1:/var/www/www1.i-e-c.co.jp$ sudo chown -R tarokun:tarokun .
次にセッションのテーブルを作成します。
hanachan@server1:~/Sledge-1.10$ mysql -u root -p www1 < eg/sessions.sql
Enter password: ********
これで、以後はtarokunがコンテンツを設定できます。
では、tarokunがログインしたところから始めます。ディレクトリを作成し、テストに使用したコンテンツを移動します。
tarokun@server1:~$ cd www1.i-e-c.co.jp/
tarokun@server1:/var/www/www1.i-e-c.co.jp$ mkdir htdocs view
tarokun@server1:/var/www/www1.i-e-c.co.jp$ mv index.html test.cgi htdocs/
Sledgeのセットアップを行ないます。
tarokun@server1:/var/www/www1.i-e-c.co.jp$ sledge-setup Www1
mkdir Www1
mkdir Www1/Config
Initial setup for Www1 is now finished.
Remember:
1) Edit Www1/Config/_common.pm
2) Make session table.
Thanks for using Sledge!
- Sledge development team
tarokun@server1:/var/www/www1.i-e-c.co.jp$ chmod 640 Www1/Config/_common.pm
tarokun@server1:/var/www/www1.i-e-c.co.jp$ chgrp www-data Www1/Config/_common.pm
tarokun@server1:/var/www/www1.i-e-c.co.jp$
_common.pmを編集する。
tarokun@server1:/var/www/www1.i-e-c.co.jp$ cat Www1/Config/_common.pm
package Www1::Config::_common;
use strict;
use vars qw(%C);
*Config = \%C;
$C{TMPL_PATH} = '/var/www/www1.i-e-c.co.jp/view';
$C{DATASOURCE} = [ 'dbi:mysql:www1','wwwdata', '3edc4rfv' ];
$C{COOKIE_NAME} = 'sledge_sid';
$C{COOKIE_PATH} = '/';
$C{COOKIE_DOMAIN} = undef;
1;
tarokun@server1:/var/www/www1.i-e-c.co.jp$
_common.pmにはパスワードを書きこみますので、tarokunとapache以外からは讀めないようにしておきます。
tarokun@server1:/var/www/www1.i-e-c.co.jp$ tree
.
|-- Www1
| |-- Config
| | `-- _common.pm
| |-- Config.pm
| `-- Pages.pm
|-- htdocs
| |-- index.html
| `-- test.cgi
`-- view
4 directories, 5 files
HelloWorldを作成アプリケーションを作成するには、アプリケーションクラス、トリガファイル、テンプレートファイルを作成します。
アプリケーションクラスはPagesのサブクラスで作成し實現したいロジックを記述します。
トリガファイルは、Pagesのサブクラスを呼出すためだけのもので、ユーザが呼び出すスクリプトです。中身は、アプリケーションクラスのインスタンスを生成し、dispatchメソッドを呼び出します。
テンプレートファイルはページの見榮えを定義し、アプリケーションロジックと見榮えを分離するためにあります。 Template-Toolkitの形式で記述することができますが、HelloWorldは、靜的なコンテンツなので通常のHTMLファイルです。
アプリケーションクラスHelloWorldは以下のようになります。
package Www1::Pages::HelloWorld;
use strict;
use base qw(Www1::Pages);
sub dispatch_helloworld {
my $self = shift;
}
1;
この例ではページが一つだけですが、複數のページで證移してアプリケーションを構成する場合には、dispatch_XXX というメソッドをページの數だけ記述します。
トリガファイルhelloworld.cgiは以下のようになります。ファイルに實行屬性をつけておいてください。
#!/usr/bin/perl -w
use strict;
use Www1::Pages::HelloWorld;
Www1::Pages::HelloWorld->new->dispatch('helloworld');
トリガファイルはどんなアプリケーションでも同じ形式です。インスタンスを生成して、dispatch('XXX')とします。すると、該當するクラスのdispatch_XXXメソッドが呼び出されます。
テンプレートhelloworld.htmlは以下のようになります。
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head><title>Hello,World!</title></head>
<body>
<h1>Hello,World!</h1>
こんにちわ
</body>
</html>
ブラウザからhelloworld.cgi へアクセスして、正常に表示されることを確認してください。
helbreathszw 回复于:2006-09-10 17:08:08
来一个我从sledge的邮件组收集到的
helbreathszw 回复于:2006-09-10 17:21:52
日本人的东西还是蛮有意思的
我以前接手过一个活力门开发的项目后来要用php重写
就是因为perl的使用者不多,更何况sledge框架开发
另外有一点打算提一下,关于sledge的名字含义,
其实活力门前身叫英极(edge)的发音
所以sledge并不是楼主所说的那个英文意思,是因为sl.edge.jp就是最好的证明,包含公司名字的一个产品名字
嘿嘿!
helbreathszw 回复于:2006-09-10 17:23:47
而且楼主那个第一个关于sledge的介绍入门,我看好象就是一个sledge::document的翻译版
原创成分不多.不过这上面的这个帖不错,赞一个
helbreathszw 回复于:2006-09-10 17:33:38
如果各位要深入的话,看那个sledge的的邮件组很有好处的!
sledge因为结合apache,可以在直接对请求的header就能作出反应
并且直接能够更改各种网络参数传递,比如手机wap开发,可以迅速根据
各种手机的运营商如vodafone,iMODE、EZWEB迅速可以结合不同的运营商的绘文字等
日本的绘文字类似与QQ表情文字.
[ 本帖最后由 helbreathszw 于 2006-9-10 17:45 编辑 ]
helbreathszw 回复于:2006-09-10 17:57:03
希望楼主能结合一个项目来说,或是可否把一些不涉及商业版权问题的日本项目开源出来,如果可能的话
汉化一下给那些日语看不太懂的人也能够享受到sledge之美.
[ 本帖最后由 helbreathszw 于 2006-9-10 18:02 编辑 ]
gsging 回复于:2006-09-11 12:01:52
TO :helbreathszw 你好
我写这两篇文章的目的是初步向大家介绍一下Sledge是什么东西。
其实我本来是想像你说得那样再写第三篇文章来结合一个具体的项目来介绍基于Sledge的实际开发过程。
但是我写到一般有点写不下去了,因为说实话我们实际的开发环境要比我第一篇写的简易安装过程复杂得多。
本人小学语文没有学好,怕描述不清楚所以就不打算写第三篇了,呵呵。
第一篇文章的安装方法与我们项目中的安装方式并不太一样。
项目中有现成的安装脚本,很方便。
我写出来的是我自己通过实验总结出来的。
安装sledge本身的部分我当然需要参考Sledge自己的文档。
有谁学习新知识的时候不参考官方文档呢?
[ 本帖最后由 gsging 于 2006-9-11 12:48 编辑 ]
gsging 回复于:2006-09-11 12:57:16
引用:原帖由 helbreathszw 于 2006-9-10 17:21 发表
日本人的东西还是蛮有意思的
我以前接手过一个活力门开发的项目后来要用php重写
就是因为perl的使用者不多,更何况sledge框架开发
另外有一点打算提一下,关于sledge的名字含义,
其实活力门前身叫英极(edge)的 ...
原来如此,在livedoor工作两年,刚知道sledge的含义,呵呵。
|