socketcmd

package module
v0.0.0-...-f8e1335 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 13, 2017 License: GPL-3.0 Imports: 15 Imported by: 0

README

go-socketcmd

A Golang library to enable socket-based communication with applications not designed to support it. This allows for communicating with the wrapped application's stdin through the socket, as well as configurable stdout mirroring back along the socket. While running, the wrapped process will also be attached to the caller's stdin and stdout, allowing for transparent interaction in addition to the socket integration.

This package is primarily designed with Unix domain sockets in mind, though any standard implementation of net.Listener should be compatible with the wrapper, including TCP sockets. See the offiical documentation for more details.

Note: Only one connection will be processed at a time to prevent data corruption and race conditions. Multiple clients may connect to the socket, but those connections will be accepted and processed serially.

Wrapper Usage

Browse the provided examples to get started.

Package installation

go get github.com/faceless-saint/go-socketcmd

Setup and wrapper execution
import (
	"github.com/faceless-saint/go-socketcmd"

	"net"
	"os/exec"
)

// Note: error handling has been omitted for brevity

func main() {
	var cmd *exec.Cmd
	//... prepare the exec.Cmd object here (see examples)

	// Create listener for the socketcmd.Wrapper
	ln, err := net.Listen("tcp", "0.0.0.0")

	// create wrapper using the given net.Listener
	tcpWrapper, err := socketcmd.New(ln, cmd)

	// create wrapper using the specified UNIX socket
	unixWrapper, err := socketcmd.NewUnix("/path/to/control.sock", cmd)

	// Start the wrapped command
	err = tcpWrapper.Start()

	// Wait for the wrapped command to exit
	err = tcpWrapper.Wait()

	// Run the wrapped command and block until it exits
	err = unixWrapper.Run()
}
Client connections
import (
	"github.com/faceless-saint/go-socketcmd"

	"context"
	"net"
	"os/exec"
)

// Note: error handling has been omitted for brevity

func main() {
	// Command arguments to send - will be joined into a space-separated string
	command := []string{"cmd", "arg1", "arg2"}

	// Create a new Client connecting to the given socket using the default header
	client := socketcmd.NewClient("unix", "/path/to/control.sock", nil)

	// Create a new Client with a custom parse function
	parsef = func(args []string) string {
		return "-1:" // Placeholder - return the default header
	}
	customClient := socketcmd.NewClient("unix", "/path/to/control.sock", parsef)

	// Send a command to the socket. The header is generated using the client's parse function
	err := client.Send(command...)

	// Command arguments to send, including the socketcmd header
	command = []string{"-1:", "cmd", "arg1", "arg2"}

	// Send a command, manually specifying the desired socktcmd header
	err = client.Send(command...)

	// You can also provide a context for the connection
	err = client.SendContext(context.Background, command...)
}
Header parsing rules

The socketcmd header is in the following format: [n]:[t]

General rules:

  • Both n and t must be integers
  • Either value (but not the :) may be omitted to use the default value

Lines:

  • If n is less than 1, then an unlimited number of lines may be read
  • If n is exactly 0, then no lines will be read regardless of timeout
  • If n is greater than 0, then at most n lines will be read

Timeout:

  • t cannot be negative
  • If t is 0, then the default timeout will be used
  • If t is greater than 0, then the timeout will be t milliseconds

Setting a maximum line count is very useful if you know exactly how many lines of stdout will result from the send command, because this allows the client to return immediately after receiving those lines. Otherwise, you must wait for the timeout to elapse before releasing the connection.

Examples:

  • -1: - default header, read unlimited lines until the default timeout has elapsed
  • : - null header, expect no lines of output and exit immediately
  • 4: - counting header, read at most 4 lines of output with the default timeout
  • -1:10000 - waiting header, like default but with a custom (typically slower) timeout
  • 4:10000 - combination of counting and waiting, read at most 4 lines with a custom timeout

Documentation

Index

Constants

View Source
const (
	// Default timeout in milliseconds
	DefaultTimeout = 1000
	// Buffer size in bytes for incoming connections
	ConnBufferSize = 2048
)

Variables

View Source
var (
	// Read unlimited lines until the default timeout
	DefaultHeader = "-1:"

	// Command not allowed (will not be sent to Wrapper)
	ForbiddenHeader = "-:"

	// Do not wait for any response
	EmptyHeader = ":"

	ErrMissingHeader    = fmt.Errorf("missing or invalid socketcmd header")
	ErrCommandForbidden = fmt.Errorf("the provided command is not allowed")
)

Functions

func Cmd

func Cmd(cmd string, args ...string) *exec.Cmd

Cmd returns a new exec.Cmd for use with a wrapper.

func DefaultParseFunc

func DefaultParseFunc(_ []string) string
func Header(lines, timeout int) string

Header representation of the given line count and timeout.

func ParseHeader

func ParseHeader(header string) (lines, timeout int, err error)

ParseHeader extracts the line count and timeout from the given header.

Types

type Argument

type Argument struct {
	Args   map[string]Argument
	Header string
}

func NewArguments

func NewArguments(table map[string]string, defaultHeader string) Argument

func (*Argument) AddArguments

func (a *Argument) AddArguments(table map[string]string)

func (*Argument) AddNestedArguments

func (a *Argument) AddNestedArguments(table map[string]map[string]string)

func (*Argument) Match

func (a *Argument) Match(args []string) string

func (*Argument) ParseFunc

func (a *Argument) ParseFunc() ParseFunc

type Client

type Client interface {
	/* Dialer sets the dialer configuration used by the Client.
	 */
	Dialer(net.Dialer)
	/* Send the given arguments to the socket Wrapper. The Client's parser function is used
	 * to generate the socketcmd header appropriate for the given arguments.
	 */
	Send(args ...string) ([]string, error)
	/* SendContext sends the given arguments to the socket Wrapper, using the given context
	 * to manage the connection. The Client's parser function is used to generate the
	 * socketcmd header appropriate for the given arguments.
	 */
	SendContext(ctx context.Context, args ...string) ([]string, error)
}

A Client connects to a Wrapper's socket.

func NewClient

func NewClient(proto, addr string, parser ParseFunc) Client

NewClient returns a new Client for the given socket address and parser.

type Handler

type Handler interface {
	// Addr returns the address of the underlying net Listener.
	Addr() net.Addr
	// Close the socket listener.
	Close() error
	// Start the goroutines for managing process I/O redirection.
	Start()
}

A Handler manages network socket and I/O redirection.

func NewHandler

func NewHandler(listener net.Listener, stdin io.Writer, stdout io.Reader) Handler

NewHandler returns a new Handler for the given socket listener and I/O pipes.

type ParseFunc

type ParseFunc func(args []string) string

A ParseFunc determines the proper header for a given command sequence.

type Wrapper

type Wrapper interface {
	// Addr returns the address of the underlying net Listener.
	Addr() net.Addr
	// Run the wrapped process.
	Run() error
	// Start the wrapped process.
	Start() error
	// Wait for the wrapped process to exit.
	Wait() error

	// ExposeAPI for high-level network operations.
	ExposeAPI(ParseFunc) WrapperAPI
}

A Wrapper provides I/O redirection for a process. Input to the Wrapper's network socket * will be forwarded to the process, with the resulting lines of stdout returned to the * socket client.

func New

func New(listener net.Listener, cmd *exec.Cmd) (Wrapper, error)

New returns a new socket Wrapper around the given command using the given net Listener.

func NewUnix

func NewUnix(socket string, cmd *exec.Cmd) (Wrapper, error)

NewUnix returns a new socket Wrapper around the given command using a new UNIX domain * socket Listener with the given address.

type WrapperAPI

type WrapperAPI interface {
	Wrapper
	/* Listen on the given address and serve the command endpoint at the given path.
	 */
	Listen(addr, path string) error
	/* Default Handler function for the WrapperAPI. This method may be used to integrate
	 * the WrapperAPI into an existing API or extend it with other endpoints. This endpoint
	 * expects to receive a command sequence as an array of strings in JSON format. If the
	 * first element is a valid socketcmd Header then it will be used to parse the response.
	 * Otherwise, the configured ParseFunc will be used to generate a header based on the
	 * given command sequence. The response will by sent back as a JSON array of strings.
	 */
	CommandEndpoint(http.ResponseWriter, *http.Request)
}

A WrapperAPI extends an enclosed Wrapper with high-level remote API operations.

Directories

Path Synopsis
client command
server command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL