Skip to content

WebSockets

Alex Vigdor edited this page Mar 26, 2018 · 1 revision

Web Sockets in groovity

Groovity offers both client and server functionality for using WebSockets to exchange multiple messages over a single long-lived upgraded HTTP connection. WebSockets differ from a traditional http request/response in that once opened, either client or server may send 0 or more messages over the socket at any time until the socket is closed. Thus the programming model for sockets is one of reacting to incoming messages.

The lifecycle of a WebSocket connection is mapped to a single instance of the groovity script that declares the socket; per normal Groovity the load() method if present will be called at instantiation, and then an open() method will be called to establish the websocket session. A close() method if present is called when the socket is closed; the script body is called once for every message received on the socket while open, bound to "message". By default messages will be Strings or byte[], but a message attribute of the web map can be used to declare a different format such as InputStream or Reader for large transfers, or Map.class, List.class or some bean class to trigger json message parsing. Because a single script instance lives for the lifespan of the socket, it is easy to use either the groovy binding or @Field to manage state between open, message handling and closing. We'll create a simple chat application to demonstrate.

On the server side, we create a socket endpoint named "chat" using a static web declaration. We will require that the request carry a "user" parameter to identify the person connecting, and an optional "room" parameter to enable different chat rooms. We will use a named asynchronous channel to represent the chat room; our subscriber will publish messages from the channel over the websocket to the client, and the endpoint takes incoming messages from the websocket and offers them to the named channel.

// web socket declaration using default String message format at endpoint /chat
static web = [
	socket : 'chat'
]

// require a 'user' parameter in the websocket request, default chat 'room' parameter to 'lobby'
static args = [
	user : String.class,
	room : 'lobby'
]

// define chat room async channel name at load time
def load(){
	channelName = "chat/${room}"
}

// wrap a string into a message data structure and offer to the room channel
def sendChatMessage(msg){
	offer(channel : channelName){ [
		user : user,
		message : msg.toString(),
		date : new Date().toString()    
	] }
}

// open() is automatically called when the websocket session has been established
// this is our hook to begin to listen to the room channel for messages
def open(){
	subscription = accept(channel : channelName){
		//we will send all messages from the chat room channel to the socket,
		//except we will not echo back a user's own messages
		if(it.user != user && socket.open){
			socket(it)
		}
	}
	sendChatMessage "${user} has joined ${room}"
}

// close() is called automatically after the websocket session has ended for any reason
def close(){
	// we will close out the subscription to stop trying to send channel messages to the socket
	subscription.close()
	sendChatMessage "${user} has left ${room}"
}

// the body of the script is called once for every incoming socket message, here we forward to the channel
sendChatMessage message

You can connect to this websocket using any websocket client; here is a command-line groovity websocket chat client that uses the "ws" tag to create the websocket client connection, and enters a command-line loop, sending each line of input as a message to the chat room.

//To start a chat session we need a user name, chat room name and socket address
static args=[
	user : String.class,
	room : 'lobby',
	address : 'ws://localhost:9880/ws/chat'
]

//establish the client socket and indicate we expect to parse incoming messages as Maps
def clientSock = ws( url : address, message : Map.class ){
	//pass user and room info to websocket as parameters
	param(name : 'user', value : user)
	param(name : 'room', value : room)
	handler{
		//simple handler just prints out the chat message
		System.out.println("${it.user}: ${it.message} (${it.date})")
	}
}

//enter a command line loop; every line entered is sent to the socket, 'q' exits the loop
try{
	System.out.println("Enter a message or 'q' to quit")
	while((message = System.console().readLine()) != 'q' && clientSock.open){
		clientSock(message)
	}
}
finally{
	clientSock.close()
}
Clone this wiki locally