Golang構(gòu)建HTTP服務(wù)(一)--- net/http庫(kù)源碼筆記

實(shí)現(xiàn)一個(gè)最簡(jiǎn)單HTTP server需要多少代碼?只需要一行颅湘,Python2的python -m SimpleHTTPServer,ruby的ruby -run -e httpd . -p 8888栗精。對(duì)于Golang闯参,實(shí)現(xiàn)一個(gè)最簡(jiǎn)單的http server也用不著幾行,卻能帶來(lái)更具殺傷力的性能悲立。

一個(gè)Go最簡(jiǎn)單的http服務(wù)器:

package main

import (
    "fmt"
    "net/http"
)

func IndexHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintln(w, "hello world")
}

func main() {
    http.HandleFunc("/", IndexHandler)
    http.ListenAndServe("127.0.0.0:8000", nil)
}

上述代碼究竟有什么魔法呢鹿寨?下面將會(huì)解密go的net/http實(shí)現(xiàn)http服務(wù)的原理。學(xué)習(xí)net/http源碼不僅可以學(xué)習(xí)網(wǎng)絡(luò)編程中常見(jiàn)范式薪夕,也是學(xué)習(xí)Golang接口設(shè)計(jì)哲學(xué)脚草。

HTTP

網(wǎng)絡(luò)發(fā)展,很多網(wǎng)絡(luò)應(yīng)用都是構(gòu)建再 HTTP 服務(wù)基礎(chǔ)之上原献。HTTP 協(xié)議從誕生到現(xiàn)在玩讳,發(fā)展從1.0涩蜘,1.1到2.0也不斷再進(jìn)步。除去細(xì)節(jié)熏纯,理解 HTTP 構(gòu)建的網(wǎng)絡(luò)應(yīng)用只要關(guān)注兩個(gè)端---客戶(hù)端(clinet)和服務(wù)端(server)同诫,兩個(gè)端的交互來(lái)自 clinet 的 request,以及server端的response樟澜。所謂的http服務(wù)器误窖,主要在于如何接受 clinet 的 request,并向client返回response秩贰。

接收request的過(guò)程中霹俺,最重要的莫過(guò)于路由(router),即實(shí)現(xiàn)一個(gè)Multiplexer器毒费。Go中既可以使用內(nèi)置的mutilplexer --- DefautServeMux丙唧,也可以自定義。Multiplexer路由的目的就是為了找到處理器函數(shù)(handler)觅玻,后者將對(duì)request進(jìn)行處理想际,同時(shí)構(gòu)建response。

簡(jiǎn)單總結(jié)就是這個(gè)流程為:

Clinet -> Requests ->  [Multiplexer(router) -> handler  -> Response -> Clinet

因此溪厘,理解go中的http服務(wù)胡本,最重要就是要理解Multiplexer和handler,Golang中的Multiplexer基于ServeMux結(jié)構(gòu)畸悬,同時(shí)也實(shí)現(xiàn)了Handler接口侧甫。

對(duì)于handler的其實(shí)沒(méi)有合適的中文詞語(yǔ),只可意會(huì)蹋宦,不可言傳的感覺(jué)披粟。為了更好的說(shuō)明問(wèn)題,本文約定了如下規(guī)則:

  • hander函數(shù): 具有func(w http.ResponseWriter, r *http.Requests)簽名的函數(shù)
  • handler處理器(函數(shù)): 經(jīng)過(guò)HandlerFunc結(jié)構(gòu)包裝的handler函數(shù)冷冗,它實(shí)現(xiàn)了ServeHTTP接口方法的函數(shù)守屉。調(diào)用handler處理器的ServeHTTP方法時(shí),即調(diào)用handler函數(shù)本身贾惦。
  • handler對(duì)象:實(shí)現(xiàn)了Handler接口ServeHTTP方法的結(jié)構(gòu)。

handler處理器和handler對(duì)象的差別在于敦捧,一個(gè)是函數(shù)须板,另外一個(gè)是結(jié)構(gòu),它們都有實(shí)現(xiàn)了ServeHTTP方法兢卵。很多情況下它們的功能類(lèi)似习瑰,下文就使用統(tǒng)稱(chēng)為handler。這算是Golang通過(guò)接口實(shí)現(xiàn)的類(lèi)動(dòng)態(tài)類(lèi)型吧秽荤。

hander-handler處理器.jpeg

Golang的http處理流程可以用下面一張圖表示甜奄,后面內(nèi)容是針對(duì)圖進(jìn)行說(shuō)明:

go-http.png

Handler

Golang沒(méi)有繼承柠横,類(lèi)多態(tài)的方式可以通過(guò)接口實(shí)現(xiàn)。所謂接口則是定義聲明了函數(shù)簽名课兄,任何結(jié)構(gòu)只要實(shí)現(xiàn)了與接口函數(shù)簽名相同的方法牍氛,就等同于實(shí)現(xiàn)了接口。go的http服務(wù)都是基于handler進(jìn)行處理烟阐。

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

任何結(jié)構(gòu)體搬俊,只要實(shí)現(xiàn)了ServeHTTP方法,這個(gè)結(jié)構(gòu)就可以稱(chēng)之為handler對(duì)象蜒茄。ServeMux會(huì)使用handler并調(diào)用其ServeHTTP方法處理請(qǐng)求并返回響應(yīng)唉擂。

ServeMux

了解了Handler之后,再看ServeMux檀葛。ServeMux的源碼很簡(jiǎn)單:

type ServeMux struct {
    mu    sync.RWMutex
    m     map[string]muxEntry
    hosts bool 
}

type muxEntry struct {
    explicit bool
    h        Handler
    pattern  string
}

ServeMux結(jié)構(gòu)中最重要的字段為m玩祟,這是一個(gè)map,key是一些url模式屿聋,value是一個(gè)muxEntry結(jié)構(gòu)空扎,后者里定義存儲(chǔ)了具體的url模式和handler。

當(dāng)然胜臊,所謂的ServeMux也實(shí)現(xiàn)了ServeHTTP接口勺卢,也算是一個(gè)handler,不過(guò)ServeMux的ServeHTTP方法不是用來(lái)處理request和respone象对,而是用來(lái)找到路由注冊(cè)的handler黑忱,后面再做解釋。

Server

除了ServeMux和Handler勒魔,還有一個(gè)結(jié)構(gòu)Server需要了解甫煞。從http.ListenAndServe的源碼可以看出,它創(chuàng)建了一個(gè)server對(duì)象冠绢,并調(diào)用server對(duì)象的ListenAndServe方法:

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

查看server的結(jié)構(gòu)如下:

type Server struct {
    Addr         string        
    Handler      Handler       
    ReadTimeout  time.Duration 
    WriteTimeout time.Duration 
    TLSConfig    *tls.Config   

    MaxHeaderBytes int

    TLSNextProto map[string]func(*Server, *tls.Conn, Handler)

    ConnState func(net.Conn, ConnState)
    ErrorLog *log.Logger
    disableKeepAlives int32     nextProtoOnce     sync.Once 
    nextProtoErr      error     
}

server結(jié)構(gòu)存儲(chǔ)了服務(wù)器處理請(qǐng)求常見(jiàn)的字段抚吠。其中Handler字段也保留Handler接口。如果Server接口沒(méi)有提供Handler結(jié)構(gòu)對(duì)象弟胀,那么會(huì)使用DefautServeMux做multiplexer楷力,后面再做分析。

創(chuàng)建HTTP服務(wù)

創(chuàng)建一個(gè)http服務(wù)孵户,大致需要經(jīng)歷兩個(gè)過(guò)程萧朝,首先需要注冊(cè)路由,即提供url模式和handler函數(shù)的映射夏哭,其次就是實(shí)例化一個(gè)server對(duì)象检柬,并開(kāi)啟對(duì)客戶(hù)端的監(jiān)聽(tīng)。

再看gohttp服務(wù)的代碼

http.HandleFunc("/", indexHandler)

即是注冊(cè)路由竖配。

http.ListenAndServe("127.0.0.1:8000", nil)

或者:

server := &Server{Addr: addr, Handler: handler}

server.ListenAndServe()

注冊(cè)路由

閱讀框架源碼是學(xué)習(xí)的好方式何址,通常閱讀也有兩個(gè)方法里逆,一是不求甚解,框架的主要流程要清晰用爪,別的細(xì)枝末節(jié)原押,如果尚不能理解作者的用意,可以先忽略项钮,不必馬上深究班眯;其次,庖丁解牛烁巫,對(duì)于作者想要表達(dá)的主要流程署隘,一定要明確,執(zhí)行的邏輯和結(jié)構(gòu)亚隙。兩者看起來(lái)略矛盾磁餐,其實(shí)不然。大體而言就是對(duì)主流程要清晰阿弃,主流程以外的細(xì)節(jié)需要先忽略诊霹。最簡(jiǎn)單實(shí)踐方式就是,看不懂的就先放一邊渣淳。直到所有的都看不懂脾还,再回去看以前不懂的部分,搞懂為止入愧。下面就查看http是如何注冊(cè)路由鄙漏。

net/http包暴露的注冊(cè)路由的api很簡(jiǎn)單,http.HandleFunc選取了DefaultServeMux作為multiplexer:

func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    DefaultServeMux.HandleFunc(pattern, handler)
}

那么什么是DefaultServeMux呢棺蛛?實(shí)際上怔蚌,DefaultServeMux是ServeMux的一個(gè)實(shí)例。當(dāng)然http包也提供了NewServeMux方法創(chuàng)建一個(gè)ServeMux實(shí)例旁赊,默認(rèn)則創(chuàng)建一個(gè)DefaultServeMux:

// 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

注意桦踊,go創(chuàng)建實(shí)例的過(guò)程中,也可以使用指針?lè)绞街粘?br> type Server struct{}
server := Server{}
和下面的一樣都可以創(chuàng)建Server的實(shí)例
var DefalutServer Server
var server = &DefalutServer


因此DefaultServeMux的HandleFunc(pattern, handler)方法實(shí)際是定義在ServeMux下的:

func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
mux.Handle(pattern, HandlerFunc(handler))
}


上述代碼中籍胯,HandlerFunc是一個(gè)函數(shù)類(lèi)型。同時(shí)實(shí)現(xiàn)了Handler接口的ServeHTTP方法离福。使用HandlerFunc類(lèi)型包裝一下路由定義的indexHandler函數(shù)杖狼,其目的就是為了讓這個(gè)函數(shù)也實(shí)現(xiàn)ServeHTTP方法,即轉(zhuǎn)變成一個(gè)handler處理器(函數(shù))术徊。

type HandlerFunc func(ResponseWriter, *Request)

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


一旦這樣做了本刽,就意味著我們的 indexHandler 函數(shù)也有了ServeHTTP方法鲸湃。

此外赠涮,ServeMux的Handle方法子寓,將會(huì)對(duì)pattern和handler函數(shù)做一個(gè)map映射:

func (mux *ServeMux) Handle(pattern string, handler Handler) {
mux.mu.Lock()
defer mux.mu.Unlock()

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

if mux.m == nil {
    mux.m = make(map[string]muxEntry)
}
mux.m[pattern] = muxEntry{explicit: true, h: handler, pattern: pattern}

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

n := len(pattern)
if n > 0 && pattern[n-1] == '/' && !mux.m[pattern[0:n-1]].explicit {

    path := pattern
    if pattern[0] != '/' {
        path = pattern[strings.Index(pattern, "/"):]
    }
    url := &url.URL{Path: path}
    mux.m[pattern[0:n-1]] = muxEntry{h: RedirectHandler(url.String(), StatusMovedPermanently), pattern: pattern}
}

}


由此可見(jiàn),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)就是如何開(kāi)始server的監(jiān)聽(tīng)国拇,以接收客戶(hù)端的請(qǐng)求洛史。

#### 開(kāi)啟監(jiān)聽(tīng)

注冊(cè)好路由之后,啟動(dòng)web服務(wù)還需要開(kāi)啟服務(wù)器監(jiān)聽(tīng)酱吝。http的ListenAndServer方法中可以看到創(chuàng)建了一個(gè)Server對(duì)象也殖,并調(diào)用了Server對(duì)象的同名方法:

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

func (srv Server) ListenAndServe() error {
addr := srv.Addr
if addr == "" {
addr = ":http"
}
ln, err := net.Listen("tcp", addr)
if err != nil {
return err
}
return srv.Serve(tcpKeepAliveListener{ln.(
net.TCPListener)})
}


Server的ListenAndServe方法中,會(huì)初始化監(jiān)聽(tīng)地址Addr务热,同時(shí)調(diào)用Listen方法設(shè)置監(jiān)聽(tīng)忆嗜。最后將監(jiān)聽(tīng)的TCP對(duì)象傳入Serve方法:

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

baseCtx := context.Background()
ctx := context.WithValue(baseCtx, ServerContextKey, srv)
ctx = context.WithValue(ctx, LocalAddrContextKey, l.Addr())
for {
    rw, e := l.Accept()
    ...
    c := srv.newConn(rw)
    c.setState(c.rwc, StateNew) // before Serve can return
    go c.serve(ctx)
}

}


#### 處理請(qǐng)求

監(jiān)聽(tīng)開(kāi)啟之后,一旦客戶(hù)端請(qǐng)求到底崎岂,go就開(kāi)啟一個(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è)連接都開(kāi)起了一個(gè)協(xié)程,請(qǐng)求的上下文都不同嫁审,同時(shí)又保證了go的高并發(fā)跋炕。serve也是一個(gè)長(zhǎng)長(zhǎng)的方法:

func (c *conn) serve(ctx context.Context) {
c.remoteAddr = c.rwc.RemoteAddr().String()
defer func() {
if err := recover(); err != nil {
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)
}
}()

...

for {
    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)
    }
    ...
    
    }
    
    ...
  
    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)
}

}

盡管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方法。

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) (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)
}

func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
if r.Method != "CONNECT" {
if p := cleanPath(r.URL.Path); p != r.URL.Path {
_, pattern = mux.handler(r.Host, p)
url := *r.URL
url.Path = p
return RedirectHandler(url.String(), StatusMovedPermanently), pattern
}
}
return mux.handler(r.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) {
var n = 0
for k, v := range mux.m {
if !pathMatch(k, path) {
continue
}
if h == nil || len(k) > n {
n = len(k)
h = v.h
pattern = v.pattern
}
}
return
}


mux的ServeHTTP方法通過(guò)調(diào)用其Handler方法尋找注冊(cè)到路由上的handler函數(shù)街图,并調(diào)用該函數(shù)的ServeHTTP方法,本例則是IndexHandler函數(shù)懒构。

mux的Handler方法對(duì)URL簡(jiǎn)單的處理餐济,然后調(diào)用handler方法,后者會(huì)創(chuàng)建一個(gè)鎖胆剧,同時(shí)調(diào)用match方法返回一個(gè)handler和pattern颤介。

在match方法中,mux的m字段是map[string]muxEntry圖赞赖,后者存儲(chǔ)了pattern和handler處理器函數(shù)滚朵,因此通過(guò)迭代m尋找出注冊(cè)路由的patten模式與實(shí)際url匹配的handler函數(shù)并返回。

返回的結(jié)構(gòu)一直傳遞到mux的ServeHTTP方法前域,接下來(lái)調(diào)用handler函數(shù)的ServeHTTP方法辕近,即IndexHandler函數(shù),然后把response寫(xiě)到http.RequestWirter對(duì)象返回給客戶(hù)端匿垄。

上述函數(shù)運(yùn)行結(jié)束即`serverHandler{c.server}.ServeHTTP(w, w.req)`運(yùn)行結(jié)束移宅。接下來(lái)就是對(duì)請(qǐng)求處理完畢之后上希望和連接斷開(kāi)的相關(guān)邏輯。

至此椿疗,Golang中一個(gè)完整的http服務(wù)介紹完畢漏峰,包括注冊(cè)路由,開(kāi)啟監(jiān)聽(tīng)届榄,處理連接浅乔,路由處理函數(shù)。

### 總結(jié)

多數(shù)的web應(yīng)用基于HTTP協(xié)議铝条,客戶(hù)端和服務(wù)器通過(guò)request-response的方式交互靖苇。一個(gè)server并不可少的兩部分莫過(guò)于路由注冊(cè)和連接處理。Golang通過(guò)一個(gè)ServeMux實(shí)現(xiàn)了的multiplexer路由多路復(fù)用器來(lái)管理路由班缰。同時(shí)提供一個(gè)Handler接口提供ServeHTTP用來(lái)實(shí)現(xiàn)handler處理其函數(shù)贤壁,后者可以處理實(shí)際request并構(gòu)造response。

ServeMux和handler處理器函數(shù)的連接橋梁就是Handler接口埠忘。ServeMux的ServeHTTP方法實(shí)現(xiàn)了尋找注冊(cè)路由的handler的函數(shù)脾拆,并調(diào)用該handler的ServeHTTP方法馒索。ServeHTTP方法就是真正處理請(qǐng)求和構(gòu)造響應(yīng)的地方。

回顧go的http包實(shí)現(xiàn)http服務(wù)的流程名船,可見(jiàn)大師們的編碼設(shè)計(jì)之功力双揪。學(xué)習(xí)有利提高自身的代碼邏輯組織能力。更好的學(xué)習(xí)方式除了閱讀包帚,就是實(shí)踐,接下來(lái)运吓,我們將著重討論來(lái)構(gòu)建http服務(wù)渴邦。尤其是構(gòu)建http中間件函數(shù)。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末拘哨,一起剝皮案震驚了整個(gè)濱河市谋梭,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌倦青,老刑警劉巖瓮床,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異产镐,居然都是意外死亡隘庄,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)癣亚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)丑掺,“玉大人,你說(shuō)我怎么就攤上這事述雾〗种荩” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵玻孟,是天一觀的道長(zhǎng)唆缴。 經(jīng)常有香客問(wèn)我,道長(zhǎng)黍翎,這世上最難降的妖魔是什么面徽? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮匣掸,結(jié)果婚禮上斗忌,老公的妹妹穿的比我還像新娘。我一直安慰自己旺聚,他們只是感情好织阳,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著砰粹,像睡著了一般唧躲。 火紅的嫁衣襯著肌膚如雪造挽。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,631評(píng)論 1 305
  • 那天弄痹,我揣著相機(jī)與錄音饭入,去河邊找鬼。 笑死肛真,一個(gè)胖子當(dāng)著我的面吹牛谐丢,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蚓让,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼乾忱,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了历极?” 一聲冷哼從身側(cè)響起窄瘟,我...
    開(kāi)封第一講書(shū)人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎趟卸,沒(méi)想到半個(gè)月后蹄葱,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡锄列,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年图云,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片邻邮。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡琼稻,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出饶囚,到底是詐尸還是另有隱情帕翻,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布萝风,位于F島的核電站嘀掸,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏规惰。R本人自食惡果不足惜睬塌,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望歇万。 院中可真熱鬧揩晴,春花似錦、人聲如沸贪磺。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)寒锚。三九已至劫映,卻和暖如春违孝,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背泳赋。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工雌桑, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人祖今。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓校坑,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親千诬。 傳聞我的和親對(duì)象是個(gè)殘疾皇子耍目,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

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

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)大渤,斷路器,智...
    卡卡羅2017閱讀 134,659評(píng)論 18 139
  • Golang標(biāo)準(zhǔn)庫(kù)http包提供了基礎(chǔ)的http服務(wù),這個(gè)服務(wù)又基于Handler接口和ServeMux結(jié)構(gòu)的做M...
    人世間閱讀 31,555評(píng)論 1 27
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,162評(píng)論 25 707
  • 在那個(gè)梔子花開(kāi)樹(shù)影斑駁的午后衔掸,一件帶著沐浴露香味的白襯衫烫幕,都能讓你飛蛾撲火奮不顧身,后來(lái)燒退了敞映,夢(mèng)醒了较曼。之后不是沒(méi)...
    檸C小姐閱讀 468評(píng)論 2 4
  • 很難得有時(shí)間陪孩子一起出來(lái)走走,看著孩子在前面雀躍著振愿,呼喚著我們前去追逐捷犹,霎時(shí)間自己也找到了兒時(shí)的快樂(lè)感覺(jué)。...
    天使影記閱讀 249評(píng)論 0 2