Documentation ¶
Overview ¶
Package serialserver provides a server for synchronous client-server communication.
This work is licensed under CC0 1.0 Universal (CC0 1.0), Public Domain Dedication. For details see:
http://creativecommons.org/publicdomain/zero/1.0/
Online go-documentation is available at:
https://godoc.org/bitbucket.org/pcas/serverutil/serial/serialserver
The basic communication protocol is described in package bitbucket.org/pcas/serverutil/serial (https://godoc.org/bitbucket.org/pcas/serverutil/serial). In summary:
client sends a task code specifying the task for the server to perform;
client sends message data (implementation specific, encoded as described in https://godoc.org/bitbucket.org/pcas/serverutil/serial);
server sends a response code, which is one of: - serverutil.OK - serverutil.Error - serverutil.Goodbye
4a. if the response code is serverutil.OK, server sends response data (implementation-specific, encoded as described in https://godoc.org/bitbucket.org/pcas/serverutil/serial);
4b. if the response code is serverutil.Error, server sends a serial.ErrorCode describing the error;
4c. if the response code is serverutil.Goodbye then the server is closing the connection without handling the task.
The server is free to silently close the connection at any time.
To get a new server, do something like:
S := serialserver.New(&serialserver.Timeout{ Wait: 30 * time.Minute, Read: 5 * time.Second, Write: 5 * time.Second, })
where Wait, Read, and Write are, respectively, the time.Duration timeout to use whilst waiting for a new task, the rolling read timeout once a task has started, and the rolling write timeout.
You then need to register a task function of type TaskFunc for each task code that the server could be passed. You do this by calling S.Register(t,f) where t is the serverutil.TaskCode and f is the TaskFunc.
The task function f will be called whenever the task code received by the server in step 1 above is t. The task function is passed a binaryutil.BinaryReader 'in' that contains the message data (if any) sent by the client in step 2. The reader 'in' will automatically decode the message data; in particular, the user never needs to encode or decode data, and 'in' will automatically return io.EOF at the end of the message data. The client should return a io.Reader 'out', or an error. Any response data should be written to the returned io.Reader 'out'; this response data will be sent by the server as in step 4a. The contents of 'out' will automatically be encoded before being sent to the server; the user never needs to encode data.
If a non-nil error err is returned by the task function then the reader 'out' will be ignored. If err satisfies the the serverutil.ErrorCoder interface, then a serverutil.Error response code will be sent to the client, followed by the err.ErrorCode(), as in step 4b. If the returned error does not satisfy the serverutil.ErrorCoder interface then the situation is serious: the client will be placed in an error state, and the connection will be dropped.
This task function is passed a context.Context ctx that will be cancelled on server shutdown, and which could also for example contain session-specific data such as username or authentication credentials (these would have been negotiated during a handshake, which takes place outside the serialclient/serialserver framework). Any long-running or blocking tasks should make use of this context. The task function is also provided with a log.Interface logger lg, to which it should write useful feedback.
Once all task functions have been registered, the server can be used as follows:
// Listen on the port listener, err := net.Listen("tcp", ":"+port) if err != nil { // Handle the error // ... } defer listener.Close() // Make a context to cancel everything ctx, cancel := context.WithCancel(context.Background()) defer cancel() // Register the task handlers (see the discussion above) // ... // Listen for connections for { conn, err := listener.Accept() if err != nil { // Handle the error // ... } // Do any handshake required and establish the byte // order e being used by the client // ... e := binary.BigEndian // Call the connection handler; updatedCtx is the // context ctx along with with any session-specific // data provided during the handshake. s.HandleConnection(updatedCtx, conn, e, args...) }
Any variadic arguments 'args' passed to HandleConnection will be passed to any task functions called for this connection. This provides a way of passing in session-specific data without attaching them to the context.
See package bitbucket.org/pcas/logger/cmd/logd (https://godoc.org/bitbucket.org/pcas/logger/cmd/logd) for a real-world example of a server that uses the serialserver package.
Package serialserver provides a server for basic, synchronous client-server communication.
This work is licensed under CC0 1.0 Universal (CC0 1.0), Public Domain Dedication. For details see:
http://creativecommons.org/publicdomain/zero/1.0/
Online go-documentation is available at:
https://godoc.org/bitbucket.org/pcas/serverutil/serial/serialserver
/////////////////////////////////////////////////////////////////////// server.go ///////////////////////////////////////////////////////////////////////
A Server listens for tasks on a specified connection and responds appropriately, with cancellation provided by a context ctx. So typical usage looks like:
ln, err := net.Listen("tcp", ":8080")
if err != nil { // handle error }
s := serialserver.New(timeouts)
for acceptingConnections { conn, err := ln.Accept() if err != nil { // handle error } // Typically some form of client handshake code goes here; this provides an // opportunity to determine the byte order e being used by the client. // ... // Handle the connection go s.HandleConnection(ctx, conn, e) }
Here timeouts describes the *serialserver.Timeout durations (and can be nil, in which case the default timeouts will be used), and ctx is a context.Context that can be used to terminate the client connection. Typically you will install a signal handler which, on catching a signal that indicates we should shut down, sets acceptingConnections to false and calls the cancel function for the context ctx.
Index ¶
- Constants
- Variables
- type Client
- func (c *Client) ByteOrder() binary.ByteOrder
- func (c *Client) Cancel()
- func (c *Client) Close() error
- func (c *Client) Disconnect(d time.Duration)
- func (c *Client) LocalAddr() net.Addr
- func (c *Client) Log() log.Interface
- func (c *Client) Metadata() *serial.TaskMetadata
- func (c *Client) Metrics() metrics.Interface
- func (c *Client) RemoteAddr() net.Addr
- func (c *Client) Server() *Server
- func (c *Client) SetLogger(lg log.Interface)
- func (c *Client) SetMetrics(m metrics.Interface)
- func (c *Client) String() string
- func (c *Client) TaskCodes() []serverutil.TaskCode
- func (c *Client) Timestamp() time.Time
- type Server
- func (s *Server) Clients() []serverutil.Client
- func (s *Server) Disconnect(d time.Duration)
- func (s *Server) HandleConnection(ctx context.Context, conn net.Conn, e binary.ByteOrder, args ...interface{}) error
- func (s *Server) Log() log.Interface
- func (s *Server) Metrics() metrics.Interface
- func (s *Server) NumClients() (n int)
- func (s *Server) Register(t serverutil.TaskCode, f TaskFunc, name string)
- func (s *Server) RegisteredTaskCodes() []serverutil.TaskCode
- func (s *Server) SetLogger(l log.Interface)
- func (s *Server) SetMetrics(m metrics.Interface)
- func (s *Server) TaskFunc(t serverutil.TaskCode) (TaskFunc, string, bool)
- func (s *Server) Wait()
- type TaskFunc
- type Timeout
Constants ¶
const ( DefaultWait = 30 * time.Minute DefaultRead = 5 * time.Second DefaultWrite = 5 * time.Second )
The default timeout durations.
Variables ¶
var ( ErrGracefulDisconnect = errors.New("Client requested to disconnect") // ErrGracefulDisconnect indicates that a graceful disconnection was used to close the client connection. ErrTimeoutWaitingForTask = errors.New("Timeout whilst waiting for task") // ErrTimeoutWaitingForTask indicates that the server timed-out whilst waiting for a new task from the client. )
Common errors.
Functions ¶
This section is empty.
Types ¶
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client describes a client.
func (*Client) Cancel ¶
func (c *Client) Cancel()
Cancel cancels the current task. If no task is currently executing, or if the task has already been cancelled, this does nothing.
func (*Client) Disconnect ¶
Disconnect begins a graceful disconnection. The connection will Close after duration d has elapsed, or as soon as the current task has finished being handled (whichever happens first). If Disconnect is called again, then the final Close will be performed after the shortest of the two durations has expired. This is a non-blocking method.
func (*Client) Metadata ¶
func (c *Client) Metadata() *serial.TaskMetadata
Metadata returns the metadata for the current task, or nil if no task is currently assigned.
func (*Client) RemoteAddr ¶
RemoteAddr returns the remote network address.
func (*Client) SetLogger ¶
SetLogger sets the logger for this client. By default the logger is inherited from the server.
func (*Client) SetMetrics ¶
SetMetrics sets the metrics endpoint for this client. By default this is inherited from the server.
func (*Client) String ¶
String returns a string description of the client of the form "remote addr->local addr".
func (*Client) TaskCodes ¶
func (c *Client) TaskCodes() []serverutil.TaskCode
TaskCodes returns a slice of all assigned tasks codes.
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server handles serial communication with clients.
func New ¶
New creates a new server. If timeout is nil then the default timeout durations will be used. This satisfies the serverutil.Server interface.
func (*Server) Clients ¶
func (s *Server) Clients() []serverutil.Client
Clients returns all client connections currently being handled by this server. The returned slice is sorted by increasing timestamp. Note: The underlying type of the entries in the returned slice is *Client.
func (*Server) Disconnect ¶
Disconnect calls the Disconnect method for each client currently being handled by this server.
func (*Server) HandleConnection ¶
func (s *Server) HandleConnection(ctx context.Context, conn net.Conn, e binary.ByteOrder, args ...interface{}) error
HandleConnection handles tasks from the given connection conn using the given byte order e. Use the given context to trigger a graceful disconnection. The connection will be closed on return. The given args will be passed to the task functions.
func (*Server) NumClients ¶
NumClients returns the number of client connections currently being handled by this server.
func (*Server) Register ¶
func (s *Server) Register(t serverutil.TaskCode, f TaskFunc, name string)
Register registers a task function for the given task code. If a function is already registered for this task code, it will be replaced with the new function. The given name is used purely for logging purposes, and will be ignored if empty. This will panic if the task code is in the reserved range.
func (*Server) RegisteredTaskCodes ¶
func (s *Server) RegisteredTaskCodes() []serverutil.TaskCode
RegisteredTaskCodes returns a slice of all currently registered task codes.
func (*Server) SetMetrics ¶
SetMetrics sets the metrics endpoint.
type TaskFunc ¶
type TaskFunc func(ctx context.Context, c *Client, in binaryutil.BinaryReader, lg log.Interface, args ...interface{}) (out io.Reader, err error)
TaskFunc is the function that will run a task.
The message data sent by the client can be read from the binaryutil.BinaryReader 'in'. The contents of 'in' are automatically decoded as per the definition of client-server communication; the task function does not need to handle message data decoding. The response data (if any) should be written to the returned io.Reader 'out'. The contents of 'out' will be automatically encoded as per the definition of client-server communication before being sent to the client; the task function does not need to handle response data encoding. The variadic arguments 'args' are the arguments provided to the server's HandleConnection method when the client connected. Potentially long-running or blocking tasks should make use of the context ctx to allow the task to be cancelled. The task function should provide useful feedback via the logger lg.
If no error is returned (i.e. if err == nil), then success header
- response code serverutil.OK (1 byte)
will be sent to the client, followed by the contents of the returned io.Reader 'out'. If the copying of data from 'out' to the client fails because of an error then the situation is serious: the client will be placed in an error state, and the connection will be dropped. If 'out' satisfies the io.Closer interface then its Close() method will be called once copying stops (for whatever reason). If you do not need to send any response data to the client, it is permitted for 'out' to be nil.
If a non-nil error is returned then the reader 'out' will be ignored. If err satisfies the the serverutil.ErrorCoder interface, then
- response code serverutil.Error (1 byte)
- error code (1 byte)
will be sent to the client, where the error code is given by err.ErrorCode(). If the returned error does not satisfy the serverutil.ErrorCoder interface then the situation is serious: the client will be placed in an error state, and the connection will be dropped.