Chapter 11. Writing Networked Services

One of the many reasons for Go's popularity, as a system language, is its inherent support for creating networked programs. The standard library exposes APIs ranging from low-level socket primitives to higher-level service abstractions such as HTTP and RPC. This chapter explores fundamental topics about creating connected applications including the following:

  • The net package
  • A TCP API server
  • The HTTP package
  • A JSON API server

The net package

The starting point for all networked programs in Go is the net package (https://golang.org/pkg/net). It provides a rich API to handle low-level networking primitives as well as application-level protocols such as HTTP. Each logical component of a network is represented by a Go type including hardware interfaces, networks, packets, addresses, protocols, and connections. Furthermore, each type exposes a multitude of methods giving Go one of the most complete standard libraries for network programming supporting both IPv4 and IPv6.

Whether creating a client or a server program, Go programmers will need, at a minimum, the network primitives covered in the following sections. These primitives are offered as functions and types to facilitate clients connecting to remote services and servers to handle incoming requests.

Addressing

One of the basic primitives, when doing network programming, is the address. The types and functions of the net package use a string literal to represent an address such as "127.0.0.1". The address can also include a service port separated by a colon such as "74.125.21.113:80". Functions and methods in the net package also support string literal representation for IPv6 addresses such as "::1" or "[2607:f8b0:4002:c06::65]:80" for an address with a service port of 80.

The net.Conn Type

The net.Conn interface represents a generic connection established between two nodes on the network. It implements io.Reader and io.Writer interfaces which allow connected nodes to exchange data using streaming IO primitives. The net package offers network protocol-specific implementations of the net.Conn interface such as IPConn, UDPConn, and TCPConn. Each implementation exposes additional methods specific to its respective network and protocol. However, as we will see in this chapter, the default method set defined in net.Conn is adequate for most uses.

Dialing a connection

Client programs use the net.Dial function, which has the following signature, to connect to a host service over the network:

func Dial(network, address string) (Conn, error) 

The function takes two parameters where the first parameter, network, specifies the network protocol for the connection which can be:

  • tcp, tcp4, tcp6 : tcp defaults to tcp4
  • udp, udp4, udp6: udp defaults to udp4
  • ip, ip4, ip6: ip defaults to ip4
  • unix, unixgram, unixpacket: for Unix domain sockets

The latter parameter of the net.Dial function specifies a string value for the host address to which to connect. The address can be provided as IPv4 or IPv6 addresses as discussed earlier. The net.Dial function returns an implementation of the net.Conn interface that matches the specified network parameter.

For instance, the following code snippet dials a "tcp" network at the host address, www.gutenberg.org:80, which returns a TCP connection of the *net.TCPConn type. The abbreviated code uses the TCP connection to issue an "HTTP GET" request to retrieve the full text of the literary classic Beowulf from the Project Gutenberg's website (http://gutenberg.org/). The raw and unparsed HTTP response is subsequently written to a local file, beowulf.txt:

func main() { 
   host, port := "www.gutenberg.org", "80" 
   addr := net.JoinHostPort(host, port) 
   httpRequest:="GET  /cache/epub/16328/pg16328.txt HTTP/1.1
" + 
         "Host: " + host + "

" 
 
   conn, err := net.Dial("tcp", addr) 
   if err != nil { 
         fmt.Println(err) 
         return 
   } 
   defer conn.Close() 
 
   if _, err = conn.Write([]byte(httpRequest)); err != nil { 
         fmt.Println(err) 
         return 
   } 
 
   file, err := os.Create("beowulf.txt") 
   if err != nil { 
         fmt.Println(err) 
         return 
   } 
   defer file.Close() 
 
   io.Copy(file, conn) 
   fmt.Println("Text copied to file", file.Name()) 
}

golang.fyi/ch11/dial0.go

Because the net.Conn type implements the io.Reader and io.Writer, it can be used to both send data and receive data using streaming IO semantics. In the preceding example, conn.Write([]byte(httpRequest)) sends the HTTP request to the server. The response returned by the host is copied from the conn variable to the file variable using io.Copy(file, conn).

Note

Note that the previous is an illustration that shows how to connect to an HTTP server using raw TCP. The Go standard library provides a separate package designed specifically for HTTP programming which abstracts away the low-level protocol details (covered later in the chapter).

The net package also makes available network specific dialing functions such as DialUDP, DiapTCP, or DialIP, each returning its respective connection implementation. In most cases, the net.Dial function and the net.Conn interface provide adequate capabilities to connect and manage connections to a remote host.

Listening for incoming connections

When creating a service program, one the first steps is to announce the port which the service will use to listen for incoming requests from the network. This is done by invoking the net.Listen function which has the following signature:

func Listen(network, laddr string) (net.Listener, error) 

It takes two parameters where the first parameter specifies a protocol with valid values of "tcp", "tcp4", "tcp6", "unix", or "unixpacket".

The second parameter is the local host address for the service. The local address can be specified without an IP address such as ":4040". Omitting the IP address of the host means that the service is bound to all network card interfaces installed on the host. As an alternative, the service can be bound to a specific network hardware interface on the host by specifying its IP address on the network, that is, "10.20.130.240:4040".

A successful call to the net.Listen function returns a value of the net.Listener type (or a non-nil error if it fails). The net.Listener interface exposes methods used to manage the life cycle of incoming client connections. Depending on the value of the network parameter ("tcp", "tcp4", "tcp6", and so on.), net.Listen will return either a net.TCPListener or net.UnixListener , both of which are concrete implementations of the net.Listener interface.

Accepting client connections

The net.Listener interface uses the Accept method to block indefinitely until a new connection arrives from a client. The following abbreviated code snippet shows a simple server that returns the string "Nice to meet you!" to each client connection and then disconnects immediately:

func main() { 
   listener, err := net.Listen("tcp", ":4040") 
   if err != nil { 
         fmt.Println(err) 
         return 
   } 
   defer listener.Close() 
 
   for { 
         conn, err := listener.Accept() 
         if err != nil { 
               fmt.Println(err) 
               return 
         } 
         conn.Write([]byte("Nice to meet you!")) 
         conn.Close() 
   } 
} 

golang.fyi/ch11/listen0.go

In the code, the listener.Accept method returns a value of the net.Conn type to handle data exchange between the server and the client (or it returns a non-nil error if it fails). The conn.Write([]byte("Nice to meet you!")) method call is used to write the response to the client. When the server program is running, it can be tested using a telnet client as shown in the following output:

$> go run listen0.go & 
[1] 83884 
 
$> telnet 127.0.0.1 4040 
Trying 127.0.0.1... 
Connected to localhost. 
Escape character is '^]'. 
Nice to meet you! Connection closed by foreign host.

To ensure that the server program continues to run and handle subsequent client connections, the call to the Accept method is wrapped within an infinite for-loop. As soon as a connection is closed, the loop restarts the cycle to wait for the next client connection. Also notice that it is a good practice to close the listener when the server process is shutting down with a call to Listener.Close().

Note

The observant reader may notice that this simple server will not scale as it cannot handle more than one client request at once. In the next section, we will see the techniques for creating a scalable server.

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

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