blockbook/server/internal.go
2023-04-24 23:58:08 +02:00

188 lines
5.8 KiB
Go

package server
import (
"context"
"encoding/json"
"fmt"
"html/template"
"net/http"
"path/filepath"
"github.com/golang/glog"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/trezor/blockbook/api"
"github.com/trezor/blockbook/bchain"
"github.com/trezor/blockbook/common"
"github.com/trezor/blockbook/db"
"github.com/trezor/blockbook/fiat"
)
// InternalServer is handle to internal http server
type InternalServer struct {
htmlTemplates[InternalTemplateData]
https *http.Server
certFiles string
db *db.RocksDB
txCache *db.TxCache
chain bchain.BlockChain
chainParser bchain.BlockChainParser
mempool bchain.Mempool
is *common.InternalState
api *api.Worker
}
// NewInternalServer creates new internal http interface to blockbook and returns its handle
func NewInternalServer(binding, certFiles string, db *db.RocksDB, chain bchain.BlockChain, mempool bchain.Mempool, txCache *db.TxCache, metrics *common.Metrics, is *common.InternalState, fiatRates *fiat.FiatRates) (*InternalServer, error) {
api, err := api.NewWorker(db, chain, mempool, txCache, metrics, is, fiatRates)
if err != nil {
return nil, err
}
addr, path := splitBinding(binding)
serveMux := http.NewServeMux()
https := &http.Server{
Addr: addr,
Handler: serveMux,
}
s := &InternalServer{
htmlTemplates: htmlTemplates[InternalTemplateData]{
debug: true,
},
https: https,
certFiles: certFiles,
db: db,
txCache: txCache,
chain: chain,
chainParser: chain.GetChainParser(),
mempool: mempool,
is: is,
api: api,
}
s.htmlTemplates.newTemplateData = s.newTemplateData
s.htmlTemplates.newTemplateDataWithError = s.newTemplateDataWithError
s.htmlTemplates.parseTemplates = s.parseTemplates
s.templates = s.parseTemplates()
serveMux.Handle(path+"favicon.ico", http.FileServer(http.Dir("./static/")))
serveMux.HandleFunc(path+"metrics", promhttp.Handler().ServeHTTP)
serveMux.HandleFunc(path, s.index)
serveMux.HandleFunc(path+"admin", s.htmlTemplateHandler(s.adminIndex))
if s.chainParser.GetChainType() == bchain.ChainEthereumType {
serveMux.HandleFunc(path+"admin/internal-data-errors", s.htmlTemplateHandler(s.internalDataErrors))
}
return s, nil
}
// Run starts the server
func (s *InternalServer) Run() error {
if s.certFiles == "" {
glog.Info("internal server: starting to listen on http://", s.https.Addr)
return s.https.ListenAndServe()
}
glog.Info("internal server: starting to listen on https://", s.https.Addr)
return s.https.ListenAndServeTLS(fmt.Sprint(s.certFiles, ".crt"), fmt.Sprint(s.certFiles, ".key"))
}
// Close closes the server
func (s *InternalServer) Close() error {
glog.Infof("internal server: closing")
return s.https.Close()
}
// Shutdown shuts down the server
func (s *InternalServer) Shutdown(ctx context.Context) error {
glog.Infof("internal server: shutdown")
return s.https.Shutdown(ctx)
}
func (s *InternalServer) index(w http.ResponseWriter, r *http.Request) {
si, err := s.api.GetSystemInfo(true)
if err != nil {
glog.Error(err)
w.WriteHeader(http.StatusInternalServerError)
return
}
buf, err := json.MarshalIndent(si, "", " ")
if err != nil {
glog.Error(err)
w.WriteHeader(http.StatusInternalServerError)
return
}
w.Write(buf)
}
const (
adminIndexTpl = iota + errorInternalTpl + 1
adminInternalErrorsTpl
internalTplCount
)
// InternalTemplateData is used to transfer data to the templates
type InternalTemplateData struct {
CoinName string
CoinShortcut string
CoinLabel string
ChainType bchain.ChainType
Error *api.APIError
InternalDataErrors []db.BlockInternalDataError
RefetchingInternalData bool
}
func (s *InternalServer) newTemplateData(r *http.Request) *InternalTemplateData {
t := &InternalTemplateData{
CoinName: s.is.Coin,
CoinShortcut: s.is.CoinShortcut,
CoinLabel: s.is.CoinLabel,
ChainType: s.chainParser.GetChainType(),
}
return t
}
func (s *InternalServer) newTemplateDataWithError(error *api.APIError, r *http.Request) *InternalTemplateData {
td := s.newTemplateData(r)
td.Error = error
return td
}
func (s *InternalServer) parseTemplates() []*template.Template {
templateFuncMap := template.FuncMap{
"formatUint32": formatUint32,
}
createTemplate := func(filenames ...string) *template.Template {
if len(filenames) == 0 {
panic("Missing templates")
}
return template.Must(template.New(filepath.Base(filenames[0])).Funcs(templateFuncMap).ParseFiles(filenames...))
}
t := make([]*template.Template, internalTplCount)
t[errorTpl] = createTemplate("./static/internal_templates/error.html", "./static/internal_templates/base.html")
t[errorInternalTpl] = createTemplate("./static/internal_templates/error.html", "./static/internal_templates/base.html")
t[adminIndexTpl] = createTemplate("./static/internal_templates/index.html", "./static/internal_templates/base.html")
t[adminInternalErrorsTpl] = createTemplate("./static/internal_templates/block_internal_data_errors.html", "./static/internal_templates/base.html")
return t
}
func (s *InternalServer) adminIndex(w http.ResponseWriter, r *http.Request) (tpl, *InternalTemplateData, error) {
data := s.newTemplateData(r)
return adminIndexTpl, data, nil
}
func (s *InternalServer) internalDataErrors(w http.ResponseWriter, r *http.Request) (tpl, *InternalTemplateData, error) {
if r.Method == http.MethodPost {
err := s.api.RefetchInternalData()
if err != nil {
return errorTpl, nil, err
}
}
data := s.newTemplateData(r)
internalErrors, err := s.db.GetBlockInternalDataErrorsEthereumType()
if err != nil {
return errorTpl, nil, err
}
data.InternalDataErrors = internalErrors
data.RefetchingInternalData = s.api.IsRefetchingInternalData()
return adminInternalErrorsTpl, data, nil
}