PHP重定向POST数据

288

我对这个主题进行了一些研究,有一些专家说不可能实现,所以我想要寻求替代解决方案。

我的情况是:

页面A:[checkout.php] 客户填写其帐单详细信息。

页面B:[process.php] 生成发票编号并将客户详细信息存储在数据库中。

页面C:[thirdparty.com] 第三方支付网关(仅接受POST数据)。

客户在页面A中填写其详细信息并设置其购物车,然后将其POST到页面B。在process.php中,将POST的数据存储在数据库中并生成发票编号。之后,将客户数据和发票编号POST到第三方支付网关thirdparty.com。问题是如何在页面B中进行POST操作。cURL可以将数据POST到页面C,但问题是该页面没有重定向到页面C。客户需要在页面C上填写信用卡详细信息。

第三方支付网关确实为我们提供了API示例,该示例将发票号码与客户详细信息一起POST。我们不希望系统生成过多不需要的发票号码。

是否有任何解决方案? 我们目前的解决方案是让客户在页面A中填写详细信息,然后在页面B中创建另一页,显示所有客户详细信息,并且用户可以单击“确认”按钮将其POST到页面C。

我们的目标是让客户只需单击一次。

希望我的问题很清楚 :)


我不认为这是重复的,因为我的目标是在PHP页面内传递POST数据并重定向它,而使用cURL来实现可能行不通。我只是寻求专家提供任何替代方案。 - Shiro
ʜᴛᴛᴘ 308 重定向代码是什么? - user2284570
13个回答

245

在页面B上生成一个表单,包括所有必需的数据和设置为页面C的操作,并在页面加载时使用JavaScript提交它。这样用户就不必费力地将数据发送到页面C。

这是唯一的方法。重定向是一个303 HTTP头,您可以通过http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html 了解更多信息,但我会引用其中的一些内容:

  

请求的响应可以在不同的URI下找到,应该使用GET方法在那个资源上检索它。这种方法存在主要是为了允许POST激活脚本的输出将用户代理重定向到所选资源。新的URI不是原始请求的替代引用。303响应不得被缓存,但对第二个(重定向)请求的响应可能是可缓存的。

实现您正在做的唯一方法是使用一个中间页面将用户发送到页面C。以下是一个简单的代码片段,演示如何实现这一点:

<form id="myForm" action="Page_C.php" method="post">
<?php
    foreach ($_POST as $a => $b) {
        echo '<input type="hidden" name="'.htmlentities($a).'" value="'.htmlentities($b).'">';
    }
?>
</form>
<script type="text/javascript">
    document.getElementById('myForm').submit();
</script>

你还应该在一个noscript标签内放置一个简单的“确认”表单,以确保没有启用JavaScript的用户也能够使用你的服务。


2
我的目标是从页面A到页面C,页面B是控制器(业务逻辑),生成发票号码。从系统的角度来看,是页面A-> 页面B-> 页面C,但从用户的角度来看,应该是页面A-> 页面B,他们不应该知道页面B的存在。 - Shiro
3
@Peeter 提出的建议很明智,值得赞赏。唯一的问题是当用户在 C 页面点击后退按钮时会再次提交 C 页面。此外,您忘记使用 htmlentities/htmlspecialchars$a$b 进行编码,请参考 https://dev59.com/7W025IYBdhLWcg3wKSeP#6180337。 - Marco Demaio
9
如果客户端禁用了JavaScript,那么问题是页面B是否能重定向到页面C? - Imran Omar Bukhsh
28
<form>中,<noscript><input type="submit" value="Click here if you are not redirected."/></noscript>的含义是:如果您没有被重定向,请点击此处。 - nullability
4
“language” 属性已经过时,应该使用 <script type="text/javascript">...</script> - Alex W
显示剩余4条评论

45

如果你不想处理Javascript,$_SESSION会是你的好朋友。

假设你想要传递一个电子邮件地址:

在页面A上:

// Start the session
session_start();

// Set session variables
$_SESSION["email"] = "awesome@email.com";

header('Location: page_b.php');

并且在页面 B 上:

// Start the session
session_start();

// Show me the session!  
echo "<pre>";
print_r($_SESSION);
echo "</pre>";
销毁会话。
unset($_SESSION['email']);
session_destroy();

你为什么认为这个版本比JavaScript版本不安全呢? - Adam Baranyai
1
不,我的意思是相对于JavaScript而言不太安全。总的来说,仅使用$_SESSION可能比使用$_SESSION + cookie要稍微不安全一些,尽管$_SESSION本身已经相当安全了。更多信息请参见:https://dev59.com/zmQm5IYBdhLWcg3wswpv - Robert Sinclair
如何关闭会话? - Sayed Muhammad Idrees
你需要关闭会话吗? - Ivan P.
2
这不是一个有效的选项,因为用户想要发布信息的 Page_C 是外部页面,而 $_SESSION 对于此不可用。 - Dawson Irvine
1
@Robert Sinclair非常感谢您的全面回答。这真的非常有帮助。 - Muhammad Khan

42
/**
  * Redirect with POST data.
  *
  * @param string $url URL.
  * @param array $post_data POST data. Example: ['foo' => 'var', 'id' => 123]
  * @param array $headers Optional. Extra headers to send.
  */
public function redirect_post($url, array $data, array $headers = null) {
  $params = [
    'http' => [
      'method' => 'POST',
      'content' => http_build_query($data)
    ]
  ];

  if (!is_null($headers)) {
    $params['http']['header'] = '';
    foreach ($headers as $k => $v) {
      $params['http']['header'] .= "$k: $v\n";
    }
  }

  $ctx = stream_context_create($params);
  $fp = @fopen($url, 'rb', false, $ctx);

  if ($fp) {
    echo @stream_get_contents($fp);
    die();
  } else {
    // Error
    throw new Exception("Error loading '$url', $php_errormsg");
  }
}

19
虽然一开始看起来比被接受的答案更好,但我注意到它没有将页面重定向到提供的“$url”--它只是用“$url”页面的内容替换现有页面的内容。重要的是,“$url”页面中的PHP代码不会被评估。 - iPadDeveloper2011
@iPadDeveloper2011,在URL中你没有PHP代码!PHP代码在服务器上执行,而不是在客户端。 - Eduardo Cuomo
1
redirect_post()是服务器端的PHP代码,它向服务器发送一个关于$url的请求。如果$url是一个.php页面,那么我注意到PHP不会被解析,而是返回带有PHP标签的HTML。难道发送POST数据到服务器端脚本不是整个操作的重点吗? - iPadDeveloper2011
2
@EduardoCuomo 这个方法只适用于绝对URL,因为fopen()的工作方式如此:http://php.net/manual/en/function.fopen.php 这仍然是一个相当聪明的答案。 - Omn
@Omn 你说得对!“这个相对URL不起作用?”不是一个问题,而是一个陈述。抱歉造成困惑。 - Eduardo Cuomo
显示剩余2条评论

28

我有另一种解决方案,可以实现这一点。它需要客户端运行JavaScript(我认为这是一个公平的要求)。

只需在页面A上使用AJAX请求,在后台生成您的发票号码和客户详细信息(您以前的页面B),然后一旦请求成功返回正确的信息 - 只需将表单提交到您的支付网关(页面C)。

这将实现您希望用户只需单击一个按钮即可进入支付网关的结果。以下是一些伪代码:

HTML:

<form id="paymentForm" method="post" action="https://example.com">
  <input type="hidden" id="customInvoiceId" .... />
  <input type="hidden" .... />

  <input type="submit" id="submitButton" />
</form>

使用jQuery方便,但也可以用纯Javascript实现:

$('#submitButton').click(function(e) {
  e.preventDefault(); //This will prevent form from submitting

  //Do some stuff like build a list of things being purchased and customer details

  $.getJSON('setupOrder.php', {listOfProducts: products, customerDetails: details }, function(data) {
  if (!data.error) {
    $('#paymentForm #customInvoiceID').val(data.id);
    $('#paymentForm').submit();   //Send client to the payment processor
  }
});

1
这个方案存在一个问题,如果有人在该页面上关闭JavaScript、进行刷新和提交操作,那么它将直接跳转至支付页面而没有保存任何信息。这可能是一个漏洞。 - caoglish
2
那么只有使用Javascript设置paymentForm操作吗?如果JS被禁用,表单将无法提交。 - MikeMurko
用户关闭JS不应该给我们造成问题 - 我们必须前往页面B的原因可能是为了生成指纹或添加订单代码等。如果没有这些,那么第三方页面C应该会失败,或者最坏的情况是当结果从第三方返回给我们时没有订单代码,我们不应该接受订单。如果JS可接受,这总体上是一个好的解决方案。 - Coder

7

我知道这是一个老问题,但我有另一种使用jQuery的替代解决方案:

var actionForm = $('<form>', {'action': 'nextpage.php', 'method': 'post'}).append($('<input>', {'name': 'action', 'value': 'delete', 'type': 'hidden'}), $('<input>', {'name': 'id', 'value': 'some_id', 'type': 'hidden'}));
actionForm.submit();

上面的代码使用jQuery创建一个表单标签,作为POST字段附加隐藏字段,并最终提交它。页面将转发到表单目标页面,并带有POST数据附加。
附注:这种情况需要JavaScript和jQuery。如其他答案的评论所建议,您可以利用

7

我知道这个问题跟PHP有关,但是重定向一个POST请求最好的方式可能是使用.htaccess文件,例如:

RewriteEngine on
RewriteCond %{REQUEST_URI} string_to_match_in_url
RewriteCond %{REQUEST_METHOD} POST
RewriteRule ^(.*)$ https://domain.tld/$1 [L,R=307]

说明:

默认情况下,如果要重定向请求并带有POST数据,则浏览器会通过GET进行302重定向这也会删除与请求关联的所有POST数据。 浏览器这样做是为了预防任何意外的POST事务重新提交。

但是,如果您仍然想要重定向带有POST数据的请求怎么办?在HTTP 1.1中,有一个状态码可以解决此问题。 状态码307表示应该使用相同的HTTP方法和数据重复请求。因此,如果使用此状态码,则将重复POST请求及其数据。

SRC


6
您可以让PHP进行POST请求,但是这样您的PHP将会收到返回值,会出现各种复杂情况。我认为最简单的方法是让用户进行POST请求。
所以,就像您建议的那样,您确实会得到以下部分:
客户在页面A中填写详细信息,然后在页面B中创建另一个页面,在那里显示所有客户详细信息,点击确认按钮然后POST到页面C。
但是您实际上可以在页面B上进行javascript提交,因此不需要点击。将其设置为带有加载动画的“重定向”页面即可。

这是我们现在所做的。我在想是否有更好的 PHP 逻辑来解决它。谢谢。;) - Shiro
是的,更偏向于HTTP,但我工作在PHP环境中,所以我认为两者都重要。谢谢!Col. Shrapnel。 - Shiro

5

有一个简单的技巧,可以使用$_SESSION并创建一个由提交的值组成的array,一旦您进入File_C.php,您就可以使用它,然后在处理完之后将其销毁。


1
你能提供一个小例子来说明你的意思吗? - caro
8
我理解的是,Page C 是一个外部来源,它接收 POST 值但不接收会话(session)。 - Narayan Bhandari
1
会话值可能无法在不同的域中访问。 - asela daskon

5
您可以使用会话来保存$_POST数据,然后检索该数据并在随后的请求中将其设置为$_POST
用户提交请求到/dirty-submission-url.php
执行以下操作:
if (session_status()!==PHP_SESSION_ACTIVE)session_start();
     $_SESSION['POST'] = $_POST;
}
header("Location: /clean-url");
exit;

然后浏览器会重定向并从您的服务器请求/clean-submission-url。您需要进行一些内部路由以确定如何处理此请求。
在请求开始时,您需要执行以下操作:

if (session_status()!==PHP_SESSION_ACTIVE)session_start();
if (isset($_SESSION['POST'])){
    $_POST = $_SESSION['POST'];
    unset($_SESSION['POST']);
}

现在,通过您的其余请求,您可以像第一个请求时一样访问$_POST

你也可以将 $_POST 数据处理成查询字符串并以此方式传递到后续页面。但是,数据不能太大。如果涉及文件上传,则可能无法正常工作,但我不确定。 - Reed
1
你让我开心了,谢谢 @Reed。 - Gursewak Dhindsa
1
这里似乎有一种感觉,即 $_SESSION['POST'] = $_POST; 似乎具有神奇的作用。 - webs

5
我在后端传递变量等时,使用POST请求遇到了类似的问题,而GET请求却正常工作。问题出在后端进行了大量的重定向操作,这些重定向操作无法使用fopen或php头部方法处理。所以我唯一能做的就是在页面加载时使用一个隐藏表单,并通过POST提交将值传递过去,这样才使得它正常工作。
echo
'<body onload="document.redirectform.submit()">   
    <form method="POST" action="http://someurl/todo.php" name="redirectform" style="display:none">
    <input name="var1" value=' . $var1. '>
    <input name="var2" value=' . $var2. '>
    <input name="var3" value=' . $var3. '>
    </form>
</body>';

嘿@kaya,这看起来很棒,这可以作为原始页面和目标页面之间的中间页面使用吗? - Daniel
@Daniel,正确 - 我使用这个来通过hashValue从仪表板执行网站的自动登录。 仪表板仅包含一个链接,没有像用户名/密码之类的用户数据,因此我通过GET请求将hashValue传递给LoginHook.php,然后从那里获取DB中的登录信息,并通过POST请求将用户重定向到目标。 - kaya
你难道忘了加上输入类型为"text"或者"hidden"的标签了吗? - asela daskon
说实话,我也不知道,但这对我有效,如果你查看样式属性,整个表单都被隐藏了。 - kaya

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接