FEMT: Basic frontend-service implementation (#104229)
* create the most basic frontend-server module * expose prom metrics?? * add todo list * move frontend-service to its own folder in services * check error from writer.Write * reword comment, add launch config
This commit is contained in:
@@ -138,6 +138,7 @@
|
||||
/pkg/services/dashboardversion/ @grafana/grafana-backend-group
|
||||
/pkg/services/encryption/ @grafana/grafana-operator-experience-squad
|
||||
/pkg/services/folder/ @grafana/grafana-search-and-storage
|
||||
/pkg/services/frontend/ @grafana/grafana-frontend-platform
|
||||
/pkg/services/apiserver @grafana/grafana-app-platform-squad
|
||||
/pkg/services/hooks/ @grafana/grafana-backend-group
|
||||
/pkg/services/kmsproviders/ @grafana/grafana-operator-experience-squad
|
||||
|
||||
Vendored
+10
@@ -82,6 +82,16 @@
|
||||
"cwd": "${workspaceFolder}",
|
||||
"args": ["server", "target", "--homepath", "${workspaceFolder}", "--packaging", "dev"]
|
||||
},
|
||||
{
|
||||
"name": "Run Frontend Server",
|
||||
"type": "go",
|
||||
"request": "launch",
|
||||
"mode": "auto",
|
||||
"program": "${workspaceFolder}/pkg/cmd/grafana/",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"env": { "GF_DEFAULT_TARGET": "frontend-server", "GF_SERVER_HTTP_PORT": "3003" },
|
||||
"args": ["server", "target", "--homepath", "${workspaceFolder}", "--packaging", "dev"]
|
||||
},
|
||||
{
|
||||
"name": "Attach to Chrome",
|
||||
"port": 9222,
|
||||
|
||||
@@ -9,6 +9,7 @@ const (
|
||||
StorageServer string = "storage-server"
|
||||
ZanzanaServer string = "zanzana-server"
|
||||
InstrumentationServer string = "instrumentation-server"
|
||||
FrontendServer string = "frontend-server"
|
||||
)
|
||||
|
||||
var dependencyMap = map[string][]string{
|
||||
@@ -17,4 +18,5 @@ var dependencyMap = map[string][]string{
|
||||
ZanzanaServer: {InstrumentationServer},
|
||||
Core: {},
|
||||
All: {Core},
|
||||
FrontendServer: {},
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/modules"
|
||||
"github.com/grafana/grafana/pkg/services/authz"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/frontend"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/storage/unified/resource"
|
||||
"github.com/grafana/grafana/pkg/storage/unified/sql"
|
||||
@@ -120,7 +121,7 @@ func (s *ModuleServer) Run() error {
|
||||
|
||||
// only run the instrumentation server module if were not running a module that already contains an http server
|
||||
m.RegisterInvisibleModule(modules.InstrumentationServer, func() (services.Service, error) {
|
||||
if m.IsModuleEnabled(modules.All) || m.IsModuleEnabled(modules.Core) {
|
||||
if m.IsModuleEnabled(modules.All) || m.IsModuleEnabled(modules.Core) || m.IsModuleEnabled(modules.FrontendServer) {
|
||||
return services.NewBasicService(nil, nil, nil).WithName(modules.InstrumentationServer), nil
|
||||
}
|
||||
return NewInstrumentationService(s.log, s.cfg, s.promGatherer)
|
||||
@@ -151,6 +152,10 @@ func (s *ModuleServer) Run() error {
|
||||
return authz.ProvideZanzanaService(s.cfg, s.features)
|
||||
})
|
||||
|
||||
m.RegisterModule(modules.FrontendServer, func() (services.Service, error) {
|
||||
return frontend.ProvideFrontendService(s.cfg, s.promGatherer)
|
||||
})
|
||||
|
||||
m.RegisterModule(modules.All, nil)
|
||||
|
||||
return m.Run(s.context)
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
package frontend
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/dskit/services"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
)
|
||||
|
||||
type frontendService struct {
|
||||
*services.BasicService
|
||||
cfg *setting.Cfg
|
||||
httpServ *http.Server
|
||||
log log.Logger
|
||||
errChan chan error
|
||||
promGatherer prometheus.Gatherer
|
||||
}
|
||||
|
||||
func ProvideFrontendService(cfg *setting.Cfg, promGatherer prometheus.Gatherer) (*frontendService, error) {
|
||||
s := &frontendService{
|
||||
cfg: cfg,
|
||||
log: log.New("frontend-server"),
|
||||
promGatherer: promGatherer,
|
||||
}
|
||||
s.BasicService = services.NewBasicService(s.start, s.running, s.stop)
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (s *frontendService) start(ctx context.Context) error {
|
||||
s.httpServ = s.newFrontendServer(ctx)
|
||||
s.errChan = make(chan error)
|
||||
go func() {
|
||||
s.errChan <- s.httpServ.ListenAndServe()
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *frontendService) running(ctx context.Context) error {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
case err := <-s.errChan:
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
func (s *frontendService) stop(failureReason error) error {
|
||||
s.log.Info("stopping frontend server", "reason", failureReason)
|
||||
if err := s.httpServ.Shutdown(context.Background()); err != nil {
|
||||
s.log.Error("failed to shutdown frontend server", "error", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *frontendService) newFrontendServer(ctx context.Context) *http.Server {
|
||||
s.log.Info("starting frontend server", "addr", ":"+s.cfg.HTTPPort)
|
||||
|
||||
router := http.NewServeMux()
|
||||
router.Handle("/metrics", promhttp.HandlerFor(s.promGatherer, promhttp.HandlerOpts{EnableOpenMetrics: true}))
|
||||
router.HandleFunc("/", s.handleRequest)
|
||||
|
||||
server := &http.Server{
|
||||
// 5s timeout for header reads to avoid Slowloris attacks (https://thetooth.io/blog/slowloris-attack/)
|
||||
ReadHeaderTimeout: 5 * time.Second,
|
||||
Addr: ":" + s.cfg.HTTPPort,
|
||||
Handler: router,
|
||||
BaseContext: func(_ net.Listener) context.Context { return ctx },
|
||||
}
|
||||
|
||||
return server
|
||||
}
|
||||
|
||||
func (s *frontendService) handleRequest(writer http.ResponseWriter, request *http.Request) {
|
||||
// This should:
|
||||
// - get correct asset urls from fs or cdn
|
||||
// - generate a nonce
|
||||
// - render them into the index.html
|
||||
// - and return it to the user!
|
||||
|
||||
s.log.Info("handling request", "method", request.Method, "url", request.URL.String())
|
||||
htmlContent := `<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Grafana Frontend Server</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Grafana Frontend Server</h1>
|
||||
<p>This is a simple static HTML page served by the Grafana frontend server module.</p>
|
||||
</body>
|
||||
</html>`
|
||||
|
||||
writer.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
_, err := writer.Write([]byte(htmlContent))
|
||||
if err != nil {
|
||||
s.log.Error("could not write to response", "err", err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user