Serve Prysm Web UI from Validator (#7470)

* Prysm web UI basic idea

* Refactor to use shared.Service interface, add sanity test

* register web server

* Determine mimetype

* Use 4242 as port for web

* Allow localhost or 127.0.0.1 for CORS. More tests, commentary

* Add flags, add site_data.go

* ignore site data

* Add sha

* gofmt

* gofmt

* fix script

* Lints

* fix vis

Co-authored-by: Victor Farazdagi <simple.square@gmail.com>
This commit is contained in:
Preston Van Loon
2020-10-09 07:58:30 -07:00
committed by GitHub
parent c944f29c7c
commit 1f707842d2
15 changed files with 333 additions and 5 deletions

35
validator/web/BUILD.bazel Normal file
View File

@@ -0,0 +1,35 @@
load("@io_bazel_rules_go//go:def.bzl", "go_test")
load("@prysm//tools/go:def.bzl", "go_library")
load("@io_bazel_rules_go//extras:embed_data.bzl", "go_embed_data")
# gazelle:ignore site_data.go
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"handler.go",
"server.go",
":site_data", # keep
],
importpath = "github.com/prysmaticlabs/prysm/validator/web",
visibility = ["//validator:__subpackages__"],
deps = [
"//shared:go_default_library",
"@com_github_sirupsen_logrus//:go_default_library",
],
)
go_embed_data(
name = "site_data",
srcs = [
"@prysm_web_ui//:site",
],
var = "site",
)
go_test(
name = "go_default_test",
srcs = ["handler_test.go"],
embed = [":go_default_library"],
deps = ["//shared/testutil/assert:go_default_library"],
)

2
validator/web/doc.go Normal file
View File

@@ -0,0 +1,2 @@
// Package web is the service to serve the Prysm web UI. See https://github.com/prysmaticlabs/prysm-web-ui
package web

49
validator/web/handler.go Normal file
View File

@@ -0,0 +1,49 @@
package web
import (
"mime"
"net/http"
"net/url"
"path"
log "github.com/sirupsen/logrus"
)
const prefix = "external/prysm_web_ui"
// webHandler serves web requests from the bundled site data.
var webHandler = func(res http.ResponseWriter, req *http.Request) {
u, err := url.ParseRequestURI(req.RequestURI)
if err != nil {
panic(err)
}
p := u.Path
if p == "/" {
p = "/index.html"
}
p = path.Join(prefix, p)
if d, ok := site[p]; ok {
m := mime.TypeByExtension(path.Ext(p))
res.Header().Add("Content-Type", m)
res.WriteHeader(200)
if _, err := res.Write(d); err != nil {
log.WithError(err).Error("Failed to write http response")
}
} else if d, ok := site[path.Join(prefix, "index.html")]; ok {
// Angular routing expects that routes are rewritten to serve index.html. For example, if
// requesting /login, this should serve the single page app index.html.
m := mime.TypeByExtension(".html")
res.Header().Add("Content-Type", m)
res.WriteHeader(200)
if _, err := res.Write(d); err != nil {
log.WithError(err).Error("Failed to write http response")
}
} else { // If index.html is not present, serve 404. This should never happen.
log.WithField("URI", req.RequestURI).Error("Path not found")
res.WriteHeader(404)
if _, err := res.Write([]byte("Not found")); err != nil {
log.WithError(err).Error("Failed to write http response")
}
}
}

View File

@@ -0,0 +1,53 @@
package web
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/prysmaticlabs/prysm/shared/testutil/assert"
)
func TestWebHandler(t *testing.T) {
tests := []struct {
name string
requestURI string
wantStatus int
wantContentType string
}{
{
name: "base route",
requestURI: "/",
wantStatus: 200,
wantContentType: "text/html; charset=utf-8",
},
{
name: "index.html",
requestURI: "/index.html",
wantStatus: 200,
wantContentType: "text/html; charset=utf-8",
},
{
name: "bad route",
requestURI: "/foobar_bad",
wantStatus: 200, // Serves index.html by default.
wantContentType: "text/html; charset=utf-8",
},
{
name: "favicon.ico",
requestURI: "/favicon.ico",
wantStatus: 200,
wantContentType: "image/vnd.microsoft.icon",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
req := &http.Request{RequestURI: tt.requestURI}
res := httptest.NewRecorder()
webHandler(res, req)
assert.Equal(t, tt.wantStatus, res.Result().StatusCode)
assert.Equal(t, tt.wantContentType, res.Result().Header.Get("Content-Type"))
})
}
}

52
validator/web/server.go Normal file
View File

@@ -0,0 +1,52 @@
package web
import (
"context"
"net/http"
"time"
"github.com/prysmaticlabs/prysm/shared"
log "github.com/sirupsen/logrus"
)
var _ = shared.Service(&Server{})
// Server for the Prysm Web UI.
type Server struct {
http *http.Server
}
// NewServer creates a server service for the Prysm web UI.
func NewServer(addr string) *Server {
mux := http.NewServeMux()
mux.HandleFunc("/", webHandler)
return &Server{
http: &http.Server{
Addr: addr,
Handler: mux,
},
}
}
// Start the web server.
func (s *Server) Start() {
go func() {
log.WithField("address", s.http.Addr).Info("Starting Prysm web UI")
if err := s.http.ListenAndServe(); err != nil {
log.WithError(err).Error("Failed to start validator web server")
}
}()
}
// Stop the web server gracefully with 1s timeout.
func (s *Server) Stop() error {
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
return s.http.Shutdown(ctx)
}
// Status check for web server. Always returns nil.
func (s *Server) Status() error {
return nil
}

File diff suppressed because one or more lines are too long