-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'main' of https://github.com/isd-sgcu/cutu-2024
- Loading branch information
Showing
20 changed files
with
673 additions
and
75 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"] | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
|
Oops, something went wrong.