diff --git a/Makefile b/Makefile index c61bad6..d1f8fbf 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: help install install-dev lint format test race coverage check fuzz build-go build build-fast bundle bundle-watch bundle-clean clean clean-all build-all typecheck bump-patch +.PHONY: help install install-dev lint format test race coverage check fuzz build-go build build-fast bundle bundle-watch bundle-clean clean clean-all build-all typecheck bump-patch push GO_DIR = go STATIC_JS_DIR = go/webterm/static/js @@ -83,3 +83,20 @@ bump-patch: ## Bump patch version in VERSION and create git tag git commit -m "Bump version to $$NEW"; \ git tag "v$$NEW"; \ echo "Bumped version: $$OLD -> $$NEW (tagged v$$NEW)" + +push: ## Push current branch and tags pointing at HEAD + @BRANCH=$$(git rev-parse --abbrev-ref HEAD); \ + if [ "$$BRANCH" = "HEAD" ]; then \ + echo "Detached HEAD; refusing to push"; \ + exit 1; \ + fi; \ + git push origin "$$BRANCH"; \ + TAGS=$$(git tag --points-at HEAD); \ + if [ -n "$$TAGS" ]; then \ + for TAG in $$TAGS; do \ + echo "Pushing tag $$TAG"; \ + git push origin "$$TAG"; \ + done; \ + else \ + echo "No tags on current commit"; \ + fi diff --git a/go/webterm/server.go b/go/webterm/server.go index e68564f..5c084a1 100644 --- a/go/webterm/server.go +++ b/go/webterm/server.go @@ -2,6 +2,7 @@ package webterm import ( "bufio" + "compress/gzip" "context" "crypto/sha1" "encoding/json" @@ -67,6 +68,11 @@ type loggingResponseWriter struct { bytes int } +type gzipResponseWriter struct { + http.ResponseWriter + writer *gzip.Writer +} + func (w *loggingResponseWriter) WriteHeader(statusCode int) { w.status = statusCode w.ResponseWriter.WriteHeader(statusCode) @@ -114,6 +120,41 @@ func (w *loggingResponseWriter) Push(target string, opts *http.PushOptions) erro return http.ErrNotSupported } +func (w *gzipResponseWriter) WriteHeader(statusCode int) { + w.Header().Del("Content-Length") + w.Header().Set("Content-Encoding", "gzip") + w.Header().Add("Vary", "Accept-Encoding") + w.ResponseWriter.WriteHeader(statusCode) +} + +func (w *gzipResponseWriter) Write(payload []byte) (int, error) { + if w.Header().Get("Content-Encoding") == "" { + w.WriteHeader(http.StatusOK) + } + return w.writer.Write(payload) +} + +func (w *gzipResponseWriter) ReadFrom(r io.Reader) (int64, error) { + if w.Header().Get("Content-Encoding") == "" { + w.WriteHeader(http.StatusOK) + } + return io.Copy(w.writer, r) +} + +func (w *gzipResponseWriter) Flush() { + _ = w.writer.Flush() + if flusher, ok := w.ResponseWriter.(http.Flusher); ok { + flusher.Flush() + } +} + +func (w *gzipResponseWriter) Push(target string, opts *http.PushOptions) error { + if pusher, ok := w.ResponseWriter.(http.Pusher); ok { + return pusher.Push(target, opts) + } + return http.ErrNotSupported +} + type LocalServer struct { host string port int @@ -369,6 +410,26 @@ func (s *LocalServer) loggingMiddleware(next http.Handler) http.Handler { }) } +func (s *LocalServer) gzipMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") { + next.ServeHTTP(w, r) + return + } + if strings.EqualFold(strings.TrimSpace(r.Header.Get("Upgrade")), "websocket") { + next.ServeHTTP(w, r) + return + } + gz, err := gzip.NewWriterLevel(w, gzip.BestSpeed) + if err != nil { + next.ServeHTTP(w, r) + return + } + defer func() { _ = gz.Close() }() + next.ServeHTTP(&gzipResponseWriter{ResponseWriter: w, writer: gz}, r) + }) +} + func (s *LocalServer) handleWebSocket(w http.ResponseWriter, r *http.Request) { routeKey := strings.TrimPrefix(r.URL.Path, "/ws/") if routeKey == "" { @@ -755,7 +816,378 @@ func (s *LocalServer) handleRoot(w http.ResponseWriter, r *http.Request) { if s.dockerWatch { dockerWatchJS = "true" } - html := fmt.Sprintf(`Session Dashboard

Sessions

`, string(tilesJSON), composeModeJS, dockerWatchJS) + html := fmt.Sprintf(` + + + Session Dashboard + + + + + + +

Sessions

+
+
+ +
+
Type to search • ↑↓ to navigate • Enter to open • Esc to clear
+ + +`, string(tilesJSON), composeModeJS, dockerWatchJS) w.Header().Set("Content-Type", "text/html") _, _ = io.WriteString(w, html) return @@ -897,7 +1329,7 @@ func (s *LocalServer) Handler() http.Handler { if strings.TrimSpace(s.staticPath) != "" { mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir(s.staticPath)))) } - return s.loggingMiddleware(mux) + return s.loggingMiddleware(s.gzipMiddleware(mux)) } func (s *LocalServer) Run(ctx context.Context) error {