s/backend/middleware

This commit is contained in:
Jimmy Zelinskie 2016-08-09 20:08:15 -04:00
parent c7b17d3195
commit c9fe95b103
7 changed files with 142 additions and 169 deletions

View file

@ -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
}
}
}

View file

@ -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())
} }

View file

@ -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)
} }

View file

@ -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

View file

@ -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
View 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
}
}
}

View file

@ -1,4 +1,4 @@
package backend package middleware
import ( import (
"time" "time"