C# HttpWebResponse.Cookies 空,尽管 Set-Cookie 标头(无重定向)
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/15103513/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
HttpWebResponse.Cookies empty despite Set-Cookie Header (no-redirect)
提问by Brad
I'm struggling to figure out what is wrong here. I'm sending login information, I can see the Set-Cookie in the Header with the correct value, but the Cookies collection is not getting filled.
我正在努力弄清楚这里出了什么问题。我正在发送登录信息,我可以看到 Header 中的 Set-Cookie 具有正确的值,但 Cookies 集合没有被填充。
This is HTTPS, the login auto-redirects, but I disabled it with AllowAutoRedirect=false to try to troubleshoot this issue.
这是 HTTPS,登录自动重定向,但我使用 AllowAutoRedirect=false 禁用它以尝试解决此问题。
In this screenshot, you can easily see the debug information and that the cookie should be getting set. I am setting my httpWebRequest.Cookie to a new CookieCollection.
在此屏幕截图中,您可以轻松查看调试信息以及应该设置 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();
Tried the exact same code connecting to http://www.yahoo.comand the cookies are put into my collection... Argh...
尝试连接到http://www.yahoo.com的完全相同的代码,并将 cookie 放入我的收藏中...唉...
Here is the Set-Cookie Header value:
这是 Set-Cookie 标头值:
s=541E2101-B768-45C8-B814-34A00525E50F; Domain=example.com; Path=/; Version=1
s=541E2101-B768-45C8-B814-34A00525E50F;域=example.com; 路径=/;版本=1
采纳答案by Nicolas78
UPDATEfive years later, someone actually mentioned the correct way to do it: setting up the CookieContainer correctly in the first place and letting it handle everything. Please refer to Sam's solution further down.
UPDATE五年后,有人竟提到了正确的方式做到这一点:首先正确设置的CookieContainer并让它处理一切。请进一步参考 Sam 的解决方案。
I've found that issue as well, when reading Cookies in C# that were created by a C# ASP.NET app... ;)
在读取由 C# ASP.NET 应用程序创建的 C# Cookie 时,我也发现了这个问题......;)
Not sure if it has to do with it, but I found that the two Cookies that are set in my case are written in a single Set-Cookie header, with the cookie payload separated by commas. So I adapted AppDeveloper's solution to deal with this multiple-cookie issue, as well as fixing the name/value thing I mentioned in the comments.
不确定它是否与它有关,但我发现在我的情况下设置的两个 Cookie 写在单个 Set-Cookie 标头中,cookie 负载以逗号分隔。所以我调整了 AppDeveloper 的解决方案来处理这个多 cookie 问题,并修复我在评论中提到的名称/值问题。
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]));
}
}
}
回答by Parimal Raj
It seems like Set-Cookie
header sent by the website is malformed (Not in the typical format it should have been).
Set-Cookie
网站发送的标头似乎格式不正确(不是它应该的典型格式)。
In such case you need to Parse cookie manually and it it to the CookieContainer
.
在这种情况下,您需要手动解析 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"));
}
}
}
回答by Cameron Tinker
I know that this question is old, but I came across some code that properly parses a "Set-Cookie" header. It handles cookies separated by commas and extracts the name, expiration, path, value, and domain of each cookie.
我知道这个问题很老,但我遇到了一些正确解析“Set-Cookie”标头的代码。它处理以逗号分隔的 cookie,并提取每个 cookie 的名称、过期时间、路径、值和域。
This code works better than Microsoft's own cookie parser and this is really what the official cookie parser should be doing. I don't have any clue why Microsoft hasn't fixed this yet since it's a very common issue.
这段代码比微软自己的 cookie 解析器工作得更好,这才是官方 cookie 解析器应该做的。我不知道为什么微软还没有解决这个问题,因为这是一个非常普遍的问题。
Here is the original code: http://snipplr.com/view/4427/
这是原始代码:http: //snipplr.com/view/4427/
I'm posting it here in case the link goes down at some point:
我将其发布在这里以防链接在某些时候断开:
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;
}
回答by James
Use a CookieContainer
as in this answer.
What tripped these regex approaches up for me was a comma in expires=Tue, ...
.
使用 a CookieContainer
as in this answer。对我来说,这些正则表达式方法的绊脚石是expires=Tue, ...
.
回答by Anatolii Humennyi
Looking on other answers I improved incorrect cookie handling. Unlike those answers this one automatically handles all cookie properties (such as expired, secure, etc.) and works with all range of cookies (even when there are more than 1 incorrect cookie).
查看其他答案,我改进了不正确的 cookie 处理。与那些答案不同,这个答案会自动处理所有 cookie 属性(例如过期、安全等)并适用于所有范围的 cookie(即使有 1 个以上不正确的 cookie)。
It's implemented as extension method and can be used in the following way:
它是作为扩展方法实现的,可以通过以下方式使用:
//...
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
{
request.FixCookies(response);
//...
FixCookies()
extension method:
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;
}
}
}
回答by ASpirin
That could be a bit late, but you can use function SetCookies
这可能有点晚了,但您可以使用函数 SetCookies
var cHeader = responce.Headers.Get("Set-Cookie");
var cookie = new CookieContainer();
cookie.SetCookies(new Uri("[...]"), cHeader);
回答by Sam
The proper way to do this is to set the CookieContainer
member before retrieving the response:
正确的方法是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.
You don't need to manually parse SetCookie
.
您无需手动解析SetCookie
.