Programação com WebSockets em Kotlin
Introdução
[editar | editar código]O Hypertext Transfer Protocol (HTTP) é o protocolo padrão utilizado na comunicação entre clientes e servidores na Web. Ele é eficiente, confiável e atende à maioria das necessidades de comunicação em aplicações web, como o carregamento de páginas, o envio de formulários e as chamadas de APIs REST. A comunicação é baseada em requisições feitas pelo cliente e respostas fornecidas pelo servidor, em um ciclo pontual.
No entanto, esse modelo de requisição-resposta torna-se limitado em situações onde é necessária uma comunicação contínua e bidirecional entre cliente e servidor. Exemplos comuns incluem bate-papos em tempo real, salas de jogos web online, notificações instantâneas ou atualizações simultâneas entre vários usuários. Neles, a troca constante de mensagens não exige apenas quantidade, mas também exige relativa baixa latência e grande interatividade.
Aqui entra o conceito de socket. Um socket é um ponto de comunicação que permite a troca de dados entre duas máquinas em uma rede, como um cliente e um servidor. Ele é um mecanismo fundamental para possibilitar a comunicação entre processos em máquinas diferentes. Embora o HTTP use sockets para estabelecer a conexão inicial, ele é limitado a um modelo de comunicação de requisição-resposta.
WebSocket é um protoloco que faz um uso específica de sockets, operando sobre o protocolo TCP. Ele trata a comunicação entre cliente e servidor como uma conexão persistente e bidirecional. Ao contrário do HTTP, que requer múltiplas conexões para cada requisição e resposta, o WebSocket estabelece uma única conexão que permanece aberta, permitindo que tanto o cliente quanto o servidor enviem e recebam dados a qualquer momento, sem a necessidade de novas requisições de conexão. Desse modo, sua principal vantagem sobre o HTTP é a redução da sobrecarga de comunicação.
No Kotlin, o framework Ktor facilita o uso de WebSockets oferecendo uma API baseada em corrotinas que simplifica o gerenciamento de conexões e a troca assíncrona de mensagens entre cliente e servidor. Isso torna o processo mais eficiente e reduz a complexidade do código. Neste tutorial, vamos entender os conceitos fundamentais de sockets e WebSockets, e aprender como funciona a programação com eles e, por fim, construir um exemplo prático de um chat em broadcast.
Sockets
[editar | editar código]Intuição e Conceitos
[editar | editar código]Imagine que você quer que dois processos distintos troquem mensagens. Em um mesmo computador, isso pode ser feito usando pipes: um programa coloca dados em um lado, e o outro programa lê do outro. É uma abstração de comunicação entre processos locais.
Um socket funciona como um pipe, mas com alcance além do sistema local, já que conecta dois processos operando em máquinas distintas. Dessa forma, enquanto pipes tradicionais são canais unidirecionais de comunicação entre dois processos no mesmo sistema operacional, sockets estendem esse paradigma para permitir a comunicação entre processos que possivelmente estarão sendo executados em sistemas diferentes, desde que estejam interligados por uma rede.
A estrutura do socket, então, é parecida — define-se uma interface na qual um lado escreve, o outro lê, e existem mecanismos internos para notificar a disponibilidade de dados. No entanto, diferentemente do pipe tradicional, o socket opera de forma bidirecional, permitindo o envio e o recebimento de dados pelo mesmo descritor.
Intuitivamente, podemos considerar sockets como uma ponta de um fio virtual que liga dois programas. Esse fio é identificado de forma única por dois pares (IP, número de porta) de cada lado da conexão, que é o destino remoto — o socket do lado oposto da conexão.
No par (IP, número de porta) de um socket, o número de porta funciona como um identificador local para um serviço específico dentro do computador remoto, e o IP identifica o computador remoto em si. Por exemplo, quando você acessa um site via navegador, seu computador cria um socket cliente, digamos por exemplo, a porta 54231 (efêmera) do cliente, e se conecta ao socket servidor do site, geralmente na porta 80 (HTTP) ou 443 (HTTPS) do servidor. O identificador do socket cliente seria (IP do servidor, 80).
Estabelecendo conversas
[editar | editar código]Do ponto de vista do sistema operacional, um socket é tratado de maneira muito semelhante a um descritor de arquivo, assim como o pipe. Isso significa que, internamente, os sockets são identificados por números inteiros (descritores) que o sistema operacional usa para acessar e manipular a comunicação. Isso padroniza a forma como o sistema lida com a entrada e saída de dados, permitindo que operações comuns, como ler e escrever dados, sejam feitas através de funções padrão como read(), write(), recv() e send() (em C, por exemplo), ou suas versões em outras linguagens.
Esse modelo unificado de E/S permite que programadores tratem diferentes formas de comunicação (como arquivos, redes, pipes, etc.) de maneira consistente, usando as mesmas operações, e com a mesma interface, sem precisar se preocupar com os detalhes de como cada uma delas funciona.
Existem diferentes tipos de sockets, definidos por domínio e tipo. Os domínios e tipos de socket definem onde e como a comunicação de rede ocorrerá, respectivamente.
Os domínios mais comuns são o AF_INET, que é usado para comunicação via IP (como na internet), e o AF_UNIX, que é usado para comunicação local entre processos no mesmo host, onde não há necessidade de IPs ou portas. Já o tipo do socket define o protocolo (idioma) usado para a transmissão de dados.
1. Tipo SOCK_STREAM
[editar | editar código]Implementa o Transmission Control Protocol (TCP). O TCP é um protocolo orientado à conexão. Isso significa que, antes de enviar e receber dados, o cliente e o servidor devem estabelecer uma conexão entre si. Esse processo é conhecido como handshake. Do ponto de vista técnico, uma conexão é o processo de estabelecer um canal de comunicação entre dois processos em sistemas diferentes ou no mesmo sistema. A conexão de dois sockets TCP funciona assim:
- Servidor: Cria um socket com uma função de escuta por pedidos de conexão (como
listen()em C ouServerSocketem Java/Kotlin) e associa-o a uma porta usando a funçãobind(), o que permite que o servidor seja acessado por clientes através de um número de porta específico e conhecido. O servidor então espera por conexões de clientes usandoaccept(). Isso é como o servidor fica “escutando” a porta. Uma vez que um cliente se conecta, o servidor pode aceitá-lo e transferi-lo para um novo socket (para liberar a porta antiga!) e começar a enviar e receber dados (comwrite()eread()) através do canal estabelecido. - Cliente: Cria um socket e usa
connect()para se conectar ao servidor, que está aguardando na porta específica. Após a conexão ser estabelecida, o cliente pode começar a enviar e receber dados (comwrite()eread()) através do canal.
Dado uma conexão estabelecida, o TCP garante que os dados sejam entregues na ordem correta e sem erros, controlando o fluxo de dados e retransmitindo pacotes perdidos, o que o torna confiável, ideal para aplicações que exigem alta precisão e entrega garantida, como navegação na web, e-mails e transferências de arquivos.
Abaixo, temos, por exemplo, a criação de um socket AF_INET/TCP na porta 9999 (socket de servidor, especificamente para aguardar por e aceitar conexões de entrada):
import java.net.ServerSocket
fun main() {
val server = ServerSocket(9999) // internamente, faz bind(9999)
println("Estou esperando alguém falar comigo…")
val client = server.accept()
println("Cliente conectado!")
client.getOutputStream().write("Olá, novo amigo!\n".toByteArray())
client.close()
server.close()
}
2. Tipo SOCK_DGRAM
[editar | editar código]Implementa o User Datagram Protocol (UDP). O UDP é um protocolo sem conexão. Isso significa que, ao contrário do TCP, não há necessidade de estabelecer uma conexão antes de começar a comunicação. Não há “aperto de mão” entre o cliente e o servidor. O servidor simplesmente escuta pacotes de dados de qualquer cliente, como quem ouve transmissões de rádio, e os processa assim que chegam, sem nenhuma garantia de ordem ou entrega. Cada pacote chega de forma independente, e o servidor lida com ele sem manter uma “sessão” com o remetente.
- Servidor: Cria um socket (usando
DatagramSocketem Java/Kotlin) e o associa a uma porta combind(). Em vez de esperar por conexões, o servidor simplesmente escuta pacotes de dados usandoreceive(). O servidor não precisa estabelecer uma conexão e nem manter um estado contínuo com o cliente. - Cliente: Cria um socket e envia pacotes de dados ao servidor. O UDP não exige que o cliente espere por uma resposta ou que a comunicação seja estabelecida. Cada pacote é enviado de forma independente.
Ele não garante que os pacotes cheguem na ordem correta ou que sejam entregues corretamente. Por isso, ele é mais rápido que o TCP, mas não oferece a confiabilidade que o TCP garante. O UDP é utilizado em aplicações onde a perda de pacotes não é crítica, como em transmissões de áudio e vídeo em tempo real e em jogos online. Abaixo, por exemplo, temos a criação de um socket AF_INET/UDP na porta 9999, para o servidor:
import java.net.DatagramPacket
import java.net.DatagramSocket
fun main() {
val socket = DatagramSocket(9999) // internamente, faz bind(9999)
println("Estou ouvindo o rádio…")
val buffer = ByteArray(1024)
val packet = DatagramPacket(buffer, buffer.size)
socket.receive(packet)
println("Eu ouvi isso: " + String(packet.data, 0, packet.length))
socket.close()
}
HTTP(S) e Sockets
[editar | editar código]O protocolo HTTP é um protocolo de camada de aplicação, usado principalmente para comunicação entre clientes e servidores web. No entanto, o HTTP, por si só, não é capaz de transmitir dados diretamente pela rede. Para isso, ele se apoia em protocolos de camadas inferiores, principalmente o TCP, que pertence à camada de transporte do modelo OSI. É nesse ponto que entram os sockets.
Quando um cliente faz uma requisição HTTP, como acessar um website, ele primeiro cria um socket AF_INET/TCP e se conecta ao servidor na porta padrão do HTTP (geralmente a porta 80 ou 443, no caso de HTTPS).
Uma vez conectados, o cliente envia uma requisição HTTP como texto puro através do socket — por exemplo, "GET / HTTP/1.1\r\nHost: exemplo.com\r\n\r n". Essa mensagem é enviada byte a byte pela rede usando o socket, e o servidor web, que está com um socket escutando na mesma porta, recebe a requisição, interpreta o conteúdo HTTP e responde com outra mensagem formatada em HTTP, como "HTTP/1.1 200 OK" seguida do conteúdo da página HTML.
Essa troca de mensagens HTTP ocorre totalmente dentro da conexão estabelecida pelo socket AF_INET/TCP, que garante que os dados cheguem completos e na ordem correta. Tanto o envio quanto o recebimento das mensagens são feitos por meio de operações de escrita e leitura no socket (write() e read()).
Vale destacar que o socket é apenas um meio e não entende o que é HTTP — ele apenas transmite os dados como uma sequência de bytes. A interpretação do protocolo HTTP é responsabilidade da aplicação que usa o socket.
No caso do HTTPS, a lógica é a mesma, mas antes de qualquer dado HTTP ser transmitido, o socket TCP é envolvido por uma camada de criptografia (TLS). Isso transforma o socket em um canal seguro por onde os dados HTTP trafegam criptografados.
WebSocket
[editar | editar código]O WebSocket é um protocolo de comunicação de camada de aplicação alternativo ao HTTP, embora utilize HTTP para sua comunicação inicial. O WebSocket fornece um canal full-duplex, bidirecional e persistente sobre uma conexão TCP. Ele foi padronizado pela RFC 6455 e desenvolvido para superar as limitações do HTTP em aplicações que exigem comunicação em tempo real.
O HTTP tem um aspecto que pode ser limitante a depender do tipo de aplicação. Como ele é unidirecional e baseado no modelo requisição-resposta (stateless), então cada requisição é iniciada pelo cliente, e o servidor só pode responder após recebê-la. Isso significa que, para receber atualizações em tempo real, o cliente precisa ficar perguntando repetidamente ao servidor se houveram novas atualizações — um padrão conhecido como “polling”. Essa abordagem envolve sobrecarga de rede e complexidade adicional, o que é problemático.
Para resolver esse problema, foi criado o protocolo WebSocket, que permite uma comunicação bidirecional e contínua entre cliente e servidor, usando também sockets como base. Diferente do HTTP tradicional, em que cada conexão é curta e isolada, uma conexão WebSocket é aberta uma única vez e mantida ativa, permitindo que dados fluam em ambas as direções sem a necessidade de novas requisições. Então, é permitido que cliente e servidor troquem mensagens a qualquer momento, sem que um precise aguardar o outro iniciar a comunicação. Essa característica o torna ideal para aplicações como chats, jogos, dashboards de dados em tempo real, sistemas de monitoramento e notificações instantâneas.
Apesar do protocolo WebSocket ser da camada de aplicação no modelo OSI, assim como o HTTP, o termo “socket” no nome do protocolo faz analogia à capacidade de manter uma conexão aberta e bidirecional, semelhante a um socket de rede (camada de transporte). Também partilha de características com sockets tradicionais, como as operações de leitura e escrita. Após a conexão estabelecida, tanto o cliente quanto o servidor podem ler e escrever dados diretamente no canal aberto, de maneira bidirecional e assíncrona, sem a necessidade de repetir o processo de conexão, o que, como explicado anteriormente, é uma característica típica dos sockets de rede.
A conexão WebSocket começa com um handshake HTTP que solicita ao servidor a elevação do protocolo para WebSocket. Este processo utiliza o mecanismo de upgrade do HTTP/1.1. É enviado ao servidor uma requisição HTTP solicitando o upgrade a WebSocket:
GET /chat HTTP/1.1
Host: exemplo.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: ZGVpeGEgZXUgbWUgY29uZWN0YXIgcG9yIGZhdm9y
Sec-WebSocket-Version: 13Sec-WebSocket-Key é uma string Base64 de 16 bytes aleatórios, usada para validação da resposta do servidor. Se aceita, o servidor responde, em HTTP, com:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: dGEgYm9tIGRpc2dyYW1lbnRvO valor de Sec-WebSocket-Accept é gerado pelo servidor concatenando a chave enviada pelo cliente com uma string fixa (guid: 258EAFA5-E914-47DA-95CA-C5AB0DC85B11), aplicando SHA-1, e depois codificando o resultado em Base64.
Uma vez estabelecida, a comunicação WebSocket se dá através de mensagens trocadas em frames binários estruturados, e não mais em texto/string como no HTTP. Segue uma tabela dos frames de um cabeçalho de mensagem em WebSocket:
| Campo | Trabalho | Descrição |
|---|---|---|
| FIN | 1 bit | Indica se é o último frame de uma mensagem |
| RSV1, RSV2, RSV3 | 1 bit cada | Reservados (usados para extensões como compressão) |
| Opcode | 4 bits | Tipo de frame (texto (0x1), binário (0x2), ping (0x9), pong (0xA), etc.) |
| Mask | 1 bit | Indica se o payload está mascarado (obrigatório no cliente → servidor). Por segurança, todo frame enviado do cliente para o servidor deve ser mascarado, enquanto frames do servidor para o cliente não precisam ser. A máscara é aplicada com uma operação XOR entre cada byte do payload e a chave de 4 bytes, repetida ciclicamente. |
| Payload length | 7 bits ou mais | Tamanho do payload (expansível para 16 ou 64 bits) |
| Masking-key | 0 ou 4 bytes | Chave de 4 bytes usada para mascarar/desmascarar os dados |
| Payload data | Variável | Dados da mensagem (texto, binário, controle) |
A conexão pode ser encerrada de forma limpa com um Close Frame (Opcode 0x8). O encerramento pode ser iniciado por qualquer lado (cliente ou servidor) e pode conter um código de status e uma mensagem. Por exemplo, 1000 é um encerramento normal, 1001 seria o endpoint estar indo embora e 1006 seria um fechamento anormal. Como WebSocket é uma conexão persistente, muitas implementações enviam Ping/Pong frames periodicamente para manter a conexão viva e detectar desconexões.
Na prática, um programador que utiliza de WebSocket não precisa saber essas tecnicalidades. Existem muitas APIs que implementam WebSocket, como o Ktor.
Comparativo HTTP x WebSocket
[editar | editar código]Abaixo, uma tabela comparativa entre HTTP e WebSockets:
| Característica | HTTP | WebSocket |
|---|---|---|
| Protocolo | Texto, requisição-resposta | Binário, full-duplex |
| Iniciador da comunicação | Sempre o cliente | Qualquer um dos lados |
| Persistência | Opcional (geralmente curta) | Persistente (até ser fechada) |
| Suporte a Push | Não (precisa ser polling ou SSE) | Nativo |
| Uso de recursos | Overhead por requisição | Overhead apenas no handshake |
| Eficiência em tempo real | Baixa | Alta |
| Porta padrão | 80 (HTTP), 443 (HTTPS) | 80 (ws), 443 (wss) |
| Camada de transporte | TCP | TCP |
Uma boa intuição para entender a principal diferença entre HTTP e WebSocket é imaginar uma conversa entre vizinhos: no HTTP, cada vez que você quiser falar com alguém, precisa bater na porta, enquanto no WebSocket, a porta permanece aberta, permitindo um diálogo contínuo e bidirecional. Assim, o HTTP é mais adequado para trocas pontuais de dados, enquanto o WebSocket favorece comunicações dinâmicas e contínuas.
Implementação de um servidor de chat global com Kotlin/Ktor
[editar | editar código]Para demonstrar o uso de WebSocket num contexto em que utlizar HTTP, embora possível, seja ineficiente, podemos fazer um chat em broadcast — isso é, cada usuário envia sua mensagem para todos ao mesmo tempo. Não seria adequado fazer essa aplicação com HTTP porque os clientes precisariam constantemente enviar requisições para verificar se há novas mensagens, o que resultaria em um tráfego excessivo na rede.
Um exemplo desse tipo, embora simples, serve bem para explicar programação com WebSocket em Kotlin.
Criação do projeto
[editar | editar código]Primeiramente, cria-se um projeto Ktor com os seguintes plugins:
- Routing: Implementação das rotas;
- WebSockets: Implementação do WebSocket;
- Content Negotiation + Kotlinx Serialization: Operam na conversão/tradução de JSON's;
- CORS: Permitir que diferentes hosts realizem uma conexão com o servidor;
- Static Content: Permite que tenhamos um arquivo HTML rodando na própria máquina, porta 8080.
Definição do modelo
[editar | editar código]Como vamos implementar um chat global, precisamos de um modelo que represente as mensagens. No caso, vamos precisar apenas de 3 informações: Nome do usuário que enviou a mensagem, o corpo da mensagem e o horário em que ela foi enviada. Dessa forma, temos:
Message.kt
[editar | editar código]import kotlinx.serialization.Serializable
@Serializable
data class Message (
val username: String,
val message: String,
val time: String
)
A mensagem, é claro, poderia conter outros metadados, não estando restrita aos três citados.
Serialização
[editar | editar código]WebSocket transmite dados em formato binário. No entanto, na maioria das aplicações modernas, os dados estruturados são manipulados em formatos JSON ou XML.
Para enviar objetos complexos (no nosso caso, mensagens de chat) pelo WebSocket, precisamos convertê-los para um formato serializável, que no caso será JSON. Isso garante que tanto o cliente quanto o servidor possam interpretar e reconstruir os dados corretamente.
Serialization.kt
[editar | editar código]package com.example
import io.ktor.serialization.kotlinx.json.*
import io.ktor.server.application.*
import io.ktor.server.plugins.contentnegotiation.*
import kotlinx.serialization.json.Json
fun Application.configureSerialization() {
install(ContentNegotiation) {
json(Json {
prettyPrint = true
})
}
}
O plugin ContentNegotiation e a biblioteca kotlinx.serialization permitem configurar a aplicação para realizar essa conversão automática.
Configurando o broadcast
[editar | editar código]É necessário definir um conjunto inicialmente vazio que será responsável por armazenar todos os clientes conectados ao servidor em um determinado momento. Além disso, toda vez que algum usuário envia algo no chat, é necessário capturar as informações da mensagem, formatá-las e enviá-las para todos os clientes ativos no servidor. Para isso, definimos uma função broadcast em Sockets.kt.
Apesar de não ser o padrão para chats em broadcast, definimos que caso um usuário envie a mensagem de comando “quit”, então isso é um indicativo de que ele quer encerrar a conexão (tradicionalmente, deveríamos usar um socket exclusivo para essa solicitação). Será necessário encerrar a conexão do cliente remetente com o servidor.
A última funcionalidade necessária é permitir que o arquivo HTML da pasta resources/static/ seja executado no endereço http://0.0.0.0:8080/static. Para tal, basta definir as seguintes funcionalidades:
Routing.kt
[editar | editar código]import io.ktor.server.routing.*
import io.ktor.server.application.*
import io.ktor.server.http.content.*
import io.ktor.server.websocket.*
import io.ktor.websocket.*
import kotlinx.serialization.json.Json
import model.Message
fun Application.configureRouting() {
routing {
webSocket("/chat") {
connections.add(this) // adiciona o cliente no conjunto de conexões
// fica esperando o buffer 'incoming' existir
// ==> significa que tem mensagem nova
try {
for (frame in incoming) {
if (frame is Frame.Text) {
// formata a mensagem recebida para JSON
val receivedText = frame.readText()
val message = Json.decodeFromString<Message>(receivedText)
val formattedMessage = Json.encodeToString(message)
// envia pra todo mundo ==> nota-se que é uma string
broadcast(formattedMessage)
// remove conexão ao receber "quit"
if (message.message == "quit") {
connections.remove(this)
close(CloseReason(CloseReason.Codes.NORMAL, "User quit"))
}
}
}
} finally {
// em casos inesperados, evita conexões fantasma
connections.remove(this)
}
}
// permite que seja executado o .html no resources/static/
staticResources("/static", "static")
}
}
Sockets.kt
[editar | editar código]import io.ktor.server.websocket.*
import io.ktor.websocket.*
// conjunto de clientes conectados
val connections = mutableSetOf<DefaultWebSocketServerSession>()
// função que envia a mensagem nova para todos os clientes
suspend fun broadcast(message: String) {
connections.forEach {
it.send(Frame.Text(message))
}
}
Nota-se como o envio de mensagens com WebSocket utiliza a bem-conhecida função send para escrever no buffer de envio.
Por fim, para que o servidor possa ser executado corretamente, basta chamar as configurações no arquivo da aplicação:
Application.kt
[editar | editar código]import io.ktor.server.application.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import io.ktor.server.plugins.cors.routing.*
import io.ktor.server.websocket.*
import kotlin.time.Duration.Companion.seconds
fun main() {
// configura as funcionalidades necessárias e inicializa o servidor
embeddedServer(Netty, port = 8080, host = "0.0.0.0") {
install(WebSockets) {
pingPeriod = 15.seconds
timeout = 60.seconds
maxFrameSize = Long.MAX_VALUE
masking = false
}
install(CORS) {
anyHost()
}
configureSerialization()
configureRouting()
}.start(wait = true)
}
Nota-se que, o caso do nosso chat, definimos que um “ping” será enviado ao(s) cliente(s) conectado(s) a cada 15 segundos, e o socket terá timeout de 60. Isso significa que, quando um cliente recebe “ping”, ele tem até 60 segundos para enviar “pong” de volta ao servidor, numa confirmação de que a conexão está viva e o cliente está presente. Caso falhe, a conexão é fechada porque o servidor entende que o cliente é fantasma (teve falha na conexão ou saiu dela sem avisar). Isso evita que conexões desnecessárias sejam mantidas abertas. Os tempos utilizados aqui são arbitrários, e vai do programador decidir quais são as quantidades de tempos necessárias e suficientes para garantir que a conexão vive sem desnecessariamente sobrecarregar a rede.
Implementação da página do cliente
[editar | editar código]O usuário terá acesso a dois inputs, um contendo o seu username e o outro contendo a mensagem que ele deseja enviar, bem como dois botões — o primeiro para enviar a mensagem, e o segundo para encerrar a sua conexão com o servidor. Para isso, basta escrever um script em JavaScript simples:
// inicializa a conexão do cliente com o servidor
const socket = new WebSocket("ws://localhost:8080/chat");
// elemento em que as mensagens serão impressas (chatbox)
const chatDiv = document.getElementById("chat");
// função que é executada toda vez que tem uma mensagem nova no buffer
// ==> imprime a mensagem na chatbox convertendo o JSON em 3 strings.
socket.onmessage = (event) => {
const msg = JSON.parse(event.data);
const user = document.getElementById("username").value.trim();
const className = (msg.username === user) ? "me" : "other";
chatDiv.innerHTML += `<div class="${className}"><b>${msg.time} ${msg.username}</b>: ${msg.message}</div>`;
chatDiv.scrollTop = chatDiv.scrollHeight;
};
// quando o botão de enviar mensagem é clicado e os dois inputs não estão
// vazios, cria um JSON com username, mensagem e tempo atual e envia para
// o socket do servidor
function sendMessage() {
const username = document.getElementById("username").value.trim();
const message = document.getElementById("message").value.trim();
if (!username || !message) {
alert("Preencha seu nome e a mensagem!");
return;
}
const msgObj = {
username: username,
message: message,
time: today(),
};
socket.send(JSON.stringify(msgObj));
document.getElementById("message").value = "";
}
// quando o botão de sair é clicado e o input do username não está vazio,
// é criado um JSON com o username, mensagem 'quit' e tempo atual e esse é
// enviado para o socket do servidor
function sendQuit() {
const username = document.getElementById("username").value.trim();
if (!username) {
alert("Preencha seu nome para sair!");
return;
}
const quitMsg = {
username: username,
message: "quit",
time: today(),
};
socket.send(JSON.stringify(quitMsg));
}
Vale notar que essa é uma implementação um tanto insegura para o sistema, porque não existe sanitação das mensagens enviadas pelos clientes — ou seja, um cliente com intenções maliciosas poderia inserir algum script abusando-se de HTML Injection para conseguir alguma informação privilegiada. Contudo, como o sistema apresentado neste tutorial não armazena dados sensíveis (não utiliza banco de dados) e tem como objetivo principal demonstrar o uso de WebSockets, então fugiria do escopo a sanitação das mensagens. Mas ainda é importante considerar essa vulnerabilidade caso essa implementação seja aplicada em sistemas maiores.
Agora, tendo implementado tanto o servidor quanto os clientes, estamos prontos para executar a aplicação do nosso chat com broadcast.
Projeto com a implementação
[editar | editar código]Caso se interesse, neste repositório está disponibilizado o projeto com a implementação do que aqui foi descrito.