Files
quake-kube/internal/quake/client/router.go
Chris Marshall ffca54fdb8 Initial commit
2020-08-02 13:46:12 -04:00

131 lines
3.0 KiB
Go

package client
import (
"bytes"
"html/template"
"io"
"io/ioutil"
"net"
"net/http"
"net/url"
"time"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)
type Config struct {
ContentServerURL string
ServerAddr string
Files http.FileSystem
}
func NewRouter(cfg *Config) (*echo.Echo, error) {
e := echo.New()
e.Use(middleware.Logger())
e.Use(middleware.Recover())
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
AllowOrigins: []string{"*"},
AllowHeaders: []string{echo.HeaderOrigin, echo.HeaderContentType, echo.HeaderAccept},
}))
f, err := cfg.Files.Open("index.html")
if err != nil {
return nil, err
}
defer f.Close()
data, err := ioutil.ReadAll(f)
if err != nil {
return nil, err
}
templates, err := template.New("index").Parse(string(data))
if err != nil {
return nil, err
}
e.Renderer = &TemplateRenderer{templates}
// default route
e.GET("/", func(c echo.Context) error {
return c.Render(http.StatusOK, "index", map[string]string{
"ServerAddr": cfg.ServerAddr,
})
})
raddr, err := net.ResolveUDPAddr("udp", cfg.ServerAddr)
if err != nil {
return nil, err
}
e.GET("/info", func(c echo.Context) error {
conn, err := net.ListenPacket("udp", "0.0.0.0:0")
if err != nil {
return err
}
defer conn.Close()
buffer := make([]byte, 1024*1024)
if err := conn.SetDeadline(time.Now().Add(5 * time.Second)); err != nil {
return err
}
n, err := conn.WriteTo([]byte("\xff\xff\xff\xffgetinfo xxx"), raddr)
if err != nil {
return err
}
n, _, err = conn.ReadFrom(buffer)
if err != nil {
return err
}
resp := buffer[:n]
resp = bytes.TrimPrefix(resp, []byte("\xff\xff\xff\xffinfoResponse\n\\"))
resp = bytes.TrimSuffix(resp, []byte("\\xxx"))
parts := bytes.Split(resp, []byte("\\"))
m := make(map[string]string)
for i := 0; i < len(parts)-1; i += 2 {
m[string(parts[i])] = string(parts[i+1])
}
return c.JSON(http.StatusOK, m)
})
// static files
e.GET("/*", echo.WrapHandler(http.FileServer(cfg.Files)))
// Quake3 assets requests must be proxied to the content server. The host
// header is manipulated to ensure that services like CloudFlare will not
// reject requests based upon incorrect host header.
csurl, err := url.Parse(cfg.ContentServerURL)
if err != nil {
return nil, err
}
g := e.Group("/assets")
g.Use(middleware.ProxyWithConfig(middleware.ProxyConfig{
Balancer: middleware.NewRoundRobinBalancer([]*middleware.ProxyTarget{
{URL: csurl},
}),
Transport: &HostHeaderTransport{RoundTripper: http.DefaultTransport, Host: csurl.Host},
}))
return e, nil
}
type HostHeaderTransport struct {
http.RoundTripper
Host string
}
func (t *HostHeaderTransport) RoundTrip(req *http.Request) (*http.Response, error) {
req.Host = t.Host
return t.RoundTripper.RoundTrip(req)
}
type TemplateRenderer struct {
*template.Template
}
func (t *TemplateRenderer) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
return t.ExecuteTemplate(w, name, data)
}