• golang之网络开发


    1. TCP/UDP Server/Client开发

    net包提供network I/O开发接口,包括TCP/IPUDPDNSUnix domain sockets

    常用开发一般仅需要最基础接口或函数:

    服务器:net.Listen()net.Accept()

    ln, err := net.Listen("tcp", ":8080")
    if err != nil {
        // handle error
    }
    for {
        conn, err := ln.Accept()
        if err != nil {
            // handle error
        }
        go handleConnection(conn)
    }

    客户端:net.Dial()

    conn, err := net.Dial("tcp", "golang.org:80")
    if err != nil {
        // handle error
    }
    fmt.Fprintf(conn, "GET / HTTP/1.0
    
    ")
    status, err := bufio.NewReader(conn).ReadString('
    ')
    // ...

    相关API定义:

    func Listen(network, address string) (Listener, error)
    func (l *TCPListener) Accept() (Conn, error)
    func Dial(network, address string) (Conn, error)

    2. web开发

    net/http包为web开发提供支持,能很简单地对Web的路由,静态文件,模版,cookie等数据进行设置和操作。

    主要是两步:设置访问路由,设置监听端口(转发路由:根据用户请求调用不同路由函数)。

    type HandlerFunc func(w http.ResponseWriter, r *http.Request)
    func (f HandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) // ServeHTTP calls f(w, r).
    func HandleFunc(pattern string, handle func(http.ResponseWriter, *http.Request))
    type Handler interface {
      ServeHTTP(http.ResponseWriter, *http.Request)
    }
    func Handle(pattern string, handler http.Handler)
    func ListenAndServe(addr string, handler http.Handler) error

    ListenAndServe starts an HTTP server with a given address and handler. The handler is usually nil, which means to use DefaultServeMux. 若此时给定Handler,则采用给定的Handler(给定的handler需实现ServeHTTP()函数的接口)。

    Handle and HandleFunc add handlers to DefaultServeMux.

    DefaultServeMux路由规则:

    • URL分为两种,末尾为/:表示一个子树,后面可以跟其他子路经;末尾不是/,表示一个叶子,固定路径。以/结尾的URL可以匹配它的任何子路经,比如/images会匹配/images/cute-cat.
    • 它采用最长匹配原则,如果有多个匹配,一定采用匹配路径最长的那个进行处理。
    • 如果没有任何匹配项,返回404错误。

    Handle()的参数为地址匹配字符串和handler接口,handler需要实现ServeHTTP()函数的接口。

    HandleFunc()的参数是地址匹配字符串和函数handle,函数handle等同http.Handler接口要实现的函数。

    http.Handle("/foo", fooHandler)
    
    http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))
    })
    http.Handle("/zoo", http.HandlerFunc(zoo)) // 直接将函数zoo赋值为http.HandlerFunc log.Fatal(http.ListenAndServe(
    ":8080", nil))
    package main
    
    import (
        "fmt"
        "net/http"
        _"html"
        "log"
    )
    
    type fooHandler struct{
    }
    
    func (f fooHandler) ServeHTTP(w http.ResponseWriter, r *http.Request){
        fmt.Fprintf(w, "Hello, first!
    ")
    }
    
    func main(){
        http.Handle("/foo", fooHandler{})
    
        http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request){
            fmt.Fprintf(w, "Hello, %q
    ", r.URL.Path)
        })
    
        log.Fatal(http.ListenAndServe(":8088", nil))
    }
    
    # go build -o first first.go
    # ./first &
    $ curl localhost:8088/foo
    Hello, first!
    $ curl localhost:8088/bar?house=wang
    Hello, "/bar"
    Handler示例

    简单应用

    Get, Head, Post and PostForm make HTTP or HTTPS requests:

    resp, err := http.Get("http://example.com/")
    ...
    resp, err := http.Post("http://example.com/upload", "image/jpeg", &buf)
    ...
    resp, err := http.PostForm("http://example.com/form", url.Values{"key": {"Value"}, "id": {"123"}})
    ...
    defer resp.Body.Close()  // when finished
    body, err := ioutil.ReadAll(resp.Body)

    复杂应用

    client := &http.Client{
        CheckRedirect: redirectPolicyFunc,
    }
    
    resp, err := client.Get("http://example.com")
    // ...
    
    req, err := http.NewRequest("GET", "http://example.com", nil)
    // ...
    req.Header.Add("If-None-Match", `W/"wyzzy"`)
    resp, err := client.Do(req)
    // ...
    1)通过创建Client控制HTTP客户端client headers, redirect policy and other settings
    tr := &http.Transport{
        MaxIdleConns:       10,
        IdleConnTimeout:    30 * time.Second,
        DisableCompression: true,
    }
    client := &http.Client{Transport: tr}
    resp, err := client.Get("https://example.com")
    2)通过创建Transport控制客户端三层四层配置,如TLS,keepalives,compression等

    注:Client和Transport可并发运行。

    s := &http.Server{
        Addr:           ":8080",
        Handler:        myHandler,
        ReadTimeout:    10 * time.Second,
        WriteTimeout:   10 * time.Second,
        MaxHeaderBytes: 1 << 20,
    }
    log.Fatal(s.ListenAndServe())
    3)通过创建Server控制服务器端配置

    常用量和函数

    const (
        MethodGet     = "GET"
        MethodHead    = "HEAD"
        MethodPost    = "POST"
        MethodPut     = "PUT"
        MethodPatch   = "PATCH" // RFC 5789
        MethodDelete  = "DELETE"
        MethodConnect = "CONNECT"
        MethodOptions = "OPTIONS"
        MethodTrace   = "TRACE"
    )const DefaultMaxHeaderBytes = 1 << 20 // 1 MB
    const DefaultMaxIdleConnsPerHost = 2
    var DefaultServeMux = &defaultServeMux
    var ErrHandlerTimeout = errors.New("http: Handler timeout")
    const (
        StatusContinue           = 100 // RFC 7231, 6.2.1
        StatusSwitchingProtocols = 101 // RFC 7231, 6.2.2
        StatusProcessing         = 102 // RFC 2518, 10.1
        StatusEarlyHints         = 103 // RFC 8297
    
        StatusOK                   = 200 // RFC 7231, 6.3.1
        StatusCreated              = 201 // RFC 7231, 6.3.2
        StatusAccepted             = 202 // RFC 7231, 6.3.3
        StatusNonAuthoritativeInfo = 203 // RFC 7231, 6.3.4
        StatusNoContent            = 204 // RFC 7231, 6.3.5
        StatusResetContent         = 205 // RFC 7231, 6.3.6
        StatusPartialContent       = 206 // RFC 7233, 4.1
        StatusMultiStatus          = 207 // RFC 4918, 11.1
        StatusAlreadyReported      = 208 // RFC 5842, 7.1
        StatusIMUsed               = 226 // RFC 3229, 10.4.1
    
        StatusMultipleChoices  = 300 // RFC 7231, 6.4.1
        StatusMovedPermanently = 301 // RFC 7231, 6.4.2
        StatusFound            = 302 // RFC 7231, 6.4.3
        StatusSeeOther         = 303 // RFC 7231, 6.4.4
        StatusNotModified      = 304 // RFC 7232, 4.1
        StatusUseProxy         = 305 // RFC 7231, 6.4.5
    
        StatusTemporaryRedirect = 307 // RFC 7231, 6.4.7
        StatusPermanentRedirect = 308 // RFC 7538, 3
    
        StatusBadRequest                   = 400 // RFC 7231, 6.5.1
        StatusUnauthorized                 = 401 // RFC 7235, 3.1
        StatusPaymentRequired              = 402 // RFC 7231, 6.5.2
        StatusForbidden                    = 403 // RFC 7231, 6.5.3
        StatusNotFound                     = 404 // RFC 7231, 6.5.4
        StatusMethodNotAllowed             = 405 // RFC 7231, 6.5.5
        StatusNotAcceptable                = 406 // RFC 7231, 6.5.6
        StatusProxyAuthRequired            = 407 // RFC 7235, 3.2
        StatusRequestTimeout               = 408 // RFC 7231, 6.5.7
        StatusConflict                     = 409 // RFC 7231, 6.5.8
        StatusGone                         = 410 // RFC 7231, 6.5.9
        StatusLengthRequired               = 411 // RFC 7231, 6.5.10
        StatusPreconditionFailed           = 412 // RFC 7232, 4.2
        StatusRequestEntityTooLarge        = 413 // RFC 7231, 6.5.11
        StatusRequestURITooLong            = 414 // RFC 7231, 6.5.12
        StatusUnsupportedMediaType         = 415 // RFC 7231, 6.5.13
        StatusRequestedRangeNotSatisfiable = 416 // RFC 7233, 4.4
        StatusExpectationFailed            = 417 // RFC 7231, 6.5.14
        StatusTeapot                       = 418 // RFC 7168, 2.3.3
        StatusMisdirectedRequest           = 421 // RFC 7540, 9.1.2
        StatusUnprocessableEntity          = 422 // RFC 4918, 11.2
        StatusLocked                       = 423 // RFC 4918, 11.3
        StatusFailedDependency             = 424 // RFC 4918, 11.4
        StatusTooEarly                     = 425 // RFC 8470, 5.2.
        StatusUpgradeRequired              = 426 // RFC 7231, 6.5.15
        StatusPreconditionRequired         = 428 // RFC 6585, 3
        StatusTooManyRequests              = 429 // RFC 6585, 4
        StatusRequestHeaderFieldsTooLarge  = 431 // RFC 6585, 5
        StatusUnavailableForLegalReasons   = 451 // RFC 7725, 3
    
        StatusInternalServerError           = 500 // RFC 7231, 6.6.1
        StatusNotImplemented                = 501 // RFC 7231, 6.6.2
        StatusBadGateway                    = 502 // RFC 7231, 6.6.3
        StatusServiceUnavailable            = 503 // RFC 7231, 6.6.4
        StatusGatewayTimeout                = 504 // RFC 7231, 6.6.5
        StatusHTTPVersionNotSupported       = 505 // RFC 7231, 6.6.6
        StatusVariantAlsoNegotiates         = 506 // RFC 2295, 8.1
        StatusInsufficientStorage           = 507 // RFC 4918, 11.5
        StatusLoopDetected                  = 508 // RFC 5842, 7.2
        StatusNotExtended                   = 510 // RFC 2774, 7
        StatusNetworkAuthenticationRequired = 511 // RFC 6585, 6
    )
    StatusOK = 200 // RFC 7231, 6.3.1
    func Error(w ResponseWriter, error string, code int)
    Error replies to the request with the specified error message and HTTP code. 
    It does not otherwise end the request; the caller should ensure no further writes are done to w. 
    The error message should be plain text.
    
    func DetectContentType(data []byte) string
    DetectContentType always returns a valid MIME type: 
    if it cannot determine a more specific one, it returns "application/octet-stream".
    
    func Handle(pattern string, handler Handler)
    Handle registers the handler for the given pattern in the DefaultServeMux. 
    The documentation for ServeMux explains how patterns are matched.
    
    func HandleFunc(pattern string, handler func(ResponseWriter, *Request))
    HandleFunc registers the handler function for the given pattern in the DefaultServeMux. 
    The documentation for ServeMux explains how patterns are matched.
    
    func ListenAndServe(addr string, handler Handler) error
    The handler is typically nil, in which case the DefaultServeMux is used.
    ListenAndServe always returns a non-nil error.
    
    func ListenAndServeTLS(addr, certFile, keyFile string, handler Handler) error
    
    func NotFound(w ResponseWriter, r *Request)
    NotFound replies to the request with an HTTP 404 not found error.
    ParseHTTPVersion parses a HTTP version string. "HTTP/1.0" returns (1, 0, true).
    
    func Redirect(w ResponseWriter, r *Request, url string, code int)
    The provided code should be in the 3xx range and is usually StatusMovedPermanently, StatusFound or StatusSeeOther.
    If the Content-Type header has not been set, Redirect sets it to "text/html; charset=utf-8" and writes a small HTML body. 
    Setting the Content-Type header to any value, including nil, disables that behavior. func Serve(l net.Listener, handler Handler) error Serve accepts incoming HTTP connections on the listener l, creating a new service goroutine for each.
    The service goroutines read requests and then call handler to reply to them. The handler
    is typically nil, in which case the DefaultServeMux is used. Serve always returns a non-nil error. func ServeContent(w ResponseWriter, req *Request, name string, modtime time.Time, content io.ReadSeeker) ServeContent replies to the request using the content in the provided ReadSeeker.
    The main benefit of ServeContent over io.Copy is that it handles Range requests properly, sets the MIME type, and handles If-Match,
    If-Unmodified-Since, If-None-Match, If-Modified-Since, and If-Range requests. func ServeFile(w ResponseWriter, r *Request, name string) ServeFile replies to the request with the contents of the named file or directory. func StatusText(code int) string StatusText returns a text for the HTTP status code. It returns the empty string if the code is unknown.
    func Error(w ResponseWriter, error string, code int)
    Error replies to the request with the specified error message and HTTP code. 
    It does not otherwise end the request; the caller should ensure no further writes are done to w. 
    The error message should be plain text.
    
    func DetectContentType(data []byte) string
    DetectContentType always returns a valid MIME type: 
    if it cannot determine a more specific one, it returns "application/octet-stream".
    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.  (RFC 7230, section 3.2.2 requires that multiple headers
        // be semantically equivalent to a comma-delimited sequence.) When
        // Header values are duplicated by other fields in this struct (e.g.,
        // ContentLength, TransferEncoding, Trailer), the field values are
        // authoritative.
        //
        // Keys in the map are canonicalized (see CanonicalHeaderKey).
        Header Header
    
        // Body represents the response body.
        //
        // The response body is streamed on demand as the Body field
        // is read. If the network connection fails or the server
        // terminates the response, Body.Read calls return an error.
        //
        // 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 may not
        // reuse HTTP/1.x "keep-alive" TCP connections if the Body is
        // not read to completion and closed.
        //
        // The Body is automatically dechunked if the server replied
        // with a "chunked" Transfer-Encoding.
        //
        // As of Go 1.12, the Body will also implement io.Writer
        // on a successful "101 Switching Protocols" response,
        // as used by WebSockets and HTTP/2's "h2c" mode.
        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 // Go 1.7
    
        // 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 // Go 1.3
    }
    http.Response
    type Request struct {
        // Method specifies the HTTP method (GET, POST, PUT, etc.).
        // For client requests, an empty string means GET.
        //
        // Go's HTTP client does not support sending a request with
        // the CONNECT method. See the documentation on Transport for
        // details.
        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 7230, Section 5.3)
        //
        // 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) // Go 1.8
    
        // 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. For HTTP/1 (per RFC 7230, section 5.4), this
        // is either the value of the "Host" header or the host name
        // given in the URL itself. For HTTP/2, it is the value of the
        // ":authority" pseudo-header field.
        // 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.
        // To prevent DNS rebinding attacks, server Handlers should
        // validate that the Host header has a value for which the
        // Handler considers itself authoritative. The included
        // ServeMux supports patterns registered to particular host
        // names and thus protects its registered Handlers.
        //
        // 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 PATCH, 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 PATCH, POST
        // 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 // Go 1.1
    
        // 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-target of the
        // Request-Line (RFC 7230, Section 3.1.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: Set the Request's context with NewRequestWithContext
        // instead. If a Request's Cancel field and context are both
        // set, it is undefined whether Cancel is respected.
        Cancel <-chan struct{} // Go 1.5
    
        // Response is the redirect response which caused this request
        // to be created. This field is only populated during client
        // redirects.
        Response *Response // Go 1.7
        // contains filtered or unexported fields
    }
    http.Request

    godoc提供了一个web开发示例Writing Web Applications,用到了net/http,html/template, regexp,闭包等技术:

    package main
    
    import (
        _"fmt"
        "io/ioutil"
        "net/http"
        "log"
        "html/template"
        "regexp"
        _"errors"
    )
    
    type Page struct {
        Title string
        Body []byte
    }
    
    func (p *Page) save() error {
        filename := p.Title + ".txt"
        return ioutil.WriteFile(filename, p.Body, 0600)
    }
    
    func loadPage(title string) (*Page, error){
        filename := title + ".txt"
        body, err := ioutil.ReadFile(filename)
        if err != nil {
            return  nil, err
        }
        return &Page{Title: title, Body : body}, nil
    }
    
    var templates *template.Template
    var validPath  *regexp.Regexp
    func main(){
        /*
        p1 := &Page{Title: "TestPage", Body: []byte("This is a sample Page.")}
        p1.save()
        p2, _ := loadPage("TestPage")
        fmt.Println(string(p2.Body))
        */
        // import templates
        // panic when failed
        templates = template.Must(template.ParseFiles("edit.html", "view.html"))
    
        // validation
        validPath = regexp.MustCompile("^/(edit|save|view)/([a-zA-Z0-9]+)$")
    /*
        http.HandleFunc("/view/", viewHandler)
        http.HandleFunc("/edit/", editHandler)
        http.HandleFunc("/save/", saveHandler)
    */
        http.HandleFunc("/view/", makeHandler(viewHandler))
        http.HandleFunc("/edit/", makeHandler(editHandler))
        http.HandleFunc("/save/", makeHandler(saveHandler))
        log.Fatal(http.ListenAndServe(":8090", nil))
    }
    /*
    func getTitle(w http.ResponseWriter, r *http.Request)(string, error){
        m := validPath.FindStringSubmatch(r.URL.Path)
        if m == nil {
            http.NotFound(w, r)
            return "",  errors.New("Invalid Page Title")
        }
        return m[2], nil // The title is the second subexpression
    }
    */
    func makeHandler(fn func(http.ResponseWriter, *http.Request, string))  http.HandlerFunc {
        return func(w http.ResponseWriter, r *http.Request){
            m := validPath.FindStringSubmatch(r.URL.Path)
            if m == nil {
                http.NotFound(w, r)
                return 
            }
            fn(w, r, m[2])
        }
    }
    
    /*
    func viewHandler(w http.ResponseWriter, r *http.Request){
        title := r.URL.Path[len("/view/"):]
        p, _ :=  loadPage(title)
        fmt.Fprintf(w, "<h1>%s</h1><div>%s</div>", p.Title, p.Body)
    }
    */
    
    //func viewHandler(w http.ResponseWriter, r *http.Request){
    func viewHandler(w http.ResponseWriter, r *http.Request, title string){
    //    title := r.URL.Path[len("/view/"):]
    /*
        title, err := getTitle(w, r)
        if err != nil {
            return
        } 
    */
        p, err:= loadPage(title)
        if err != nil {
            http.Redirect(w, r, "/edit/"+title, http.StatusFound)
            return
        }
        //t, _:= template.ParseFiles("view.html")
        //t.Execute(w, p)
        renderTemplate(w, "view", p)
    }
    
    /*
    func editHandler(w http.ResponseWriter, r *http.Request){
        title := r.URL.Path[len("/edit/"):]
        p, err := loadPage(title)
        if err != nil {
            p = &Page{Title: title}
        }
        fmt.Fprintf(w, "<h1>Editing %s</h1>" +
        "<form action="/save/%s" method="POST">" +
        "<textarea name="body">%s</textarea><br>" +
        "<input type="submit" value="Save">" +
        "</form>",
        p.Title, p.Title, p.Body)
    }
    */
    //func editHandler(w http.ResponseWriter, r*http.Request){
    func editHandler(w http.ResponseWriter, r*http.Request, title string){
    //    title := r.URL.Path[len("/edit/"):]
    /*
        title, err := getTitle(w, r) 
        if err != nil {
            return
        }
    */
        p, err := loadPage(title)
        if err != nil {
            p = &Page{Title: title}
        }
    //    t, _:= template.ParseFiles(edit.html)
    //    t.Execute(w, p)
        renderTemplate(w, "edit", p)
    }
    
    //func saveHandler(w http.ResponseWriter, r *http.Request){
    func saveHandler(w http.ResponseWriter, r *http.Request, title string){
    //    title := r.URL.Path[len("/save/"):]
    /*
        title , err := getTitle(w, r)
        if err != nil {
            return
        }
    */
        body := r.FormValue("body")
        p := &Page{Title: title, Body: []byte(body)}
        err := p.save()
        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }
        http.Redirect(w, r, "/view/"+title, http.StatusFound)
    }
    
    func renderTemplate(w http.ResponseWriter, tmpl string, p *Page){
        /*
        t, err := template.ParseFiles(tmpl + ".html")
        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }
    
        err = t.Execute(w, p)
        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
        }
        */
        err := templates.ExecuteTemplate(w, tmpl+".html", p)
        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
        }
    }
    View Code

    2.2 HTTP/2

    HTTP/2协议握手分2种方式,一种叫h2,一种叫h2c。

    h2要求必须使用TLS加密,在TLS握手期间会顺带完成HTTPS/2协议的协商,如果协商失败(比如客户端不支持或者服务端不支持),则会使用HTTPS/1继续后续通讯。

    h2c不使用TLS,而是多了一次基于HTTP协议的握手往返来完成向HTTP/2协议的升级,一般不建议使用。

    Golang的http库在设计API时并没有支持用户使用h2c,而是鼓励使用h2。

    GO的http库默认支持HTTP/2协议,只要我们使用TLS则会默认启动HTTP/2特性,如果协商失败则蜕化为HTTPS/1。。

    对http Client或者Server做一些更加定制化的配置时,就会覆盖掉http库的默认行为,从而导致无法启用HTTP/2协议。

    只要server端开启TLS服务即可启用HTTP/2。

    sever.go
    package main
    
    import (
        "log"
        "net/http"
    )
    
    func main(){
        srv := &http.Server{Addr: ":8088", Handler: http.HandlerFunc(handle)}
    
        log.Printf("Serving on https://server:8088")
        log.Fatal(srv.ListenAndServeTLS("cert/server.crt", "cert/server.key"))
    }
    
    func handle(w http.ResponseWriter, r *http.Request){
        w.Write([]byte("Hello"))
    }

    除了使用ServeTLS/ListenAndServeTLS来启动支持HTTPS/2特性的服务端之外,还可以通过http2.ConfigureServer来为http.Server启动HTTPS/2特性并直接使用Serve来启动服务。

    package main
    
    import (
        "fmt"
        "crypto/tls"
        "crypto/x509"
        "flag"
        "io/ioutil"
        "log"
        "net/http"
        "golang.org/x/net/http2"
    )
    
    var addr = flag.String("addr", "https://server:8088?numa=4&numb=6", "connect to")
    var httpVer = flag.Int("httpVer", 2, "HTTP version")
    
    func main(){
        flag.Parse()
    
        client := &http.Client{}
    
        caCert, err := ioutil.ReadFile("cert/ca.crt")
        if err != nil {
            log.Fatalf("Reading server certificate: %s", err)
        }   
    
        pool := x509.NewCertPool()
        pool.AppendCertsFromPEM(caCert)
        tlsConfig := &tls.Config{
            RootCAs: pool,
        }
    
        switch *httpVer {
            case 1:
                client.Transport = &http.Transport {
                    TLSClientConfig: tlsConfig,
                }
            case 2:
                client.Transport = &http2.Transport {
                    TLSClientConfig: tlsConfig,
                }
        }   
    
        resp, err := client.Get(*addr)
        if err != nil {
            log.Fatalf("Failed get: %s", err)
        }   
        defer resp.Body.Close()
    
        body, err := ioutil.ReadAll(resp.Body)
        if err != nil {
            log.Fatalf("Failed reading response body: %s", err)
        }
    
        fmt.Printf("Response %d: %s
    body: %s
    ", resp.StatusCode, resp.Proto, string(body))
    }

    client定制后要开启HTTP/2,需要配置Transport(或使用http2.ConfigureTransport(transport))。

    $ ./cli -httpVer=1
    Response 200: HTTP/1.1
    body: 10
    
    $ ./cli -httpVer=20
    Response 200: HTTP/2.0
    body: 10 

    2.3 cookie相关

    A Cookie represents an HTTP cookie as sent in the Set-Cookie header of an HTTP response or the Cookie header of an HTTP request.

    See https://tools.ietf.org/html/rfc6265 for details.

    type Cookie struct {
        Name  string
        Value string
    
        Path       string    // optional
        Domain     string    // optional
        Expires    time.Time // optional
        RawExpires string    // for reading cookies only
    
        // MaxAge=0 means no 'Max-Age' attribute specified.
        // MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0'
        // MaxAge>0 means Max-Age attribute present and given in seconds
        MaxAge   int
        Secure   bool
        HttpOnly bool
        SameSite SameSite // Go 1.11
        Raw      string
        Unparsed []string // Raw text of unparsed attribute-value pairs
    }
    
    func (c *Cookie) String() string
    String returns the serialization of the cookie for use in a Cookie header (if only Name and Value are set) or 
    a Set-Cookie response header (if other fields are set). If c is nil or c.Name is invalid, the empty string is returned. func SetCookie(w ResponseWriter, cookie *Cookie) SetCookie adds a Set-Cookie header to the provided ResponseWriter's headers.
    The provided cookie must have a valid Name. Invalid cookies may be silently dropped.

    2.4 URL解析

    r.URL参考net/url包,定义如下:

    type URL struct {
        Scheme     string
        Opaque     string    // encoded opaque data
        User       *Userinfo // username and password information
        Host       string    // host or host:port
        Path       string    // path (relative paths may omit leading slash)
        RawPath    string    // encoded path hint (see EscapedPath method); added in Go 1.5
        ForceQuery bool      // append a query ('?') even if RawQuery is empty; added in Go 1.7
        RawQuery   string    // encoded query values, without '?'
        Fragment   string    // fragment for references, without '#'
    }
    resp, err := http.PostForm("http://example.com/form", url.Values{"key": {"Value"}, "id": {"123"}})

    示例应用如下:

    表单form可由两种方式提交GET或POST,GET方式form表单键值对直接跟在url后(?分割,&链接),POST方式Form以请求body方式发送。

    import (
        "fmt"
        "net/http"
        "strings"
        "log"
    )
    
    func main(){
        http.HandleFunc("/", sayhelloName)  // 设置访问路由
        err := http.ListenAndServe(":9090", nil) // 设置监听端口
        if err != nil {
            log.Fatal("ListenAndServe: ", err)
        }
    }
    
    func sayhelloName(w http.ResponseWriter, r *http.Request){
        r.ParseForm()
        fmt.Println("---------------")
        fmt.Println(r.Form)
        fmt.Println("path:", r.URL.Path)
        fmt.Println("scheme:", r.URL.Scheme)
        fmt.Println(r.Form["url_long"])
        fmt.Println("====")
        for k, v := range r.Form{
            fmt.Println("key:", k)
            fmt.Println("val:", strings.Join(v, " "))
        }
        fmt.Fprintf(w, "Hello wang!")
    }

    2.5 优雅关闭

    http.Server 内置的 Shutdown() 方法优雅地关机。

    func (srv *Server) Shutdown(ctx context.Context) error

    Shutdown gracefully shuts down the server without interrupting any active connections. Shutdown works by first closing all open listeners, then closing all idle connections, and then waiting indefinitely for connections to return to idle and then shut down. If the provided context expires before the shutdown is complete, Shutdown returns the context's error, otherwise it returns any error returned from closing the Server's underlying Listener(s).

    package main
    
    import (
        "context"
        "log"
        "net/http"
        "os"
        "os/signal"
    )
    
    func main() {
        var srv http.Server
    
        idleConnsClosed := make(chan struct{})
        go func() {
            sigint := make(chan os.Signal, 1)
            signal.Notify(sigint, os.Interrupt)
            <-sigint
    
            // We received an interrupt signal, shut down.
            if err := srv.Shutdown(context.Background()); err != nil {
                // Error from closing listeners, or context timeout:
                log.Printf("HTTP server Shutdown: %v", err)
            }
            close(idleConnsClosed)
        }()
    
        if err := srv.ListenAndServe(); err != http.ErrServerClosed {
            // Error starting or closing listener:
            log.Fatalf("HTTP server ListenAndServe: %v", err)
        }
    
        <-idleConnsClosed
    }

    参考:

    1. https://golang.google.cn/pkg/net/

    2. https://www.kancloud.cn/kancloud/the-way-to-go/165098 go入门指南

    3. http://wiki.jikexueyuan.com/project/go-web-programming/03.3.html go web编程

    4. Writing Web Applications

    5. Go语言练习:网络编程实例——简易图片上传网站 photoweb
    6. Go 源码学习之--net/http

    7. Go语言的http/2服务器功能及客户端使用

    8. Golang爬虫全攻略  网络应用解析

  • 相关阅读:
    广告小程序后端开发(8.发布免费广告或店铺,以及上传图片)
    广告小程序后端开发(7.获取价格表数据)
    广告小程序后端开发(6.获取类别逻辑)
    广告小程序后端开发(5.安装配置django-rest-framework,编写后台登录逻辑)
    广告小程序后端开发(4.导入地区数据,修改adminx,修改models,手动添加模拟数据)
    广告小程序后端开发(3.xadmin的引入)
    广告小程序后端开发(2.Models设计)
    使用django执行数据更新命令时报错:django.db.migrations.exceptions.InconsistentMigrationHistory: Migration admin.0001_initial is applied before its dependency users.00 01_initial on database 'default'.
    广告小程序后端开发(1.新建项目、连接数据库)
    pycharm的Database连接新的Mysql5.7报错[08001]
  • 原文地址:https://www.cnblogs.com/embedded-linux/p/11318418.html
Copyright © 2020-2023  润新知