Last active
December 24, 2024 19:35
-
-
Save abdusco/c2d46ad01946ddeaf7c5246097d9f8de to your computer and use it in GitHub Desktop.
Open up HTML from stdin in a browser tab
This file contains hidden or 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
package main | |
import ( | |
"context" | |
"fmt" | |
"io" | |
"log" | |
"net" | |
"net/http" | |
"os" | |
"os/exec" | |
"os/signal" | |
"runtime" | |
"strings" | |
"syscall" | |
"time" | |
) | |
// language=html | |
const injectScript = ` | |
<script> | |
const sendShutdown = () => navigator.sendBeacon('/shutdown'); | |
window.addEventListener('beforeunload', sendShutdown); | |
window.addEventListener('unload', sendShutdown); | |
</script> | |
` | |
func main() { | |
if err := run(); err != nil { | |
log.Fatal(err) | |
} | |
} | |
func newHandler(html string, onShutdown func()) *http.ServeMux { | |
handler := http.NewServeMux() | |
handler.HandleFunc("POST /shutdown", func(w http.ResponseWriter, r *http.Request) { | |
onShutdown() | |
}) | |
handler.HandleFunc("GET /", func(w http.ResponseWriter, r *http.Request) { | |
w.Header().Set("Content-Type", "text/html") | |
w.Write([]byte(html)) | |
}) | |
return handler | |
} | |
func run() error { | |
html, err := io.ReadAll(os.Stdin) | |
if err != nil { | |
return fmt.Errorf("failed to read HTML from stdin: %w", err) | |
} | |
htmlStr := string(html) | |
if strings.Contains(htmlStr, "</body>") { | |
htmlStr = strings.ReplaceAll(string(html), "</body>", injectScript+"</body>") | |
} else { | |
htmlStr += injectScript | |
} | |
listener, err := net.Listen("tcp", ":0") | |
if err != nil { | |
return fmt.Errorf("failed to create listener: %w", err) | |
} | |
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) | |
defer cancel() | |
port := listener.Addr().(*net.TCPAddr).Port | |
server := &http.Server{Addr: listener.Addr().String()} | |
server.Handler = newHandler(htmlStr, func() { | |
cancel() | |
}) | |
servedURL := fmt.Sprintf("http://localhost:%d", port) | |
go func() { | |
log.Printf("serving at %s", servedURL) | |
if err := server.Serve(listener); err != nil && err != http.ErrServerClosed { | |
log.Fatalf("failed to serve: %v", err) | |
} | |
}() | |
go func() { | |
// wait for server to start | |
time.Sleep(250 * time.Millisecond) | |
if err := openBrowser(servedURL); err != nil { | |
log.Fatalf("failed to open browser: %v", err) | |
} | |
}() | |
<-ctx.Done() | |
log.Printf("shutting down server") | |
if err := server.Shutdown(ctx); err != nil { | |
return fmt.Errorf("failed to shutdown server: %w", err) | |
} | |
return nil | |
} | |
func openBrowser(url string) error { | |
var cmd *exec.Cmd | |
switch runtime.GOOS { | |
case "darwin": | |
cmd = exec.Command("open", url) | |
case "linux": | |
cmd = exec.Command("xdg-open", url) | |
case "windows": | |
cmd = exec.Command("cmd", "/c", "start", url) | |
default: | |
return fmt.Errorf("invalid URL: %s", url) | |
} | |
return cmd.Run() | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment