webwire-go: github.com/qbeon/webwire-go Index | Files | Directories

package webwire

import "github.com/qbeon/webwire-go"

Index

Package Files

connection.go connectionOptions.go defaultSessionManager.go deregisterHandler.go errors.go failMsg.go fulfillMsg.go genericSessionInfo.go handleConnection.go handleMessage.go handleRequest.go handleSessionClosure.go handleSessionRestore.go handleSignal.go interfaces.go isShuttingDown.go newServer.go payload.go registerHandler.go server.go serverOptions.go session.go sessionInfoToVarMap.go sessionLookupResult.go sessionRegistry.go socket.go translateContextError.go transport.go

func IsErrCanceled Uses

func IsErrCanceled(err error) bool

IsErrCanceled returns true if the given error is a ErrCanceled, otherwise returns false

func IsErrTimeout Uses

func IsErrTimeout(err error) bool

IsErrTimeout returns true if the given error is either a ErrTimeout or a ErrDeadlineExceeded, otherwise returns false

func SessionInfoToVarMap Uses

func SessionInfoToVarMap(info SessionInfo) map[string]interface{}

SessionInfoToVarMap is a utility function that turns a session info compliant object into a map of variants. This is helpful for serialization of session info objects.

func TranslateContextError Uses

func TranslateContextError(err error) error

TranslateContextError translates context errors to webwire error types

type ClientSocket Uses

type ClientSocket interface {
    // Dial connects the socket to the server
    Dial(deadline time.Time) error

    Socket
}

ClientSocket defines the abstract client socket implementation interface

type ClientTransport Uses

type ClientTransport interface {
    // NewSocket initializes a new client socket
    NewSocket(dialTimeout time.Duration) (ClientSocket, error)
}

ClientTransport defines the interface of a webwire client transport

type Connection Uses

type Connection interface {
    // IsActive returns true if this connection is in active state
    // ready to accept incoming messages, otherwise returns false
    IsActive() bool

    // RemoteAddr returns the remote address
    RemoteAddr() net.Addr

    // Creation returns the time of connection establishment
    Creation() time.Time

    // Info returns arbitrary information by key which was assigned by the
    // transport layer implementation during the connection establishment.
    // Returns nil if the provided key was not found
    Info(key int) interface{}

    // Signal sends a named signal containing the given payload to the client.
    // The name is optional
    Signal(name []byte, payload Payload) error

    // CreateSession creates a new session for this connection and
    // automatically synchronizes the new session to the remote client.
    // The synchronization happens asynchronously using a signal
    // and doesn't block the calling goroutine.
    // Returns an error if there's already another session active
    CreateSession(attachment SessionInfo) error

    // CloseSession disables the currently active session for this connection
    // and synchronize the closure to the remote client.
    // The session will be destroyed if this is it's last connection remaining.
    // Does nothing if there's no active session
    CloseSession() error

    // HasSession returns true if this connection currently has
    // a session assigned, otherwise returns false
    HasSession() bool

    // Session returns an exact copy of the session object or nil if there's no
    // session currently assigned to this connection
    Session() *Session

    // SessionKey returns the key of the currently assigned session.
    // Returns an empty string if there's no session assigned to this connection
    SessionKey() string

    // SessionCreation returns the creation time
    // of the currently assigned session.
    // Warning: be sure to check whether there's a session beforehand because
    // this function will return garbage if
    // there's currently no session assigned
    SessionCreation() time.Time

    // SessionInfo returns a copy of the session info field value
    // in the form of an empty interface to be casted to either concrete type
    SessionInfo(name string) interface{}

    // Close marks this connection for shutdown.
    // It defers closing the connection until all work on it is done
    // and removes it from the session registry.
    // Does nothing when called multiple times
    Close()
}

Connection represents a connected client

type ConnectionAcceptance Uses

type ConnectionAcceptance byte

ConnectionAcceptance defines whether a connection is to be accepted

const (
    // Accept instructs the server to accept the incoming connection
    Accept ConnectionAcceptance = iota

    // Refuse instructs the server to refuse the incoming connection
    Refuse
)

type ConnectionOptions Uses

type ConnectionOptions struct {
    // Info stores arbitrary connection information provided by the transport
    // layer implementation
    Info map[int]interface{}

    // Connection refuses the incoming connection when explicitly set to
    // wwr.Refuse. It's set to wwr.Accept by default.
    Connection ConnectionAcceptance

    // ConcurrencyLimit defines the maximum number of operations to be processed
    // concurrently for this particular client connection. If ConcurrencyLimit
    // is 0 (which it is by default) then the number of concurrent operations
    // for this particular connection will be limited to 1. Anything below 0
    // will lift the limitation entirely while everything above 0 will set the
    // limit to the specified number of handlers
    ConcurrencyLimit int
}

ConnectionOptions represents the options applied to an individual connection during accept

type DefaultSessionKeyGenerator Uses

type DefaultSessionKeyGenerator struct{}

DefaultSessionKeyGenerator implements the webwire.SessionKeyGenerator interface

func (*DefaultSessionKeyGenerator) Generate Uses

func (gen *DefaultSessionKeyGenerator) Generate() string

Generate implements the webwire.Sessio

type DefaultSessionManager Uses

type DefaultSessionManager struct {
    // contains filtered or unexported fields
}

DefaultSessionManager represents a default session manager implementation. It uses files as a persistent storage

func NewDefaultSessionManager Uses

func NewDefaultSessionManager(sessFilesPath string) *DefaultSessionManager

NewDefaultSessionManager constructs a new default session manager instance. Verifies the existence of the given session directory and creates it if it doesn't exist yet

func (*DefaultSessionManager) OnSessionClosed Uses

func (mng *DefaultSessionManager) OnSessionClosed(sessionKey string) error

OnSessionClosed implements the session manager interface. It closes the session by deleting the according session file

func (*DefaultSessionManager) OnSessionCreated Uses

func (mng *DefaultSessionManager) OnSessionCreated(conn Connection) error

OnSessionCreated implements the session manager interface. It writes the created session into a file using the session key as file name

func (*DefaultSessionManager) OnSessionLookup Uses

func (mng *DefaultSessionManager) OnSessionLookup(key string) (
    SessionLookupResult,
    error,
)

OnSessionLookup implements the session manager interface. It searches the session file directory for the session file and loads it. It also updates the file by updating the last lookup session field.

type ErrBufferOverflow Uses

type ErrBufferOverflow struct{}

ErrBufferOverflow represents a message buffer overflow error

func (ErrBufferOverflow) Error Uses

func (err ErrBufferOverflow) Error() string

Error implements the error interface

type ErrCanceled Uses

type ErrCanceled struct {
    Cause error
}

ErrCanceled represents a failure due to cancellation

func (ErrCanceled) Error Uses

func (err ErrCanceled) Error() string

Error implements the error interface

type ErrDeadlineExceeded Uses

type ErrDeadlineExceeded struct {
    Cause error
}

ErrDeadlineExceeded represents a failure due to an excess of a user-defined deadline

func (ErrDeadlineExceeded) Error Uses

func (err ErrDeadlineExceeded) Error() string

Error implements the error interface

type ErrDialTimeout Uses

type ErrDialTimeout struct{}

ErrDialTimeout represents a dialing error caused by a timeout

func (ErrDialTimeout) Error Uses

func (err ErrDialTimeout) Error() string

Error implements the error interface

type ErrDisconnected Uses

type ErrDisconnected struct {
    Cause error
}

ErrDisconnected represents an error indicating a disconnected connection

func (ErrDisconnected) Error Uses

func (err ErrDisconnected) Error() string

Error implements the error interface

type ErrIncompatibleProtocolVersion Uses

type ErrIncompatibleProtocolVersion struct {
    RequiredVersion  string
    SupportedVersion string
}

ErrIncompatibleProtocolVersion represents a connection error indicating that the server requires an incompatible version of the protocol and can't therefore be connected to

func (ErrIncompatibleProtocolVersion) Error Uses

func (err ErrIncompatibleProtocolVersion) Error() string

Error implements the error interface

type ErrInternal Uses

type ErrInternal struct{}

ErrInternal represents a server-side internal error

func (ErrInternal) Error Uses

func (err ErrInternal) Error() string

Error implements the error interface

type ErrMaxSessConnsReached Uses

type ErrMaxSessConnsReached struct{}

ErrMaxSessConnsReached represents an authentication error indicating that the given session already reached the maximum number of concurrent connections

func (ErrMaxSessConnsReached) Error Uses

func (err ErrMaxSessConnsReached) Error() string

Error implements the error interface

type ErrProtocol Uses

type ErrProtocol struct {
    Cause error
}

ErrProtocol represents a protocol error

func ErrNewProtocol Uses

func ErrNewProtocol(err error) ErrProtocol

ErrNewProtocol constructs a new ErrProtocol error based on the actual error

func (ErrProtocol) Error Uses

func (err ErrProtocol) Error() string

Error implements the error interface

type ErrRequest Uses

type ErrRequest struct {
    Code    string
    Message string
}

ErrRequest represents an error returned when a request couldn't be processed

func (ErrRequest) Error Uses

func (err ErrRequest) Error() string

Error implements the error interface

type ErrServerShutdown Uses

type ErrServerShutdown struct{}

ErrServerShutdown represents a request error indicating that the request cannot be processed due to the server currently being shut down

func (ErrServerShutdown) Error Uses

func (err ErrServerShutdown) Error() string

Error implements the error interface

type ErrSessionNotFound Uses

type ErrSessionNotFound struct{}

ErrSessionNotFound represents a session restoration error indicating that the server didn't find the session to be restored

func (ErrSessionNotFound) Error Uses

func (err ErrSessionNotFound) Error() string

Error implements the error interface

type ErrSessionsDisabled Uses

type ErrSessionsDisabled struct{}

ErrSessionsDisabled represents an error indicating that the server has sessions disabled

func (ErrSessionsDisabled) Error Uses

func (err ErrSessionsDisabled) Error() string

Error implements the error interface

type ErrSockRead Uses

type ErrSockRead interface {
    error

    // IsCloseErr must return true if the error represents a closure error
    IsCloseErr() bool
}

ErrSockRead defines the interface of a webwire.Socket.Read error

type ErrTimeout Uses

type ErrTimeout struct {
    Cause error
}

ErrTimeout represents a failure caused by a timeout

func (ErrTimeout) Error Uses

func (err ErrTimeout) Error() string

Error implements the error interface

type ErrTransmission Uses

type ErrTransmission struct {
    Cause error
}

ErrTransmission represents a connection error indicating a failed transmission

func (ErrTransmission) Error Uses

func (err ErrTransmission) Error() string

Error implements the error interface

type GenericSessionInfo Uses

type GenericSessionInfo struct {
    // contains filtered or unexported fields
}

GenericSessionInfo defines a default webwire.SessionInfo interface implementation type used by the client when no explicit session info parser is used

func (*GenericSessionInfo) Copy Uses

func (sinf *GenericSessionInfo) Copy() SessionInfo

Copy implements the webwire.SessionInfo interface. It deep-copies the object and returns it's exact clone

func (*GenericSessionInfo) Fields Uses

func (sinf *GenericSessionInfo) Fields() []string

Fields implements the webwire.SessionInfo interface. It returns a constant list of the names of all fields of the object

func (*GenericSessionInfo) Value Uses

func (sinf *GenericSessionInfo) Value(fieldName string) interface{}

Value implements the webwire.SessionInfo interface. It returns an exact deep copy of a session info field value

type HeadlessServer Uses

type HeadlessServer interface {
    // Shutdown appoints a server shutdown and blocks the calling goroutine
    // until the server is gracefully stopped awaiting all currently processed
    // signal and request handlers to return.
    // During the shutdown incoming connections are rejected
    // with 503 service unavailable.
    // Incoming requests are rejected with an error while incoming signals
    // are just ignored
    Shutdown() error

    // ActiveSessionsNum returns the number of currently active sessions
    ActiveSessionsNum() int

    // SessionConnectionsNum implements the SessionRegistry interface
    SessionConnectionsNum(sessionKey string) int

    // SessionConnections implements the SessionRegistry interface
    SessionConnections(sessionKey string) []Connection

    // CloseSession closes the session identified by the given key and returns
    // the affected connections, a list of errors for each session session
    // closure attempt and a general error which is not nil if at least
    // of the closeErrors errors is not nil.
    // If no session was closed then (nil, nil, nil) is returned.
    CloseSession(sessionKey string) (
        affectedConnections []Connection,
        closeErrors []error,
        err error,
    )
}

HeadlessServer defines the interface of a headless webwire server instance

type IsShuttingDown Uses

type IsShuttingDown func() bool

IsShuttingDown must be called when the server is accepting a new connection and refuse the connection if true is returned

type JSONEncodedSession Uses

type JSONEncodedSession struct {
    Key        string                 `json:"k"`
    Creation   time.Time              `json:"c"`
    LastLookup time.Time              `json:"l"`
    Info       map[string]interface{} `json:"i,omitempty"`
}

JSONEncodedSession represents a JSON encoded session object. This structure is used during session restoration for unmarshalling TODO: move to internal shared package

type Message Uses

type Message interface {
    // Identifier returns the message identifier
    Identifier() [8]byte

    // Name returns the name of the message
    Name() []byte

    // PayloadEncoding returns the payload encoding type
    PayloadEncoding() PayloadEncoding

    // Payload returns the message payload in binary format
    Payload() []byte

    // PayloadUtf8 returns the message payload in textual UTF8 format
    PayloadUtf8() ([]byte, error)

    // Close closes the message releasing the underlying buffer
    Close()
}

Message represents a WebWire message

Message is not thread-safe and must not be used concurrently from within multiple goroutines

type OnNewConnection Uses

type OnNewConnection func(
    connectionOptions ConnectionOptions,
    socket Socket,
)

OnNewConnection must be called when the connection is ready to be used by the webwire server

type OptionValue Uses

type OptionValue = int32

OptionValue represents the setting value of an option

const (
    // OptionUnset represents the default unset value
    OptionUnset OptionValue = iota

    // Disabled disables an option
    Disabled

    // Enabled enables an option
    Enabled
)

type Payload Uses

type Payload struct {
    // Encoding represents the encoding type of the payload which is
    // EncodingBinary by default
    Encoding PayloadEncoding

    // Data represents the payload data
    Data []byte
}

Payload represents an encoded payload

type PayloadEncoding Uses

type PayloadEncoding = payload.Encoding

PayloadEncoding represents the type of encoding of the message payload

const (
    // EncodingBinary represents unencoded binary data
    EncodingBinary PayloadEncoding = payload.Binary

    // EncodingUtf8 represents UTF8 encoding
    EncodingUtf8 PayloadEncoding = payload.Utf8

    // EncodingUtf16 represents UTF16 encoding
    EncodingUtf16 PayloadEncoding = payload.Utf16
)

type Reply Uses

type Reply interface {
    // PayloadEncoding returns the payload encoding type
    PayloadEncoding() PayloadEncoding

    // Payload returns the message payload in binary format
    Payload() []byte

    // PayloadUtf8 returns the message payload in textual UTF8 format
    PayloadUtf8() ([]byte, error)

    // Close closes the reply message releasing the underlying buffer
    Close()
}

Reply defines a webwire request reply message

Reply is not thread-safe and must not be used concurrently from within multiple goroutines

type Server Uses

type Server interface {
    // Run will launch the webwire server blocking the calling goroutine
    // until the server is either gracefully shut down
    // or crashes returning an error
    Run() error

    // Address returns the URL address the webwire server is listening on
    Address() url.URL

    HeadlessServer
}

Server defines the interface of a headed webwire server instance

func NewServer Uses

func NewServer(
    implementation ServerImplementation,
    opts ServerOptions,
    transport Transport,
) (instance Server, err error)

NewServer creates a new webwire server instance

type ServerImplementation Uses

type ServerImplementation interface {
    // OnClientConnected is invoked when a new client successfully established a
    // connection to the server.
    //
    // OnClientConnected must return for the server to start processing incoming
    // requests & signals. To prevent blocking the connection initialization
    // process it is advised to move any time consuming work to a separate
    // goroutine
    OnClientConnected(connectionOptions ConnectionOptions, client Connection)

    // OnClientDisconnected is invoked after a client connection was closed.
    //
    // This hook will be invoked by the goroutine serving the calling client
    // before it's unlinked
    OnClientDisconnected(client Connection, reason error)

    // OnSignal is invoked when the webwire server receives a signal from a
    // client. The invocation of OnSignal is potentially concurrent.
    //
    // BEWARE: The byte-slice returned by message.Payload() references a
    // potentially pooled buffer that's reused by another handler goroutine as
    // soon as OnSignal returns. Aliasing it and using it after OnSignal
    // returned will cause a data race! Make a copy of the slice if you need the
    // message payload to escape the OnSignal handler context.
    //
    // This hook will be invoked by the goroutine serving the calling client and
    // will block any other interactions with this client while executing
    OnSignal(ctx context.Context, client Connection, message Message)

    // OnRequest is invoked when the webwire server receives a request from a
    // client. It must return either a response payload or an error. The
    // invocation of OnRequest is potentially concurrent.
    //
    // A webwire.ErrRequest error can be returned to reply with an error code
    // and an error message, this is useful when the clients user code needs to
    // be able to understand the error and react accordingly. If a non-webwire
    // error type is returned such as an error created by fmt.Errorf() then a
    // special kind of error (internal server error) is returned to the client
    // as a reply, in this case the error will be logged and the error message
    // will not be sent to the client for security reasons as this might
    // accidentally leak sensitive information to the client.
    //
    // BEWARE: The byte-slice returned by message.Payload() references a
    // potentially pooled buffer that's reused by another handler goroutine as
    // soon as OnRequest returns. Aliasing it and using it after OnRequest
    // returned will cause a data race! Make a copy of the slice if you need the
    // message payload to escape the OnRequest handler context.
    //
    // This hook will be invoked by the goroutine serving the calling client and
    // will block any other interactions with this client while executing
    OnRequest(
        ctx context.Context,
        client Connection,
        message Message,
    ) (
        payload Payload,
        err error,
    )
}

ServerImplementation defines the interface of a webwire server implementation

type ServerOptions Uses

type ServerOptions struct {
    Sessions              OptionValue
    SessionManager        SessionManager
    SessionKeyGenerator   SessionKeyGenerator
    SessionInfoParser     SessionInfoParser
    MaxSessionConnections uint
    WarnLog               *log.Logger
    ErrorLog              *log.Logger
    ReadTimeout           time.Duration

    // SubProtocolName defines the optional name of the hosted webwire
    // sub-protocol
    SubProtocolName []byte

    // MessageBufferSize defines the size of the message buffer
    MessageBufferSize uint32
}

ServerOptions represents the options used during the creation of a new WebWire server instance

func (*ServerOptions) Prepare Uses

func (op *ServerOptions) Prepare() error

Prepare verifies the specified options and sets the default values to unspecified options

type Session Uses

type Session struct {
    Key        string
    Creation   time.Time
    LastLookup time.Time
    Info       SessionInfo
}

Session represents a session object. If the key is empty the session is invalid. Info can contain arbitrary attached data

func NewSession Uses

func NewSession(info SessionInfo, generator func() string) Session

NewSession generates a new session object generating a cryptographically random secure key

func (*Session) Clone Uses

func (s *Session) Clone() *Session

Clone returns an exact copy of the session object

type SessionInfo Uses

type SessionInfo interface {
    // Fields returns the exact names of all fields of the session info object.
    // This getter method is idempotent, which means that it always returns the
    // same list of names
    Fields() []string

    // Value returns an exact deep copy of the value of a session info object
    // field identified by the given field name.
    //
    // Note that returning a shallow copy (such as shallow copies of maps or
    // slices for example) could lead to potentially dangerous race conditions
    // and undefined behavior
    Value(fieldName string) interface{}

    // Copy returns an exact deep copy of the entire session info object.
    //
    // Note that returning a shallow copy (such as shallow copies of
    // maps or slices for example) could lead to potentially dangerous
    // race conditions and undefined behavior
    Copy() SessionInfo
}

SessionInfo represents a session info object implementation interface. It defines a set of important methods that must be implemented carefully in order to avoid race conditions

func GenericSessionInfoParser Uses

func GenericSessionInfoParser(data map[string]interface{}) SessionInfo

GenericSessionInfoParser represents a default implementation of a session info object parser. It parses the info object into a generic session info type implementing the webwire.SessionInfo interface

type SessionInfoParser Uses

type SessionInfoParser func(map[string]interface{}) SessionInfo

SessionInfoParser represents the type of a session info parser function. The session info parser is invoked during the parsing of a newly assigned session on the client, as well as during the parsing of a saved serialized session. It returns a webwire.SessionInfo compliant object constructed from the data given

type SessionKeyGenerator Uses

type SessionKeyGenerator interface {
    // Generate is invoked when the webwire server creates a new session
    // and requires a new session key to be generated.
    // This hook must not be used except the user knows exactly what he/she does
    // as it would compromise security if implemented improperly
    Generate() string
}

SessionKeyGenerator defines the interface of a webwire server's session key generator. This interface must not be implemented (!) unless the default generator doesn't meet the exact needs of the library user, because the default generator already provides a secure implementation

func NewDefaultSessionKeyGenerator Uses

func NewDefaultSessionKeyGenerator() SessionKeyGenerator

NewDefaultSessionKeyGenerator constructs a new default session key generator implementation

type SessionLookupResult Uses

type SessionLookupResult interface {
    // Creation returns the retrieved creation time
    Creation() time.Time

    // LastLookup returns the retrieved last lookup time
    LastLookup() time.Time

    // Info returns the retrieved session information
    // in the form of a JSON compliant variant map
    Info() map[string]interface{}
}

SessionLookupResult represents the result of a session lookup

func NewSessionLookupResult Uses

func NewSessionLookupResult(
    creation time.Time,
    lastLookup time.Time,
    info map[string]interface{},
) SessionLookupResult

NewSessionLookupResult creates a new result of a session lookup operation

type SessionManager Uses

type SessionManager interface {
    // OnSessionCreated is invoked after the synchronization of the new session
    // to the remote client.
    // The actual created session can be retrieved from the provided connection.
    // If OnSessionCreated returns an error then this error is logged
    // but the session will not be destroyed and will remain active!
    // The only consequence of OnSessionCreation failing is that the server
    // won't be able to restore the session after the client is disconnected.
    //
    // This hook will be invoked by the goroutine calling the
    // client.CreateSession connection method
    OnSessionCreated(client Connection) error

    // OnSessionLookup is invoked when the server is looking for a specific
    // session given its key. If the session wasn't found then nil will be
    // returned without an error, otherwise if the session was found then a
    // SessionLookupResult instance created by wwr.NewSessionLookupResult will
    // be returned with the required parameters set.
    //
    // If an error (that's not a webwire.SessNotFoundErr) is returned then it'll
    // be logged and the session restoration will fail.
    //
    // This hook will be invoked by the goroutine serving the associated client
    // and will block any other interactions with this client while executing
    //
    // WARNING: if this hooks doesn't update the LastLookup field of the found
    // session object then the session garbage collection won't work properly
    OnSessionLookup(key string) (result SessionLookupResult, err error)

    // OnSessionClosed is invoked when the session associated with the given key
    // is closed (thus destroyed) either by the server or the client. A closed
    // session is permanently deleted and becomes undiscoverable in the
    // OnSessionLookup hook. A returned error is logged to the wwr error log.
    //
    // This hook is invoked by either a goroutine calling the method
    // connection.CloseSession(), or the goroutine serving the associated
    // client, in the case of which it will block any other interactions with
    // this client while executing
    OnSessionClosed(sessionKey string) error
}

SessionManager defines the interface of a webwire server's session manager

type Socket Uses

type Socket interface {
    // GetWriter returns a writer for the next message to send. The writer's
    // Close method flushes the written message to the network. In case of
    // concurrent use GetWriter will block until the previous writer is closed
    // and a new one is available
    GetWriter() (io.WriteCloser, error)

    // Read blocks the calling goroutine and awaits an incoming message. If
    // deadline is 0 then Read will never timeout. In case of concurrent use
    // Read will block until the previous call finished
    Read(into *message.Message, deadline time.Time) ErrSockRead

    // IsConnected returns true if the given socket maintains an open connection
    // or otherwise return false
    IsConnected() bool

    // RemoteAddr returns the address of the remote client or nil if the client
    // is not connected
    RemoteAddr() net.Addr

    // Close closes the socket
    Close() error
}

Socket defines the abstract socket implementation interface

type Transport Uses

type Transport interface {
    // Initialize initializes the server
    Initialize(
        options ServerOptions,
        isShuttingdown IsShuttingDown,
        onNewConnection OnNewConnection,
    ) error

    // Serve starts serving blocking the calling goroutine
    Serve() error

    // Shutdown shuts the server down
    Shutdown() error

    // Address returns the URL address the server is listening on
    Address() url.URL
}

Transport defines the interface of a webwire transport

Directories

PathSynopsis
client
connopt
message
payload
requestManager
test
transport/memchan
wwrerr

Package webwire imports 19 packages (graph) and is imported by 8 packages. Updated 2018-12-14. Refresh now. Tools for package owners.