Skip to content

Commit

Permalink
Merge branch 'main' of https://github.com/isd-sgcu/cutu-2024
Browse files Browse the repository at this point in the history
  • Loading branch information
TeeGoood committed Mar 26, 2024
2 parents 7172e15 + 85f76d3 commit f87f3f7
Show file tree
Hide file tree
Showing 20 changed files with 673 additions and 75 deletions.
23 changes: 23 additions & 0 deletions apps/admin_api/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# syntax=docker/dockerfile:1

FROM golang:1.22.1

# Set destination for COPY
WORKDIR /app

# Download Go modules
COPY go.mod go.sum ./
RUN go mod download

# Copy the source code. Note the slash at the end, as explained in
# https://docs.docker.com/reference/dockerfile/#copy
COPY *.go ./

# Build
RUN CGO_ENABLED=0 GOOS=linux go build ./cmd/main.go -o /docker-gs-ping

EXPOSE 8080

# Run
CMD ["/docker-gs-ping"]

77 changes: 77 additions & 0 deletions apps/admin_api/cmd/broadcaster.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package main

import (
"context"
"encoding/json"
"errors"
"log"
"strconv"
"time"

"github.com/redis/go-redis/v9"
)

var (
broadcastInterval = 1
)

type Broadcaster struct {
hub *Hub
cache *redis.Client
}

func newBroadcaster(hub *Hub, cache *redis.Client) Broadcaster {
return Broadcaster{
hub: hub,
cache: cache,
}
}

type TeamStatus struct {
CU int `json:"cu"`
TU int `json:"tu"`
}

func (b *Broadcaster) run() {
for {
time.Sleep(time.Duration(broadcastInterval) * time.Second)
ctx := context.Background()
var cuInt int
cu, err := b.cache.Get(ctx, "t:cu").Result()
if err != nil {
if !errors.Is(err, redis.Nil) {
log.Printf("[ERROR] broadcaster: %v", err)
continue
}
cuInt = 0
} else {
cuInt, err = strconv.Atoi(cu)
if err != nil {
log.Printf("[ERROR] cu is not int broadcaster: %v", err)
continue
}
}
var tuInt int
tu, err := b.cache.Get(ctx, "t:tu").Result()
if err != nil {
if !errors.Is(err, redis.Nil) {
log.Printf("[ERROR] broadcaster: %v", err)
continue
}
tuInt = 0
} else {
tuInt, err = strconv.Atoi(tu)
if err != nil {
log.Printf("[ERROR] tu is not int broadcaster: %v", err)
continue
}
}

raw, _ := json.Marshal(TeamStatus{
CU: cuInt,
TU: tuInt,
})

b.hub.broadcast <- raw
}
}
132 changes: 132 additions & 0 deletions apps/admin_api/cmd/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package main

import (
"log"
"net/http"
"time"

"github.com/gorilla/websocket"
"github.com/redis/go-redis/v9"
)

const (
// Time allowed to write a message to the peer.
writeWait = 10 * time.Second

// Time allowed to read the next pong message from the peer.
pongWait = 60 * time.Second

// Send pings to peer with this period. Must be less than pongWait.
pingPeriod = (pongWait * 9) / 10

// Maximum message size allowed from peer.
maxMessageSize = 512
)

var (
newline = []byte{'\n'}
space = []byte{' '}
)

var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
}

// Client is a middleman between the websocket connection and the hub.
type Client struct {
hub *Hub

// The websocket connection.
conn *websocket.Conn
redisConn *redis.Client

// Buffered channel of outbound messages.
send chan []byte
}

// readPump pumps messages from the websocket connection to the hub.
//
// The application runs readPump in a per-connection goroutine. The application
// ensures that there is at most one reader on a connection by executing all
// reads from this goroutine.
func (c *Client) readPump() {
defer func() {
c.hub.unregister <- c
c.conn.Close()
}()
c.conn.SetReadLimit(maxMessageSize)
c.conn.SetReadDeadline(time.Now().Add(pongWait))
c.conn.SetPongHandler(func(string) error { c.conn.SetReadDeadline(time.Now().Add(pongWait)); return nil })
for {
_, _, err := c.conn.ReadMessage()
if err != nil {
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
log.Printf("error: %v", err)
}
break
}
}
}

// writePump pumps messages from the hub to the websocket connection.
//
// A goroutine running writePump is started for each connection. The
// application ensures that there is at most one writer to a connection by
// executing all writes from this goroutine.
func (c *Client) writePump() {
ticker := time.NewTicker(pingPeriod)
defer func() {
ticker.Stop()
c.conn.Close()
}()
for {
select {
case message, ok := <-c.send:
c.conn.SetWriteDeadline(time.Now().Add(writeWait))
if !ok {
// The hub closed the channel.
c.conn.WriteMessage(websocket.CloseMessage, []byte{})
return
}

w, err := c.conn.NextWriter(websocket.TextMessage)
if err != nil {
return
}
w.Write(message)

// Add queued chat messages to the current websocket message.
n := len(c.send)
for i := 0; i < n; i++ {
w.Write(newline)
w.Write(<-c.send)
}

if err := w.Close(); err != nil {
return
}
case <-ticker.C:
c.conn.SetWriteDeadline(time.Now().Add(writeWait))
if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil {
return
}
}
}
}

// serveWs handles websocket requests from the peer.
func serveWs(hub *Hub, w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Println(err)
return
}
client := &Client{hub: hub, conn: conn, send: make(chan []byte, 256)}
client.hub.register <- client

// Allow collection of memory referenced by the caller by doing all work in
// new goroutines.
go client.writePump()
go client.readPump()
}
49 changes: 49 additions & 0 deletions apps/admin_api/cmd/hub.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package main

// Hub maintains the set of active clients and broadcasts messages to the
// clients.
type Hub struct {
// Registered clients.
clients map[*Client]bool

// Inbound messages from the clients.
broadcast chan []byte

// Register requests from the clients.
register chan *Client

// Unregister requests from clients.
unregister chan *Client
}

func newHub() *Hub {
return &Hub{
broadcast: make(chan []byte),
register: make(chan *Client),
unregister: make(chan *Client),
clients: make(map[*Client]bool),
}
}

func (h *Hub) run() {
for {
select {
case client := <-h.register:
h.clients[client] = true
case client := <-h.unregister:
if _, ok := h.clients[client]; ok {
delete(h.clients, client)
close(client.send)
}
case message := <-h.broadcast:
for client := range h.clients {
select {
case client.send <- message:
default:
close(client.send)
delete(h.clients, client)
}
}
}
}
}
44 changes: 44 additions & 0 deletions apps/admin_api/cmd/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package main

import (
"flag"
"log"
"net/http"
"time"

"github.com/redis/go-redis/v9"
)

var addr = flag.String("addr", ":8080", "http service address")

var hcString = []byte("OK!")

func healthCheck(w http.ResponseWriter, r *http.Request) {
w.Write(hcString)
}

func main() {
flag.Parse()
conn := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "",
})
hub := newHub()
broadcaster := newBroadcaster(hub, conn)
go hub.run()
go broadcaster.run()
handler := newHandler(conn)
http.HandleFunc("/", healthCheck)
http.HandleFunc("PUT /state", handler.ChangeState)
http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
serveWs(hub, w, r)
})
server := &http.Server{
Addr: *addr,
ReadHeaderTimeout: 3 * time.Second,
}
err := server.ListenAndServe()
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
55 changes: 55 additions & 0 deletions apps/admin_api/cmd/route.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package main

import (
"context"
"encoding/json"
"io"
"log"
"net/http"

"github.com/redis/go-redis/v9"
)

type Handler struct {
conn *redis.Client
}

func newHandler(conn *redis.Client) Handler {
return Handler{
conn,
}
}

type ChangeStateBody struct {
NewState string `json:"new_state"`
}

func (h *Handler) ChangeState(w http.ResponseWriter, req *http.Request) {
var body ChangeStateBody
reader := req.Body
defer reader.Close()

raw, err := io.ReadAll(reader)
if err != nil {
log.Print("[ERROR] Unable to get body reader")
w.WriteHeader(http.StatusBadRequest)
return
}

log.Printf("%v", raw)
if err := json.Unmarshal(raw, &body); err != nil {
log.Printf("[ERROR] Invalid json: %v", err)
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte("Invalid json"))
return
}

ctx := context.Background()
if err := h.conn.Set(ctx, "s:state", body.NewState, 0).Err(); err != nil {
log.Printf("[ERROR] Unable to set new state: %v", err)
w.WriteHeader(http.StatusBadRequest)
return
}

w.WriteHeader(http.StatusOK)
}
9 changes: 9 additions & 0 deletions apps/admin_api/docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
version: '3.9'

services:
redis:
image: redis:7.2.4-bookworm
container_name: redis
ports:
- 6379:6379

Loading

0 comments on commit f87f3f7

Please sign in to comment.