NAV
  • GOSF
  • Getting Started
  • Handling Connections
  • Message Protocol
  • Request/Response Cycle
  • Broadcast Messages
  • Microservices
  • Plugin Development
  • Hooks
  • Roadmap
  • Author
  • License
  • GOSF

    Go Socket.IO Framework or GOSF is an easy-to-use framework for developing Socket.IO APIs in Google's Go programming language (GoLang).

    Features

    Getting Started

    Starting a GOSF server is very easy. Download the framework, include it in your project, and follow the examples in this getting started guide to get up and running quickly. You can also download the sample app to get a more in-depth example and great starting point for your own project.

    Download

    Installing the framework for use in your project is simple. Run this shell command to get started in your GoLang environment.

    go get -u "github.com/ambelovsky/gosf"

    import "github.com/ambelovsky/gosf"
    

    Once GOSF has been downloaded, import the package into your project and get to reading the rest of this documentation!

    Your First Server

    package main
    
    import "github.com/ambelovsky/gosf"
    
    func echo(client *gosf.Client, request *gosf.Request) *gosf.Message {
      return gosf.NewSuccessMessage(request.Message.Text)
    }
    
    func init() {
      // Listen on an endpoint
      gosf.Listen("echo", echo)
    }
    
    func main() {
      // Start the server using a basic configuration
      gosf.Startup(map[string]interface{}{"port": 9999})
    }
    

    The given sample will start a server that responds on an "echo" endpoint and returns the same message received from the client back to the client.

    Configuring a server using GOSF can be done in just a few lines of code. This is the simplest setup to get your server up and running.

    First, we configure a method called echo. This method takes a client argument using the standard Client type from GOSF and a request argument using the standard Request type from GOSF. It then builds a response and returns that response. The framework picks up the returned response and sends that message back to the client automatically.

    Routing Requests With Listen

    In the init() method, a listener is configured using the standard Listen method from GOSF. gosf.Listen("echo", echo) tells the framework to keep an eye out for anyone calling the server at the echo endpoint socket.emit('echo', ...). When a message comes in on the echo endpoint, run the echo method that we created. This completes a full request/response cycle.

    Starting the Server

    Using the Go main() method, the server is started using the standard Startup method from GOSF. This method takes a configuration. In this case, we've passed in a port that we want the server to listen on for new SocketIO requests.

    Your First Client

    <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.2.0/socket.io.slim.js"></script>
    <script>
      var socket = io.connect('ws://localhost:9999', { transports: ['websocket'] });
    
      socket.emit('echo', { text: 'Hello world.' }, function(response) {
        console.log(response);
      });
    </script>
    

    Coding a Socket.IO client is easy. In the given example, be sure you have the console in your browser open to see the response message. If you're not used to this JavaScript code, take a look at the resources provided at socket.io for more information.

    In this example, we are using the acknowledgement or ack model. In an ack model, the callback function is registered at the time of the emit as the third argument of the emit. When the server responds to this emit, its response will be returned directly to the callback function provided.

    Going Further

    An example server project to help you get started quickly can be found at github.com/ambelovsky/gosf-sample-app. It is highly recommended that you start with this project as the base for your own to get a sense for how to organize your project and work with GOSF.

    Handling Connections

    Starting the Server

    A server is started using the Startup(config map[string]interface{}) function of GOSF.

    Be sure that all plugins and listeners have been registered before calling the Startup function.

    Configuration Options

    propertytypedefaultdescription
    hoststring""host the server should bind to, listens to all requests by default
    portint9999the port the server should listen on for new socket.io endpoint requests
    pathstring"/"path this socket.io server should be accessible on, root path by default
    secureboolfalsewhether or not to use WSS (web-socket secure)
    enableCORSstringniluseless at the moment
    rejectInvalidHostnamesboolfalsewhether or not to reject invalid hostnames when using a secure connection
    ssl-keystring""file system path to the SSL key, required if secure is true
    ssl-certstring""file system path to the SSL certificate, required if secure is true

    Client Connect

    After a client establishes a connection, their connection remains open. If a client established a secure connection, the connection continues to be secure for the life of the connection. In the event of an unexpected disconnect, reconnects are handled automatically by the client.

    Currently, the only way to know when a connection has been established is through the use of Hooks.

    Connects have one event hook: OnConnect. For more information on event hooks, see Hooks.

    Client Disconnect

    When a client disconnects, their client session is destroyed. It is recommended that tokens and other persistent session information be held in a separate datastore.

    Currently, the only way to know when a disconnect occurs is through the use of Hooks.

    Disconnects have one event hook: OnDisconnect. For more information on event hooks, see Hooks.

    Message Protocol

    To keep communication clean, GOSF uses a strict message protocol.

    Message Type

    type Message

    The standard message type that GOSF passes to and expects from clients and microservices.

    propertyjson outputtypedescription
    IDidinta message identifier sent by the client and returned by the server
    GUIDguidstringa message identifier sent by the client and returned by the server
    UUIDuuidstringa message identifier sent by the client and returned by the server
    Successsuccessboolwhether the client's request was successfully processed
    Texttextstringshort message describing success/fail status
    Metameta{}map[string]interface{}relevant information related to the data set returned to the client
    Bodybody{}map[string]interface{}complete data set returned to the client

    Message API

    There are many functions available to help when working with a standard GOSF Message.

    NewSuccessMessage

    NewSuccessMessage([text string], [body map[string]interface{}]) *Message

    Generates a new Message based on the given text status string and body data map. It sets the Success status indicator to true. This convenience function helps to refactor basic responses.

    In this function, the text and body parameters are both optional.

    NewFailureMessage

    NewFailureMessage([text string], [body map[string]interface{}]) *Message

    Generates a new Message based on the given text status string and body data map. It sets the Success status indicator to false. This convenience function helps to refactor basic responses.

    In this function, the text and body parameters are both optional.

    StructToMap

    StructToMap(input interface{}) map[string]interface{}

    Returns a map based on the given struct. Helpful for converting instances of Message into maps.

    MapToStruct

    MapToStruct(input map[string]interface{}, output interface{}) error

    Accepts an input map and an output interface{}. When passing an instance of Message as the output parameter, this function helps convert maps into a standard Message.

    Message.WithoutMeta

    (m *Message) WithoutMeta() *Message

    Returns a copy of the current Message without the Meta map. This is helpful when you want to move meta data among microservices but do not want it exposed to a connected client.

    Request/Response Cycle

    The request/response cycle describes how data flows from the client to the server and back to the client through GOSF. Socket.IO's connections are stateful, which means that data can be pushed directly to the client without being instigated by a request. However, GOSF models a basic request/response cycle to make instigated flows easy.

    For details on pushing data to clients outside of the request/response cycle, see Broadcast Messages.

    Endpoint Request

    gosf.Listen("sample", sampleController)
    

    Server listening for requests on "sample" endpoint

    socket.emit("sample", {id: 1, text: "Here's some user data...", body: {age: 32}})
    

    Client sending a request to "sample" endpoint

    When a client sends a message into your Socket.IO server, they will emit that message to an endpoint. In the example Listen("myEndpoint", myController), "myEndpoint" is the name of the endpoint that the client is submitting a request to.

    Requests are expected to pass standard JSON objects with the following optional properties.

    Properties

    propertytypedescription
    idintan ID to be returned for request/response matching
    textstringshort message describing request
    bodyObject{}complete data set received by the server

    Requests have two event hooks: OnBeforeRequest and OnAfterRequest. For more information on event hooks, see Hooks.

    Controller Function

    func myFirstController(client *gosf.Client, request *gosf.Request) *gosf.Message {
      var statement string
    
      // Parsing arguments in the body element
      if val, ok := request.Message.Body["statement"]; ok {
        statement = val.(string)
      }
    
      // Default value
      if statement == "" {
        statement = "that I love you."
      }
      
      // Construct response
      response := new(gosf.Message)
      response.Success = true
      response.Text = "Hello World, I'd just like to say " + statement
    
      // Send response back to the client
      return response
    }
    

    Standard controller function template

    Controllers are generally the component of an application that connect the view or consumer input/output with the data model. It is where the logic of the application is stored. GOSF builds the controller concept directly into its request/response cycle to take an MVC (model-view-controller) style of approach. In this paradigm, Message.Body acts as your view, and the data models interacting with other data sources are left up to you.

    GOSF provides a standard template for controller functions. Once a controller function is built, it can be provided to Listen along with an endpoint name as described in Endpoint Request. For example, Listen("myEndpoint", myController).

    Notice that the controller in the code sample creates a Message response and simply returns it. This is the standard way to return a response to the client. If you prefer not to return a response to the client, then return nil.

    The standard Message object accepts the following optional properties.

    Properties

    propertytypedescription
    Successboolwhether the client's request was successfully processed
    Textstringshort message describing success/fail status
    Metamap[string]interface{}relevant information related to the data set returned to the client
    Bodymap[string]interface{}complete data set returned to the client

    Endpoint Response

    func echo(client *gosf.Client, request *gosf.Request) *gosf.Message {
      response := new(gosf.Message)
      response.Success = true
      response.Text = request.Message.Text
      return response
    }
    

    Controller function returning a Message

    socket.on('echo', function(response) {
      console.log(response);
    });
    socket.emit('echo', { text: 'Hello world.' });
    

    Client emitting with a registered event handler

    An endpoint response is automatically sent to the requesting client's registered event handler anytime a controller returns a Message.

    If the request is sent with an id property (integer only), then the same message ID will be returned to the client with the response. This helps the client match requests with responses in cases where an acknowledgement response is not used.

    If a controller returns nil, then no endpoint response will be sent. If the client does not register an event handler to catch a response, no response will be received.

    If a Message is returned to the client, then the client receives a response object with the following optional properties.

    Properties

    propertytypedescription
    idintan ID to be returned for request/response matching
    successboolwhether the client's request was successfully processed
    textstringshort message describing results
    metaObject{}meta data returned to the client
    bodyObject{}complete data set returned to the client

    Responses have two event hooks: OnBeforeResponse and OnAfterResponse. For more information on event hooks, see Hooks.

    Acknowledgement Response

    socket.emit('echo', { text: 'Hello world.' }, function(response) {
      console.log(response);
    });
    

    Client emitting with an ack callback function

    In GOSF, acknowledgement or ack responses are sent back to the requesting client automatically when a controller returns a Message. Ack responses are only received by the callback on the client side that was registered at the time of the emit. This makes it easy to match requests with responses on the client side without the need to mess with message ID matching.

    On the client side, if you have an ack callback registered with the emit and an event handler registered with the same endpoint name, both the ack and the event handler will receive a copy of the response.

    If a controller returns nil, then no ack response will be sent. If the client does not register an event handler with the emit, then no ack response will be received.

    Error Messages

    Errors are returned as a part of the standard message response for clients. Simply set Message.Success to false to tell clients that an error has occurred. Use NewFailureMessage to quickly generate an error message with Message.Success preset.

    It's a recommended best practice to develop your own error codes, which should be delivered to the client in Message.Text while Message.Body can be used to contain error details. Short error codes in Message.Text allow the client to build an easily programmable reaction on the client side.

    Use Message.Meta to transmit potentially sensitive error information among Microservices. Message.Meta can be hidden from client endpoint view by calling Message.WithoutMeta.

    Broadcast Messages

    Broadcasting messages is commonly necessary for statefully connected Socket.IO applications, but it's not a standard part of the request/response cycle. Broadcast messages are messages that get sent to more than one connected client. You can broadcast to all connected clients or to a room of joined connected clients from anywhere in your application.

    This ability to share messages makes it easy to communicate mass updates made by a single user or push information to users that may have been generated by an automated system. In GOSF, you can send broadcast messages globally by working with the Broadcast function, or you can send broadcasts from the controller level by working with the Client.Broadcast function.

    GOSF considers it a best-practice to join every user to a room uniquely identified by the notation user- followed by a user's ID or username. For example, user-193. When communicating with a user outside the standard request/response cycle, broadcasting to the user's room makes certain that the Message is received by all of that user's connected devices.

    Global Broadcasts

    func example() {
      message := new(gosf.Message)
      message.Success = true
      message.Text = "Hello World"
    
      gosf.Broadcast("", "example", message)
    }
    

    Broadcasting a message to all clients listening on the "example" endpoint

    func example() {
      message := new(gosf.Message)
      message.Success = true
      message.Text = "Hello World"
    
      gosf.Broadcast("chat", "example", message)
    }
    

    Broadcasting a message to all clients in a specific room listening on the "example" endpoint

    Broadcast(room string, endpoint string, message *Message)

    Broadcast messages to multiple connected clients.

    Attributes

    attributetypedescription
    roomstringgrouping of connected clients to send this broadcast to
    endpointstringendpoint that connected clients are listening on
    message*Messagemessage to broadcast to connected clients

    To send a message to all connected clients, simply pass an empty string instead of a specific room name.

    Global broadcasts have two event hooks: OnBeforeGlobalBroadcast and OnAfterGlobalBroadcast. For more information on event hooks, see Hooks.

    Client Broadcasts

    func echo(client *gosf.Client, request *gosf.Request) *gosf.Message {
      response := new(gosf.Message)
      response.Success = true
      response.Text = request.Message.Text
    
      client.Broadcast("friends", request.Endpoint, response)
      return nil
    }
    

    Broadcasting the response to all clients in the room "friends" except the requesting client

    (c *Client) Broadcast(room string, endpoint string, message *Message)

    Broadcast messages to multiple connected clients excluding the requesting client.

    Attributes

    attributetypedescription
    roomstringgrouping of connected clients to send this broadcast to
    endpointstringendpoint that connected clients are listening on
    message*Messagemessage to broadcast to connected clients

    To send a message to all connected clients, simply pass an empty string instead of a specific room name.

    Client broadcasts have two event hooks: OnBeforeBroadcast and OnAfterBroadcast. For more information on event hooks, see Hooks.

    Joining a Room

    (c *Client) Join(room string)

    In order for a broadcast message sent to a room to be received by a client, the client must be joined to the room receiving the broadcast message. This function will join the current client to a broadcast room.

    Attributes

    attributetypedescription
    roomstringname of the room to join this client connection to

    Leaving a Room

    (c *Client) Leave(room string)

    If you'd no longer like a client to receive broadcasts meant for a specific room, you can remove them from that room with this function.

    Attributes

    attributetypedescription
    roomstringname of the room to remove this client connection from

    Leaving All Rooms

    (c *Client) LeavAll()

    LeaveAll() removes a client from all rooms they were previously joined to. This function can be safely called on a client with no rooms assignments.

    Microservices

    Microservice architecture is important in environments where smaller packagable features are a concern. Generally, microservices are called using RPC, but GOSF connects to microservices using Socket.IO.

    When creating a Socket.IO microservice in another framework or language, remember that GOSF generates standard messages using the GOSF Message Protocol.

    Because microservices are expected to communicate with the same GOSF Message Protocol, there's no need to worry about any of the traditional microservice protocol buffers. If you prefer strict typing, you can convert Body and Meta maps in the incoming Message using MapToStruct and in the outgoing message using StructToMap.

    Creating a Microservice

    package main
    
    import "github.com/ambelovsky/gosf"
    
    func echo(client *gosf.Client, request *gosf.Request) *gosf.Message {
      return gosf.NewSuccessMessage(request.Message.Text)
    }
    
    func init() {
      // Listen on an endpoint
      gosf.Listen("echo", echo)
    }
    
    func main() {
      // Start the server using a basic configuration
      gosf.Startup(map[string]interface{}{"port": 5001})
    }
    

    A microservice is built as a standard GOSF API using a standard request/response cycle. You can also develop a microservice in other languages as long as the JSON object that gets returned can map to a GOSF Message type.

    In this example, we create a microservice that echos the given value back to the caller.

    Consuming a Microservice

    import (
      "log"
      "github.com/ambelovsky/gosf"
    )
    
    func init() {
      gosf.RegisterMicroservice("utils", "127.0.0.1", 5001, false)
    }
    
    func main() {
      request := gosf.NewSuccessMessage("Echo this...")
    
      msUtils := gosf.GetMicroservice("utils")
      if msUtils == nil {
        panic("unable to get a reference to utils microservice")
      }
    
      if response, err := msUtils.Call("echo", request); err != nil {
        log.Println(err.Error())
      } else {
        log.Println(response.Text)
      }
    }
    

    Register a microservice by calling the RegisterMicroservice function on init. Once registered, the microservice will be available in the App.Microservices registry by the name given when the microservice was registered.

    You can make a call to any of the microservice's endpoints after registration using App.Microservices["..."].Call(endpoint string, request *Message).

    It is also possible to get a reference to a registered microservice using GetMicroservice(name string) *Microservice.

    Microservice API

    Many built-in functions are available for working with microservices.

    RegisterMicroservice

    RegisterMicroservice(name string, host string, port int, secure bool) error

    Connects to a running microservice and adds the microservice reference to the GOSF App.Microservices registry.

    DeregisterMicroservice

    DeregisterMicroservice(name string)

    Disconnects and removes the microservice from the GOSF App.Microservices registry.

    GetMicroservice

    GetMicroservice(name string) *Microservice

    Returns a reference to a registered microservice or nil if the microservice could not be found.

    Microservice.Lob

    (m *Microservice) Lob(endpoint string, message *Message) error

    Sends a message to the microservice endpoint without requiring a response.

    Microservice.Call

    (m *Microservice) Call(endpoint string, message *Message) (*Message, error)

    Sends a message to a microservice endpiont expecting a response to be returned. Microservice responses timeout if not answered in 2 seconds or less.

    Microservice.Listen

    (m *Microservice) Listen(endpoint string, callback func(message *Message))

    Listens for broadcast or non-request/response cycle messages to be delivered from the server.

    Microservice.Connect

    (m *Microservice) Connect() (*Microservice, error)

    Connects a previously disconnected microservice.

    Microservice.Connected

    (m *Microservice) Connected() bool

    Checks current connection status with the microservice. If the connection to a microservice is interrupted, GOSF will automatically reconnect when the microservice comes back online.

    Microservice.Disconnect

    (m *Microservice) Disconnect()

    Deliberately disconnects from a microservice without attempting to reconnect.

    Plugin Development

    An example plugin project to help you get started quickly with plugins can be found at github.com/ambelovsky/gosf-sample-plugin. This sample project uses all of the hooks to create a basic console logging system and exposes an Echo app method to applications that register the plugin.

    Hooks

    It is possible to take advantage of hooks from anywhere in your application. However, it is a best-practice that hooks only be registered from within plugins. This keeps functionality modularly contained. For more information on plugin development, see Plugin Development.

    OnConnect

    gosf.OnConnect(func(client *gosf.Client, request *gosf.Request) {
      log.Println("Client connected.")
    })
    

    Registering an event handler with the OnConnect hook

    OnConnect(callback func(client *Client, request *Request))

    Every time a client connects to the server, this hook gets called. Pass a callback function you would like called everytime this hook fires.

    Callback Attributes

    The following attributes can be consumed by your callback function.

    attributetype
    client*Client
    request*Request

    OnDisconnect

    gosf.OnDisconnect(func(client *gosf.Client, request *gosf.Request) {
      log.Println("Client disconnected.")
    })
    

    Registering an event handler with the OnDisconnect hook

    OnDisconnect(callback func(client *Client, request *Request))

    Every time a client disconnects from the server, this hook gets called. Pass a callback function you would like called everytime this hook fires.

    Callback Attributes

    The following attributes can be consumed by your callback function.

    attributetype
    client*Client
    request*Request

    OnBeforeRequest

    gosf.OnBeforeRequest(func(client *gosf.Client, request *gosf.Request) {
      log.Println("Request received for " + request.Endpoint + " endpoint.")
    })
    

    Registering an event handler with the OnBeforeRequest hook

    OnBeforeRequest(callback func(client *Client, request *Request))

    Before the server sends a request to a controller, this hook gets called. Pass a callback function you would like called everytime this hook fires.

    Callback Attributes

    The following attributes can be consumed by your callback function.

    attributetype
    client*Client
    request*Request

    OnAfterRequest

    gosf.OnAfterRequest(func(client *gosf.Client, request *gosf.Request, response *gosf.Message) {
      log.Println("Request for " + request.Endpoint + " endpoint was processed by the controller.")
    })
    

    Registering an event handler with the OnAfterRequest hook

    OnAfterRequest(callback func(client *Client, request *Request, response *Message))

    After the controller has finished processing a request, this hook gets called. Pass a callback function you would like called everytime this hook fires.

    Callback Attributes

    The following attributes can be consumed by your callback function.

    attributetype
    client*Client
    request*Request
    response*Message

    OnBeforeResponse

    gosf.OnBeforeResponse(func(client *gosf.Client, request *gosf.Request, response *gosf.Message) {
      log.Println("Response for " + request.Endpoint + " endpoint is being prepared.")
    })
    

    Registering an event handler with the OnBeforeResponse hook

    OnBeforeResponse(callback func(client *Client, request *Request, response *Message))

    After the controller has finished processing a request and before a response is sent back to the client, this hook gets called. Pass a callback function you would like called everytime this hook fires.

    Callback Attributes

    The following attributes can be consumed by your callback function.

    attributetype
    client*Client
    request*Request
    response*Message

    OnAfterResponse

    gosf.OnAfterResponse(func(client *gosf.Client, request *gosf.Request, response *gosf.Message) {
      log.Println("Response for " + request.Endpoint + " endpoint was sent.")
    })
    

    Registering an event handler with the OnAfterResponse hook

    OnAfterResponse(callback func(client *Client, request *Request, response *Message))

    After a response is sent back to the client, this hook gets called. Pass a callback function you would like called everytime this hook fires.

    Callback Attributes

    The following attributes can be consumed by your callback function.

    attributetype
    client*Client
    request*Request
    response*Message

    OnBeforeBroadcast

    gosf.OnBeforeBroadcast(func(endpoint string, room string, response *gosf.Message) {
      log.Println("Broadcast for " + endpoint + " endpoint is preparing to send to " + getRoom(room) + ".")
    })
    

    Registering an event handler with the OnBeforeBroadcast hook

    OnBeforeBroadcast(callback func(endpoint string, room string, response *Message))

    Before a global broadcast message is sent, this hook gets called. Pass a callback function you would like called everytime this hook fires.

    Callback Attributes

    The following attributes can be consumed by your callback function.

    attributetype
    endpointstring
    roomstring
    response*Message

    OnAfterBroadcast

    gosf.OnAfterBroadcast(func(endpoint string, room string, response *gosf.Message) {
      log.Println("Broadcast for " + endpoint + " endpoint was sent to " + getRoom(room) + ".")
    })
    

    Registering an event handler with the OnAfterBroadcast hook

    OnAfterBroadcast(callback func(endpoint string, room string, response *Message))

    After a global broadcast message has been sent, this hook gets called. Pass a callback function you would like called everytime this hook fires.

    Callback Attributes

    The following attributes can be consumed by your callback function.

    attributetype
    endpointstring
    roomstring
    response*Message

    OnBeforeClientBroadcast

    gosf.OnBeforeClientBroadcast(func(client *gosf.Client, endpoint string, room string, response *gosf.Message) {
      log.Println("Broadcast for " + endpoint + " endpoint is preparing to send to " + getRoom(room) + ".")
    })
    

    Registering an event handler with the OnBeforeClientBroadcast hook

    OnBeforeClientBroadcast(callback func(client *Client, endpoint string, room string, response *Message))

    Before a client broadcast message is sent, this hook gets called. Pass a callback function you would like called everytime this hook fires.

    Callback Attributes

    The following attributes can be consumed by your callback function.

    attributetype
    client*Client
    endpointstring
    roomstring
    response*Message

    OnAfterClientBroadcast

    gosf.OnAfterClientBroadcast(func(client *gosf.Client, endpoint string, room string, response *gosf.Message) {
      log.Println("Broadcast for " + endpoint + " endpoint was sent to " + getRoom(room) + ".")
    })
    

    Registering an event handler with the OnAfterClientBroadcast hook

    OnAfterClientBroadcast(callback func(client *Client, endpoint string, room string, response *Message))

    After a client broadcast message has been sent, this hook gets called. Pass a callback function you would like called everytime this hook fires.

    Callback Attributes

    The following attributes can be consumed by your callback function.

    attributetype
    client*Client
    endpointstring
    roomstring
    response*Message

    Roadmap

    Recently Added

    Author

    Aaron Belovsky is a senior technologist, avid open source contributor, and author of GOSF.

    Help support ongoing development effort with a donation to the following BTC wallet: 13pkXi8bW5bKjWewQYtv36CUgwhgQEn7eA

    License

    MIT