我对 PHP Post/Redirect/Get 感到困惑

Posted

技术标签:

【中文标题】我对 PHP Post/Redirect/Get 感到困惑【英文标题】:I am confused about PHP Post/Redirect/Get 【发布时间】:2011-12-02 15:55:58 【问题描述】:

在关于防止 php 表单重新提交的 article 中,我阅读了以下内容:

(不引用)这可能是接收表单数据的页面,例如称为“form.php”:

<form action="submit.php">
  <input type="text" name="user" required />
  <input type="password" name="pass" required />
  <input type="submit" value="Log in" />
</form>

因此,处理 POST 数据的页面将被称为“submit.php”。如果登录正确,此代码将运行:

header('Location: /login/form.php?success=true');

但是,用户不能直接导航到上面的 URL 吗?另外,GET 变量的目的是什么?我不能只在 form.php 中有一个脚本来检查用户是否登录吗?

在 submit.php 中,我应该将登录的用户名保存为 $_SESSION['username'],然后在 form.php 中检查是否 isset()?此外,由于其中包含“成功”的 URL 不是很漂亮,再次重定向用户是否经济?我应该使用 PHP header() 还是 javascript window.location.href? 如您所见,我有点困惑。

感谢您的帮助。

【问题讨论】:

“但是,用户不能直接导航到上面的 URL 吗?” - - 是的,他能。那么? 你是对的,没有 POST 数据不会造成任何伤害。 网址中的?success 并没有那么难看。我一直都在使用这样的东西,因为大多数人都不注意它,而且它真的很容易。 我假设该示例中查询字符串的目的是供form.php 用来确定它是否应该显示成功消息。如果你愿意,你可以在会话中做这种事情。重要的部分是有一个重定向,所以重新加载不会重新执行 POST,只是对成功页面的无害 GET。 @user805556:它是任何类型表单的通用解决方案,例如添加评论表单。在这种情况下,检查消息是否已成功添加并不像if (isset($_SESSION... 那样容易 【参考方案1】:

但是,用户不能直接导航到上面的 URL 吗?

是的,他可以。不过这不会造成任何不良后果。

另外,GET 变量的用途是什么?

要有一些标志表示表单已成功处理,您需要祝贺用户。

我不能只在 form.php 中有一个脚本来检查用户是否登录吗?

嗯,您可以按照您喜欢的方式保留您的代码。没有任何强烈的要求

在 submit.php 中,我应该将登录的用户名保存为 $_SESSION['username'],然后在 form.php 中检查 ifisset() 吗?

如果您需要在当前会话中保留它 - 是的,这样做。

此外,由于其中包含“成功”的 URL 不是很漂亮,再次重定向用户是否经济?

重定向到哪里。重定向是相当便宜的东西。

我应该使用 PHP header() 还是 Javascript window.location.href?

你绝对应该在 php 中这样做,否则你会遇到你试图避免遵循 PRG 方式的麻烦。

【讨论】:

好的,这有点清楚了。但是,由于我从不使用单独的登录页面,而是在每个页面上都有它们,我应该重定向到这个:? “submit.php?page=whatever.php”,然后使用该变量重定向回该特定页面?另外,我怎样才能在 PHP 中找到当前页面,以便我可以自动化我上面建议的过程?谢谢。 @user805556:只需将重定向网址作为任何其他参数传递,并在处理后 - 重定向回它。您可以在$_SERVER 找到当前 url(或者更准确地说是当前 url 的片段)【参考方案2】:

PRG 或 Post/Redirect/Get 只是一种可以用来防止消息框的模式。您如何详细使用它(本文仅提供一般性建议)取决于您的需求。

如果您想在 cookie、会话或 get 变量中标记成功 Flash 消息,这完全取决于您。顺便说一句,第二次重定向无济于事,如果你玩弄它,你就会明白这一点。

唯一重要的部分是,在您收到 POST 请求后,您进行重定向。然后,用户仍然可以在历史记录中前后移动,而不会被要求重新提交 POST 数据。

该模式有效并且是一件好事。就在两天前,我又做了一次,一步一步的 weppapp 安装程序在浏览器界面中导航要好得多。

关于您的重定向

这段代码是错误的:

header('Location:/login/form.php?success=true');

首先,冒号后面需要有一个空格:

header('Location: /login/form.php?success=true');

那么地址必须是绝对URI,它必须包含完整的URL:

header('Location: http://example.com/login/form.php?success=true');

header() 旁边,您应该根据 RFC 提供消息正文,许多所谓的“网络开发人员”甚至都不知道:

$url = 'http://example.com/login/form.php?success=true';
header(sprintf('Location: %s', $url));
printf('<a href="%s">Moved</a>.', $url);
exit;

别忘了出口。当然,这几乎是在重新发明***,而是安装 PHP 的 http 扩展,然后执行以下操作:

http_redirect('/login/form.php?success=true');

你发现nifty helper here。

回顾一下:重要的是您在发布后进行重定向。其他一切,比如传递一个变量,完全取决于你想怎么做。

【讨论】:

@user805556:请点击:sprintf 不,它不需要是绝对 URI,甚至是根路径。【参考方案3】:

是的,您永远不应该依赖 GET 变量(甚至是隐藏的 POST 变量)说“当然,让我进入,我是有效用户!”。

就我个人而言,我会从链接中删除 GET 信息并仅依赖会话变量。记得放置一个 'session_start();'如果您使用 PHP 激活会话,则作为第一行代码。

对于提交.php:

<?php
session_start();
if ($_POST['user'] && $_POST['pass'])  // Make sure both variable are set
 if (your_method) 
 // Code to check if the user and pass are valid however you plan
  $_SESSION['user'] = $_POST['user'];
  $_SESSION['loggedin'] = time();
 

header('Location: form.php'); // Either way, pass or fail, return to form.php
exit();
?>

然后在form.php中:

<?php
session_start();
$activeuser = false;
if ($_SESSION['user'] && $_SESSION['loggedin'] < (time()+600)) 
// Check if the user exists and the last access was with in 10 minutes.
 $_SESSION['loggedin'] = time(); // If so, keep them up to date!
 $activeuser = true;

if ($activeuser) 
 // whatever should show to someone logged in
 else 
 // Show log in form

?>

另外,你可能已经知道了,但是默认的传输方式是GET,所以一定要在form标签中指定method="post"。

如果需要,通常最好使用 header() 进行重定向,因为 Javascript 是客户端并且可以避免这可能会破坏您网站运行的意图。

【讨论】:

【参考方案4】:

正如您链接到的文章所指出的那样,POST/REDIRECT/GET 背后的主要思想是避免用户重新提交数据(大部分时间)。通常,您不希望相同的 POST(具有完全相同的数据)发生两次——事实上,在某些情况下,它最终可能会第二次执行某些操作(例如从信用卡中收取费用),这会很糟糕.

您在问题中询问的大部分内容都是实现细节(例如在重定向中发送 ?success 请求参数)。

在实践中,通常发生的情况是您的重定向成功。例如,如果用户的输入验证失败,您不重定向,而是重新显示表单以及相关的错误消息。

这是一个基本示例,全部在一个脚本中。我试图只包含重要的内容,尽可能少的无关内容。

login.php

<?php
/**
 * ensure user supplied both username & password
 * @return mixed true or an array of error messages
 */ 
function validate_login_values($vars)
    $errors = array();
    if (empty($vars['username'])) $errors[] = 'You must supply a username, genius.';
    if (empty($vars['password'])) $errors[] = 'You must supply a password, dummy.';
    if (empty($errors)) return true;
    return $errors; // $errors must be an array.


if (! empty($_POST))
    $validationResults = validate_login_values($_POST);
    if ($validationResults === true)
        // assume here that authenticate properly escapes it's arguments before sending them
        // to the database.
        if (authenticate($_POST['username'],$_POST['password']))
            //GREAT SUCCESS!  The user is now logged in.  Redirect to home page
            header("Location: /");
            die();
        
        $errors[] = 'Invalid username/password.  Try again, slim";

    else
        $errors = $validationResults; // validate_login_values created errors.
      

?>

<h1>Log In, Friend!</h1>]

<?php 
//display errors, if there were any
if (! empty($errors)): ?>
<div class="errors">Something went horribly wrong:
<ul><?php foreach($errors as $e) echo "<li>$e</li>"; ?></ul>
<div>
<?php endif; ?>

<form method="POST">
<!--   //username, password, and submit   -->
</form>

【讨论】:

一个问题:当我使用 header() 重定向用户然后回显时,这些内容在哪里得到回显?在顶部?

以上是关于我对 PHP Post/Redirect/Get 感到困惑的主要内容,如果未能解决你的问题,请参考以下文章

Flask post / redirect / get模式无法从错误中恢复

如果请求和响应的 uri 相同,为啥 Microsoft Edge 在调用 POST/REDIRECT/GET 方法时发送空的 http-referer?

刷新PHP时清除POST数据

HTTP 303 (SeeOther):GET 工作,POST 失败

如何避免表单重复提交

如何防止在 PHP 中重复提交表单(跨多个窗口进行保护)?