mirror of https://github.com/poanetwork/gecko.git
178 lines
4.8 KiB
Go
178 lines
4.8 KiB
Go
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package api
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"net/http"
|
|
"net/url"
|
|
"sync"
|
|
|
|
"github.com/gorilla/handlers"
|
|
|
|
"github.com/rs/cors"
|
|
|
|
"github.com/ava-labs/gecko/snow"
|
|
"github.com/ava-labs/gecko/snow/engine/common"
|
|
"github.com/ava-labs/gecko/utils/logging"
|
|
)
|
|
|
|
const baseURL = "/ext"
|
|
|
|
var (
|
|
errUnknownLockOption = errors.New("invalid lock options")
|
|
)
|
|
|
|
// Server maintains the HTTP router
|
|
type Server struct {
|
|
log logging.Logger
|
|
factory logging.Factory
|
|
router *router
|
|
listenAddress string
|
|
}
|
|
|
|
// Initialize creates the API server at the provided host and port
|
|
func (s *Server) Initialize(log logging.Logger, factory logging.Factory, host string, port uint16) {
|
|
s.log = log
|
|
s.factory = factory
|
|
s.listenAddress = fmt.Sprintf("%s:%d", host, port)
|
|
s.router = newRouter()
|
|
}
|
|
|
|
// Dispatch starts the API server
|
|
func (s *Server) Dispatch() error {
|
|
handler := cors.Default().Handler(s.router)
|
|
listener, err := net.Listen("tcp", s.listenAddress)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
s.log.Info("API server listening on %q", s.listenAddress)
|
|
return http.Serve(listener, handler)
|
|
}
|
|
|
|
// DispatchTLS starts the API server with the provided TLS certificate
|
|
func (s *Server) DispatchTLS(certFile, keyFile string) error {
|
|
handler := cors.Default().Handler(s.router)
|
|
listener, err := net.Listen("tcp", s.listenAddress)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
s.log.Info("API server listening on %q", s.listenAddress)
|
|
return http.ServeTLS(listener, handler, certFile, keyFile)
|
|
}
|
|
|
|
// RegisterChain registers the API endpoints associated with this chain That
|
|
// is, add <route, handler> pairs to server so that http calls can be made to
|
|
// the vm
|
|
func (s *Server) RegisterChain(ctx *snow.Context, vmIntf interface{}) {
|
|
vm, ok := vmIntf.(common.VM)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
// all subroutes to a chain begin with "bc/<the chain's ID>"
|
|
chainID := ctx.ChainID.String()
|
|
defaultEndpoint := "bc/" + chainID
|
|
httpLogger, err := s.factory.MakeChain(chainID, "http")
|
|
if err != nil {
|
|
s.log.Error("Failed to create new http logger: %s", err)
|
|
return
|
|
}
|
|
s.log.Verbo("About to add API endpoints for chain with ID %s", ctx.ChainID)
|
|
|
|
// Register each endpoint
|
|
for extension, service := range vm.CreateHandlers() {
|
|
// Validate that the route being added is valid
|
|
// e.g. "/foo" and "" are ok but "\n" is not
|
|
_, err := url.ParseRequestURI(extension)
|
|
if extension != "" && err != nil {
|
|
s.log.Warn("could not add route to chain's API handler because route is malformed: %s", extension)
|
|
continue
|
|
}
|
|
s.log.Verbo("adding API endpoint: %s", defaultEndpoint+extension)
|
|
if err := s.AddRoute(service, &ctx.Lock, defaultEndpoint, extension, httpLogger); err != nil {
|
|
s.log.Error("error adding route: %s", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// AddRoute registers the appropriate endpoint for the vm given an endpoint
|
|
func (s *Server) AddRoute(handler *common.HTTPHandler, lock *sync.RWMutex, base, endpoint string, log logging.Logger) error {
|
|
url := fmt.Sprintf("%s/%s", baseURL, base)
|
|
s.log.Info("adding route %s%s", url, endpoint)
|
|
h := handlers.CombinedLoggingHandler(log, handler.Handler)
|
|
switch handler.LockOptions {
|
|
case common.WriteLock:
|
|
return s.router.AddRouter(url, endpoint, middlewareHandler{
|
|
before: lock.Lock,
|
|
after: lock.Unlock,
|
|
handler: h,
|
|
})
|
|
case common.ReadLock:
|
|
return s.router.AddRouter(url, endpoint, middlewareHandler{
|
|
before: lock.RLock,
|
|
after: lock.RUnlock,
|
|
handler: h,
|
|
})
|
|
case common.NoLock:
|
|
return s.router.AddRouter(url, endpoint, h)
|
|
default:
|
|
return errUnknownLockOption
|
|
}
|
|
}
|
|
|
|
// AddAliases registers aliases to the server
|
|
func (s *Server) AddAliases(endpoint string, aliases ...string) error {
|
|
url := fmt.Sprintf("%s/%s", baseURL, endpoint)
|
|
endpoints := make([]string, len(aliases))
|
|
for i, alias := range aliases {
|
|
endpoints[i] = fmt.Sprintf("%s/%s", baseURL, alias)
|
|
}
|
|
return s.router.AddAlias(url, endpoints...)
|
|
}
|
|
|
|
// AddAliasesWithReadLock registers aliases to the server assuming the http read
|
|
// lock is currently held.
|
|
func (s *Server) AddAliasesWithReadLock(endpoint string, aliases ...string) error {
|
|
// This is safe, as the read lock doesn't actually need to be held once the
|
|
// http handler is called. However, it is unlocked later, so this function
|
|
// must end with the lock held.
|
|
s.router.lock.RUnlock()
|
|
defer s.router.lock.RLock()
|
|
|
|
return s.AddAliases(endpoint, aliases...)
|
|
}
|
|
|
|
// Call ...
|
|
func (s *Server) Call(
|
|
writer http.ResponseWriter,
|
|
method,
|
|
base,
|
|
endpoint string,
|
|
body io.Reader,
|
|
headers map[string]string,
|
|
) error {
|
|
url := fmt.Sprintf("%s/vm/%s", baseURL, base)
|
|
|
|
handler, err := s.router.GetHandler(url, endpoint)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
req, err := http.NewRequest("POST", "*", body)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for key, value := range headers {
|
|
req.Header.Set(key, value)
|
|
}
|
|
|
|
handler.ServeHTTP(writer, req)
|
|
|
|
return nil
|
|
}
|