mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-09 15:37:56 -05:00
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:
35
validator/web/BUILD.bazel
Normal file
35
validator/web/BUILD.bazel
Normal 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
2
validator/web/doc.go
Normal 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
49
validator/web/handler.go
Normal 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")
|
||||
}
|
||||
}
|
||||
}
|
||||
53
validator/web/handler_test.go
Normal file
53
validator/web/handler_test.go
Normal 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
52
validator/web/server.go
Normal 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
|
||||
}
|
||||
93
validator/web/site_data.go
Normal file
93
validator/web/site_data.go
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user