标签:des style blog http java color
【故事背景】:
公司某个站点,特别依赖Cookie的使用,而且用的比较狠。在设计之初想当然地以为到达Cookie上限是猴年马月的事儿,没想到时过境迁,这个上限真的来了。
着手改吧,也不想投入太多。于是下面的思路就涌上心头:
【问题】
目前遇到的瓶颈主要是单个Cookie的尺寸超大,在IE下,没有问题,在Firefox下和Chrome下均出现单个Cookie过大,以至于被丢弃的现象。
【思路】
因此为了不侵入旧代码,打算在站点前加一个HttpModule来切分和拼装Cookie。为了保证一次成型,减少投入,仔细阅读了RFC2109文档/RFC6265(http://www.w3.org/Protocols/rfc2109/rfc2109 或http://www.rfc-editor.org/rfc/rfc6265.txt),下面是文档里关于对浏览器Cookie实现的一些说明:
6.3 Implementation Limits Practical user agent implementations have limits on the number and size of cookies that they can store. In general, user agents‘ cookie support should have no fixed limits. They should strive to store as many frequently-used cookies as possible. Furthermore, general-use user agents should provide each of the following minimum capabilities individually, although not necessarily simultaneously: * at least 300 cookies * at least 4096 bytes per cookie (as measured by the size of the characters that comprise the cookie non-terminal in the syntax description of the Set-Cookie header) * at least 20 cookies per unique host or domain nameUser agents created for specific purposes or for limited-capacity devices should provide at least 20 cookies of 4096 bytes, to ensure that the user can interact with a session-based origin server. The information in a Set-Cookie response header must be retained in its entirety. If for some reason there is inadequate space to store the cookie, it must be discarded, not truncated. Applications should use as few and as small cookies as possible, and they should cope gracefully with the loss of a cookie.
文档中,要求:
至少有300个Cookie总量
每个Cookie至少4096kb
每个域/主机下至少能有20个Cookie
并且还要求:
如果没有什么特殊原因,Cookie就别做啥限制啦!
【实现】
为什么带问号呢?因为这个地方其实最让人头疼。在测试的过程中,我发现Windows下,对Cookie标准理解最正确的是IE,其他浏览器也中规中矩地在做事,顶多是让你做事的时候不得不畏手畏脚。但是iPad则似乎比较调皮,让人捉摸不定。
iPad下的Cookie问题:
现在讲一下我Windows版本的基本实现思路:
但这一切在iPad下,就令人头疼了。因为只有发现超长的时候,才进行拆分,而且同时为了向上兼容,那么两个4096就已经接近其上限了,那么在这样的场景下,再去使用,就会发生Cookie莫名其妙随机丢失的情况,而实验的结果也是如此。因此,可能就需要(还没时间去测试)首先判断浏览器是不是来自于移动设备?iOS?iPad?等,然后再进行Cookie实现,那么这个方案就会更复杂,但这个上限给人的感觉是,跑得了初一跑不了十五。很快这个Cookie值就会穿过枷锁,让你头疼欲裂。
真是一失足成千古恨,任何一个依赖客户端的场景都会因为时间地推移而变得无奈。关于iPad Cookie相关的问题,如果有园友知情的也请不吝留言告知。
附上代码:
1 using System; 2 using System.Collections.Generic; 3 using System.Globalization; 4 using System.Linq; 5 using System.Text; 6 using System.Threading.Tasks; 7 using System.Web; 8 9 namespace LargeCookieModule 10 { 11 public class CookieHelper 12 { 13 /// <summary> 14 /// firefox.total:4145 15 /// chrome.total:4096 16 /// </summary> 17 private const int CONST_MAX_COOKIE_BYTES = 4096; 18 private const string CONST_BEYOND_SEPARATOR = "_C_SPO_"; 19 /// <summary> 20 /// http://www.rfc-editor.org/rfc/rfc6265.txt 21 /// At least 4096 bytes per cookie (as measured by the sum of the length of the cookie‘s name, value, and attributes). 22 /// At least 50 cookies per domain. 23 /// At least 3000 cookies total. 24 /// 目前考虑最多2位数来解决cookie溢出的问题。 25 /// 另CONST_BEYOND_SEPARATOR.Length位用来解决SEPARATOR的问题。 26 /// </summary> 27 private const int CONST_BEYOND_CHARS = (2 + 7); 28 private static System.Text.Encoding defaultEncoding 29 { 30 get 31 { 32 return System.Text.Encoding.UTF8; 33 } 34 } 35 36 public static string GetSetCookieHeaderString(HttpContext context, HttpCookie cookie) 37 { 38 string name = cookie.Name; 39 string value = cookie.Value; 40 string domain = cookie.Domain; 41 DateTime expires = cookie.Expires; 42 string path = cookie.Path; 43 bool secure = cookie.Secure; 44 bool httpOnly = cookie.HttpOnly; 45 46 StringBuilder stringBuilder = new StringBuilder(); 47 if (!string.IsNullOrEmpty(name)) 48 { 49 stringBuilder.Append(name); 50 stringBuilder.Append(‘=‘); 51 } 52 if (!string.IsNullOrEmpty(value)) 53 { 54 stringBuilder.Append(value); 55 } 56 if (!string.IsNullOrEmpty(domain)) 57 { 58 stringBuilder.Append("; domain="); 59 stringBuilder.Append(domain); 60 } 61 if (expires != DateTime.MinValue) 62 { 63 stringBuilder.Append("; expires="); 64 stringBuilder.Append(FormatHttpCookieDateTime(expires)); 65 } 66 if (!string.IsNullOrEmpty(path)) 67 { 68 stringBuilder.Append("; path="); 69 stringBuilder.Append(path); 70 } 71 if (secure) 72 { 73 stringBuilder.Append("; secure"); 74 } 75 if (httpOnly && SupportsHttpOnly(context)) 76 { 77 stringBuilder.Append("; HttpOnly"); 78 } 79 return stringBuilder.ToString(); 80 } 81 82 private static bool SupportsHttpOnly(HttpContext context) 83 { 84 if (context != null && context.Request != null) 85 { 86 HttpBrowserCapabilities browser = context.Request.Browser; 87 return browser != null && (browser.Type != "IE5" || browser.Platform != "MacPPC"); 88 } 89 return false; 90 } 91 92 /// <summary> 93 /// equals to HttpUtility.FormatHttpCookieDateTime 94 /// for java system to rewrite code. 95 /// </summary> 96 /// <param name="dt"></param> 97 /// <returns></returns> 98 private static string FormatHttpCookieDateTime(DateTime dt) 99 { 100 if (dt < DateTime.MaxValue.AddDays(-1.0) && dt > DateTime.MinValue.AddDays(1.0)) 101 { 102 dt = dt.ToUniversalTime(); 103 } 104 return dt.ToString("ddd, dd-MMM-yyyy HH‘:‘mm‘:‘ss ‘GMT‘", DateTimeFormatInfo.InvariantInfo); 105 } 106 107 108 /// <summary> 109 /// 是否是合法的Cookies(目前只考虑长度) 110 /// 目前暂时只知道用System.Text.Encoding.UTF8来解决。无法获取客户端存储的情况。 111 /// </summary> 112 /// <param name="context"></param> 113 /// <param name="cookie"></param> 114 /// <returns></returns> 115 public static bool IsLengthLegalCookie(HttpContext context, HttpCookie cookie) 116 { 117 if (cookie == null) 118 return false; 119 string cookieString = CookieHelper.GetSetCookieHeaderString(context, cookie); 120 121 if (cookieString != null && defaultEncoding.GetByteCount(cookieString) <= CONST_MAX_COOKIE_BYTES) 122 { 123 return true; 124 } 125 126 return false; 127 } 128 129 /// <summary> 130 /// 获取除了Value(name+value)值以外的字符长度 131 /// </summary> 132 /// <param name="context"></param> 133 /// <param name="cookie"></param> 134 /// <returns></returns> 135 private static int GetLengthExceptValue(HttpContext context, HttpCookie cookie) 136 { 137 if (cookie == null) 138 return 0; 139 string cookieString = CookieHelper.GetSetCookieHeaderString(context, cookie); 140 if (!string.IsNullOrEmpty(cookieString)) 141 { 142 int index = cookieString.IndexOf(‘;‘); 143 // if index equals to -1 result is - (-1) - 1. 144 return cookieString.Length - index - 1; 145 } 146 return 0; 147 } 148 149 public static List<HttpCookie> GetCookies(HttpCookieCollection cookies) 150 { 151 Dictionary<string, HttpCookie> resultCookies = new Dictionary<string, HttpCookie>(); 152 SeparatorSortedDictionary<HttpCookie> separatorCookies = new SeparatorSortedDictionary<HttpCookie>(); 153 154 if (cookies != null && cookies.AllKeys != null && cookies.AllKeys.Length > 0) 155 { 156 foreach (string key in cookies.AllKeys) 157 { 158 HttpCookie cookie = cookies[key]; 159 if (cookie != null && !string.IsNullOrEmpty(cookie.Name)) 160 { 161 int separatorIndex = cookie.Name.IndexOf(CONST_BEYOND_SEPARATOR); 162 if (separatorIndex != -1) 163 { 164 string prefixName = cookie.Name.Substring(0, separatorIndex); 165 string sOrder = cookie.Name.Substring(prefixName.Length + CONST_BEYOND_SEPARATOR.Length); 166 int iOrder = 0; 167 if (int.TryParse(sOrder, out iOrder)) 168 { 169 separatorCookies.Add(prefixName, iOrder, cookie); 170 } 171 } 172 else 173 { 174 resultCookies[cookie.Name] = cookie; 175 } 176 } 177 } 178 List<string> separatorKeys = separatorCookies.Keys; 179 foreach (string key in separatorKeys) 180 { 181 if(resultCookies.ContainsKey(key)) 182 continue; 183 HttpCookie joinCookie = separatorCookies.Join(key, 184 (c) => c.Value, 185 (s, c) => { c.Value = s; return c; }, 186 CloneHttpCookie); 187 resultCookies[key] = joinCookie; 188 } 189 } 190 return resultCookies.Values.ToList(); 191 } 192 193 public static List<HttpCookie> GetSetCookies(HttpContext context, HttpCookie cookie) 194 { 195 if (IsLengthLegalCookie(context, cookie)) 196 { 197 List<HttpCookie> cookies = new List<HttpCookie>(); 198 cookies.Add(cookie); 199 return cookies; 200 } 201 else 202 { 203 string name = cookie.Name; 204 string value = cookie.Value; 205 int maxValueCount = CONST_MAX_COOKIE_BYTES - CONST_BEYOND_CHARS - GetLengthExceptValue(context, cookie) - name.Length - 1/*name=value后面的分号*/; 206 if (maxValueCount > 0) 207 { 208 byte[] bValue = defaultEncoding.GetBytes(value); 209 int iEachChunk = 0, iChunkNum, iChunkCount = 0; 210 SortedList<int, byte[]> chunks = new SortedList<int, byte[]>(); 211 iChunkCount = bValue.Length / maxValueCount + 1; 212 int iChunkLastIndex = iChunkCount-1; 213 for (iChunkNum = 0; iChunkNum < iChunkCount; ++iChunkNum) 214 { 215 byte[] chunk = null; 216 if (iChunkNum != iChunkLastIndex) 217 { 218 chunk = new byte[maxValueCount]; 219 } 220 else 221 { 222 chunk = new byte[bValue.Length % maxValueCount]; 223 } 224 for (iEachChunk = 0; iEachChunk < chunk.Length; ++iEachChunk) 225 { 226 chunk[iEachChunk] = bValue[iChunkNum * maxValueCount + iEachChunk]; 227 } 228 chunks.Add(iChunkNum, chunk); 229 } 230 List<HttpCookie> cookies = new List<HttpCookie>(); 231 foreach (KeyValuePair<int, byte[]> item in chunks) 232 { 233 string itemName = name + CONST_BEYOND_SEPARATOR + item.Key.ToString(); 234 string itemValue = defaultEncoding.GetString(item.Value); 235 HttpCookie cloneCookie = CloneHttpCookie(cookie); 236 cloneCookie.Name = itemName; 237 cloneCookie.Value = itemValue; 238 cookies.Add(cloneCookie); 239 } 240 return cookies; 241 } 242 } 243 return null; 244 } 245 246 public static bool IsSeparatorCookie(HttpCookie cookie) 247 { 248 if (cookie == null) 249 return false; 250 if (!string.IsNullOrEmpty(cookie.Name) && cookie.Name.Contains(CONST_BEYOND_SEPARATOR)) 251 { 252 return true; 253 } 254 return false; 255 } 256 257 private static HttpCookie CloneHttpCookie(HttpCookie cookie) 258 { 259 HttpCookie cloneCookie = new HttpCookie(cookie.Name); 260 cloneCookie.Value = cookie.Value; 261 cloneCookie.Path = cookie.Path; 262 cloneCookie.Secure = cookie.Secure; 263 cloneCookie.HttpOnly = cookie.HttpOnly; 264 cloneCookie.Domain = cookie.Domain; 265 cloneCookie.Expires = cookie.Expires; 266 return cloneCookie; 267 } 268 269 public static void FilterRequestCookies(HttpContext context) 270 { 271 if (context != null) 272 { 273 IList<HttpCookie> cookies = CookieHelper.GetCookies(context.Request.Cookies); 274 if (cookies != null) 275 { 276 foreach (HttpCookie subCookie in cookies) 277 { 278 if (context.Request.Cookies[subCookie.Name] == null) 279 { 280 context.Request.Cookies.Add(subCookie); 281 } 282 } 283 } 284 } 285 } 286 287 public static void FilterResponseCookies(HttpContext context) 288 { 289 if (context != null && context.Response.Cookies != null && context.Response.Cookies.Keys.Count > 0) 290 { 291 int keyCount = context.Response.Cookies.Keys.Count; 292 if (keyCount > 0) 293 { 294 string[] keys = new string[keyCount]; 295 for (int i = 0; i < keyCount; ++i) 296 { 297 keys[i] = context.Response.Cookies.Keys[i]; 298 } 299 300 foreach (string key in keys) 301 { 302 HttpCookie everyCookie = context.Response.Cookies[key]; 303 304 IList<HttpCookie> cookies = CookieHelper.GetSetCookies(context, everyCookie); 305 if (cookies != null && cookies.Count > 1 /*means it is separator cookie or the raw cookie is not legal in length.*/) 306 { 307 foreach (HttpCookie subCookie in cookies) 308 { 309 if (IsSeparatorCookie(subCookie)) 310 { 311 context.Response.AppendCookie(subCookie); 312 } 313 } 314 } 315 } 316 } 317 } 318 } 319 320 internal class SeparatorSortedDictionary<T> : Dictionary<string, SortedList<int, T>> 321 { 322 Dictionary<string, SortedList<int, T>> _innerStore = new Dictionary<string, SortedList<int, T>>(); 323 public void Add(string name, int index, T value) 324 { 325 if (_innerStore != null) 326 { 327 if (!_innerStore.ContainsKey(name)) 328 _innerStore[name] = new SortedList<int, T>(); 329 SortedList<int, T> itemCollection = _innerStore[name]; 330 if (!itemCollection.ContainsKey(index)) 331 { 332 itemCollection.Add(index, value); 333 } 334 } 335 } 336 337 public T Join(string name, Func<T, string> toStringFunc, Func<string, T, T> fromStringFunc, Func<T, T> cloneFunc) 338 { 339 if (_innerStore != null) 340 { 341 if (_innerStore.ContainsKey(name)) 342 { 343 SortedList<int, T> itemCollection = _innerStore[name]; 344 string value = string.Empty; 345 T firstElement = default(T); 346 bool getFirstElement = false; 347 foreach (T itemValue in itemCollection.Values) 348 { 349 if (!getFirstElement) 350 { 351 firstElement = itemValue; 352 getFirstElement = true; 353 } 354 value += toStringFunc(itemValue); 355 } 356 T cloneItem = cloneFunc(firstElement); 357 cloneItem = fromStringFunc(value, cloneItem); 358 return cloneItem; 359 } 360 } 361 return default(T); 362 } 363 364 public List<string> Keys 365 { 366 get 367 { 368 List<string> result = new List<string>(); 369 if (_innerStore != null) 370 { 371 foreach (string key in _innerStore.Keys) 372 { 373 result.Add(key); 374 } 375 } 376 return result; 377 } 378 } 379 } 380 } 381 }
调用代码:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 using System.Web; 7 8 namespace LargeCookieModule 9 { 10 public class LargeCookieModule : IHttpModule 11 { 12 public void Dispose() 13 { 14 throw new NotImplementedException(); 15 } 16 17 public void Init(HttpApplication context) 18 { 19 context.BeginRequest += context_BeginRequest; 20 context.EndRequest += context_EndRequest; 21 } 22 23 void context_BeginRequest(object sender, EventArgs e) 24 { 25 HttpApplication app = sender as HttpApplication; 26 if (app != null) 27 { 28 if (app.Context.Request.ContentEncoding != null) 29 app.Context.Response.Write("app.Context.Request.ContentEncoding:" + app.Context.Request.ContentEncoding.ToString() + "<br />"); 30 31 app.Context.Response.Write("<br />-----------(start)raw request cookies, context_BeginRequest:--------------<br />"); 32 33 foreach (string key in app.Request.Cookies.AllKeys) 34 { 35 HttpCookie cookie = app.Request.Cookies[key]; 36 if (cookie != null) 37 { 38 string cookieString = CookieHelper.GetSetCookieHeaderString(app.Context, cookie); 39 app.Context.Response.Write("context_BeginRequest:" + cookieString + "<br />"); 40 app.Context.Response.Write("context_BeginRequest.cookieLength:" + cookieString.Length + "<br />"); 41 app.Context.Response.Write("context_BeginRequest.GetByteCount:" + System.Text.Encoding.UTF8.GetByteCount(cookieString) + "<br />"); 42 } 43 } 44 45 app.Context.Response.Write("<br />-----------(end)raw request cookies, context_BeginRequest:--------------<br /><br />"); 46 47 app.Context.Response.Write("<br />-----------(start)merged by CookieHelper.cs, context_BeginRequest:--------------<br />"); 48 // 唯一一句用来过滤 49 CookieHelper.FilterRequestCookies(app.Context); 50 foreach (string key in app.Request.Cookies.AllKeys) 51 { 52 HttpCookie cookie = app.Request.Cookies[key]; 53 if (cookie != null) 54 { 55 string cookieString = CookieHelper.GetSetCookieHeaderString(app.Context, cookie); 56 app.Context.Response.Write("context_BeginRequest:" + cookieString + "<br />"); 57 app.Context.Response.Write("context_BeginRequest.cookieLength:" + cookieString.Length + "<br />"); 58 app.Context.Response.Write("context_BeginRequest.GetByteCount:" + System.Text.Encoding.UTF8.GetByteCount(cookieString) + "<br />"); 59 } 60 } 61 app.Context.Response.Write("<br />-----------(end)merged by CookieHelper.cs, context_BeginRequest:--------------<br /><br />"); 62 } 63 } 64 65 void context_EndRequest(object sender, EventArgs e) 66 { 67 //HttpApplication app = sender as HttpApplication; 68 //if (app != null) 69 //{ 70 // // CookieHelper.FilterResponseCookies(app.Context); 71 // app.Context.Response.Write("<br />-----------(start)response the merged cookies to the client, context_EndRequest:--------------<br />"); 72 // foreach (string key in app.Response.Cookies.AllKeys) 73 // { 74 // HttpCookie cookie = app.Response.Cookies[key]; 75 // if (cookie != null) 76 // { 77 // IList<HttpCookie> cookies = CookieHelper.GetSetCookies(app.Context, cookie); 78 // if (cookies != null) 79 // { 80 // foreach (HttpCookie subCookie in cookies) 81 // { 82 // string cookieString = CookieHelper.GetSetCookieHeaderString(app.Context, subCookie); 83 // app.Context.Response.AppendCookie(subCookie); 84 // app.Context.Response.Write("context_EndRequest:" + cookieString + "<br />"); 85 // app.Context.Response.Write("context_EndRequest.cookieLength:" + cookieString.Length + "<br />"); 86 // app.Context.Response.Write("context_EndRequest.GetByteCount:" + System.Text.Encoding.UTF8.GetByteCount(cookieString) + "<br />"); 87 // } 88 // } 89 // } 90 // } 91 // app.Context.Response.Write("<br />-----------(end)response the merged cookies to the client, context_EndRequest:--------------<br /><br />"); 92 //} 93 94 HttpApplication app = sender as HttpApplication; 95 if (app != null) 96 { 97 app.Context.Response.Write("<br />-----------(start)response the all cookies to the client, context_EndRequest:--------------<br />"); 98 99 CookieHelper.FilterResponseCookies(app.Context); 100 101 List<HttpCookie> reverseList = new List<HttpCookie>(); 102 List<string> keys = app.Response.Cookies.AllKeys.ToList(); 103 foreach (string key in keys) 104 { 105 HttpCookie cookie = app.Response.Cookies[key]; 106 if (cookie != null) 107 { 108 reverseList.Add(cookie); 109 string cookieString = CookieHelper.GetSetCookieHeaderString(app.Context, cookie); 110 app.Context.Response.Write("context_EndRequest:" + cookieString + "<br />"); 111 app.Context.Response.Write("context_EndRequest.cookieLength:" + cookieString.Length + "<br />"); 112 app.Context.Response.Write("context_EndRequest.GetByteCount:" + System.Text.Encoding.UTF8.GetByteCount(cookieString) + "<br />"); 113 } 114 } 115 116 app.Context.Response.Write("<br />-----------(end)response the all cookies to the client, context_EndRequest:--------------<br />"); 117 } 118 } 119 } 120 }
其中BeginRequest和EndRequest中其实只需要调用:
CookieHelper.FilterRequestCookies(app.Context);
CookieHelper.FilterResponseCookies(app.Context);
以上其余代码都是用来测试的。
另外附上iPad测试的代码:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Web; 5 using System.Web.UI; 6 using System.Web.UI.WebControls; 7 8 namespace TestIPadCookie 9 { 10 public partial class Default : System.Web.UI.Page 11 { 12 protected void Page_Load(object sender, EventArgs e) 13 { 14 for (int j = 0; j < 30; ++j) 15 { 16 string text = string.Empty; 17 string cookieString = string.Empty; 18 HttpCookie cookie = new HttpCookie("ipadTest" + j.ToString().PadLeft(2, ‘0‘)); 19 cookie.Expires = DateTime.Now.AddDays(1); 20 for (int i = 0; i < 10000; ++i) 21 { 22 text += ‘c‘; 23 cookie.Value = text; 24 cookieString = LargeCookieModule.CookieHelper.GetSetCookieHeaderString(HttpContext.Current, cookie); 25 if (System.Text.Encoding.UTF8.GetByteCount(cookieString) >= 317) 26 { 27 break; 28 } 29 } 30 HttpContext.Current.Response.AppendCookie(cookie); 31 HttpContext.Current.Response.Write("cookieLength=" + cookieString.Length.ToString() + "<br />"); 32 HttpContext.Current.Response.Write("cookieValueLength=" + text.Length.ToString() 33 + ", gap = " + Math.Abs(cookieString.Length - text.Length) + "<br />"); 34 HttpContext.Current.Response.Write("cookieString=" + cookieString + "<br />"); 35 HttpContext.Current.Response.Write("<br />"); 36 } 37 } 38 } 39 }
源代码打包下载:(点击这里)
http://files.cnblogs.com/volnet/WebAppLargeCookieModule.zip
iPad上的Cookie到底有多长?,布布扣,bubuko.com
标签:des style blog http java color
原文地址:http://www.cnblogs.com/volnet/p/ipad-cookie-length.html