go restful源碼剖析-1

restful hello world


首次瀏覽下go-restful的工程結(jié)構(gòu)矾缓,從工程組織上面來看萝映,工程包括兩個部分:source文件及example文件记罚,其中source文件組成了工程的主體次泽,包括restful主要功能接口的實現(xiàn)及單元測試文件(以test.go結(jié)尾命名的文件)变秦,另外example目錄中主要包括了接口的使用案例成榜。

第一次閱讀go-restful源碼,以example目錄下的restful-hello-world.go作為入坑樣例,來跟蹤了解下restful如何組織封裝webservice及route的維護蹦玫。

restful-hello-world.go代碼如下:

func main() {
 ws := new(restful.WebService)
    ws.Route(ws.GET("/hello").To(hello))
    restful.Add(ws)
    log.Fatal(http.ListenAndServe(":8080", nil))
}

func hello(req *restful.Request, resp *restful.Response) {
    io.WriteString(resp, "world")
}

restful初始化流程


1. ws := new(restful.WebService)

webservice定義在web_service.go中, 通過new分配了webservicef空間, 傳遞給ws指向新分配零值的指針

// WebService holds a collection of Route values that bind a Http Method + URL Path to a function.
type WebService struct {
    rootPath       string
    pathExpr       *pathExpression // cached compilation of rootPath as RegExp
    routes         []Route
    produces       []string
    consumes       []string
    pathParameters []*Parameter
    filters        []FilterFunction
    documentation  string
    apiVersion     string

    typeNameHandleFunc TypeNameHandleFunction

    dynamicRoutes bool

 // protects 'routes' if dynamic routes are enabled
    routesLock sync.RWMutex
}
1.png

2.ws.Route(ws.GET("/hello").To(hello))

2.1 ws.GET("/hello")

第一步執(zhí)行的函數(shù),GET方法綁定在WebServices結(jié)構(gòu)體上赎婚,調(diào)用改函數(shù)參數(shù)為subPath = /hello, 返回RouteBuilder指針

func (w *WebService) GET(subPath string) *RouteBuilder {
 return new(RouteBuilder).typeNameHandler(w.typeNameHandleFunc).servicePath(w.rootPath).Method("GET").Path(subPath)
}
2.1.1 new(RouteBuilder)

在Get函數(shù)中刘绣,首先執(zhí)行new(RouteBuilder), 返回routebuilder指針,在webservice的初始化后期可以發(fā)現(xiàn)挣输,RouteBuilder的作用是通過用戶定義的參數(shù)纬凤,初始化route結(jié)構(gòu),route結(jié)構(gòu)最終由websocket中的routes結(jié)構(gòu)進行存儲維護

// RouteBuilder is a helper to construct Routes.
type RouteBuilder struct {
    rootPath    string
    currentPath string
    produces    []string
    consumes    []string
    httpMethod  string // required
    function    RouteFunction // required
    filters     []FilterFunction
    conditions  []RouteSelectionConditionFunction

    typeNameHandleFunc TypeNameHandleFunction // required

 // documentation
    doc                     string
    notes                   string
    operation               string
    readSample, writeSample interface{}
    parameters              []*Parameter
    errorMap                map[int]ResponseError
    metadata                map[string]interface{}
    deprecated              bool
}
2.1.2 typeNameHandler(w.typeNameHandleFunc)

typeNameHandler方法綁定在RouteBuilder上, 該函數(shù)為賦值函數(shù)撩嚼,將webservice中定義的typeNameHandleFunc TypeNameHandleFunction // required傳遞給routebuilder, 該變量定義了一類環(huán)函數(shù)的通用化模板停士,在初始化前RouteBuilder結(jié)構(gòu)體中已經(jīng)初始化定義了typeNameHandleFunc TypeNameHandleFunction

// TypeNameHandleFunction declares functions that can handle translating the name of a sample object
// into the restful documentation for the service.
type TypeNameHandleFunction func(sample interface{}) string
// typeNameHandler sets the function that will convert types to strings in the parameter
// and model definitions.
func (b *RouteBuilder) typeNameHandler(handler TypeNameHandleFunction) *RouteBuilder {
    b.typeNameHandleFunc = handler
    return b
}
2.1.3 servicePath(w.rootPath)

w.rootPath 初始值為"",在該函數(shù)中完丽, 將webservice中定義的rootpath初始值傳遞給RouteBuilder,在RouteBuilder中默認的rootPath初始值也為""

func (b *RouteBuilder) servicePath(path string) *RouteBuilder {
 b.rootPath = path
 return b
}
2.1.4 Method("GET")

設(shè)置http方法恋技, 在該例子中,傳遞了GET方法

// Method specifies what HTTP method to match. Required.
func (b *RouteBuilder) Method(method string) *RouteBuilder {
 b.httpMethod = method
 return b
}
2.1.5 Path(subPath)

設(shè)置RouteBuilder的currentPath設(shè)置為subPath

// Path specifies the relative (w.r.t WebService root path) URL path to match. Default is "/".
func (b *RouteBuilder) Path(subPath string) *RouteBuilder {
 b.currentPath = subPath
 return b
}
2.2 To(hello)

改函數(shù)綁定用戶自定義的的處理函數(shù)handler逻族,當用戶發(fā)起的http訪問, 命中method=GET path = subPath后蜻底,執(zhí)行相關(guān)邏輯的function handler

func hello(req *restful.Request, resp *restful.Response) {
    io.WriteString(resp, "world")
}

實際上在執(zhí)行webservice GET()函數(shù),直接生成了RouteBuilder并返回對象聘鳞, 因此function的綁定朱躺,直接在RouteBuilder中進行

// RouteFunction declares the signature of a function that can be bound to a Route.
type RouteFunction func(*Request, *Response)
// To bind the route to a function.
// If this route is matched with the incoming Http Request then call this function with the *Request,*Response pair. Required.
func (b *RouteBuilder) To(function RouteFunction) *RouteBuilder {
 b.function = function
 return b
}

從代碼中可以看出自定義的函數(shù)類型包括兩部分, 分別是*Request, *Response在restful中搁痛, 定義的request和response都是結(jié)構(gòu)體, 該結(jié)構(gòu)體中定義了http包數(shù)據(jù)各個模塊的存儲及獲取格式

type Request struct {
    Request           *http.Request
    pathParameters    map[string]string
    attributes        map[string]interface{} // for storing request-scoped values
    selectedRoutePath string // root path + route path that matched the request, e.g. /meetings/{id}/attendees
}
type Response struct {
    http.ResponseWriter
    requestAccept string // mime-type what the Http Request says it wants to receive
    routeProduces []string // mime-types what the Route says it can produce
    statusCode    int // HTTP status code that has been written explicitly (if zero then net/http has written 200)
    contentLength int // number of bytes written for the response body
    prettyPrint   bool // controls the indentation feature of XML and JSON serialization. It is initialized using var PrettyPrintResponses.
    err           error // err property is kept when WriteError is called
    hijacker      http.Hijacker // if underlying ResponseWriter supports it
}
2.3 ws.Route(RouteBuilder))]

該函數(shù)主要創(chuàng)建新的路由關(guān)系,并將產(chǎn)生的routebuilder添加到路由表中

// Route creates a new Route using the RouteBuilder and add to the ordered list of Routes.
func (w *WebService) Route(builder *RouteBuilder) *WebService {
    w.routesLock.Lock()
 defer w.routesLock.Unlock()
    builder.copyDefaults(w.produces, w.consumes)
 w.routes = append(w.routes, builder.Build())
 return w
}
2.3.1 w.routesLock.Lock()

在webservice初始化中宇弛,routesLock初始化為routesLock sync.RWMutex,該變量主要作用是在動態(tài)路由使用后鸡典,保護路由表,防止被多線程同時讀寫

2.3.2 defer w.routesLock.Unlock()

采用defer方式golang特性枪芒, 函數(shù)執(zhí)行完成后彻况,stack執(zhí)行,釋放掉持有的鎖

2.3.3 builder.copyDefaults(w.produces, w.consumes)

將produces和consumes賦值給RouteBuilder

2.3.4 RouteBuilder.build()
// Build creates a new Route using the specification details collected by the RouteBuilder
func (b *RouteBuilder) Build() Route {
 pathExpr, err := newPathExpression(b.currentPath)
 if err != nil {
        log.Printf("Invalid path:%s because:%v", b.currentPath, err)
        os.Exit(1)
    }
 if b.function == nil {
        log.Printf("No function specified for route:" + b.currentPath)
        os.Exit(1)
    }
 operationName := b.operation
 if len(operationName) == 0 && b.function != nil {
 // extract from definition
 operationName = nameOfFunction(b.function)
    }
 route := Route{
        Method:         b.httpMethod,
        Path:           concatPath(b.rootPath, b.currentPath),
        Produces:       b.produces,
        Consumes:       b.consumes,
        Function:       b.function,
        Filters:        b.filters,
        If:             b.conditions,
        relativePath:   b.currentPath,
        pathExpr:       pathExpr,
        Doc:            b.doc,
        Notes:          b.notes,
        Operation:      operationName,
        ParameterDocs:  b.parameters,
        ResponseErrors: b.errorMap,
        ReadSample:     b.readSample,
        WriteSample:    b.writeSample,
        Metadata:       b.metadata,
        Deprecated:     b.deprecated}
    route.postBuild()
 return route
}

build的過程主要是初始化參數(shù)的檢查及route初始化過程, 在route的初始化中除了使用routebuiler之前初始化的部分參數(shù)舅踪,還針對path處理了pathexpr纽甘,newPathExpression(b.currentPath)

RouteBuilder.png

pathExpr.png

2.3.5 w.routes = append(w.routes, builder.Build())

將創(chuàng)建的route添加到routes slice中。

  • 可以看出來結(jié)構(gòu)上 route是由routebuilder進行創(chuàng)建
  • 實際上產(chǎn)生作用的是webservice中的route列表
3.restful.Add(ws)
// Add registers a new WebService add it to the DefaultContainer.
func Add(service *WebService) {
    DefaultContainer.Add(service)
}

在ws外面在加了一層container抽碌, 改container在init中進行了初始化

func init() {
 DefaultContainer = NewContainer()
 DefaultContainer.ServeMux = http.DefaultServeMux
}
3.1 DefaultContainer = NewContainer()

創(chuàng)建container對象悍赢,并進行初始化, 因此可以看出ws可以有很多個货徙,統(tǒng)一由container進行維護左权,默認的route路徑/就是在這里進行的賦值

// NewContainer creates a new Container using a new ServeMux and default router (CurlyRouter)
func NewContainer() *Container {
 return &Container{
        webServices:            []*WebService{},
        ServeMux:               http.NewServeMux(),
        isRegisteredOnRoot:     false,
        containerFilters:       []FilterFunction{},
        doNotRecover:           true,
        recoverHandleFunc:      logStackOnRecover,
        serviceErrorHandleFunc: writeServiceError,
        router:                 CurlyRouter{},
        contentEncodingEnabled: false}
}
container.png
3.2 DefaultContainer.Add(service)

從代碼中可以看出來webservice的區(qū)分是通過rootPath進行區(qū)分的, webservices要保證rootpath的唯一性

// Add a WebService to the Container. It will detect duplicate root paths and exit in that case.
func (c *Container) Add(service *WebService) *Container {
    c.webServicesLock.Lock()
 defer c.webServicesLock.Unlock()

 // if rootPath was not set then lazy initialize it
 if len(service.rootPath) == 0 {
        service.Path("/")
    }

 // cannot have duplicate root paths
 for _, each := range c.webServices {
 if each.RootPath() == service.RootPath() {
            log.Printf("WebService with duplicate root path detected:['%v']", each)
            os.Exit(1)
        }
    }

 // If not registered on root then add specific mapping
 if !c.isRegisteredOnRoot {
 c.isRegisteredOnRoot = c.addHandler(service, c.ServeMux)
    }
 c.webServices = append(c.webServices, service)
 return c
}

其中有一點仍然需要注意,在container的綁定中痴颊,只將根目錄注冊到了ServeMux中, 綁定的對應(yīng)的函數(shù)為container.dispatch()赏迟,這樣做的原因是在http請求中,通過golang中net包中的ServeMux進行路由轉(zhuǎn)發(fā)蠢棱,將所有命中根目錄的uri流量分發(fā)到ws中锌杀。

// addHandler may set a new HandleFunc for the serveMux
// this function must run inside the critical region protected by the webServicesLock.
// returns true if the function was registered on root ("/")
func (c *Container) addHandler(service *WebService, serveMux *http.ServeMux) bool {
 pattern := fixedPrefixPath(service.RootPath())
 // check if root path registration is needed
 if "/" == pattern || "" == pattern {
        serveMux.HandleFunc("/", c.dispatch)
 return true
    }
 // detect if registration already exists
 alreadyMapped := false
 for _, each := range c.webServices {
 if each.RootPath() == service.RootPath() {
 alreadyMapped = true
 break
        }
    }
 if !alreadyMapped {
        serveMux.HandleFunc(pattern, c.dispatch)
 if !strings.HasSuffix(pattern, "/") {
            serveMux.HandleFunc(pattern+"/", c.dispatch)
        }
    }
 return false
}
4. log.Fatal(http.ListenAndServe(":8080", nil))

創(chuàng)建端口監(jiān)聽甩栈,將用戶的http請求route到對應(yīng)的函數(shù)中, ListenAndServe 在net package中, http routes相關(guān)由ServMux進行維護, 由于在上文hello worlde的樣子中,container將根目錄及c.dispatch初始化到ServeMux中糕再。因此量没,當發(fā)送get請求后,會跳轉(zhuǎn)到container.dispatch中進行二次http route亿鲜。

// ListenAndServe always returns a non-nil error.
func ListenAndServe(addr string, handler Handler) error {
 server := &Server{Addr: addr, Handler: handler}
 return server.ListenAndServe()
}

restful響應(yīng)流程


1. 路由

上文中通過container允蜈、webservice、route的初始化流程蒿柳,在helloworld的樣例中饶套,外部通過http get訪問時,http package中的servMux進行了第一次路由找到了對應(yīng)的container垒探,后調(diào)用container.dispatch進行二次路由查找

//閉包
func() {
c.webServicesLock.RLock()
defer c.webServicesLock.RUnlock()
webService, route, err = c.router.SelectRoute(
    c.webServices,
    httpRequest)
}()
2 c.router.SelectRoute
func (c CurlyRouter) SelectRoute(
webServices []*WebService,
httpRequest *http.Request) (selectedService *WebService, selected *Route, err error) {

 requestTokens := tokenizePath(httpRequest.URL.Path)

 detectedService := c.detectWebService(requestTokens, webServices)
 if detectedService == nil {
 if trace {
            traceLogger.Printf("no WebService was found to match URL path:%s\n", httpRequest.URL.Path)
        }
 return nil, nil, NewError(http.StatusNotFound, "404: Page Not Found")
    }
 candidateRoutes := c.selectRoutes(detectedService, requestTokens)
 if len(candidateRoutes) == 0 {
 if trace {
            traceLogger.Printf("no Route in WebService with path %s was found to match URL path:%s\n", detectedService.rootPath, httpRequest.URL.Path)
        }
 return detectedService, nil, NewError(http.StatusNotFound, "404: Page Not Found")
    }
 selectedRoute, err := c.detectRoute(candidateRoutes, httpRequest)
 if selectedRoute == nil {
 return detectedService, nil, err
    }
 return detectedService, selectedRoute, nil
}

在路由轉(zhuǎn)換的邏輯中妓蛮, 主要包含了3次路由轉(zhuǎn)換

2.1 路由查找webservice
//從url中split路徑為token列表
requestTokens := tokenizePath(httpRequest.URL.Path)
/*
將獲取到的tokens與webServices中的pathExpr.tokens進行計算最大分值(計算分值的方式是token比對
直到出現(xiàn)不同對于{分數(shù)自增1跳轉(zhuǎn)到下一步),并返回分值最高的匹配
*/
c.detectWebService(requestTokens, webServices)
func (c CurlyRouter) detectWebService(requestTokens []string, webServices []*WebService) *WebService {
 var best *WebService
 score := -1
 for _, each := range webServices {
 //在該例子中each.pathExpr.tokens為空圾叼,如果為空蛤克,默認范圍列表中第一個ws
 matches, eachScore := c.computeWebserviceScore(requestTokens, each.pathExpr.tokens)
 if matches && (eachScore > score) {
 best = each
 score = eachScore
        }
    }
 return best
}
2.2 路由查找Route
func (c CurlyRouter) selectRoutes(ws *WebService, requestTokens []string) sortableCurlyRoutes {
 candidates := sortableCurlyRoutes{}
 for _, each := range ws.routes {
 //遍歷路由查找,將routes中的tokens進行遍歷查找夷蚊, 找到能夠匹配到當前路徑的route
 matches, paramCount, staticCount := c.matchesRouteByPathTokens(each.pathParts, requestTokens)
 if matches {
            candidates.add(curlyRoute{each, paramCount, staticCount}) // TODO make sure Routes() return pointers?
        }
    }
    sort.Sort(sort.Reverse(candidates))
 return candidates
}
func (c CurlyRouter) matchesRouteByPathTokens(routeTokens, requestTokens []string) (matches bool, paramCount int, staticCount int) {
 if len(routeTokens) < len(requestTokens) {
 // proceed in matching only if last routeToken is wildcard
 count := len(routeTokens)
 if count == 0 || !strings.HasSuffix(routeTokens[count-1], "*}") {
 return false, 0, 0
        }
 // proceed
    }
 for i, routeToken := range routeTokens {
 if i == len(requestTokens) {
 // reached end of request path
 return false, 0, 0
        }
 requestToken := requestTokens[i]
 if strings.HasPrefix(routeToken, "{") {
            paramCount++
 if colon := strings.Index(routeToken, ":"); colon != -1 {
 // match by regex
 matchesToken, matchesRemainder := c.regularMatchesPathToken(routeToken, colon, requestToken)
 if !matchesToken {
 return false, 0, 0
                }
 if matchesRemainder {
 break
                }
            }
        } else { // no { prefix
 if requestToken != routeToken {
 return false, 0, 0
            }
            staticCount++
        }
    }
 return true, paramCount, staticCount
}

在routes中查找到的對應(yīng)的route构挤,統(tǒng)計放在sortableCurlyRoute中進行處理,sortableCurlyRoute是一個封裝的按照一定規(guī)則進行排序的curlyRoute數(shù)組

type curlyRoute struct {
    route       Route
    paramCount  int //正則命中
    staticCount int //完全匹配命中
}
2.3. route屬性相關(guān)匹配

在上面中通過path的比對拿到了一個route列表惕鼓, 列表中記錄了route的優(yōu)先級筋现,在列表中的route都保證了對path的命中, 在function的匹配過程中需要依次檢查:

  • http method
     // http method
     methodOk := []Route{}
     for _, each := range ifOk {
        if httpRequest.Method == each.Method {
          methodOk = append(methodOk, each)
         }
     }
     if len(methodOk) == 0 {
       if trace {
         traceLogger.Printf("no Route found (in %d routes) that matches HTTP method %s\n", len(routes), httpRequest.Method)
     }
      return nil, NewError(http.StatusMethodNotAllowed, "405: Method Not Allowed")
     }
    inputMediaOk := methodOk
    
  • content-type
    contentType := httpRequest.Header.Get(HEADER_ContentType)
    inputMediaOk = []Route{}
    for _, each := range methodOk {
    if each.matchesContentType(contentType) {
          inputMediaOk = append(inputMediaOk, each)
      }
    }
    
  • accept
    outputMediaOk := []Route{}
    accept := httpRequest.Header.Get(HEADER_Accept)
    if len(accept) == 0 {
        accept = "*/*"
    }
    for _, each := range inputMediaOk {
      if each.matchesAccept(accept) {
        outputMediaOk = append(outputMediaOk, each)
       }
     }
     if len(outputMediaOk) == 0 {
       if trace {
         traceLogger.Printf("no Route found (from %d) that matches HTTP Accept: %s\n", len(inputMediaOk), accept)
     }
     return nil, NewError(http.StatusNotAcceptable, "406: Not Acceptable")
    }
    
    pathProcessor, routerProcessesPath := c.router.(PathProcessor)
    if !routerProcessesPath {
     pathProcessor = defaultPathProcessor{}
    }
    

在go-restful中支持fliter的定義箱歧, 在執(zhí)行對應(yīng)function之前矾飞,需要對fliter檢查是否命中

pathProcessor, routerProcessesPath := c.router.(PathProcessor)
if !routerProcessesPath {
 pathProcessor = defaultPathProcessor{}
}
pathParams := pathProcessor.ExtractParameters(route, webService, httpRequest.URL.Path)
wrappedRequest, wrappedResponse := route.wrapRequestResponse(writer, httpRequest, pathParams)
// pass through filters (if any)
if len(c.containerFilters)+len(webService.filters)+len(route.Filters) > 0 {
 // compose filter chain
 allFilters := []FilterFunction{}
 allFilters = append(allFilters, c.containerFilters...)
 allFilters = append(allFilters, webService.filters...)
 allFilters = append(allFilters, route.Filters...)
 chain := FilterChain{Filters: allFilters, Target: func(req *Request, resp *Response) {
 // handle request by route after passing all filters
        route.Function(wrappedRequest, wrappedResponse)
    }}
    chain.ProcessFilter(wrappedRequest, wrappedResponse)
} else {
 // no filters, handle request by route
    route.Function(wrappedRequest, wrappedResponse)
}

在hello world例子中沒有fliter相關(guān)功能, 因此可以直接跳轉(zhuǎn)到route.Function(wrappedRequest, wrappedResponse)呀邢,執(zhí)行hello world對應(yīng)的function, function中調(diào)用io.WriteString(resp, "world")將resp寫回到net/server緩沖區(qū)中洒沦,io loop并寫回到socket fd中完成通信。

func hello(req *restful.Request, resp *restful.Response) {
    fmt.Println("hello world")
    io.WriteString(resp, "world")
}

層級關(guān)系


Route

路由包含兩種价淌,一種是標準JSR311接口規(guī)范的實現(xiàn)RouterJSR311,一種是快速路由CurlyRouter申眼。
CurlyRouter支持正則表達式和動態(tài)參數(shù),相比RouterJSR11更加輕量級输钩,apiserver中使用的就是這種路由豺型。
一種Route的設(shè)定包含:請求方法(http Method),請求路徑(URL Path),輸入輸出類型(JSON/YAML)以及對應(yīng)的回掉函數(shù)restful.RouteFunction买乃,響應(yīng)內(nèi)容類型(Accept)等姻氨。

WebService

webservice中維護了route的集合,功能上主要維護了一組route的rootpath、method剪验、fliter等屬性

Container

Container邏輯上是WebService的集合肴焊,功能上可以實現(xiàn)多終端的效果前联,不同的container可以綁定到不同的ip或者port上面

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市娶眷,隨后出現(xiàn)的幾起案子似嗤,更是在濱河造成了極大的恐慌,老刑警劉巖届宠,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件烁落,死亡現(xiàn)場離奇詭異,居然都是意外死亡豌注,警方通過查閱死者的電腦和手機伤塌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來轧铁,“玉大人每聪,你說我怎么就攤上這事绎秒“邕叮” “怎么了崭放?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵嘹履,是天一觀的道長。 經(jīng)常有香客問我羹蚣,道長摄职,這世上最難降的妖魔是什么几颜? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任脸候,我火速辦了婚禮巾陕,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘纪他。我一直安慰自己,他們只是感情好晾匠,可當我...
    茶點故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布茶袒。 她就那樣靜靜地躺著,像睡著了一般凉馆。 火紅的嫁衣襯著肌膚如雪薪寓。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天澜共,我揣著相機與錄音向叉,去河邊找鬼。 笑死嗦董,一個胖子當著我的面吹牛母谎,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播京革,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼奇唤,長吁一口氣:“原來是場噩夢啊……” “哼幸斥!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起咬扇,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤甲葬,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后懈贺,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體经窖,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年梭灿,在試婚紗的時候發(fā)現(xiàn)自己被綠了画侣。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,617評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡胎源,死狀恐怖棉钧,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情涕蚤,我是刑警寧澤宪卿,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站万栅,受9級特大地震影響佑钾,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜烦粒,卻給世界環(huán)境...
    茶點故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一休溶、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧扰她,春花似錦兽掰、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至忧勿,卻和暖如春杉女,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背鸳吸。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工熏挎, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人晌砾。 一個月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓坎拐,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子廉白,可洞房花燭夜當晚...
    茶點故事閱讀 43,486評論 2 348