GCDWebServer

GCDWebServer

摘要

关键词:iOS服务器框架,基于GCD

GCDWebServer是一个现代和轻量级的基于 HTTP 1.1的服务器,它的设计旨在嵌入OS X和iOS应用程序中。它的实现在一开始就考虑了以下目标:

  • 一个优雅轻巧的使用架构带有四个核心的类:请求类,连接类,请求类和响应类(详情请参阅“了解GCDWebServer的架构”下)。
  • 一个精心设计的可以轻松集成和定制完整的方便查看的头文件
  • 完全使用基于事件驱动的Grand Central Dispatch以求最佳性能和并发能力
  • 不依赖任何第三方的源码
  • 新的友好的BSD许可证下可使用

其他的内置功能

  • 实现-对于进入的HTTP请求采用完全异步的处理手段
  • 最小化内存消耗-尽可能把大量的HTTP请求和响应体放在磁盘流中
  • 使用 "application/x-www-form-urlencoded" 或者 "multipart/form-data" 编码 解析网站表单提交
  • 对HTTP体的请求和响应采用JSON解析和序列化
  • 对HTTP体的请求和响应 分块传输解码
  • 对HTTP体的请求和响应 使用gzip对HTTP进行压缩
  • 对请求的本地文件的范围也支持
  • 密码保护有基本的摘要接入认证-维基百科
  • 自动处理iOS apps前台,后台和假死状态的转换过渡
  • 全面支持IPv4和IPv6
  • NAT端口映射(仅限IPv4)
扩展
  • GCDWebUploader : GCDWebServer的子类,使用Web浏览器实现 上传和下载文件的接口
  • GCDWebDAVServer : GCDWebServer的子类,实现1级WebDAV服务器(与OS X的Finder部分2级支持)
暂不支持(非一个嵌入式HTTP服务器必须的)
  • 长连接
  • HTTPS

使用条件

  • OS X 10.7以后(X86_64)
  • iOS 5.0之后(armv7,armv7s或者arm64)
  • ARC的内存管理(MRC使用GCDWebServer 3.1和更早版本)

开始使用

下载或查看GCDWebServer的最新版本,直接添加整个“ GCDWebServer ”子文件夹到Xcode项目。如果您打算使用像GCDWebDAVServer或GCDWebUploader扩展之一,还要添加这些子文件夹。

  • 1.如果是直接手动添加GCDWebServer,那么第一步需要添加动态库libz(路径: Target > Build Phases > Link Binary With Libraries) 第二步添加 $(SDKROOT)/usr/include/libxml2)到header search paths(路径:arget > Build Settings > HEADER_SEARCH_PATHS);

  • 2.安装使用CocoaPods可以简单的添加(通过添加下面到Podfile中即可):

    pod "GCDWebServer", "~> 3.0"

  • 如果你还想使用GCDWebUploader ,使用该行:

    pod "GCDWebServer/WebUploader", "~> 3.0"

  • GCDWebDAVServer使用下面:

    pod "GCDWebServer/WebDAV", "~> 3.0"

最后终端输入命令:$ pod install

您还可以使用由Carthage加入这一行到你的Cartfile ( 3.2.5第一次正式发布与Carthage支持) :

github "swisspol/GCDWebServer" ~> 3.2.5

然后终端输入命令 $ carthage update 添加生成的frameworks到你的xcode项目中即可(详情查看Carthage instructions

帮助与支持

有关使用GCDWebServer的帮助下,最好把你的问题上在StackOverflow贴上GCDWebserver标签。

请务必先虽然阅读整本自述!

Hello World

下面代码介绍了如何实现在8080端口上运行,而且无论任何请求服务器都会返回一个“ Hello World”的HTML页面。

由于GCDWebServer使用GCD块来处理请求,没有使用子类或者代理,因此代码整体看起来很干净。

重要提示:如果不使用的CocoaPods ,务必在项目中将libz进行系统共享库添加到Xcode的target中。

OS X版本(命令行工具) :

 #import "GCDWebServer.h"
 #import "GCDWebServerDataResponse.h"

int main(int argc, const char* argv[]) {
  @autoreleasepool {

    // Create server
    GCDWebServer* webServer = [[GCDWebServer alloc] init];

    // Add a handler to respond to GET requests on any URL
    [webServer addDefaultHandlerForMethod:@"GET"
                             requestClass:[GCDWebServerRequest class]
                             processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {

      return [GCDWebServerDataResponse responseWithHTML:@"<html><body><p>Hello World</p></body></html>"];

    }];

    // Use convenience method that runs server on port 8080
    // until SIGINT (Ctrl-C in Terminal) or SIGTERM is received
    [webServer runWithPort:8080 bonjourName:nil];
    NSLog(@"Visit %@ in your web browser", webServer.serverURL);

  }
  return 0;
}

iOS版本:

 #import "GCDWebServer.h"
 #import "GCDWebServerDataResponse.h"

@interface AppDelegate : NSObject <UIApplicationDelegate> {
  GCDWebServer* _webServer;
}
@end

@implementation AppDelegate

- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {

  // Create server
  _webServer = [[GCDWebServer alloc] init];

  // Add a handler to respond to GET requests on any URL
  [_webServer addDefaultHandlerForMethod:@"GET"
                            requestClass:[GCDWebServerRequest class]
                            processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {

    return [GCDWebServerDataResponse responseWithHTML:@"<html><body><p>Hello World</p></body></html>"];

  }];

  // Start server on port 8080
  [_webServer startWithPort:8080 bonjourName:nil];
  NSLog(@"Visit %@ in your web browser", _webServer.serverURL);

  return YES;
}

@end

OS X Swift(命令行工具) :

webServer.swift
import Foundation
import GCDWebServers

func initWebServer() {

    let webServer = GCDWebServer()

    webServer.addDefaultHandlerForMethod("GET", requestClass: GCDWebServerRequest.self, processBlock: {request in
    return GCDWebServerDataResponse(HTML:"<html><body><p>Hello World</p></body></html>")

    })

    webServer.runWithPort(8080, bonjourName: "GCD Web Server")

    print("Visit \(webServer.serverURL) in your web browser")
}
WebServer-Bridging-Header.h
 #import <GCDWebServers/GCDWebServer.h>
 #import <GCDWebServers/GCDWebServerDataResponse.h>

iOS Apps中基于Web 的上传

GCDWebUploader是GCDWebServer的子类,提供了一个现成供 HTML5文件上传下载。GCDWebUploader让用户可以在浏览器里使用一个干净的UI来上传,下载,删除文件,以及在iOS应用的沙盒中的目录创建目录。

从你的浏览器中简单地实例化GCDWebUploader和运行然后访问 http://{YOUR-IOS-DEVICE-IP-ADDRESS}/

 #import "GCDWebUploader.h"

@interface AppDelegate : NSObject <UIApplicationDelegate> {
  GCDWebUploader* _webUploader;
}
@end

@implementation AppDelegate

- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
  NSString* documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
  _webUploader = [[GCDWebUploader alloc] initWithUploadDirectory:documentsPath];
  [_webUploader start];
  NSLog(@"Visit %@ in your web browser", _webUploader.serverURL);
  return YES;
}

@end
在iOS应用WebDAV服务器

GCDWebDAVServer 是GCDWebServer的子类提供了一个兼容的WebDAV服务器。 使用任何的WebDAV服务器像Transmit(Mac),ForkLift (Mac) 或者 CyberDuck(Mac / Windows)一样可以在ISO沙盒目录中让用户上传,下载,删除或者创建目录文件。

简单地实例化和运行GCDWebDAVServer,然后使用WebDAV连接到 http://{自己的IOS设备的IP地址}/ :

 #import "GCDWebDAVServer.h"

@interface AppDelegate : NSObject <UIApplicationDelegate> {
  GCDWebDAVServer* _davServer;
}
@end

@implementation AppDelegate

- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
  NSString* documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
  _davServer = [[GCDWebDAVServer alloc] initWithUploadDirectory:documentsPath];
  [_davServer start];
  NSLog(@"Visit %@ in your WebDAV client", _davServer.serverURL);
  return YES;
}

@end

Serving a Static Website 服务于静态网站

GCDWebServer有一个固定的handler用来递归式的服务一个目录(一个容许你控制并设置HTTP header的 "Cache-control",Cache-control用于控制HTTP缓存)

OS X 版(command line tool):

import "GCDWebServer.h"

int main(int argc, const char* argv[]) {
@autoreleasepool {

GCDWebServer* webServer = [[GCDWebServer alloc] init];
[webServer addGETHandlerForBasePath:@"/" directoryPath:NSHomeDirectory() indexFilename:nil cacheAge:3600 allowRangeRequests:YES];
[webServer runWithPort:8080];
  }
   return 0;
 }

使用 GCDWebServer

你可以通过创建一个实例 GCDWebServer类开始。注意:你可以在同一个应用程序上运行多个web服务器只要这些web服务器是挂在不同的端口。
然后你可以添加一个或者多个“处理”给服务器:每一个处理器都有机会去处理传入的Web请求并且提供响应。处理程序我们叫做LIFO队列,所以最新加入的“处理器”会覆写之前所有添加的“处理程序”。

理解 GCDWebServer的架构

GCDWebServer的体系结构包括只有4个核心类:
GCDWebServer 负责管理监听新的HTTP连接和服务器使用的一系列的处理程序列表 的接口socket.

GCDWebServerConnection 是由GCDWebServer来实例化处理每一个新的HTTP连接。每一个GCDWebServerConnection实例一直保持活跃状态一直到连接被关闭! 你可不能直接使用这个类,但是由于它是暴露的所以你可以继承至它来覆写一些hooks.

GCDWebServerRequest 由GCDWebServerConnection在接收到HTTP 表头后实例化 创建。 用它来包装请求和处理HTTP主体(如果有主体的话)。GCDWebServer 包含了几个GCDWebServerRequest的子类来处理常见情况下的情况:如存储 body 到内存或者传输到磁盘的一个文件中。

GCDWebServerResponse 由请求处理器创建 和 包装该响应HTTP headers和一些可选择的body. GCDWebServer 也是通过由几个GCDWebServerResponse的子类来处理常见的情况的如内存中的HTML文本或者从磁盘来传输一个文件时,

GCDWebServer 实现

GCDWebServer的实现依赖于“处理程序” 来处理传入的Web请求并且做出响应。 “处理程序”通过GCD块来实现的使得GCDWebServer方便你使用。然而,由于在GCD中“处理程序”是在任意的线程中执行,所以要注意的是线程安全和同一程序重复执行问题。

处理程序用到2个GCD块:
GCDWebServerMatchBlock 会被 在添加到GCDWebServer中的每一个"处理程序"所调用只要一个web请求已经开始后(如HTTP 表头已经收到)。它可以传递Web请求的基本信息(HTTP method, URL, headers...)而且必须要决定是否会处理这个请求。 如果返回yes,那么必须要返回一个新的这有这些信息的GCDWebServerRequest实例(看上面的GCDWebServerRequest),否则它只是返回nil.

之后,Web请求已经被完全接收并传递在上一步创建的GCDWebServerRequest实例GCDWebServerProcessBlock或GCDWebServerAsyncProcessBlock被调用。它必须返回同步(如果使用GCDWebServerProcessBlock)或异步(如果使用GCDWebServerAsyncProcessBlock)一个GCDWebServerResponse实例(见上文)或nil ,nil返回一个500的HTTP状态代码返回给客户端。一般我们推荐针对错误 返回一个GCDWebServerErrorResponse实例 以便可以把更多有用信息返回给客户端。

请注意:GCDWebServer的大多数方法来添加handlers只需要GCDWebServerProcessBlock或GCDWebServerAsyncProcessBlock ,因为它们已经提供了一种内置位于GCDWebServerMatchBlock中--例如 通过正则Regex匹配URL路径。

异步响应HTTP

GCDWebServer 3.0新增特性是异步地处理HTTP请求,如增加了 请求服务器后可以异步生成GCDWebServerResponse 的handlers,
这一过程通过使用GCDWebServerAsyncProcessBlock代替GCDWebServerProcessBlock的处理程序来实现。下面是一个例子:

(同步版)handler在响应HTTP后会产生blocks回调:
    [webServer addDefaultHandlerForMethod:@"GET"
                             requestClass:[GCDWebServerRequest class]
                             processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
    
      GCDWebServerDataResponse* response = [GCDWebServerDataResponse responseWithHTML:@"<html><body><p>Hello World</p></body></html>"];
      return response;
    
    }];
(异步版本) handler会立即返回结果并会在响应HTTP后回调GCDWebServer
[webServer addDefaultHandlerForMethod:@"GET"
                         requestClass:[GCDWebServerRequest class]
                    asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) {

  // Do some async operation like network access or file I/O (simulated here using dispatch_after())
  dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    GCDWebServerDataResponse* response = [GCDWebServerDataResponse responseWithHTML:@"<html><body><p>Hello World</p></body></html>"];
    completionBlock(response);
  });

}];
(高级异步版本)handler会立即返回一个HTTP本身异步内容流的HTTP响应:
[webServer addDefaultHandlerForMethod:@"GET"
                         requestClass:[GCDWebServerRequest class]
                         processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {

  NSMutableArray* contents = [NSMutableArray arrayWithObjects:@"<html><body><p>\n", @"Hello World!\n", @"</p></body></html>\n", nil];  // Fake data source we are reading from
  GCDWebServerStreamedResponse* response = [GCDWebServerStreamedResponse responseWithContentType:@"text/html" asyncStreamBlock:^(GCDWebServerBodyReaderCompletionBlock completionBlock) {

    // Simulate a delay reading from the fake data source
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
      NSString* string = contents.firstObject;
      if (string) {
        [contents removeObjectAtIndex:0];
        completionBlock([string dataUsingEncoding:NSUTF8StringEncoding], nil);  // Generate the 2nd part of the stream data
      } else {
        completionBlock([NSData data], nil);  // Must pass an empty NSData to signal the end of the stream
      }
    });

  }];
  return response;

}];
注意:甚至你可以结合异步的和高级异步这两个版本去异步返回一个异步的HTTP响应!

GCDWebServer & iOS APPs的后台运行模式

当iOS APP在进行网络请求时,我们必须注意当我们把应用程序压入后台后会发生什么情况。 一般情况下,应用程序进入后台我们应该停止网络请求当应用程序再次进入前台时我们再重新开始网络请求。
这使得情况可能会较为复杂,考虑到当服务器需要停止服务时他们仍然在保持着不断连接的状态。
幸运的事是GCDWebServer会代替我们自动完成以上的工作:

  • GCDWebServer 会开启后台模式,当第一次HTTP连接开始至最后一个HTTP请求结束,这样就阻止了应用系统一旦进入后台就会被iOS系统杀死,而且也会把HTTP连接一起杀死。

  • 当我们的应用程序在后台时,只要新的HTTP连接一直保持创建,后台任务就会持续进行,iOS系统就不会kill我们的应用程序(除了app很快的意外的内存升高问题).

  • 如果应用程序一直保持后台运行当一个HTTP请求都没有打开,GCDWebServer会立即结束自己 而且 一旦调用了 -stop方法就会停止接收新的HTTP请求(这一模式在 GCDWebServerOption_AutomaticallySuspendInBackground 选项中无效).
    +如果app进入了前台模式而且GCDWebServer是在暂停期间, 一旦调用了 -start 方法,GCDWebServer会自动的结束自己暂停状态开始接收新的HTTP连接。

  • HTTP连接经常是分批次(或者突然性)发起的,例如当加载一个带有多个资源文件的网页时就会这样。 当很靠后的HTTP 连接已经被关闭时就很难去精确地检测到了:它可能同一批的2个连续的HTTP连接部分很短的时间的延迟,而不是覆盖。如果GCDWebServer在它们之间的时间缝隙里停止在客户端了就不太好了(估计这样的话效率就低了),GCDWebServerOption_ConnectedStateCoalescingInterval这个enum值通过优雅的强制GCDWebServer 在每一个HTTP连接结束后等待一定时间的间隔,以防一个新的HTTP请求会在这个很短的时间间隔里被再次创建。

GCDWebServer日志

无论出于调试还是情报的目的, GCDWebServer无论何时都会记录messages。此外,在“调试”模式与“释发布”模式建设GCDWebServer时,它会记录更多的信息,但也进行了一些内部的重复性内容检查。要启用此行为,编译时GCDWebServer定义预处理器时设置常量DEBUG = 1 。在Xcode目标设置,这可以通过增加DEBUG = 1 开启 “调试”模式和 设置GCC_PREPROCESSOR_DEFINITIONS完成。最后,您还可以通过运行时调用类方法 +[GCDWebServer setLogLevel:]来完成。

  • 默认情况下, GCDWebServer记录的所有消息都发送到其内置的日志记录功能,它只是输出到stderr (假设终端设备连接了) 。为了更好地依靠日志的记录来完善APP,你可能想使用另一种记录工具。

  • GCDWebServer 对XLFacility自动支持(GCDWebServer的同一个作者而且开源)和CocoaLumberjack 。如果其中一方是在同一个Xcode项目, GCDWebServer会自动用它代替内置的日志记录功能(见GCDWebServerPrivate.h的实施细节)。

它也可以使用自定义日志记录功能 - 见GCDWebServer.h获取更多信息。

高级示例1 :实现HTTP重定向

下面是重定向的例子处理程序“/ ”到“/index.htm“,使用的GCDWebServerResponse的简便方法(它会自动设置HTTP状态代码和”位置“标头) :

[self addHandlerForMethod:@"GET"
                     path:@"/"
             requestClass:[GCDWebServerRequest class]
             processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {

  return [GCDWebServerResponse responseWithRedirect:[NSURL URLWithString:@"index.html" relativeToURL:request.URL]
                                          permanent:NO];

}];
高级示例2 :实现形式

要实现HTTP表单,你需要两个处理程序:

  • 一个获取处理程序不期望在HTTP请求任何机构,因此使用GCDWebServerRequest类。处理程序生成包含一个简单的HTML表单的响应。

  • 另一个POST处理器期望表单值是在HTTP请求和百分比编码的主体。幸运的是, GCDWebServer提供请求类GCDWebServerURLEncodedFormRequest能够自动解析这样的主题。处理程序简单地显示对应的用户提交的表单中的值。

      [webServer addHandlerForMethod:@"GET"
                                path:@"/"
                        requestClass:[GCDWebServerRequest class]
                        processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
      
        NSString* html = @" \
          <html><body> \
            <form name=\"input\" action=\"/\" method=\"post\" enctype=\"application/x-www-form-urlencoded\"> \
            Value: <input type=\"text\" name=\"value\"> \
            <input type=\"submit\" value=\"Submit\"> \
            </form> \
          </body></html> \
        ";
        return [GCDWebServerDataResponse responseWithHTML:html];
      
      }];
      
      [webServer addHandlerForMethod:@"POST"
                                path:@"/"
                        requestClass:[GCDWebServerURLEncodedFormRequest class]
                        processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
      
        NSString* value = [[(GCDWebServerURLEncodedFormRequest*)request arguments] objectForKey:@"value"];
        NSString* html = [NSString stringWithFormat:@"<html><body><p>%@</p></body></html>", value];
        return [GCDWebServerDataResponse responseWithHTML:html];
          }];
    
高级示例3 :服务动态网站

GCDWebServer提供的扩展类 - GCDWebServerDataResponse,可以返回从模板和一组变量中(使用格式%变量%)生成的HTML内容。这是一个非常基本的模板系统,并且打算以它为出发点通过继承GCDWebServerResponse建造更先进的系统。

假设你的应用程序有一个网站目录在包含了HTML模板文件和相应的CSS,脚本和图像。这是很容易把它变成一个动态的网站:

// Get the path to the website directory
NSString* websitePath = [[NSBundle mainBundle] pathForResource:@"Website" ofType:nil];

// Add a default handler to serve static files (i.e. anything other than HTML files)
[self addGETHandlerForBasePath:@"/" directoryPath:websitePath indexFilename:nil cacheAge:3600 allowRangeRequests:YES];

// Add an override handler for all requests to "*.html" URLs to do the special HTML templatization
[self addHandlerForMethod:@"GET"
                pathRegex:@"/.*\.html"
             requestClass:[GCDWebServerRequest class]
             processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {

    NSDictionary* variables = [NSDictionary dictionaryWithObjectsAndKeys:@"value", @"variable", nil];
    return [GCDWebServerDataResponse responseWithHTMLTemplate:[websitePath stringByAppendingPathComponent:request.path]
                                                    variables:variables];

}];

// Add an override handler to redirect "/" URL to "/index.html"
[self addHandlerForMethod:@"GET"
                     path:@"/"
             requestClass:[GCDWebServerRequest class]
             processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {

    return [GCDWebServerResponse responseWithRedirect:[NSURL URLWithString:@"index.html" relativeToURL:request.URL]
                                            permanent:NO];

];
最后一个例子:iOS 文件的下载和上传

GCDWebServer最初被写为iPad上的ComicFlow漫画阅读器应用程序。它允许用户使用其Web浏览器通过WiFi连接到他们的iPad ,然后上传,下载和整理漫画文件的应用程序内。

ComicFlow是完全开源的,你可以看到它是如何在WebServer.h和WebServer.m文件使用GCDWebServer 。

补充:github传送门,喜欢请Star

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

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,594评论 18 139
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,016评论 4 62
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,419评论 25 707
  • 十四班,全班一人一本《谈美》,都在读。好。 担心读不懂——今天的中国学生很少读论理著作,遇上朱光潜,得到一点趣味,...
    行吟斯基阅读 1,816评论 11 13
  • 我想当一个傻子 活在自己的世界里 不去听 尘世多嘈杂 不去看 人心多险恶 不去管 世事多纠缠 离别时 不会难过 再...
    蓝绿小巨人阅读 522评论 32 34