『閱讀源代碼的姿勢:以 go-restful 為例』

go-restful.png
go-restful-code.png

大家好,我叫謝偉,是一名程序員。

下面結合我的經(jīng)歷和見聞,講述下一名非科班程序員的成長過程:

  • 學習一門編程語言
  • 寫盡量多的代碼
  • 補盡量多的基礎知識
  • 一定階段后(有開發(fā)任務,能按時完成),開始思考架構:即如何更好的設計一個項目
  • 閱讀源代碼,看熱門的項目的源代碼
  • 重點梳理源代碼的流程而不是細節(jié)
  • 借鑒好的源代碼的思路編寫程序
  • 掌握更多的軟件設計知識
  • 架構師:技術選型、設計
  • ...

一般初學者確定一個方向,比如web 后端、前端等,會選擇一門編程語言深入下去,比如后端java、python、go等。通過項目不斷練習編程語言和編程思維,知道如何將需求實現(xiàn)出來。一段時間后,有可能算是某一階段的瓶頸,希望寫出更好的代碼,除了繼續(xù)做項目之外,更好的方式是閱讀某一個庫或者某一項目的源代碼,從源代碼里學習一些編程的處理方式,之后借鑒到自己的項目中。突破瓶頸,繼續(xù)精進技能。

一般的軟件構建過程是這樣的:

  • 設計:方案確定
  • 編寫代碼
    • 編碼風格
    • 技術選型
    • 子程序
    • 語句
  • 測試
  • 聯(lián)調
  • 迭代:繼續(xù)改善代碼

本節(jié)的主題是:如何閱讀源代碼?

1. 明確你的問題

開源領域,值得學習的東西太多了,你應該明確知道你需要解決的問題是什么,才能針對性的對某一項目或者某一庫進行源代碼的閱讀。

2. 示例

go-restful是用于構建REST-style web服務的golang包。

在這之前我們需要了解下 HTTP 協(xié)議、Web 客戶端、服務端。

這些知識和我們訪問網(wǎng)址獲取到的信息息息相關。

我們在瀏覽器中輸入:URLwww.baidu.com)的整體過程如下:

  • 瀏覽器(客戶端)請求DNS(域名管理系統(tǒng)),獲取IP
  • IP 能夠找到對應的服務器
  • 建立TCP 服務
  • 服務器根據(jù)請求處理請求包(HTTP Request)
  • 服務器返回HTTP Response
  • 瀏覽器(客戶端)收到響應后渲染Response 包里的主體(body)
  • 斷開連接,瀏覽器顯示網(wǎng)頁信息

我們關注里面的:HTTP RequestHTTP Response

隨意找個網(wǎng)頁查看源代碼看看:

HTTP Request.png

HTTP 協(xié)議:HTTP Request

GET /u/58f0817209aa HTTP/1.1
Host: www.itdecent.cn
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Referer: http://www.itdecent.cn/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8

主要包括:

  • 請求行: 請求方法、請求URI、HTTP 協(xié)議、協(xié)議版本
  • 服務端信息: Host、...
  • 消息體

HTTP 協(xié)議 HTTP Response

HTTP/1.1 200 OK
Date: Sun, 20 May 2018 03:19:36 GMT
Server: Tengine
Content-Type: text/html; charset=utf-8
Transfer-Encoding: chunked
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
Content-Security-Policy: script-src 'self' 'unsafe-inline' 'unsafe-eval' *.jianshu.com *.jianshu.io api.geetest.com static.geetest.com dn-staticdown.qbox.me zz.bdstatic.com *.google-analytics.com # push.zhanzhang.baidu.com res.wx.qq.com qzonestyle.gtimg.cn as.alipayobjects.com ;style-src 'self' 'unsafe-inline' *.jianshu.com *.jianshu.io api.geetest.com static.geetest.com ;
ETag: W/"4d22fb2fcef7cdb3f874a6b4960ff2ae"
Cache-Control: max-age=0, private, must-revalidate
Set-Cookie: locale=zh-CN; path=/
Set-Cookie: _m7e_session=708ecf714930ebc19da67ae3141bd6c0; path=/; expires=Sun, 20 May 2018 09:19:36 -0000; secure; HttpOnly
X-Request-Id: c61a268c-896f-4e03-afbe-2547db04943d
X-Runtime: 0.137573
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
Content-Encoding: gzip
X-Via: 1.1 PSfjfzdx2wn96:6 (Cdn Cache Server V2.0), 1.1 jsyz89:1 (Cdn Cache Server V2.0)
Connection: keep-alive
X-Dscp-Value: 0

主要包括:

  • 狀態(tài)行:HTTP 協(xié)議、HTTP 協(xié)議版本、狀態(tài)碼
  • 服務端信息
  • 消息體

所以關于設計 restful api 的主體部分包括這些:

  • HTTP 方法:GET、POST、PUT、DELETE
  • HTTP Request:URI 路徑、路徑參數(shù)、請求參數(shù)
  • HTTP Response:狀態(tài)碼(2XX、3XX、4XX、5XX)、消息體(body)

鑒于上面的知識點,我們如果使用內置的golang 包,處理 http 信息會這么做:

func Downloader(url string) ([]byte, error) {
    var (
        req *http.Request
        err error
    )
    if req, err = http.NewRequest("GET", url, nil); err != nil {
        return nil, ErrorHttpRequest
    }

    client := http.DefaultClient
    req.Header.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36")
    var (
        resp *http.Response
    )
    if resp, err = client.Do(req); err != nil {
        return nil, ErrorHttpResponse
    }

    defer resp.Body.Close()

    return ioutil.ReadAll(resp.Body)
}

查看下源代碼 net/http 庫中的 http.Request 和 http.Response 都有些什么?

type Request struct {
    // Method specifies the HTTP method (GET, POST, PUT, etc.).
    // For client requests an empty string means GET.
    Method string

    // URL specifies either the URI being requested (for server
    // requests) or the URL to access (for client requests).
    //
    // For server requests the URL is parsed from the URI
    // supplied on the Request-Line as stored in RequestURI.  For
    // most requests, fields other than Path and RawQuery will be
    // empty. (See RFC 2616, Section 5.1.2)
    //
    // For client requests, the URL's Host specifies the server to
    // connect to, while the Request's Host field optionally
    // specifies the Host header value to send in the HTTP
    // request.
    URL *url.URL

    // The protocol version for incoming server requests.
    //
    // For client requests these fields are ignored. The HTTP
    // client code always uses either HTTP/1.1 or HTTP/2.
    // See the docs on Transport for details.
    Proto      string // "HTTP/1.0"
    ProtoMajor int    // 1
    ProtoMinor int    // 0

    // Header contains the request header fields either received
    // by the server or to be sent by the client.
    //
    // If a server received a request with header lines,
    //
    //  Host: example.com
    //  accept-encoding: gzip, deflate
    //  Accept-Language: en-us
    //  fOO: Bar
    //  foo: two
    //
    // then
    //
    //  Header = map[string][]string{
    //      "Accept-Encoding": {"gzip, deflate"},
    //      "Accept-Language": {"en-us"},
    //      "Foo": {"Bar", "two"},
    //  }
    //
    // For incoming requests, the Host header is promoted to the
    // Request.Host field and removed from the Header map.
    //
    // HTTP defines that header names are case-insensitive. The
    // request parser implements this by using CanonicalHeaderKey,
    // making the first character and any characters following a
    // hyphen uppercase and the rest lowercase.
    //
    // For client requests, certain headers such as Content-Length
    // and Connection are automatically written when needed and
    // values in Header may be ignored. See the documentation
    // for the Request.Write method.
    Header Header

    // Body is the request's body.
    //
    // For client requests a nil body means the request has no
    // body, such as a GET request. The HTTP Client's Transport
    // is responsible for calling the Close method.
    //
    // For server requests the Request Body is always non-nil
    // but will return EOF immediately when no body is present.
    // The Server will close the request body. The ServeHTTP
    // Handler does not need to.
    Body io.ReadCloser

    // GetBody defines an optional func to return a new copy of
    // Body. It is used for client requests when a redirect requires
    // reading the body more than once. Use of GetBody still
    // requires setting Body.
    //
    // For server requests it is unused.
    GetBody func() (io.ReadCloser, error)

    // ContentLength records the length of the associated content.
    // The value -1 indicates that the length is unknown.
    // Values >= 0 indicate that the given number of bytes may
    // be read from Body.
    // For client requests, a value of 0 with a non-nil Body is
    // also treated as unknown.
    ContentLength int64

    // TransferEncoding lists the transfer encodings from outermost to
    // innermost. An empty list denotes the "identity" encoding.
    // TransferEncoding can usually be ignored; chunked encoding is
    // automatically added and removed as necessary when sending and
    // receiving requests.
    TransferEncoding []string

    // Close indicates whether to close the connection after
    // replying to this request (for servers) or after sending this
    // request and reading its response (for clients).
    //
    // For server requests, the HTTP server handles this automatically
    // and this field is not needed by Handlers.
    //
    // For client requests, setting this field prevents re-use of
    // TCP connections between requests to the same hosts, as if
    // Transport.DisableKeepAlives were set.
    Close bool

    // For server requests Host specifies the host on which the
    // URL is sought. Per RFC 2616, this is either the value of
    // the "Host" header or the host name given in the URL itself.
    // It may be of the form "host:port". For international domain
    // names, Host may be in Punycode or Unicode form. Use
    // golang.org/x/net/idna to convert it to either format if
    // needed.
    //
    // For client requests Host optionally overrides the Host
    // header to send. If empty, the Request.Write method uses
    // the value of URL.Host. Host may contain an international
    // domain name.
    Host string

    // Form contains the parsed form data, including both the URL
    // field's query parameters and the POST or PUT form data.
    // This field is only available after ParseForm is called.
    // The HTTP client ignores Form and uses Body instead.
    Form url.Values

    // PostForm contains the parsed form data from POST, PATCH,
    // or PUT body parameters.
    //
    // This field is only available after ParseForm is called.
    // The HTTP client ignores PostForm and uses Body instead.
    PostForm url.Values

    // MultipartForm is the parsed multipart form, including file uploads.
    // This field is only available after ParseMultipartForm is called.
    // The HTTP client ignores MultipartForm and uses Body instead.
    MultipartForm *multipart.Form

    // Trailer specifies additional headers that are sent after the request
    // body.
    //
    // For server requests the Trailer map initially contains only the
    // trailer keys, with nil values. (The client declares which trailers it
    // will later send.)  While the handler is reading from Body, it must
    // not reference Trailer. After reading from Body returns EOF, Trailer
    // can be read again and will contain non-nil values, if they were sent
    // by the client.
    //
    // For client requests Trailer must be initialized to a map containing
    // the trailer keys to later send. The values may be nil or their final
    // values. The ContentLength must be 0 or -1, to send a chunked request.
    // After the HTTP request is sent the map values can be updated while
    // the request body is read. Once the body returns EOF, the caller must
    // not mutate Trailer.
    //
    // Few HTTP clients, servers, or proxies support HTTP trailers.
    Trailer Header

    // RemoteAddr allows HTTP servers and other software to record
    // the network address that sent the request, usually for
    // logging. This field is not filled in by ReadRequest and
    // has no defined format. The HTTP server in this package
    // sets RemoteAddr to an "IP:port" address before invoking a
    // handler.
    // This field is ignored by the HTTP client.
    RemoteAddr string

    // RequestURI is the unmodified Request-URI of the
    // Request-Line (RFC 2616, Section 5.1) as sent by the client
    // to a server. Usually the URL field should be used instead.
    // It is an error to set this field in an HTTP client request.
    RequestURI string

    // TLS allows HTTP servers and other software to record
    // information about the TLS connection on which the request
    // was received. This field is not filled in by ReadRequest.
    // The HTTP server in this package sets the field for
    // TLS-enabled connections before invoking a handler;
    // otherwise it leaves the field nil.
    // This field is ignored by the HTTP client.
    TLS *tls.ConnectionState

    // Cancel is an optional channel whose closure indicates that the client
    // request should be regarded as canceled. Not all implementations of
    // RoundTripper may support Cancel.
    //
    // For server requests, this field is not applicable.
    //
    // Deprecated: Use the Context and WithContext methods
    // instead. If a Request's Cancel field and context are both
    // set, it is undefined whether Cancel is respected.
    Cancel <-chan struct{}

    // Response is the redirect response which caused this request
    // to be created. This field is only populated during client
    // redirects.
    Response *Response

    // ctx is either the client or server context. It should only
    // be modified via copying the whole Request using WithContext.
    // It is unexported to prevent people from using Context wrong
    // and mutating the contexts held by callers of the same request.
    ctx context.Context
}
type Response struct {
    Status     string // e.g. "200 OK"
    StatusCode int    // e.g. 200
    Proto      string // e.g. "HTTP/1.0"
    ProtoMajor int    // e.g. 1
    ProtoMinor int    // e.g. 0

    // Header maps header keys to values. If the response had multiple
    // headers with the same key, they may be concatenated, with comma
    // delimiters.  (Section 4.2 of RFC 2616 requires that multiple headers
    // be semantically equivalent to a comma-delimited sequence.) Values
    // duplicated by other fields in this struct (e.g., ContentLength) are
    // omitted from Header.
    //
    // Keys in the map are canonicalized (see CanonicalHeaderKey).
    Header Header

    // Body represents the response body.
    //
    // The http Client and Transport guarantee that Body is always
    // non-nil, even on responses without a body or responses with
    // a zero-length body. It is the caller's responsibility to
    // close Body. The default HTTP client's Transport does not
    // attempt to reuse HTTP/1.0 or HTTP/1.1 TCP connections
    // ("keep-alive") unless the Body is read to completion and is
    // closed.
    //
    // The Body is automatically dechunked if the server replied
    // with a "chunked" Transfer-Encoding.
    Body io.ReadCloser

    // ContentLength records the length of the associated content. The
    // value -1 indicates that the length is unknown. Unless Request.Method
    // is "HEAD", values >= 0 indicate that the given number of bytes may
    // be read from Body.
    ContentLength int64

    // Contains transfer encodings from outer-most to inner-most. Value is
    // nil, means that "identity" encoding is used.
    TransferEncoding []string

    // Close records whether the header directed that the connection be
    // closed after reading Body. The value is advice for clients: neither
    // ReadResponse nor Response.Write ever closes a connection.
    Close bool

    // Uncompressed reports whether the response was sent compressed but
    // was decompressed by the http package. When true, reading from
    // Body yields the uncompressed content instead of the compressed
    // content actually set from the server, ContentLength is set to -1,
    // and the "Content-Length" and "Content-Encoding" fields are deleted
    // from the responseHeader. To get the original response from
    // the server, set Transport.DisableCompression to true.
    Uncompressed bool

    // Trailer maps trailer keys to values in the same
    // format as Header.
    //
    // The Trailer initially contains only nil values, one for
    // each key specified in the server's "Trailer" header
    // value. Those values are not added to Header.
    //
    // Trailer must not be accessed concurrently with Read calls
    // on the Body.
    //
    // After Body.Read has returned io.EOF, Trailer will contain
    // any trailer values sent by the server.
    Trailer Header

    // Request is the request that was sent to obtain this Response.
    // Request's Body is nil (having already been consumed).
    // This is only populated for Client requests.
    Request *Request

    // TLS contains information about the TLS connection on which the
    // response was received. It is nil for unencrypted responses.
    // The pointer is shared between responses and should not be
    // modified.
    TLS *tls.ConnectionState
}

可以看出這兩個結構體內存在著我們之前分析的那些點。

如果只使用內置的 net/http 的包如何啟動一個web 服務?

package main

import (
    "fmt"
    "net/http"
)

func Say(resp http.ResponseWriter, req *http.Request) {
    req.ParseForm()
    fmt.Println(req.URL.Host, "-", req.URL.Path, "-", req.Form)
    fmt.Fprintf(resp, "hello world")
}

func main() {
    http.HandleFunc("/user/hello", Say)
    http.ListenAndServe(":8080", nil)
}

訪問:localhost:8080/user/hello 返回響應值:"hello world"

上文中:URL、和響應值response,我們在代碼中進行了處理。同樣的我們訪問真實的網(wǎng)址, 比如 https://www.baidu.com 則是百度的服務器端代碼進行了處理。

3. 抄和使用 example

上文中大概知道了構建 restful api 相關的一些 http 協(xié)議的知識, 和內置的庫 net/http 的基本使用方法。

但別忘了我們的主題是:閱讀 go-restful 的源代碼。

首先,我們應該根據(jù)官方文檔學會基本的使用:

package main

import (
    "fmt"
    "log"
    "net/http"

    "github.com/emicklei/go-restful"
)



type User struct {
    Name string
    Age  string
    ID   []int
}

type UserResource struct {
    // normally one would use DAO (data access object)
    users map[string]User
}

// WebService creates a new service that can handle REST requests for User resources.
func (u UserResource) WebService() *restful.WebService {
    ws := new(restful.WebService)
    ws.
        Path("/users").
        Consumes(restful.MIME_XML, restful.MIME_JSON).
        Produces(restful.MIME_JSON, restful.MIME_XML) // you can specify this per route as well

    ws.Route(ws.GET("/").To(u.findAllUsers).
        // docs
        Doc("get all users").
        Writes([]User{}).
        Returns(200, "OK", []User{}))

    ws.Route(ws.GET("/{user-id}").To(u.findUser).
        // docs
        Doc("get a user").
        Param(ws.PathParameter("user-id", "identifier of the user").DataType("integer").DefaultValue("1")).
        Writes(User{}). // on the response
        Returns(200, "OK", User{}).
        Returns(404, "Not Found", nil))

    return ws
}

// GET http://localhost:8080/users
//
func (u UserResource) findAllUsers(request *restful.Request, response *restful.Response) {
    list := []User{}
    for _, each := range u.users {
        list = append(list, each)
    }
    response.WriteEntity(list)
}

func (u UserResource) findUser(request *restful.Request, response *restful.Response) {
    id := request.PathParameter("user-id")
    usr := u.users[id]
    if len(usr.ID) == 0 {
        response.WriteErrorString(http.StatusNotFound, "User could not be found.")
    } else {
        response.WriteEntity(usr)
    }
}

func main() {
    type APIServer struct {
        Container *restful.Container
    }
    u := UserResource{map[string]User{}}
    u.users["xiewei"] = User{
        Name: "xiewei",
        Age:  "20",
        ID:   []int{1, 2, 3, 4},
    }
    apiServer := &APIServer{
        Container: restful.DefaultContainer.Add(u.WebService()),
    }

    log.Printf("start listening on localhost:9990")
    log.Fatal(http.ListenAndServe(":9990", apiServer.Container))
}

訪問:localhost:9990/users

HTTP/1.1 200 OK
Content-Type: application/json
Date: Sun, 20 May 2018 04:21:29 GMT
Content-Length: 92

[
  {
   "Name": "xiewei",
   "Age": "20",
   "ID": [
    1,
    2,
    3,
    4
   ]
  }
 ]

訪問:localhost:9990/users/xiewei

HTTP/1.1 200 OK
Content-Type: application/json
Date: Sun, 20 May 2018 04:21:29 GMT
Content-Length: 92

[
  {
   "Name": "xiewei",
   "Age": "20",
   "ID": [
    1,
    2,
    3,
    4
   ]
  }
 ]

訪問:localhost:9990/users/xiewei2

HTTP/1.1 404 Not Found
Date: Sun, 20 May 2018 04:22:59 GMT
Content-Length: 24
Content-Type: text/plain; charset=utf-8

User could not be found.

通過這個簡單的例子,我們大概能夠使用 go-restful 了。

無外乎還是操作:http.Request、http.Response, 上述例子的核心是:findAllUsersfindUser 這個兩個函數(shù),具體的返回值、狀態(tài)碼什么的都是由這兩個函數(shù)定義。其他的都是一些路由的定義、定義生產者和消費者格式、啟動指定端口的web 服務。

4. 梳理流程

restful-flow.png

1. 啟動并監(jiān)控指定端口的 http 服務

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

能看出函數(shù)的入口是:Handler 接口

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

httpServer 包含 container .

log.Fatal(http.ListenAndServe(":9990", apiServer.Container))

一個 Container 包含多個 WebService

type Container struct {
    webServicesLock        sync.RWMutex
    webServices            []*WebService
    ServeMux               *http.ServeMux
    isRegisteredOnRoot     bool
    containerFilters       []FilterFunction
    doNotRecover           bool // default is true
    recoverHandleFunc      RecoverHandleFunction
    serviceErrorHandleFunc ServiceErrorHandleFunction
    router                 RouteSelector // default is a CurlyRouter (RouterJSR311 is a slower alternative)
    contentEncodingEnabled bool          // default is false
}

container 實現(xiàn)的了Handler 接口

func (c *Container) ServeHTTP(httpwriter http.ResponseWriter, httpRequest *http.Request) {
    c.ServeMux.ServeHTTP(httpwriter, httpRequest)
}

一個 webservice 包含多個Route

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
}

一個 Route 包含HTTP 協(xié)議協(xié)議相關的HTTP Request 、HTTP Reponse 、方法等處理

type Route struct {
    Method   string
    Produces []string
    Consumes []string
    Path     string // webservice root path + described path
    Function RouteFunction
    Filters  []FilterFunction
    If       []RouteSelectionConditionFunction

    // cached values for dispatching
    relativePath string
    pathParts    []string
    pathExpr     *pathExpression // cached compilation of relativePath as RegExp

    // documentation
    Doc                     string
    Notes                   string
    Operation               string
    ParameterDocs           []*Parameter
    ResponseErrors          map[int]ResponseError
    ReadSample, WriteSample interface{} // structs that model an example request or response payload

    // Extra information used to store custom information about the route.
    Metadata map[string]interface{}

    // marks a route as deprecated
    Deprecated bool
}

具體的處理函數(shù)是:RouteFunction

type RouteFunction func(*Request, *Response)

再回過來看一下,我們的代碼是怎么處理的:

  • 啟動http 服務,指定端口并監(jiān)聽:需要傳入端口和Handler 接口
log.Fatal(http.ListenAndServe(":9990", apiServer.Container))
  • 定義一個 container ,container 類實現(xiàn)了Handler 接口
    apiServer := &APIServer{
        Container: restful.DefaultContainer.Add(u.WebService()),
    }

  • container 內需要定義一個或者多個 webservice, 內含具體的Route 處理函數(shù) RouteFunction
func (u UserResource) WebService() *restful.WebService {
    ws := new(restful.WebService)
    ws.
        Path("/users").
        Consumes(restful.MIME_XML, restful.MIME_JSON).
        Produces(restful.MIME_JSON, restful.MIME_XML) // you can specify this per route as well

    ws.Route(ws.GET("/").To(u.findAllUsers).
        // docs
        Doc("get all users").
        Writes([]User{}).
        Returns(200, "OK", []User{}))

    ws.Route(ws.GET("/{user-id}").To(u.findUser).
        // docs
        Doc("get a user").
        Param(ws.PathParameter("user-id", "identifier of the user").DataType("integer").DefaultValue("1")).
        Writes(User{}). // on the response
        Returns(200, "OK", User{}).
        Returns(404, "Not Found", nil))

    return ws
}

好,上面的大致處理流程我們已經(jīng)梳理清楚。

5. 借鑒使用

  • 如何抽象出的客觀實體:比如Route、Webservice、Container
  • 如何對Router、WebService、Container 定義方法
  • 如何對項目進行組織。
  • 方法如何進行的復用

內置庫內存在很多的接口,對接口的實現(xiàn),不斷的對內置庫的擴展,有可能就重新發(fā)明了一個熱門的輪子。

go-restful 庫便是對內置庫 net/http 的擴展。

總結:
閱讀源代碼首先你需要明確解決的問題是什么,其次你會使用該項目的Demo 或者多個示例,然后你需要根據(jù)源代碼梳理源代碼流程,最后由抄的過程轉變?yōu)榻梃b使用的過程。

再會,希望對你有所啟發(fā),我是謝偉。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容