【问题标题】:HttpWebResponse.Cookies empty despite Set-Cookie Header (no-redirect)HttpWebResponse.Cookies 为空,尽管 Set-Cookie 标头(无重定向)
【发布时间】:2013-02-12 18:05:24
【问题描述】:

我正在努力找出这里出了什么问题。我正在发送登录信息,我可以看到 Header 中的 Set-Cookie 具有正确的值,但是 Cookies 集合没有被填充。

这是 HTTPS,登录自动重定向,但我使用 AllowAutoRedirect=false 禁用它以尝试解决此问题。

在此屏幕截图中,您可以轻松查看调试信息以及应该设置 cookie。我正在将我的 httpWebRequest.Cookie 设置为新的 CookieCollection。

HttpWebRequest httpRequest;
CookieContainer reqCookies = new CookieContainer();
string url = "https://example.com";
string[] email = user.Split('@');
email[0] = System.Web.HttpUtility.UrlEncode(email[0]);
user = email[0] + "@" + email[1];
pass = System.Web.HttpUtility.UrlEncode(pass);

string postData = "email=" + user + "&password=" + pass;
byte[] byteData = Encoding.UTF8.GetBytes(postData);

httpRequest = (HttpWebRequest)WebRequest.Create(url);
httpRequest.Method = "POST";
httpRequest.Referer = url;
httpRequest.CookieContainer = reqCookies;
httpRequest.UserAgent = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1003.1 Safari/535.19";
httpRequest.Accept = "text/html, application/xhtml+xml, */*";
httpRequest.ContentType = "application/x-www-form-urlencoded";
httpRequest.ContentLength = byteData.Length;
using (Stream postStream = httpRequest.GetRequestStream())
{
    postStream.Write(byteData, 0, byteData.Length);
    postStream.Close();
}

httpRequest.AllowAutoRedirect = false;
HttpWebResponse b = (HttpWebResponse)httpRequest.GetResponse();

尝试使用完全相同的代码连接到http://www.yahoo.com,并将 cookie 放入我的收藏中...啊...

这里是 Set-Cookie Header 值:

s=541E2101-B768-45C8-B814-34A00525E50F;域=example.com;路径=/; 版本=1

【问题讨论】:

  • 您确定从服务器设置的域名正确吗?是 domain.com 还是 .domain.com ,它们都是不同的。您可以发布您的 ASP.NET 代码吗?

标签: c# cookies httpwebrequest


【解决方案1】:

除非在HttpWebRequest 上设置了CookieContainer,否则Cookies 属性将为空。因此,正确的做法是在检索响应之前设置CookieContainer 成员:

var request = (HttpWebRequest)HttpWebRequest.Create(..);
request.CookieContainer = new CookieContainer();

var response = request.GetResponse();
// ..response.Cookies will now contain the cookies sent back by the server.

您无需手动解析Set-Cookie

请参阅documentation 了解更多信息。

【讨论】:

    【解决方案2】:

    更新 五年后,实际上有人提到了正确的方法:首先正确设置 CookieContainer 并让它处理所有事情。请进一步参考 Sam 的解决方案。

    在读取由 C# ASP.NET 应用程序创建的 C# Cookie 时,我也发现了这个问题... ;)

    不确定是否与它有关,但我发现在我的案例中设置的两个 Cookie 写在一个 Set-Cookie 标头中,Cookie 有效负载用逗号分隔。所以我调整了 AppDeveloper 的解决方案来处理这个多 cookie 问题,并修复了我在 cmets 中提到的名称/值。

    private static void fixCookies(HttpWebRequest request, HttpWebResponse response) 
    {
        for (int i = 0; i < response.Headers.Count; i++)
        {
            string name = response.Headers.GetKey(i);
            if (name != "Set-Cookie")
                continue;
            string value = response.Headers.Get(i);
            foreach (var singleCookie in value.Split(','))
            {
                Match match = Regex.Match(singleCookie, "(.+?)=(.+?);");
                if (match.Captures.Count == 0)
                    continue;
                response.Cookies.Add(
                    new Cookie(
                        match.Groups[1].ToString(), 
                        match.Groups[2].ToString(), 
                        "/", 
                        request.Host.Split(':')[0]));
            }
        }
    }
    

    【讨论】:

    • 快速修复" coki1=val1; expires=sat,monday 2017 ; domain:asdf.com , coki2=val2; expires=sat,monday 2017 ; domain:asdf.com , ".逗号也用于过期日期。 su 它搞砸了 split(",") 添加这个临时修复值 = Regex.Replace(value , "(e|E)xpires=(.+?)(;|$)|(P|p)ath=( .+?);", "");
    • 正如@blackholeearth0_gmail 提到的,从逗号拆分会产生额外的不正确cookie,因为expires 标头Wed, 04-Apr-18 中有一个逗号
    • @Sam 的回答要优雅得多。
    • @TimWilson 你是绝对正确的。更新答案以引用该解决方案。
    • 谢谢!我几乎沿着解析路线走,直到找到 Sam 的答案。我想确保任何其他必须在 C# 中处理 cookie 的可怜人都知道如何正确地处理它。我的用例是针对使用表单身份验证的 API 编写自动化测试。
    【解决方案3】:

    这可能有点晚了,但你可以使用函数 SetCookies

    var cHeader = responce.Headers.Get("Set-Cookie");
    var cookie = new CookieContainer();
    cookie.SetCookies(new Uri("[...]"), cHeader);
    

    【讨论】:

    • 如果set-cookie 包含path=/,这将失败
    【解决方案4】:

    查看其他答案,我改进了不正确的 cookie 处理。与那些答案不同,这个答案会自动处理所有 cookie 属性(例如过期、安全等)并适用于所有范围的 cookie(即使有超过 1 个不正确的 cookie)。

    它是作为扩展方法实现的,可以通过以下方式使用:

    //...
                using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
                {
                    request.FixCookies(response);
    //...
    

    FixCookies()扩展方法:

    using System;
    using System.Collections.Generic;
    using System.Net;
    
    namespace AG.WebHelpers
    {
        static public class ExtensionMethods
        {
            static public void FixCookies(this HttpWebRequest request, HttpWebResponse response)
            {
                for (int i = 0; i < response.Headers.Count; i++)
                {
                    string name = response.Headers.GetKey(i);
                    if (name != "Set-Cookie")
                        continue;
                    string value = response.Headers.Get(i);
                    var cookieCollection = ParseCookieString(value, () => request.Host.Split(':')[0]);
                    response.Cookies.Add(cookieCollection);
                }
            }
    
            static private CookieCollection ParseCookieString(string cookieString, Func<string> getCookieDomainIfItIsMissingInCookie)
            {
                bool secure = false;
                bool httpOnly = false;
    
                string domainFromCookie = null;
                string path = null;
                string expiresString = null;
    
                Dictionary<string, string> cookiesValues = new Dictionary<string, string>();
    
                var cookieValuePairsStrings = cookieString.Split(';');
                foreach(string cookieValuePairString in cookieValuePairsStrings)
                {
                    var pairArr = cookieValuePairString.Split('=');
                    int pairArrLength = pairArr.Length;
                    for (int i = 0; i < pairArrLength; i++)
                    {
                        pairArr[i] = pairArr[i].Trim();
                    }
                    string propertyName = pairArr[0];
                    if (pairArrLength == 1)
                    {
                        if (propertyName.Equals("httponly", StringComparison.OrdinalIgnoreCase))
                            httpOnly = true;
                        else if (propertyName.Equals("secure", StringComparison.OrdinalIgnoreCase))
                            secure = true;
                        else
                            throw new InvalidOperationException(string.Format("Unknown cookie property \"{0}\". All cookie is \"{1}\"", propertyName, cookieString));
                        continue;
                    }
    
                    string propertyValue = pairArr[1];
                    if (propertyName.Equals("expires", StringComparison.OrdinalIgnoreCase))
                        expiresString = propertyValue;
                    else if (propertyName.Equals("domain", StringComparison.OrdinalIgnoreCase))
                        domainFromCookie = propertyValue;
                    else if (propertyName.Equals("path", StringComparison.OrdinalIgnoreCase))
                        path = propertyValue;
                    else
                        cookiesValues.Add(propertyName, propertyValue);
                }
    
                DateTime expiresDateTime;
                if (expiresString != null)
                {
                    expiresDateTime = DateTime.Parse(expiresString);
                }
                else
                {
                    expiresDateTime = DateTime.MinValue;
                }
                if (string.IsNullOrEmpty(domainFromCookie))
                {
                    domainFromCookie = getCookieDomainIfItIsMissingInCookie();
                }
    
                CookieCollection cookieCollection = new CookieCollection();
                foreach (var pair in cookiesValues)
                {
                    Cookie cookie = new Cookie(pair.Key, pair.Value, path, domainFromCookie);
                    cookie.Secure = secure;
                    cookie.HttpOnly = httpOnly;
                    cookie.Expires = expiresDateTime;
    
                    cookieCollection.Add(cookie);
                }
                return cookieCollection;
            }
        }
    }
    

    【讨论】:

    • 您在 '=' 上拆分 cookie 值 - 如果该值是 base64 编码字符串,这可能是一个陷阱
    【解决方案5】:

    使用CookieContainer,如this answer。 对我来说,让这些正则表达式方法出错的是expires=Tue, ... 中的逗号。

    【讨论】:

      【解决方案6】:

      我知道这个问题很老,但我遇到了一些正确解析“Set-Cookie”标头的代码。它处理以逗号分隔的 cookie,并提取每个 cookie 的名称、到期时间、路径、值和域。

      这段代码比微软自己的 cookie 解析器工作得更好,这确实是官方 cookie 解析器应该做的。我不知道为什么微软还没有解决这个问题,因为这是一个非常常见的问题。

      这里是原始代码: http://snipplr.com/view/4427/

      我在这里发布它以防链接在某些时候断开:

      public static CookieCollection GetAllCookiesFromHeader(string strHeader, string strHost)
      {
          ArrayList al = new ArrayList();
          CookieCollection cc = new CookieCollection();
          if (strHeader != string.Empty)
          {
              al = ConvertCookieHeaderToArrayList(strHeader);
              cc = ConvertCookieArraysToCookieCollection(al, strHost);
          }
          return cc;
      }
      
      
      private static ArrayList ConvertCookieHeaderToArrayList(string strCookHeader)
      {
          strCookHeader = strCookHeader.Replace("\r", "");
          strCookHeader = strCookHeader.Replace("\n", "");
          string[] strCookTemp = strCookHeader.Split(',');
          ArrayList al = new ArrayList();
          int i = 0;
          int n = strCookTemp.Length;
          while (i < n)
          {
              if (strCookTemp[i].IndexOf("expires=", StringComparison.OrdinalIgnoreCase) > 0)
              {
                  al.Add(strCookTemp[i] + "," + strCookTemp[i + 1]);
                  i = i + 1;
              }
              else
              {
                  al.Add(strCookTemp[i]);
              }
              i = i + 1;
          }
          return al;
      }
      
      
      private static CookieCollection ConvertCookieArraysToCookieCollection(ArrayList al, string strHost)
      {
          CookieCollection cc = new CookieCollection();
      
          int alcount = al.Count;
          string strEachCook;
          string[] strEachCookParts;
          for (int i = 0; i < alcount; i++)
          {
              strEachCook = al[i].ToString();
              strEachCookParts = strEachCook.Split(';');
              int intEachCookPartsCount = strEachCookParts.Length;
              string strCNameAndCValue = string.Empty;
              string strPNameAndPValue = string.Empty;
              string strDNameAndDValue = string.Empty;
              string[] NameValuePairTemp;
              Cookie cookTemp = new Cookie();
      
              for (int j = 0; j < intEachCookPartsCount; j++)
              {
                  if (j == 0)
                  {
                      strCNameAndCValue = strEachCookParts[j];
                      if (strCNameAndCValue != string.Empty)
                      {
                          int firstEqual = strCNameAndCValue.IndexOf("=");
                          string firstName = strCNameAndCValue.Substring(0, firstEqual);
                          string allValue = strCNameAndCValue.Substring(firstEqual + 1, strCNameAndCValue.Length - (firstEqual + 1));
                          cookTemp.Name = firstName;
                          cookTemp.Value = allValue;
                      }
                      continue;
                  }
                  if (strEachCookParts[j].IndexOf("path", StringComparison.OrdinalIgnoreCase) >= 0)
                  {
                      strPNameAndPValue = strEachCookParts[j];
                      if (strPNameAndPValue != string.Empty)
                      {
                          NameValuePairTemp = strPNameAndPValue.Split('=');
                          if (NameValuePairTemp[1] != string.Empty)
                          {
                              cookTemp.Path = NameValuePairTemp[1];
                          }
                          else
                          {
                              cookTemp.Path = "/";
                          }
                      }
                      continue;
                  }
      
                  if (strEachCookParts[j].IndexOf("domain", StringComparison.OrdinalIgnoreCase) >= 0)
                  {
                      strPNameAndPValue = strEachCookParts[j];
                      if (strPNameAndPValue != string.Empty)
                      {
                          NameValuePairTemp = strPNameAndPValue.Split('=');
      
                          if (NameValuePairTemp[1] != string.Empty)
                          {
                              cookTemp.Domain = NameValuePairTemp[1];
                          }
                          else
                          {
                              cookTemp.Domain = strHost;
                          }
                      }
                      continue;
                  }
              }
      
              if (cookTemp.Path == string.Empty)
              {
                  cookTemp.Path = "/";
              }
              if (cookTemp.Domain == string.Empty)
              {
                  cookTemp.Domain = strHost;
              }
              cc.Add(cookTemp);
          }
          return cc;
      }
      

      【讨论】:

        【解决方案7】:

        网站发送的Set-Cookie 标头似乎格式不正确(不是应有的典型格式)。

        在这种情况下,您需要手动解析 cookie 并将其发送到 CookieContainer

        for (int i = 0; i < b.Headers.Count; i++)
        {
            string name = b.Headers.GetKey(i);
            string value = b.Headers.Get(i);
            if (name == "Set-Cookie")
            {
                Match match = Regex.Match(value, "(.+?)=(.+?);");
                if (match.Captures.Count > 0)
                {
                    reqCookies.Add(new Cookie(match.Groups[1].Value, match.Groups[2].Value, "/", "example.com"));
                }
            }
        }
        

        【讨论】:

        • 我的代码是一个自定义对象,我必须对其进行修改和简化才能在此处发布。抱歉遗漏了一行,我马上修好了。
        • 请看截图。 b.Cookies 不为空,但其中没有 Cookie。 Count = 0 并且枚举没有产生任何结果。
        • 这有效,在我修复 reqCookies.Add 以解析匹配而不是尝试使用标题名称和值之后。我很难相信 cookie 格式错误,因为它在我尝试过的每个浏览器中都能正常工作。这是它可能认为格式错误的 Set-Cookie 值:“s=541E2101-B768-45C8-B814-34A00525E50F; Domain=example.com; Path=/; Version=1” 我真的很想完全理解什么是错误,因为此解决方案只是一种解决方法。
        • @Brad - Set-Cookie 标头的格式与构建 CookieParser 的格式不完全相同!
        • 啊是的,这是真的。但这有点独立于您添加一个名为“Set-Cookie”的 cookie 的事实,不是吗?
        猜你喜欢
        • 2017-07-01
        • 2016-03-05
        • 2018-08-29
        • 2021-03-20
        • 1970-01-01
        • 2016-10-11
        • 2021-01-08
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多