Initial commit
This commit is contained in:
171
cmd/web/helpers.go
Normal file
171
cmd/web/helpers.go
Normal file
@@ -0,0 +1,171 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
"html/template"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"runtime/debug"
|
||||
"time"
|
||||
"vkane.cz/tinyquiz/pkg/model"
|
||||
"vkane.cz/tinyquiz/pkg/model/ent"
|
||||
"vkane.cz/tinyquiz/pkg/rtcomm"
|
||||
)
|
||||
|
||||
func newTemplateCache(dir string) (map[string]*template.Template, error) {
|
||||
cache := map[string]*template.Template{}
|
||||
|
||||
pages, err := filepath.Glob(filepath.Join(dir, "*.page.tmpl.html"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, page := range pages {
|
||||
name := filepath.Base(page)
|
||||
|
||||
ts, err := template.ParseFiles(page)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if layouts, err := filepath.Glob(filepath.Join(dir, "*.layout.tmpl.html")); err == nil && len(layouts) > 0 {
|
||||
fmt.Println(layouts)
|
||||
fmt.Println(filepath.Rel(dir, layouts[0]))
|
||||
ts, err = ts.ParseFiles(layouts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if partials, err := filepath.Glob(filepath.Join(dir, "*.partial.tmpl.html")); err == nil && len(partials) > 0 {
|
||||
ts, err = ts.ParseFiles(partials...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cache[name] = ts
|
||||
}
|
||||
|
||||
return cache, nil
|
||||
}
|
||||
|
||||
func (app *application) render(w http.ResponseWriter, r *http.Request, name string, td interface{}) {
|
||||
ts, ok := app.templateCache[name]
|
||||
if !ok {
|
||||
app.serverError(w, fmt.Errorf("The template %s does not exist", name))
|
||||
return
|
||||
}
|
||||
|
||||
err := ts.Execute(w, td)
|
||||
if err != nil {
|
||||
app.serverError(w, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (app *application) serverError(w http.ResponseWriter, err error) {
|
||||
trace := fmt.Sprintf("%s\n%s", err.Error(), debug.Stack())
|
||||
app.errorLog.Output(2, trace)
|
||||
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
func (app *application) clientError(w http.ResponseWriter, status int) {
|
||||
app.errorLog.Output(2, string(debug.Stack()))
|
||||
|
||||
http.Error(w, http.StatusText(status), status)
|
||||
}
|
||||
|
||||
func (app *application) notFound(w http.ResponseWriter) {
|
||||
app.clientError(w, http.StatusNotFound)
|
||||
}
|
||||
|
||||
var upgrader = websocket.Upgrader{
|
||||
ReadBufferSize: 1, // we will not be reading from the socket
|
||||
EnableCompression: true,
|
||||
}
|
||||
const suBufferSize = 100
|
||||
const writeDeadline = time.Second * 10
|
||||
const socketGCPeriod = writeDeadline
|
||||
|
||||
// TODO utilize request context
|
||||
func (app *application) processWebSocket(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
||||
var playerUid uuid.UUID
|
||||
if uid, err := uuid.Parse(params.ByName("playerUid")); err == nil {
|
||||
playerUid = uid
|
||||
} else {
|
||||
app.clientError(w, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var player *ent.Player
|
||||
if p, err := app.model.GetPlayerWithSessionAndGame(playerUid, r.Context()); err == nil {
|
||||
player = p
|
||||
} else if errors.Is(err, model.NoSuchEntity) {
|
||||
app.clientError(w, http.StatusNotFound)
|
||||
return
|
||||
} else {
|
||||
app.serverError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if c, err := upgrader.Upgrade(w, r, nil); err != nil {
|
||||
app.clientError(w, http.StatusBadRequest)
|
||||
return
|
||||
} else {
|
||||
defer c.Close()
|
||||
if tcp, ok := c.UnderlyingConn().(*net.TCPConn); ok {
|
||||
tcp.SetKeepAlivePeriod(writeDeadline)
|
||||
tcp.SetKeepAlive(true)
|
||||
} else {
|
||||
app.infoLog.Printf("could not set keepalive for %s\n", playerUid)
|
||||
}
|
||||
var ch = make(chan rtcomm.StateUpdate, suBufferSize)
|
||||
app.rtClients.AddClient(player.Edges.Session.ID, ch)
|
||||
defer app.rtClients.RemoveClient(player.Edges.Session.ID, ch)
|
||||
if su, err := app.model.GetFullStateUpdate(player.Edges.Session.ID, r.Context()); err == nil {
|
||||
select {
|
||||
case ch <- su:
|
||||
break
|
||||
default:
|
||||
app.infoLog.Printf("could not send initial StateUpdate to %s\n", playerUid.String())
|
||||
}
|
||||
} else {
|
||||
app.errorLog.Printf("failed getting initial StateUpdate for %s with %v\n", playerUid.String(), err)
|
||||
}
|
||||
var gcTicker = time.Tick(socketGCPeriod)
|
||||
var devnull [0]byte
|
||||
loop:
|
||||
for {
|
||||
select {
|
||||
case <- gcTicker:
|
||||
c.UnderlyingConn().SetWriteDeadline(time.Time{})
|
||||
if _, err := c.UnderlyingConn().Write(devnull[:]); err != nil {
|
||||
app.infoLog.Printf("closing broken (%v) connection of %s\n", err, playerUid.String())
|
||||
break loop
|
||||
}
|
||||
case su := <- ch:
|
||||
if err := c.SetWriteDeadline(time.Now().Add(writeDeadline)); err != nil {
|
||||
app.errorLog.Printf("setting write deadline for %s failed with %v\n", playerUid.String(), err)
|
||||
break loop
|
||||
}
|
||||
if err := c.WriteJSON(su); errors.Is(err, io.EOF) {
|
||||
break loop
|
||||
} else if err != nil {
|
||||
app.infoLog.Printf("sending message for %s failed with %v\n", playerUid.String(), err)
|
||||
break loop
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user