s/backend/middleware
This commit is contained in:
parent
c7b17d3195
commit
c9fe95b103
7 changed files with 142 additions and 169 deletions
|
@ -1,122 +0,0 @@
|
||||||
// Copyright 2016 Jimmy Zelinskie
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
// Package backend implements the TrackerLogic interface by executing
|
|
||||||
// a series of middleware hooks.
|
|
||||||
package backend
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/jzelinskie/trakr/bittorrent"
|
|
||||||
"github.com/jzelinskie/trakr/frontend"
|
|
||||||
)
|
|
||||||
|
|
||||||
type BackendConfig struct {
|
|
||||||
AnnounceInterval time.Duration `yaml:"announce_interval"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ frontend.TrackerLogic = &Backend{}
|
|
||||||
|
|
||||||
func New(config BackendConfig, peerStore PeerStore, announcePreHooks, announcePostHooks, scrapePreHooks, scrapePostHooks []Hook) (*Backend, error) {
|
|
||||||
toReturn := &Backend{
|
|
||||||
announceInterval: config.AnnounceInterval,
|
|
||||||
peerStore: peerStore,
|
|
||||||
announcePreHooks: announcePreHooks,
|
|
||||||
announcePostHooks: announcePostHooks,
|
|
||||||
scrapePreHooks: scrapePreHooks,
|
|
||||||
scrapePostHooks: scrapePostHooks,
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(toReturn.announcePreHooks) == 0 {
|
|
||||||
toReturn.announcePreHooks = []Hook{nopHook{}}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(toReturn.announcePostHooks) == 0 {
|
|
||||||
toReturn.announcePostHooks = []Hook{nopHook{}}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(toReturn.scrapePreHooks) == 0 {
|
|
||||||
toReturn.scrapePreHooks = []Hook{nopHook{}}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(toReturn.scrapePostHooks) == 0 {
|
|
||||||
toReturn.scrapePostHooks = []Hook{nopHook{}}
|
|
||||||
}
|
|
||||||
|
|
||||||
return toReturn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Backend is a protocol-agnostic backend of a BitTorrent tracker.
|
|
||||||
type Backend struct {
|
|
||||||
announceInterval time.Duration
|
|
||||||
peerStore PeerStore
|
|
||||||
announcePreHooks []Hook
|
|
||||||
announcePostHooks []Hook
|
|
||||||
scrapePreHooks []Hook
|
|
||||||
scrapePostHooks []Hook
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleAnnounce generates a response for an Announce.
|
|
||||||
func (b *Backend) HandleAnnounce(ctx context.Context, req *bittorrent.AnnounceRequest) (*bittorrent.AnnounceResponse, error) {
|
|
||||||
resp := &bittorrent.AnnounceResponse{
|
|
||||||
Interval: b.announceInterval,
|
|
||||||
}
|
|
||||||
for _, h := range b.announcePreHooks {
|
|
||||||
if err := h.HandleAnnounce(ctx, req, resp); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return resp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AfterAnnounce does something with the results of an Announce after it has
|
|
||||||
// been completed.
|
|
||||||
func (b *Backend) AfterAnnounce(ctx context.Context, req *bittorrent.AnnounceRequest, resp *bittorrent.AnnounceResponse) {
|
|
||||||
for _, h := range b.announcePostHooks {
|
|
||||||
if err := h.HandleAnnounce(ctx, req, resp); err != nil {
|
|
||||||
log.Println("trakr: post-announce hooks failed:", err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleScrape generates a response for a Scrape.
|
|
||||||
func (b *Backend) HandleScrape(ctx context.Context, req *bittorrent.ScrapeRequest) (*bittorrent.ScrapeResponse, error) {
|
|
||||||
resp := &bittorrent.ScrapeResponse{
|
|
||||||
Files: make(map[bittorrent.InfoHash]bittorrent.Scrape),
|
|
||||||
}
|
|
||||||
for _, h := range b.scrapePreHooks {
|
|
||||||
if err := h.HandleScrape(ctx, req, resp); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return resp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AfterScrape does something with the results of a Scrape after it has been
|
|
||||||
// completed.
|
|
||||||
func (b *Backend) AfterScrape(ctx context.Context, req *bittorrent.ScrapeRequest, resp *bittorrent.ScrapeResponse) {
|
|
||||||
for _, h := range b.scrapePostHooks {
|
|
||||||
if err := h.HandleScrape(ctx, req, resp); err != nil {
|
|
||||||
log.Println("trakr: post-scrape hooks failed:", err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -14,17 +14,17 @@ import (
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
|
|
||||||
"github.com/jzelinskie/trakr/backend"
|
|
||||||
httpfrontend "github.com/jzelinskie/trakr/frontend/http"
|
httpfrontend "github.com/jzelinskie/trakr/frontend/http"
|
||||||
udpfrontend "github.com/jzelinskie/trakr/frontend/udp"
|
udpfrontend "github.com/jzelinskie/trakr/frontend/udp"
|
||||||
|
"github.com/jzelinskie/trakr/middleware"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ConfigFile struct {
|
type ConfigFile struct {
|
||||||
Config struct {
|
MainConfigBlock struct {
|
||||||
PrometheusAddr string `yaml:"prometheus_addr"`
|
PrometheusAddr string `yaml:"prometheus_addr"`
|
||||||
backend.BackendConfig
|
HTTPConfig httpfrontend.Config `yaml:"http"`
|
||||||
HTTPConfig httpfrontend.Config `yaml:"http"`
|
UDPConfig udpfrontend.Config `yaml:"udp"`
|
||||||
UDPConfig udpfrontend.Config `yaml:"udp"`
|
middleware.Config
|
||||||
} `yaml:"trakr"`
|
} `yaml:"trakr"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,13 +81,14 @@ func main() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("failed to read config: " + err.Error())
|
return errors.New("failed to read config: " + err.Error())
|
||||||
}
|
}
|
||||||
|
cfg := configFile.MainConfigBlock
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
promServer := http.Server{
|
promServer := http.Server{
|
||||||
Addr: configFile.Config.PrometheusAddr,
|
Addr: cfg.PrometheusAddr,
|
||||||
Handler: prometheus.Handler(),
|
Handler: prometheus.Handler(),
|
||||||
}
|
}
|
||||||
log.Println("started serving prometheus stats on", configFile.Config.PrometheusAddr)
|
log.Println("started serving prometheus stats on", cfg.PrometheusAddr)
|
||||||
if err := promServer.ListenAndServe(); err != nil {
|
if err := promServer.ListenAndServe(); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -95,7 +96,7 @@ func main() {
|
||||||
|
|
||||||
// TODO create PeerStore
|
// TODO create PeerStore
|
||||||
// TODO create Hooks
|
// TODO create Hooks
|
||||||
trackerBackend, err := backend.New(configFile.Config.BackendConfig, nil, nil, nil, nil, nil)
|
logic := middleware.NewLogic(cfg.Config, nil, nil, nil, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -106,24 +107,24 @@ func main() {
|
||||||
var hFrontend *httpfrontend.Frontend
|
var hFrontend *httpfrontend.Frontend
|
||||||
var uFrontend *udpfrontend.Frontend
|
var uFrontend *udpfrontend.Frontend
|
||||||
|
|
||||||
if configFile.Config.HTTPConfig.Addr != "" {
|
if cfg.HTTPConfig.Addr != "" {
|
||||||
// TODO get the real TrackerLogic
|
// TODO get the real TrackerLogic
|
||||||
hFrontend = httpfrontend.NewFrontend(trackerBackend, configFile.Config.HTTPConfig)
|
hFrontend = httpfrontend.NewFrontend(logic, cfg.HTTPConfig)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
log.Println("started serving HTTP on", configFile.Config.HTTPConfig.Addr)
|
log.Println("started serving HTTP on", cfg.HTTPConfig.Addr)
|
||||||
if err := hFrontend.ListenAndServe(); err != nil {
|
if err := hFrontend.ListenAndServe(); err != nil {
|
||||||
errChan <- errors.New("failed to cleanly shutdown HTTP frontend: " + err.Error())
|
errChan <- errors.New("failed to cleanly shutdown HTTP frontend: " + err.Error())
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
if configFile.Config.UDPConfig.Addr != "" {
|
if cfg.UDPConfig.Addr != "" {
|
||||||
// TODO get the real TrackerLogic
|
// TODO get the real TrackerLogic
|
||||||
uFrontend = udpfrontend.NewFrontend(trackerBackend, configFile.Config.UDPConfig)
|
uFrontend = udpfrontend.NewFrontend(logic, cfg.UDPConfig)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
log.Println("started serving UDP on", configFile.Config.UDPConfig.Addr)
|
log.Println("started serving UDP on", cfg.UDPConfig.Addr)
|
||||||
if err := uFrontend.ListenAndServe(); err != nil {
|
if err := uFrontend.ListenAndServe(); err != nil {
|
||||||
errChan <- errors.New("failed to cleanly shutdown UDP frontend: " + err.Error())
|
errChan <- errors.New("failed to cleanly shutdown UDP frontend: " + err.Error())
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,15 +71,15 @@ type Config struct {
|
||||||
type Frontend struct {
|
type Frontend struct {
|
||||||
grace *graceful.Server
|
grace *graceful.Server
|
||||||
|
|
||||||
backend frontend.TrackerLogic
|
logic frontend.TrackerLogic
|
||||||
Config
|
Config
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFrontend allocates a new instance of a Frontend.
|
// NewFrontend allocates a new instance of a Frontend.
|
||||||
func NewFrontend(backend frontend.TrackerLogic, cfg Config) *Frontend {
|
func NewFrontend(logic frontend.TrackerLogic, cfg Config) *Frontend {
|
||||||
return &Frontend{
|
return &Frontend{
|
||||||
backend: backend,
|
logic: logic,
|
||||||
Config: cfg,
|
Config: cfg,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,7 +150,7 @@ func (t *Frontend) announceRoute(w http.ResponseWriter, r *http.Request, _ httpr
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := t.backend.HandleAnnounce(context.TODO(), req)
|
resp, err := t.logic.HandleAnnounce(context.TODO(), req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
WriteError(w, err)
|
WriteError(w, err)
|
||||||
return
|
return
|
||||||
|
@ -162,7 +162,7 @@ func (t *Frontend) announceRoute(w http.ResponseWriter, r *http.Request, _ httpr
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
go t.backend.AfterAnnounce(context.TODO(), req, resp)
|
go t.logic.AfterAnnounce(context.TODO(), req, resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
// scrapeRoute parses and responds to a Scrape by using t.TrackerLogic.
|
// scrapeRoute parses and responds to a Scrape by using t.TrackerLogic.
|
||||||
|
@ -177,7 +177,7 @@ func (t *Frontend) scrapeRoute(w http.ResponseWriter, r *http.Request, _ httprou
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := t.backend.HandleScrape(context.TODO(), req)
|
resp, err := t.logic.HandleScrape(context.TODO(), req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
WriteError(w, err)
|
WriteError(w, err)
|
||||||
return
|
return
|
||||||
|
@ -189,5 +189,5 @@ func (t *Frontend) scrapeRoute(w http.ResponseWriter, r *http.Request, _ httprou
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
go t.backend.AfterScrape(context.TODO(), req, resp)
|
go t.logic.AfterScrape(context.TODO(), req, resp)
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,15 +74,15 @@ type Frontend struct {
|
||||||
closing chan struct{}
|
closing chan struct{}
|
||||||
wg sync.WaitGroup
|
wg sync.WaitGroup
|
||||||
|
|
||||||
backend frontend.TrackerLogic
|
logic frontend.TrackerLogic
|
||||||
Config
|
Config
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFrontend allocates a new instance of a Frontend.
|
// NewFrontend allocates a new instance of a Frontend.
|
||||||
func NewFrontend(backend frontend.TrackerLogic, cfg Config) *Frontend {
|
func NewFrontend(logic frontend.TrackerLogic, cfg Config) *Frontend {
|
||||||
return &Frontend{
|
return &Frontend{
|
||||||
closing: make(chan struct{}),
|
closing: make(chan struct{}),
|
||||||
backend: backend,
|
logic: logic,
|
||||||
Config: cfg,
|
Config: cfg,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -221,7 +221,7 @@ func (t *Frontend) handleRequest(r Request, w ResponseWriter) (response []byte,
|
||||||
}
|
}
|
||||||
|
|
||||||
var resp *bittorrent.AnnounceResponse
|
var resp *bittorrent.AnnounceResponse
|
||||||
resp, err = t.backend.HandleAnnounce(context.TODO(), req)
|
resp, err = t.logic.HandleAnnounce(context.TODO(), req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
WriteError(w, txID, err)
|
WriteError(w, txID, err)
|
||||||
return
|
return
|
||||||
|
@ -229,8 +229,7 @@ func (t *Frontend) handleRequest(r Request, w ResponseWriter) (response []byte,
|
||||||
|
|
||||||
WriteAnnounce(w, txID, resp)
|
WriteAnnounce(w, txID, resp)
|
||||||
|
|
||||||
// TODO(mrd0ll4r): evaluate if it's worth spawning another goroutine.
|
go t.logic.AfterAnnounce(context.TODO(), req, resp)
|
||||||
go t.backend.AfterAnnounce(context.TODO(), req, resp)
|
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -245,7 +244,7 @@ func (t *Frontend) handleRequest(r Request, w ResponseWriter) (response []byte,
|
||||||
}
|
}
|
||||||
|
|
||||||
var resp *bittorrent.ScrapeResponse
|
var resp *bittorrent.ScrapeResponse
|
||||||
resp, err = t.backend.HandleScrape(context.TODO(), req)
|
resp, err = t.logic.HandleScrape(context.TODO(), req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
WriteError(w, txID, err)
|
WriteError(w, txID, err)
|
||||||
return
|
return
|
||||||
|
@ -253,7 +252,7 @@ func (t *Frontend) handleRequest(r Request, w ResponseWriter) (response []byte,
|
||||||
|
|
||||||
WriteScrape(w, txID, resp)
|
WriteScrape(w, txID, resp)
|
||||||
|
|
||||||
go t.backend.AfterScrape(context.TODO(), req, resp)
|
go t.logic.AfterScrape(context.TODO(), req, resp)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
|
@ -1,18 +1,4 @@
|
||||||
// Copyright 2016 Jimmy Zelinskie
|
package middleware
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package backend
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
109
middleware/middleware.go
Normal file
109
middleware/middleware.go
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
// Package middleware implements the TrackerLogic interface by executing
|
||||||
|
// a series of middleware hooks.
|
||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
|
||||||
|
"github.com/jzelinskie/trakr/bittorrent"
|
||||||
|
"github.com/jzelinskie/trakr/frontend"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
AnnounceInterval time.Duration `yaml:"announce_interval"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ frontend.TrackerLogic = &Logic{}
|
||||||
|
|
||||||
|
func NewLogic(config Config, peerStore PeerStore, announcePreHooks, announcePostHooks, scrapePreHooks, scrapePostHooks []Hook) *Logic {
|
||||||
|
l := &Logic{
|
||||||
|
announceInterval: config.AnnounceInterval,
|
||||||
|
peerStore: peerStore,
|
||||||
|
announcePreHooks: announcePreHooks,
|
||||||
|
announcePostHooks: announcePostHooks,
|
||||||
|
scrapePreHooks: scrapePreHooks,
|
||||||
|
scrapePostHooks: scrapePostHooks,
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(l.announcePreHooks) == 0 {
|
||||||
|
l.announcePreHooks = []Hook{nopHook{}}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(l.announcePostHooks) == 0 {
|
||||||
|
l.announcePostHooks = []Hook{nopHook{}}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(l.scrapePreHooks) == 0 {
|
||||||
|
l.scrapePreHooks = []Hook{nopHook{}}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(l.scrapePostHooks) == 0 {
|
||||||
|
l.scrapePostHooks = []Hook{nopHook{}}
|
||||||
|
}
|
||||||
|
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logic is an implementation of the TrackerLogic that functions by
|
||||||
|
// executing a series of middleware hooks.
|
||||||
|
type Logic struct {
|
||||||
|
announceInterval time.Duration
|
||||||
|
peerStore PeerStore
|
||||||
|
announcePreHooks []Hook
|
||||||
|
announcePostHooks []Hook
|
||||||
|
scrapePreHooks []Hook
|
||||||
|
scrapePostHooks []Hook
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleAnnounce generates a response for an Announce.
|
||||||
|
func (l *Logic) HandleAnnounce(ctx context.Context, req *bittorrent.AnnounceRequest) (*bittorrent.AnnounceResponse, error) {
|
||||||
|
resp := &bittorrent.AnnounceResponse{
|
||||||
|
Interval: l.announceInterval,
|
||||||
|
}
|
||||||
|
for _, h := range l.announcePreHooks {
|
||||||
|
if err := h.HandleAnnounce(ctx, req, resp); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AfterAnnounce does something with the results of an Announce after it has
|
||||||
|
// been completed.
|
||||||
|
func (l *Logic) AfterAnnounce(ctx context.Context, req *bittorrent.AnnounceRequest, resp *bittorrent.AnnounceResponse) {
|
||||||
|
for _, h := range l.announcePostHooks {
|
||||||
|
if err := h.HandleAnnounce(ctx, req, resp); err != nil {
|
||||||
|
log.Println("trakr: post-announce hooks failed:", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleScrape generates a response for a Scrape.
|
||||||
|
func (l *Logic) HandleScrape(ctx context.Context, req *bittorrent.ScrapeRequest) (*bittorrent.ScrapeResponse, error) {
|
||||||
|
resp := &bittorrent.ScrapeResponse{
|
||||||
|
Files: make(map[bittorrent.InfoHash]bittorrent.Scrape),
|
||||||
|
}
|
||||||
|
for _, h := range l.scrapePreHooks {
|
||||||
|
if err := h.HandleScrape(ctx, req, resp); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AfterScrape does something with the results of a Scrape after it has been
|
||||||
|
// completed.
|
||||||
|
func (l *Logic) AfterScrape(ctx context.Context, req *bittorrent.ScrapeRequest, resp *bittorrent.ScrapeResponse) {
|
||||||
|
for _, h := range l.scrapePostHooks {
|
||||||
|
if err := h.HandleScrape(ctx, req, resp); err != nil {
|
||||||
|
log.Println("trakr: post-scrape hooks failed:", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package backend
|
package middleware
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
Loading…
Add table
Reference in a new issue