github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/engine/access/rest/routes/router.go (about)

     1  package routes
     2  
     3  import (
     4  	"fmt"
     5  	"net/http"
     6  	"regexp"
     7  	"strings"
     8  
     9  	"github.com/gorilla/mux"
    10  	"github.com/rs/zerolog"
    11  
    12  	"github.com/onflow/flow-go/access"
    13  	"github.com/onflow/flow-go/engine/access/rest/middleware"
    14  	"github.com/onflow/flow-go/engine/access/rest/models"
    15  	"github.com/onflow/flow-go/engine/access/state_stream"
    16  	"github.com/onflow/flow-go/engine/access/state_stream/backend"
    17  	"github.com/onflow/flow-go/model/flow"
    18  	"github.com/onflow/flow-go/module"
    19  )
    20  
    21  // RouterBuilder is a utility for building HTTP routers with common middleware and routes.
    22  type RouterBuilder struct {
    23  	logger      zerolog.Logger
    24  	router      *mux.Router
    25  	v1SubRouter *mux.Router
    26  }
    27  
    28  // NewRouterBuilder creates a new RouterBuilder instance with common middleware and a v1 sub-router.
    29  func NewRouterBuilder(
    30  	logger zerolog.Logger,
    31  	restCollector module.RestMetrics) *RouterBuilder {
    32  	router := mux.NewRouter().StrictSlash(true)
    33  	v1SubRouter := router.PathPrefix("/v1").Subrouter()
    34  
    35  	// common middleware for all request
    36  	v1SubRouter.Use(middleware.LoggingMiddleware(logger))
    37  	v1SubRouter.Use(middleware.QueryExpandable())
    38  	v1SubRouter.Use(middleware.QuerySelect())
    39  	v1SubRouter.Use(middleware.MetricsMiddleware(restCollector))
    40  
    41  	return &RouterBuilder{
    42  		logger:      logger,
    43  		router:      router,
    44  		v1SubRouter: v1SubRouter,
    45  	}
    46  }
    47  
    48  // AddRestRoutes adds rest routes to the router.
    49  func (b *RouterBuilder) AddRestRoutes(backend access.API, chain flow.Chain) *RouterBuilder {
    50  	linkGenerator := models.NewLinkGeneratorImpl(b.v1SubRouter)
    51  	for _, r := range Routes {
    52  		h := NewHandler(b.logger, backend, r.Handler, linkGenerator, chain)
    53  		b.v1SubRouter.
    54  			Methods(r.Method).
    55  			Path(r.Pattern).
    56  			Name(r.Name).
    57  			Handler(h)
    58  	}
    59  	return b
    60  }
    61  
    62  // AddWsRoutes adds WebSocket routes to the router.
    63  func (b *RouterBuilder) AddWsRoutes(
    64  	stateStreamApi state_stream.API,
    65  	chain flow.Chain,
    66  	stateStreamConfig backend.Config,
    67  ) *RouterBuilder {
    68  
    69  	for _, r := range WSRoutes {
    70  		h := NewWSHandler(b.logger, stateStreamApi, r.Handler, chain, stateStreamConfig)
    71  		b.v1SubRouter.
    72  			Methods(r.Method).
    73  			Path(r.Pattern).
    74  			Name(r.Name).
    75  			Handler(h)
    76  	}
    77  
    78  	return b
    79  }
    80  
    81  func (b *RouterBuilder) Build() *mux.Router {
    82  	return b.router
    83  }
    84  
    85  type route struct {
    86  	Name    string
    87  	Method  string
    88  	Pattern string
    89  	Handler ApiHandlerFunc
    90  }
    91  
    92  type wsroute struct {
    93  	Name    string
    94  	Method  string
    95  	Pattern string
    96  	Handler SubscribeHandlerFunc
    97  }
    98  
    99  var Routes = []route{{
   100  	Method:  http.MethodGet,
   101  	Pattern: "/transactions/{id}",
   102  	Name:    "getTransactionByID",
   103  	Handler: GetTransactionByID,
   104  }, {
   105  	Method:  http.MethodPost,
   106  	Pattern: "/transactions",
   107  	Name:    "createTransaction",
   108  	Handler: CreateTransaction,
   109  }, {
   110  	Method:  http.MethodGet,
   111  	Pattern: "/transaction_results/{id}",
   112  	Name:    "getTransactionResultByID",
   113  	Handler: GetTransactionResultByID,
   114  }, {
   115  	Method:  http.MethodGet,
   116  	Pattern: "/blocks/{id}",
   117  	Name:    "getBlocksByIDs",
   118  	Handler: GetBlocksByIDs,
   119  }, {
   120  	Method:  http.MethodGet,
   121  	Pattern: "/blocks",
   122  	Name:    "getBlocksByHeight",
   123  	Handler: GetBlocksByHeight,
   124  }, {
   125  	Method:  http.MethodGet,
   126  	Pattern: "/blocks/{id}/payload",
   127  	Name:    "getBlockPayloadByID",
   128  	Handler: GetBlockPayloadByID,
   129  }, {
   130  	Method:  http.MethodGet,
   131  	Pattern: "/execution_results/{id}",
   132  	Name:    "getExecutionResultByID",
   133  	Handler: GetExecutionResultByID,
   134  }, {
   135  	Method:  http.MethodGet,
   136  	Pattern: "/execution_results",
   137  	Name:    "getExecutionResultByBlockID",
   138  	Handler: GetExecutionResultsByBlockIDs,
   139  }, {
   140  	Method:  http.MethodGet,
   141  	Pattern: "/collections/{id}",
   142  	Name:    "getCollectionByID",
   143  	Handler: GetCollectionByID,
   144  }, {
   145  	Method:  http.MethodPost,
   146  	Pattern: "/scripts",
   147  	Name:    "executeScript",
   148  	Handler: ExecuteScript,
   149  }, {
   150  	Method:  http.MethodGet,
   151  	Pattern: "/accounts/{address}",
   152  	Name:    "getAccount",
   153  	Handler: GetAccount,
   154  }, {
   155  	Method:  http.MethodGet,
   156  	Pattern: "/accounts/{address}/keys/{index}",
   157  	Name:    "getAccountKeyByIndex",
   158  	Handler: GetAccountKeyByIndex,
   159  }, {
   160  	Method:  http.MethodGet,
   161  	Pattern: "/events",
   162  	Name:    "getEvents",
   163  	Handler: GetEvents,
   164  }, {
   165  	Method:  http.MethodGet,
   166  	Pattern: "/network/parameters",
   167  	Name:    "getNetworkParameters",
   168  	Handler: GetNetworkParameters,
   169  }, {
   170  	Method:  http.MethodGet,
   171  	Pattern: "/node_version_info",
   172  	Name:    "getNodeVersionInfo",
   173  	Handler: GetNodeVersionInfo,
   174  }}
   175  
   176  var WSRoutes = []wsroute{{
   177  	Method:  http.MethodGet,
   178  	Pattern: "/subscribe_events",
   179  	Name:    "subscribeEvents",
   180  	Handler: SubscribeEvents,
   181  }}
   182  
   183  var routeUrlMap = map[string]string{}
   184  var routeRE = regexp.MustCompile(`(?i)/v1/(\w+)(/(\w+)(/(\w+))?)?`)
   185  
   186  func init() {
   187  	for _, r := range Routes {
   188  		routeUrlMap[r.Pattern] = r.Name
   189  	}
   190  	for _, r := range WSRoutes {
   191  		routeUrlMap[r.Pattern] = r.Name
   192  	}
   193  }
   194  
   195  func URLToRoute(url string) (string, error) {
   196  	normalized, err := normalizeURL(url)
   197  	if err != nil {
   198  		return "", err
   199  	}
   200  
   201  	name, ok := routeUrlMap[normalized]
   202  	if !ok {
   203  		return "", fmt.Errorf("invalid url")
   204  	}
   205  	return name, nil
   206  }
   207  
   208  func normalizeURL(url string) (string, error) {
   209  	matches := routeRE.FindAllStringSubmatch(url, -1)
   210  	if len(matches) != 1 || len(matches[0]) != 6 {
   211  		return "", fmt.Errorf("invalid url")
   212  	}
   213  
   214  	// given a URL like
   215  	//      /v1/blocks/1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef/payload
   216  	// groups  [  1  ] [                                3                             ] [  5  ]
   217  	// normalized form like /v1/blocks/{id}/payload
   218  
   219  	parts := []string{matches[0][1]}
   220  
   221  	switch len(matches[0][3]) {
   222  	case 0:
   223  		// top level resource. e.g. /v1/blocks
   224  	case 64:
   225  		// id based resource. e.g. /v1/blocks/1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef
   226  		parts = append(parts, "{id}")
   227  		if matches[0][5] != "" {
   228  			parts = append(parts, matches[0][5])
   229  		}
   230  	case 16:
   231  		// address based resource. e.g. /v1/accounts/1234567890abcdef
   232  		parts = append(parts, "{address}")
   233  		if matches[0][5] == "keys" {
   234  			parts = append(parts, "keys", "{index}")
   235  		}
   236  	default:
   237  		// named resource. e.g. /v1/network/parameters
   238  		parts = append(parts, matches[0][3])
   239  	}
   240  
   241  	return "/" + strings.Join(parts, "/"), nil
   242  }