Go Standard Library Cookbook

4.7 (3 reviews total)
By Radomir Sohlich
  • Instant online access to over 7,500+ books and videos
  • Constantly updated with 100+ new titles each month
  • Breadth and depth in over 1,000+ technologies
  1. Interacting with the Environment

About this book

Google's Golang will be the next talk of the town, with amazing features and a powerful library. This book will gear you up for using golang by taking you through recipes that will teach you how to leverage the standard library to implement a particular solution. This will enable Go developers to take advantage of using a rock-solid standard library instead of third-party frameworks.

The book begins by exploring the functionalities available for interaction between the environment and the operating system. We will explore common string operations, date/time manipulations, and numerical problems. We'll then move on to working with the database, accessing the filesystem, and performing I/O operations. From a networking perspective, we will touch on client and server-side solutions. The basics of concurrency are also covered, before we wrap up with a few tips and tricks.

By the end of the book, you will have a good overview of the features of the Golang standard library and what you can achieve with them. Ultimately, you will be proficient in implementing solutions with powerful standard libraries.

Publication date:
February 2018
Publisher
Packt
Pages
340
ISBN
9781788475273

 

Chapter 1. Interacting with the Environment

In this chapter, the following recipes are covered:

  • Retrieving the Golang version
  • Accessing program arguments
  • Creating a program interface with the flag package
  • Getting and setting environment variables with default values
  • Retrieving the current working directory
  • Getting the current process PID
  • Handling operating system signals
  • Calling an external process
  • Retrieving child process information
  • Reading/writing from the child process
  • Shutting down the application gracefully
  • File configuration with functional options
 

Introduction


Every program, once it is executed, exists in the environment of the operating system. The program receives input and provides output to this environment. The operating system also needs to communicate with the program to let it know what's happening outside. And finally, the program needs to respond with appropriate actions. 

This chapter will walk you through the basics of the discovery of the system environment, the program parameterization via program arguments, and the concept of the operating system signals. You will also learn how to execute and communicate with the child process.

 

Retrieving the Golang version


While building a program, it is a good practice to log the environment settings, build version, and runtime version, especially if your application is more complex. This helps you to analyze the problem, in case something goes wrong.

Besides the build version and, for example, the environmental variables, the Go version by which the binary was compiled could be included in the log. The following recipe will show you how to include the Go runtime version into such program information.

Getting ready

Install and verify the Go installation. The following steps could help:

  1. Download and install Go on your machine.
  2. Verify that your GOPATH and GOROOT environmental variables are set properly.
  3. Open your Terminal and execute go version. If you get output with a version name, then Go is installed properly.
  4. Create a repository in the GOPATH/src folder.

How to do it...

The following steps cover the solution:

  1. Open the console and create the folder chapter01/recipe01.
  2. Navigate to the directory.
  3. Create the main.go file with the following content:
        package main
        import (
          "log"
          "runtime"
        )
        const info = `
          Application %s starting.
          The binary was build by GO: %s`

        func main() {
          log.Printf(info, "Example", runtime.Version())
        }

  1. Run the code by executing the go run main.go.
  2. See the output in the Terminal:

How it works...

The runtime package contains a lot of useful functions. To find out the Go runtime version, the Version function could be used. The documentation states that the function returns the hash of the commit, and the date or tag at the time of the binary build.

The Version function, in fact, returns the runtime/internal/sys .The Version constant. The constant itself is located in the $GOROOT/src/runtime/internal/sys/zversion.go file.

This .go file is generated by the go dist tool and the version is resolved by the findgoversion function in the go/src/cmd/dist/build.go file, as explained next.

The $GOROOT/VERSION takes priority. If the file is empty or does not exist, the $GOROOT/VERSION.cache file is used. If the $GOROOT/VERSION.cache is also not found, the tool tries to resolve the version by using the Git information, but in this case, you need to initialize the Git repository for the Go source.

 

Accessing program arguments


The most simple way to parameterize the program run is to use the command-line arguments as program parameters. 

Simply, the parameterized program call could look like this: ./parsecsv user.csv role.csv. In this case, parsecsv is the name of the executed binary and user.csv and role.csv are the arguments, that modify the program call (in this case it refers to files to be parsed).

How to do it...

  1. Open the console and create the folder chapter01/recipe02.
  2. Navigate to the directory.
  3. Create the main.go file with the following content:
        package main
        import (
          "fmt"
          "os"
        )

        func main() {

          args := os.Args

          // This call will print
          // all command line arguments.
          fmt.Println(args)

          // The first argument, zero item from slice,
          // is the name of the called binary.
          programName := args[0]
          fmt.Printf("The binary name is: %s \n", programName)

          // The rest of the arguments could be obtained
          // by omitting the first argument.
          otherArgs := args[1:]
          fmt.Println(otherArgs)

          for idx, arg := range otherArgs {
            fmt.Printf("Arg %d = %s \n", idx, arg)
          }
        }
  1. Build the binary by executing go build -o test.
  2. Execute the command ./test arg1 arg2. (Windows users can run test.exe arg1 arg2).
  3. See the output in the Terminal:

How it works...

The Go standard library offers a few ways to access the arguments of the program call. The most generic way is to access the arguments by the Args variable from the OS package.

This way you can get all the arguments from the command line in a string slice. The advantage of this approach is that the number of arguments is dynamic and this way you can, for example, pass the names of the files to be processed by the program.

The preceding example just echoes all the arguments that are passed to the program. Finally, let's say the binary is called test and the program run is executed by the Terminal command ./test arg1 arg2.

In detail, the os.Args[0] will return ./test. The os.Args[1:] returns the rest of the arguments without the binary name. In the real world, it is better to not rely on the number of arguments passed to the program, but always check the length of the argument array. Otherwise, naturally, if the argument on a given index is not within the range, the program panics.

There's more…

If the arguments are defined as flags, -flag value, additional logic is needed to assign the value to the flag. In this case, there is a better way to parse these by using the flag package. This approach is part of the next recipe.

 

Creating a program interface with the flag package


The previous recipe describes how to access the program arguments by a very generic approach.

This recipe will provide a way of defining an interface via the program flags. This approach dominates systems based on GNU/Linux, BSD, and macOS.  The example of the program call could be ls -l which will, on *NIX systems, list the files in a current directory. 

The Go package for flag handling does not support flag combining like ls -ll, where there are multiple flags after a single dash. Each flag must be separate. The Go flag package also does not differentiate between long options and short ones. Finally, -flag and --flag are equivalent. 

How to do it...

  1. Open the console and create the folder chapter01/recipe03.
  2. Navigate to the directory.
  3. Create the main.go file with the following content:
        package main
        import (
          "flag"
          "fmt"
          "log"
          "os"
          "strings"
        )

        // Custom type need to implement
        // flag.Value interface to be able to
        // use it in flag.Var function.
        type ArrayValue []string

        func (s *ArrayValue) String() string {
          return fmt.Sprintf("%v", *s)
        }

        func (a *ArrayValue) Set(s string) error {
          *a = strings.Split(s, ",")
          return nil
        }

        func main() {

          // Extracting flag values with methods returning pointers
          retry := flag.Int("retry", -1, "Defines max retry count")

          // Read the flag using the XXXVar function.
          // In this case the variable must be defined
          // prior to the flag.
          var logPrefix string
          flag.StringVar(&logPrefix, "prefix", "", "Logger prefix")

          var arr ArrayValue
          flag.Var(&arr, "array", "Input array to iterate through.")

          // Execute the flag.Parse function, to
          // read the flags to defined variables.
          // Without this call the flag
          // variables remain empty.
          flag.Parse()

          // Sample logic not related to flags
          logger := log.New(os.Stdout, logPrefix, log.Ldate)

          retryCount := 0
          for retryCount < *retry {
            logger.Println("Retrying connection")
            logger.Printf("Sending array %v\n", arr)
            retryCount++
          }
        }
  1. Build the binary by executing the go build -o util.
  2. From the console, execute ./util -retry 2 -prefix=example -array=1,2.
  1. See the output in the Terminal:

How it works…

For the flag definition in code, the flag package defines two types of functions.

The first type is the simple name of the flag type such as Int. This function will return the pointer to the integer variable where the value of the parsed flag is.

The XXXVar functions are the second type. These provide the same functionality, but you need to provide the pointer to the variable. The parsed flag value will be stored in the given variable.

The Go library also supports a custom flag type. The custom type must implement the Value interface from the flag package.

As an example, let's say the flag retry defines the retry limit for reconnecting to the endpoint, the prefix flag defines the prefix of each row in a log, and the array is the array flag that will be send as an payload to server. The program call from the Terminal will look like ./util -retry 2 -prefix=example array=1,2.

The important part of the preceding code is the Parse() function which parses the defined flags from Args[1:]. The function must be called after all flags are defined and before the values are accessed.

The preceding code shows how to parse some data types from the command-line flags. Analogously, the other built-in types are parsed. 

The last flag, array, demonstrates the definition of the custom type flag. Note that the ArrayType implements the Value interface from the flag package.

There's more…

The flag package contains more functions to design the interface with flags. It is worth reading the documentation for FlagSet.

By defining the new FlagSet, the arguments could be parsed by calling the myFlagset.Parse(os.Args[2:]). This way you can have flag subsets based on, for example, the first flag.

 

Getting and setting environment variables with default values


The previous recipe, Creating a program interface with the flag package, describes how to use flags as program parameters.

The other typical way of parameterization, especially for larger applications, is the configuration with the use of environment variables. Environment variables as a configuration option significantly simplify the deployment of the applications. These are also very common in cloud infrastructure.

Usually, the configuration of a database connection for a local and for an automated build environment is different.

If the configuration is defined by the environment variables, it is not necessary to change the application config files or even the application code. The exported environment variables (for example, DBSTRING) are all we need. It is also very practical to default the configuration if the environmental variable is not in place. This way, the life of the application developers is much easier.

This recipe will demonstrate how to read, set and unset the environment variable. It will also show you how to implement the default option if the variable is not set.

How to do it…

  1. Open the console and create the folder chapter01/recipe04.
  2. Navigate to the directory.
  1. Create the get.go file with the following content:
        package main

        import (
          "log"
          "os"
        )

        func main() {
          connStr := os.Getenv("DB_CONN")
          log.Printf("Connection string: %s\n", connStr)
        }
  1. Execute the code by calling DB_CONN=db:/[email protected] && go run get.go in the Terminal.
  2. See the output in the Terminal:
  1. Create the lookup.go file with the following content:
        package main

        import (
          "log"
          "os"
        )

        func main() {

          key := "DB_CONN"

          connStr, ex := os.LookupEnv(key)
          if !ex {
            log.Printf("The env variable %s is not set.\n", key)
          }
          fmt.Println(connStr)
        }
  1. Execute the code by calling unset DB_CONN && go run lookup.go in the Terminal.
  2. See the output in the Terminal:
  1. Create the main.go file with the following content:
        package main
        import (
          "log"
          "os"
        )

        func main() {

          key := "DB_CONN"
          // Set the environmental variable.
          os.Setenv(key, "postgres://as:[email protected]/pg?
                          sslmode=verify-full")
          val := GetEnvDefault(key, "postgres://as:[email protected]/pg?
                                     sslmode=verify-full")
          log.Println("The value is :" + val)

          os.Unsetenv(key)
          val = GetEnvDefault(key, "postgres://as:[email protected]/pg?
                                    sslmode=verify-full")
          log.Println("The default value is :" + val)

        }

        func GetEnvDefault(key, defVal string) string {
          val, ex := os.LookupEnv(key)
          if !ex {
            return defVal
          }
          return val
        }
  1. Run the code by executing go run main.go.
  2. See the output in the Terminal:

How it works…

The environment variables are accessed by the Getenv and Setenv functions  in the os package. The names of the functions are self-explanatory and do not need any further description. 

There is one more useful function in the os package. The LookupEnv function provides two values as a result; the value of the variable, and the boolean value which defines if the variable was set or not in the environment.

The disadvantage of the os.Getenv function is that it returns an empty string, even in cases where the environment variable is not set.

This handicap could be overcome by the os.LookupEnv function, which returns the string as a value of the environment variable and the boolean value that indicates whether the variable was set or not.

 To implement the retrieval of the environment variable or the default one, use the os.LookupEnv function. Simply, if the variable is not set, which means that the second returned value is false, then the default value is returned. The use of the function is part of step 9.

 

Retrieving the current working directory


Another useful source of information for the application is the directory, where the program binary is located. With this information, the program can access the assets and files collocated with the binary file.

Note

This recipe is using the solution for Go since version 1.8. This one is the preferred one.

How to do it...

  1. Open the console and create the folder chapter01/recipe05.
  2. Navigate to the directory.
  3. Create the main.go file with the following content:
        package main

        import (
          "fmt"
          "os"
          "path/filepath"
        )

        func main() {
          ex, err := os.Executable()
          if err != nil {
            panic(err)
          }

          // Path to executable file
          fmt.Println(ex)

          // Resolve the direcotry
          // of the executable
          exPath := filepath.Dir(ex)
          fmt.Println("Executable path :" + exPath)

          // Use EvalSymlinks to get
          // the real path.
          realPath, err := filepath.EvalSymlinks(exPath)
          if err != nil {
            panic(err)
          }
          fmt.Println("Symlink evaluated:" + realPath)
        }
  1. Build the binary by the command go build -o binary.
  2. Execute the binary by the Terminal call ./binary.
  3. See the output. It should display the absolute path on your machine:

How it works…

Since Go 1.8, the Executable function from the os package is the preferred way of resolving the path of the executable. The Executable function returns the absolute path of the binary that is executed (unless the error is returned).

To resolve the directory from the binary path, the Dir from the filepath package is applied. The only pitfall of this is that the result could be the symlink or the path it pointed to.

To overcome this unstable behavior, the EvalSymlinks from the filepath package could be applied to the resultant path. With this hack, the returned value would be the real path of the binary.

The information about the directory where the binary is located could be obtained with the use of the Executable function in the os library.

Note that if the code is run by the command go run, the actual executable is located in a temporary directory.

 

Getting the current process PID


Getting to know the PID of the running process is useful. The PID could be used by OS utilities to find out the information about the process itself. It is also valuable to know the PID in case of process failure, so you can trace the process behavior across the system in system logs, such as /var/log/messages, /var/log/syslog.

This recipe shows you how to use the os package to obtain a PID of the executed program, and use it with the operating system utility to obtain some more information.

How to do it…

  1. Open the console and create the folder chapter01/recipe06.
  2. Navigate to the directory.
  3. Create the main.go file with the following content:
        package main

        import (
          "fmt"
          "os"
          "os/exec"
          "strconv"
        )

        func main() {

          pid := os.Getpid()
          fmt.Printf("Process PID: %d \n", pid)

          prc := exec.Command("ps", "-p", strconv.Itoa(pid), "-v")
          out, err := prc.Output()
          if err != nil {
            panic(err)
          }

          fmt.Println(string(out))
        }
  1. Run the code by executing the go run main.go.
  1. See the output in the Terminal:

How it works…

The function Getpid from the os package returns the PID of a process. The sample code shows how to get more information on the process from the operating system utility ps.

It could be useful to print the PID at the start of the application, so at the time of the crash, the cause could also be investigated by the retrieved PID.

 

Handling operating system signals


Signals are the elementary way the operating systems communicate with the running process. Two of the most usual signals are called SIGINT and SIGTERM.These cause the program to terminate.

There are also signals such as SIGHUP. SIGHUP indicates that the terminal which called the process was closed and, for example, the program could decide to move to the background.

Go provides a way of handling the behavior in case the application received the signal. This recipe will provide an example of implementing the handling.

How to do it…

  1. Open the console and create the folder chapter01/recipe07.
  2. Navigate to the directory.
  3. Create the main.go file with the following content:
        package main

        import (
          "fmt"
          "os"
          "os/signal"
          "syscall"
        )

        func main() {

          // Create the channel where the received
          // signal would be sent. The Notify
          // will not block when the signal
          // is sent and the channel is not ready.
          // So it is better to
          // create buffered channel.
          sChan := make(chan os.Signal, 1)

          // Notify will catch the
          // given signals and send
          // the os.Signal value
          // through the sChan.
          // If no signal specified in 
          // argument, all signals are matched.
          signal.Notify(sChan,
            syscall.SIGHUP,
            syscall.SIGINT,
            syscall.SIGTERM,
            syscall.SIGQUIT)

          // Create channel to wait till the
          // signal is handled.
          exitChan := make(chan int)
          go func() {
            signal := <-sChan
            switch signal {
              case syscall.SIGHUP:
                fmt.Println("The calling terminal has been closed")
                exitChan <- 0

              case syscall.SIGINT:
                fmt.Println("The process has been interrupted by CTRL+C")
                exitChan <- 1

              case syscall.SIGTERM:
                fmt.Println("kill SIGTERM was executed for process")
                exitChan <- 1

              case syscall.SIGQUIT:
                fmt.Println("kill SIGQUIT was executed for process")
                exitChan <- 1
            }
          }()

          code := <-exitChan
          os.Exit(code)
        }
  1. Run the code by executing go run main.go.
  2. Send the SIGINT signal to the application by pressing CTRL + C.
  3. See the output:

How it works…

In an application, where the resources are acquired, a resource leak could happen in the case of an instant termination. It is better to handle the signals and take some necessary steps to release the resources. The preceding code shows the concept of how to do that.

The Notify function from the signal package would be the one that helps us to handle the received signals.

Note

If no signal is specified as an argument in a Notify function, the function will catch all possible signals.

Note that the Notify function of the signal package is communicating with the goroutine by the sChan channel. Notify then catches the defined signals and sends these to goroutine to be handled. Finally, exitChan is used to resolve the exit code of the process.

The important information is that the Notify function will not block the signal if the assigned channel is not ready. This way the signal could be missed. To avoid missing the signal, it is better to create the buffered channel.

Note

Note that the SIGKILL and SIGSTOP signals may not be caught by the Notify function, thus it is not possible to handle these.

 

Calling an external process


The Go binary could also be used as a tool for various utilities and with use of go run as a replacement for the bash script. For these purposes, it is usual that the command-line utilities are called.

In this recipe, the basics of how to execute and handle the child process will be provided.

Getting ready

Test if the following commands work in your Terminal:

  1. Test if the ls (dir for Windows) command exists in your $PATH.
  2. You should be able to execute the ls (dir in Windows) command in your Terminal.

How to do it…

The following steps cover the solution:

  1. Open the console and create the folder chapter01/recipe08.
  2. Navigate to the directory.
  3. Create the run.go file with the following content:
        package main

        import (
          "bytes"
          "fmt"
          "os/exec"
        )

        func main() {

          prc := exec.Command("ls", "-a")
          out := bytes.NewBuffer([]byte{})
          prc.Stdout = out
          err := prc.Run()
          if err != nil {
            fmt.Println(err)
          }

          if prc.ProcessState.Success() {
            fmt.Println("Process run successfully with output:\n")
            fmt.Println(out.String())
          }
        }
  1. Run the code by executing go run run.go.
  1. See the output in the Terminal:
  1. Create the start.go file with the following content:
        package main

        import (
          "fmt"
          "os/exec"
        )

        func main() {

          prc := exec.Command("ls", "-a")
          err := prc.Start()
          if err != nil {
            fmt.Println(err)
          }

          prc.Wait()

          if prc.ProcessState.Success() {
            fmt.Println("Process run successfully with output:\n")
            fmt.Println(out.String())
          }
        }
  1. Run the code by executing go run start.go.
  2. See the output in Terminal:

How it works…

The Go standard library provides a simple way of calling the external process. This could be done by the Command function of the os/exec package.

The simplest way is to create the Cmd struct and call the Run function. The Run function executes the process and waits until it completes. If the command exited with an error, the err value is not null.

This is more suitable for calling the OS utils and tools, so the program does not hang too long.

The process could be executed asynchronously too. This is done by calling the Start method of the Cmd structure. In this case, the process is executed, but the main goroutine does not wait until it ends. The Wait method could be used to wait until the process ends. After the Wait method finishes, the resources of the process are released.

This approach is more suitable for executing long-running processes and services that the program depends on.

See also

This recipe describes how to simply execute the child process. There are Retrieve child process information and Reading/writing from the child process recipes in this chapter that also provide the steps on how to read from and write to the child process, and get useful information about the process.

 

Retrieving child process information


The recipe Calling an external process describes how to call the child process, synchronously and asynchronously. Naturally, to handle the process behavior you need to find out more about the process.  This recipe shows how to obtain the PID and elementary information about the child process after it terminates.

The information about the running process could be obtained only via the syscall package and it is highly platform-dependent.

Getting ready

Test if the sleep (timeout for Windows) command exists in the Terminal.

How to do it…

  1. Open the console and create the folder chapter01/recipe09.
  2. Navigate to the directory.
  3. Create the main_running.go file with the following content:
        package main

        import (
          "fmt"
          "os/exec"
          "runtime"
        )

        func main() {

          var cmd string
          if runtime.GOOS == "windows" {
            cmd = "timeout"
          } else {
            cmd = "sleep"
          }
          proc := exec.Command(cmd, "1")
          proc.Start()

          // No process state is returned
          // till the process finish.
          fmt.Printf("Process state for running process: %v\n",
                     proc.ProcessState)

          // The PID could be obtain
          // event for the running process
          fmt.Printf("PID of running process: %d\n\n", 
                     proc.Process.Pid)
        }
  1. Run the code by executing go run main_running.go.
  2. See the output in the Terminal:
  1. Create the main.go file with the following content:
        func main() {

          var cmd string
          if runtime.GOOS == "windows" {
            cmd = "timeout"
          } else {
            cmd = "sleep"
          }

          proc := exec.Command(cmd, "1")
          proc.Start()

          // Wait function will
          // wait till the process ends.
          proc.Wait()

          // After the process terminates
          // the *os.ProcessState contains
          // simple information
          // about the process run
          fmt.Printf("PID: %d\n", proc.ProcessState.Pid())
          fmt.Printf("Process took: %dms\n", 
                     proc.ProcessState.SystemTime()/time.Microsecond)
          fmt.Printf("Exited sucessfuly : %t\n",
                     proc.ProcessState.Success())
        }
  1. Run the code by executing go run main.go.
  2. See the output in the Terminal:

How it works…

The os/exec standard library provides the way to execute the process. Using Command, the Cmd structure is returned. The Cmd provides the access to process the representation. When the process is running, you can only find out the PID.

There is only a little information that you can retrieve about the process. But by retrieving the PID of the process, you are able to call the utilities from the OS to get more information.

Note

Remember that it is possible to obtain the PID of the child process, even if it is running. On the other hand, the ProcessState structure of the os package is available, only after the process terminates.

See also

There are Reading/writing from the child process and Calling an external process recipes in this chapter that are related to process handling.

 

Reading/writing from the child process


Every process, that is executed, has the standard output, input and error output. The Go standard library provides the way to read and write to these.

This recipe will walk through the approaches on how to read the output and write to the input of the child process.

Getting ready

Verify if the following commands work in the Terminal:

  1. Test if the ls (dir for Windows) command exists in the Terminal.
  2. You should be able to execute the ls (dir in Windows) command in your Terminal.

How to do it…

  1. Open the console and create the folder chapter01/recipe10.
  2. Navigate to the directory.
  3. Create the main_read_output.go file with the following content:
       package main

       import (
         "fmt"
         "os/exec"
         "runtime"
       )

       func main() {

         var cmd string

         if runtime.GOOS == "windows" {
           cmd = "dir"
         } else {
           cmd = "ls"
         }

         proc := exec.Command(cmd)

         // Output will run the process
         // terminates and returns the standard
         // output in a byte slice.
         buff, err := proc.Output()

         if err != nil {
           panic(err)
         }

         // The output of child
         // process in form
         // of byte slice
         // printed as string
         fmt.Println(string(buff))

       }
  1. Run the code by executing go run main_read_output.go.
  2. See the output in the Terminal:
  1. Create the main_read_stdout.go file with the following content:
        package main

        import (
          "bytes"
          "fmt"
          "os/exec"
          "runtime"
        )

        func main() {

          var cmd string

          if runtime.GOOS == "windows" {
            cmd = "dir"
          } else {
            cmd = "ls"
          }

          proc := exec.Command(cmd)

          buf := bytes.NewBuffer([]byte{})

          // The buffer which implements
          // io.Writer interface is assigned to
          // Stdout of the process
          proc.Stdout = buf

          // To avoid race conditions
          // in this example. We wait till
          // the process exit.
          proc.Run()

          // The process writes the output to
          // to buffer and we use the bytes
          // to print the output.
          fmt.Println(string(buf.Bytes()))

        }
  1. Run the code by executing go run main_read_stdout.go.
  2. See the output in the Terminal:
  1. Create the main_read_read.go file with the following content:
        package main

        import (
          "bufio"
          "context"
          "fmt"
          "os/exec"
          "time"
        )

        func main() {
          cmd := "ping"
          timeout := 2 * time.Second

          // The command line tool
          // "ping" is executed for
          // 2 seconds
          ctx, _ := context.WithTimeout(context.TODO(), timeout)
          proc := exec.CommandContext(ctx, cmd, "example.com")

          // The process output is obtained
          // in form of io.ReadCloser. The underlying
          // implementation use the os.Pipe
          stdout, _ := proc.StdoutPipe()
          defer stdout.Close()

          // Start the process
          proc.Start()

          // For more comfortable reading the
          // bufio.Scanner is used.
          // The read call is blocking.
          s := bufio.NewScanner(stdout)
          for s.Scan() {
            fmt.Println(s.Text())
          }
        }
  1. Run the code by executing go run main_read.go.
  2. See the output in the Terminal:
  1. Create the sample.go file with the following content:
        package main

        import (
          "bufio"
          "fmt"
          "os"
        )

        func main() {
          sc := bufio.NewScanner(os.Stdin)

          for sc.Scan() {
            fmt.Println(sc.Text())
          }
        }
  1. Create the main.go file with the following content:
        package main

        import (
          "bufio"
          "fmt"
          "io"
          "os/exec"
          "time"
        )

        func main() {
          cmd := []string{"go", "run", "sample.go"}

          // The command line tool
          // "ping" is executed for
          // 2 seconds
          proc := exec.Command(cmd[0], cmd[1], cmd[2])

          // The process input is obtained
          // in form of io.WriteCloser. The underlying
          // implementation use the os.Pipe
          stdin, _ := proc.StdinPipe()
          defer stdin.Close()

          // For debugging purposes we watch the
          // output of the executed process
          stdout, _ := proc.StdoutPipe()
          defer stdout.Close()

          go func() {
            s := bufio.NewScanner(stdout)
            for s.Scan() {
              fmt.Println("Program says:" + s.Text())
            }
          }()

          // Start the process
          proc.Start()

          // Now the following lines
          // are written to child
          // process standard input
          fmt.Println("Writing input")
          io.WriteString(stdin, "Hello\n")
          io.WriteString(stdin, "Golang\n")
          io.WriteString(stdin, "is awesome\n")

          time.Sleep(time.Second * 2)

          proc.Process.Kill()

        }
  1. Run the code by executing go run main.go.
  2. See the output in the Terminal:

How it works…

The Cmd structure of the os/exec package provides the functions to access the output/input of the process. There are a few approaches to read the output of the process.

One of the simplest ways to read the process output is to use the Output or CombinedOutput method of the Cmd structure (gets Stderr and Stdout). While calling this function, the program synchronously waits till the child process terminates and then returns the output to a byte buffer.

Besides the Output and OutputCombined methods, the Cmd structure provides the Stdout property, where the io.Writer could be assigned. The assigned writer then serves as a destination for the process output. It could be a file, byte buffer or any type implementing the io.Writer interface.

The last approach to read the process output is to get the io.Reader from the Cmd structure by calling the StdoutPipe method. The StdoutPipe method creates the pipe between the Stdout, where the process writes the output, and provides Reader which works as the interface for the program to read the process output. This way the output of the process is piped to the retrieved io.Reader . 

Writing to a process stdin works the same way. Of all the options, the one with io.Writer will be demonstrated.

As could be seen, there are a few ways to read and write from the child process. The use of stderr and stdin is almost the same as described in steps 6-7. Finally, the approach of how to access the input/output could be divided this way:

  • Synchronous (wait until the process ends and get the bytes): The Output and CombinedOutput methods of Cmd are used.
  • IO: The output or input are provided in the form of io.Writer/Reader. The XXXPipe and StdXXX properties are the right ones for this approach.

The IO type is more flexible and could also be used asynchronously.

 

Shutting down the application gracefully


Servers and daemons are the programs that run for a long time (typically days or even weeks). These long-running programs usually allocate resources (database connections, network sock) at the start and keep these resources as long as they exist. If such a process is killed and the shutdown is not handled properly, a resource leak could happen. To avoid that behavior, the so-called graceful shutdown should be implemented.

Graceful, in this case, means that the application catches the termination signal, if possible, and tries to clean up and release the allocated resources before it terminates. This recipe will show you how to implement the graceful shutdown.

The recipe, Handling operating system signals describes the catching of OS signals. The same approach will be used for implementing the graceful shutdown. Before the program terminates, it will clean up and carry out some other activities.

How to do it…

  1. Open the console and create the folder chapter01/recipe11.
  2. Navigate to the directory.
  3. Create the main.go file with the following content:
        package main

        import (
          "fmt"
          "io"
          "log"
          "os"
          "os/signal"
          "syscall"
          "time"
        )

        var writer *os.File

        func main() {

          // The file is opened as
          // a log file to write into.
          // This way we represent the resources
          // allocation.
          var err error
          writer, err = os.OpenFile(fmt.Sprintf("test_%d.log",
                time.Now().Unix()), os.O_RDWR|os.O_CREATE, os.ModePerm)
          if err != nil {
            panic(err)
          }

          // The code is running in a goroutine
          // independently. So in case the program is
          // terminated from outside, we need to
          // let the goroutine know via the closeChan
          closeChan := make(chan bool)
          go func() {
            for {
              time.Sleep(time.Second)
              select {
                case <-closeChan:
                  log.Println("Goroutine closing")
                  return
                default:
                  log.Println("Writing to log")
                  io.WriteString(writer, fmt.Sprintf("Logging access
                                 %s\n", time.Now().String()))
              }  

            }
          }()

          sigChan := make(chan os.Signal, 1)
          signal.Notify(sigChan,
            syscall.SIGTERM,
            syscall.SIGQUIT,
            syscall.SIGINT)

          // This is blocking read from
          // sigChan where the Notify function sends
          // the signal.
          <-sigChan

          // After the signal is received
          // all the code behind the read from channel could be
          // considered as a cleanup.
          // CLEANUP SECTION
          close(closeChan)
          releaseAllResources()
          fmt.Println("The application shut down gracefully")
        }

        func releaseAllResources() {
          io.WriteString(writer, "Application releasing 
                         all resources\n")
          writer.Close()
        }
  1. Run the code by executing go run main.go.
  2. Press CTRL + C to send a SIGINT signal.
  3. Wait until the Terminal output looks like this:
  1. The recipe11 folder should also contain a file called test_XXXX.log, which contains lines like this:

How it works…

The reading from a sigChan is blocking so the program keeps running until the Signal is sent through the channel. The sigChan is the channel where the Notify function sends the signals.

The main code of the program runs in a new goroutine. This way, the work continues while the main function is blocked on the sigChan. Once the signal from operation system is sent to process, the sigChan receives the signal and the code below the line where the reading from the sigChan channel is executed. This code section could be considered as the cleanup section.

Note that the step 7 terminal output contains the final log, Application releasing all resources, which is part of the cleanup section.

See also

A detailed description of how the signal catching works is in the recipe Handling operating system signals.

 

File configuration with functional options


This recipe is not directly related to the Go standard library but includes how to handle an optional configuration for your application. The recipe will use the functional options pattern in a real case with a file configuration. 

How to do it...

  1. Open the console and create the folder chapter01/recipe12.
  2. Navigate to the directory.
  3. Create the main.go file with the following content:
        package main

        import (
          "encoding/json"
          "fmt"
          "os"
        )

        type Client struct {
          consulIP string
          connString string
        }

        func (c *Client) String() string {
          return fmt.Sprintf("ConsulIP: %s , Connection String: %s",
                             c.consulIP, c.connString)
        }

        var defaultClient = Client{
          consulIP: "localhost:9000",
          connString: "postgres://localhost:5432",
        }

        // ConfigFunc works as a type to be used
        // in functional options
        type ConfigFunc func(opt *Client)

        // FromFile func returns the ConfigFunc
        // type. So this way it could read the configuration
        // from the json.
        func FromFile(path string) ConfigFunc {
          return func(opt *Client) {
            f, err := os.Open(path)
            if err != nil {
              panic(err)
            }
            defer f.Close()
            decoder := json.NewDecoder(f)

            fop := struct {
              ConsulIP string `json:"consul_ip"`
            }{}
            err = decoder.Decode(&fop)
            if err != nil {
              panic(err)
            }
            opt.consulIP = fop.ConsulIP
          }
        }

        // FromEnv reads the configuration
        // from the environmental variables
        // and combines them with existing ones.
        func FromEnv() ConfigFunc {
          return func(opt *Client) {
            connStr, exist := os.LookupEnv("CONN_DB")
            if exist {
              opt.connString = connStr
            }
          }
        }

        func NewClient(opts ...ConfigFunc) *Client {
          client := defaultClient
          for _, val := range opts {
            val(&client)
          }
          return &client
        }

        func main() {
          client := NewClient(FromFile("config.json"), FromEnv())
          fmt.Println(client.String())
        }
  1. In the same folder, create the file config.json with content:
        {
          "consul_ip":"127.0.0.1"
        }
  1. Execute the code by the command CONN_DB=oracle://local:5921 go run main.go.
  2. See the output:

How it works...

The core concept of the functional options pattern is that the configuration API contains the functional parameters. In this case, the NewClient function accepts a various number of ConfigFunc arguments, which are then applied one by one on the defaultClient struct. This way, the default configuration is modified with huge flexibility. 

See the FromFile and FromEnv functions, which return the ConfigFunc, that is in fact, accessing the file or environmental variables.

Finally, you can check the output which applied both the configuration options and resulting Client struct that contains the values from the file and environmental variables.

About the Author

  • Radomir Sohlich

    Radomír Sohlich received the master's degree in Applied Informatics from Faculty of Applied Informatics at Tomas Bata University in Zlín. After that, he got a job in a start-up company as a software developer and worked on various projects, usually based on the Java platform. Currently, he continues a software developer career as a contractor for a large international company.

    In 2015, he fell in love with Go and kept exploring the endless power and possibilities of the language. He is passionate about learning new approaches and technology and feels the same about sharing the knowledge with others.

    Browse publications by this author

Latest Reviews

(3 reviews total)
Ouvrage très clair et complet
Great code snippets. Some really interesting utilities
Handig boek over standaardlibrary

Recommended For You

Go Programming Cookbook - Second Edition

Tackle the trickiest of problems in Go programming with this practical guide

By Aaron Torres
Mastering Go - Second Edition

Dive deep into the Go language and become an expert Go developer

By Mihalis Tsoukalos
Learn Data Structures and Algorithms with Golang

Explore Golang's data structures and algorithms to design, implement, and analyze code in the professional setting

By Bhagvan Kommadi
Hands-On GUI Application Development in Go

Discover Golang's GUI libraries such as Go-GTK (GIMP Toolkit) and Go-Qt and build beautiful, performant, and responsive graphical applications

By Andrew Williams