Go Web服務器的基礎

Go是一種面向網絡的編程語言,事實上,標準庫具有非常強大的功能net/http模塊,Donovan和Kernighan撰寫的“ The Go Programming Language”一書中有一個Web服務器示例在第一章

// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/

package main

import ( “fmt” “log” “net/http” )

func main() { http.HandleFunc("/", handler) // each request calls handler log.Fatal(http.ListenAndServe(“localhost:8000”, nil)) }

// handler echoes the Path component of the requested URL. func handler(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, “URL.Path = %q\n”, r.URL.Path) }

這是HTTP服務器的最基本形式。

類型go run filename.go並且服務器處於活動狀態,接受傳入的請求。

handler函數採用ResponseWriter接口,該接口允許我們寫回客戶端以及HTTP請求的詳細信息。

http.Request顯示有關傳入請求的很多信息:

type Request struct {
        // Method specifies the HTTP method (GET, POST, PUT, etc.).
        // For client requests an empty string means GET.
        Method string
    <span style="color:#75715e">// 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

    <span style="color:#75715e">// 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

    <span style="color:#75715e">// 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

    <span style="color:#75715e">// 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)

    <span style="color:#75715e">// 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

    <span style="color:#75715e">// 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

    <span style="color:#75715e">// 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

    <span style="color:#75715e">// 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

    <span style="color:#75715e">// 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

    <span style="color:#75715e">// 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

    <span style="color:#75715e">// 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

    <span style="color:#75715e">// 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

    <span style="color:#75715e">// 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

    <span style="color:#75715e">// 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

    <span style="color:#75715e">// 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

    <span style="color:#75715e">// 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{}

    <span style="color:#75715e">// Response is the redirect response which caused this request

// to be created. This field is only populated during client // redirects. Response *Response // contains filtered or unexported fields }

在這種情況下,我們感興趣r.URL,是net.url包中定義的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
        RawPath    string // encoded path hint (Go 1.5 and later only; see EscapedPath method)
        ForceQuery bool   // append a query ('?') even if RawQuery is empty
        RawQuery   string // encoded query values, without '?'
        Fragment   string // fragment for references, without '#'
}

r.URL.Path打印當前請求的路徑,因此,簡而言之,我們目前編寫的Web服務器只是請求URL的簡單回顯。


多個請求處理程序

您如何為特定路由設置第二個請求處理程序,並讓原始處理程序管理所有其他路由請求?

package main

import ( “fmt” “log” “sync” )

func main() { http.HandleFunc("/", handler) http.HandleFunc("/about", aboutHandler) log.Fatal(http.ListenAndServe(“localhost:8000”, nil)) }

// handler echoes the Path component of the requested URL. func handler(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, “URL.Path = %q\n”, r.URL.Path) }

// counter echoes the number of calls so far. func aboutHandler(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, “URL.Path = %q\n”, r.URL.Path) //… }

這裡所有的請求任何網址仍由handler(), 除了/count。這是因為通過pattern以結尾的參數/http.HandleFunc()會導致所有子路由都匹配,除了找到更專門的子路由。

訪問資源

第二個Web服務器示例為處理程序提供了一種在並發環境中管理公共資源的方法。

// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/

package main

import ( “fmt” “log” “net/http” “sync” )

var mu sync.Mutex var count int

func main() { http.HandleFunc("/", handler) http.HandleFunc("/count", counter) log.Fatal(http.ListenAndServe(“localhost:8000”, nil)) }

// handler echoes the Path component of the requested URL. func handler(w http.ResponseWriter, r *http.Request) { mu.Lock() count++ mu.Unlock() fmt.Fprintf(w, “URL.Path = %q\n”, r.URL.Path) }

// counter echoes the number of calls so far. func counter(w http.ResponseWriter, r *http.Request) { mu.Lock() fmt.Fprintf(w, “Count %d\n”, count) mu.Unlock() }

本示例使用互斥鎖,因為在內部http.HandleFun()用途例行程序觸發請求處理程序,然後handler()函數增加包變量。為了避免比賽條件為了避免這種情況發生,我們必須在更改其值之前調用Mutex.Lock(對於counter()在打印其值時)。

打印請求頭

本書還提供了一個示例處理程序,其工作是打印格式正確的請求標頭:

// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/

// handler echoes the HTTP request.
func handler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "%s %s %s\n", r.Method, r.URL, r.Proto)
	for k, v := range r.Header {
		fmt.Fprintf(w, "Header[%q] = %q\n", k, v)
	}
	fmt.Fprintf(w, "Host = %q\n", r.Host)
	fmt.Fprintf(w, "RemoteAddr = %q\n", r.RemoteAddr)
	if err := r.ParseForm(); err != nil {
		log.Print(err)
	}
	for k, v := range r.Form {
		fmt.Fprintf(w, "Form[%q] = %q\n", k, v)
	}
}

在瀏覽器中打開服務器將得到以下輸出,例如:

GET / HTTP/1.1
Header["Connection"] = ["keep-alive"]
Header["Accept"] = ["text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"]
Header["Accept-Language"] = ["en-us"]
Header["Accept-Encoding"] = ["gzip, deflate"]
Header["Upgrade-Insecure-Requests"] = ["1"]
Header["User-Agent"] = ["Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.1 Safari/603.1.30"]
Header["Dnt"] = ["1"]
Host = "localhost:8000"
RemoteAddr = "127.0.0.1:51774"

更多教程: