Due to its importance and ubiquity, HTTP is one of a handful of protocols directly implemented in Go. The net/http
package (https://golang.org/pkg/net/http/) provides code to implement both HTTP clients and HTTP servers. This section explores the fundamentals of creating HTTP clients and servers using the net/http
package. Later, we will return our attention back to building versions of our currency service using HTTP.
The http.Client
struct represents an HTTP client and is used to create HTTP requests and retrieve responses from a server. The following illustrates how to retrieve the text content of Beowulf from Project Gutenberg's website located at http://gutenberg.org/cache/epub/16328/pg16328.txt, using the client
variable of the http.Client
type and prints its content to a standard output:
func main() { client := http.Client{} resp, err := client.Get( " http://gutenberg.org/cache/epub/16328/pg16328.txt") if err != nil { fmt.Println(err) return } defer resp.Body.Close() io.Copy(os.Stdout, resp.Body) }
golang.fyi/ch11/httpclient1.go
The previous example uses the client.Get
method to retrieve content from the remote server using the HTTP protocol method GET
internally. The GET
method is part of several convenience methods offered, by the Client type, to interact with HTTP servers as summarized in the following table. Notice that all of these methods return a value of the *http.Response
type (discussed later) to handle responses returned by the HTTP server.
Method |
Description |
|
As discussed earlier, Get(url string, ) (resp *http.Response, err error) |
|
The Post( url string, bodyType string, body io.Reader, ) (resp *http.Response, err error) |
|
The PostForm( url string, data url.Values, ) (resp *http.Response, err error) |
|
The Head(url string, )(resp *http.Response, err error) |
|
This method generalizes the request and response interaction with a remote HTTP server. It is wrapped internally by the methods listed in this table. Section Handling client requests and responses discusses how to use this method to talk to the server. |
It should be noted that the HTTP package uses an internal http.Client
variable designed to mirror the preceding methods as package functions for further convenience. They include http.Get
,
http.Post
, http.PostForm
, and http.Head
. The following snippet shows the previous example using http.Get
instead of the method from the http.Client
:
func main() { resp, err := http.Get( "http://gutenberg.org/cache/epub/16328/pg16328.txt") if err != nil { fmt.Println(err) return } defer resp.Body.Close() io.Copy(os.Stdout, resp.Body) }
golang.fyi/ch11/httpclient1a.go
Besides the methods to communicate with the remote server, the http.Client
type exposes additional attributes that can be used to modify and control the behavior of the client. For instance, the following source snippet sets the timeout to handle a client request to complete within 21 seconds using the Timeout
attribute of the Client
type:
func main() { client := &http.Client{ Timeout: 21 * time.Second } resp, err := client.Get( "http://tools.ietf.org/rfc/rfc7540.txt") if err != nil { fmt.Println(err) return } defer resp.Body.Close() io.Copy(os.Stdout, resp.Body) }
golang.fyi/ch11/httpclient2.go
The Transport
field of the Client
type provides further means of controlling the settings of a client. For instance, the following snippet creates a client that disables the connection reuse between successive HTTP requests with the DisableKeepAlive
field. The code also uses the Dial
function to specify further granular control over the HTTP connection used by the underlying client, setting its timeout value to 30 seconds:
func main() { client := &http.Client{ Transport: &http.Transport{ DisableKeepAlives: true, Dial: (&net.Dialer{ Timeout: 30 * time.Second, }).Dial, }, } ... }
An http.Request
value can be explicitly created using the http.NewRequest
function. A request value can be used to configure HTTP settings, add headers, and specify the content body of the request. The following source snippet uses the http.Request
type to create a new request which is used to specify the headers sent to the server:
func main() { client := &http.Client{} req, err := http.NewRequest( "GET", "http://tools.ietf.org/rfc/rfc7540.txt", nil, ) req.Header.Add("Accept", "text/plain") req.Header.Add("User-Agent", "SampleClient/1.0") resp, err := client.Do(req) if err != nil { fmt.Println(err) return } defer resp.Body.Close() io.Copy(os.Stdout, resp.Body) }
golang.fyi/ch11/httpclient3.go
The http.NewRequest
function has the following signature:
func NewRequest(method, uStr string, body io.Reader) (*http.Request, error)
It takes a string that specifies the HTTP method as its first argument. The next argument specifies the destination URL. The last argument is an io.Reader
that can be used to specify the content of the request (or set to nil if the request has no content). The function returns a pointer to a http.Request
struct value (or a non-nil error
if one occurs). Once the request value is created, the code uses the Header
field to add HTTP headers to the request to be sent to the server.
Once a request is prepared (as shown in the previous source snippet), it is sent to the server using the Do method of the http.Client
type and has the following signature:
Do(req *http.Request) (*http.Response, error)
The method accepts a pointer to an http.Request
value, as discussed in the previous section. It then returns a pointer to an http.Response
value or an error if the request fails. In the previous source code, resp, err := client.Do(req)
is used to send the request to the server and assigns the response to the resp
variable.
The response from the server is encapsulated in struct http.Response
which contains several fields to describe the response including the HTTP response status, content length, headers, and the response body. The response body, exposed as the http.Response.Body
field, implements the io.Reader
which affords the use streaming IO primitives to consume the response content.
The Body
field also implements
io.Closer
which allows the closing of IO resources. The previous source uses defer resp.Body.Close()
to close the IO resource associated with the response body. This is a recommended idiom when the server is expected to return a non-nil body.
The HTTP package provides two main components to accept HTTP requests and serve responses:
http.Handler
interfacehttp.Server
typeThe http.Server
type uses the http.Handler
interface type, defined in the following listing, to receive requests and server responses:
type Handler interface { ServeHTTP(ResponseWriter, *Request) }
Any type that implements http.Handler
can be registered (explained next) as a valid handler. The Go http.Server
type is used to create a new server. It is a struct whose values can be configured, at a minimum, with the TCP address of the service and a handler that will respond to incoming requests. The following code snippet shows a simple HTTP server that defines the msg
type as handler registered to handle incoming client requests:
type msg string func (m msg) ServeHTTP( resp http.ResponseWriter, req *http.Request) { resp.Header().Add("Content-Type", "text/html") resp.WriteHeader(http.StatusOK) fmt.Fprint(resp, m) } func main() { msgHandler := msg("Hello from high above!") server := http.Server{Addr: ":4040", Handler: msgHandler} server.ListenAndServe() }
golang.fyi/ch11/httpserv0.go
In the previous code, the msg
type, which uses a string as its underlying type, implements the ServeHTTP()
method making it a valid HTTP handler. Its ServeHTTP
method uses the response parameter, resp
, to print response headers "200 OK"
and "Content-Type: text/html"
. The method also writes the string value m
to the response variable using fmt.Fprint(resp, m)
which is sent back to the client.
In the code, the variable server
is initialized as http.Server{Addr: ":4040", Handler: msgHandler}
. This means the server will listen on all network interfaces at port 4040
and will use variable msgHandler
as its http.Handler
implementation. Once initialized, the server is started with the server.ListenAndServe()
method call that is used to block and listen for incoming requests.
Besides the Addr
and Handler
, the http.Server
struct exposes several additional fields that can be used to control different aspects of the HTTP service such as connection, timeout values, header sizes, and TLS configuration. For instance, the following snippet shows an updated example which specifies the server's read and write timeouts:
type msg string func (m msg) ServeHTTP( resp http.ResponseWriter, req *http.Request) { resp.Header().Add("Content-Type", "text/html") resp.WriteHeader(http.StatusOK) fmt.Fprint(resp, m) } func main() { msgHandler := msg("Hello from high above!") server := http.Server{ Addr: ":4040", Handler: msgHandler, ReadTimeout: time.Second * 5, WriteTimeout: time.Second * 3, } server.ListenAndServe() }
golang.fyi/ch11/httpserv1.go
It should be noted that the HTTP package includes a default server that can be used in simpler cases when there is no need for configuration of the server. The following abbreviated code snippet starts a simple server without explicitly creating a server variable:
type msg string func (m msg) ServeHTTP( resp http.ResponseWriter, req *http.Request) { resp.Header().Add("Content-Type", "text/html") resp.WriteHeader(http.StatusOK) fmt.Fprint(resp, m) } func main() { msgHandler := msg("Hello from high above!") http.ListenAndServe(":4040", msgHandler) }
golang.fyi/ch11/httpserv2.go
In the code, the http.ListenAndServe(":4040", msgHandler)
function is used to start a server which is declared as a variable in the HTTP package. The server is configured with the local address ":4040"
and the handler msgHandler
(as was done earlier) to handle all incoming requests.
The http.Handler
implementation introduced in the previous section is not sophisticated. No matter what URL path is sent with the request, it sends the same response back to the client. That is not very useful. In most cases, you want to map each path of a request URL to a different response.
Fortunately, the HTTP package comes with the http.ServeMux
type which can multiplex incoming requests based on URL patterns. When an http.ServeMux
handler receives a request, associated with a URL path, it dispatches a function that is mapped to that URL. The following abbreviated code snippet shows http.ServeMux
variable mux
configured to handle two URL paths "/hello"
and "/goodbye"
:
func main() { mux := http.NewServeMux() hello := func(resp http.ResponseWriter, req *http.Request) { resp.Header().Add("Content-Type", "text/html") resp.WriteHeader(http.StatusOK) fmt.Fprint(resp, "Hello from Above!") } goodbye := func(resp http.ResponseWriter, req *http.Request) { resp.Header().Add("Content-Type", "text/html") resp.WriteHeader(http.StatusOK) fmt.Fprint(resp, "Goodbye, it's been real!") } mux.HandleFunc("/hello", hello) mux.HandleFunc("/goodbye", goodbye) http.ListenAndServe(":4040", mux) }
golang.fyi/ch11/httpserv3.go
The code declares two functions assigned to variables hello
and goodbye
. Each function is mapped to a path "/hello"
and "/goodbye"
respectively using the mux.HandleFunc("/hello", hello)
and mux.HandleFunc("/goodbye", goodbye)
method calls. When the server is launched, with http.ListenAndServe(":4040", mux)
, its handler will route the request "http://localhost:4040/hello"
to the hello
function and requests with the path "http://localhost:4040/goodbye"
to the goodbye
function.
It is worth pointing out that the HTTP package makes available a default ServeMux internally. When used, it is not necessary to explicitly declare a ServeMux variable. Instead the code uses the package function, http.HandleFunc,
to map a path to a handler function as illustrated in the following code snippet:
func main() {
hello := func(resp http.ResponseWriter, req *http.Request) {
...
}
goodbye := func(resp http.ResponseWriter, req *http.Request) {
...
}
http.HandleFunc("/hello", hello)
http.HandleFunc("/goodbye", goodbye)
http.ListenAndServe(":4040", nil)
}
golang.fyi/ch11/httpserv4.go
To launch the server, the code calls http.ListenAndServe(":4040", nil)
where the ServerMux parameter is set to nil
. This implies that the server will default to the per-declared package instance of http.ServeMux to handle incoming requests.
18.219.4.174