Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- package main
- import (
- "encoding/json"
- "fmt"
- "io"
- "net/http"
- "net/url"
- "os"
- "runtime"
- "sync"
- "time"
- "github.com/patrickmn/go-cache"
- "github.com/sirupsen/logrus"
- )
- // client - the core type of the package
- type client struct {
- *config
- *logrus.Logger
- *http.Client
- *cache.Cache
- }
- // config - configuration of the client
- type config = struct {
- CacheExpiration time.Duration
- LogLevel logrus.Level
- ipstack service // parameters of api.ipstack.com
- geoip service // parameters of geoip.nekudo.com
- }
- // service - describes parameters of a service which provides information about the country by the provided IP
- type service struct {
- Key string
- Host string
- Path string
- Scheme string
- QueryLimit int // maximum amount of queries per minute
- QueryCounter int // number of used queries
- QueryOffset time.Time // time of sending the first query
- }
- // New - creates new instance of client type
- func New(cfg *config) *client {
- log := logrus.New()
- logFile, _ := os.Create("cquery.log")
- log.SetLevel(cfg.LogLevel)
- mw := io.MultiWriter(os.Stdout, logFile)
- log.Out = mw
- return &client{
- config: cfg,
- Logger: log,
- Client: &http.Client{},
- Cache: cache.New(cfg.CacheExpiration*time.Second, cfg.CacheExpiration*time.Second),
- }
- }
- // Code - gets country codes for an array of IPs
- func (c *client) Code(input []string, output chan string) {
- wg := &sync.WaitGroup{}
- for _, IP := range input {
- if code, found := c.Cache.Get(IP); found {
- c.Debugf("found in cache: %v - %v", IP, code)
- output <- fmt.Sprintf("%v: %v", IP, code)
- continue
- }
- wg.Add(1)
- serv := c.selectService()
- serv.update()
- go c.query(IP, serv, output, wg)
- }
- wg.Wait()
- close(output)
- }
- // selectService - selects a service
- func (c *client) selectService() (serv *service) {
- elapsedIPSTACK := time.Since(c.config.ipstack.QueryOffset)
- elapsedGEOIP := time.Since(c.config.geoip.QueryOffset)
- // choose a server which first query was the earliest
- switch {
- case elapsedIPSTACK >= elapsedGEOIP:
- serv = &c.config.ipstack
- default:
- serv = &c.config.geoip
- }
- c.Logger.Debugf("%v has been chosen: ipstack %v %v/%v, geoip %v %v/%v", serv.Host,
- elapsedIPSTACK, c.config.ipstack.QueryCounter, c.config.ipstack.QueryLimit,
- elapsedGEOIP, c.config.geoip.QueryCounter, c.config.geoip.QueryLimit)
- return serv
- }
- // update - update the service statistics about queries
- func (s *service) update() {
- if s.QueryCounter == s.QueryLimit-1 {
- // nullify the counter and update the time of query
- s.QueryOffset = time.Now()
- s.QueryCounter = 0
- }
- s.QueryCounter += 1
- }
- // query - gets a country code for the provided IP
- func (c *client) query(IP string, s *service, result chan string, wg *sync.WaitGroup) {
- runtime.Gosched() // TODO: remove
- defer wg.Done()
- ur := url.URL{
- Host: s.Host,
- Scheme: s.Scheme,
- Path: s.Path + IP,
- }
- query := ur.Query()
- query.Set("access_key", s.Key)
- ur.RawQuery = query.Encode()
- c.Logger.Debug("url: ", ur.String())
- res, err := c.Client.Get(ur.String())
- defer res.Body.Close()
- if err != nil {
- c.Logger.Errorf("failed to receive a response for the url %v: %v", ur.String(), err)
- }
- var a answer
- decoder := json.NewDecoder(res.Body)
- err = decoder.Decode(&a)
- if err != nil {
- c.Logger.Errorf("failed to unmarshal json for the url %v: %v", ur.String(), err)
- }
- switch s.Host {
- case "geoip.nekudo.com":
- result <- IP + " - " + a.Country.Code
- default:
- result <- IP + " - " + a.Code
- }
- }
- // answer - structure for JSON unmarshalling of responses of services
- type answer struct {
- Code string `json:"country_code"`
- Country struct {
- Code string `json:"code"`
- } `json:"country"`
- }
- func main() {
- var defaultCfg = config{
- CacheExpiration: 300,
- ipstack: service{
- Key: "76a08091e75e6395b71193f387b9fb27",
- Host: "api.ipstack.com",
- Path: "",
- Scheme: "http",
- QueryLimit: 5,
- },
- geoip: service{
- Key: "",
- Host: "geoip.nekudo.com",
- Path: "api/",
- Scheme: "http",
- QueryLimit: 5,
- },
- }
- cl := New(&defaultCfg)
- cl.Logger.SetLevel(5)
- output := make(chan string, 15)
- addr := []string{"108.10.10.10", "152.168.0.0", "128.10.10.10", "234.23.2.11", "33.33.33.2", "34.3.5.2", "200.200.200.200", "168.10.10.10", "192.168.10.0", "128.10.110.10", "134.23.21.11", "133.33.33.12", "134.3.5.2", "200.200.200.200", "133.33.33.2"}
- cl.Code(addr, output)
- for code := range output {
- fmt.Println(code)
- }
- }
Add Comment
Please, Sign In to add comment