REST架构详解

    1. 前言
    1. REST是什么
    • 2.1、起源
    • 2.2、REST架构的标志
    • 2.3、超媒体(hypermedia)
    • 2.4、REST误解]
    1. REST 架构风格的推导过程
    • 3.1. REST 所继承的架构风格约束
    • 3.2. 在论文中推导出的 REST 架构风格图示
    • 3.3. 一个基于 REST 的架构的过程视图(包括HTTP/1.1应用实践)]
    1. 为什么要用RESTful]
    • 4.1 常见的三种分布式应用架构风格
    • 4.2 REST和DO对比
    • 4.3 REST与RPC对比]
    1. RESTful如何设计
    1. 各端的具体实现]
    1. Django REST Framework使用

1.前言

在「远古时代」前端后端是融合在一起的,比如之前的PHP,JSP,ASP等等。近年来随着移动互联网的飞速发展,各种类型的Client端层出不穷,就需要通过一套统一的接口分别为Web,iOS和Android乃至桌面端提供服务。另外对于广大平台来说,比如Facebook platform,微博开放平台,微信、QQ公共平台等,它们不需要有显式的前端,只需要一套提供服务的接口,于是RESTful更是它们最好的选择。

2.RESTful是什么

越来越多的人开始意识到,网站即软件,而且是一种新型的软件。

这种"互联网软件"采用客户端/服务器模式,建立在分布式体系上,通过互联网通信,具有高延时、高并发等特点。

网站开发,完全可以采用软件开发的模式。但是传统上,软件和网络是两个不同的领域,很少有交集;软件开发主要针对单机环境,网络则主要研究系统之间的通信。互联网的兴起,使得这两个领域开始融合,现在我们必须考虑,如何开发在互联网环境中使用的软件。

RESTful架构,就是目前最流行的一种互联网软件架构。它结构清晰、符合标准、易于理解、扩展方便,所以正得到越来越多网站的采用。

需要注意的是,REST是一种设计风格而不是标准,如果一个架构符合REST原则,我们就称它为RESTful架构。

2.1、起源

在互联网行业,实践总是走在理论的前面。Web 发展到了 1995 年,在 CGI、ASP 等技术出现之后,沿用了多年、主要面向静态文档的 HTTP/1.0 协议已经无法满足 Web 应用的开发需求,因此需要设计新版本的 HTTP 协议。在 HTTP/1.0 协议专家组之中,有一位年轻人脱颖而出,显示出了不凡的洞察力,后来他成为了 HTTP/1.1 协议专家组的负责人。这位年轻人就是 Apache HTTP 服务器的核心开发者 Roy Fielding,他还是 Apache 软件基金会的合作创始人。

Roy Fielding 和他的同事们在 HTTP/1.1 协议的设计工作中,对于 Web 之所以取得巨大成功,在技术架构方面的因素做了一番深入的总结。Fielding 将这些总结纳入到了一套理论框架之中,然后使用这套理论框架中的指导原则,来指导 HTTP/1.1 协议的设计方向。HTTP/1.1 协议的第一个草稿是在 1996 年 1 月发布的,经过了三年多时间的修订,于 1999 年 6 月成为了 IETF 的正式规范(包括了 RFC 2616 以及用于对客户端做身份认证的 RFC 2617)。HTTP/1.1 协议设计的极为成功,以至于发布之后整整 10 年时间里,都没有多少人认为有修订的必要。用来指导 HTTP/1.1 协议设计的这套理论框架,最初是以备忘录的形式在专家组成员之间交流,除了 IETF/W3C 的专家圈子,并没有在外界广泛流传。Fielding 在完成 HTTP/1.1 协议的设计工作之后,回到了加州大学欧文分校继续攻读自己的博士学位。

第二年(2000 年)在他的博士学位论文 Architectural Styles and the Design of Network-based Software Architectures 中,Fielding 更为系统、严谨地阐述了这套理论框架,并且使用这套理论框架推导出了一种新的架构风格,并且为这种架构风格取了一个费解难懂名字“REST”,即Representational State Transfer(表现层状态转化,一般是这个翻译,但你听听这是人话吗?)的缩写.应该是缺少了主语。全称是 Resource Representational State Transfer:通俗来讲就是:资源在网络中以某种表现形式进行状态转移。分解开来:

Resource: 资源,即数据

Representional:某种表现形式,比如xml,json.

State Transfer:状态变化。通过HTTP动词实现。

2.2、REST架构的标志

根据理查德森模型,REST架构的成熟度有3分等级

gloryofrest.png

  • Level 0 POX(这个就不算REST了)。

    • 这类应用只有一个URL上的上帝接口,根据交换的XML内容操作所有的资源,往往导致上帝接口越来越复杂,越来越难维护。
  • Level 1 Resource。

    • 这一级别主要解决了上帝接口的问题,使得各种资源有了自己相应URL,虽然仍然是POX的交互方式,但每一个接口都更加紧凑和内聚,相应的容易维护起来。
    • URL templating带来的结果是服务器端和客户端的紧耦合,任何时候服务器端想改变自身的URL scheme的时候,都要break已经存在的客户端应用。
    • URL tunneling带来的问题包含URL templating,而且放弃了使用HTTP协议标准带来的任何好处(level2中详述)。
    • 早期的rails routes就是url templating/tunneling。Rails3开始已经更新更加靠近level 2了。
  • Level 2 Http verbs

    这一级别的使用http verbs来对各种资源进行CRUD操作,使得应用程序的接口更加统一,语义个更加明确。同时应为遵照http协议的标准进行交互,很多http提供的好处几乎可以免费的得到。此时使用多个URI的话,需要让不同的URI代表不同的资源(注意多个URI可能指向同一个Resource,而一个URI不能指向不同Resource。),同时使用多个HTTP方法操作这些资源,例如使用POST/GET/PUT/DELET分别进行CRUD操作。这时候HTTP头和有效载荷都包含业务逻辑,例如HTTP方法对应CRUD操作,HTTP状态码对应操作结果的状态。

    • Cache

      • 按照HTTP协议,GET操作是安全的,幂等(idempotent)的,任意多次对同一资源的GET操作,都不会导致资源的状态变化。所以GET的结果是可以安全的cache 。所有http提供的cache facilities都可以被利用起来,大幅度提高了应用程序的性能。甚至你仅仅只是在response里面加上cache directives就可以免费获得网络上各级的缓存服务器,代理服务器,以及用户客户端的缓存支持。互联网上几乎所有的应用你都可以粗略统计得到Get VS Non-Get的请求比例约为4:1,如果你能为GET操作加上缓存,将极大的提升你的程序性能。
    • Robust

      • 在HTTP常用的几个动词里,HRAD,GET,PUT,DELETE是安全的,幂等的。因为对同一资源的任一多次请求,永远代表同一语义,所以任何时候客户端发送出去这些动词的时候,如果服务器没有响应,或者返回错误代码,客户端都可以非常安全的再执行一次同一操作而不是担心重复操作带来的不同语义及最终结果。POST,PATCH就不是安全的,因为客户端向服务器端发送请求之后,服务器没有响应或者返回错误代码,客户端是不能安全的重复操作的,一定只是从新与服务器确认现在的资源状态才能决定下一步的操作。

    绝大部分的RESTful应用就停在这里了,当然也满足了大多的需求。

  • Level 3 Hypermedia controls

    • RESTful的架构本意是“在符合架构原理的前提下,理解和评估以网络为基础的应用软件的架构设计,得到一个功能强,性能好,适宜通信的架构”。

    • 目前规模最大,耦合度最低、最稳定的、性能最好的分布式网络应用是什么?就是WEB本身。 规模、稳定、性能都不用说,为什么耦合度低呢?想想每个人上网的经历,你几乎不需要什么任何培训就可以上一个新的网络购物平台挑选商品,用信用卡付款,邮到自己家里。把网站的程序想象成一个状态机,用户在一系列的状态转换中完成自己的目标,中间的每一步,用用程序都告诉你当前的状态和可能的下一步操作,最终引导用户从挑选商品到付款邮寄到家,到达状态机的终点。

      这种service discoverability和self-documenting就是Level 3想解决的问题。

      在这里面,告诉用户当前的状态以及各种下一步操作的东西,例如链接,按钮等等,就是Hypermedia Controls。Hypermedia Controls 就是这个状态机的引擎。

      Level 3的REST架构就是希望能够统一这一类的Hypermedia Controls, 赋予他们标准的, 高度可扩展的标准语义及表现形式, 使得甚至无人工干预的机器与机器间的通用交互协议边的可能. 比如你可以告诉一个通用的购物客户端, "给我买个最便宜的xbox", 客户端自动连上google进行搜索, 自动在前10个购物网站进行搜索, 进行价格排序, 然后自动挑选最便宜的网站, 进行一系列操作最终完成用信用卡付费, 填写个人收件地址然后邮寄.

      这些都依赖于Hypermedia Controls带来的这种service discoverablility和self-documenting

当然另外一种更简便的总结式可以这样理解:

  • REST四个基本原则:

    1. 使用HTTP动词:GET、 POST 、PUT 、DELETE;
    2. 无状态连接,服务器端不应保存过多上下文状态,即每个请求都是独立的;
    3. 为每个资源设置URI;
    4. 通过XML JSON进行数据传递;

    实现上述原则的架构即可称为RESTFul架构。

    1. 互联网环境下,任何应用的架构和API可以被快速理解;
    2. 分布式环境下,任何请求都可以被发送到任意服务器;
    3. 异构环境下,任何资源的访问和使用方式都统一;

RESTful简单来说就是:URL定位资源,用HTTP动词(GET,POST,DELETE,DETC)描述操作。

2.3、超媒体(hypermedia)

根据Roy的严格规定,超媒体(hypermedia)是REST的先决条件,任何其他东西都不应该自我标榜为REST,这里要要先解释一个概念:超媒体,wiki上面有这样一个解释:

多媒体(Multimedia),在电脑应用系统中,组合两种或两种以上媒体的一种人机交互式资讯交流和传播媒体。使用的媒体包括文字、图片、照片、声音(包含音乐、语音旁白、特殊音效)、动画和影片,以及程序所提供的互动功能。

超媒体(Hypermedia)是多媒体系统中的一个子集,超媒体系统是使用超链接(Hyperlink)构成的全球资讯系统,全球资讯系统是互联网上使用TCP/IP协议和UDP/IP协议的应用系统。2D的多媒体网页使用HTML、XML等语言编写,3d的多媒体网页使用VRML等语言编写。目前许多多媒体作品使用光碟发行,以后将更多地使用网络发行。

此种关键的角色还是超链接,使用超媒体作为应用引擎状态,意思是应用引擎状态变更,由客户端访问不同的超媒体资源驱动。

如下示例:

GET :https://api.github.com/authorizations
{
  "author": "github",
  "video": {
    "large": "https://api.github.com/Japan/large.avi.png",
    "medium": "https://api.github.com/Japan/media.avi.png",
    "small": "https://api.github.com/Japan/small.avi.png"
  }
}

由于在响应中包含了链接地址,因此使用该API的客户端就能够自由选择要下载怎样的信息。这些链接告知了客户端有哪些选择,并且它们的地址在哪里。

因此在这里我们无需同时返回三个不同版本小视频,我们所做的只是告诉客户端有三种不同清晰度可以选择,并且告诉客户端能够在哪里找到这些avi。

这样一来,客户端就能够根据不同的场景,做出符合自身需要的选择。而且,如果客户端只需要一种高清无码格式的,那就无需下载全部三种。这样一来可谓一箭三雕:既减少了网络负载,又增进了客户端的灵活性,更增进了API的可探索性。

超媒体的核心概念就是所谓的元素,而这些相互链接的资源实际上描述了一个协议,即引导我们达成某个目标的一系列步骤:

例如订购一杯咖啡所需要的点单、付款、取咖啡等等。这就是超媒体的本质:经由资源之间的链接,我们改变整个应用的状态,即超媒体转换了,分布式应用的状态。需要注意的是,服务器和消费者两者间,交换的是资源状态的表述,而不是应用的状态,被转移的表述中,包括了反应,应用状态的链接。

2.4、REST误解

现在看来,REST在2000年那个时代,确实是超前于时代的。Web开发者社区对于HTTP的设计意图存在着大量的误解,由此导致了对于HTTP的大量低效率的误用。这个情况持续一直到2005年Web 2.0的崛起。那个时候,DCOM、EJB、SOAP/WSDL这些DO风格的架构由于难以满足互联网环境对分布式应用架构设计的约束,与Web自身的架构风格REST相冲突,很难融入到Web之中。所谓的「Web Services」,其实除了将HTTP作为底层的传输协议外,跟(互联网环境中的)真正的Web没有什么关系。

而随着Ruby on Rails这个著名的Web开发框架开始大力支持REST开发之后,一线的Web开发者才真正接触到了REST。然而Rails所支持的REST开发将对资源的操作局限于CRUD(创建、获取、修改、删除)的语义(即,将对资源的CRUD操作映射到 GET/POST/PUT/DELETE四个HTTP方法),这其实是收窄了REST的适用范围。其他编程语言的Web开发框架(例如Java语言的 Struts、Spring MVC等等)也紧接着模仿了Rails的方式开始支持REST开发,然而这更加导致了一线的Web开发者误以为:REST开发就是 通过GET/POST/PUT/DELETE四个HTTP方法对资源执行CRUD操作。甚至还有很多仅仅使用了HTTP,而没有使用SOAP的Web服 务API,都自称是REST风格(RESTful)的API。

对于什么才是真正的REST风格的误解是如此之多,而将REST作为一个便于营销的 buzzword的挂羊头卖狗肉者也是如此之多,以至于REST的创造者Fielding终于忍无可忍了。2008年10月Fielding写了一篇博 客,做出了一个非常明确的断言:REST APIs must be hypertext-driven!(REST API必须是超文本驱动的!)超文本驱动这个理念变成了一个缩写词HATEOAS,这个缩写词来自于当初Fielding博士论文中的一句话: hypermedia as the engine of application state(将超媒体作为应用状态的引擎)。

其实超文本驱动(Hypertext Driven)的理念才是REST架构风格最核心的理念,也是REST风格的架构达到松耦合目标的根本原因。

3.REST 架构风格的推导过程

3.1 REST 所继承的架构风格约束(图 1)
restconstrain.png

在图 1 中,每一个椭圆形里面的缩写词代表了一种架构风格,而每一个箭头边的单词代表了一种架构约束。

  • REST 架构风格最重要的架构约束有 6 个:
    • 客户-服务器(Client-Server)

      通信只能由客户端单方面发起,表现为请求--响应模式。

    • 无状态(Stateless)

      通信会话状态(Session State)应该全部有客户端负责维护。

    • 缓存(Cache)

      响应内容可以在通信连接的某处缓存,以改善网络效率

    • 统一接口(Uniform Interface)

      通信链的组件之间通过统一的接口相互通信,以提高交互的可见性。

    • 分层系统(Layered System)

      通过限制组件的行为(即,每个组件只能“看到”与其交互的紧邻层),将结构分解成若干等级。

    • 按需代码(Code-On—Demand,可选)

      通过支持下载并执行一些代码(例如:Java Applet,Flash或者JS),对客户端的功能进行扩展。

3.2. 在论文中推导出的 REST 架构风格如下图所示:
deduce.png
3.3 .一个基于 REST 的架构的过程视图

而 HTTP/1.1 协议作为一种 REST 架构风格的架构实例,其架构如下图所示:

httprestful.png

用户代理处在三个并行交互(a、b 和 c)的中间。用户代理的客户端连接器缓存无法满足请求,因此它根据每个资源标识符的属性和客户端连接器的配置,将每个请求路由到资源的来源。

请求(a)被发送到一个本地代理,代理随后访问一个通过 DNS 查找发现的缓存网关,该网关将这个请求转发到一个能够满足该请求的来源服务器,服务器的内部资源由一个封装过的对象请求代理(object request broker)架构来定义。

请求(b)直接发送到一个来源服务器,它能够通过自己的缓存来满足这个请求。

请求(c)被发送到一个代理,它能够直接访问 WAIS(一种与 Web 架构分离的信息服务),并将 WAIS 的响应翻译为一种通用的连接器接口能够识别的格式。每一个组件只知道与它们自己的客户端或服务器连接器的交互;

4. 为什么要用RESTful结构呢?

4.1. 从架构风格的抽象角度来看,常见的分布式应用架构风格有三种:
  • 1.分布式对象(Distributed Objects,简称DO)

    架构实例有 CORBA/RMI/EJB/DCOM/.NET Remoting 等等

  • 2.远程过程调用(Remote Procedure Call,简称RPC)

    架构实例有 SOAP/XML-RPC/Hessian/Flash AMF/DWR 等等

  • 3.表现层状态转移(Representational State Transfer,简称REST)

    架构实例有HTTP/1.1、WebDav

DO 和 RPC 这两种架构风格在企业应用中非常普遍,而 REST 则是 Web 应用的架构风格,它们之间有非常大的差别。

4.2. REST和DO的差别在于:
  • REST支持抽象(即建模)的工具是资源。DO支持抽象的工具是对象。在不同的编程语言中,对象的定义有很大的差别,所以DO风格的架构通常都是与某种编程语言绑定的。跨语言交互即使能实现,实现是来也是会很复杂。而REST中的资源,则完全中立与开发平台和编程语言,可以使用任何编程语言来实现。

  • DO中没有统一接口的概念。不同API,接口风格可以完全不同。DO也不支持操作语义对于中间件的可见性。

  • DO中没有使用超文本,响应的内容只包含对象本身。REST使用了超媒体,可以实现更大颗粒度的交互,交互的效率比DO高。

  • REST支持数据流和管道,DO不支持数据流和管道。

  • DO风格通常会来来客户端和服务端的紧耦合。在三种架构风格之中,DO风格的耦合度是最大的,而REST的风格耦合度是最小的。REST松偶合的来源来自统一接口 + 超媒体驱动。

4.3. REST与RPC的差别在于:
  • REST支持抽象的工具是资源,RPC支持抽象工具是过程。REST风格架构建模是以名词为核心的,RPC风格的架构建模是以动词为核心的。简单类比一下,REST是面向对象编程,RPC则是面向过程编程。

  • RPC中没有统一的接口概念。不同的API,接口设计风格可以完全不同,RPC也不支持语义对于中间件的可见性。

  • RPC没有使用超媒体,响应的内容中只包含消息本身。REST使用了超媒体,可以实现更大颗粒度的交互效率也更高。

  • REST支持数据流和管道,RPC不支持数据流和管道

  • 因为使用了平台中立的消息。RPC风格耦合度比DO风格小一些,但是RPC风格也常常会带来客户端和服务器的紧耦合。支持统一接口+超媒体驱动的风格,可以达到最小的耦合度

4.4. REST 架构风格优越之处:

比较了三种架构风格之间的差别之后,从面向实用的角度来看,REST 架构风格可以为 Web 开发者带来三方面的利益:

  • 简单性

采用REST架构风格,对于开发、测试、运维人员来说都会更简单。可以充分利用大量HTTP服务端和客户端开发库、Web功能测试/性能测试工具、HTTP缓存、HTTP代理服务器、防火墙。这些开发库和基础设施早已成为日常用品,不需要什么高科技(例如神奇昂贵的应用服务器、中间件)就能解决大多数可伸缩性方面的问题。

  • 可伸缩性

充分利用好通讯链各个位置的HTTP缓存组件,可以带来更好的伸缩性。其实很多时候,在Web前端做性能优化,产生的效果不亚于仅仅在服务器端做性能优化,但是HTTP协议层面的缓存常常被一些看似很牛逼的人给忽略掉了。

  • 松耦合

统一接口 + 超媒体引擎驱动,带来了最大限度的松耦合。允许服务器和客户端在很大范围内,相对独立的运行。对于设计面向企业内网的API来说,松耦合并不是一个很重要的设计关注点。但是对于设计面向互联网的API来说,松耦合成了一个必选项,不仅仅在设计时应该关注,而且应该昂在最优的位子。

下面这段话出自知乎覃超,是一个浅显易懂的总结:

大家都知道"古代"网页都是前端后端融在一起的,比如之前的PHP,JSP等。在之前的桌面时代问题不大,但是近年来移动互联网的发展,各种类型的Client层出不穷,RESTful可以通过一套统一的接口为 Web,iOS和Android提供服务。另外对于广大平台来说,比如Facebook platform,微博开放平台,微信公共平台等,它们不需要有显式的前端,只需要一套提供服务的接口,于是RESTful更是它们最好的选择。在RESTful架构下:


restful.jpg

5. RESTful如何设计

  • 1、协议

    • api与用户的通讯协议,HTTP/HTTPS。
  • 2、域名

    • 应尽量将API部署在专用域名之下。

        https://api.example.com
      
    • 如果确定API很简单,不会有修改和进一步的扩展,可以考虑放在主域名下。

        https://example.com/api/...
      
  • 3、版本号

    • 版本号放入ULR或者HTTP头的信息中

        https://api.example.com/v1/...
      
  • 4、路径

    在RESTful的架构中,每个网址代表着一种资源(resource),所以一个好的设计中,网址中不要有动词,只能用名词。其实「资源」表示一种实体,所以应该是名词,动词应该放在HTTP协议中。而与此同时URI也有可能破坏HTTP GET的安全性和幕等性,比如某个客户端在http://example.com/updateOrder?id=1234&coffee=latte上执行GET(而不是POST),就能创建一笔新的咖啡订单(一个资源),按理来说GET请求不能改变服务的任何状态。而且所用名词往往与数据库中的表格对应。一般来说,数据库中的表都是同种记录的集合“集合”。
    例如:API提供动物园(zoo)的信息,可以这样设计;

      https://api.example.com/v1/zoos
      https://api.example.com/v1/animals
      https://api.example.com/v1/dogs
    
  • 5、HTTP动词

    • GET :用来获取资源,
    • POST :用来新建资源(也可以用于更新资源),
    • PUT :用来更新资源,
    • DELETE :用来删除资源。
    • PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)。
    • HEAD:获取资源的元数据。
    • PTIONS:获取信息,关于资源的哪些属性是客户端可以改变的。
  • 6、过滤信息

    如果记录数量很多,服务器不可能都将他们返回给用户。API提供参数返回过滤后的数据:

      * ?limit=10:指定返回记录的数量
      * ?offset=10:指定返回记录开始的位置
      * ?page-2&per_page=100:指定第几页以及每页的返回数量
      * ?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序
      * ?animal_type_id=1:指定筛选条件
    

    参数的设计允许存在冗余,即允许API路径和URL参数偶尔有重复。比如,GET /zoo/ID/animals 与 GET /animals?zoo_id=ID 的含义是相同的。

  • 7、状态码

服务器向用户返回的状态码和提示信息,常见的有以下一些(方括号中是该状态码对应的HTTP动词),查看全部状态码定义点击这里

* 200 OK-[GET]:服务器成功返回请求数据,该操作是幂等的。
* 201 create -[POST/PUT/PATCH]:用户新建或者修改数据
* 202 Accept - [*]标识一个请求进入后台排队(异步任务)
* 204 no content - [DELETE]:用户删除数据成功
* 400 invalid requests- [POST/PUT/PATCH]:用户发出的请求错误,服务器没有进行新建或者修改的操作,该操作是幂等的。
* 401 Unauthorized- [*]:标识没有权限(令牌,用户名,密码错误)。
* 403 Forbidden - [*]: 标识用户得到授权(与401错误相对),但是访问是被禁止的。
* 404 not found - [*]: y用户发出的请求针对的是不是存在的记录,服务器没进行操作,这个操作是幂等的。
* 406 Not Acceptable - [GET]:用户请求格式不可得:(比如用户请求的是json格式,但是只要xml格式)
* 410 Gone - [GET]:用户请求资源被永久删除,且不会再得到。
* 422 Unprocesable entity - [POST/PUT/PATCH]当创建一个对象时候,发生一个验证错误。
* 500 internal server error - [*] :服务器发生错误,用户将无法判断发出的请求是否成功。

另外附上GitHub的API设计,更具参考价值。

6. 各端的具体实现

如上图说是,service统一提供了一套RESTful API, web+android+ios作为同级调用API,现在都有一些比较成熟的框架来帮助开发者。

1. Server

2. Android

3. iOS

4. Web

  • 可以用重量级的AngularJS,也可以用轻量级 Backbone + jQuery 等。

7. Django REST Framework使用

Django REST Framework应用会在下一遍文章中做详细分析。

参考资料链接:

REST APIs must be hypertext-driven

REST 架构该怎么生动地理解?

理解RESTful架构

RESTful API 设计指南

Richardson Maturity Model

Status Code Definitions

理解本真的 REST 架构风格

架构风格与基于网络的软件架构设计(Roy Fielding 博士论文中文版)

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

推荐阅读更多精彩内容