github.com/ethersphere/bee/v2@v2.2.0/pkg/api/router.go (about) 1 // Copyright 2020 The Swarm Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package api 6 7 import ( 8 "expvar" 9 "fmt" 10 "net/http" 11 "net/http/pprof" 12 "strings" 13 14 "github.com/ethersphere/bee/v2/pkg/jsonhttp" 15 "github.com/ethersphere/bee/v2/pkg/log/httpaccess" 16 "github.com/ethersphere/bee/v2/pkg/swarm" 17 "github.com/gorilla/handlers" 18 "github.com/gorilla/mux" 19 "github.com/prometheus/client_golang/prometheus/promhttp" 20 "resenje.org/web" 21 ) 22 23 const ( 24 apiVersion = "v1" // Only one api version exists, this should be configurable with more. 25 rootPath = "/" + apiVersion 26 ) 27 28 func (s *Service) MountTechnicalDebug() { 29 router := mux.NewRouter() 30 router.NotFoundHandler = http.HandlerFunc(jsonhttp.NotFoundHandler) 31 s.router = router 32 33 s.mountTechnicalDebug() 34 35 s.Handler = web.ChainHandlers( 36 httpaccess.NewHTTPAccessLogHandler(s.logger, s.tracer, "api access"), 37 handlers.CompressHandler, 38 s.corsHandler, 39 web.NoCacheHeadersHandler, 40 web.FinalHandler(router), 41 ) 42 } 43 44 func (s *Service) MountDebug() { 45 s.mountBusinessDebug() 46 47 s.Handler = web.ChainHandlers( 48 httpaccess.NewHTTPAccessLogHandler(s.logger, s.tracer, "api access"), 49 handlers.CompressHandler, 50 s.corsHandler, 51 web.NoCacheHeadersHandler, 52 web.FinalHandler(s.router), 53 ) 54 } 55 56 func (s *Service) MountAPI() { 57 if s.router == nil { 58 s.router = mux.NewRouter() 59 s.router.NotFoundHandler = http.HandlerFunc(jsonhttp.NotFoundHandler) 60 } 61 62 s.mountAPI() 63 64 compressHandler := func(h http.Handler) http.Handler { 65 downloadEndpoints := []string{ 66 "/bzz", 67 "/bytes", 68 "/chunks", 69 rootPath + "/bzz", 70 rootPath + "/bytes", 71 rootPath + "/chunks", 72 } 73 74 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 75 // Skip compression for GET requests on download endpoints. 76 // This is done in order to preserve Content-Length header in response, 77 // because CompressHandler is always removing it. 78 if r.Method == http.MethodGet { 79 for _, endpoint := range downloadEndpoints { 80 if strings.HasPrefix(r.URL.Path, endpoint) { 81 h.ServeHTTP(w, r) 82 return 83 } 84 } 85 } 86 87 if r.Method == http.MethodHead { 88 h.ServeHTTP(w, r) 89 return 90 } 91 92 handlers.CompressHandler(h).ServeHTTP(w, r) 93 }) 94 } 95 96 s.Handler = web.ChainHandlers( 97 httpaccess.NewHTTPAccessLogHandler(s.logger, s.tracer, "api access"), 98 compressHandler, 99 s.responseCodeMetricsHandler, 100 s.pageviewMetricsHandler, 101 s.corsHandler, 102 web.FinalHandler(s.router), 103 ) 104 } 105 106 func (s *Service) mountTechnicalDebug() { 107 s.router.Handle("/node", jsonhttp.MethodHandler{ 108 "GET": http.HandlerFunc(s.nodeGetHandler), 109 }) 110 111 s.router.Handle("/addresses", jsonhttp.MethodHandler{ 112 "GET": http.HandlerFunc(s.addressesHandler), 113 }) 114 115 s.router.Handle("/chainstate", jsonhttp.MethodHandler{ 116 "GET": http.HandlerFunc(s.chainStateHandler), 117 }) 118 119 s.router.Handle("/debugstore", jsonhttp.MethodHandler{ 120 "GET": web.ChainHandlers( 121 httpaccess.NewHTTPAccessSuppressLogHandler(), 122 web.FinalHandlerFunc(s.debugStorage), 123 ), 124 }) 125 126 s.router.Path("/metrics").Handler(web.ChainHandlers( 127 httpaccess.NewHTTPAccessSuppressLogHandler(), 128 web.FinalHandler(promhttp.InstrumentMetricHandler( 129 s.metricsRegistry, 130 promhttp.HandlerFor(s.metricsRegistry, promhttp.HandlerOpts{}), 131 )), 132 )) 133 134 s.router.Handle("/debug/pprof", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 135 u := r.URL 136 u.Path += "/" 137 http.Redirect(w, r, u.String(), http.StatusPermanentRedirect) 138 })) 139 s.router.Handle("/debug/pprof/cmdline", http.HandlerFunc(pprof.Cmdline)) 140 s.router.Handle("/debug/pprof/profile", http.HandlerFunc(pprof.Profile)) 141 s.router.Handle("/debug/pprof/symbol", http.HandlerFunc(pprof.Symbol)) 142 s.router.Handle("/debug/pprof/trace", http.HandlerFunc(pprof.Trace)) 143 144 s.router.PathPrefix("/debug/pprof/").Handler(http.HandlerFunc(pprof.Index)) 145 s.router.Handle("/debug/vars", expvar.Handler()) 146 147 s.router.Handle("/loggers", jsonhttp.MethodHandler{ 148 "GET": web.ChainHandlers( 149 httpaccess.NewHTTPAccessSuppressLogHandler(), 150 web.FinalHandlerFunc(s.loggerGetHandler), 151 ), 152 }) 153 s.router.Handle("/loggers/{exp}", jsonhttp.MethodHandler{ 154 "GET": web.ChainHandlers( 155 httpaccess.NewHTTPAccessSuppressLogHandler(), 156 web.FinalHandlerFunc(s.loggerGetHandler), 157 ), 158 }) 159 s.router.Handle("/loggers/{exp}/{verbosity}", jsonhttp.MethodHandler{ 160 "PUT": web.ChainHandlers( 161 httpaccess.NewHTTPAccessSuppressLogHandler(), 162 web.FinalHandlerFunc(s.loggerSetVerbosityHandler), 163 ), 164 }) 165 166 s.router.Handle("/readiness", web.ChainHandlers( 167 httpaccess.NewHTTPAccessSuppressLogHandler(), 168 web.FinalHandlerFunc(s.readinessHandler), 169 )) 170 171 s.router.Handle("/health", web.ChainHandlers( 172 httpaccess.NewHTTPAccessSuppressLogHandler(), 173 web.FinalHandlerFunc(s.healthHandler), 174 )) 175 } 176 177 func (s *Service) mountAPI() { 178 subdomainRouter := s.router.Host("{subdomain:.*}.swarm.localhost").Subrouter() 179 180 subdomainRouter.Handle("/{path:.*}", jsonhttp.MethodHandler{ 181 "GET": web.ChainHandlers( 182 web.FinalHandlerFunc(s.subdomainHandler), 183 ), 184 }) 185 186 s.router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 187 fmt.Fprintln(w, "Ethereum Swarm Bee") 188 }) 189 190 s.router.HandleFunc("/robots.txt", func(w http.ResponseWriter, r *http.Request) { 191 fmt.Fprintln(w, "User-agent: *\nDisallow: /") 192 }) 193 194 // handle is a helper closure which simplifies the router setup. 195 handle := func(path string, handler http.Handler) { 196 s.router.Handle(path, handler) 197 s.router.Handle(rootPath+path, handler) 198 } 199 200 handle("/bytes", jsonhttp.MethodHandler{ 201 "POST": web.ChainHandlers( 202 s.contentLengthMetricMiddleware(), 203 s.newTracingHandler("bytes-upload"), 204 web.FinalHandlerFunc(s.bytesUploadHandler), 205 ), 206 }) 207 208 handle("/bytes/{address}", jsonhttp.MethodHandler{ 209 "GET": web.ChainHandlers( 210 s.contentLengthMetricMiddleware(), 211 s.newTracingHandler("bytes-download"), 212 s.actDecryptionHandler(), 213 web.FinalHandlerFunc(s.bytesGetHandler), 214 ), 215 "HEAD": web.ChainHandlers( 216 s.newTracingHandler("bytes-head"), 217 s.actDecryptionHandler(), 218 web.FinalHandlerFunc(s.bytesHeadHandler), 219 ), 220 }) 221 222 handle("/chunks", jsonhttp.MethodHandler{ 223 "POST": web.ChainHandlers( 224 jsonhttp.NewMaxBodyBytesHandler(swarm.SocMaxChunkSize), 225 web.FinalHandlerFunc(s.chunkUploadHandler), 226 ), 227 }) 228 229 handle("/chunks/stream", web.ChainHandlers( 230 s.newTracingHandler("chunks-stream-upload"), 231 web.FinalHandlerFunc(s.chunkUploadStreamHandler), 232 )) 233 234 handle("/chunks/{address}", jsonhttp.MethodHandler{ 235 "GET": web.ChainHandlers( 236 s.actDecryptionHandler(), 237 web.FinalHandlerFunc(s.chunkGetHandler), 238 ), 239 "HEAD": web.ChainHandlers( 240 s.actDecryptionHandler(), 241 web.FinalHandlerFunc(s.hasChunkHandler), 242 ), 243 }) 244 245 handle("/envelope/{address}", jsonhttp.MethodHandler{ 246 "POST": http.HandlerFunc(s.envelopePostHandler), 247 }) 248 249 handle("/soc/{owner}/{id}", jsonhttp.MethodHandler{ 250 "POST": web.ChainHandlers( 251 jsonhttp.NewMaxBodyBytesHandler(swarm.ChunkWithSpanSize), 252 web.FinalHandlerFunc(s.socUploadHandler), 253 ), 254 }) 255 256 handle("/feeds/{owner}/{topic}", jsonhttp.MethodHandler{ 257 "GET": http.HandlerFunc(s.feedGetHandler), 258 "POST": web.ChainHandlers( 259 jsonhttp.NewMaxBodyBytesHandler(swarm.ChunkWithSpanSize), 260 web.FinalHandlerFunc(s.feedPostHandler), 261 ), 262 }) 263 264 handle("/bzz", jsonhttp.MethodHandler{ 265 "POST": web.ChainHandlers( 266 s.contentLengthMetricMiddleware(), 267 s.newTracingHandler("bzz-upload"), 268 web.FinalHandlerFunc(s.bzzUploadHandler), 269 ), 270 }) 271 272 handle("/grantee", jsonhttp.MethodHandler{ 273 "POST": web.ChainHandlers( 274 web.FinalHandlerFunc(s.actCreateGranteesHandler), 275 ), 276 }) 277 278 handle("/grantee/{address}", jsonhttp.MethodHandler{ 279 "GET": web.ChainHandlers( 280 web.FinalHandlerFunc(s.actListGranteesHandler), 281 ), 282 "PATCH": web.ChainHandlers( 283 web.FinalHandlerFunc(s.actGrantRevokeHandler), 284 ), 285 }) 286 287 handle("/bzz/{address}", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 288 u := r.URL 289 u.Path += "/" 290 http.Redirect(w, r, u.String(), http.StatusPermanentRedirect) 291 })) 292 293 handle("/bzz/{address}/{path:.*}", jsonhttp.MethodHandler{ 294 "GET": web.ChainHandlers( 295 s.contentLengthMetricMiddleware(), 296 s.newTracingHandler("bzz-download"), 297 s.actDecryptionHandler(), 298 web.FinalHandlerFunc(s.bzzDownloadHandler), 299 ), 300 "HEAD": web.ChainHandlers( 301 s.actDecryptionHandler(), 302 web.FinalHandlerFunc(s.bzzHeadHandler), 303 ), 304 }) 305 306 handle("/pss/send/{topic}/{targets}", web.ChainHandlers( 307 web.FinalHandler(jsonhttp.MethodHandler{ 308 "POST": web.ChainHandlers( 309 jsonhttp.NewMaxBodyBytesHandler(swarm.ChunkSize), 310 web.FinalHandlerFunc(s.pssPostHandler), 311 ), 312 })), 313 ) 314 315 handle("/pss/subscribe/{topic}", web.ChainHandlers( 316 web.FinalHandlerFunc(s.pssWsHandler), 317 )) 318 319 handle("/tags", web.ChainHandlers( 320 web.FinalHandler(jsonhttp.MethodHandler{ 321 "GET": http.HandlerFunc(s.listTagsHandler), 322 "POST": web.ChainHandlers( 323 jsonhttp.NewMaxBodyBytesHandler(1024), 324 web.FinalHandlerFunc(s.createTagHandler), 325 ), 326 })), 327 ) 328 329 handle("/tags/{id}", web.ChainHandlers( 330 web.FinalHandler(jsonhttp.MethodHandler{ 331 "GET": http.HandlerFunc(s.getTagHandler), 332 "DELETE": http.HandlerFunc(s.deleteTagHandler), 333 "PATCH": web.ChainHandlers( 334 jsonhttp.NewMaxBodyBytesHandler(1024), 335 web.FinalHandlerFunc(s.doneSplitHandler), 336 ), 337 })), 338 ) 339 340 handle("/pins", web.ChainHandlers( 341 web.FinalHandler(jsonhttp.MethodHandler{ 342 "GET": http.HandlerFunc(s.listPinnedRootHashes), 343 })), 344 ) 345 346 handle("/pins/check", web.ChainHandlers( 347 web.FinalHandler(jsonhttp.MethodHandler{ 348 "GET": http.HandlerFunc(s.pinIntegrityHandler), 349 }), 350 )) 351 352 handle("/pins/{reference}", web.ChainHandlers( 353 web.FinalHandler(jsonhttp.MethodHandler{ 354 "GET": http.HandlerFunc(s.getPinnedRootHash), 355 "POST": http.HandlerFunc(s.pinRootHash), 356 "DELETE": http.HandlerFunc(s.unpinRootHash), 357 })), 358 ) 359 360 handle("/stewardship/{address}", jsonhttp.MethodHandler{ 361 "GET": web.ChainHandlers( 362 web.FinalHandlerFunc(s.stewardshipGetHandler), 363 ), 364 "PUT": web.ChainHandlers( 365 web.FinalHandlerFunc(s.stewardshipPutHandler), 366 ), 367 }) 368 369 handle("/readiness", web.ChainHandlers( 370 httpaccess.NewHTTPAccessSuppressLogHandler(), 371 web.FinalHandlerFunc(s.readinessHandler), 372 )) 373 374 handle("/health", web.ChainHandlers( 375 httpaccess.NewHTTPAccessSuppressLogHandler(), 376 web.FinalHandlerFunc(s.healthHandler), 377 )) 378 } 379 380 func (s *Service) mountBusinessDebug() { 381 handle := func(path string, handler http.Handler) { 382 s.router.Handle(path, handler) 383 s.router.Handle(rootPath+path, handler) 384 } 385 386 if s.transaction != nil { 387 handle("/transactions", jsonhttp.MethodHandler{ 388 "GET": http.HandlerFunc(s.transactionListHandler), 389 }) 390 handle("/transactions/{hash}", jsonhttp.MethodHandler{ 391 "GET": http.HandlerFunc(s.transactionDetailHandler), 392 "POST": http.HandlerFunc(s.transactionResendHandler), 393 "DELETE": http.HandlerFunc(s.transactionCancelHandler), 394 }) 395 } 396 397 handle("/peers", jsonhttp.MethodHandler{ 398 "GET": http.HandlerFunc(s.peersHandler), 399 }) 400 401 handle("/pingpong/{address}", jsonhttp.MethodHandler{ 402 "POST": http.HandlerFunc(s.pingpongHandler), 403 }) 404 405 handle("/reservestate", jsonhttp.MethodHandler{ 406 "GET": http.HandlerFunc(s.reserveStateHandler), 407 }) 408 409 handle("/connect/{multi-address:.+}", jsonhttp.MethodHandler{ 410 "POST": http.HandlerFunc(s.peerConnectHandler), 411 }) 412 413 handle("/blocklist", jsonhttp.MethodHandler{ 414 "GET": http.HandlerFunc(s.blocklistedPeersHandler), 415 }) 416 417 handle("/peers/{address}", jsonhttp.MethodHandler{ 418 "DELETE": http.HandlerFunc(s.peerDisconnectHandler), 419 }) 420 421 //handle("/chunks/{address}", jsonhttp.MethodHandler{ 422 // "GET": http.HandlerFunc(s.hasChunkHandler), 423 //}) 424 425 handle("/topology", jsonhttp.MethodHandler{ 426 "GET": http.HandlerFunc(s.topologyHandler), 427 }) 428 429 handle("/welcome-message", jsonhttp.MethodHandler{ 430 "GET": http.HandlerFunc(s.getWelcomeMessageHandler), 431 "POST": web.ChainHandlers( 432 jsonhttp.NewMaxBodyBytesHandler(welcomeMessageMaxRequestSize), 433 web.FinalHandlerFunc(s.setWelcomeMessageHandler), 434 ), 435 }) 436 437 handle("/balances", jsonhttp.MethodHandler{ 438 "GET": http.HandlerFunc(s.compensatedBalancesHandler), 439 }) 440 441 handle("/balances/{peer}", jsonhttp.MethodHandler{ 442 "GET": http.HandlerFunc(s.compensatedPeerBalanceHandler), 443 }) 444 445 handle("/consumed", jsonhttp.MethodHandler{ 446 "GET": http.HandlerFunc(s.balancesHandler), 447 }) 448 449 handle("/consumed/{peer}", jsonhttp.MethodHandler{ 450 "GET": http.HandlerFunc(s.peerBalanceHandler), 451 }) 452 453 handle("/timesettlements", jsonhttp.MethodHandler{ 454 "GET": http.HandlerFunc(s.settlementsHandlerPseudosettle), 455 }) 456 457 if s.swapEnabled { 458 handle("/settlements", jsonhttp.MethodHandler{ 459 "GET": http.HandlerFunc(s.settlementsHandler), 460 }) 461 462 handle("/settlements/{peer}", jsonhttp.MethodHandler{ 463 "GET": http.HandlerFunc(s.peerSettlementsHandler), 464 }) 465 466 handle("/chequebook/cheque/{peer}", jsonhttp.MethodHandler{ 467 "GET": http.HandlerFunc(s.chequebookLastPeerHandler), 468 }) 469 470 handle("/chequebook/cheque", jsonhttp.MethodHandler{ 471 "GET": http.HandlerFunc(s.chequebookAllLastHandler), 472 }) 473 474 handle("/chequebook/cashout/{peer}", jsonhttp.MethodHandler{ 475 "GET": http.HandlerFunc(s.swapCashoutStatusHandler), 476 "POST": web.ChainHandlers( 477 s.gasConfigMiddleware("swap cashout"), 478 web.FinalHandlerFunc(s.swapCashoutHandler), 479 ), 480 }) 481 } 482 483 if s.chequebookEnabled { 484 handle("/chequebook/balance", jsonhttp.MethodHandler{ 485 "GET": http.HandlerFunc(s.chequebookBalanceHandler), 486 }) 487 488 handle("/chequebook/address", jsonhttp.MethodHandler{ 489 "GET": http.HandlerFunc(s.chequebookAddressHandler), 490 }) 491 492 handle("/chequebook/deposit", jsonhttp.MethodHandler{ 493 "POST": web.ChainHandlers( 494 s.gasConfigMiddleware("chequebook deposit"), 495 web.FinalHandlerFunc(s.chequebookDepositHandler), 496 ), 497 }) 498 499 handle("/chequebook/withdraw", jsonhttp.MethodHandler{ 500 "POST": web.ChainHandlers( 501 s.gasConfigMiddleware("chequebook withdraw"), 502 web.FinalHandlerFunc(s.chequebookWithdrawHandler), 503 ), 504 }) 505 506 if s.swapEnabled { 507 handle("/wallet", jsonhttp.MethodHandler{ 508 "GET": http.HandlerFunc(s.walletHandler), 509 }) 510 handle("/wallet/withdraw/{coin}", jsonhttp.MethodHandler{ 511 "POST": web.ChainHandlers( 512 s.gasConfigMiddleware("wallet withdraw"), 513 web.FinalHandlerFunc(s.walletWithdrawHandler), 514 ), 515 }) 516 } 517 } 518 519 handle("/stamps", web.ChainHandlers( 520 s.postageSyncStatusCheckHandler, 521 web.FinalHandler(jsonhttp.MethodHandler{ 522 "GET": http.HandlerFunc(s.postageGetStampsHandler), 523 })), 524 ) 525 526 handle("/stamps/{batch_id}", web.ChainHandlers( 527 s.postageSyncStatusCheckHandler, 528 web.FinalHandler(jsonhttp.MethodHandler{ 529 "GET": http.HandlerFunc(s.postageGetStampHandler), 530 })), 531 ) 532 533 handle("/stamps/{batch_id}/buckets", web.ChainHandlers( 534 s.postageSyncStatusCheckHandler, 535 web.FinalHandler(jsonhttp.MethodHandler{ 536 "GET": http.HandlerFunc(s.postageGetStampBucketsHandler), 537 })), 538 ) 539 540 handle("/stamps/{amount}/{depth}", web.ChainHandlers( 541 s.postageAccessHandler, 542 s.postageSyncStatusCheckHandler, 543 s.gasConfigMiddleware("create batch"), 544 web.FinalHandler(jsonhttp.MethodHandler{ 545 "POST": http.HandlerFunc(s.postageCreateHandler), 546 })), 547 ) 548 549 handle("/stamps/topup/{batch_id}/{amount}", web.ChainHandlers( 550 s.postageAccessHandler, 551 s.postageSyncStatusCheckHandler, 552 s.gasConfigMiddleware("topup batch"), 553 web.FinalHandler(jsonhttp.MethodHandler{ 554 "PATCH": http.HandlerFunc(s.postageTopUpHandler), 555 })), 556 ) 557 558 handle("/stamps/dilute/{batch_id}/{depth}", web.ChainHandlers( 559 s.postageAccessHandler, 560 s.postageSyncStatusCheckHandler, 561 s.gasConfigMiddleware("dilute batch"), 562 web.FinalHandler(jsonhttp.MethodHandler{ 563 "PATCH": http.HandlerFunc(s.postageDiluteHandler), 564 })), 565 ) 566 567 handle("/batches", web.ChainHandlers( 568 web.FinalHandler(jsonhttp.MethodHandler{ 569 "GET": http.HandlerFunc(s.postageGetAllBatchesHandler), 570 })), 571 ) 572 573 handle("/accounting", jsonhttp.MethodHandler{ 574 "GET": http.HandlerFunc(s.accountingInfoHandler), 575 }) 576 577 handle("/readiness", web.ChainHandlers( 578 httpaccess.NewHTTPAccessSuppressLogHandler(), 579 web.FinalHandlerFunc(s.readinessHandler), 580 )) 581 582 handle("/health", web.ChainHandlers( 583 httpaccess.NewHTTPAccessSuppressLogHandler(), 584 web.FinalHandlerFunc(s.healthHandler), 585 )) 586 587 handle("/stake/withdrawable", web.ChainHandlers( 588 s.stakingAccessHandler, 589 s.gasConfigMiddleware("get or withdraw withdrawable stake"), 590 web.FinalHandler(jsonhttp.MethodHandler{ 591 "GET": http.HandlerFunc(s.getWithdrawableStakeHandler), 592 "DELETE": http.HandlerFunc(s.withdrawStakeHandler), 593 })), 594 ) 595 596 handle("/stake/{amount}", web.ChainHandlers( 597 s.stakingAccessHandler, 598 s.gasConfigMiddleware("deposit stake"), 599 web.FinalHandler(jsonhttp.MethodHandler{ 600 "POST": http.HandlerFunc(s.stakingDepositHandler), 601 }), 602 )) 603 604 handle("/stake", web.ChainHandlers( 605 s.stakingAccessHandler, 606 s.gasConfigMiddleware("get or migrate stake"), 607 web.FinalHandler(jsonhttp.MethodHandler{ 608 "GET": http.HandlerFunc(s.getPotentialStake), 609 "DELETE": http.HandlerFunc(s.migrateStakeHandler), 610 })), 611 ) 612 613 handle("/redistributionstate", jsonhttp.MethodHandler{ 614 "GET": http.HandlerFunc(s.redistributionStatusHandler), 615 }) 616 617 handle("/status", jsonhttp.MethodHandler{ 618 "GET": web.ChainHandlers( 619 httpaccess.NewHTTPAccessSuppressLogHandler(), 620 web.FinalHandlerFunc(s.statusGetHandler), 621 ), 622 }) 623 624 handle("/status/peers", jsonhttp.MethodHandler{ 625 "GET": web.ChainHandlers( 626 httpaccess.NewHTTPAccessSuppressLogHandler(), 627 s.statusAccessHandler, 628 web.FinalHandlerFunc(s.statusGetPeersHandler), 629 ), 630 }) 631 632 handle("/rchash/{depth}/{anchor1}/{anchor2}", web.ChainHandlers( 633 web.FinalHandler(jsonhttp.MethodHandler{ 634 "GET": http.HandlerFunc(s.rchash), 635 }), 636 )) 637 }