cmd/chihaya: refactor root run command

This change refactors a bunch of the state of execution into its own
object. It also attempts to simplify stopping and adjusts some other
packages to integrate with the stopper interface.

Fixes .
This commit is contained in:
Jimmy Zelinskie 2017-04-29 22:29:27 -04:00
parent 20d1cbf537
commit ea0dba3a3d
5 changed files with 330 additions and 312 deletions
frontend

View file

@ -5,7 +5,6 @@ package http
import (
"context"
"crypto/tls"
"errors"
"net"
"net/http"
"time"
@ -72,94 +71,95 @@ type Config struct {
TLSKeyPath string `yaml:"tls_key_path"`
}
// Frontend holds the state of an HTTP BitTorrent Frontend.
// Frontend represents the state of an HTTP BitTorrent Frontend.
type Frontend struct {
s *http.Server
srv *http.Server
tlsCfg *tls.Config
logic frontend.TrackerLogic
Config
}
// NewFrontend allocates a new instance of a Frontend.
func NewFrontend(logic frontend.TrackerLogic, cfg Config) *Frontend {
return &Frontend{
// NewFrontend creates a new instance of an HTTP Frontend that asynchronously
// serves requests.
func NewFrontend(logic frontend.TrackerLogic, cfg Config) (*Frontend, error) {
f := &Frontend{
logic: logic,
Config: cfg,
}
// If TLS is enabled, create a key pair.
if cfg.TLSCertPath != "" && cfg.TLSKeyPath != "" {
var err error
f.tlsCfg = &tls.Config{
Certificates: make([]tls.Certificate, 1),
}
f.tlsCfg.Certificates[0], err = tls.LoadX509KeyPair(cfg.TLSCertPath, cfg.TLSKeyPath)
if err != nil {
return nil, err
}
}
go func() {
if err := f.listenAndServe(); err != nil {
log.Fatal("failed while serving http: " + err.Error())
}
}()
return f, nil
}
// Stop provides a thread-safe way to shutdown a currently running Frontend.
func (t *Frontend) Stop() {
if err := t.s.Shutdown(context.Background()); err != nil {
log.Warn("Error shutting down HTTP frontend:", err)
}
func (f *Frontend) Stop() <-chan error {
c := make(chan error)
go func() {
if err := f.srv.Shutdown(context.Background()); err != nil {
c <- err
} else {
close(c)
}
}()
return c
}
func (t *Frontend) handler() http.Handler {
func (f *Frontend) handler() http.Handler {
router := httprouter.New()
router.GET("/announce", t.announceRoute)
router.GET("/scrape", t.scrapeRoute)
router.GET("/announce", f.announceRoute)
router.GET("/scrape", f.scrapeRoute)
return router
}
// ListenAndServe listens on the TCP network address t.Addr and blocks serving
// BitTorrent requests until t.Stop() is called or an error is returned.
func (t *Frontend) ListenAndServe() error {
t.s = &http.Server{
Addr: t.Addr,
Handler: t.handler(),
ReadTimeout: t.ReadTimeout,
WriteTimeout: t.WriteTimeout,
ConnState: func(conn net.Conn, state http.ConnState) {
switch state {
case http.StateNew:
//stats.RecordEvent(stats.AcceptedConnection)
case http.StateClosed:
//stats.RecordEvent(stats.ClosedConnection)
case http.StateHijacked:
panic("http: connection impossibly hijacked")
// Ignore the following cases.
case http.StateActive, http.StateIdle:
default:
panic("http: connection transitioned to unknown state")
}
},
}
t.s.SetKeepAlivesEnabled(false)
// If TLS is enabled, create a key pair and add it to the HTTP server.
if t.Config.TLSCertPath != "" && t.Config.TLSKeyPath != "" {
var err error
tlsCfg := &tls.Config{
Certificates: make([]tls.Certificate, 1),
}
tlsCfg.Certificates[0], err = tls.LoadX509KeyPair(t.Config.TLSCertPath, t.Config.TLSKeyPath)
if err != nil {
return err
}
t.s.TLSConfig = tlsCfg
// listenAndServe blocks while listening and serving HTTP BitTorrent requests
// until Stop() is called or an error is returned.
func (f *Frontend) listenAndServe() error {
f.srv = &http.Server{
Addr: f.Addr,
TLSConfig: f.tlsCfg,
Handler: f.handler(),
ReadTimeout: f.ReadTimeout,
WriteTimeout: f.WriteTimeout,
}
// Start the HTTP server and gracefully handle any network errors.
if err := t.s.ListenAndServe(); err != nil && err != http.ErrServerClosed {
return errors.New("http: failed to run HTTP server: " + err.Error())
// Disable KeepAlives.
f.srv.SetKeepAlivesEnabled(false)
// Start the HTTP server.
if err := f.srv.ListenAndServe(); err != http.ErrServerClosed {
return err
}
return nil
}
// announceRoute parses and responds to an Announce by using t.TrackerLogic.
func (t *Frontend) announceRoute(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
// announceRoute parses and responds to an Announce.
func (f *Frontend) announceRoute(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
var err error
start := time.Now()
var af *bittorrent.AddressFamily
defer func() { recordResponseDuration("announce", af, err, time.Since(start)) }()
req, err := ParseAnnounce(r, t.RealIPHeader, t.AllowIPSpoofing)
req, err := ParseAnnounce(r, f.RealIPHeader, f.AllowIPSpoofing)
if err != nil {
WriteError(w, err)
return
@ -167,7 +167,7 @@ func (t *Frontend) announceRoute(w http.ResponseWriter, r *http.Request, _ httpr
af = new(bittorrent.AddressFamily)
*af = req.IP.AddressFamily
resp, err := t.logic.HandleAnnounce(context.Background(), req)
resp, err := f.logic.HandleAnnounce(context.Background(), req)
if err != nil {
WriteError(w, err)
return
@ -179,11 +179,11 @@ func (t *Frontend) announceRoute(w http.ResponseWriter, r *http.Request, _ httpr
return
}
go t.logic.AfterAnnounce(context.Background(), req, resp)
go f.logic.AfterAnnounce(context.Background(), req, resp)
}
// scrapeRoute parses and responds to a Scrape by using t.TrackerLogic.
func (t *Frontend) scrapeRoute(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
// scrapeRoute parses and responds to a Scrape.
func (f *Frontend) scrapeRoute(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
var err error
start := time.Now()
var af *bittorrent.AddressFamily
@ -215,7 +215,7 @@ func (t *Frontend) scrapeRoute(w http.ResponseWriter, r *http.Request, _ httprou
af = new(bittorrent.AddressFamily)
*af = req.AddressFamily
resp, err := t.logic.HandleScrape(context.Background(), req)
resp, err := f.logic.HandleScrape(context.Background(), req)
if err != nil {
WriteError(w, err)
return
@ -227,5 +227,5 @@ func (t *Frontend) scrapeRoute(w http.ResponseWriter, r *http.Request, _ httprou
return
}
go t.logic.AfterScrape(context.Background(), req, resp)
go f.logic.AfterScrape(context.Background(), req, resp)
}

View file

@ -17,6 +17,7 @@ import (
"github.com/chihaya/chihaya/bittorrent"
"github.com/chihaya/chihaya/frontend"
"github.com/chihaya/chihaya/frontend/udp/bytepool"
"github.com/chihaya/chihaya/pkg/stop"
)
var allowedGeneratedPrivateKeyRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890")
@ -82,8 +83,9 @@ type Frontend struct {
Config
}
// NewFrontend allocates a new instance of a Frontend.
func NewFrontend(logic frontend.TrackerLogic, cfg Config) *Frontend {
// NewFrontend creates a new instance of an UDP Frontend that asynchronously
// serves requests.
func NewFrontend(logic frontend.TrackerLogic, cfg Config) (*Frontend, error) {
// Generate a private key if one isn't provided by the user.
if cfg.PrivateKey == "" {
rand.Seed(time.Now().UnixNano())
@ -96,40 +98,68 @@ func NewFrontend(logic frontend.TrackerLogic, cfg Config) *Frontend {
log.Warn("UDP private key was not provided, using generated key: ", cfg.PrivateKey)
}
return &Frontend{
f := &Frontend{
closing: make(chan struct{}),
logic: logic,
Config: cfg,
}
go func() {
if err := f.listenAndServe(); err != nil {
log.Fatal("failed while serving udp: " + err.Error())
}
}()
return f, nil
}
// Stop provides a thread-safe way to shutdown a currently running Frontend.
func (t *Frontend) Stop() {
close(t.closing)
t.socket.SetReadDeadline(time.Now())
t.wg.Wait()
func (t *Frontend) Stop() <-chan error {
select {
case <-t.closing:
return stop.AlreadyStopped
default:
}
c := make(chan error)
go func() {
close(t.closing)
t.socket.SetReadDeadline(time.Now())
t.wg.Wait()
if err := t.socket.Close(); err != nil {
c <- err
} else {
close(c)
}
}()
return c
}
// ListenAndServe listens on the UDP network address t.Addr and blocks serving
// BitTorrent requests until t.Stop() is called or an error is returned.
func (t *Frontend) ListenAndServe() error {
// listenAndServe blocks while listening and serving UDP BitTorrent requests
// until Stop() is called or an error is returned.
func (t *Frontend) listenAndServe() error {
udpAddr, err := net.ResolveUDPAddr("udp", t.Addr)
if err != nil {
return err
}
log.Debugf("listening on udp socket")
t.socket, err = net.ListenUDP("udp", udpAddr)
if err != nil {
return err
}
defer t.socket.Close()
pool := bytepool.New(2048)
t.wg.Add(1)
defer t.wg.Done()
for {
// Check to see if we need to shutdown.
select {
case <-t.closing:
log.Debugf("returning from udp listen&serve")
return nil
default:
}