The HTTP package

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 type

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

Client.Get

As discussed earlier, Get is a convenience method that issues an HTTP GET method to retrieve the resource specified by the url parameter from the server:

Get(url string,   
) (resp *http.Response, err   error)     

Client.Post

The Post method is a convenience method that issues an HTTP POST method to send the content specified by the body parameter to the server specified by the url parameter:

Post(   
  url string,    
  bodyType string,    
  body io.Reader,   
) (resp *http.Response, err error)   

Client.PostForm

The PostForm method is a convenience method that uses the HTTP POST method to send form data, specified as mapped key/value pairs, to the server:

PostForm(   
  url string,    
  data url.Values,   
) (resp *http.Response, err error)   

Client.Head

The Head method is a convenience method that issues an HTTP method, HEAD, to the remote server specified by the url parameter:

Head(url string,   
)(resp *http.Response, err error)   

Client.Do

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

Configuring the client

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, 
         }, 
   } 
... 
} 

Handling client requests and responses

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.

A simple HTTP server

The HTTP package provides two main components to accept HTTP requests and serve responses:

  • The http.Handler interface
  • The http.Server type

The 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

The default server

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.

Routing requests with http.ServeMux

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.

The default ServeMux

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.147.85.221