(转)细说Cookie

细说Cookie

阅读目录

开始

Cookie 概述

Cookie的写、读过程

使用Cookie保存复杂对象

Js中读写Cookie

Cookie在Session中的应用

Cookie在身份验证中的应用

Cookie的安全状况

如何在C#发请的请求中使用Cookie

重构与使用总结

补充

Cookie虽然是个很简单的东西,但它又是WEB开发中一个很重要的客户端数据来源,而且它可以实现扩展性很好的会话状态,

所以我认为每个WEB开发人员都有必要对它有个清晰的认识。本文将对Cookie这个话题做一个全面的描述,

也算是我对Cookie的认识总结。

回到顶部

Cookie 概述

Cookie是什么?Cookie 是一小段文本信息,伴随着用户请求和页面在 Web 服务器和浏览器之间传递。Cookie 包含每次用户访问站点时 Web 应用程序都可以读取的信息。

为什么需要Cookie?因为HTTP协议是无状态的,对于一个浏览器发出的多次请求,WEB服务器无法区分是不是来源于同一个浏览器。所以,需要额外的数据用于维护会话。Cookie 正是这样的一段随HTTP请求一起被传递的额外数据。

Cookie能做什么?Cookie只是一段文本,所以它只能保存字符串。而且浏览器对它有大小限制以及它会随着每次请求被发送到服务器,所以应该保证它不要太大。Cookie的内容也是明文保存的,有些浏览器提供界面修改,所以,不适合保存重要的或者涉及隐私的内容。

Cookie 的限制。大多数浏览器支持最大为 4096 字节的 Cookie。由于这限制了 Cookie 的大小,最好用 Cookie 来存储少量数据,或者存储用户 ID 之类的标识符。用户 ID 随后便可用于标识用户,以及从数据库或其他数据源中读取用户信息。浏览器还限制站点可以在用户计算机上存储的 Cookie 的数量。大多数浏览器只允许每个站点存储 20 个 Cookie;如果试图存储更多 Cookie,则最旧的 Cookie 便会被丢弃。有些浏览器还会对它们将接受的来自所有站点的 Cookie 总数作出绝对限制,通常为 300 个。

通过前面的内容,我们了解到Cookie是用于维持服务端会话状态的,通常由服务端写入,在后续请求中,供服务端读取。

下面本文将按这个过程看看Cookie是如何从服务端写入,最后如何传到服务端以及如何读取的。

回到顶部

Cookie的写、读过程

在Asp.net中,读写Cookie是通过使用HttpCookie类来完成的,它的定义如下:

public sealed classHttpCookie{// 获取或设置将此 Cookie 与其关联的域。默认值为当前域。public stringDomain {get;set; }// 获取或设置此 Cookie 的过期日期和时间(在客户端)。publicDateTimeExpires {get;set; }// 获取一个值,通过该值指示 Cookie 是否具有子键。public boolHasKeys {get; }// 获取或设置一个值,该值指定 Cookie 是否可通过客户端脚本访问。

// 如果 Cookie 具有 HttpOnly 属性且不能通过客户端脚本访问,则为 true;否则为 false。默认为 false。public boolHttpOnly {get;set; }// 获取或设置 Cookie 的名称。public stringName {get;set; }// 获取或设置要与当前 Cookie 一起传输的虚拟路径。默认值为当前请求的路径。public stringPath {get;set; }// 获取或设置一个值,该值指示是否使用安全套接字层 (SSL)(即仅通过 HTTPS)传输 Cookie。public boolSecure {get;set; }// 获取或设置单个 Cookie 值。默认值为空引用。public stringValue {get;set; }// 获取单个 Cookie 对象所包含的键值对的集合。publicNameValueCollection Values {get; }// 获取 System.Web.HttpCookie.Values 属性的快捷方式。public string this[stringkey] {get;set; }}

Cookie写入浏览器的过程:我们可以使用如下代码在Asp.net项目中写一个Cookie 并发送到客户端的浏览器(为了简单我没有设置其它属性)。

HttpCookiecookie=newHttpCookie("MyCookieName","string value");Response.Cookies.Add(cookie);

我想很多人都写过类似的代码,但是,大家有没有想过:Cookie最后是如何发送到客户端的呢?我们打开Fiddler来看一下吧。

从上图,您应该能发现,我们在服务端写的Cookie,最后其实是通过HTTP的响应头这种途径发送到客户端的。每一个写入动作,都会产生一个【Set-Cookie】的响应头。

浏览器正是在每次获取请求的响应后,检查这些头来接收Cookie的。

Asp.net获取Cookie的过程:我们可以使用如下代码在Asp.net项目中读取一个Cookie

HttpCookiecookie=Request.Cookies["MyCookieName"];if( cookie!=null)    labCookie1.Text=cookie.Value;elselabCookie1.Text="未定义";

代码同样也很简单,还是类似的问题:大家有没有想过,Cookie是如何传到服务端的呢?我们还是继续使用Fiddler来寻找答案吧。

从图片中,我们可以发现,Cookie是放在请求头中,发送到服务端的。如果你一直刷新页面,就能发现,每次HTTP请求,Cookie都会被发送。当然了,浏览器也不是发送它所接收到的所有Cookie,它会检查当前要请求的域名以及目录,只要这二项目与Cookie对应的Domain和Path匹配,才会发送。对于Domain则是按照尾部匹配的原则进行的。

所以,我在访问 www.cnblogs.com 时,浏览器并不会将我在浏览 www.163.com 所接收到的 Cookie 发出去。

删除Cookie:其实就是在写Cookie时,设置Expires为一个【早于现在时间的时间】。也就是:设置此Cookie已经过期,浏览器接收到这个Cookie时,便会删除它们。

HttpCookiecookie=newHttpCookie("MyCookieName",null);cookie.Expires=newDateTime(1900,1,1);Response.Cookies.Add(cookie);

回到顶部

使用Cookie保存复杂对象

前面的示例代码大致演示了Cookie的读写操作。不过,我们平时可能希望将更复杂的【自定义类型】通过Cookie来保存,

那么又该如何操作呢?对于这个问题,我们定义一个类型来看看如何处理。

public classDisplaySettings{public intStyle;public intSize;public override stringToString()    {return string.Format("Style = {0}, Size = {1}",this.Style,this.Size);    }    }

上面的代码,我定义一个类型,用于保存用户在浏览页面时的显示设置。接下来,我将介绍二种方法在Cookie中保存并读取它们。

方法-1,经典做法。(注意前面给出的HttpCookie定义代码中的最后二个成员)

private voidWriteCookie_2a(){DisplaySettingssetting=newDisplaySettings{ Style=1, Size=24};HttpCookiecookie=newHttpCookie("DisplaySettings1");    cookie["Style"]=setting.Style.ToString();    cookie["Size"]=setting.Size.ToString();    Response.Cookies.Add(cookie);}private voidReadCookie_2a(){HttpCookiecookie=Request.Cookies["DisplaySettings1"];if( cookie==null)        labDisplaySettings1.Text="未定义";else{DisplaySettingssetting=newDisplaySettings();        setting.Style=cookie["Style"].TryToInt();        setting.Size=cookie["Size"].TryToInt();        labDisplaySettings1.Text=setting.ToString();    }}

方法-2,将对象JSON序列化为字符串。

private voidWriteCookie_2b(){DisplaySettingssetting=newDisplaySettings{ Style=2, Size=48};HttpCookiecookie=newHttpCookie("DisplaySettings2", setting.ToJson());    Response.Cookies.Add(cookie);}private voidReadCookie_2b(){HttpCookiecookie=Request.Cookies["DisplaySettings2"];if( cookie==null)        labDisplaySettings2.Text="未定义";else{DisplaySettingssetting=cookie.Value.FromJson();        labDisplaySettings2.Text=setting.ToString();    }}

这段代码使用了我定义的二个扩展方法。

///

///将一个对象序列化成 JSON 格式字符串///

///

/// public static stringToJson(this objectobj){if( obj==null)return string.Empty;JavaScriptSerializerjss=newJavaScriptSerializer();returnjss.Serialize(obj);}///

///从JSON字符串中反序列化对象///

///

///

/// public staticT FromJson(this stringcookie){if(string.IsNullOrEmpty(cookie) )return default(T);JavaScriptSerializerjss=newJavaScriptSerializer();returnjss.Deserialize(cookie);}

对于这二种方法,我个人更喜欢后者,因为它具有更好扩展性:如果类型增加了成员,不需要修改读写Cookie的代码。

不过,这种方式产生的有些字符,比如【双引号】,极少数浏览器(Opera)不支持,所以需要做UrlEncode或者Base64编码处理。

同理,对于第一种方法,遇到Value有【双引号】时,我们同样需要做UrlEncode或者Base64编码处理。

回到顶部

Js中读写Cookie

Cookie并非只能在服务端读写,在客户端的浏览器中也可以实现对它的读写访问。而且在JS中创建的Cookie对于服务端仍然有效(可见),

接下来我们来看看在JS中如何写入Cookie,演示代码将创建一个按钮,并在点击按钮后写入Cookie

functionWriteCookie() {varcookie="cookie_js=22222222; path=/";        document.cookie=cookie;    }

在JS中写Cookie很简单,只要给document.cookie赋值一个Cookie字符串即可,至于格式,可以参考前面用Fiddle看到的结果。

再来看一下如何使用JS读取Cookie吧。请参考如下代码:

functionReadCookie() {        alert(document.cookie);    }

仍然是访问document.cookie,不过,这次我们得到却是全部的Cookie值,每个Key/Value项用分号分开,中间则用等号分开。 所以,如果您想在JS中读取Cookie,一定要按照这个规则来拆分并解析您要读取的Cookie项。鉴于这样的操作有些繁琐,我们可以jquery.cookie.js插件来轻松完成这个功能,有兴趣的朋友也可以看一下它是如何处理的。这个插件的代码比较少,这里就直接贴出,

注意哦:前面我们看到了HttpCookie有个HttpOnly属性,如果它为true,那么JS是读不到那个Cookie的,也就是说:

我们如果在服务端生成的Cookie不希望在JS中能被访问,可以在写Cookie时,设置这个属性。不过,通过一些工具,还是可以看到它们。

接下来,我们再来看看Asp.net中Cookie有哪些应用。

回到顶部

Cookie在Session中的应用

在Asp.net中,HttpContext, Page对象都有个Session的对象,我们可以使用它来方便地在服务端保存一些与会话相关的信息。

前面我们也提到过,HTTP协议是无状态的,对于一个浏览器发出的多次请求,WEB服务器无法区分 是不是来源于同一个浏览器。所以,为了实现会话,服务端需要一个会话标识ID能保存到浏览器,让它在后续的请求时都带上这个会话标识ID,以便让服务端知道某个请求属于哪个会话,这样便可以维护与会话相关的状态数据。由于Cookie对于用户来说,是个不可见的东西,而且每次请求都会传递到服务端,所以它就是很理想的会话标识ID的保存容器。在Asp.net中,默认也就是使用Cookie来保存这个ID的。注意:虽然Asp.net 2.0也支持无Cookie的会话,但那种方式要修改URL,也有它的缺点,因此这种方法并没有广泛的使用。本文将不对这个话题做过多的分析,就此略过无Cookie会话这种方式。

我们来看看Session是如何使用Cookie来保存会话标识ID的,在默认的Asp.net配置中,Web.config有着如下定义:

如果我们执行以下操作:

Session["Key1"]=DateTime.Now;

此时,我们可以使用一些浏览器提供的工具来查看一下现在的Cookie情况。

从图片上看,这个Cookie的名字就是我们在配置文件中指出的名称,我们可以修改一下配置文件:

再来执行上面的写Session的操作,然后看Cookie

我们可以看到:SK的Cookie出现了。说明:在截图时我把名称为"ASP.NET_SessionId"的Cookie删除了。

通过上面示例,我们可以得到结论,Session的实现是与Cookie有关的,服务端需要将会话标识ID保存到Cookie中。

这里再一次申明,除非你使用无Cookie的会话模式,否则Session是需要Cookie的支持。反过来,Cookie并不需要Session的支持。

回到顶部

Cookie在身份验证中的应用

我想很多人都在Asp.net的开发中使用过Form身份认证。对于一个用户请求,

我们可以在服务端很方便地判断它是不是代表一个已登录用户。

this.labStatus.Text=(Request.IsAuthenticated?"已登录":"未登录");

那么,您有没有好奇过:Asp.net是如何识别一个请求是不是一个已登录用户发起的呢?说到这里,我们就要从用户登录说起了。

为了实现登录及Form认证方式,我们需要如下配置:

接下来,我们需要实现用户登录逻辑。具体实现方式有很多,不过,最终的调用都是差不多的,如下代码所示:

private voidSetLogin(){    System.Web.Security.FormsAuthentication.SetAuthCookie("fish",false);}

只要执行了以上代码,我们就可以看到,前面的判断【Request.IsAuthenticated】返回true,最终会显示"已登录"。

为了探寻这个秘密,我们还是来看一下当前页面的Cookie情况。

果然,多出来一个Cookie,名称与我在配置文件中指定的名称相同。我们再来看看如果注销当前登录会是什么样子的:

private voidSetLogout(){    System.Web.Security.FormsAuthentication.SignOut();}

看到了吗,名为"UserStatus"的Cookie不见了。此时如果你再去观察【Request.IsAuthenticated】,可以发现它此时返回 false。或者,您也可以再试一次,登录后,直接删除名为"UserStatus"的Cookie,也能发现登录状态将显示"未登录"。或许,您还是有点不清楚前面我调用【System.Web.Security.FormsAuthentication.SetAuthCookie("fish", false);】后,Asp.net做了些什么,回答这个问题其实很简单:自己用Reflector.exe去看一下Asp.net的实现吧。

这里为了更让您能信服登录与Cookie有关,我将直接创建一个Cookie看一下Asp.net能不能认可我创建的Cookie,并认为登录有效。请看代码:

private voidSetLogin(){//System.Web.Security.FormsAuthentication.SetAuthCookie("fish", false);

// 下面的代码和上面的代码在作用上是等效的。FormsAuthenticationTicketticket=newFormsAuthenticationTicket(2,"fish",DateTime.Now,DateTime.Now.AddDays(30d),false,string.Empty);stringstr=FormsAuthentication.Encrypt(ticket);HttpCookiecookie=newHttpCookie(FormsAuthentication.FormsCookieName, str);    Response.Cookies.Add(cookie);}

如果执行这段代码,您将发现:【Request.IsAuthenticated】返回true,登录状态会显示"已登录"。

至此,我们可以得出一个结论:Form身份认证依赖Cookie,Asp.net就是每次检查我们在配置文件中指定的Cookie名称,并解密这个Cookie来判断当前请求用户的登录状态。

回到顶部

Cookie的安全状况

从以上图片,您应该能发现:浏览器能提供一些界面让用户清楚的观察我们在服务端写的Cookie,

甚至有些浏览器还提供很方便的修改功能。如下图所示:

所以,我们在服务端写代码读取Cookie时,尤其是涉及类型转换、反序列化或者解密时,一定要注意这些操作都有可能会失败。

而且上图也清楚的反映了一个事实:Cookie中的值都是“一目了然”的,任何人都能看到它们。所以,我们尽量不要直接在Cookie中

保存一些重要的或者敏感的内容。如果我们确实需要使用Cookie保存一些重要的内容,但又不希望被他人看懂,

我们可以使用一些加密的方法来保护这些内容。

1. 对于一些重要性不高的内容,我们可以使用Base64之类的简单处理方式来处理。

2. 对于重要性相对高一点的内容,我们可以利用.net提供的一些加密工具类,自己来设计加密方法来保护。不过,密码学与加密解密并不是很简单的算法,因此,自己设计的加密方式可能不会很安全。

3. 重要的内容,我们可以使用.net提供的FormsAuthenticationTicket,FormsAuthentication来加密。我认为这种方式还是比较安全的。毕竟前面我们也看过了,Asp.net的Form身份认证就是使用这种方式来加密用户登录的身份标识的,所以,如果这种方式不安全,也就意味着Asp.net的身份认证也不安全了。如果您使用这种方式来加密,那么请注意:它产生的加密后文本还是比较大的,前面我也提到过,每次请求时,浏览器都会带上与请求相匹配的所有Cookie,因此,这种Cookie会对传输性能产生一定的影响,所以,请小心使用,切记不可过多的使用。

这里要补充一下:去年曾经出现过【Padding Oracle Attack】这个话题,一些人甚至错误的认为是Asp.net加密方式不安全!如果您也是这样认为的,那么可以看一下这篇文章:浅谈这次ASP.NET的Padding Oracle Attack相关内容,以消除这个错误的认识。当然了,我们也可以从这个话题得到一些收获:解密失败时,不要给出过多的提示,就当没有这个Cookie存在。

回到顶部

如何在C#发请的请求中使用Cookie

前面我们一直在谈服务端与浏览器中使用Cookie,其实浏览器也是一个普通的应用程序,.net framework也提供一些类也能让我们

直接发起HTTP请求,下面我们来看一下如何在C#发请的请求中使用Cookie ,其实也很简单,主要是使用了CookieContainer类,请看以下演示代码:

private static stringSendHttpRequestGet(stringurl,Encodingencoding,CookieContainercookieContainer)    {if(string.IsNullOrEmpty(url) )throw newArgumentNullException("url");if( encoding==null)throw newArgumentNullException("encoding");HttpWebRequestrequest=(HttpWebRequest)WebRequest.Create(url);        request.Method="GET";        request.CookieContainer=cookieContainer;using(WebResponseresponse=request.GetResponse() ) {using(StreamReaderreader=newStreamReader(response.GetResponseStream(), encoding) ) {returnreader.ReadToEnd();            }        }    }private voidSendHttpDEMO()    {StringBuildersb=newStringBuilder();CookieContainercookieContainer=newCookieContainer();stringurl="http://www.taobao.com";        SendHttpRequestGet(url,Encoding.Default, cookieContainer);// 后面可以继续发起HTTP请求,此时将会包含上次从服务器写入的Cookie

//SendHttpRequestGet("同域名下的其它URL", Encoding.Default, cookieContainer);

// 至此,我们可以显示取得了哪些CookieCookieCollectioncookies=cookieContainer.GetCookies(newUri(url));if( cookies!=null) {foreach( System.Net.Cookiecookieincookies )                sb.AppendLine(cookie.ToString());        }        txtCookies.Text=sb.ToString();    }

回到顶部

重构与使用总结

在前面的Asp.net示例代码中,我一直使用.net提供的HttpCookie类来操作Cookie,是为了展示用原始的方式来使用Cookie,

这些代码有点重复,也有点繁琐,

为此,我提供了几个简单的方法可以更容易的使用Cookie,也算是对Cookie使用的一个总结。

///

///用于方便使用Cookie的扩展工具类/// public static classCookieExtension{// 我们可以为一些使用频率高的类型写专门的【读取】方法///

///从一个Cookie中读取字符串值。///

///

/// public static stringGetString(thisHttpCookiecookie)    {if( cookie==null)return null;returncookie.Value;    }///

///从一个Cookie中读取 Int 值。///

///

///

/// public static intToInt(thisHttpCookiecookie,intdefaultVal)    {if( cookie==null)returndefaultVal;returncookie.Value.TryToInt(defaultVal);    }///

///从一个Cookie中读取值并转成指定的类型///

///

///

/// public staticT ConverTo(thisHttpCookiecookie)    {if( cookie==null)return default(T);return(T)Convert.ChangeType(cookie.Value,typeof(T));    }///

///从一个Cookie中读取【JSON字符串】值并反序列化成一个对象,用于读取复杂对象///

///

///

/// public staticT FromJson(thisHttpCookiecookie)    {if( cookie==null)return default(T);returncookie.Value.FromJson();    }///

///将一个对象写入到Cookie///

///

///

/// public static voidWriteCookie(this objectobj,stringname,DateTime?expries)    {if( obj==null)throw newArgumentNullException("obj");if(string.IsNullOrEmpty(name) )throw newArgumentNullException("name");HttpCookiecookie=newHttpCookie(name, obj.ToString());if( expries.HasValue )            cookie.Expires=expries.Value;HttpContext.Current.Response.Cookies.Add(cookie);    }///

///删除指定的Cookie///

/// public static voidDeleteCookie(stringname)    {if(string.IsNullOrEmpty(name) )throw newArgumentNullException("name");HttpCookiecookie=newHttpCookie(name);// 删除Cookie,其实就是设置一个【过期的日期】cookie.Expires=newDateTime(1900,1,1);HttpContext.Current.Response.Cookies.Add(cookie);    }}

更完整的代码可以从本文的示例代码中获得。(文章底部有下载地址)

使用方式:

public static classTestClass{public static voidWrite()    {stringstr="中国";intaa=25;DisplaySettingssetting=newDisplaySettings{ Style=3, Size=50};DateTimedt=newDateTime(2012,1,1,12,0,0);        str.WriteCookie("Key1",DateTime.Now.AddDays(1d));        aa.WriteCookie("Key2",null);        setting.ToJson().WriteCookie("Key3",null);        dt.WriteCookie("Key4",null);    }public static voidRead()    {HttpRequestrequest=HttpContext.Current.Request;stringstr=request.Cookies["Key1"].GetString();intnum=request.Cookies["Key2"].ToInt(0);DisplaySettingssetting=request.Cookies["Key3"].FromJson();DateTimedt=request.Cookies["Key4"].ConverTo();    }    }

注意哦:以上代码中都是直接使用字符串"Key"的形式,这种方式对于大一些的程序在后期可能会影响维护。

所以建议:将访问Cookie所使用的Key能有一个类来统一的定义,或者将读写操作包装成一些属性放在一个类中统一的管理。

public static classCookieValues{// 建议把Cookie相关的参数放在一起,提供 get / set 属性(或者方法)来访问,以避免"key"到处乱写public static stringAAA    {get{returnHttpContext.Current.Request.Cookies["Key1"].GetString(); }    }public static intBBB    {get{returnHttpContext.Current.Request.Cookies["Key2"].ToInt(0); }    }public staticDisplaySettingsCCC    {get{returnHttpContext.Current.Request.Cookies["Key3"].FromJson(); }    }public staticDateTimeDDD    {get{returnHttpContext.Current.Request.Cookies["Key4"].ConverTo(); }    }}

回到顶部

补充

根据一些朋友提供的反馈,这里再补充4个需要注意的地方:

1. 如果使用Form登录验证且希望使用Cookie方式时,建议设置 cookieless="UseCookies",因为这个参数的默认值是:cookieless="UseDeviceProfile",Asp.net可能会误判。dudu就吃过亏。

2. Cookie有3个属性,一般我们可以不用设置,但它们的值可以在Web.config中指定默认值:

3. 虽然在写Cookie时,我们可以设置name, value之外的其它属性,但是在读取时,是读不到这些设置的。

其实在我的示例代码中有体现,我前面也忘记了说明了。

4. HttpRequest.Cookies 与 HttpResponse.Cookies 会有关系(很奇怪吧)。

以下代码演示了这个现象:

protected voidPage_Load(objectsender,EventArgse){DateTime.Now.ToString().WriteCookie("t1",null);    label1.Text=ShowAllCookies();Guid.NewGuid().ToString().WriteCookie("t2",null);// 如果去掉下面代码,将会看到2个t1Response.Cookies.Remove("t1");    Response.Cookies.Remove("t2");}private stringShowAllCookies(){StringBuildersb=newStringBuilder();for(inti=0; i", cookie.Name, cookie.Value);    }returnsb.ToString();}

上面的试验代码将会一直显示 t1 的Cookie ,这里就不再贴图了。

本文的所有示例代码可以点击此处下载。

如果,您认为阅读这篇博客让您有些收获,不妨点击一下右下角的推荐按钮。

如果,您希望更容易地发现我的新博客,不妨点击一下右下角的关注 Fish Li

因为,我的写作热情也离不开您的肯定支持。

感谢您的阅读,如果您对我的博客所讲述的内容有兴趣,请继续关注我的后续博客,我是Fish Li 。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容