HTTP协议原理+实践 Web开发工程师必学(笔记)

1.1导学开始

数据内容传输都是通过http协议的。

需要理解http

http很重要

提升自身价值

简单的例子:浏览器中输入网页,ajax数据,img加载图片

Cache-Control?

缓存的验证

深入到TCP

Nginx使用

准备:http这个概念

1.2内容介绍

image.png

2.1五层网络模型介绍

一、经典五层模型
1、应用层:为应用软件提供了很多服务,构建于tcp协议之上,屏蔽网络传输相关细节。
(1)http,ftp…
2、传输层:向用户提供可靠的端到端(end-to-end)服务
(1)如tcp,udp
(2)传输层向高层屏蔽了下层数据通信的细节
————底三层————
3、网络层:为数据在结点之间传输创建逻辑链路。
4、数据链路层:在通信的实体间建立数据链路连接。
5、物理层:定义物理设备如何传输数据。
(1)如电脑硬件,网卡端口,网线,光缆。


image.png

2.2HTTP协议的发展历史

HTTP/0.9

只有一个Get命令
没有Header等描述数据的信息
服务器发送完毕,就关闭TCP连接

HTTP/1.0

增加了很多命令
增加了status code和header
多字符集支持、多部分发送、权限、缓存等

HTTP/1.1

持久连接
pipeline
增加host和其他一些命令

HTTP2

所有数据以二进制传输
同一个连接里面发送多个请求不再需要按照顺序来
头信息压缩以及推送等提高效率的功能

2.3HTTP的三次握手

一、tcp connection
1、http只有请求和响应的概念,没有连接的概念,靠tcp来连接请求。
2、一个tcp连接可以发送多个http请求。1.0请求发送就会就断开了,1.1保持连接,减少Tcp开销,只要开一个TCP连接。
3、三次握手
(1)防止服务器开启一些无用的连接

image.png

二、wireshark抓包:网络层、tcp层、http层都可以抓到
charles是抓到http层

2.4 URI、URL、URN

1、Uniform Resource Identifier 统一资源标识符
2、用来唯一标识互联网上的信息资源
3、包含URL、URN
二、URL
1、Uniform Resource Locator 统一资源定位符
2、http://user:pass@host.com:80/path?query=string#hash
3、此类格式的都叫做url,比如ftp协议、 file

protocol :// hostname[:port] / path / [;parameters][?query]#fragment

protocol(协议)
指定使用的传输协议,下表列出 protocol 属性的有效方案名称。 最常用的是HTTP协议,它也是目前WWW中应用最广的协议。
file 资源是本地计算机上的文件。格式file:///,注意后边应是三个斜杠。
ftp 通过 FTP访问资源。格式 FTP://
http 通过 HTTP 访问该资源。 格式 HTTP://
https 通过安全的 HTTPS 访问该资源。 格式 HTTPS://

hostname(主机名)

是指存放资源的服务器的域名系统(DNS) 主机名或 IP 地址。有时,在主机名前也可以包含连接到服务器所需的用户名和密码(格式:username:password@hostname)。

port(端口号)

整数,可选,省略时使用方案的默认端口,各种传输协议都有默认的端口号,如http的默认端口为80。如果输入时省略,则使用默认端口号。有时候出于安全或其他考虑,可以在服务器上对端口进行重定义,即采用非标准端口号,此时,URL中就不能省略端口号这一项。

path(路径)

由零或多个“/”符号隔开的字符串,一般用来表示主机上的一个目录或文件地址。

parameters(参数)

这是用于指定特殊参数的可选项。

query(查询)

可选,用于给动态网页(如使用CGI、ISAPI、PHP/JSP/ASP/ASP。NET等技术制作的网页)传递参数,可有多个参数,用“&”符号隔开,每个参数的名和值用“=”符号隔开。

fragment(信息片断)

字符串,用于指定网络资源中的片断。例如一个网页中有多个名词解释,可使用fragment直接定位到某一名词解释

三、URN
1、Uniform Resource Name,永久统一资源定位符
2、在资源移动之后还能被找到
3、目前还没有非常成熟的使用方案。

2.5http报文格式

image.png

报文主要分为起始行、首部、主体。

起始行

在请求报文中,起始行包含了HTTP的Method、路由地址和HTTP协议版本,在响应报文中则是HTTP协议版本和Status Code如上图

首部:后续详细讲
主体

HTTP请求中返回的内容。

HTTP方法

用来定义对资源的操作
常用有GET、POST等
从定义上讲有各自的语义

HTTP CODE,定义服务器对请求的处理结果

各个区间的CODE有各自的语义,
如100-199之间代表这个操作需要持续的进行,
200-299之间代表操作是成功的,
300-399之间代表重定向,
400-499之间代表发送的请求有问题,
500-599之间代表服务器出现了错误。
好的HTTP服务可以通过CODE判断结果

2.6 实现一个最简单的http服务(nodejs)

const http = require('http')

http.createServer(function (request, response) {
  console.log('request come', request.url)

  response.end('123')

}).listen('2333')

console.log('server listening on 2333')

3.1认识http客户端

一、浏览器是最常用的http客户端
二、查看http请求, terminal
1、curl baidu.com。curl只是发送请求,并返回,不会对页面进行渲染
2、curl www.baidu.com
3、curl -v www.baidu.com
可以看到dns解析后的ip地址。

3.2跨域请求的限制与解决

跨域:指的是浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器施加的安全限制。
所谓同源是指,协议、域名、端口均相同,不明白没关系,举个栗子:

http://www.123.com/index.html 调用 http://www.123.com/server.php (非跨域)

http://www.123.com/index.html 调用 http://www.456.com/server.php (主域名不同:123/456,跨域)

http://abc.123.com/index.html 调用 http://def.123.com/server.php (子域名不同:abc/def,跨域)

http://www.123.com:8080/index.html 调用 http://www.123.com:8081/server.php (端口不同:8080/8081,跨域)

http://www.123.com/index.html 调用 https://www.123.com/server.php (协议不同:http/https,跨域)

请注意:localhost和127.0.0.1虽然都指向本机,但也属于跨域

那么该如何解决跨域问题呢

JSONP使用方式

<script src="需要请求的地址">

</script>

但是要注意JSONP只支持GET请求,不支持POST请求。

添加请求头
还是用Node环境进行测试,在当前目录下有server.js,server2.js,index.html。

贴上server.js(请求方)的代码

const http = require('http')
const fs = require('fs')

http.createServer(function (request, response) {
  console.log('request come', request.url)

  const html = fs.readFileSync('index.html', 'utf8')
  response.writeHead(200, {
    'Content-Type': 'text/html'
  })
  response.end(html)

}).listen('2333')

console.log('server listening on 2333')

server2.js(响应方)代码

const http = require('http')

http.createServer(function (request, response) {
  console.log('request come', request.url)

  response.writeHead(200, {
    'Access-Control-Allow-Origin': '*'
  })

  response.end('123')

}).listen('2334')
console.log('server listening on 2334')

index.html的代码

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>

</body>
<script>
  var xhr = new XMLHttpRequest()
  xhr.open('GET', 'http://127.0.0.1:2334/')
  xhr.send()
</script>
</html>

新建两个终端分别运行node server.js,node server2.js,打开localhost:2333,可以看到,我们当前从不同端口请求时,是属于跨域请求的,但是并没有跨域的报错,原因是因为我们在响应方里添加了请求头'Access-Control-Allow-Origin': '*',如果去掉了这个请求头的话,则会报错

代理:

例如 www.123.com/index.html需要调用www.456.com/server.php,可以写一个接口www.123.com/server.php,由这个接口在后端去调用www.456.com/server.php并拿到返回值,然后再返回给index.html,这就是一个代理的模式。相当于绕过了浏览器端,自然就不存在跨域问题

3.3CORS跨域限制以及预请求验证

1、首先要说一下CORS跨域的限制,CORS允许的方法只有

GET HEAD POST

2、CORS允许的Content-Type只有

text/plain

multipart/form-data

application/x-www-form-urlencoded`

其他限制:请求头限制、XMLHttpRequestUpload对象均没有注册任何事件监听器、请求中没有使用ReadableStream对象

假如我们使用fetch预请求,并需要带一个自定义的请求头如:X-Test-Cors,那么必须要在服务端中定义好Access-Control-Allow-Headers: X-Test-Cors,否则则会报错。

同理,如果我们需要使用其他自定义的方法的话,我们同样需要在服务端中定义Access-Control-Allow-Methods: Post, Put, Delete, xxxxxxx

3.4缓存头Cache-Control

1、可缓存性

public:代表http请求经过的任何地方(代理服务器等等)都可以进行缓存。

private:代表只有发起请求的浏览器才可以进行缓存

no-cache:任何节点都不可以进行缓存

2、到期

缓存到期时间:max-age = <seconds>(秒)

代理服务器使用:s-maxage = <seconds>(秒)

指示客户机可以接收超出max-age时间的响应消息,max-stale在请求设置中有效,在响应设置中无效:`max-stale =

3、重新验证

must-revalidate:如果过期,则必须更新

proxy-revalidate
和must-revalidate差不多,是给缓存服务器使用的。

4、其他

no-store
no-cache从字面意义上很容易误解为不缓存,但是no-cache代表不缓存过期的资源,缓存会向服务器进行有效处理确认之后处理资源,更确切的说,no-cache应该是:do-not-serve-from-cache-without-revalidation

而no-store才是真正的不进行缓存
使用no-cache的目的就是为了防止从缓存中获取过期的资源。

no-transform
如果第三方网站不希望页面转码,可在页面中添加此协议,当用户进入时,会直接跳转至原网页

具体实践:Cache-Control缓存到期时间

const http = require('http')
const fs = require('fs')

http.createServer(function (request, response) {
    console.log('request come --- ', request.url)

    if (request.url === '/') {
        const html = fs.readFileSync('test.html', 'utf8')
        response.writeHead(200, {
            'Content-Type': 'text/html'
        })
        response.end(html)
    }

    if (request.url === '/script.js') {
        response.writeHead(200, {
            'Content-Type': 'text/javascript',
            'Cache-Control': 'max-age=200'
        })
        response.end('console.log("script loaded")')
    }

}).listen('8888')

console.log('server listening on 8888')

加入了Cache-Control': 'max-age=200'之后,浏览器(客户端)即可对静态资源进行缓存,效果如下图所示。过了200秒后,缓存失效,


image.png

3.5缓存验证Last-Modified和Etag

image.png

一、Last-Modified
1、上次修改时间,配合If-Modified-Since或者If-Unmodified-Since使用。
2、对比上次修改时间以验证资源是否需要更新。
二、Etag
1、数据签名
2、最常见的:对资源进行hash计算
3、对比资源的签名判断是否使用缓存

server.js

const http = require('http')
const fs = require('fs')

http.createServer(function (request, response) {
    console.log('request come --- ', request.url)

    const etag = request.headers['if-none-match'];
    if (etag === '777') {
        response.writeHead(304, {
            'Content-Type': 'text/javascript',
            'Cache-Control': 'max-age=200000, no-cache',
            'Last-Modified': '123',
            'Etag': '777'
        })
        response.writeHead()
    } else {
        response.writeHead(200, {
            'Content-Type': 'text/javascript',
            'Cache-Control': 'max-age=200000, no-cache',
            'Last-Modified': '123',
            'Etag': '777'
        })
        response.end('console.log("scripte loaded twice")')
    }


}).listen('8888')

console.log('server listening on 8888')

index.html

3.6 cookie和session

1、Cookie
通过Set-Cookie设置
下次请求会自动带上
键值对,可以设置多个

2、Cookie属性
max-age和expires设置过期时间
Secure只在https的时候发送
HttpOnly无法通过document.cookie访问

测试cookie返回的方式:
首先新建文件夹,然后新建server.js、index.html。
index.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div>content</div>
</body>
<script>
    console.log(document.cookie);
</script>

</html>

server.js

const http = require('http')
const fs = require('fs')

http.createServer(function (request, response) {
    console.log('request come --- ', request.url)

    if (request.url === '/') {
        const html = fs.readFileSync('test.html', 'utf8')
        response.writeHead(200, {
            'Content-Type': 'text/html',
            'Set-Cookie': 'id=123'
        })
        response.end(html)
    }
}).listen('8888')

console.log('server listening on 8888')

1、我们通过在请求头中加入'Set-Cookie': 'id=123',来将Cookie写入到浏览器,随后我们在index.html中使用console.log(document.cookie);来打印当前的cookie。

image.png

2、我们也可以在chrome开发者工具中的Application中可以查找到当前的cookie

image.png

3、Cookie !== Session,Cookie可以是Session的一种实现方式,但是Cookie和Session不是一一对应的,Session有许多别的实现。
4、cookie如果没有设置过期时间,浏览器关闭,就过期了
5、chrome工具,hostAdmin,可以改host,域名映射。
6、同一个主域名下,二级域名可以共享cookie
7、常用:使用cookie来保存session
8、只要是能定位到这个用户,就是一种session的实现方案

3.7HTTP长连接

在HTTP/1.0中默认使用短连接。也就是说,客户端和服务器每进行一次HTTP操作,就建立一次连接,任务结束就中断连接。当客户端浏览器访问的某个HTML或其他类型的Web页中包含有其他的Web资源(如JavaScript文件、图像文件、CSS文件等),每遇到这样一个Web资源,浏览器就会重新建立一个HTTP会话。

而从HTTP/1.1起,默认使用长连接,用以保持连接特性。使用长连接的HTTP协议,会在响应头加入这行代码:

Connection:keep-alive
在使用长连接的情况下,当一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的TCP连接不会关闭,客户端再次访问这个服务器时,会继续使用这一条已经建立的连接。Keep-Alive不会永久保持连接,它有一个保持时间,可以在不同的服务器软件(如Apache)中设定这个时间。实现长连接需要客户端和服务端都支持长连接。

HTTP协议的长连接和短连接,实质上是TCP协议的长连接和短连接。

那么,长连接的优劣是什么?

由上可以看出,长连接可以省去较多的TCP建立和关闭的操作,减少浪费,节约时间。对于频繁请求资源的客户来说,较适用长连接。不过这里存在一个问题,存活功能的探测周期太长,还有就是它只是探测TCP连接的存活,属于比较斯文的做法,遇到恶意的连接时,保活功能就不够使了。在长连接的应用场景下,client端一般不会主动关闭它们之间的连接,Client与server之间的连接如果一直不关闭的话,会存在一个问题,随着客户端连接越来越多,server早晚有扛不住的时候,这时候server端需要采取一些策略,如关闭一些长时间没有读写事件发生的连接,这样可 以避免一些恶意连接导致server端服务受损;如果条件再允许就可以以客户端机器为颗粒度,限制每个客户端的最大长连接数,这样可以完全避免某个蛋疼的客户端连累后端服务。

短连接对于服务器来说管理较为简单,存在的连接都是有用的连接,不需要额外的控制手段。但如果客户请求频繁,将在TCP的建立和关闭操作上浪费时间和带宽。

长连接和短连接的产生在于client和server采取的关闭策略,具体的应用场景采用具体的策略,没有十全十美的选择,只有合适的选择。
接下来在node环境中测试下chrome长连接的并发数限制

我们新建一个文件夹,里面新建三个文件,分别是:server.js、test.html、guo.jpg
test.html

const http = require('http')
const fs = require('fs')

http.createServer(function (request, response) {
    console.log('request come --- ', request.url)
    const html = fs.readFileSync('test.html', 'utf8')

    const img = fs.readFileSync('guo.jpg')
    if (request.url === '/') {
        response.writeHead(200, {
            'Content-Type': 'text/html',
            // 默认状态keep-alive 创建一个tcp链接 可以并发多个http请求
            // 'Connection': 'keep-alive',
            // 关闭之后 
            // 'Connection': 'close'
        })
        response.end(html)
    } else {
        response.writeHead(200, {
            'Content-Type': 'image/jpg',
            'Connection': 'close'
        })
        response.end(img)
    }
}).listen('8888')

console.log('server listening on 8888')

test.index.html

<body>
    <!-- 用多张地址不一样的图片 -->
    <div><img src="../guo.jpg" alt=""></div>
    <div><img src="../guo2.jpg" alt=""></div>
    <div><img src="../guo3.jpg" alt=""></div>
    <div><img src="../guo4.jpg" alt=""></div>
    <div><img src="../guo5.jpg" alt=""></div>
    <div><img src="../guo6.jpg" alt=""></div>
    <div><img src="../guo7.jpg" alt=""></div>
    <div><img src="../guo8.jpg" alt=""></div>
    <div><img src="../guo9.jpg" alt=""></div>
    <div><img src="../guo10.jpg" alt=""></div>
    <div><img src="../guo11.jpg" alt=""></div>
    <div><img src="../guo12.jpg" alt=""></div>
    <div><img src="../guo13.jpg" alt=""></div>
    <div><img src="../guo14.jpg" alt=""></div>
    <div><img src="../guo15.jpg" alt=""></div>
    <div><img src="../guo16.jpg" alt=""></div>
</body>
image.png

打开chrome的network可以发现,由于并发数的限制,最后一张图片得等前面的几个连接执行完毕,才能加载,因此有了很长的waterfull时间

3.8 数据协商

客户端请求时会声明希望拿到的数据格式和限制,服务端根据请求头返回不同的数据

1. Accept

声明想要的数据类型,[主类型]/[子类型] ,如text/html,image/jpg

2、Accept-Encoding

声明进行传输的编码方式,主要是数据压缩的算法,如gzip, deflate, br

3、Accept-Lanuage

声明希望返回信息的语言,如zh-CN,zh;q=0.9(q表权重,0~1)

4、User-Agent

声明浏览器和操作系统的相关信息

2. Content

1、Content-Type

声明返回的数据格式,如’X-Content-Type-Options’:‘nosniff’,可阻止浏览器自行猜测返回数据类型而引发的安全问题。

不同的文件后缀对应不同的Content-Type

2、Content-Encoding

声明返回的编码方式,即数据压缩

3、Content-Lanuage

声明返回的语言

这里先实践下在请求头中声明Content-Type和Content-Encoding。

先上代码,server.js:

const http = require('http')
const fs = require('fs')
const zlib = require('zlib')

http.createServer(function (request, response) {
  console.log('request come', request.url)

  const html = fs.readFileSync('index.html')
  response.writeHead(200, {
    'Content-Type': 'text/html',
    'Content-Encoding': 'gzip' //声明以gzip的方式传输
  })
  response.end(zlib.gzipSync(html))
  // response.end(html)

}).listen('2333')

console.log('server listening on 2333')

上述代码在请求头重声明了Content-Type和Content-Encoding,那么Content-Type我们肯定很好理解,他告诉了浏览器我们文本的格式是什么。

image.png

接下来我们先看下在运行node server.js启动之后,Network的截图。红框中上面的数字是文件在传输过程中加上HTTP传输信息后的体积,下面的数字则是body中内容的体积。那么在Content-Type声明gzip压缩方式,开启gzip压缩后,传输时的体积会比body的体积更小,从而加快传输的速度。

3.9redirect

重定向

通过 url 访问某个路径请求资源时,发现资源不在 url 所指定的位置,这时服务器要告诉浏览器,新的资源地址,浏览器再重新请求新的 url,从而拿到资源。

若服务器指定了某个资源的地址,现在需要更换地址,不应该立刻废弃掉 url,如果废弃掉可能直接返回 404,这时应该告诉客户端新的资源地址。

node server.js启动,发现url路径自动带了/new。

//server.js

const http = require('http')
const fs = require('fs')

http.createServer(function (request, response) {
  console.log('request come', request.url)

  if (request.url === '/') {
    response.writeHead(302, {
      'Location': '/new'
    })
    response.end()
  } else if (request.url === '/new') {
    response.writeHead(200, {
      'Content-Type': 'text/html'
    })
    response.end(`<div>this is new Content</div>`)
  }

}).listen('2333')

console.log('server listening on 2333')

可以看到根据定义的302临时重定向,页面自动的加载到了/new路由下的内容。
但如果我们需要永久重定向,把status code 改成301即可。

Redirect 301 和 302 的区别

302 临时跳转,每次请求仍然需要经过服务端指定跳转地址
301 永久跳转

302的情况
每次访问 locahost:8888,都要经过服务端跳转,服务端通过 console.log 可以看到 / → /new 两次请求。

const http = require('http')

http.createServer(function (request, response) {
  console.log('request come', request.url)

  if (request.url === '/') {
    response.writeHead(302, {
      'Location': '/new'
    })
    response.end()
  }
  if (request.url === '/new') {
    response.writeHead(200, {
      'Content-Type': 'text/html',
    })
    response.end('<div>this is content</div>')
  }
}).listen(8888)

console.log('server listening on 8888')

301 的情况
访问 locahost:8888,第一次经过服务端跳转,服务端通过 console.log 可以看到 / /new 两次请求;第二次 服务端 console.log 只显示 /new ,没有再次经过服务器指定新的 Location。

response.writeHead(301, {
      'Location': '/new'
})

注意:使用 301 要慎重,一旦使用,服务端更改路由设置,用户如果不清理浏览器缓存,就会一直重定向。

设置了 301,locahost 会从缓存中读取,并且这个缓存会保留到浏览器,当我们访问 8888 都会进行跳转。此时,就算服务端改变设置也是没有用的,浏览器还是会从缓存中读取。

3.10 内容安全策略( CSP )

Content-Security-Policy 的作用:

使用白名单的方式告诉客户端(浏览器)允许加载和不允许加载的资源。

向服务器举报这种强贴牛皮鲜广告的行为,以便做出更加针对性的措施予以绝杀。

限制方式:

default-src 限制全局

指定资源类型
connect-src、manifest-src、img-src、font-src/media-src、frame-src、style-src、script-src…
更详细的文档请移步MDN的:内容安全策略( CSP )

  1. http/https 协议

1.0 协议缺陷:

无法复用链接,完成即断开,重新慢启动和 TCP 3次握手
head of line blocking: 线头阻塞,导致请求之间互相影响

1.1 改进:

长连接(默认 keep-alive),复用
host 字段指定对应的虚拟站点
新增功能:

断点续传
身份认证
状态管理
cache 缓存

Cache-Control
Expires
Last-Modified
Etag

2.0:

多路复用
二进制分帧层: 应用层和传输层之间
首部压缩
服务端推送

https: 较为安全的网络传输协议

证书(公钥)
SSL 加密
端口 443

TCP:

三次握手
四次挥手
滑动窗口: 流量控制
拥塞处理

慢开始
拥塞避免
快速重传
快速恢复

缓存策略: 可分为 强缓存 和 协商缓存

Cache-Control/Expires: 浏览器判断缓存是否过期,未过期时,直接使用强缓存,Cache-Control的 max-age 优先级高于 Expires

当缓存已经过期时,使用协商缓存

唯一标识方案: Etag(response 携带) & If-None-Match(request携带,上一次返回的 Etag): 服务器判断资源是否被修改,
最后一次修改时间: Last-Modified(response) & If-Modified-Since (request,上一次返回的Last-Modified)

如果一致,则直接返回 304 通知浏览器使用缓存
如不一致,则服务端返回新的资源

Last-Modified 缺点:

周期性修改,但内容未变时,会导致缓存失效
最小粒度只到 s, s 以内的改动无法检测到

Etag 的优先级高于 Last-Modified

4.1

4.2

4.3

4.4

4.5

5.1

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

推荐阅读更多精彩内容