Skip to content

A UNIX domain socket server and client implementation in go (bidirectional, nonstreaming)

License

Notifications You must be signed in to change notification settings

vaitekunas/unixsock

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

unixsock godoc Go Report Card Build Status Coverage Status

UNIX domain sockets are a method by which processes on the same host can communicate with each other. The communication is bidirectional, so that both the client and the server can send and receive messages.

One special case, which unixsock has been written to address, is to enable communication with a running process (e.g. a web server), so that it could be reconfigured without downtime and monitored without additional tools (e.g. fetching real-time statistics).

Some of the advantages of using UNIX domain sockets instead of alternatives (reconfiguration via environment variables, or a web-based UI):

  • Accessible only on the host by a user having permission to read the socket file.
  • No need to implement additional authentication methods - authentication can be handled by the ssh agent
  • No need to build a UI (although it could be more comfortable in some cases). Building a sysadmin UI usually requires additional security measures, user isolation and so on. In case of UNIX sockets all this is handled by the host.

The distributed logging facility journald is an example of using the UNIX domain socket to configure and monitor a running application.

Server

UnixSockSrv's core is the request handler. At instantiation time the server accepts only a single monolithic handler, which should be able to handle all the relevant commands, as well as unknown requests.

The simplest way of implementing this is by puttint the whole API into a switch statement, e.g.:

func handler(cmd string, args unixsock.Args) *unixsock.Response {

  switch strings.ToLower(cmd) {
  case "echo":
    return echo()
  default:
    return &unixsock.Response{
      Status: unixsock.STATUS_FAIL,
      Error: fmt.Errorf("handler: unknown command '%s'", cmd),      
    }
  }  
}

func echo() *unixsock.Response {
  return &unixsock.Response{
    Status: unixsock.STATUS_OK,
    Payload: cmd
  }
}

Having written a request handler, we can start the server. If the UnixSockSrv is used for configuration and monitoring, then it will usually run in its own goroutine until the main application exits, e.g.:

unixSockPath := fmt.Sprintf("%s/server.sock", os.Getenv("HOME"))

quitChan := make(chan bool, 1)

go func() {
  srv, err := server.New(unixSockPath, handler)
  if err != nil {
    log.Fatal(err.Error())
  }
  <- quitChan
  srv.Stop()
}
...

Client

The client must know the path to the socket file as well as the API that the server is handling. Other than that, the communication with an instance of UnixSockSrv is straightforward:

unixSockPath := fmt.Sprintf("%s/server.sock", os.Getenv("HOME"))

client, err := client.New(unixSockPath)
if err != nil {
  log.Fatal(err.Error())
}

cmd := "echo"
args := map[string]interface{}{}

resp, err := client.Send(cmd, args, true, true)
if err != nil {
  log.Fatal(err.Error())
}

The client can choose whether the server should respond and/or close the connection after receiving the message. If the message does not require immediate closing of the connection, it will timeout on its own after 5 seconds (default). The timeout duration can be adjusted via the UnixSockClient.Options method:

client, err := client.New(unixSockPath)
if err != nil {
  log.Fatal(err.Error())
}

// Set maximum message size to 4Mb and the timeout to 30 seconds
client.Options(4<<20, 30*time.Second, true, false)

About

A UNIX domain socket server and client implementation in go (bidirectional, nonstreaming)

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages