DevGang
Авторизоваться

Как создать приложение для чата в реальном времени с помощью 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 в терминале, чтобы запустить веб-сервер.

go run команда запуска веб-сервера
go run команда запуска веб-сервера

Если вы зайдете в браузер и протестируете маршрут /ping, должен появиться такой ответ:

/ping маршрут в браузере
/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 у вас должен появиться примерно такой экран:

Поле ввода, кнопка отправки и область отображения чата на localhost:3000
Поле ввода, кнопка отправки и область отображения чата на 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.

Источник:

#CSS #Golang #HTML
Комментарии
Чтобы оставить комментарий, необходимо авторизоваться

Присоединяйся в тусовку

В этом месте могла бы быть ваша реклама

Разместить рекламу