Un websocket es una conexión en tiempo real bidireccional entre un cliente y un servidor. Es muy utilizado para chat pero no es el único caso de uso. Otro serían los GPS.
“WebSocket es una tecnología que proporciona un canal de comunicación bidireccional y full-duplex sobre un único socket TCP. Está diseñada para ser implementada en navegadores y servidores web, pero puede utilizarse por cualquier aplicación cliente/servidor.” Wikipedia
Para implementar websocket con JavasScript e inicar una conexión desde el navegador
var ws = new Websocket("ws://example.com/ws");
Esto envía una solicitud http, pero si usas una conexión segura debes usar "wss://"
Los websockets no vienen incluidos en la biblioteca estándar de Go. Pero hay paquetes de terceros muy fáciles de conseguir y que hacen un excelente trabajo. El que usaremos es “gorilla/websocket”. Para instalarlo solo agregar en la terminal:
goget github.com/gorilla/websocket
<dl>
<dt>Go</dt>
<dd>Es un lenguaje de programación de código abierto orientado a eventos inspirado en C y creado por Google. golang.org</dd>
<dt>Gorilla</dt>
<dd>Es un conjunto de herramientas para el desarrollo web con Go. Se puede combinar con cualquier framework. http://www.gorillatoolkit.org/</dd>
</dl>
<dl>
<dt>Docker</dt>
<dd>Es un programa de código abierto hecho en Go que crea un entorno virtualizado llamado contenedor. Facilita en gran medida el despliegue de las aplicaciones desde el desarrollo a producción resolviendo el problema de las dependencias y también muy usado en microservicios. Platzi tiene un excelente curso de docker que recomiendo mucho 😄</dd>
<dt>Now.sh</dt>
<dd>Now.sh es una plataforma como servicio (PaaS) que permite el despliegue de aplicaciones en NodeJS y aplicaciones Docker. El implementar Docker significa que puedes hacer deploy de cualquier lenguaje. Una de las ventajas de Now.sh es que en su versión gratuita puedes hacer tanto deploy quieras, pero debes estar consciente que todo lo que subas con ese plan será de código abierto. Platzi igualmente tiene un excelente curso de esto que también recomiendo mucho.</dd>
</dl>
Es buena práctica hacer todos nuestros programas de go en ***~/go/src/***, asumiendo que estás allí ingresa el la terminal lo siguiente
mkdir chat-go && cd chat-go && mkdir public && mkdir src
package main
// Importamos las librerías necesarias, aunque con solo guardar se importan automáticamente :Dimport (
"log""net/http""github.com/gorilla/websocket"
)
// La primera variable es un mapa donde la clave es en realidad un puntero a un WebSocket, el valor es un booleano.// La segunda variable es un canal que actuará como una cola de mensajes enviados por los clientes.var clients = make(map[*websocket.Conn]bool) // Connected clientsvar broadcast = make(chan Message) // Broadcast channel// Este es solo un objeto con métodos para tomar una conexión HTTP normal y actualizarla a un WebSocket var upgrader = websocket.Upgrader{}
// Definiremos un objeto para guardar nuestros mensajes, para interactuar con el servicio ***Gravatar*** que nos proporcionará un avatar único.type Message struct {
Email string`json:"email"`
Username string`json:"username"`
Message string`json:"message"`
}
// Esta función manejará nuestras conexiones WebSocket entrantesfunc handleConnections(w http.ResponseWriter, r *http.Request) {
// El método Upgrade() permite cambiar nuesra solicitud GET inicial a una completa en WebSocket, si hay un error lo mostramos en consola pero no salimos.
ws, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Fatal(err)
}
// Para cerrar la conexión una vez termina la funcióndefer ws.Close()
// Registramos nuestro nuevo cliente al agregarlo al mapa global de "clients" que fue creado anteriormente.
clients[ws] = true// Bucle infinito que espera continuamente que se escriba un nuevo mensaje en el WebSocket, lo desserializa de JSON a un objeto Message y luego lo arroja al canal de difusión.for {
var msg Message
// Read in a new message as JSON and map it to a Message object// Si hay un error, registramos ese error y eliminamos ese cliente de nuestro mapa global de clients
err := ws.ReadJSON(&msg)
if err != nil {
log.Printf("error: %v", err)
delete(clients, ws)
break
}
// Send the newly received message to the broadcast channel
broadcast <- msg
}
}
// Ciclo que lee continuamente desde el canal broadcast y luego transmite el mesaje a todos nuestros clientes a través de su respectiva conexión WebSocket. Si hay un error cerramos la conexión y elminamos del mapa "clients".func handleMessages() {
for {
// Grab the next message from the broadcast channel
msg := <-broadcast
// Send it out to every client that is currently connectedfor client := range clients {
err := client.WriteJSON(msg)
if err != nil {
log.Printf("error: %v", err)
client.Close()
delete(clients, client)
}
}
}
}
func main() {
// Para que al entrar en la aplicación el cliente tome el index.html
fs := http.FileServer(http.Dir("../public"))
http.Handle("/", fs)
// "/ws" es la ruta que nos ayudará a manejar cualquier solicitud para iniciar un WebSocket.
http.HandleFunc("/ws", handleConnections)
// Creamos una goroutina que nos ayudará a manejar los mensajesgo handleMessages()
// Iniciamos el servidor en localhost en el puerto 8080 y un mensaje que muetre por consola
log.Println("http server started on :8080")
err := http.ListenAndServe(":8080", nil)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
Archivo public/index.html
<htmllang= "en">
<head>
<metacharset= "UTF-8">
<title>Simple chattitle>
<linkrel ="stylesheet"href="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.97.8/css/materialize.min.css">
<linkrel ="stylesheet"href="https://fonts.googleapis.com/icon?family=Material+Icons">
<linkrel ="stylesheet"href="https://cdn.jsdelivr.net/emojione/2.2.6/assets/css/emojione.min.css"/>
<linkrel ="stylesheet"href="/style.css">
head>
<body>
<header>
<nav>
<divclass="nav-wrapper">
<ahref="/"class="brand-logo right">Simple Chata>
div>
nav>
header>
<mainid="app">
<divclass="row">
<divclass="col s12">
<divclass="card horizontal">
<divid="chat-messages"class="card-content"v-html="chatContent">
div>
div>
div>
div>
<divclass="row"v-if="joined">
<divclass="input-field col s8">
<inputtype="text"v-model="newMsg" @keyup.enter="send">
div>
<divclass="input-field col s4">
<buttonclass="waves-effect waves-light btn" @click="send">
<iclass="material-icons right">chati>
Send
button>
div>
div>
<divclass="row"v-if="!joined">
<divclass="input-field col s8">
<inputtype="email"v-model.trim="email"placeholder="Email">
div>
<divclass="input-field col s8">
<inputtype="text"v-model.trim="username"placeholder="Username">
div>
<divclass="input-field col s4">
<buttonclass="waves-effect waves-light btn" @click="join()">
<iclass="material-icons right">donei>
Join
button>
div>
div>
main>
<footerclass="page-footer">
footer>
<scriptsrc="https://unpkg.com/[email protected]/dist/vue.js">script>
<scriptsrc="https://cdn.jsdelivr.net/emojione/2.2.6/lib/js/emojione.min.js">script>
<scriptsrc="https://code.jquery.com/jquery-2.1.1.min.js">script>
<scriptsrc="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.2/rollups/md5.js">script>
<scriptsrc="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.97.8/js/materialize.min.js">script>
<scriptsrc="/app.js">script>
body>
html>
Para public/app.js
new Vue({
el: '#app',
data:{
ws: null, // Our websocket
newMsg: '', // Holds new messages to be sent to the server
chatContent: '', // A running list of chat messages displayed on the screen
email: null, // Email addres used for grabbing an avatar
username: null, // Our username
joined: false//True if email and username have been filled in
},
created: function(){
var self = this;
this.ws = new WebSocket('wss://' + window.location.host + '/ws');
this.ws.addEventListener('message', function(e){
var msg = JSON.parse(e.data);
self.chatContent += ''
+ ''">'// Avatar
+ msg.username
+ ''
+ emojione.toImage(msg.message) + ''; // Parse emojis
var element = document.getElementById('chat-messages');
element.scrollTop = element.scrollHeight; // Auto scroll to the bottom
});
},
methods: {
send: function(){if(this.newMsg != ''){
this.ws.send(
JSON.stringify({
email: this.email,
username: this.username,
message: $('').html(this.newMsg).text() // Strip out html
}
));
this.newMsg = ''; // Reset newMsg
}
},
join: function(){if(!this.email){
Materialize.toast('You must enter an email', 2000);
return
}
if (!this.username){
Materialize.toast('You must choose a username', 2000);
return
}
this.email = $('').html(this.email).text();
this.username = $('
Para public/style.css
body{
display: flex;
min-height: 100vh;
flex-direction: column;
}
main{
flex: 10 auto;
}
#chat-messages{
min-height: 10vh;
height: 60vh;
width: 100%;
}
A partir de aquí ya debes poder correr en localhost, para ello debes estar dentro de la carpeta chat-go/src e ingresar en la terminal
go run main.go
Ingresas en el navegador localhost:8080 y si todo salió bien tienes tu chat en Go y Gorilla con WebSocket lista
Archivo Dockerfile
FROM golang
EXPOSE8080
WORKDIRgo/src
COPY./ .
CMDgo get github.com/gorilla/websocket && cd src && go run main.go
Es preferible usar Now por terminal, haciendo así te ubicas dentro de la carpeta chat-go e ingresas en la terminal
Nota Si vas a hacer deploy a now.sh, debes cambiar al protocolo seguro, es decir de ws:// a wss://
now --docker
Notas:
Déjame tu comentario, ¿qué te pareció? ¿Llevaste a cabo el tutorial?
Realiza un chat websocket con tu lenguaje de BackEnd preferido y me cuentas tu experiencia
Saludos y gracias por leer este tutorial 😄
Repositorio en el que me he guiado
Tutorial en el que está basado