Building real-time web apps with Go 02-14-2017, 03:43 AM
#1
I did a tutorial not too long ago on my old standby for server engines: Express, a pretty minimal and very scalable and extensible Node JS web framework great for creating web apps of all kinds. But recently, I've come across a situation wherein I need another web app server, but I'm using Go as my language of choice instead of Node, so Express is out for my engine. That's where Gin and Melody come in!
When used with the standard library net/http package, it's super simple to create robust and insanely fast real-time apps using Gin as a routing framework and Melody as a websocket middleware. For this intro, I'll go through how to make really simple chat program (which includes a little client-side javascript to communicate with Melody, but it's nothing too much).
Before we get started with these packages, we need to install them from github. Use your Go toolchain of choice to do this, or enter the following command if you use the default one (if you're not sure what I'm talking about, use this):
Now that we've done that, we need to write the base program and import the newly installed packages along with the http package, which should be easy enough if you've ever written something in go:
Now that we have a basic skeleton for our program, let's create some interfaces to Gin and Melody, then define our route for the server root ("/"), which will be the page that holds our chat pane, and serve a static file when users request said page.
We don't have a static/index.html file yet, but all the front end will come once the server is finished.
This sounds like it might be difficult and time-consuming, but thanks to Melody, it's as easy as one more route and an event handler server-side. First, let's define another route after the first one, this time for "/ws", for WebSocket, and pass the context reader and writer to Melody so it can handle the request.
Now for the event handler. Like the routes we've already defined, we pass a callback function, but this time it's a Melody session (which we need to include, but we don't use it here) and some data instead of a Gin context struct. In the function body, we'll broadcast the message to all connected websockets.
Before we move to front end, we need to define routes for static files (like scripts and CSS) and actually run the server.
First, the static routes. Afaik, you can put the following line in any spot in your main function, but I like to put it right after creating the Gin and Melody interfaces (structs, whatever) to keep things tidy. This will serve files from a local directory, static/public, whenever a file is requested from the /public/* route (our chat page will request a CSS and JavaScript file from this route later).
Finally for the back end, we need to listen for incoming requests. This is done in a single, final line of the main function (replace 3000 with whatever port you want to connect on):
Now we need to actually make an HTML page for the user to interact with. I went with a pretty minimal design, but it gets the job done.
Note: the HTML file(s) go in the static/ directory, and JS/CSS goes in static/public, as per our routes.
You'll notice we request a file, /public/socket.js, at the end of our body. This is what will hold the client-side JS that interacts with the server we just finished. Go ahead and create a static/public/socket.js file, then add the following code:
This anonymous function will contain the WebSocket object that talks to the server, as well as the listeners for manipulating messages and other text. It's wrapped to avoid polluting the global namespace, as well as to prevent clients from interacting with the server in ways they shouldn't. The function also calls itself as soon as it's created, so any delay that might be there is trivial.
After creating the anonymous function, we need to define some variables at the beginning of it: one for the WebSocket object, and three for the chatpane, name input, and message input.
At this point, we have a back end server to communicate with and DOM element objects to interact with, so all we need is event listeners. The first one we'll define is for the WebSocket's onmessage event, which will append the message it receives to the "chatpane" <pre> tag.
Note: all further javascript is still contained in our anonymous function.
Our second listener is for keypresses in the message box. Provided the message isn't empty, this listener will send the entered username and whatever data is in the box to our back end Melody handlers when the enter key is pressed, then reset the message box.
Hooray, we're all done with the code! All that's left to do now is compile your program, fire up a browser, and point it to localhost:3000 (127.0.0.1 works in place of localhost; if you changed the port in your code, mirror that change here, too).
A complete version of the code is available here on github.
When used with the standard library net/http package, it's super simple to create robust and insanely fast real-time apps using Gin as a routing framework and Melody as a websocket middleware. For this intro, I'll go through how to make really simple chat program (which includes a little client-side javascript to communicate with Melody, but it's nothing too much).
Prep
Before we get started with these packages, we need to install them from github. Use your Go toolchain of choice to do this, or enter the following command if you use the default one (if you're not sure what I'm talking about, use this):
Code:
go get github.com/gin-gonic/gin github.com/olahol/melodyNow that we've done that, we need to write the base program and import the newly installed packages along with the http package, which should be easy enough if you've ever written something in go:
Code:
// package declaration
package main
// package imports
import (
"github.com/gin-gonic/gin"
"github.com/olahol/melody"
"net/http"
)
// main function
func main(){
}Defining your route(s)
Now that we have a basic skeleton for our program, let's create some interfaces to Gin and Melody, then define our route for the server root ("/"), which will be the page that holds our chat pane, and serve a static file when users request said page.
Code:
func main(){
router:=gin.Default() // default Gin router config
mel:=melody.New() // new Melody middleware instance
// handle get requests to route with a callback function
router.GET("/",func(c *gin.Context){
// serve the "static/index.html" file to requests on this route
http.ServeFile(c.Writer,c.Request,"static/index.html")
})
}We don't have a static/index.html file yet, but all the front end will come once the server is finished.
Handling websocket communication
This sounds like it might be difficult and time-consuming, but thanks to Melody, it's as easy as one more route and an event handler server-side. First, let's define another route after the first one, this time for "/ws", for WebSocket, and pass the context reader and writer to Melody so it can handle the request.
Code:
func main(){
router:=gin.Default()
mel:=melody.New()
router.GET("/",func(c *gin.Context){
http.ServeFile(c.Writer,c.Request,"static/index.html")
})
// new websocket route
router.GET("/ws",func(c *gin.Context){
// let Melody handle it
mel.HandleRequest(c.Writer,c.Request)
})
}Now for the event handler. Like the routes we've already defined, we pass a callback function, but this time it's a Melody session (which we need to include, but we don't use it here) and some data instead of a Gin context struct. In the function body, we'll broadcast the message to all connected websockets.
Code:
func main(){
...
// Melody message handler
mel.HandleMessage(func(s *melody.Session,msg []byte){
// broadcast message to websockets
mel.Broadcast(msg)
})
}Static files and a running server
Before we move to front end, we need to define routes for static files (like scripts and CSS) and actually run the server.
First, the static routes. Afaik, you can put the following line in any spot in your main function, but I like to put it right after creating the Gin and Melody interfaces (structs, whatever) to keep things tidy. This will serve files from a local directory, static/public, whenever a file is requested from the /public/* route (our chat page will request a CSS and JavaScript file from this route later).
Code:
func main(){
router:=gin.Default()
mel:=melody.New()
// static route
router.Static("/public","static/public")
...
}Finally for the back end, we need to listen for incoming requests. This is done in a single, final line of the main function (replace 3000 with whatever port you want to connect on):
Code:
func main(){
...
router.Run(":3000")
}Designing and styling the chat page
Now we need to actually make an HTML page for the user to interact with. I went with a pretty minimal design, but it gets the job done.
Note: the HTML file(s) go in the static/ directory, and JS/CSS goes in static/public, as per our routes.
Spoiler: static/index.html
Code:
<!DOCTYPE html>
<html>
<head>
<title>Gin + Melody chat test</title>
<link rel="stylesheet" href="/public/style.css">
</head>
<body>
<center>
<h3>Gin + Melody chat test</h3>
<pre id="chatpane"></pre>
<input placeholder="your name" id="name" style="width: 100px">
<input placeholder="type a message" id="msg" style="width: 250px">
</center>
<script src="/public/socket.js"></script>
</body>
</html>Spoiler: static/public/style.css
Code:
/* global font style */
body,input{
font: 1em consolas,monospace
}
/* styling for chat pane box */
#chatpane{
text-align: left;
background: #f1f1f1;
width: 500px;
min-height: 300px;
padding: 20px
}Client-side JS, part 1
You'll notice we request a file, /public/socket.js, at the end of our body. This is what will hold the client-side JS that interacts with the server we just finished. Go ahead and create a static/public/socket.js file, then add the following code:
Code:
(function(){
})();This anonymous function will contain the WebSocket object that talks to the server, as well as the listeners for manipulating messages and other text. It's wrapped to avoid polluting the global namespace, as well as to prevent clients from interacting with the server in ways they shouldn't. The function also calls itself as soon as it's created, so any delay that might be there is trivial.
After creating the anonymous function, we need to define some variables at the beginning of it: one for the WebSocket object, and three for the chatpane, name input, and message input.
Code:
(function(){
var ws=new WebSocket("ws://"+window.location.host+"/ws"),
chat=document.getElementById('chatpane'),
alias=document.getElementById('name'),
text=document.getElementById('msg');
})();Client-side JS, part 2
At this point, we have a back end server to communicate with and DOM element objects to interact with, so all we need is event listeners. The first one we'll define is for the WebSocket's onmessage event, which will append the message it receives to the "chatpane" <pre> tag.
Note: all further javascript is still contained in our anonymous function.
Code:
ws.onmessage=function(msg){
chat.innerText+=new Date().toLocaleString()+" "+msg.data+"\n";
};Our second listener is for keypresses in the message box. Provided the message isn't empty, this listener will send the entered username and whatever data is in the box to our back end Melody handlers when the enter key is pressed, then reset the message box.
Code:
text.onkeydown=function(e){
if(e.keyCode===13&&text.value!=""){
ws.send("<"+(alias.value||'guest')+">: "+text.value);
text.value="";
}
}Building and running
Hooray, we're all done with the code! All that's left to do now is compile your program, fire up a browser, and point it to localhost:3000 (127.0.0.1 works in place of localhost; if you changed the port in your code, mirror that change here, too).
Code:
go build -o goweb.exe main.go # compile with default toolchain
goweb # run the buildA complete version of the code is available here on github.
(This post was last modified: 02-14-2017, 04:03 AM by Inori.)
It's often the outcasts, the iconoclasts ... those who have the least to lose because they
don't have much in the first place, who feel the new currents and ride them the farthest.
don't have much in the first place, who feel the new currents and ride them the farthest.

















![[+]](https://sinister.li/images/modern/collapse_collapsed.png)


