mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-03-30 15:08:33 +00:00
[NOD-310] Implement REST server in API server (#389)
* [NOD-310] Implement REST server in API server * [NOD-310] MetaData -> Metadata * [NOD-310] Make custom context methods instead of custom request functions * [NOD-310] change "Request ID" prefix to "RID" and convert to apiServerContext with newAPIServerContext everywhere
This commit is contained in:
parent
893b8a88c8
commit
169e96e851
@ -2,6 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/daglabs/btcd/apiserver/logger"
|
||||
"github.com/daglabs/btcd/util"
|
||||
"github.com/jessevdk/go-flags"
|
||||
"path/filepath"
|
||||
@ -14,8 +15,9 @@ const (
|
||||
|
||||
var (
|
||||
// Default configuration options
|
||||
defaultLogDir = util.AppDataDir("apiserver", false)
|
||||
defaultDBHost = "localhost:3306"
|
||||
defaultLogDir = util.AppDataDir("apiserver", false)
|
||||
defaultDBAddr = "localhost:3306"
|
||||
defaultHTTPListen = "0.0.0.0:8080"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
@ -28,12 +30,14 @@ type config struct {
|
||||
DBHost string `long:"dbhost" description:"Database host"`
|
||||
DBUser string `long:"dbuser" description:"Database user" required:"true"`
|
||||
DBPassword string `long:"dbpass" description:"Database password" required:"true"`
|
||||
HTTPListen string `long:"listen" description:"HTTP address to listen on (default: 0.0.0.0:8080)"`
|
||||
}
|
||||
|
||||
func parseConfig() (*config, error) {
|
||||
cfg := &config{
|
||||
LogDir: defaultLogDir,
|
||||
DBHost: defaultDBHost,
|
||||
LogDir: defaultLogDir,
|
||||
DBHost: defaultDBAddr,
|
||||
HTTPListen: defaultHTTPListen,
|
||||
}
|
||||
parser := flags.NewParser(cfg, flags.PrintErrors|flags.HelpFlag)
|
||||
_, err := parser.Parse()
|
||||
@ -52,7 +56,7 @@ func parseConfig() (*config, error) {
|
||||
|
||||
logFile := filepath.Join(cfg.LogDir, defaultLogFilename)
|
||||
errLogFile := filepath.Join(cfg.LogDir, defaultErrLogFilename)
|
||||
initLog(logFile, errLogFile)
|
||||
logger.InitLog(logFile, errLogFile)
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
@ -1,27 +1,11 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/daglabs/btcd/logs"
|
||||
"github.com/daglabs/btcd/logger"
|
||||
"github.com/daglabs/btcd/util/panics"
|
||||
"os"
|
||||
)
|
||||
|
||||
var (
|
||||
backendLog = logs.NewBackend()
|
||||
log = backendLog.Logger("APIS")
|
||||
spawn = panics.GoroutineWrapperFunc(log, backendLog)
|
||||
log = logger.BackendLog.Logger("APIS")
|
||||
spawn = panics.GoroutineWrapperFunc(log, logger.BackendLog)
|
||||
)
|
||||
|
||||
func initLog(logFile, errLogFile string) {
|
||||
err := backendLog.AddLogFile(logFile, logs.LevelTrace)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error adding log file %s as log rotator for level %s: %s", logFile, logs.LevelTrace, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
err = backendLog.AddLogFile(errLogFile, logs.LevelWarn)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error adding log file %s as log rotator for level %s: %s", errLogFile, logs.LevelWarn, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
24
apiserver/logger/logger.go
Normal file
24
apiserver/logger/logger.go
Normal file
@ -0,0 +1,24 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/daglabs/btcd/logs"
|
||||
"os"
|
||||
)
|
||||
|
||||
// BackendLog is the logging backend used to create all subsystem loggers.
|
||||
var BackendLog = logs.NewBackend()
|
||||
|
||||
// InitLog attaches log file and error log file to the backend log.
|
||||
func InitLog(logFile, errLogFile string) {
|
||||
err := BackendLog.AddLogFile(logFile, logs.LevelTrace)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error adding log file %s as log rotator for level %s: %s", logFile, logs.LevelTrace, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
err = BackendLog.AddLogFile(errLogFile, logs.LevelWarn)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error adding log file %s as log rotator for level %s: %s", errLogFile, logs.LevelWarn, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
@ -2,12 +2,14 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/daglabs/btcd/apiserver/server"
|
||||
"github.com/daglabs/btcd/logger"
|
||||
"github.com/daglabs/btcd/signal"
|
||||
"github.com/daglabs/btcd/util/panics"
|
||||
)
|
||||
|
||||
func main() {
|
||||
defer panics.HandlePanic(log, backendLog)
|
||||
defer panics.HandlePanic(log, logger.BackendLog)
|
||||
|
||||
cfg, err := parseConfig()
|
||||
if err != nil {
|
||||
@ -18,13 +20,17 @@ func main() {
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("Error connecting to servers: %s", err))
|
||||
}
|
||||
defer disconnect(client)
|
||||
shutdownServer := server.Start(cfg.HTTPListen)
|
||||
defer func() {
|
||||
shutdownServer()
|
||||
disconnectFromNode(client)
|
||||
}()
|
||||
|
||||
interrupt := signal.InterruptListener()
|
||||
<-interrupt
|
||||
}
|
||||
|
||||
func disconnect(client *apiServerClient) {
|
||||
func disconnectFromNode(client *apiServerClient) {
|
||||
log.Infof("Disconnecting client")
|
||||
client.Disconnect()
|
||||
}
|
||||
|
62
apiserver/server/context.go
Normal file
62
apiserver/server/context.go
Normal file
@ -0,0 +1,62 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type contextKey string
|
||||
|
||||
const (
|
||||
contextKeyRequestID contextKey = "REQUEST_ID"
|
||||
)
|
||||
|
||||
type apiServerContext struct {
|
||||
context.Context
|
||||
}
|
||||
|
||||
func newAPIServerContext(ctx context.Context) *apiServerContext {
|
||||
if asCtx, ok := ctx.(*apiServerContext); ok {
|
||||
return asCtx
|
||||
}
|
||||
return &apiServerContext{Context: ctx}
|
||||
}
|
||||
|
||||
func (ctx *apiServerContext) setRequestID(requestID uint64) context.Context {
|
||||
context.WithValue(ctx, contextKeyRequestID, nextRequestID)
|
||||
return ctx
|
||||
}
|
||||
|
||||
func (ctx *apiServerContext) requestID() uint64 {
|
||||
id := ctx.Value(contextKeyRequestID)
|
||||
return id.(uint64)
|
||||
}
|
||||
|
||||
func (ctx *apiServerContext) getLogString(format string, params ...interface{}) string {
|
||||
params = append(params, ctx.requestID())
|
||||
return fmt.Sprintf("RID %d: "+format, params)
|
||||
}
|
||||
|
||||
func (ctx *apiServerContext) tracef(format string, params ...interface{}) {
|
||||
log.Tracef(ctx.getLogString(format, params))
|
||||
}
|
||||
|
||||
func (ctx *apiServerContext) debugf(format string, params ...interface{}) {
|
||||
log.Debugf(ctx.getLogString(format, params))
|
||||
}
|
||||
|
||||
func (ctx *apiServerContext) infof(format string, params ...interface{}) {
|
||||
log.Infof(ctx.getLogString(format, params))
|
||||
}
|
||||
|
||||
func (ctx *apiServerContext) warnf(format string, params ...interface{}) {
|
||||
log.Warnf(ctx.getLogString(format, params))
|
||||
}
|
||||
|
||||
func (ctx *apiServerContext) errorf(format string, params ...interface{}) {
|
||||
log.Errorf(ctx.getLogString(format, params))
|
||||
}
|
||||
|
||||
func (ctx *apiServerContext) criticalf(format string, params ...interface{}) {
|
||||
log.Criticalf(ctx.getLogString(format, params))
|
||||
}
|
9
apiserver/server/log.go
Normal file
9
apiserver/server/log.go
Normal file
@ -0,0 +1,9 @@
|
||||
package server
|
||||
|
||||
import "github.com/daglabs/btcd/util/panics"
|
||||
import "github.com/daglabs/btcd/apiserver/logger"
|
||||
|
||||
var (
|
||||
log = logger.BackendLog.Logger("REST")
|
||||
spawn = panics.GoroutineWrapperFunc(log, logger.BackendLog)
|
||||
)
|
48
apiserver/server/middlewares.go
Normal file
48
apiserver/server/middlewares.go
Normal file
@ -0,0 +1,48 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
var nextRequestID uint64 = 1
|
||||
|
||||
func addRequestMetadataMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
rCtx := newAPIServerContext(r.Context()).setRequestID(nextRequestID)
|
||||
r.WithContext(rCtx)
|
||||
nextRequestID++
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func loggingMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newAPIServerContext(r.Context())
|
||||
ctx.infof("Method: %s URI: %s", r.Method, r.RequestURI)
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func recoveryMiddleware(h http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newAPIServerContext(r.Context())
|
||||
var errStr string
|
||||
defer func() {
|
||||
recoveryErr := recover()
|
||||
if recoveryErr != nil {
|
||||
switch t := recoveryErr.(type) {
|
||||
case string:
|
||||
errStr = t
|
||||
case error:
|
||||
errStr = t.Error()
|
||||
default:
|
||||
errStr = "unknown error"
|
||||
}
|
||||
ctx.errorf("got error: %s", errStr)
|
||||
http.Error(w, fmt.Sprintf("got error in request %d: %s", ctx.requestID(), errStr), http.StatusInternalServerError)
|
||||
}
|
||||
}()
|
||||
h.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
44
apiserver/server/server.go
Normal file
44
apiserver/server/server.go
Normal file
@ -0,0 +1,44 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/gorilla/handlers"
|
||||
"github.com/gorilla/mux"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
const gracefulShutdownTimeout = 30 * time.Second
|
||||
|
||||
func mainHandler(w http.ResponseWriter, r *http.Request) {
|
||||
_, err := fmt.Fprintf(w, "API Server is running")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Start starts the HTTP REST server and returns a
|
||||
// function to gracefully shutdown it.
|
||||
func Start(listenAddr string) func() {
|
||||
router := mux.NewRouter()
|
||||
router.Use(addRequestMetadataMiddleware)
|
||||
router.Use(recoveryMiddleware)
|
||||
router.Use(loggingMiddleware)
|
||||
router.HandleFunc("/", mainHandler)
|
||||
httpServer := &http.Server{
|
||||
Addr: listenAddr,
|
||||
Handler: handlers.CORS()(router),
|
||||
}
|
||||
spawn(func() {
|
||||
log.Errorf("%s", httpServer.ListenAndServe())
|
||||
})
|
||||
return func() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), gracefulShutdownTimeout)
|
||||
defer cancel()
|
||||
err := httpServer.Shutdown(ctx)
|
||||
if err != nil {
|
||||
log.Errorf("Error shutting down http httpServer: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
2
go.mod
2
go.mod
@ -12,6 +12,8 @@ require (
|
||||
github.com/btcsuite/winsvc v1.0.0
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/golang/protobuf v1.3.1 // indirect
|
||||
github.com/gorilla/handlers v1.4.2
|
||||
github.com/gorilla/mux v1.6.2
|
||||
github.com/jessevdk/go-flags v1.4.0
|
||||
github.com/jinzhu/gorm v1.9.10
|
||||
github.com/jrick/logrotate v1.0.0
|
||||
|
3
go.sum
3
go.sum
@ -55,6 +55,9 @@ github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXi
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
github.com/gorilla/handlers v1.4.2 h1:0QniY0USkHQ1RGCLfKxeNHK9bkDHGRYGNDFBCS+YARg=
|
||||
github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
|
||||
github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk=
|
||||
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||
|
@ -24,8 +24,6 @@ import (
|
||||
// InitLog.
|
||||
var (
|
||||
// BackendLog is the logging backend used to create all subsystem loggers.
|
||||
// The backend must not be used before the log rotator has been initialized,
|
||||
// or data races and/or nil pointer dereferences will occur.
|
||||
BackendLog = logs.NewBackend()
|
||||
|
||||
adxrLog = BackendLog.Logger("ADXR")
|
||||
|
Loading…
x
Reference in New Issue
Block a user