Add week 3 materials

This commit is contained in:
2026-09-24 21:25:39 +03:00
parent 25fd4ed555
commit 0a1eb51bf8
18 changed files with 743 additions and 0 deletions
@@ -0,0 +1,32 @@
# websockets
В данной директории находится простая реализация websocket сервера на Go.
## Управление
Запуск сервера: `go run .`.
Для подключения к серверу предлагается использовать утилиту `wscat`:
```bash
wscat --connect 'localhost:8000/connect?author=igor'
# В параметре author указываем наш никнейм
```
Далее можно отправить JSON сообщение с полем `text`, после чего это сообщение будет доставлено всем
клиентам сервера:
```bash
~ wscat --connect 'localhost:8000/connect?author=igor'
> {"text": "privet"}
< {"Text":"privet","Author":"igor","SendTime":"2024-09-24T01:49:50.623671+03:00"}
```
## Бонус
Чтобы открыть свой сервер, запущенный локально, всему интернету,
можно использовать ngrok:
```
ngrok http 8000
```
После выполнения вам будет выдан публичный домен, по которому будет доступен
порт на вашей локальной системе.
@@ -0,0 +1,8 @@
module ws
go 1.20
require (
github.com/google/uuid v1.3.1
github.com/gorilla/websocket v1.5.0
)
@@ -0,0 +1,4 @@
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
@@ -0,0 +1,118 @@
package main
/*
Connect client 1:
wscat --connect 'localhost:8000/connect?author=lupa'
Connect client 2:
wscat --connect 'localhost:8000/connect?author=lupa'
Message: {"Text": "hi there"}
*/
import (
"log"
"net/http"
"os"
"time"
"github.com/google/uuid"
"github.com/gorilla/websocket"
)
type MessageStream struct {
Text string
Author string
SendTime *time.Time
}
type MessengerServer struct {
history chan *MessageStream
toSend map[string]chan *MessageStream
}
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
}
func (s *MessengerServer) Connect(w http.ResponseWriter, r *http.Request) {
// Extract author from the URL query parameters
author := r.URL.Query().Get("author")
if author == "" {
w.WriteHeader(http.StatusBadRequest)
return
}
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Printf("failed to upgrade: %s\n", err)
return
}
id := uuid.New()
s.toSend[id.String()] = make(chan *MessageStream, 1000)
log.Printf("Connected: %v\n", id)
go func() {
for {
var msg MessageStream
err := conn.ReadJSON(&msg)
if err != nil {
log.Println("Failed to decode a message:", err)
return
}
log.Printf("Received from %s: %v\n", author, msg)
now := time.Now()
msg.SendTime = &now
msg.Author = author
s.history <- &msg
}
}()
for {
mes := <-s.toSend[id.String()]
err := conn.WriteJSON(mes)
if err != nil {
log.Printf("Deleted %v stream, sending error: %v", id.String(), err)
delete(s.toSend, id.String())
return
}
log.Printf("Sent to stream %v: %v\n", id, mes)
}
}
func (s *MessengerServer) fanout() {
for {
mes := <-s.history
log.Printf("Got from history: %v", mes)
for _, ch := range s.toSend {
ch <- mes
}
}
}
func main() {
port := os.Getenv("MESSENGER_SERVER_PORT")
if port == "" {
port = "8000"
log.Println("Missing MESSENGER_SERVER_PORT, using default value: " + port)
}
server := MessengerServer{
history: make(chan *MessageStream, 1000),
toSend: make(map[string]chan *MessageStream),
}
http.HandleFunc("/connect", server.Connect)
go server.fanout()
log.Fatal(http.ListenAndServe("0.0.0.0:"+port, nil))
}