Login Register






Tutorial Building real-time web apps with Go filter_list
Author
Message
Building real-time web apps with Go #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).

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/melody

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:
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 build

A 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.

Reply

RE: Building real-time web apps with Go #2
Thanks for the share mate, this is of good use to me. Might have to read-up on the other GO tutorials first though, since I didn't program in it yet. Just checked it out. Nicely formatted as well. Go for more GO!
~~ Might be back? ~~

Reply

RE: Building real-time web apps with Go #3
Nicely done.

I love writing "go get x".


(11-02-2018, 02:51 AM)Skullmeat Wrote: Ok, there no real practical reason for doing this, but that's never stopped me.

Reply

RE: Building real-time web apps with Go #4
Very Nice ... Tutorials, Am gonna try it out, Thanks ...
[Image: images?q=tbn:ANd9GcT50FI8HF1eN-BmbBePtw2...OYRiEjj8iA]

Reply