Cookie学习笔记(二)

(4) 从URL中删除任何结尾斜杠。

$url = rtrim($url, '/\\');

由于子文件夹可能添加额 外的斜杠(/)或反斜杠(\,对于Windows),所以函数需要删除它们。为了执行该任务,可使用rtrim()函数。默认情况下,这个函数会删除字符 串右边的空格。如果提供了要删除的字符列表作为第二个参数,就会代之以删除这些字符。这里要删除的字符应该是/或\。但是,由于PHP中的反斜杠是转义 符,需要使用\\来指示单个反斜杠。因此,简而言之,如果$url以这些字符中的任何一个结尾,rtrim()函数将删除它们。

(5) 添加特定的页面到URL中并完成函数。

$url .= '/' . $page;

  return $url;

} // End of absolute_url() function.

最后,将特定的页面名称追加到$url中。在它前面放置一个斜杠,因为任何尾随的斜杠都会在第(4)步中被删除,并且不能把www.example.compage.php作为URL,然后返回该URL。

这些看起来似乎都相当复杂,但它可以非常有效地确保重定向正确工作,而不管在什么服务器上、来自于什么目录,或者脚本是否在运行(只要重定向发生在那个目录内即可)。

(6) 开始创建新函数。

function check_login($dbc, $email = '', $pass = '') {

这个函数将验证登录信息。它带有三个参数:数据库连接(它是必需的)、电子邮件地址(它是可选的)和密码(它也是可选的)。

尽管这个函数可以直接访问$_POST['email']和$_POST['pass'],但是更好的做法是给函数传递这些值,从而使得函数更独立。

(7) 验证电子邮件地址和密码。

$errors = array();

if (empty($email)) {

  $errors[] = 'You forgot to enter your email address.';

} else {

  $e = mysqli_real_escape_string($dbc, trim($email));

}

if (empty($pass)) {

  $errors[] = 'You forgot to enter your password.';

} else {

  $p = mysqli_real_escape_string($dbc, trim($pass));

}

这个验证例程类似于注册页面中使用的那个验证例程。如果发生任何问题,都会把它们添加到$errors数组中,最终将把它们用在登录页面上(参见图11-2)。

(8) 如果没有发生错误,就运行数据库查询。

if (empty($errors)) {

  $q = "SELECT user_id, first_name FROM users WHERE email='$e' AND pass=SHA1('$p')";

    $r = @mysqli_query ($dbc, $q);

这个查询从数据库中选择user_id和first_name值,其中提交的电子邮件地址(来自于表单)匹配存储的电子邮件地址,并且提交的密码的SHA1()版本匹配存储的密码(参见图11-3)。

 

图11-3  如果用户提交正确的电子邮件地址/密码组合,登录查询就会得到这个结果

(9) 检查查询的结果。

if (mysqli_num_rows($r) == 1) {

  $row = mysqli_fetch_array ($r, MYSQLI_ASSOC);

  return array(true, $row);

} else {

  $errors[] = 'The email address and password entered do not match those on file.';

}

如果查询返回一行,那么登录信息是正确的。然后将把结果获取进$row中。成功登录中的最后一步是把两种信息返回到请求的脚本中:值true和从MySQL获取的数据,其中前者指示登录成功。使用array()函数,可以返回布尔值和$row数组。

如果查询没有返回一行,就会把一条出错消息添加到数组中。它最终将显示在登录页面上(参见图11-4)。

 

图11-4  如果用户输入了电子邮件地址和密码,但是它们与数据库中存储的值不匹配,就会得到这个结果

(10) 完成开始于第(8)步中的条件语句,并完成函数。

    } // End of empty($errors) IF.

  return array(false, $errors);

} // End of check_login() function.

该 函数的最后一步是返回值false指示登录失败,以及返回$errors数组,它存储失败的原因。可以把这个return语句放在这里(函数的末尾,而不 是条件语句内)因为如果登录失败,函数将只会执行到这里。如果登录成功,第(9)步中的return行将阻止函数继续执行(一旦函数执行return语 句,它就会停止执行)。

(11) 完成页面。

?>

(12) 将文件另存为login_functions.inc.php,并将其存放在Web目录中(该目录与header.html、footer.html和style.css这几个文件都在includes文件夹中)。

该页面也会使用.inc.php扩展名,指示它是一个可包含的文件并且它包含PHP代码。

ü提示

l    本章中的脚本不包含任何调试代码(如MySQL错误或查询)。如果你有关于这些脚本的任何问题,可以应用第7章中概括的调试技术。

l    可以在header()调用中把name=value对添加到URL中,以把值传递到目标页面:

   $url .= '?name=' . urlencode(value);

 

11.3  使用cookie

cookie是服务器在用户的机器上存储信息的一种方式。利用这种方式,站点可以在访问期间记住或跟踪用户。可以把cookie看作像名字标签一样,你告诉服务器你的名字,它就会给你戴上一个标签。这样,它就可以回过头来查阅名字标签,从而知道你是谁。

有 些人对cookie心存疑虑,因为他们认为cookie允许服务器知道了他们太多的事情。不过,cookie只能用于存储提供给服务器的信息,因此,与大 多数其他在线技术相比,cookie的安全性并不差。不幸的是,许多人仍然误解了这种技术,由于这些误解可能会破坏Web应用程序的功能,所以这就成了一 个问题。

在本节中,你将学习如何设置一个cookie,从存储的cookie中检索信息,改变cookie的设置以及删除一个cookie。

测试cookie

要使用cookie有效地编程,需要能够准确地测试它们是否存在。执行该任务的最佳方式是:当接收到一个cookie时,让Web浏览器询问应该做什么。在这种情况下,每当PHP试图发送一个cookie时,浏览器都会用cookie信息提示你。

不同平台上不同浏览器的不同版本都在不同的位置定义了它们的cookie处理策略。我将快速介绍两个针对流行的Web浏览器的选项。

要 使用Windows XP上的Internet Explorer设置它,可选择“工具”→“Internet选项”。然后单击“隐私”选项卡,接着单击“设置”下面的“高级”按钮。单击选中“覆盖自动 cookie处理”复选框,然后为“第一方Cookie”和“第三方Cookie”选择“提示”。

如果在Windows上使用 Firefox,可选择“工具”→“选项”→“隐私”。在cookie区域中,在“保存到”下拉菜单中选择“每次询问我”。如果在Mac OS X上使用Firefox,其步骤是相同的,但是必须通过选择Firefox→“参数设置”来开始设置步骤。

不幸的是,Mac OS X上的Safari没有cookie提示选项,但是,它允许查看现有的cookie,这仍然是一种有用的调试工具。可以在“Safari参数设置”面板下面找到这个选项。

11.3.1  设置cookie

关 于cookie要理解的最重要的事情是,必须在任何其他信息之前把它们从服务器发送给客户。万一服务器试图在Web浏览器已经接收到HTML(甚至是无关 紧要的空白)之后发送cookie,就会导致一条出错消息,并且不会发送cookie(参见图11-5)。迄今为止,这是最常见的与cookie相关的错 误,但是很容易修复它。

 

图11-5  在创建cookie时,headers already sent…出错消息实在太常见。要注意出错消息说了些什么,以便找出并修复问题

通过setcookie()函数发送cookie:

setcookie(name, value);

setcookie ('name', 'Nicole');

第二行代码在把cookie发送到浏览器时,带有名字name和值Nicole(参见图11-6)。

 

图11-6  如果把浏览器设置成在接收cookie时请求许可,则当站点试图发送一个cookie时,将看到一条如本图所示的消息(这是Firefox版本的提示)

通过接着使用setcookie()函数,可以继续把更多的cookie发送给浏览器:

setcookie('ID', 263);

setcookie('email', 'email@example.com');

与在PHP中使用任何变量时一样,在给cookie命名时,不要使用空格或标点符号,但是,要特别注意使用正确的大小写字母。

发送cookie

(1) 在文本编辑器中创建一个新的PHP文档(参见脚本11-3)。

脚本11-3  login.php脚本会在成功登录时创建两个cookie

1 <?php # Script 11.3 - login.php

2

3 // This page processes the login form submission.

4 // Upon successful login, the user is redirected.

5 // Two included files are necessary.

6 // Send NOTHING to the Web browser prior to the setcookie() lines!

7

8 // Check if the form has been submitted:

9 if (isset($_POST['submitted'])) {

10 

11    // For processing the login:

12    require_once ('includes/login_functions.inc.php');

13 

14    // Need the database connection:

15    require_once ('../mysqli_connect.php');

16 

17    // Check the login:

18    list ($check, $data) = check_login($dbc, $_POST['email'], $_POST['pass']);

19 

20    if ($check) { // OK!

21 

22      // Set the cookies:

23      setcookie ('user_id', $data['user_id']);

24      setcookie ('first_name', $data ['first_name']);

25 

26      // Redirect:

27      $url = absolute_url ('loggedin.php');

28      header("Location: $url");

29      exit(); // Quit the script.

30 

31    } else { // Unsuccessful!

32 

33      // Assign $data to $errors for error reporting

34      // in the login_page.inc.php file.

35      $errors = $data;

36 

37    }

38 

39    mysqli_close($dbc); // Close the database connection.

40 

41  } // End of the main submit conditional.

42 

43  // Create the page:

44  include ('includes/login_page.inc.php');

45  ?>

对于这个示例,我将建立一个新的login.php脚本(它将与第8章中的脚本协同工作)。这个脚本还需要在本章开头创建的两个文件。

(2) 验证表单。

if (isset($_POST['submitted'])) {

  require_once ('includes/login_functions.inc.php');

  require_once ('../mysqli_connect.php');

    list ($check, $data) = check_login($dbc, $_POST['email'], $_POST['pass']);

这个脚本将做两件事:处理表单提交和显示表单。这个条件语句检查提交。

在这个条件语句内,脚本必须包含login_functions.inc.php和mysqli_connect.php(它是在第8章中创建的,并且仍然应该位于相对于这个脚本的相同位置)。

在包含这两个文件后,可以调用check_login()函数,将数据库连接(它来自于mysqli_connect.php)以及电子邮件地址和密码(它们都来自于表单)传递给它。

该函数返回两个元素的数组:布尔值和另一个数组(其中包含用户数据或错误)。为了把这些返回的值赋予变量,可使用list()函数。该函数返回的第一个值(布尔值)将被赋予$check,返回的第二个值($row或$errors数组)将被赋予$data。

(3) 如果用户输入了正确的信息,则登录。

if ($check) {

  setcookie ('user_id', $data['user_id']);

    setcookie ('first_name', $data['first_name']);

$check变量指示登录尝试是否成功。如果它为true,那么$data将包含用户的ID和名字。可以在cookie中使用这两个值。

(4) 把用户重定向到另一个页面。

$url = absolute_url ('loggedin.php');

header("Location: $url");

exit();

使用本章中概括的步骤,首先动态生成重定向URL,并通过absolute_url()函数返回它。要重定向到的特定页面是loggedin.php。然后在header()函数中使用绝对URL,并用exit()终止脚本的执行。

(5) 完成$check条件语句(开始于第(3)步),然后关闭数据库连接。

} else {

  $errors = $data;

}

mysqli_close($dbc);

如果$check的值为false,那么$data变量就会存储在check_login()函数内生成的错误。如果是这样,就应该把它们赋予$errors变量,因为这是脚本中显示登录页面(login_page.inc.php)的代码所期待的。

(6) 完成主提交条件语句,并包含登录页面。

}

include

('includes/login_page.inc.php');

?>

这个login.php脚本主要验证登录表单,这是通过调用check_login()函数完成的。login_page. inc.php文件包含登录页面本身,因此只需要包含它即可。

(7) 将文件另存为login.php,存放在Web目录中(在与第8章中的文件相同的目录中),并在Web浏览器中加载这个页面(参见图11-2)。

ü提示

l    cookie被限制为总共包含大约4 KB的数据,每个Web浏览器可以记住来自任何一个站点的有限数量的cookie。对于目前的大多数Web浏览器,这个限制是50个cookie(但是, 如果发出50个不同的cookie,你可能想重新考虑如何执行该任务)。

l    因为不同的浏览器将以不同的方式处理cookie,PHP中有几个函数可以在不同的浏览器中生成不同的结果,setcookie()函数是其中之一。一定要在不同平台上的多个浏览器中测试你的Web站点,以确保一致性。

l    如果前两个包含文件向Web浏览器发送任何内容,或者甚至在关闭PHP标签后面具有空白行或空格,你将看到headers already sent错误。如果看到这种错误,可以查看错误中提到的文档和行号(在output started at之后),并修复问题。

11.3.2  访问cookie

要从cookie中检索一个值,只需要把合适的cookie名称用作键来引用$_COOKIE超全局数组(就像利用任何数组一样)。例如,为了从用如下一行代码建立的cookie中检索一个值:

setcookie('username', 'Trout');

将引用$_COOKIE['username']。

在下面的示例中,将以两种方式来访问login.php脚本设置的cookie。首先,将检查用户是否已登录(如果未登录,他们不应该访问这个页面)。接下来,将通过用户的名字来问候他们,他们的名字存储在一个cookie中。

访问cookie

(1) 在文本编辑器中创建一个新的PHP文档(参见脚本11-4)。

脚本11-4  loggedin.php脚本基于一个存储的cookie向用户打印一个问候

1 <?php # Script 11.4 - loggedin.php

2

3 // The user is redirected here from login.php.

4

5 // If no cookie is present, redirect the user:

6 if (!isset($_COOKIE['user_id'])) {

7

8   // Need the functions to create an absolute URL:

9   require_once ('includes/login_functions.inc.php');

10    $url = absolute_url();

11    header("Location: $url");

12    exit(); // Quit the script.

13 

14  }

15 

16  // Set the page title and include the HTML header:

17  $page_title = 'Logged In!';

18  include ('includes/header.html');

19 

20  // Print a customized message:

21  echo "<h1>Logged In!</h1>

22  <p>You are now logged in, {$_COOKIE ['first_name']}!</p>

23  <p><a href=\"logout.php\">Logout</a></p>";

24 

25  include ('includes/footer.html');

26  ?>

在用户成功登录后,将把他们重定向到这个页面。它将打印一个特定于用户的问候。

(2) 检查cookie是否存在。

if(!isset($_COOKIE['user_id'])){

因为不希望用户访问这个页面(除非那个用户已经登录),所以应该首先检查是否已经设置了cookie(在login.php中)。

(3) 如果用户未登录,就重定向他们。

    require_once ('includes/login_functions.inc.php');

  $url = absolute_url();

  header("Location: $url");

  exit();

}

如果用户没有登录,就会自动把他们重定向到这个主页面。这是一种限制访问内容的简单方式。

(4) 包含页面的标题。

$page_title = 'Logged In!';

include('includes/header.html');

(5) 使用cookie欢迎用户。

echo "<h1>Logged In!</h1>

<p>You are now logged in, {$_COOKIE ['first_name']}!</p>

<p><a href=\"logout.php\"> Logout</a></p>";

为了用名字问候用户,引用了$_COOKIE['first_name']变量(括在花括号内以避免解析错误)。还会打印一个指向注销页面(将在本章后面编写)的链接。

(6) 完成HTML页面。

include('includes/footer.html');

?>

(7) 将文件另存为loggedin.php,存放在Web目录中(在与login.php相同的文件夹中),并通过login.php登录在Web浏览器中测试它(参见图11-7)。

 

图11-7  如果使用正确的电子邮件地址和密码,则在登录后,会重定向到这里

因为这些示例使用与第8章中相同的数据库,你应该能够使用当时提交的注册用户名和密码来登录。

(8) 要查看设置的cookie(参见图11-8和图11-9),可以针对你的浏览器更改cookie设置并再次测试它。

   

图11-8  user_id cookie的值为1

图11-9  first_name cookie的值为Larry(你的可能有所不同)

ü提示

l    直到重新加载了设置页面(例如,login.php)或者访问了另一个页面(换句话说,你不能设置和访问同一个页面中的cookie),才可以访问cookie。

l    如果用户拒绝一个cookie,或者把他们的Web浏览器设置成不接受它们,则会把用户自动重定向到这个示例中的主页,即使他们成功登录也是如此。由于这个原因,你可能想让用户知道何时需要cookie。

11.3.3  设置cookie参数

尽管只把名称和值这两个参数传递给setcookie()函数就足够了,还应该知道其他可用的参数。该函数还可以带有最多另外5个参数,其中每一个都会改变cookie的定义。

setcookie (name, value, expiration, path, host, secure, httponly);

到期时间 (expiration)参数用于设置cookie存在的精确限定的时间长度,用从新纪元(epoch)(新纪元是1970年1月1日0点)起所经过的秒 数表示。如果没有设置它或者把它的值设置为0,cookie将持续起作用,直到用户关闭了他或她的浏览器。这些cookie被认为在浏览器会话期间一直存 在(图11-8和图11-9也指示了这一点)。

为了设置特定的到期时间,可在当前时刻上增加特定的分钟数或小时数,可以使用time()函数获取当前时刻。下面一行代码将把cookie的到期时间设置为当前时间后30分钟(60秒乘以30分钟):

setcookie(name, value, time()+1800);

路 径(path)和主机(host)这两个参数用于把cookie限制到Web站点内(路径)特定的文件夹,或者限制到特定的主机 (www.example.com或192.168.0.1)。例如,你可以把cookie限制为,仅当用户位于域中的admin文件夹(以及admin 文件夹的子文件夹)内时才存在。

setcookie(name, value, expire, '/admin/');

将路径设置为/将使得cookie在整个域(Web站点)内可见。将域设置为.example.com将使得cookie在整个域和每个子域(www.example.com、admin.example.com、pages.example.com等)内都可见。

安全(secure)值规定只应该通过安全的HTTPS连接来发送cookie。1指示必须使用安全的连接,0则指示标准连接也不错。

setcookie (name, value, expire, path, host, 1);

如果站点使用安全连接,则限制通过安全的HTTPS连接发送cookie比不这样做要安全得多。

最后,PHP 5.2中添加了一个httponly参数。使用一个布尔值指示只能通过HTTP(和HTTPS)访问cookie。强制执行这种限制将使得cookie更安全(阻止一些黑客攻击),但是在编写本书时并非所有的浏览器都支持它。

setcookie (name, value, expire, path, host, secure, TRUE);

与带有参数的所有函数一样,必须按顺序传递setcookie()值。要跳过任何参数,可使用NULL、0或空字符串(不要使用FALSE)。到期时间和安全这两个值都是整数,因此不能用引号括住它们。

为了演示这个信息,我将添加一个到期时间设置到登录cookie中,使得它们只持续存在1个小时。

设置cookie的参数

(1) 在文本编辑器中打开login.php(参见脚本11-3)。

(2) 更改两个setcookie()行,使之包括一个持续60分钟的到期日期(参见脚本11-5)。

setcookie ('user_id', $data['user_id'], time()+3600, '/', '', 0, 0);

setcookie ('first_name', $data ['first_name'], time()+3600, '/', '', 0, 0);

脚本11-5  login.php脚本现在使用了setcookie()函数可以带的每个参数

1 <?php # Script 11.5 - login.php #2

2

3 if (isset($_POST['submitted'])) {

4

5   require_once ('includes/login_functions.inc.php');

6   require_once ('../mysqli_connect.php');

7   list ($check, $data) = check_login($dbc, $_POST['email'], $_POST['pass']);

8

9   if ($check) { // OK!

10 

11      // Set the cookies:

12      setcookie ('user_id', $data['user_id'], time()+3600, '/', '', 0, 0);

13      setcookie ('first_name', $data['first_name'], time()+3600, '/', '', 0, 0);

14 

15      // Redirect:

16      $url = absolute_url ('loggedin.php');

17      header("Location: $url");

18      exit();

19 

20    } else { // Unsuccessful!

21      $errors = $data;

22    }

23 

24    mysqli_close($dbc);

25 

26  } // End of the main submit conditional.

27 

28  include ('includes/login_page.inc.php');

29  ?>

通过把到期日期设置成time() + 3600(60分钟乘以60秒),在设置cookie后,它将持续存在1个小时。在执行这种改变时,显式指定其他cookie参数。

对于最后一个参数,它接受一个布尔值,也可以使用0表示false(PHP将为你处理这种转换)。这样做是一个好主意,因为在任何cookie参数中都使用false可能引发问题。

(3) 保存这个脚本,存放在Web目录中,并通过登录在Web浏览器中测试它(参见图11-10)。

 

图11-10  对setcookie()参数(比如到期日期和时间)所做的更改将会反映在发送给Web浏览器的cookie中(对比图11-9)

ü提示

l    有些浏览器难以处理没有列出每个参数的cookie。显式指出每个参数,甚至是一个空字符串,对于所有浏览器将得到更可靠的结果。

l    对于cookie的到期时间有一些一般性的指导原则:如果cookie应该持续存在与会话一样长的时间,就不要设置到期时间;如果cookie在用户关闭 并重新打开他或她的浏览器之后应该继续存在,就要把到期时间向前设置几周或几个月的时间;如果cookie可能构成安全风险,就要把到期时间设置成自此开 始1个小时或不到1个小时的时间,以使得在用户离开他或她的浏览器之后,cookie不会继续存在太长的时间。

l    出于安全性考虑,可以在cookie上设置一个5分钟或10分钟的到期时间,并与用户访问的每个新页面一起重发cookie(假定cookie存在)。这 样,cookie持续存在的时间就与用户活动的时间一样长,但它会在用户最后一个动作的5分钟或10分钟后自动死亡。

l    电子商务以及其他与隐私相关的Web应用程序都应该为所有事务(包括cookie)使用一条SSL(Secure Sockets Layer,安全套接字层)连接。

l    要小心对待由目录内的脚本创建的cookie。如果没有指定路径,那么该cookie将只能由同一个目录内的其他脚本使用。

11.3.4  删除cookie

关于使用cookie要理解的最后一件事是如何删除一个cookie。虽然在关闭用户的浏览器时或者在到达到期日期/时间时cookie会自动到期,但是,有时你希望手动删除cookie。例如,在具有登录能力的Web站点内,可能希望在用户注销时删除任何cookie。

尽 管setcookie()函数可以带最多7个参数,但是实际上只有一个参数(cookie名称)是必需的。如果发送一个包含名称但没有值的cookie, 其效果就相当于删除现有的同名cookie。例如,要创建first_namecookie,可以使用下面这一行代码:

setcookie('first_name', 'Tyler');

要删除first_namecookie,可以编写如下代码:

setcookie('first_name');

一种附加的预防措施是,还可以把到期日期设置成过去的某个日期。

setcookie('first_name', '', time()-3600);

为了演示所有这些操作,将给站点添加注销能力。指向注销页面的链接将出现在loggedin.php上。作为一种附加特性,将更改头文件,使得当用户登录时显示Logout链接,并且当用户注销时显示Login链接。

1.删除cookie

(1) 在文本编辑器或IDE中创建一个新的PHP文档(参见脚本11-6)。

脚本11-6  logout.php脚本会删除以前建立的cookie

1 <?php # Script 11.6 - logout.php

2

3 // This page lets the user logout.

4

5 // If no cookie is present, redirect the user:

6 if (!isset($_COOKIE['user_id'])) {

7

8   // Need the functions to create an absolute URL:

9   require_once ('includes/login_functions.inc.php');

10    $url = absolute_url();

11    header("Location: $url");

12    exit(); // Quit the script.

13 

14  } else { // Delete the cookies.

15    setcookie ('user_id', '', time()-3600, '/', '', 0, 0);

16   setcookie ('first_name', '', time()-3600, '/', '', 0, 0);

17  }

18 

19  // Set the page title and include the HTML header:

20  $page_title = 'Logged Out!';

21  include ('includes/header.html');

22 

23  // Print a customized message:

24  echo "<h1>Logged Out!</h1>

25  <p>You are now logged out, {$_COOKIE['first_name']}!</p>";

26 

27  include ('includes/footer.html');

28  ?>

(2) 检查user_id cookie是否存在;如果它不存在,就重定向用户。

if (!isset($_COOKIE['user_id'])) {

  require_once ('includes/login_functions.inc.php');

  $url = absolute_url();

  header("Location: $url");

    exit();

与loggedin.php页面一样,如果用户还没有登录,这个页面就应该把用户重定向到主页。尝试注销尚未登录的用户没有意义。

(3) 如果cookie存在,则删除它们。

} else {

  setcookie ('first_name', '', time()-3600, '/', '', 0, 0);

  setcookie ('user_id', '', time()-3600, '/', '', 0, 0);

}

如果用户已经登录,这两个cookie将有效地删除现有的cookie。除了值和到期时间外,其他参数应该具有与创建cookie时相同的值。

(4) 建立PHP页面的提醒。

$page_title = 'Logged Out!';

include ('includes/header.html');

echo "<h1>Logged Out!</h1>

<p>You are now logged out, {$_COOKIE['first_name']}!</p>";

include ('includes/footer.html');

?>

这个页面本身也与loggedin.php页面非常相似。尽管它看起来似乎有些奇怪,但是因为仍然可以引用first_name cookie(刚才在这个脚本中删除了它),考虑以下过程很有意义:

1) 这个页面将被客户请求。

2) 服务器从客户的浏览器读取合适的cookie。

3) 运行页面,并做它的事情(包括发送新的cookie,删除现有的cookie)。

因此,简而言之,在第一次运行这个脚本时,它就可以使用原来的first_name cookie数据。这个页面发送的cookie集(删除的cookie)不能为这个页面所用,因此原来的值仍然有用。

(5) 将文件另存为logout.php,并存放在Web目录中(在与login.php相同的文件夹中)。

2.创建注销链接

(1) 在文本编辑器或IDE中打开header.html(参见脚本8-1)。

(2) 将第五个和最后一个链接更改如下(参见脚本11-7):

<li><?php

if ( (isset($_COOKIE['user_id'])) && (!strpos($_SERVER['PHP_SELF'], 'logout.php')) ) {

  echo '<a href="logout.php">Logout</a>';

} else {

  echo '<a href="login.php">Login</a>';

}

?></li>

脚本11-7  根据用户的当前状态,header.html文件现在将显示登录或注销链接

1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/ xhtml1-strict.dtd">

2 <html xmlns="http://www.w3.org/1999/xhtml">

3 <head">

4   <title><?php echo $page_title; ?></title>

5   <link rel="stylesheet" href="includes/style.css" type="text/css" media="screen" />

6   <meta http-equiv="content-type" content="text/html; charset=utf-8" />

7 </head>

8 <body>

9   <div id="header">

10    <h1>Your Website</h1>

11    <h2>catchy slogan...</h2>

12    </div>

13    <div id="navigation">

14    <ul>

15      <li><a href="index.php">Home Page</a></li>

16      <li><a href="register.php">Register</a></li>

17      <li><a href="view_users.php">View Users</a></li>

18      <li><a href="password.php">Change Password</a></li>

19      <li><?php // Create a login/logout link:

20  if ( (isset($_COOKIE['user_id'])) && (!strpos($_SERVER['PHP_SELF'], 'logout.php')) ) {

21    echo '<a href="logout.php">Logout</a>';

22  } else {

23    echo '<a href="login.php">Login</a>';

24  }

25  ?></li>

26    </ul>

27    </div>

28    <div id="content"><!-- Start of the page-specific content. -->

29  <!-- Script 11.7 - header.html -->

不是在导航区域中建立一个永久的登录链接,而是在用户登录时,让其显示一个Logout链接,或者如果用户没有登录,则显示一个Login链接。根据cookie存在与否来完成上面的条件语句。

由于logout.php脚本一般会显示一个注销链接(因为第一次查看页面时cookie存在),条件语句不得不检查当前页面是否是logout.php脚本。利用strpos()函数可以轻松完成这个任务,用于在一个字符串内发现另一个字符串。

(3) 保存文件,将其存放在Web目录中(放置在includes文件夹内),并在Web浏览器中测试登录/注销过程(参见图11-11、图11-12和图11-13)。

ü提示

l    要查看logout.php脚本中setcookie()调用的结果,可以在浏览器中打开cookie提示(图11-14)。

l    由于在Windows上的Internet Explorer处理cookie的方法中存在一个错误,可能需要把host参数设置成false(不带引号),以便在自己的计算机上开发应用程序时,可以让注销过程工作(即通过localhost)。

l    在删除一个cookie时,应该总是使用与设置cookie相同的参数。如果在创建cookie时设置了主机和路径,则在删除cookie中要再次使用它们。

l    为了讲清楚要点,记住,直到重新加载页面或者访问了另一个页面时,cookie的删除才会生效。换句话说,在那个页面删除了cookie之后,它仍然可供页面使用。

   

图11-11  带有Login链接的主页

图11-12  在用户登录后,页面现在具有一个Logout链接

   

图11-13  注销后的结果

图11-14  这就是在Firefox提示中显示删除cookie的方式

 

11.4  使用会话

使数据可供Web站点上的多个页面使用的另一种方法是使用会话 (session)。会话假定数据存储在服务器上,而不是在Web浏览器中,会话标识符用于定位特定用户的记录(会话数据)。这个会话标识符通常通过 cookie存储在用户的Web浏览器中,但是,敏感数据本身(如用户的ID、姓名等)总是保留在服务器上。

有人可能会问:本来 cookie工作得好好的,为什么还要使用会话?首先,会话更安全,这是由于所有记录的信息都存储在服务器上,并且不会持续不断地在服务器和客户之间来回 发送。其次,可以在会话中存储更多的数据。第三,有些用户拒绝cookie,或者完全关闭它们。虽然会话被设计成与cookie一起工作,但它也可以独立 起作用。

为了演示会话,并把它们与cookie作比较,我将重写以前的脚本集。

会话与cookie

本章具有一些示例,它们使用cookie和会话来完成相同的任务(登录和注销)。显然,它们都能很容易地在PHP中使用,但是,真正的问题是什么时候使用哪一个更合适。

与cookie相比,会话具有以下优点:

l      一般更安全(因为数据保存在服务器上);

l      允许存储更多数据;

l      使用会话时,可以不使用cookie。

与会话相比,cookie则具有以下优点:

l      更容易编程;

l      需要更少的服务器资源。

一般而言,要存储和检索一两份少量的信息,则可使用cookie。不过,对于大部分Web应用程序,都会使用会话。

11.4.1  设置会话变量

关于会话的最重要的规则是,将会使用它们的每个页面首先都必须调用session_start()函数。这个函数告诉PHP开启一个新的会话,或者访问一个现有的会话。必须在把任何内容发送到Web浏览器之前调用这个函数。

第 一次使用session_start()函数时,它会试图发送一个cookie,名称为PHPSESSID(会话名称),和一个类似于 a61f8670baa8e90a30c878df89a2074b(32个十六进制字母,它是会话ID)的值。由于试图发送一个cookie,所以在把 任何数据发送到Web浏览器之前,必须先调用session_start(),这与使用setcookie()和header()这两个函数时的情况一 样。

一旦启动了会话,就可以使用正常的数组语法把值注册到会话中:

$_SESSION['key'] = value;

$_SESSION['name'] = 'Roxanne';

$_SESSION['id'] = 48;

记住这一点后,让我们更新login.php脚本。

开启会话

(1) 在文本编辑器或IDE中打开login.php(参见脚本11-5)。

(2) 用以下几行代码替换setcookie()那几行代码(第12~14行)(参见脚本11-8):

session_start();

$_SESSION['user_id'] = $data['user_id'];

$_SESSION['first_name'] = $data ['first_name'];

脚本11-8  login.php脚本现在使用的是会话,而不是cookie

1 <?php # Script 11.8 - login.php #3

2

3 if (isset($_POST['submitted'])) {

4

5   require_once ('includes/login_functions.inc.php');

6   require_once ('../mysqli_connect.php');

7   list ($check, $data) = check_login($dbc, $_POST['email'], $_POST['pass']);

8

9   if ($check) { // OK!

10 

11      // Set the session data:.

12      session_start();

13      $_SESSION['user_id'] = $data['user_id'];

14      $_SESSION['first_name'] = $data['first_name'];

15 

16      // Redirect:

17      $url = absolute_url ('loggedin.php');

18      header("Location: $url");

19      exit();

20 

21    } else { // Unsuccessful!

22      $errors = $data;

23    }

24 

25    mysqli_close($dbc);

26 

27  } // End of the main submit conditional.

28 

29  include ('includes/login_page.inc.php');

30  ?>

第 一步是开启会话。因为在脚本中这一刻之前没有echo()语句、HTML文件包含,或者甚至是空白,所以现在可以安全地使用 session_start()(尽管也可以把它放在脚本的顶部)。然后,把两个键-值(key-value)对添加到$_SESSION超全局数组中, 以把用户的名字和用户ID注册到会话中。

(3) 将页面另存为login.php,存放在Web目录中,并在Web浏览器中测试它(参见图11-15)。

尽管需要重写loggedin.php以及头部和脚本,你仍然可以测试登录脚本,并查看得到的cookie(参见图11-16)。不过,loggedin.php页面应该把你重定向回主页,因为它仍会检查$_COOKIE变量存在与否。

   

图11-15  登录表单对最终用户保持不变,但是底层的功能现在使用的是会话

图11-16  由PHP的session_start()函数创建的这个cookie存储会话ID

ü提示

l    由于会话通常会发送和读取cookie,应该总是设法在脚本中尽可能早地开启它们。这样做将有助于避免如下问题:试图在已经发送头部(HTML或空白)之后发送cookie。

l    如果你愿意,可以在php.ini文件中把session.auto_start设置为1,从而不必在每个页面上使用session_start()。这样做会加大服务器上的开销,由于这个原因,如果不考虑某些环境因素,就不应该使用它。

l    可以在会话中存储数组(使$_SESSION成为一个多维数组),就像可以存储字符串或数字一样。

11.4.2  访问会话变量

一旦启动了会话,并向其注册了变量,就可以创建将访问这些变量的其他脚本。为了执行该操作,每个脚本首先必须再次使用session_start()来启用会话。

这 个函数将允许当前脚本访问以前启动的会话(如果它可以读取cookie中存储的PHPSESSID值),或者创建一个新的会话(如果它不能读取这个值)。 要理解如果不能找到当前会话ID或者生成了新的会话ID,那么在旧会话ID下存储的所有数据都将不可用。我之所以现在在这里提到这一点,是因为如果你有关 于会话的问题,那么第一个调试步骤是检查会话ID值,看看它是否因页面而异。

假定访问当前会话没有问题,要引用一个会话变量,可使用$_SESSION['var'],就像你引用任何其他数组一样。

访问会话变量

(1) 在文本编辑器或IDE中打开loggedin.php(参见脚本11-4)。

(2) 添加对session_start()函数的调用(参见脚本11-9)。

session_start();

脚本11-9  我更新了loggedin.php脚本,使得它可以引用$_SESSION,而不是引用$_COOKIE(需要在两行上执行更改)

1 <?php # Script 11.9 - loggedin.php #2

2

3 // The user is redirected here from login.php.

4

5 session_start(); // Start the session.

6

7 // If no session value is present, redirect the user:

8  if (!isset($_SESSION['user_id'])) {

9   require_once ('includes/login_functions.inc.php');

10    $url = absolute_url();

11    header("Location: $url");

12    exit();

13  }

14 

15  $page_title = 'Logged In!';

16  include ('includes/header.html');

17 

18  // Print a customized message:

19  echo "<h1>Logged In!</h1>

20  <p>You are now logged in, {$_SESSION['first_name']}!</p>

21  <p><a href=\"logout.php\">Logout</a></p>";

22 

23  include ('includes/footer.html');

24  ?>

设置或访问会话变量的每个PHP脚本都必须使用session_start()函数。必须在包含header.html文件之前以及在把任何内容发送到Web浏览器之前调用这一行代码。

(3) 用$_SESSION替换对$_COOKIE的引用(原文件第6行和第22行)。

if (!isset($_SESSION['user_id'])) {

echo "<h1>Logged In!</h1>

<p>You are now logged in, {$_SESSION['first_name']}!</p>

<p><a href=\"logout.php\">Logout</a></p>";

把一个脚本从cookie转换成会话只需要把使用的$_COOKIE更改成$_SESSION(假定使用相同的名称)。

(4) 将文件另存为loggedin.php,存放在Web目录中,并在浏览器中测试它(参见图11-17)。

(5) 在header.html中用$_SESSION替换对$_COOKIE的引用(从脚本11-7到脚本11-10)。

if ( (isset($_SESSION['user_id'])) && (!strpos($_SERVER['PHP_SELF'], 'logout.php')) ) {

 

图11-17  在登录后,将会把用户重定向到loggedin.php,它会使用存储的会话值通过名字来欢迎用户

脚本11-10  header.html文件现在也会引用$_SESSION,而不是引用$_COOKIE

1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/ xhtml1-strict.dtd">

2 <html xmlns="http://www.w3.org/1999/xhtml">

3 <head">

4   <title><?php echo $page_title; ?></title>

5   <link rel="stylesheet" href="includes/style.css" type="text/css" media="screen" />

6   <meta http-equiv="content-type" content="text/html; charset=utf-8" />

7 </head>

8 <body>

9   <div id="header">

10    <h1>Your Website</h1>

11    <h2>catchy slogan...</h2>

12    </div>

13    <div id="navigation">

14    <ul>

15      <li><a href="index.php">Home Page</a></li>

16      <li><a href="register.php">Register</a></li>

17      <li><a href="view_users.php">View Users</a></li>

18      <li><a href="password.php">Change Password</a></li>

19      <li><?php // Create a login/logout link:

20  if ( (isset($_SESSION['user_id'])) && (!strpos($_SERVER['PHP_SELF'], 'logout.php')) ) {

21    echo '<a href="logout.php">Logout</a>';

22  } else {

23    echo '<a href="login.php">Login</a>';

24  }

25  ?></li>

26    </ul>

27    </div>

28    <div id="content"><!-- Start of the page-specific content. -->

29  <!-- Script 11.10 - header.html -->

为了让Login/Logout链接正确地工作(注意图11-17中不正确的链接),必须把头文件内对cookie变量的引用转变成会话。头文件不需要调用session_start()函数,因为它会被调用该函数的页面包括在内。

(6) 保存头文件,存放在Web目录中(在includes文件夹中),并在浏览器中测试它(参见图11-18)。

 

图11-18  利用为会话进行过更改的头文件,将会显示正确的Login/Logout链接(对比图11-17)

ü提示

l    为了让Login/Logout链接在其他页面(register.php、index.php等)上也能正确工作,将需要在其中的每一个页面上添加session_start()命令。

l    回忆一下我曾经说过的,如果你具有一个应用程序,它看起来似乎不能从一个页面到另一个页面访问会话数据,这可能是由于新的会话是在每个页面上创建的。为了 检查这一点,可以比较会话ID(值的最后几个字符就足够了)来查看它是否相同。你可以在发送会话cookie时通过查看会话cookie来查看会话的 ID,或者使用session_id()函数来执行该操作:

echo session_id();

l    一旦建立了会话变量,就可以使用它们。因此,与使用cookie时不同,可以把一个值赋予$_SESSION['var'],然后在同一个脚本的后面引用$_SESSION['var']。

垃圾收集

关于会话的垃圾收集是指删除会话文件(其中存储了实际的数据)的过程。创建一个销毁会话的注销系统是理想的,但是,并不能保证所有用户都按应该做的那样正式注销。所以PHP包含了一个清理进程。

无 论何时调用session_start()函数,都会引入PHP的垃圾收集,用于检查每个会话最近的修改日期(每当设置或获取变量时都会修改会话)。有两 个设置规定了垃圾收集,它们是:session.gc_maxlifetime和session.gc_ probability。第一个设置用于指定在一个会话持续多少秒不活动之后,可将其看作空闲会话,从而删除。第二个设置确定执行垃圾收集的概率,其取值 范围为1~100。因此,默认设置是,对session_start()的每个调用都有1%的机会调用垃圾收集。如果PHP没有启动清理进程,则会删除任 何超过1440秒未使用的会话。

你可以使用ini_set()函数改变这些设置,尽管要小心谨慎地执行该操作。频率过高或概率太大的垃圾收集可能使服务器陷入困境,并且会不经意地结束较慢用户的会话。

 

posted on 2014-05-31 11:14  猫咪yy  阅读(249)  评论(0)    收藏  举报

导航