Как создать приложение для чата в реальном времени с помощью Go, Fiber и HTMX
В этом руководстве вы создадите простое приложение для чата в реальном времени с использованием Go, Fiber и HTMX.
Вы узнаете, как использовать универсальность Fiber, используя WebSocket. Вы также узнаете, как создать реактивный интерфейс без использования JavaScript.
Предварительные условия
- Хорошее понимание Go и HTTP-серверов.
- Go должен быть установлен (в этом проекте будет использоваться Go версии 1.22).
Начнём
Сначала создайте новую папку с именем go-chat
. Запустите Go в своем проекте, запустив go mod init pakacage_name
, как показано ниже:
go mod init github.com/steelthedev/go-chat
Как установить зависимости
Вам необходимо установить некоторые библиотеки, которые очень важны. Это можно сделать в терминале, выполнив следующие команды:
go get -u github.com/gofiber/fiber/v2
go get -u github.com/gofiber/websocket/v2
go get -u github.com/gofiber/template/html/v2
Они установят Fiber и другие компоненты, такие как WebSocket
и библиотеку шаблонов HTML.
Файл main.go
В корневом каталоге создайте файл main.go
. Этот файл будет точкой входа в приложение. Внутри файла мы собираемся создать простой веб-сервер:
package main
import "github.com/gofiber/fiber/v2"
func main() {
// Start new fiber instance
app := fiber.New()
// Create a "ping" handler to test the server
app.Get("/ping", func(ctx *fiber.Ctx) error{
return ctx.SendString("Welcome to fiber")
})
// Start the http server
app.Listen(":3000")
}
Сохраните файл и запустите go run main.go
в терминале, чтобы запустить веб-сервер.
Если вы зайдете в браузер и протестируете маршрут /ping
, должен появиться такой ответ:
Статические файлы
Для работы приложения нам понадобятся статические файлы, такие как файлы CSS и HTML. Создайте две папки с именами static
и view
. Внутри папки представлений создайте два html-файла: index.html
и messages.html
.
Вот как должен выглядеть файл index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Chat Room</title>
<script src="https://unpkg.com/htmx.org@1.9.10"
integrity="sha384-D1Kt99CQMDuVetoL1lrYwg5t+9QdHe7NLX/SoJYkXDFfX37iInKRy5xLSi8nO7UC"
crossorigin="anonymous"></script>
<!-- HTMX Websockets extension https://htmx.org/extensions/web-sockets/ -->
<script src="https://unpkg.com/htmx.org/dist/ext/ws.js"></script>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<link rel="stylesheet" href="/static/style.css">
</head>
<body>
<div class="container">
<div class="chat-window">
<div class="messages" id="messages" >
<!-- Messages will be appended here -->
</div>
<form id="form">
<div class="input-area">
<input type="text" name="text" min="1" id="messageInput" placeholder="Type a message...">
<button type="submit">Send</button>
</div>
</form>
</div>
</div>
</body>
</html>
В файле index.html выше мы связали необходимые плагины, такие как наш style.css, который скоро будет создан, HTMX и bootstrap 5.
Вот как должен выглядеть файл message.html:
<div id="messages" hx-swap-oob="beforeend">
<p class="text-small">{{ .Text }}</p>
</div>
Это сообщение будет ответом сервера, оно будет автоматически подменено в наш код index.html
в браузере с помощью HTMX.
Теперь создайте новую папку с именем static
. Внутри него создайте новый файл style.css
:
body {
margin: 0;
padding: 0;
font-family: Arial, sans-serif;
background-color: #f2f2f2;
}
.container {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
.chat-window {
width: 400px;
background-color: #fff;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
.messages {
padding: 10px;
overflow-y: scroll;
height: 300px;
}
.message {
margin-bottom: 10px;
}
.message p {
background-color: #f0f0f0;
border-radius: 5px;
padding: 5px 10px;
display: inline-block;
max-width: 80%;
}
.input-area {
padding: 10px;
display: flex;
}
.input-area input[type="text"] {
flex: 1;
padding: 8px;
border: 1px solid #ccc;
border-radius: 5px;
margin-right: 5px;
}
.input-area button {
padding: 8px 15px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
transition: background-color 0.3s;
}
.input-area button:hover {
background-color: #45a049;
}
.input-area button:active {
background-color: #3e8e41;
}
Как настроить статические файлы
В файле main.go
вам нужно указать Fiber, как обрабатывать ваши статические файлы, особенно папку для проверки рендеринга HTML. Обновите main.go
следующим образом:
package main
import (
"github.com/gofiber/fiber/v2"
"github.com/gofiber/template/html/v2"
)
func main() {
// Create views engine
viewsEngine := html.New("./views", ".html")
// Start new fiber instance
app := fiber.New(fiber.Config{
Views: viewsEngine,
})
// Static route and directory
app.Static("/static/", "./static")
// Create a "ping" handler to test the server
app.Get("/ping", func(ctx *fiber.Ctx) error{
return ctx.SendString("Welcome to fiber")
})
// Start the http server
app.Listen(":3000")
}
Как видно выше, к экземпляру приложения была добавлена конфигурация, а также настроен статический маршрут как /static/
.
Как создавать обработчики
Создайте новый файл с именем handlers.go
:
package handlers
import "github.com/gofiber/fiber/v2"
type AppHandler struct{}
func NewAppHandler() *AppHandler {
return &AppHandler{}
}
func (a *AppHandler) HandleGetIndex(ctx *fiber.Ctx) error {
context := fiber.Map{}
return ctx.Render("index", context)
}
В приведенном выше коде мы создали обработчик, который получил структуру AppHandler
. Это помогает с абстракциями на случай, если код станет больше. Функция HandleGetIndex
принимает указатель на контекст Fiber и отображает файл index.html.
В main.go:
package main
import (
"github.com/gofiber/fiber/v2"
"github.com/gofiber/template/html/v2"
"github.com/steelthedev/go-chat/handlers"
)
func main() {
// Start new fiber instance
app := fiber.New()
// Create a "ping" handler to test the server
app.Get("/ping", func(ctx *fiber.Ctx) error{
return ctx.SendString("Welcome to fiber")
})
// create new App Handler
appHandler := NewAppHandler()
// Add appHandler routes
app.Get("/, appHandler.HandleGetIndex)
// Start the http server
app.Listen(":3000")
}
Выше мы создали новый обработчик приложения и добавили функцию HandleGetIndex
в маршруты. Запустите команду go run main.go
. На localhost:3000
у вас должен появиться примерно такой экран:
Файл messages.go
Создайте новый файл непосредственно в проекте и назовите его message.go
. В этом файле будет размещена структура сообщения.
package main
type Message struct {
Text string `json:"text"`
}
Файл websocket.go
Создайте новый файл в каталоге проекта и назовите его websocket.go
. Здесь будет размещена основная функция, создающая сервер WebSocket
, считывающая его и записывающая во все каналы:
package main
import (
"bytes"
"encoding/json"
"html/template"
"log"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/websocket/v2"
)
type WebSocketServer struct {
clients map[*websocket.Conn]bool
broadcast chan *Message
}
func NewWebSocket() *WebSocketServer {
return &WebSocketServer{
clients: make(map[*websocket.Conn]bool),
broadcast: make(chan *Message),
}
}
func (s *WebSocketServer) HandleWebSocket(ctx *websocket.Conn) {
// Register a new Client
s.clients[ctx] = true
defer func() {
delete(s.clients, ctx)
ctx.Close()
}()
for {
_, msg, err := ctx.ReadMessage()
if err != nil {
log.Println("Read Error:", err)
break
}
// send the message to the broadcast channel
var message Message
if err := json.Unmarshal(msg, &message); err != nil {
log.Fatalf("Error Unmarshalling")
}
s.broadcast <- &message
}
}
func (s *WebSocketServer) HandleMessages() {
for {
msg := <-s.broadcast
// Send the message to all Clients
for client := range s.clients {
err := client.WriteMessage(websocket.TextMessage, getMessageTemplate(msg))
if err != nil {
log.Printf("Write Error: %v ", err)
client.Close()
delete(s.clients, client)
}
}
}
}
func getMessageTemplate(msg *Message) []byte {
tmpl, err := template.ParseFiles("views/message.html")
if err != nil {
log.Fatalf("template parsing: %s", err)
}
// Render the template with the message as data.
var renderedMessage bytes.Buffer
err = tmpl.Execute(&renderedMessage, msg)
if err != nil {
log.Fatalf("template execution: %s", err)
}
return renderedMessage.Bytes()
}
Функция HandleWebSocket
добавляет клиента, обрабатывает сообщения в формате JSON, а затем добавляет сообщение в канал для распространения среди всех клиентов с помощью HandleMessage
.
Это также поддерживает связь. getMessageTemplate
в основном обрабатывает сообщение в message.html
, а затем преобразует его в байт. Затем этот байт можно отправить клиенту в качестве ответа.
Как добавить WebSocket в маршруты и HTMX
Нам нужно добавить WebSocket в наши маршруты main.go
:
package main
import (
"github.com/gofiber/fiber/v2"
"github.com/gofiber/template/html/v2"
"github.com/gofiber/websocket/v2"
"github.com/steelthedev/go-chat/handlers"
)
func main() {
// Start new fiber instance
app := fiber.New()
// Create a "ping" handler to test the server
app.Get("/ping", func(ctx *fiber.Ctx) error{
return ctx.SendString("Welcome to fiber")
})
// create new App Handler
appHandler := NewAppHandler()
// Add appHandler routes
app.Get("/, appHandler.HandleGetIndex)
// create new webscoket
server := NewWebSocket()
app.Get("/ws", websocket.New(func(ctx *websocket.Conn) {
server.HandleWebSocket(ctx)
}))
go server.HandleMessages()
// Start the http server
app.Listen(":3000")
}
Добавлен WebSocket и его маршрут. Последний шаг — добавить теги HTMX в файл index.html
:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Chat Room</title>
<script src="https://unpkg.com/htmx.org@1.9.10"
integrity="sha384-D1Kt99CQMDuVetoL1lrYwg5t+9QdHe7NLX/SoJYkXDFfX37iInKRy5xLSi8nO7UC"
crossorigin="anonymous"></script>
<!-- HTMX Websockets extension https://htmx.org/extensions/web-sockets/ -->
<script src="https://unpkg.com/htmx.org/dist/ext/ws.js"></script>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<link rel="stylesheet" href="/static/style.css">
</head>
<body>
<div class="container">
<div class="chat-window" hx-ext="ws" ws-connect="/ws">
<div class="messages" id="messages" hx-swap="beforeend" hx-swap-oob="beforeend">
<!-- Messages will be appended here -->
</div>
<form id="form" ws-send hx ">
<div class="input-area">
<input type="text" name="text" min="1" id="messageInput" placeholder="Type a message...">
<button type="submit">Send</button>
</div>
</form>
</div>
</div>
</body>
</html>
Тег hx-ext
и тег ws-connect
указывают на URL-адрес WebSocket /ws
. Тег hx-swap
использовался для выполнения манипуляций с DOM, которые добавляют наши сообщения в div #messages
.
После сохранения запустите go run main.go
. Вы можете открыть два разных окна браузера по адресу localhost:3000
.
Если WebSocket работает нормально, вы сможете отправлять и получать сообщения из двух браузеров в режиме реального времени, как показано на рисунке.
Заключение
В этом уроке мы продемонстрировали, как создать простой сервер WebSocket с использованием Go, Fiber и HTMX.
Вы можете продолжить и улучшить этот проект, добавив дополнительные функции, такие как ClientID, аутентификация и управление пользователями. Вы можете посетить репозиторий проекта здесь: github.com/steelthedev/go-chat.