add comments and clean up
This commit is contained in:
parent
c11cf49739
commit
baf01ec809
|
@ -3,6 +3,5 @@
|
|||
TODO:
|
||||
|
||||
+ Decent Logging;
|
||||
+ Comments;
|
||||
+ README Documentation;
|
||||
+ Unit-Tests;
|
||||
+ Iteration & Improved Design;
|
||||
|
|
64
consensus.go
64
consensus.go
|
@ -6,12 +6,19 @@ import (
|
|||
"time"
|
||||
)
|
||||
|
||||
// DefaultConsensusConfig returns the ConsensusConfig,
|
||||
// with the default values:
|
||||
// + Timeout: 5 seconds;
|
||||
func DefaultConsensusConfig() *ConsensusConfig {
|
||||
return &ConsensusConfig{
|
||||
Timeout: time.Second * 5,
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultConsensus returns a consensus filled
|
||||
// with default and recommended HTTPSources.
|
||||
// TLS-Protected providers get more power,
|
||||
// compared to plain-text providers.
|
||||
func DefaultConsensus(cfg *ConsensusConfig) *Consensus {
|
||||
consensus := NewConsensus(cfg)
|
||||
|
||||
|
@ -32,6 +39,8 @@ func DefaultConsensus(cfg *ConsensusConfig) *Consensus {
|
|||
return consensus
|
||||
}
|
||||
|
||||
// NewConsensus creates a new Consensus, with no sources.
|
||||
// When the given cfg is <nil>, the `DefaultConsensusConfig` will be used.
|
||||
func NewConsensus(cfg *ConsensusConfig) *Consensus {
|
||||
if cfg == nil {
|
||||
cfg = DefaultConsensusConfig()
|
||||
|
@ -41,39 +50,54 @@ func NewConsensus(cfg *ConsensusConfig) *Consensus {
|
|||
}
|
||||
}
|
||||
|
||||
// ConsensusConfig is used to configure the Consensus, while creating it.
|
||||
type ConsensusConfig struct {
|
||||
Timeout time.Duration
|
||||
}
|
||||
|
||||
func (cfg *ConsensusConfig) WithTimout(timeout time.Duration) *ConsensusConfig {
|
||||
// WithTimeout sets the timeout of this config,
|
||||
// returning the config itself at the end, to allow for chaining
|
||||
func (cfg *ConsensusConfig) WithTimeout(timeout time.Duration) *ConsensusConfig {
|
||||
cfg.Timeout = timeout
|
||||
return cfg
|
||||
}
|
||||
|
||||
// Consensus the type at the center of this library,
|
||||
// and is the main entry point for users.
|
||||
// Its `ExternalIP` method allows you to ask for your ExternalIP,
|
||||
// influenced by all its added voters.
|
||||
type Consensus struct {
|
||||
voters []Voter
|
||||
voters []voter
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
// AddVoter adds a voter to this consensus.
|
||||
// The source cannot be <nil> and
|
||||
// the weight has to be of a value of 1 or above.
|
||||
func (c *Consensus) AddVoter(source Source, weight uint) error {
|
||||
if source == nil {
|
||||
return NoSourceError
|
||||
return ErrNoSource
|
||||
}
|
||||
if weight == 0 {
|
||||
return InsufficientWeightError
|
||||
return ErrInsufficientWeight
|
||||
}
|
||||
|
||||
c.voters = append(c.voters, Voter{
|
||||
c.voters = append(c.voters, voter{
|
||||
source: source,
|
||||
weight: weight,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddHTTPVoter creates and adds an HTTP Voter to this consensus,
|
||||
// using the HTTP Client of this Consensus, configured by the ConsensusConfig.
|
||||
func (c *Consensus) AddHTTPVoter(url string, weight uint) error {
|
||||
return c.AddVoter(NewHTTPSource(c.client, url), weight)
|
||||
}
|
||||
|
||||
// AddComplexHTTPVoter creates an adds an HTTP Voter to this consensus,
|
||||
// using a given parser, and the HTTP Client of this Consensus,
|
||||
// configured by the ConsensusConfig
|
||||
func (c *Consensus) AddComplexHTTPVoter(url string, parser ContentParser, weight uint) error {
|
||||
return c.AddVoter(
|
||||
NewHTTPSource(c.client, url).WithParser(parser),
|
||||
|
@ -81,21 +105,29 @@ func (c *Consensus) AddComplexHTTPVoter(url string, parser ContentParser, weight
|
|||
)
|
||||
}
|
||||
|
||||
// ExternalIP requests asynchronously the externalIP from all added voters,
|
||||
// returning the IP which received the most votes.
|
||||
// The returned IP will always be valid, in case the returned error is <nil>.
|
||||
func (c *Consensus) ExternalIP() (net.IP, error) {
|
||||
voteCollection := make(map[string]uint)
|
||||
ch := make(chan Vote, len(c.voters))
|
||||
ch := make(chan vote, len(c.voters))
|
||||
|
||||
for _, voter := range c.voters {
|
||||
go func(voter Voter) {
|
||||
ip, err := voter.source.IP()
|
||||
ch <- Vote{
|
||||
// start all source Requests on a seperate goroutine
|
||||
for _, v := range c.voters {
|
||||
go func(v voter) {
|
||||
ip, err := v.source.IP()
|
||||
if err == nil && ip == nil {
|
||||
err = InvalidIPError("")
|
||||
}
|
||||
ch <- vote{
|
||||
IP: ip,
|
||||
Count: voter.weight,
|
||||
Count: v.weight,
|
||||
Error: err,
|
||||
}
|
||||
}(voter)
|
||||
}(v)
|
||||
}
|
||||
|
||||
// Wait for all votes to come in
|
||||
var count int
|
||||
for count < len(c.voters) {
|
||||
select {
|
||||
|
@ -108,18 +140,24 @@ func (c *Consensus) ExternalIP() (net.IP, error) {
|
|||
}
|
||||
}
|
||||
|
||||
// if no votes were casted succesfully,
|
||||
// return early with an error
|
||||
if len(voteCollection) == 0 {
|
||||
return nil, NoIPError
|
||||
return nil, ErrNoIP
|
||||
}
|
||||
|
||||
var max uint
|
||||
var externalIP string
|
||||
|
||||
// find the IP which has received the most votes,
|
||||
// influinced by the voter's weight.
|
||||
for ip, votes := range voteCollection {
|
||||
if votes > max {
|
||||
max, externalIP = votes, ip
|
||||
}
|
||||
}
|
||||
|
||||
// as the found IP was parsed previously,
|
||||
// we know it cannot be nil and is valid
|
||||
return net.ParseIP(externalIP), nil
|
||||
}
|
||||
|
|
13
error.go
13
error.go
|
@ -2,14 +2,21 @@ package externalip
|
|||
|
||||
import "errors"
|
||||
|
||||
// InvalidIPError is returned when an value returned is invalid.
|
||||
// This error should be returned by the source itself.
|
||||
type InvalidIPError string
|
||||
|
||||
// Error implements error.Error
|
||||
func (err InvalidIPError) Error() string {
|
||||
return "Invalid IP: " + string(err)
|
||||
}
|
||||
|
||||
var (
|
||||
NoIPError = errors.New("no IP could be found")
|
||||
InsufficientWeightError = errors.New("a voter's weight has to be at least 1")
|
||||
NoSourceError = errors.New("no voter's source given")
|
||||
// ErrNoIP is returned by the Consensus when no vote was casted successfully
|
||||
ErrNoIP = errors.New("no IP could be found")
|
||||
// ErrInsufficientWeight is returned when a voter's weight is invalid
|
||||
ErrInsufficientWeight = errors.New("a voter's weight has to be at least 1")
|
||||
// ErrNoSource is returned when a voter is added,
|
||||
// which doesn't have a source specified
|
||||
ErrNoSource = errors.New("no voter's source given")
|
||||
)
|
||||
|
|
33
sources.go
33
sources.go
|
@ -7,14 +7,9 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
type HTTPSource struct {
|
||||
client *http.Client
|
||||
url string
|
||||
parser ContentParser
|
||||
}
|
||||
|
||||
type ContentParser func(string) (string, error)
|
||||
|
||||
// NewHTTPSource creates a HTTP Source object,
|
||||
// which can be used to request the (external) IP from.
|
||||
// The Default HTTP Client will be used if no client is given.
|
||||
func NewHTTPSource(client *http.Client, url string) *HTTPSource {
|
||||
if client == nil {
|
||||
client = http.DefaultClient
|
||||
|
@ -26,18 +21,37 @@ func NewHTTPSource(client *http.Client, url string) *HTTPSource {
|
|||
}
|
||||
}
|
||||
|
||||
// HTTPSource is the default source, to get the external IP from.
|
||||
// It does so by requesting the IP from a URL, via an HTTP GET Request.
|
||||
type HTTPSource struct {
|
||||
client *http.Client
|
||||
url string
|
||||
parser ContentParser
|
||||
}
|
||||
|
||||
// ContentParser can be used to add a parser to an HTTPSource
|
||||
// to parse the raw content returned from a website, and return the IP.
|
||||
// Spacing before and after the IP will be trimmed by the Consensus.
|
||||
type ContentParser func(string) (string, error)
|
||||
|
||||
// WithParser sets the parser value as the value to be used by this HTTPSource,
|
||||
// and returns the pointer to this source, to allow for chaining.
|
||||
func (s *HTTPSource) WithParser(parser ContentParser) *HTTPSource {
|
||||
s.parser = parser
|
||||
return s
|
||||
}
|
||||
|
||||
// IP implements Source.IP
|
||||
func (s *HTTPSource) IP() (net.IP, error) {
|
||||
// Define the GET method with the correct url,
|
||||
// setting the User-Agent to our library
|
||||
req, err := http.NewRequest("GET", s.url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("User-Agent", "go-external-ip (github.com/glendc/go-external-ip)")
|
||||
|
||||
// Do the request and read the body for non-error results.
|
||||
resp, err := s.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -49,6 +63,7 @@ func (s *HTTPSource) IP() (net.IP, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
// optionally parse the content
|
||||
raw := string(bytes)
|
||||
if s.parser != nil {
|
||||
raw, err = s.parser(raw)
|
||||
|
@ -57,10 +72,12 @@ func (s *HTTPSource) IP() (net.IP, error) {
|
|||
}
|
||||
}
|
||||
|
||||
// validate the IP
|
||||
externalIP := net.ParseIP(strings.TrimSpace(raw))
|
||||
if externalIP == nil {
|
||||
return nil, InvalidIPError(raw)
|
||||
}
|
||||
|
||||
// returned the parsed IP
|
||||
return externalIP, nil
|
||||
}
|
||||
|
|
18
types.go
18
types.go
|
@ -2,18 +2,26 @@ package externalip
|
|||
|
||||
import "net"
|
||||
|
||||
// Source defines the part of a voter which gives the actual voting value (IP).
|
||||
type Source interface {
|
||||
// IP returns IPv4/IPv6 address in a non-error case
|
||||
// net.IP should never be <nil> when error is <nil>
|
||||
IP() (net.IP, error)
|
||||
}
|
||||
|
||||
type Voter struct {
|
||||
// voter adds weight to the IP given by a source.
|
||||
// The weight has to be at least 1, and the more it is, the more power the voter has.
|
||||
type voter struct {
|
||||
source Source // provides the IP (see: vote)
|
||||
weight uint // provides the weight of its vote (acts as a multiplier)
|
||||
}
|
||||
|
||||
type Vote struct {
|
||||
IP net.IP
|
||||
Count uint
|
||||
Error error
|
||||
// vote is given by each voter,
|
||||
// if the Error is not <nil>, the IP and Count values are ignored,
|
||||
// and the vote has no effect.
|
||||
// The IP value should never be <nil>, when Error is <nil> as well.
|
||||
type vote struct {
|
||||
IP net.IP // the IP proposed by the Voter in question
|
||||
Count uint // equal to the Voter's weight
|
||||
Error error // defines if the Vote was cast succesfully or not
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue