-
Notifications
You must be signed in to change notification settings - Fork 110
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Added new server packages. This commit open sources a few modules that were internal to Tidbyt and refactors existing server code into a loader and watcher module. Some big changes in the refactor include directory watching inside of watcher to ensure we get every update and removing sync.Mutex in favor of channels. * Refactored server to use new packages. This commit refactors the server code to use our new packages. * Update build to go1.16 to match go.mod. This commit updates the test actions to use go 1.16.12 to match the go.mod and to be able to support go embpedding.
- Loading branch information
1 parent
50d376d
commit ce90198
Showing
14 changed files
with
683 additions
and
214 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
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
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
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
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,151 @@ | ||
// Package browser provides the ability to send WebP images to a browser over | ||
// websockets. | ||
package browser | ||
|
||
import ( | ||
_ "embed" | ||
"fmt" | ||
"html/template" | ||
"log" | ||
"net/http" | ||
|
||
"github.com/gorilla/mux" | ||
"github.com/gorilla/websocket" | ||
"golang.org/x/sync/errgroup" | ||
"tidbyt.dev/pixlet/server/fanout" | ||
"tidbyt.dev/pixlet/server/loader" | ||
) | ||
|
||
// Browser provides a structure for serving WebP images over websockets to | ||
// a web browser. | ||
type Browser struct { | ||
addr string // The address to listen on. | ||
title string // The title of the HTML document. | ||
updateChan chan string // A channel of base64 encoded WebP images. | ||
watch bool | ||
fo *fanout.Fanout | ||
r *mux.Router | ||
tmpl *template.Template | ||
loader *loader.Loader | ||
} | ||
|
||
//go:embed preview-mask.png | ||
var previewMask []byte | ||
|
||
//go:embed favicon.png | ||
var favicon []byte | ||
|
||
//go:embed preview.html | ||
var previewHTML string | ||
|
||
// previewData is used to populate the HTML template. | ||
type previewData struct { | ||
Title string | ||
WebP string | ||
Watch bool | ||
} | ||
|
||
// NewBrowser sets up a browser structure. Call Run() to kick off the main loops. | ||
func NewBrowser(addr string, title string, watch bool, updateChan chan string, l *loader.Loader) (*Browser, error) { | ||
tmpl, err := template.New("preview").Parse(previewHTML) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
b := &Browser{ | ||
updateChan: updateChan, | ||
addr: addr, | ||
fo: fanout.NewFanout(), | ||
tmpl: tmpl, | ||
title: title, | ||
loader: l, | ||
watch: watch, | ||
} | ||
|
||
r := mux.NewRouter() | ||
r.HandleFunc("/", b.rootHandler) | ||
r.HandleFunc("/ws", b.websocketHandler) | ||
r.HandleFunc("/favicon.png", b.faviconHandler) | ||
r.HandleFunc("/preview-mask.png", b.previewMaskHandler) | ||
b.r = r | ||
|
||
return b, nil | ||
} | ||
|
||
// Run starts the server process and runs forever in a blocking fashion. The | ||
// main routines include an update watcher to process incomming changes to the | ||
// webp and running the http handlers. | ||
func (b *Browser) Run() error { | ||
defer b.fo.Quit() | ||
|
||
g := errgroup.Group{} | ||
g.Go(b.updateWatcher) | ||
g.Go(b.serveHTTP) | ||
|
||
return g.Wait() | ||
} | ||
|
||
func (b *Browser) serveHTTP() error { | ||
log.Printf("listening at http://%s\n", b.addr) | ||
return http.ListenAndServe(b.addr, b.r) | ||
} | ||
|
||
func (b *Browser) faviconHandler(w http.ResponseWriter, r *http.Request) { | ||
w.Header().Set("Content-Type", "image/png") | ||
w.Write(favicon) | ||
} | ||
|
||
func (b *Browser) previewMaskHandler(w http.ResponseWriter, r *http.Request) { | ||
w.Header().Set("Content-Type", "image/png") | ||
w.Write(previewMask) | ||
} | ||
|
||
func (b *Browser) websocketHandler(w http.ResponseWriter, r *http.Request) { | ||
if !b.watch { | ||
return | ||
} | ||
|
||
var upgrader = websocket.Upgrader{ | ||
ReadBufferSize: 1024, | ||
WriteBufferSize: 1024, | ||
} | ||
conn, err := upgrader.Upgrade(w, r, nil) | ||
if err != nil { | ||
log.Printf("error establishing a new connection %v\n", err) | ||
return | ||
} | ||
|
||
b.fo.NewClient(conn) | ||
} | ||
|
||
func (b *Browser) updateWatcher() error { | ||
for { | ||
select { | ||
case webp := <-b.updateChan: | ||
b.fo.Broadcast(webp) | ||
} | ||
} | ||
} | ||
|
||
func (b *Browser) rootHandler(w http.ResponseWriter, r *http.Request) { | ||
config := make(map[string]string) | ||
for k, vals := range r.URL.Query() { | ||
config[k] = vals[0] | ||
} | ||
|
||
webp, err := b.loader.LoadApplet(config) | ||
if err != nil { | ||
w.WriteHeader(500) | ||
fmt.Fprintln(w, err) | ||
return | ||
} | ||
|
||
data := previewData{ | ||
Title: b.title, | ||
Watch: b.watch, | ||
WebP: webp, | ||
} | ||
|
||
w.Header().Set("Content-Type", "text/html") | ||
b.tmpl.Execute(w, data) | ||
} |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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,95 @@ | ||
<!DOCTYPE html> | ||
<html> | ||
|
||
<head> | ||
<title>{{ .Title }}</title> | ||
<link rel="icon" type="image/png" href="/favicon.png" /> | ||
<style type="text/css"> | ||
img { | ||
image-rendering: pixelated; | ||
image-rendering: -moz-crisp-edges; | ||
image-rendering: crisp-edges; | ||
width: 100%; | ||
mask-size: contain; | ||
-webkit-mask-size: contain; | ||
mask-image: url("./preview-mask.png"); | ||
-webkit-mask-image: url("./preview-mask.png"); | ||
} | ||
</style> | ||
</head> | ||
|
||
<body bgcolor="black"> | ||
<div style="border: solid 1px white"> | ||
<img id="render" src="data:image/webp;base64,{{ .WebP }}" /> | ||
</div> | ||
|
||
{{ if .Watch }} | ||
<script> | ||
class Watcher { | ||
constructor() { | ||
this.connect(); | ||
} | ||
|
||
connect() { | ||
this.conn = new WebSocket("ws://" + document.location.host + "/ws"); | ||
this.conn.open = this.open.bind(this); | ||
this.conn.onmessage = this.process.bind(this); | ||
this.conn.onclose = this.close.bind(this); | ||
setTimeout(this.check.bind(this), 5000) | ||
} | ||
|
||
open(e) { | ||
console.log("connection established"); | ||
} | ||
|
||
process(e) { | ||
console.log("recieved new message"); | ||
const data = JSON.parse(e.data); | ||
|
||
switch (data.type) { | ||
case "webp": | ||
const img = document.getElementById("render"); | ||
img.src = "data:image/webp;base64," + data.message; | ||
} | ||
} | ||
|
||
check() { | ||
if (this.conn.readyState === WebSocket.CONNECTING) { | ||
console.log("connection timed out"); | ||
this.reconnect(); | ||
} | ||
} | ||
|
||
close(e) { | ||
// If the underlying TCP connection is having issues, | ||
// refresh the entire page. | ||
console.log("connection closed", e.code); | ||
if (e.code === 1006) { | ||
// If the server is down, the browser has a tough time | ||
// refreshing when it actually comes back up. Keep | ||
// refreshing until this js is no longer loaded. | ||
setInterval(this.refresh.bind(this), 5000) | ||
this.refresh(); | ||
return; | ||
} | ||
|
||
this.reconnect(); | ||
} | ||
|
||
refresh() { | ||
console.log("attempting to refresh page"); | ||
location.reload(true); | ||
} | ||
|
||
reconnect() { | ||
console.log("reestablishing connection"); | ||
this.connect(); | ||
} | ||
} | ||
|
||
let watcher = new Watcher(); | ||
</script> | ||
{{ end }} | ||
</body> | ||
|
||
</html> |
Oops, something went wrong.