func (c *Client) query(ctx context.Context, qs string) (value *jq.Value, err error) { url := defaults.String(c.URL, "http://api.forensiq.com") + "/check?" + qs // perform the request resp, err := c.HTTP.Get(ctx, url) if err != nil { return } // and parse the response back value = new(jq.Value) err = value.UnmarshalFrom(resp.Body) return }
// Process sends a request to query the /check endpoint. // The response is attached to the request. func (c *Client) Process(ctx context.Context, r rtb.Request) (err error) { ctx = trace.Enter(ctx, "Forensiq") // ready? if !c.HTTP.Ready() { err = ErrUnavailable trace.Error(ctx, "Errors.Ready", err) return } var args url.Values // fast path in case the request is already prepared if p, ok := r.(*request); ok { args = p.values } else { args, err = c.prepare(r) if err != nil { trace.Error(ctx, "Errors.Prepare", err) return } } qs := args.Encode() // look into the memory cache or perform the query value := c.fromCache(qs) if value != nil { trace.Count(ctx, "CacheHit", 1) } else { value, err = c.query(ctx, qs) if err != nil { trace.Error(ctx, "Errors.Request", err) return } c.intoCache(qs, value) } // hold the result r.Attach(defaults.String(c.Target, "forensiq"), value) trace.Leave(ctx, "Check") return }
func (c *Client) prepare(r rtb.Request) (result url.Values, err error) { extractor, ok := r.Component(defaults.String(c.Source, "fields")).(rtb.Extractor) if !ok { err = rtb.ErrNoExtractor return } args := url.Values{ "ck": []string{c.ClientKey}, "output": []string{"json"}, "rt": []string{"display"}, } extract := func(key string, value []string) { item := extractor.Extract(value...) if item == nil { return } args.Set(key, fmt.Sprintf("%v", item)) } // extract fields for key, value := range c.Fields { extract(key, value) } // valid? fields := []string{"ck", "rt", "ip", "seller"} for i := range fields { items := args[fields[i]] if len(items) == 0 || items[0] == "" { err = ErrMissingFields return } } result = args return }
// HealthCheck sends a request to query the /ready endpoint. func (c *Client) HealthCheck() error { url := defaults.String(c.URL, "http://api.forensiq.com") + "/ready" return c.HTTP.HealthCheck(url) }
// Start installs the health monitor of the Forensiq API. func (c *Client) Start() error { url := defaults.String(c.URL, "http://api.forensiq.com") + "/ready" c.HTTP.MonitorHealth(url) return nil }
// Start installs the server and starts serving requests until the server is closed. func (s *Server) Start() (err error) { s.Server.Handler = s // open socket s.listen, err = net.Listen("tcp", defaults.String(s.Server.Addr, ":http")) if err != nil { return } s.name = defaults.String(s.Name, s.listen.Addr().String()) // provide a default read timeout if missing if 0 == s.Server.ReadTimeout { s.Server.ReadTimeout = time.Minute } // track new/closed HTTP connections s.conns = make(map[net.Conn]http.ConnState) cs := s.Server.ConnState s.Server.ConnState = func(conn net.Conn, state http.ConnState) { s.mu.Lock() defer s.mu.Unlock() switch state { case http.StateNew: atomic.AddInt64(&s.count, +1) s.wg.Add(1) s.conns[conn] = state case http.StateClosed, http.StateHijacked: atomic.AddInt64(&s.count, -1) s.wg.Done() delete(s.conns, conn) case http.StateActive, http.StateIdle: s.conns[conn] = state } if nil != cs { cs(conn, state) } } // update metrics s.tick = Tick(time.Second, func() { ctx := trace.Start(trace.SetHandler(context.Background(), s.Tracer), s.name+".Tick", "") trace.Set(ctx, "Connections", atomic.LoadInt64(&s.count)) trace.Set(ctx, "State", atomic.LoadInt64(&s.ready)) trace.Leave(ctx, "Ticked") }) // serve requests go func() { s.Server.Serve(s.listen) }() // set ready if !atomic.CompareAndSwapInt64(&s.ready, 0, 1) { panic("server is already ready to serve requests") } return }