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 }