代码之家  ›  专栏  ›  技术社区  ›  Matt Dawdy

回复:用post而不是get重定向?

  •  232
  • Matt Dawdy  · 技术社区  · 16 年前

    我们需要提交表单并保存一些数据,然后将用户重定向到异地页面,但是在重定向过程中,我们需要“提交”带有post的表单,而不是get。

    我本来希望有一种简单的方法来完成这个任务,但现在我开始认为没有了。我想我现在必须创建一个简单的另一个页面,只使用我想要的表单,重定向到它,填充表单变量,然后执行body.onload调用一个只调用document.forms[0].submit()的脚本;

    有人能告诉我是否还有其他选择吗?我们稍后可能需要在项目中对此进行调整,这可能会变得有点复杂,所以如果有一个简单的方法,我们可以完成所有与页面无关的工作,那将是非常棒的。

    不管怎样,谢谢你的回答。

    13 回复  |  直到 8 年前
        1
  •  216
  •   Siyual Ranjit Singh    8 年前

    这样做需要了解HTTP重定向的工作方式。当你使用 Response.Redirect() ,发送响应(到发出请求的浏览器)时, HTTP Status Code 302 它告诉浏览器下一步要去哪里。根据定义,浏览器将通过 GET 请求,即使原始请求是 POST .

    另一个选择是使用 HTTP Status Code 307 ,它指定浏览器应以与原始请求相同的方式发出重定向请求,但向用户提示安全警告。要做到这一点,您可以这样写:

    public void PageLoad(object sender, EventArgs e)
    {
        // Process the post on your side   
    
        Response.Status = "307 Temporary Redirect";
        Response.AddHeader("Location", "http://example.com/page/to/post.to");
    }
    

    不幸的是,这并不总是奏效。 Different browsers implement this differently ,因为它不是通用状态代码。

    遗憾的是,与Opera和Firefox开发者不同,IE开发者从未阅读过规范,甚至最新的、最安全的IE7也会将POST请求从域A重定向到域B,而不会出现任何警告或确认对话框!Safari的行为方式也很有趣,虽然它不会引发确认对话框并执行重定向,但它会丢弃post数据, 有效地将307改为更常见的302。

    所以,据我所知,实现这种功能的唯一方法就是使用JavaScript。有两种选择我可以考虑的最高点:

    1. 创建窗体并将其 action 属性指向第三方服务器。然后,向提交按钮添加一个单击事件,该事件首先使用数据向服务器执行Ajax请求,然后允许将表单提交到第三方服务器。
    2. 创建要发布到服务器的表单。提交表单时,向用户显示一个页面,其中包含一个表单,其中包含所有要传递的数据,所有数据都在隐藏的输入中。只需显示类似“重定向…”的消息。然后,向向向第三方服务器提交表单的页面添加一个javascript事件。

    在这两者中,我选择第二个,有两个原因。首先,它比第一个更可靠,因为JavaScript不需要它来工作;对于那些没有启用它的人,您可以始终使隐藏表单的提交按钮可见,并指示他们在超过5秒时按下它。第二,您可以决定哪些数据被传输到第三方服务器;如果您使用的只是在表单经过时对其进行处理,那么您将传递所有的POST数据,这并不总是您想要的。307解决方案也是如此,假设它适用于所有用户。

    希望这有帮助!

        2
  •  113
  •   Pavlo Neiman    15 年前

    你可以用这个围裙:

    Response.Clear();
    
    StringBuilder sb = new StringBuilder();
    sb.Append("<html>");
    sb.AppendFormat(@"<body onload='document.forms[""form""].submit()'>");
    sb.AppendFormat("<form name='form' action='{0}' method='post'>",postbackUrl);
    sb.AppendFormat("<input type='hidden' name='id' value='{0}'>", id);
    // Other params go here
    sb.Append("</form>");
    sb.Append("</body>");
    sb.Append("</html>");
    
    Response.Write(sb.ToString());
    
    Response.End();
    

    因此,在客户端从服务器获取所有HTML之后,事件 重载 触发表单提交并将所有数据发布到定义的回发URL。

        3
  •  30
  •   FlySwat    16 年前

    httpwebrequest用于此。

    在回发时,创建一个httpwebrequest到您的第三方并发布表单数据,然后一旦完成,您就可以响应。重定向到您想要的任何地方。

    您可以获得额外的优势,即不必命名所有服务器控件来生成第三方表单,您可以在构建post字符串时进行此转换。

    string url = "3rd Party Url";
    
    StringBuilder postData = new StringBuilder();
    
    postData.Append("first_name=" + HttpUtility.UrlEncode(txtFirstName.Text) + "&");
    postData.Append("last_name=" + HttpUtility.UrlEncode(txtLastName.Text));
    
    //ETC for all Form Elements
    
    // Now to Send Data.
    StreamWriter writer = null;
    
    HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
    request.Method = "POST";
    request.ContentType = "application/x-www-form-urlencoded";                        
    request.ContentLength = postData.ToString().Length;
    try
    {
        writer = new StreamWriter(request.GetRequestStream());
        writer.Write(postData.ToString());
    }
    finally
    {
        if (writer != null)
            writer.Close();
    }
    
    Response.Redirect("NewPage");
    

    但是,如果您需要用户从该表单中查看响应页,您唯一的选择是使用server.transfer,这可能有效,也可能无效。

        4
  •  6
  •   ZooZ    12 年前

    这会让生活更轻松。 您可以简单地在Web应用程序中使用Response.RedirectWithData(…)方法。

    Imports System.Web
    Imports System.Runtime.CompilerServices
    
    Module WebExtensions
    
        <Extension()> _
        Public Sub RedirectWithData(ByRef aThis As HttpResponse, ByVal aDestination As String, _
                                    ByVal aData As NameValueCollection)
            aThis.Clear()
            Dim sb As StringBuilder = New StringBuilder()
    
            sb.Append("<html>")
            sb.AppendFormat("<body onload='document.forms[""form""].submit()'>")
            sb.AppendFormat("<form name='form' action='{0}' method='post'>", aDestination)
    
            For Each key As String In aData
                sb.AppendFormat("<input type='hidden' name='{0}' value='{1}' />", key, aData(key))
            Next
    
            sb.Append("</form>")
            sb.Append("</body>")
            sb.Append("</html>")
    
            aThis.Write(sb.ToString())
    
            aThis.End()
        End Sub
    
    End Module
    
        5
  •  5
  •   Mike K    15 年前

    ASP.NET 3.5中的一个新特性是ASP按钮的“postbackurl”属性。您可以将其设置为要直接发布到的页面的地址,单击该按钮时,它不会像正常一样重新发布到同一页面,而是发布到您指定的页面。手巧。确保useSubmitBehavior也设置为true。

        6
  •  4
  •   jacob    11 年前

    想分享一下Heroku用它的SSO来添加提供者可能会很有趣

    在“kensa”工具的源代码中可以看到它如何工作的示例:

    https://github.com/heroku/kensa/blob/d4a56d50dcbebc2d26a4950081acda988937ee10/lib/heroku/kensa/post_proxy.rb

    如果你使用javascript的话,你可以在实践中看到。示例页面源:

    <!DOCTYPE HTML>
    <html>
      <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        <title>Heroku Add-ons SSO</title>
      </head>
    
      <body>
        <form method="POST" action="https://XXXXXXXX/sso/login">
    
            <input type="hidden" name="email" value="XXXXXXXX" />
    
            <input type="hidden" name="app" value="XXXXXXXXXX" />
    
            <input type="hidden" name="id" value="XXXXXXXX" />
    
            <input type="hidden" name="timestamp" value="1382728968" />
    
            <input type="hidden" name="token" value="XXXXXXX" />
    
            <input type="hidden" name="nav-data" value="XXXXXXXXX" />
    
        </form>
    
        <script type="text/javascript">
          document.forms[0].submit();
        </script>
      </body>
    </html>
    
        7
  •  3
  •   Jimmy    16 年前

    可以在ASP按钮上设置回发URL,以便发布到其他页面。

    如果您需要在codebehind中执行此操作,请尝试server.transfer。

        8
  •  2
  •   FlySwat    16 年前

    @ Matt,

    您仍然可以使用httpwebrequest,然后将收到的响应定向到实际的outputstream响应,这将为用户提供响应。唯一的问题是任何相对的URL都会被破坏。

    不过,这可能会奏效。

        9
  •  2
  •   Andrei Rînea    16 年前

    我要做的是:

    将数据放入标准表单(没有runat=“server”属性),并将表单的操作设置为发布到目标非站点页面。 在提交之前,我会将数据提交到我的服务器 使用xmlhttpRequest 分析反应。如果响应意味着您应该继续进行场外发布,那么我(javascript)将继续发布,否则我将重定向到我的站点上的页面。

        10
  •  2
  •   FlySwat    16 年前

    在PHP中,可以使用curl发送post数据。有什么可以与.NET相比较的吗?

    是的,httpwebrequest,请参阅下面的我的文章。

        11
  •  1
  •   Ben Griswold    16 年前

    我建议构建一个httpwebrequest,以编程方式执行您的帖子,然后在阅读响应后重定向(如果适用)。

        12
  •  1
  •   erickson    16 年前

    get(and head)方法不应该用于做任何有副作用的事情。副作用可能是更新Web应用程序的状态,也可能是向您的信用卡收费。如果一个动作有副作用,则应使用另一种方法(post)。

    所以,用户(或者他们的浏览器)不应该为GET所做的事情负责。如果由于GET而产生一些有害或昂贵的副作用,那将是Web应用程序的错误,而不是用户的错误。根据规范,用户代理 不能 自动跟踪重定向,除非它是对GET或HEAD请求的响应。

    当然,很多GET请求确实有一些副作用,即使它只是附加到日志文件中。重要的是应用程序,而不是用户,应该对这些影响负责。

    HTTP规范的相关部分包括 9.1.1 and 9.1.2 10.3 .

        13
  •  0
  •   John Leidegren    15 年前

    通常,您所需要的只是在这两个请求之间携带一些状态。事实上,有一种非常有趣的方法可以做到这一点,它不依赖于javascript(想想<noscript/>)。

    Set-Cookie: name=value; Max-Age=120; Path=/redirect.html
    

    有了这个cookie,您可以在下面的请求/redirect.html中检索name=value信息,您可以在这个name/value对字符串中存储任何类型的信息,最多可以存储4K的数据(典型的cookie限制)。当然,您应该避免这种情况,而是存储状态代码和标志位。

    收到此请求后,您将返回该状态代码的删除请求。

    Set-Cookie: name=value; Max-Age=0; Path=/redirect.html
    

    我的HTTP有点生锈,我一直在研究RFC2109和RFC2965的可靠性,最好我想让cookie往返一次,但这似乎也不可能, 如果您要重新定位到另一个域,第三方cookie可能是您的一个问题。这仍然是可能的,但并不像你在自己的领域里做事情那样无痛。

    这里的问题是并发性,如果一个超级用户正在使用多个选项卡并设法交错属于同一会话的几个请求(这是非常不可能的,但不是不可能的),这可能会导致应用程序中的不一致。

    这是在没有无意义的URL和javascript的情况下执行HTTP往返的<noscript/>方法。

    我将这段代码作为一个概念教授提供:如果这段代码是在一个您不熟悉的上下文中运行的,我认为您可以计算出什么部分是什么。

    其思想是在重定向时使用某种状态调用relocate,而重新定位的URL调用get state以获取数据(如果有)。

    const string StateCookieName = "state";
    
    static int StateCookieID;
    
    protected void Relocate(string url, object state)
    {
        var key = "__" + StateCookieName + Interlocked
            .Add(ref StateCookieID, 1).ToInvariantString();
    
        var absoluteExpiration = DateTime.Now
            .Add(new TimeSpan(120 * TimeSpan.TicksPerSecond));
    
        Context.Cache.Insert(key, state, null, absoluteExpiration,
            Cache.NoSlidingExpiration);
    
        var path = Context.Response.ApplyAppPathModifier(url);
    
        Context.Response.Cookies
            .Add(new HttpCookie(StateCookieName, key)
            {
                Path = path,
                Expires = absoluteExpiration
            });
    
        Context.Response.Redirect(path, false);
    }
    
    protected TData GetState<TData>()
        where TData : class
    {
        var cookie = Context.Request.Cookies[StateCookieName];
        if (cookie != null)
        {
            var key = cookie.Value;
            if (key.IsNonEmpty())
            {
                var obj = Context.Cache.Remove(key);
    
                Context.Response.Cookies
                    .Add(new HttpCookie(StateCookieName)
                    { 
                        Path = cookie.Path, 
                        Expires = new DateTime(1970, 1, 1) 
                    });
    
                return obj as TData;
            }
        }
        return null;
    }