From 1bc42063ab27830c48515e8a6da006d65a65882c Mon Sep 17 00:00:00 2001 From: Jimmy Zelinskie Date: Fri, 21 Jun 2013 21:43:11 -0400 Subject: [PATCH] copyright updated; imports renamed; misc fixes --- README.md | 4 +- config/config.go | 10 +-- example/config.json | 3 +- main.go | 6 +- server/announce.go | 55 +++++++-------- server/query.go | 48 ++++++++++--- server/server.go | 155 +++++++++++++++++++++-------------------- storage/redis/redis.go | 6 +- storage/storage.go | 18 +++-- 9 files changed, 176 insertions(+), 129 deletions(-) diff --git a/README.md b/README.md index 75a6989..ce73dfd 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ chihaya ======= -[![Build Status](https://travis-ci.org/jzelinskie/chihaya.png?branch=master)](https://travis-ci.org/jzelinskie/chihaya) +[![Build Status](https://travis-ci.org/pushrax/chihaya.png?branch=master)](https://travis-ci.org/pushrax/chihaya) chihaya is a high-performance [BitTorrent tracker](http://en.wikipedia.org/wiki/BitTorrent_tracker) written in the Go programming language. It isn't quite ready for prime-time just yet, but these are the features that it'll have: @@ -15,7 +15,7 @@ It isn't quite ready for prime-time just yet, but these are the features that it Installing ---------- - $ go install github.com/jzelinskie/chihaya + $ go install github.com/pushrax/chihaya Configuration diff --git a/config/config.go b/config/config.go index 842c960..5b6cab9 100644 --- a/config/config.go +++ b/config/config.go @@ -1,3 +1,7 @@ +// Copyright 2013 The Chihaya Authors. All rights reserved. +// Use of this source code is governed by the BSD 2-Clause license, +// which can be found in the LICENSE file. + package config import ( @@ -12,14 +16,12 @@ type Config struct { Private bool `json:"private"` Freeleech bool `json:"freeleech"` - Announce Duration `json:"announce"` - MinAnnounce Duration `json:"min_announce"` - BufferPoolSize int `json:"bufferpool_size"` + Announce Duration `json:"announce"` + MinAnnounce Duration `json:"min_announce"` Whitelist []string `json:"whitelist"` } -// StorageConfig represents the settings used for storage or cache. type Storage struct { Driver string `json:"driver"` Protocol string `json:"protocol"` diff --git a/example/config.json b/example/config.json index 991e891..e6b509d 100644 --- a/example/config.json +++ b/example/config.json @@ -1,11 +1,10 @@ { - "addr": ":34000" + "addr": ":34000", "announce": "30m", "min_announce": "15m", "freelech": false, "private": true, - "bufferpool_size": 500, "storage": { "driver": "redis", diff --git a/main.go b/main.go index 90e2297..0eb0aef 100644 --- a/main.go +++ b/main.go @@ -12,8 +12,8 @@ import ( "runtime" "runtime/pprof" - "github.com/jzelinskie/chihaya/config" - "github.com/jzelinskie/chihaya/server" + "github.com/pushrax/chihaya/config" + "github.com/pushrax/chihaya/server" ) var ( @@ -31,7 +31,7 @@ func main() { runtime.GOMAXPROCS(runtime.NumCPU()) if configFile != "" { - conf, err := config.Parse(configFile) + conf, err := config.New(configFile) if err != nil { log.Fatalf("Failed to parse configuration file: %s\n", err) } diff --git a/server/announce.go b/server/announce.go index 426f69f..e2b544a 100644 --- a/server/announce.go +++ b/server/announce.go @@ -1,58 +1,59 @@ +// Copyright 2013 The Chihaya Authors. All rights reserved. +// Use of this source code is governed by the BSD 2-Clause license, +// which can be found in the LICENSE file. + package server import ( - "bytes" "errors" - "log" + "fmt" + "net/http" + "path" - "github.com/jzelinskie/chihaya/config" - "github.com/jzelinskie/chihaya/storage" + "github.com/pushrax/chihaya/config" ) -func (h *handler) serveAnnounce(w *http.ResponseWriter, r *http.Request) { - buf := h.bufferpool.Take() - defer h.bufferpool.Give(buf) - defer h.writeResponse(&w, r, buf) - - user, err := validatePasskey(dir, h.storage) +func (h *handler) serveAnnounce(w http.ResponseWriter, r *http.Request) { + passkey, action := path.Split(r.URL.Path) + user, err := validatePasskey(passkey, h.storage) if err != nil { - fail(err, buf) + fail(err, w) return } pq, err := parseQuery(r.URL.RawQuery) if err != nil { - fail(errors.New("Error parsing query"), buf) + fail(errors.New("Error parsing query"), w) return } - ip, err := determineIP(r, pq) + ip, err := pq.determineIP(r) if err != nil { - fail(err, buf) + fail(err, w) return } - err := validateParsedQuery(pq) + err = validateParsedQuery(pq) if err != nil { - fail(errors.New("Malformed request"), buf) + fail(errors.New("Malformed request"), w) return } - if !whitelisted(peerId, h.conf) { - fail(errors.New("Your client is not approved"), buf) + if !whitelisted(pq.params["peerId"], h.conf) { + fail(errors.New("Your client is not approved"), w) return } - torrent, exists, err := h.storage.FindTorrent(infohash) + torrent, exists, err := h.storage.FindTorrent(pq.params["infohash"]) if err != nil { panic("server: failed to find torrent") } if !exists { - fail(errors.New("This torrent does not exist"), buf) + fail(errors.New("This torrent does not exist"), w) return } - if torrent.Status == 1 && left == 0 { + if left, _ := pq.getUint64("left"); torrent.Status == 1 && left == 0 { err := h.storage.UnpruneTorrent(torrent) if err != nil { panic("server: failed to unprune torrent") @@ -65,17 +66,15 @@ func (h *handler) serveAnnounce(w *http.ResponseWriter, r *http.Request) { torrent.Status, left, ), - buf, + w, ) return } - //go + // TODO } -func whitelisted(peerId string, conf config.Config) bool { - // TODO Decide if whitelist should be in storage or config -} - -func newPeer() { +func whitelisted(peerId string, conf *config.Config) bool { + // TODO + return false } diff --git a/server/query.go b/server/query.go index 35f2bb4..b263ead 100644 --- a/server/query.go +++ b/server/query.go @@ -1,7 +1,13 @@ +// Copyright 2013 The Chihaya Authors. All rights reserved. +// Use of this source code is governed by the BSD 2-Clause license, +// which can be found in the LICENSE file. + package server import ( "errors" + "net/http" + "net/url" "strconv" ) @@ -11,11 +17,11 @@ type parsedQuery struct { } func (pq *parsedQuery) getUint64(key string) (uint64, bool) { - str, exists := pq[key] + str, exists := pq.params[key] if !exists { return 0, false } - val, err := strconv.Uint64(str, 10, 64) + val, err := strconv.ParseUint(str, 10, 64) if err != nil { return 0, false } @@ -54,11 +60,11 @@ func parseQuery(query string) (*parsedQuery, error) { keyStr, err := url.QueryUnescape(query[keyStart : keyEnd+1]) if err != nil { - return err + return nil, err } valStr, err := url.QueryUnescape(query[valStart : valEnd+1]) if err != nil { - return err + return nil, err } pq.params[keyStr] = valStr @@ -67,7 +73,7 @@ func parseQuery(query string) (*parsedQuery, error) { if hasInfohash { // Multiple infohashes if pq.infohashes == nil { - pq.infohashes = []string{firstInfoHash} + pq.infohashes = []string{firstInfohash} } pq.infohashes = append(pq.infohashes, valStr) } else { @@ -87,15 +93,15 @@ func parseQuery(query string) (*parsedQuery, error) { valEnd = i } } - return + return pq, nil } func validateParsedQuery(pq *parsedQuery) error { - infohash, ok := pq["info_hash"] + infohash, ok := pq.params["info_hash"] if infohash == "" { return errors.New("infohash does not exist") } - peerId, ok := pq["peer_id"] + peerId, ok := pq.params["peer_id"] if peerId == "" { return errors.New("peerId does not exist") } @@ -117,3 +123,29 @@ func validateParsedQuery(pq *parsedQuery) error { } return nil } + +func (pq *parsedQuery) determineIP(r *http.Request) (string, error) { + ip, ok := pq.params["ip"] + if !ok { + ip, ok = pq.params["ipv4"] + if !ok { + ips, ok := r.Header["X-Real-Ip"] + if ok && len(ips) > 0 { + ip = ips[0] + } else { + portIndex := len(r.RemoteAddr) - 1 + for ; portIndex >= 0; portIndex-- { + if r.RemoteAddr[portIndex] == ':' { + break + } + } + if portIndex != -1 { + ip = r.RemoteAddr[0:portIndex] + } else { + return "", errors.New("Failed to parse IP address") + } + } + } + } + return ip, nil +} diff --git a/server/server.go b/server/server.go index 5d4af4f..2647bf7 100644 --- a/server/server.go +++ b/server/server.go @@ -1,8 +1,13 @@ +// Copyright 2013 The Chihaya Authors. All rights reserved. +// Use of this source code is governed by the BSD 2-Clause license, +// which can be found in the LICENSE file. + package server import ( - "bytes" "errors" + "fmt" + "io" "net" "net/http" "path" @@ -10,61 +15,81 @@ import ( "sync" "sync/atomic" - "github.com/jzelinskie/bufferpool" - - "github.com/jzelinskie/chihaya/config" - "github.com/jzelinskie/chihaya/storage" + "github.com/pushrax/chihaya/config" + "github.com/pushrax/chihaya/storage" ) type Server struct { + conf *config.Config + listener net.Listener + storage storage.Storage + terminated *bool + waitgroup *sync.WaitGroup + http.Server - listener *net.Listener } -func New(conf *config.Config) { - return &Server{ - Addr: conf.Addr, - Handler: newHandler(conf), +func New(conf *config.Config) (*Server, error) { + var ( + wg sync.WaitGroup + terminated bool + ) + + store, err := storage.New(&conf.Storage) + if err != nil { + return nil, err } + + handler := &handler{ + conf: conf, + storage: store, + terminated: &terminated, + waitgroup: &wg, + } + + s := &Server{ + conf: conf, + storage: store, + terminated: &terminated, + waitgroup: &wg, + } + + s.Server.Addr = conf.Addr + s.Server.Handler = handler + return s, nil } func (s *Server) Start() error { - s.listener, err = net.Listen("tcp", config.Addr) + listener, err := net.Listen("tcp", s.conf.Addr) if err != nil { return err } - s.Handler.terminated = false + *s.terminated = false s.Serve(s.listener) - s.Handler.waitgroup.Wait() - s.Handler.storage.Shutdown() + s.waitgroup.Wait() return nil } func (s *Server) Stop() error { - s.Handler.waitgroup.Wait() - s.Handler.terminated = true - return s.Handler.listener.Close() + *s.terminated = true + s.waitgroup.Wait() + err := s.storage.Shutdown() + if err != nil { + return err + } + return s.listener.Close() } type handler struct { - bufferpool *bufferpool.BufferPool conf *config.Config deltaRequests int64 - storage *storage.Storage - terminated bool - waitgroup sync.WaitGroup -} - -func newHandler(conf *config.Config) { - return &Handler{ - bufferpool: bufferpool.New(conf.BufferPoolSize, 500), - conf: conf, - storage: storage.New(&conf.Storage), - } + storage storage.Storage + terminated *bool + waitgroup *sync.WaitGroup } func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - if h.terminated { + if *h.terminated { return } @@ -72,46 +97,54 @@ func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { defer h.waitgroup.Done() if r.URL.Path == "/stats" { - h.serveStats(&w, r) + h.serveStats(w, r) return } - dir, action := path.Split(requestPath) + passkey, action := path.Split(r.URL.Path) switch action { case "announce": - h.serveAnnounce(&w, r) + h.serveAnnounce(w, r) return case "scrape": // TODO - h.serveScrape(&w, r) + h.serveScrape(w, r) return default: - buf := h.bufferpool.Take() - fail(errors.New("Unknown action"), buf) - h.writeResponse(&w, r, buf) + written := fail(errors.New("Unknown action"), w) + h.finalizeResponse(w, r, written) return } } -func writeResponse(w *http.ResponseWriter, r *http.Request, buf *bytes.Buffer) { +func (h *handler) finalizeResponse( + w http.ResponseWriter, + r *http.Request, + written int, +) { r.Close = true w.Header().Add("Content-Type", "text/plain") w.Header().Add("Connection", "close") - w.Header().Add("Content-Length", strconv.Itoa(buf.Len())) - w.Write(buf.Bytes()) + w.Header().Add("Content-Length", strconv.Itoa(written)) w.(http.Flusher).Flush() - atomic.AddInt64(h.deltaRequests, 1) + atomic.AddInt64(&h.deltaRequests, 1) } -func fail(err error, buf *bytes.Buffer) { - buf.WriteString("d14:failure reason") - buf.WriteString(strconv.Itoa(len(err))) - buf.WriteRune(':') - buf.WriteString(err) - buf.WriteRune('e') +func fail(err error, w http.ResponseWriter) int { + e := err.Error() + message := fmt.Sprintf( + "%s%s%s%s%s", + "d14:failure reason", + strconv.Itoa(len(e)), + ':', + e, + 'e', + ) + written, _ := io.WriteString(w, message) + return written } -func validatePasskey(dir string, s *storage.Storage) (storage.User, error) { +func validatePasskey(dir string, s storage.Storage) (*storage.User, error) { if len(dir) != 34 { return nil, errors.New("Your passkey is invalid") } @@ -127,29 +160,3 @@ func validatePasskey(dir string, s *storage.Storage) (storage.User, error) { return user, nil } - -func determineIP(r *http.Request, pq *parsedQuery) (string, error) { - ip, ok := pq.params["ip"] - if !ok { - ip, ok = pq.params["ipv4"] - if !ok { - ips, ok := r.Header["X-Real-Ip"] - if ok && len(ips) > 0 { - ip = ips[0] - } else { - portIndex := len(r.RemoteAddr) - 1 - for ; portIndex >= 0; portIndex-- { - if r.RemoteAddr[portIndex] == ':' { - break - } - } - if portIndex != -1 { - ip = r.RemoteAddr[0:portIndex] - } else { - return "", errors.New("Failed to parse IP address") - } - } - } - } - return &ip, nil -} diff --git a/storage/redis/redis.go b/storage/redis/redis.go index 1420383..be82e7d 100644 --- a/storage/redis/redis.go +++ b/storage/redis/redis.go @@ -1,5 +1,9 @@ +// Copyright 2013 The Chihaya Authors. All rights reserved. +// Use of this source code is governed by the BSD 2-Clause license, +// which can be found in the LICENSE file. + package redis import ( - "github.com/jzelinskie/chihaya/storage" + "github.com/pushrax/chihaya/storage" ) diff --git a/storage/storage.go b/storage/storage.go index cbc0ceb..06e02b4 100644 --- a/storage/storage.go +++ b/storage/storage.go @@ -1,15 +1,19 @@ +// Copyright 2013 The Chihaya Authors. All rights reserved. +// Use of this source code is governed by the BSD 2-Clause license, +// which can be found in the LICENSE file. + package storage import ( "fmt" - "github.com/jzelinskie/chihaya/config" + "github.com/pushrax/chihaya/config" ) var drivers = make(map[string]StorageDriver) type StorageDriver interface { - New(*config.StorageConfig) (Storage, error) + New(*config.Storage) (Storage, error) } func Register(name string, driver StorageDriver) { @@ -22,12 +26,12 @@ func Register(name string, driver StorageDriver) { drivers[name] = driver } -func New(name string, conf *config.Storage) (Storage, error) { - driver, ok := drivers[name] +func New(conf *config.Storage) (Storage, error) { + driver, ok := drivers[conf.Driver] if !ok { return nil, fmt.Errorf( "storage: unknown driver %q (forgotten import?)", - name, + conf.Driver, ) } store, err := driver.New(conf) @@ -40,8 +44,8 @@ func New(name string, conf *config.Storage) (Storage, error) { type Storage interface { Shutdown() error - FindUser(passkey []byte) (*User, bool, error) - FindTorrent(infohash []byte) (*Torrent, bool, error) + FindUser(passkey string) (*User, bool, error) + FindTorrent(infohash string) (*Torrent, bool, error) UnpruneTorrent(torrent *Torrent) error RecordUser(