go http源碼分析

在Go里你只需以下幾行代碼就能實(shí)現(xiàn)一個(gè)http服務(wù)是的沒(méi)錯(cuò)杠娱,不需要容器廷区,不需要服務(wù)器軟件嬉挡。

func main() {
    /**
    第一個(gè)參數(shù):pattern string,
    第二個(gè)參數(shù):handler func(ResponseWriter, *Request)
     */
    http.HandleFunc("/", sayHello)           // 設(shè)置訪問(wèn)的路由
    /**
    第一個(gè)參數(shù)addr:監(jiān)聽地址
    第二個(gè)參數(shù)handler:通常為空胚迫,意味著服務(wù)端調(diào)用http.DefaultServerMux進(jìn)行處理喷户,
    而服務(wù)端編寫的業(yè)務(wù)邏輯處理程序http.Handle()或http.HandleFunc()默認(rèn)注入http.DefaultServeMux中
    */
    err := http.ListenAndServe(":8080", nil) //設(shè)置監(jiān)聽的端口
    if err != nil {
        log.Fatal("ListenAndServe: ", err)
    }

可以看到,是http.ListenAndServer("8080",nil)這個(gè)方法開啟了一個(gè)http服務(wù)晌区,讓我們追進(jìn)去看看摩骨,實(shí)際上,初始化一個(gè)server對(duì)象朗若,調(diào)用了 server 的 ListenAndServe 方法恼五。
下面就是Go/Http服務(wù)的流程


想要看懂這張圖,先來(lái)了解幾個(gè)結(jié)構(gòu)

Handler

任何結(jié)構(gòu)體哭懈,只要實(shí)現(xiàn)了ServeHTTP方法灾馒,這個(gè)結(jié)構(gòu)就可以稱之為handler對(duì)象。ServeMux會(huì)使用handler并調(diào)用其ServeHTTP方法處理請(qǐng)求并返回響應(yīng)遣总。
這個(gè)handler其實(shí)就是真正的處理函數(shù)

type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

ServeMux

ServeMux結(jié)構(gòu)中最重要的字段為m睬罗,這是一個(gè)map,key是一些url模式旭斥,value是一個(gè)muxEntry結(jié)構(gòu)容达,后者里定義存儲(chǔ)了具體的url模式和handler。
m就將路由與相應(yīng)處理函數(shù)綁定了

type ServeMux struct {
    mu    sync.RWMutex
    m     map[string]muxEntry
    es    []muxEntry // slice of entries sorted from longest to shortest.
    hosts bool       // whether any patterns contain hostnames
}

type muxEntry struct {
    h       Handler
    pattern string
}

Server

下面省去了很多部分垂券,我們只關(guān)心一下重點(diǎn)的部分
server結(jié)構(gòu)存儲(chǔ)了服務(wù)器處理請(qǐng)求常見的字段花盐。其中Handler字段也保留Handler接口。如果Server接口沒(méi)有提供Handler結(jié)構(gòu)對(duì)象菇爪,那么會(huì)使用DefautServeMux做multiplexer,(可以看到官方就是這么說(shuō)的)

type Server struct {
    Addr string
     //Server也實(shí)現(xiàn)了Handler接口
    Handler Handler // handler to invoke, http.DefaultServeMux if nil
    // https
    TLSConfig *tls.Config

    ReadTimeout time.Duration
    ReadHeaderTimeout time.Duration
    WriteTimeout time.Duration
    IdleTimeout time.Duration
    MaxHeaderBytes int
    mu         sync.Mutex
    listeners  map[*net.Listener]struct{}
    activeConn map[*conn]struct{}
    doneChan   chan struct{}
    onShutdown []func()
}

HTTP服務(wù)

創(chuàng)建一個(gè)http服務(wù)算芯,大致需要經(jīng)歷兩個(gè)過(guò)程,首先需要注冊(cè)路由凳宙,即提供url模式和handler函數(shù)的映射熙揍,其次就是實(shí)例化一個(gè)server對(duì)象,并開啟對(duì)客戶端的監(jiān)聽氏涩。

注冊(cè)路由

http包提供了默認(rèn)的DefaultServeMux作為路由解析器

// Handle registers the handler for the given pattern
// in the DefaultServeMux.
// The documentation for ServeMux explains how patterns are matched.
func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, handler) }

// HandleFunc registers the handler function for the given pattern
// in the DefaultServeMux.
// The documentation for ServeMux explains how patterns are matched.
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    DefaultServeMux.HandleFunc(pattern, handler)
}
// Handle registers the handler for the given pattern.
// If a handler already exists for pattern, Handle panics.
func (mux *ServeMux) Handle(pattern string, handler Handler) {
    mux.mu.Lock()
    defer mux.mu.Unlock()

    if pattern == "" {
        panic("http: invalid pattern")
    }
    if handler == nil {
        panic("http: nil handler")
    }
    if _, exist := mux.m[pattern]; exist {
        panic("http: multiple registrations for " + pattern)
    }

    if mux.m == nil {
        mux.m = make(map[string]muxEntry)
    }
    e := muxEntry{h: handler, pattern: pattern}
    mux.m[pattern] = e
    if pattern[len(pattern)-1] == '/' {
        mux.es = appendSorted(mux.es, e)
    }

    if pattern[0] != '/' {
        mux.hosts = true
    }
}

這個(gè)就是路由與handler的綁定届囚,很好理解

// HandleFunc registers the handler function for the given pattern.
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    if handler == nil {
        panic("http: nil handler")
    }
        //HandlerFunc(handler),注意這里將handler轉(zhuǎn)成了HandlerFunc類型
    mux.Handle(pattern, HandlerFunc(handler))
}

HandlerFunc是一個(gè)函數(shù)類型是尖。同時(shí)實(shí)現(xiàn)了Handler接口的ServeHTTP方法奖亚。使用HandlerFunc類型包裝一下路由定義的sayHello函數(shù)(類型轉(zhuǎn)換),
HandlerFunc(handler)
其目的就是為了讓這個(gè)函數(shù)也實(shí)現(xiàn)ServeHTTP方法析砸,即轉(zhuǎn)變成一個(gè)handler處理器(函數(shù))昔字。

// The HandlerFunc type is an adapter to allow the use of
// ordinary functions as HTTP handlers. If f is a function
// with the appropriate signature, HandlerFunc(f) is a
// Handler that calls f.
type HandlerFunc func(ResponseWriter, *Request)

// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
    f(w, r)
}

一旦這樣做了,就意味著我們的 sayHello函數(shù)也有了ServeHTTP方法。
此外作郭,ServeMux的Handle方法陨囊,將會(huì)對(duì)pattern和handler函數(shù)做一個(gè)map映射

可以看到其實(shí)DefaultServeMux就是ServeMux的一個(gè)實(shí)例,當(dāng)然我們也可以創(chuàng)建自己的ServeMux夹攒,http包也提供了這個(gè)方法蜘醋。
很多框架其實(shí)就是實(shí)現(xiàn)了自己的ServeMux

// NewServeMux allocates and returns a new ServeMux.
func NewServeMux() *ServeMux { return new(ServeMux) }

// DefaultServeMux is the default ServeMux used by Serve.
var DefaultServeMux = &defaultServeMux

var defaultServeMux ServeMux

Handle函數(shù)的主要目的在于把handler和pattern模式綁定到map[string]muxEntry的map上,其中muxEntry保存了更多pattern和handler的信息咏尝,還記得前面討論的Server結(jié)構(gòu)嗎压语?Server的m字段就是map[string]muxEntry這樣一個(gè)map。

此時(shí)编检,pattern和handler的路由注冊(cè)完成胎食。接下來(lái)就是如何開始server的監(jiān)聽,以接收客戶端的請(qǐng)求允懂。

server監(jiān)聽

func ListenAndServe(addr string, handler Handler) error {
    server := &Server{Addr: addr, Handler: handler}
    return server.ListenAndServe()
}

這里初始化了一個(gè)Server厕怜,這里的Handler也提到了,為nil的話會(huì)使用默認(rèn)使用http.DefaultServeMux蕾总,當(dāng)然你也可以自己初始化粥航,而很多框架就是這么干的。

func (srv *Server) ListenAndServe() error {
//如果正在關(guān)閉服務(wù)生百,就返回一個(gè)var ErrServerClosed = errors.New("http: Server closed")
    if srv.shuttingDown() {
        return ErrServerClosed
    }
    addr := srv.Addr
    if addr == "" {
        addr = ":http"
    }
//開啟tcp端口監(jiān)聽
    ln, err := net.Listen("tcp", addr)
    if err != nil {
        return err
    }
//server服務(wù)的
    return srv.Serve(ln)
}

這里就是開啟了一個(gè)監(jiān)聽递雀,并把這個(gè)監(jiān)聽傳給Serve函數(shù),讓它做相應(yīng)的處理蚀浆∽撼蹋看到這兩個(gè)函數(shù)返回類型都是error

func (srv *Server) Serve(l net.Listener) error 

監(jiān)聽開啟之后,一旦客戶端請(qǐng)求到底蜡坊,go就開啟一個(gè)協(xié)程處理請(qǐng)求,主要邏輯都在serve方法之中赎败。

serve方法比較長(zhǎng)见秽,其主要職能就是膨蛮,創(chuàng)建一個(gè)上下文對(duì)象,然后調(diào)用Listener的Accept方法用來(lái) 獲取連接數(shù)據(jù)并使用newConn方法創(chuàng)建連接對(duì)象。最后使用goroutein協(xié)程的方式處理連接請(qǐng)求倦西。因?yàn)槊恳粋€(gè)連接都開起了一個(gè)協(xié)程,請(qǐng)求的上下文都不同脊髓,同時(shí)又保證了go的高并發(fā)蜂科。serve也是一個(gè)長(zhǎng)長(zhǎng)的方法:

func (srv *Server) Serve(l net.Listener) error {
    //測(cè)試鉤子
    if fn := testHookServerServe; fn != nil {
        fn(srv, l) // call hook with unwrapped listener
    }

    origListener := l
//這里將傳入的l封裝成onceClose類型,oncloseListener包裝一個(gè)net.Listener窍仰,
//以保護(hù)它免受多個(gè)Close調(diào)用的影響汉规。
    l = &onceCloseListener{Listener: l}
    defer l.Close()

    if err := srv.setupHTTP2_Serve(); err != nil {
        return err
    }

    if !srv.trackListener(&l, true) {
        return ErrServerClosed
    }
    defer srv.trackListener(&l, false)
 //這里創(chuàng)建了一個(gè)上下文,有興趣的也可以去看看Context的源碼分析,主要用于控制goroutine
//BaseContext可選地指定一個(gè)函數(shù)针史,該函數(shù)返回此服務(wù)器上傳入請(qǐng)求的基本上下文晶伦。
//提供的Listener是即將開始接受請(qǐng)求的特定Listener。如果BaseContext為nil啄枕,
//則默認(rèn)值為context.Background()婚陪。如果非nil,則為 必須返回非nil上下文频祝。
    baseCtx := context.Background()
    if srv.BaseContext != nil {
        baseCtx = srv.BaseContext(origListener)
        if baseCtx == nil {
            panic("BaseContext returned a nil context")
        }
    }

    var tempDelay time.Duration // how long to sleep on accept failure
    //寫入一個(gè)值
    //ServerContextKey = &contextKey{"http-server"}
//ServerContextKey是上下文key泌参。 
//可以在帶有Context.Value的HTTP處理程序中使用它來(lái)訪問(wèn)啟動(dòng)處理程序的服務(wù)器。 
//關(guān)聯(lián)的值將為* Server類型常空。
    ctx := context.WithValue(baseCtx, ServerContextKey, srv)
    for {
        //從l里一直接收請(qǐng)求
       // Accept waits for and returns the next connection to the listener.
        rw, err := l.Accept()
        if err != nil {
            select {
            //外部主動(dòng)調(diào)用close方法
            case <-srv.getDoneChan():
                return ErrServerClosed
            default:
            }
            //嘗試等待沽一,重新嘗試
            if ne, ok := err.(net.Error); ok && ne.Temporary() {
                if tempDelay == 0 {
                    tempDelay = 5 * time.Millisecond
                } else {
                    tempDelay *= 2
                }
                if max := 1 * time.Second; tempDelay > max {
                    tempDelay = max
                }
                srv.logf("http: Accept error: %v; retrying in %v", err, tempDelay)
                time.Sleep(tempDelay)
                continue
            }
            return err
        }
        connCtx := ctx
//ConnContext可選地指定一個(gè)函數(shù),該函數(shù)修改用于新連接的上下文c窟绷。 
//提供的ctx派生自基本上下文锯玛,并且具有ServerContextKeyvalue。
        if cc := srv.ConnContext; cc != nil {
            connCtx = cc(connCtx, rw)
            if connCtx == nil {
                panic("ConnContext returned nil")
            }
        }
//重置時(shí)延并創(chuàng)建新連接
        tempDelay = 0
        c := srv.newConn(rw)
        c.setState(c.rwc, StateNew) // before Serve can return
        go c.serve(connCtx)
    }
}

再來(lái)看看serve函數(shù)

// Serve a new connection.
func (c *conn) serve(ctx context.Context) {
    //獲取遠(yuǎn)程地址
    c.remoteAddr = c.rwc.RemoteAddr().String()
    //保存本地地址
    ctx = context.WithValue(ctx, LocalAddrContextKey, c.rwc.LocalAddr())
    //defer收尾工作
    defer func() {
        if err := recover(); err != nil && err != ErrAbortHandler {
            const size = 64 << 10
            buf := make([]byte, size)
            buf = buf[:runtime.Stack(buf, false)]
            c.server.logf("http: panic serving %v: %v\n%s", c.remoteAddr, err, buf)
        }
        if !c.hijacked() {
            c.close()
            c.setState(c.rwc, StateClosed)
        }
    }()
    //安全連接
    if tlsConn, ok := c.rwc.(*tls.Conn); ok {
        if d := c.server.ReadTimeout; d != 0 {
            c.rwc.SetReadDeadline(time.Now().Add(d))
        }
        if d := c.server.WriteTimeout; d != 0 {
            c.rwc.SetWriteDeadline(time.Now().Add(d))
        }
        //握手
        if err := tlsConn.Handshake(); err != nil {
            // If the handshake failed due to the client not speaking
            // TLS, assume they're speaking plaintext HTTP and write a
            // 400 response on the TLS conn's underlying net.Conn.
            if re, ok := err.(tls.RecordHeaderError); ok && re.Conn != nil && tlsRecordHeaderLooksLikeHTTP(re.RecordHeader) {
                io.WriteString(re.Conn, "HTTP/1.0 400 Bad Request\r\n\r\nClient sent an HTTP request to an HTTPS server.\n")
                re.Conn.Close()
                return
            }
            c.server.logf("http: TLS handshake error from %s: %v", c.rwc.RemoteAddr(), err)
            return
        }
        c.tlsState = new(tls.ConnectionState)
        *c.tlsState = tlsConn.ConnectionState()
        if proto := c.tlsState.NegotiatedProtocol; validNextProto(proto) {
            if fn := c.server.TLSNextProto[proto]; fn != nil {
                h := initALPNRequest{ctx, tlsConn, serverHandler{c.server}}
                fn(c.server, tlsConn, h)
            }
            return
        }
    }

    // HTTP/1.x from here on.
    //封裝取消
    ctx, cancelCtx := context.WithCancel(ctx)
    c.cancelCtx = cancelCtx
    defer cancelCtx()
    //把連接封裝到讀的結(jié)構(gòu)體
    c.r = &connReader{conn: c}
    //緩沖IO
    c.bufr = newBufioReader(c.r)
    //
    c.bufw = newBufioWriterSize(checkConnErrorWriter{c}, 4<<10)

    //讀取多次請(qǐng)求的包
    for {
        //w是一個(gè)Response
        w, err := c.readRequest(ctx)
        if c.r.remain != c.server.initialReadLimitSize() {
            // If we read any bytes off the wire, we're active.
            c.setState(c.rwc, StateActive)
        }
        //這里是錯(cuò)誤處理就略過(guò)了

        // HTTP cannot have multiple simultaneous active requests.[*]
        // Until the server replies to this request, it can't read another,
        // so we might as well run the handler in this goroutine.
        // [*] Not strictly true: HTTP pipelining. We could let them all process
        // in parallel even if their responses need to be serialized.
        // But we're not going to implement HTTP pipelining because it
        // was never deployed in the wild and the answer is HTTP/2.

        serverHandler{c.server}.ServeHTTP(w, w.req)


        w.cancelCtx()
        if c.hijacked() {
            return
        }
        w.finishRequest()
        if !w.shouldReuseConnection() {
            if w.requestBodyLimitHit || w.closedRequestBodyEarly() {
                c.closeWriteAndWait()
            }
            return
        }
        c.setState(c.rwc, StateIdle)
        c.curReq.Store((*response)(nil))

        if !w.conn.server.doKeepAlives() {
            // We're in shutdown mode. We might've replied
            // to the user without "Connection: close" and
            // they might think they can send another
            // request, but such is life with HTTP/1.1.
            return
        }

        if d := c.server.idleTimeout(); d != 0 {
            c.rwc.SetReadDeadline(time.Now().Add(d))
            if _, err := c.bufr.Peek(4); err != nil {
                return
            }
        }
        c.rwc.SetReadDeadline(time.Time{})
    }
}

盡管serve很長(zhǎng)兼蜈,里面的結(jié)構(gòu)和邏輯還是很清晰的攘残,使用defer定義了函數(shù)退出時(shí),連接關(guān)閉相關(guān)的處理为狸。然后就是讀取連接的網(wǎng)絡(luò)數(shù)據(jù)歼郭,并處理讀取完畢時(shí)候的狀態(tài)。接下來(lái)就是調(diào)用serverHandler{c.server}.ServeHTTP(w, w.req)方法處理請(qǐng)求了辐棒。最后就是請(qǐng)求處理完畢的邏輯病曾。serverHandler是一個(gè)重要的結(jié)構(gòu),它近有一個(gè)字段漾根,即Server結(jié)構(gòu)泰涂,同時(shí)它也實(shí)現(xiàn)了Handler接口方法ServeHTTP,并在該接口方法中做了一個(gè)重要的事情辐怕,初始化multiplexer路由多路復(fù)用器逼蒙。如果server對(duì)象沒(méi)有指定Handler,則使用默認(rèn)的DefaultServeMux作為路由Multiplexer寄疏。并調(diào)用初始化Handler的ServeHTTP方法是牢。

serverHandler{c.server}.ServeHTTP(w, w.req)

type serverHandler struct {
    srv *Server
}

func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
    handler := sh.srv.Handler
    if handler == nil {
        handler = DefaultServeMux
    }
    if req.RequestURI == "*" && req.Method == "OPTIONS" {
        handler = globalOptionsHandler{}
    }
    handler.ServeHTTP(rw, req)
}

這里DefaultServeMux的ServeHTTP方法其實(shí)也是定義在ServeMux結(jié)構(gòu)中的,相關(guān)代碼如下:

func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
    if r.RequestURI == "*" {
        if r.ProtoAtLeast(1, 1) {
            w.Header().Set("Connection", "close")
        }
        w.WriteHeader(StatusBadRequest)
        return
    }
    h, _ := mux.Handler(r)
    h.ServeHTTP(w, r)
}

Handler函數(shù)如下
Handler返回用于給定請(qǐng)求的處理程序陕截,r.Method驳棱,r.Host和r.URL.Path。 它總是返回一個(gè)非nilhandler农曲。 如果路徑的格式不規(guī)范社搅,則該handler將是內(nèi)部生成的處理程序,該handler將重定向到規(guī)范路徑。 如果主機(jī)包含端口罚渐,則在匹配handler時(shí)將忽略該端口却汉。
//
// CONNECT請(qǐng)求的路徑和主機(jī)保持不變。
//
//handler還會(huì)返回與請(qǐng)求匹配的注冊(cè)pattern荷并,如果是內(nèi)部生成的重定向合砂,則將返回在重定向之后匹配的pattern。
//
//如果沒(méi)有適用于該請(qǐng)求的注冊(cè)handler源织,則處理程序返回一個(gè)“找不到頁(yè)面”handler和一個(gè)空pattern翩伪。

func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {

    // CONNECT requests are not canonicalized.
    if r.Method == "CONNECT" {
        // If r.URL.Path is /tree and its handler is not registered,
        // the /tree -> /tree/ redirect applies to CONNECT requests
        // but the path canonicalization does not.
        if u, ok := mux.redirectToPathSlash(r.URL.Host, r.URL.Path, r.URL); ok {
            return RedirectHandler(u.String(), StatusMovedPermanently), u.Path
        }

        return mux.handler(r.Host, r.URL.Path)
    }

    // All other requests have any port stripped and path cleaned
    // before passing to mux.handler.
    host := stripHostPort(r.Host)
    path := cleanPath(r.URL.Path)

    // If the given path is /tree and its handler is not registered,
    // redirect for /tree/.
    if u, ok := mux.redirectToPathSlash(host, path, r.URL); ok {
        return RedirectHandler(u.String(), StatusMovedPermanently), u.Path
    }

    if path != r.URL.Path {
        _, pattern = mux.handler(host, path)
        url := *r.URL
        url.Path = path
        return RedirectHandler(url.String(), StatusMovedPermanently), pattern
    }

    return mux.handler(host, r.URL.Path)
}
func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) {
    mux.mu.RLock()
    defer mux.mu.RUnlock()

    // Host-specific pattern takes precedence over generic ones
    if mux.hosts {
        h, pattern = mux.match(host + path)
    }
    if h == nil {
        h, pattern = mux.match(path)
    }
    if h == nil {
        h, pattern = NotFoundHandler(), ""
    }
    return
}
func (mux *ServeMux) match(path string) (h Handler, pattern string) {
    // Check for exact match first.
    v, ok := mux.m[path]
    if ok {
        return v.h, v.pattern
    }

    // Check for longest valid match.  mux.es contains all patterns
    // that end in / sorted from longest to shortest.
    for _, e := range mux.es {
        if strings.HasPrefix(path, e.pattern) {
            return e.h, e.pattern
        }
    }
    return nil, ""
}
h, _ := mux.Handler(r)
h.ServeHTTP(w, r)

h, _ := mux.Handler(r)在找到對(duì)應(yīng)的Handler之后 就執(zhí)行他的ServeHTTP方法,也就是我們定義的真正的handler處理業(yè)務(wù)邏輯的方法谈息。

結(jié)束

至此缘屹,整個(gè)http服務(wù)的流程已經(jīng)講清楚了,當(dāng)然里面還有很多細(xì)節(jié)沒(méi)說(shuō)侠仇,比如reponse和request的結(jié)構(gòu)轻姿,這些就留給大家去看了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末逻炊,一起剝皮案震驚了整個(gè)濱河市互亮,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌余素,老刑警劉巖豹休,帶你破解...
    沈念sama閱讀 217,734評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異桨吊,居然都是意外死亡威根,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門视乐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)洛搀,“玉大人,你說(shuō)我怎么就攤上這事佑淀×裘溃” “怎么了?”我有些...
    開封第一講書人閱讀 164,133評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵渣聚,是天一觀的道長(zhǎng)独榴。 經(jīng)常有香客問(wèn)我僧叉,道長(zhǎng)奕枝,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,532評(píng)論 1 293
  • 正文 為了忘掉前任瓶堕,我火速辦了婚禮隘道,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己谭梗,他們只是感情好忘晤,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,585評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著激捏,像睡著了一般设塔。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上远舅,一...
    開封第一講書人閱讀 51,462評(píng)論 1 302
  • 那天闰蛔,我揣著相機(jī)與錄音,去河邊找鬼图柏。 笑死序六,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的蚤吹。 我是一名探鬼主播例诀,決...
    沈念sama閱讀 40,262評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼裁着!你這毒婦竟也來(lái)了繁涂?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,153評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤跨算,失蹤者是張志新(化名)和其女友劉穎爆土,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體诸蚕,經(jīng)...
    沈念sama閱讀 45,587評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡步势,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,792評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了背犯。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片坏瘩。...
    茶點(diǎn)故事閱讀 39,919評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖漠魏,靈堂內(nèi)的尸體忽然破棺而出倔矾,到底是詐尸還是另有隱情,我是刑警寧澤柱锹,帶...
    沈念sama閱讀 35,635評(píng)論 5 345
  • 正文 年R本政府宣布哪自,位于F島的核電站,受9級(jí)特大地震影響禁熏,放射性物質(zhì)發(fā)生泄漏壤巷。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,237評(píng)論 3 329
  • 文/蒙蒙 一瞧毙、第九天 我趴在偏房一處隱蔽的房頂上張望胧华。 院中可真熱鬧寄症,春花似錦、人聲如沸矩动。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)悲没。三九已至篮迎,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間示姿,已是汗流浹背柑潦。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留峻凫,地道東北人渗鬼。 一個(gè)月前我還...
    沈念sama閱讀 48,048評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像荧琼,于是被迫代替她去往敵國(guó)和親譬胎。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,864評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容