29、CGI编程入门与实战:从基础到应用

CGI编程入门与实战:从基础到应用

1. CGI编程基础

在CGI编程中,使用 CGI.pm 模块可以方便地生成HTML页面。传统的调用方式可能会有较多的 print() 函数调用,而更推荐将多个方法组合在一个 print() 函数中,这样代码更具可读性。

例如, cgi2.pl 程序展示了这种方式:

#!/usr/bin/perl
# cgi2.pl

# this program generates HTML with the use
# of CGI.pm using the conventional style

use warnings;
use strict;
use CGI ':standard';

print
    header(),
    start_html('Generating HTML'),
    h1('Now Is:'),
    p('The current date and time is:', scalar(localtime)),
    hr(),
    h1('Our CGI Scripts');

my $file_listing = '';
$file_listing .= "<br />$_" foreach <*.pl>;

print
     p('By the time this chapter is over, you will write all of',
        'these scripts:', $file_listing),
     h1('Go Here For Excellent Books!'),
     p('Check out the',
       a({ href => 'http://www.apress.com/' }, 'Apress Home Page')),
     end_html();

需要注意的是,在使用这种方式时,方法之间要用逗号分隔,若使用分号可能会导致后续方法无法输出。

2. CGI.pm方法详解

CGI.pm 模块的方法可以分为生成多个标签和生成单个标签两类。
- 生成多个标签的方法 :如 start_html() ,它会生成 <html><head><title>...</title></head><body> 等标签。可以使用命名参数来设置页面的属性,例如:

start_html(
    -title   => 'Generating HTML',
    -bgcolor => '#cccccc',
    -text    => '#520063'
),
  • 生成单个标签的方法 :像 h1() p() hr() 等,它们分别生成 <h1>...</h1> <p>...</p> <hr /> 标签。还可以通过传递匿名哈希来为标签提供属性,示例如下:
h1({ align => 'center'}, 'Here Is An Example Web Page'),
hr({ size => 10, noshade => 1 }),
a({ href => 'http://www.apress.com/' }, 'Apress Home Page'),
3. 处理表单数据

CGI脚本不仅能生成HTML,还能进行后端处理,其中一个重要的应用就是处理表单数据。表单由 <form> </form> 标签包围, <form> 标签的 action 属性指定了用户提交表单时要调用的CGI脚本。

以下是一个简单的表单示例 form.html

<html>
  <head>
    <title>A Simple Form</title>
  </head>
  <body>
    <h1>Please Enter Your Name</h1>
    <form action="http://localhost/cgi-bin/form.pl">
      First name: <input type="text" name="firstname">
      <br>
      Last name: <input type="text" name="lastname">
      <br>
      <input type="submit">
    </form>
  </body>
</html>

对应的CGI程序 form.pl 使用 param() 方法来获取表单数据:

#!/usr/bin/perl
# form.pl

use warnings;
use strict;
use CGI ':standard';

my @params    = param();
my $firstname = param('firstname') || 'you have no first name!';
my $lastname  = param('lastname')  || 'you have no last name!';

print
    header(),
    start_html(
        -title   => 'Welcome!',
        -text    => '#520063'
    ),
    h1("Hello, $firstname $lastname!"),
    end_html();

param() 方法有两种调用方式:
- 无参数调用时,返回表单中所有参数的列表。
- 带一个参数调用时,返回该参数的值。

4. 动态CGI程序

静态CGI程序将HTML和CGI程序分开存储,有时管理起来较为困难。动态CGI程序则可以根据是否有表单数据来决定是处理数据还是构建表单。

动态CGI程序的一般形式如下:

if (param()) {
    # the program was invoked with parameters,
    # process the posted data
} else {
    # the program was invoked with no parameters,
    # build the form
}

以下是一个动态CGI程序 dynamic.pl 的示例:

#!/usr/bin/perl
# dynamic.pl

use warnings;
use strict;
use CGI ':standard';

if (param()) {
    # we have parameters, so process the form data

    my @params    = param();
    my $firstname = param('firstname') || 'you have no first name!';
    my $lastname  = param('lastname')  || 'you have no last name!';

    print
        header(),
        start_html(
            -title   => 'Welcome!',
            -text    => '#520063'
        ),
        h1("Hello, $firstname $lastname!"),
        end_html();

} else {
    # no parameters, so build the form

    print
        header(),
        start_html('A Simple Form'),
        h1('Please Enter Your Name'),
        start_form(),
        'First name: ',
        textfield(-name => 'firstname'),
        br(),
        'Last name: ',
        textfield(-name => 'lastname'),
        br(),
        submit(),
        end_form(),
        end_html();
}
5. 实战:Web象棋程序

接下来,我们将前面讨论的内容整合到一个Web象棋程序 webchess.pl 中。由于CGI脚本是无状态的,我们需要将棋盘状态存储在 webchess.dat 文件中。

以下是棋盘的初始状态:

WR:WN:WB:WQ:WK:WB:WN:WR
WP:WP:WP:WP:WP:WP:WP:WP
:::::::
:::::::
:::::::
:::::::
BP:BP:BP:BP:BP:BP:BP:BP
BR:BN:BB:BQ:BK:BB:BN:BR

webchess.pl 程序的主要流程如下:
1. 读取 webchess.dat 文件中的棋盘状态。
2. 获取用户提交的起始和结束坐标。
3. 根据坐标移动棋子,并更新棋盘状态。
4. 将更新后的棋盘状态写回 webchess.dat 文件。
5. 输出HTML页面,显示棋盘和表单。

以下是 webchess.pl 的完整代码:

#!/usr/bin/perl
# webchess.pl

use warnings;
use strict;
use CGI ':standard';

my @chessboard = read_in_chessboard();

# grab the posted data, if any:
my $start = param('start') || '';
my $end   = param('end')   || '';

my $startx = '';
my $starty = '';
my $endx   = '';
my $endy   = '';

# time to make our move!
if ($start and $end) {
    if ($start =~ /^\s*([1-8]),([1-8])/) {
        $startx = $1 - 1;
        $starty = $2 - 1;
    }
    if ($end =~ /^\s*([1-8]),([1-8])/) {
        $endx = $1 - 1;
        $endy = $2 - 1;
    }
    if ($startx ne '' and $starty ne '' and
        $endx ne ''   and $endy ne '' ) {
        # put starting square on ending square
        $chessboard[$endy][$endx] = $chessboard[$starty][$startx];
        # remove from old square
        undef $chessboard[$starty][$startx];

        # we have changed the chessboard, so write
        # back out
        write_out_chessboard(@chessboard);
    }
}

# time to print to the browser
print
    header(),
    start_html('Web Chess'),
    h1('Web Chess');

# start the table that will contain the board
print '<table>';

# loop, printing each piece
foreach my $i (reverse (0..7)) { # row
    print '<tr>';
    foreach my $j (0..7) {       # column
        print '<td>';
        if (defined $chessboard[$i][$j]) {
            print $chessboard[$i][$j];
        } elsif ( ($i % 2) == ($j % 2) ) {
            print "..";
        }
        print '</td>';
    }
    print "</tr>";     # end of row
}

# we are done with our table
print '</table>';

# print a form for the next move
# and end the html
print
    hr(),
    start_form(),
    'Starting square [x,y]:',
    textfield(-name => 'start'),
    br(),
    'Ending square [x,y]:',
    textfield(-name => 'end'),
    br(),
    submit(),
    end_form(),
    end_html();

### function definitions ###

sub read_in_chessboard {
    # this function opens webchess.dat and builds
    # the chessboard
    # an example line from webchess.dat is:
    # BR:BN:BB:BQ:BK:BB:BN:BR

    # this is our local copy of the chessboard,
    # we'll return this later
    my @cb;
    open FH, '<', 'webchess.dat';

    foreach my $i (0..7) {
        my $line = <FH>;
        # split the line on a : or any whitespace
        # which will take care of the \n at the
        # end of the line
        my @linearray = split /[:\s]/, $line;
        # $#linearray should be 7!
        foreach my $j (0..$#linearray) {
            # if the text between the colons is
            # not the empty string, we have a piece,
            # so assign it to our chessboard
            if ($linearray[$j]) {
                $cb[$i][$j] = $linearray[$j];
            }
        }
    }
    close FH;

    # time to return back the chessboard
    return @cb;
}

sub write_out_chessboard {
    # the chessboard is passed in as our
    # argument
    my @cb = @_;

    # write the chessboard to webchess.dat
    # so that each piece on a row is colon separated
    open FH, '>', 'webchess.dat';
    foreach my $i (0..7) {
        foreach my $j (0..7) {
            if (defined $chessboard[$i][$j]) {
                print FH $chessboard[$i][$j];
            }
            if ($j < 7) {
                print FH ':';
            }
        }
        print FH "\n";
    }
}
总结

通过以上内容,我们了解了CGI编程的基础知识,包括 CGI.pm 模块的使用、表单数据处理、动态CGI程序的实现,以及如何将这些知识应用到实际项目中。希望这些内容能帮助你更好地掌握CGI编程。

操作步骤总结
操作 步骤
使用 CGI.pm 生成HTML 1. 引入 CGI.pm 模块。
2. 使用相关方法生成HTML标签。
3. 可以将多个方法组合在一个 print() 函数中。
处理表单数据 1. 创建包含表单的HTML文件。
2. 在CGI程序中使用 param() 方法获取表单数据。
实现动态CGI程序 1. 根据 param() 方法的返回值判断是否有表单数据。
2. 有数据则处理数据,无数据则构建表单。
运行Web象棋程序 1. 准备 webchess.dat 文件,存储棋盘初始状态。
2. 运行 webchess.pl 程序。
3. 在浏览器中访问程序,输入起始和结束坐标进行下棋。
流程图
graph TD;
    A[开始] --> B[读取棋盘状态];
    B --> C[获取用户输入];
    C --> D{是否有输入};
    D -- 是 --> E[处理输入,更新棋盘];
    D -- 否 --> F[直接显示棋盘];
    E --> G[更新棋盘文件];
    G --> F;
    F --> H[输出HTML页面];
    H --> I[结束];

CGI编程入门与实战:从基础到应用

6. 代码详细解析
6.1 read_in_chessboard 函数
sub read_in_chessboard {
    # this function opens webchess.dat and builds
    # the chessboard
    # an example line from webchess.dat is:
    # BR:BN:BB:BQ:BK:BB:BN:BR

    # this is our local copy of the chessboard,
    # we'll return this later
    my @cb;
    open FH, '<', 'webchess.dat';

    foreach my $i (0..7) {
        my $line = <FH>;
        # split the line on a : or any whitespace
        # which will take care of the \n at the
        # end of the line
        my @linearray = split /[:\s]/, $line;
        # $#linearray should be 7!
        foreach my $j (0..$#linearray) {
            # if the text between the colons is
            # not the empty string, we have a piece,
            # so assign it to our chessboard
            if ($linearray[$j]) {
                $cb[$i][$j] = $linearray[$j];
            }
        }
    }
    close FH;

    # time to return back the chessboard
    return @cb;
}

此函数的主要作用是从 webchess.dat 文件中读取棋盘状态。具体步骤如下:
1. 创建一个本地数组 @cb ,用于存储棋盘状态。
2. 以只读模式打开 webchess.dat 文件。
3. 循环读取文件的每一行,将每行按冒号或空白字符分割成数组 @linearray
4. 遍历分割后的数组,如果元素不为空,则将其赋值给 @cb 数组对应的位置。
5. 关闭文件并返回 @cb 数组。

6.2 write_out_chessboard 函数
sub write_out_chessboard {
    # the chessboard is passed in as our
    # argument
    my @cb = @_;

    # write the chessboard to webchess.dat
    # so that each piece on a row is colon separated
    open FH, '>', 'webchess.dat';
    foreach my $i (0..7) {
        foreach my $j (0..7) {
            if (defined $chessboard[$i][$j]) {
                print FH $chessboard[$i][$j];
            }
            if ($j < 7) {
                print FH ':';
            }
        }
        print FH "\n";
    }
}

该函数的功能是将棋盘状态写回到 webchess.dat 文件中。操作步骤如下:
1. 接收传入的棋盘数组 @cb
2. 以写入模式打开 webchess.dat 文件。
3. 双重循环遍历棋盘的每一个位置,如果该位置有棋子,则将棋子信息写入文件。
4. 除每行的最后一个位置外,每个位置后都写入一个冒号。
5. 每行结束后写入换行符。

6.3 主程序逻辑
my @chessboard = read_in_chessboard();
# grab the posted data, if any:
my $start = param('start') || '';
my $end   = param('end')   || '';

my $startx = '';
my $starty = '';
my $endx   = '';
my $endy   = '';

# time to make our move!
if ($start and $end) {
    if ($start =~ /^\s*([1-8]),([1-8])/) {
        $startx = $1 - 1;
        $starty = $2 - 1;
    }
    if ($end =~ /^\s*([1-8]),([1-8])/) {
        $endx = $1 - 1;
        $endy = $2 - 1;
    }
    if ($startx ne '' and $starty ne '' and
        $endx ne ''   and $endy ne '' ) {
        # put starting square on ending square
        $chessboard[$endy][$endx] = $chessboard[$starty][$startx];
        # remove from old square
        undef $chessboard[$starty][$startx];

        # we have changed the chessboard, so write
        # back out
        write_out_chessboard(@chessboard);
    }
}

# time to print to the browser
print
    header(),
    start_html('Web Chess'),
    h1('Web Chess');

# start the table that will contain the board
print '<table>';

# loop, printing each piece
foreach my $i (reverse (0..7)) { # row
    print '<tr>';
    foreach my $j (0..7) {       # column
        print '<td>';
        if (defined $chessboard[$i][$j]) {
            print $chessboard[$i][$j];
        } elsif ( ($i % 2) == ($j % 2) ) {
            print "..";
        }
        print '</td>';
    }
    print "</tr>";     # end of row
}

# we are done with our table
print '</table>';

# print a form for the next move
# and end the html
print
    hr(),
    start_form(),
    'Starting square [x,y]:',
    textfield(-name => 'start'),
    br(),
    'Ending square [x,y]:',
    textfield(-name => 'end'),
    br(),
    submit(),
    end_form(),
    end_html();

主程序的执行流程如下:
1. 调用 read_in_chessboard 函数读取棋盘状态。
2. 使用 param 方法获取用户提交的起始和结束坐标。
3. 判断是否有有效的坐标输入,如果有则进行坐标解析和棋盘更新操作。
4. 调用 write_out_chessboard 函数将更新后的棋盘状态写回文件。
5. 输出HTML页面,包括棋盘表格和用于输入下一步棋的表单。

7. 常见问题及解决方法
问题 原因 解决方法
部分页面内容未显示 方法调用时使用了分号而非逗号 检查代码,将分号替换为逗号
表单数据无法获取 param 方法使用错误 确保 param 方法正确调用,检查表单中 name 属性的值
棋盘状态未更新 文件读写操作失败 检查文件权限,确保 webchess.dat 文件可读写
8. 扩展与优化建议
  • 增加错误处理 :在文件读写操作和坐标解析时增加更多的错误处理代码,提高程序的健壮性。例如,在打开文件失败时给出相应的错误提示。
open FH, '<', 'webchess.dat' or die "无法打开文件: $!";
  • 优化界面显示 :可以使用CSS样式来美化棋盘和表单的显示效果,提高用户体验。
  • 实现更多功能 :如悔棋、保存游戏记录等功能,增强程序的实用性。
9. 总结回顾

本文全面介绍了CGI编程的相关知识,从基础的 CGI.pm 模块使用,到表单数据处理、动态CGI程序实现,最后通过Web象棋程序的实例将这些知识进行了整合应用。通过详细的代码解析、操作步骤说明和流程图展示,帮助读者更好地理解和掌握CGI编程的核心要点。

希望读者在学习过程中能够深入理解每个知识点,并通过实践不断巩固和拓展自己的技能。在实际应用中,根据具体需求对程序进行灵活调整和优化,以实现更强大的功能。

流程图
graph LR;
    A[读取棋盘状态] --> B{是否有用户输入};
    B -- 是 --> C[解析输入坐标];
    B -- 否 --> D[显示当前棋盘];
    C --> E[更新棋盘状态];
    E --> F[写入棋盘文件];
    F --> D;
    D --> G[输出HTML页面];
    G --> H[等待下一次输入];

这个流程图展示了Web象棋程序的整体运行流程,从读取棋盘状态开始,根据用户是否有输入进行不同的处理,最终输出HTML页面供用户交互。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值