<commit_before>package fluffle

import (
	"encoding/json"
	"fmt"
	"log"
	"sync"
	"time"

	"github.com/satori/go.uuid"
	"github.com/streadway/amqp"
)

type Client struct {
	UUID          uuid.UUID
	Connection    *amqp.Connection
	Channel       *amqp.Channel
	ResponseQueue *amqp.Queue

	pendingResponses
}

type pendingResponses struct {
	data  map[string]chan *Response
	mutex *sync.RWMutex
}

func (p *pendingResponses) set(id string, responseChan chan *Response) {
	p.mutex.Lock()
	defer p.mutex.Unlock()
	p.data[id] = responseChan
}

func (p *pendingResponses) unset(id string) {
	p.mutex.Lock()
	defer p.mutex.Unlock()
	delete(p.data, id)
}

func (p *pendingResponses) get(id string) (chan *Response, bool) {
	p.mutex.RLock()
	defer p.mutex.RUnlock()
	responseChan, present := p.data[id]
	return responseChan, present
}

// Creates a client with the given UUID and initializes its internal data
// structures. Does not setup connections or perform any network operations.
// You will almost always want to use NewClient.
func NewBareClient(uuid uuid.UUID) *Client {
	return &Client{
		UUID: uuid,
		pendingResponses: pendingResponses{
			data:  make(map[string]chan *Response),
			mutex: &sync.RWMutex{},
		},
	}
}

// Create a client, connect it to the given AMQP server, and setup a queue
// to receive responses on.
func NewClient(url string) (*Client, error) {
	uuid := uuid.NewV1()
	client := NewBareClient(uuid)

	connection, err := amqp.Dial(url)
	if err != nil {
		return nil, err
	}
	client.Connection = connection

	channel, err := connection.Channel()
	if err != nil {
		return nil, err
	}
	client.Channel = channel

	err = client.SetupResponseQueue()
	if err != nil {
		return nil, err
	}

	return client, nil
}

// Declares the response queue and starts consuming (listening) deliveries
// from it. This is normally the final step in setting up a usable client.
func (c *Client) SetupResponseQueue() error {
	durable := false
	autoDelete := false
	exclusive := true
	noWait := false
	queue, err := c.Channel.QueueDeclare(ResponseQueueName(c.UUID.String()), durable, autoDelete, exclusive, noWait, nil)
	if err != nil {
		return err
	}
	c.ResponseQueue = &queue

	autoAck := false
	noLocal := false
	deliveries, err := c.Channel.Consume(queue.Name, "", autoAck, exclusive, noLocal, noWait, nil)
	if err != nil {
		return err
	}

	go func() {
		for delivery := range deliveries {
			c.handleReply(&delivery)
		}
	}()

	return nil
}

func (c *Client) handleReply(delivery *amqp.Delivery) {
	delivery.Ack(false)

	payload := &Response{}
	err := json.Unmarshal(delivery.Body, &payload)
	if err != nil {
		log.Printf("Error unmarshalling response payload: %v", err)
		return
	}

	id := payload.Id
	responseChan, present := c.pendingResponses.get(id)
	if present {
		responseChan <- payload
	} else {
		log.Printf("No response chan found: id=%s", id)
	}
}

// Call a remote method over JSON-RPC and return its response. This will block
// the goroutine on which it is called.
func (c *Client) Call(method string, params []interface{}, queue string) (interface{}, error) {
	id := uuid.NewV4().String()

	request := &Request{
		JsonRpc: "2.0",
		Id:      id,
		Method:  method,
		Params:  params,
	}

	return c.CallWithRequest(request, queue)
}

func (c *Client) CallWithRequest(request *Request, queue string) (interface{}, error) {
	response, err := c.PublishAndWait(request, queue)
	if err != nil {
		return nil, err
	}

	return c.DecodeResponse(response)
}

// Publishes a request onto the given queue, then waits for a response to that
// message on the client's response queue.
func (c *Client) PublishAndWait(payload *Request, queue string) (*Response, error) {
	timeoutChan := make(chan bool, 1)
	responseChan := make(chan *Response, 1)
	c.pendingResponses.set(payload.Id, responseChan)
	defer c.pendingResponses.unset(payload.Id)

	err := c.Publish(payload, queue)
	if err != nil {
		return nil, err
	}

	go func() {
		time.Sleep(5 * time.Second)
		timeoutChan <- true
	}()

	select {
	case response := <-responseChan:
		return response, nil
	case <-timeoutChan:
		return nil, fmt.Errorf("Timed out")
	}
}

// payload: JSON-RPC request payload to be sent
//
// queue: Queue on which to send the request
func (c *Client) Publish(payload *Request, queue string) error {
	routingKey := RequestQueueName(queue)
	correlationId := payload.Id
	replyTo := c.ResponseQueue.Name

	body, err := json.Marshal(payload)
	if err != nil {
		return err
	}

	mandatory := false
	immediate := false
	publishing := amqp.Publishing{
		CorrelationId: correlationId,
		ReplyTo:       replyTo,
		Body:          body,
	}
	return c.Channel.Publish(DEFAULT_EXCHANGE, routingKey, mandatory, immediate, publishing)
}

// Figure out what was in the response payload: was it a result, an error,
// or unknown?
func (c *Client) DecodeResponse(response *Response) (interface{}, error) {
	if response.Result != nil {
		return response.Result, nil
	}
	if response.Error != nil {
		return nil, response.Error
	}
	return nil, &ErrorResponse{
		Code:    0,
		Message: "Missing both `result' and `error' on Response object",
		Data:    nil,
	}
}
<commit_msg>Update documentation to note goroutine'd nature of `SetupResponseQueue`<commit_after>package fluffle

import (
	"encoding/json"
	"fmt"
	"log"
	"sync"
	"time"

	"github.com/satori/go.uuid"
	"github.com/streadway/amqp"
)

type Client struct {
	UUID          uuid.UUID
	Connection    *amqp.Connection
	Channel       *amqp.Channel
	ResponseQueue *amqp.Queue

	pendingResponses
}

type pendingResponses struct {
	data  map[string]chan *Response
	mutex *sync.RWMutex
}

func (p *pendingResponses) set(id string, responseChan chan *Response) {
	p.mutex.Lock()
	defer p.mutex.Unlock()
	p.data[id] = responseChan
}

func (p *pendingResponses) unset(id string) {
	p.mutex.Lock()
	defer p.mutex.Unlock()
	delete(p.data, id)
}

func (p *pendingResponses) get(id string) (chan *Response, bool) {
	p.mutex.RLock()
	defer p.mutex.RUnlock()
	responseChan, present := p.data[id]
	return responseChan, present
}

// Creates a client with the given UUID and initializes its internal data
// structures. Does not setup connections or perform any network operations.
// You will almost always want to use NewClient.
func NewBareClient(uuid uuid.UUID) *Client {
	return &Client{
		UUID: uuid,
		pendingResponses: pendingResponses{
			data:  make(map[string]chan *Response),
			mutex: &sync.RWMutex{},
		},
	}
}

// Create a client, connect it to the given AMQP server, and setup a queue
// to receive responses on.
func NewClient(url string) (*Client, error) {
	uuid := uuid.NewV1()
	client := NewBareClient(uuid)

	connection, err := amqp.Dial(url)
	if err != nil {
		return nil, err
	}
	client.Connection = connection

	channel, err := connection.Channel()
	if err != nil {
		return nil, err
	}
	client.Channel = channel

	err = client.SetupResponseQueue()
	if err != nil {
		return nil, err
	}

	return client, nil
}

// Declares the response queue and starts consuming (listening) deliveries
// from it. This is normally the final step in setting up a usable client.
//
// Note that this spawns a separate goroutine for handling replies from
// deliveries, so it does not block the calling goroutine.
func (c *Client) SetupResponseQueue() error {
	durable := false
	autoDelete := false
	exclusive := true
	noWait := false
	queue, err := c.Channel.QueueDeclare(ResponseQueueName(c.UUID.String()), durable, autoDelete, exclusive, noWait, nil)
	if err != nil {
		return err
	}
	c.ResponseQueue = &queue

	autoAck := false
	noLocal := false
	deliveries, err := c.Channel.Consume(queue.Name, "", autoAck, exclusive, noLocal, noWait, nil)
	if err != nil {
		return err
	}

	go func() {
		for delivery := range deliveries {
			c.handleReply(&delivery)
		}
	}()

	return nil
}

func (c *Client) handleReply(delivery *amqp.Delivery) {
	delivery.Ack(false)

	payload := &Response{}
	err := json.Unmarshal(delivery.Body, &payload)
	if err != nil {
		log.Printf("Error unmarshalling response payload: %v", err)
		return
	}

	id := payload.Id
	responseChan, present := c.pendingResponses.get(id)
	if present {
		responseChan <- payload
	} else {
		log.Printf("No response chan found: id=%s", id)
	}
}

// Call a remote method over JSON-RPC and return its response. This will block
// the goroutine on which it is called.
func (c *Client) Call(method string, params []interface{}, queue string) (interface{}, error) {
	id := uuid.NewV4().String()

	request := &Request{
		JsonRpc: "2.0",
		Id:      id,
		Method:  method,
		Params:  params,
	}

	return c.CallWithRequest(request, queue)
}

func (c *Client) CallWithRequest(request *Request, queue string) (interface{}, error) {
	response, err := c.PublishAndWait(request, queue)
	if err != nil {
		return nil, err
	}

	return c.DecodeResponse(response)
}

// Publishes a request onto the given queue, then waits for a response to that
// message on the client's response queue.
func (c *Client) PublishAndWait(payload *Request, queue string) (*Response, error) {
	timeoutChan := make(chan bool, 1)
	responseChan := make(chan *Response, 1)
	c.pendingResponses.set(payload.Id, responseChan)
	defer c.pendingResponses.unset(payload.Id)

	err := c.Publish(payload, queue)
	if err != nil {
		return nil, err
	}

	go func() {
		time.Sleep(5 * time.Second)
		timeoutChan <- true
	}()

	select {
	case response := <-responseChan:
		return response, nil
	case <-timeoutChan:
		return nil, fmt.Errorf("Timed out")
	}
}

// payload: JSON-RPC request payload to be sent
//
// queue: Queue on which to send the request
func (c *Client) Publish(payload *Request, queue string) error {
	routingKey := RequestQueueName(queue)
	correlationId := payload.Id
	replyTo := c.ResponseQueue.Name

	body, err := json.Marshal(payload)
	if err != nil {
		return err
	}

	mandatory := false
	immediate := false
	publishing := amqp.Publishing{
		CorrelationId: correlationId,
		ReplyTo:       replyTo,
		Body:          body,
	}
	return c.Channel.Publish(DEFAULT_EXCHANGE, routingKey, mandatory, immediate, publishing)
}

// Figure out what was in the response payload: was it a result, an error,
// or unknown?
func (c *Client) DecodeResponse(response *Response) (interface{}, error) {
	if response.Result != nil {
		return response.Result, nil
	}
	if response.Error != nil {
		return nil, response.Error
	}
	return nil, &ErrorResponse{
		Code:    0,
		Message: "Missing both `result' and `error' on Response object",
		Data:    nil,
	}
}
<|endoftext|>


<commit_before>package discoverd

import (
	"errors"
	"net"
	"os"
	"sort"
	"sync"
	"time"

	"github.com/flynn/discoverd/agent"
	"github.com/flynn/rpcplus"
)

type Service struct {
	Created uint
	Name    string
	Host    string
	Port    string
	Addr    string
	Attrs   map[string]string
}

type ServiceSet struct {
	l        sync.Mutex
	services map[string]*Service
	filters  map[string]string
	watches  map[chan *agent.ServiceUpdate]bool
	call     *rpcplus.Call
	self     *Service
	SelfAddr string
}

func copyService(service *Service) *Service {
	s := *service
	s.Attrs = make(map[string]string, len(service.Attrs))
	for k, v := range service.Attrs {
		s.Attrs[k] = v
	}
	return &s
}

func makeServiceSet(call *rpcplus.Call) *ServiceSet {
	return &ServiceSet{
		services: make(map[string]*Service),
		filters:  make(map[string]string),
		watches:  make(map[chan *agent.ServiceUpdate]bool),
		call:     call,
	}
}

func (s *ServiceSet) bind(updates chan *agent.ServiceUpdate) chan struct{} {
	// current is an event when enough service updates have been
	// received to bring us to "current" state (when subscribed)
	current := make(chan struct{})
	go func() {
		isCurrent := false
		for update := range updates {
			if update.Addr == "" && update.Name == "" && !isCurrent {
				close(current)
				isCurrent = true
				continue
			}
			s.l.Lock()
			if s.filters != nil && !s.matchFilters(update.Attrs) {
				s.l.Unlock()
				continue
			}
			if s.SelfAddr != update.Addr && update.Online {
				if _, exists := s.services[update.Addr]; !exists {
					host, port, _ := net.SplitHostPort(update.Addr)
					s.services[update.Addr] = &Service{
						Name:    update.Name,
						Addr:    update.Addr,
						Host:    host,
						Port:    port,
						Created: update.Created,
					}
				}
				s.services[update.Addr].Attrs = update.Attrs
			} else {
				if _, exists := s.services[update.Addr]; exists {
					delete(s.services, update.Addr)
				} else {
					if s.SelfAddr == update.Addr {
						s.l.Unlock()
						s.updateWatches(update)
					} else {
						s.l.Unlock()
					}
					continue
				}
			}
			s.l.Unlock()
			s.updateWatches(update)
		}
		s.closeWatches()
	}()
	return current
}

func (s *ServiceSet) updateWatches(update *agent.ServiceUpdate) {
	s.l.Lock()
	watches := make(map[chan *agent.ServiceUpdate]bool, len(s.watches))
	for k, v := range s.watches {
		watches[k] = v
	}
	s.l.Unlock()
	for ch, once := range watches {
		select {
		case ch <- update:
		case <-time.After(time.Millisecond):
		}
		if once {
			close(ch)
			s.l.Lock()
			delete(s.watches, ch)
			s.l.Unlock()
		}
	}
}

func (s *ServiceSet) closeWatches() {
	s.l.Lock()
	watches := make(map[chan *agent.ServiceUpdate]bool, len(s.watches))
	for k, v := range s.watches {
		watches[k] = v
	}
	s.l.Unlock()
	for ch := range watches {
		close(ch)
	}
}

func (s *ServiceSet) matchFilters(attrs map[string]string) bool {
	for key, value := range s.filters {
		if attrs[key] != value {
			return false
		}
	}
	return true
}

func (s *ServiceSet) Leader() *Service {
	services := s.Services()
	if len(services) > 0 {
		if s.self != nil && services[0].Created > s.self.Created {
			return s.self
		}
		return services[0]
	}
	if s.self != nil {
		return s.self
	}
	return nil
}

func (s *ServiceSet) Leaders() chan *Service {
	leaders := make(chan *Service)
	updates := s.Watch(false, false)
	go func() {
		leader := s.Leader()
		leaders <- leader
		for update := range updates {
			if !update.Online && update.Addr == leader.Addr {
				leader = s.Leader()
				leaders <- leader
			}
		}
	}()
	return leaders
}

type serviceByAge []*Service

func (a serviceByAge) Len() int           { return len(a) }
func (a serviceByAge) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a serviceByAge) Less(i, j int) bool { return a[i].Created < a[j].Created }

func (s *ServiceSet) Services() []*Service {
	s.l.Lock()
	defer s.l.Unlock()
	list := make([]*Service, 0, len(s.services))
	for _, service := range s.services {
		list = append(list, copyService(service))
	}
	if len(list) > 0 {
		sort.Sort(serviceByAge(list))
	}
	return list
}

func (s *ServiceSet) Addrs() []string {
	list := make([]string, 0, len(s.services))
	for _, service := range s.Services() {
		list = append(list, service.Addr)
	}
	return list
}

func (s *ServiceSet) Select(attrs map[string]string) []*Service {
	s.l.Lock()
	defer s.l.Unlock()
	list := make([]*Service, 0, len(s.services))
outer:
	for _, service := range s.services {
		for key, value := range attrs {
			if service.Attrs[key] != value {
				continue outer
			}
		}
		list = append(list, service)
	}
	return list
}

func (s *ServiceSet) Filter(attrs map[string]string) {
	s.l.Lock()
	defer s.l.Unlock()
	s.filters = attrs
	for key, service := range s.services {
		if !s.matchFilters(service.Attrs) {
			delete(s.services, key)
		}
	}
}

func (s *ServiceSet) Watch(bringCurrent bool, fireOnce bool) chan *agent.ServiceUpdate {
	s.l.Lock()
	defer s.l.Unlock()
	var updates chan *agent.ServiceUpdate
	if bringCurrent {
		updates = make(chan *agent.ServiceUpdate, len(s.services))
		for _, service := range s.services {
			updates <- &agent.ServiceUpdate{
				Name:    service.Name,
				Addr:    service.Addr,
				Online:  true,
				Attrs:   service.Attrs,
				Created: service.Created,
			}
		}
	} else {
		updates = make(chan *agent.ServiceUpdate)
	}
	s.watches[updates] = fireOnce
	return updates
}

func (s *ServiceSet) Unwatch(ch chan *agent.ServiceUpdate) {
	s.l.Lock()
	defer s.l.Unlock()
	close(ch)
	delete(s.watches, ch)
}

func (s *ServiceSet) Close() error {
	return s.call.CloseStream()
}

type Client struct {
	l             sync.Mutex
	client        *rpcplus.Client
	heartbeats    map[string]chan struct{}
	expandedAddrs map[string]string
	names         map[string]string
}

func NewClient() (*Client, error) {
	addr := os.Getenv("DISCOVERD")
	if addr == "" {
		addr = "127.0.0.1:1111"
	}
	return NewClientUsingAddress(addr)
}

func NewClientUsingAddress(addr string) (*Client, error) {
	client, err := rpcplus.DialHTTP("tcp", addr)
	return &Client{
		client:        client,
		heartbeats:    make(map[string]chan struct{}),
		expandedAddrs: make(map[string]string),
		names:         make(map[string]string),
	}, err
}

func (c *Client) NewServiceSet(name string) (*ServiceSet, error) {
	updates := make(chan *agent.ServiceUpdate)
	call := c.client.StreamGo("Agent.Subscribe", &agent.Args{
		Name: name,
	}, updates)
	set := makeServiceSet(call)
	<-set.bind(updates)
	return set, nil
}

func (c *Client) Services(name string, timeout time.Duration) ([]*Service, error) {
	set, err := c.NewServiceSet(name)
	if err != nil {
		return nil, err
	}
	defer set.Close()
	select {
	case <-set.Watch(true, true):
		return set.Services(), nil
	case <-time.After(timeout):
		return nil, errors.New("discover: timeout exceeded")
	}
}

func (c *Client) Register(name, addr string) error {
	return c.RegisterWithAttributes(name, addr, nil)
}

func (c *Client) RegisterWithSet(name, addr string, attributes map[string]string) (*ServiceSet, error) {
	err := c.RegisterWithAttributes(name, addr, attributes)
	if err != nil {
		return nil, err
	}
	set, err := c.NewServiceSet(name)
	if err != nil {
		c.Unregister(name, addr)
		return nil, err
	}
	set.l.Lock()
	c.l.Lock()
	set.SelfAddr = c.expandedAddrs[addr]
	c.l.Unlock()
	set.l.Unlock()
	updates := set.Watch(true, false)
	for update := range updates {
		if update.Addr == set.SelfAddr {
			set.Unwatch(updates)
			break
		}
	}
	set.l.Lock()
	set.self = set.services[set.SelfAddr]
	delete(set.services, set.SelfAddr)
	set.l.Unlock()
	return set, nil
}

func (c *Client) RegisterAndStandby(name, addr string, attributes map[string]string) (chan *Service, error) {
	set, err := c.RegisterWithSet(name, addr, attributes)
	if err != nil {
		return nil, err
	}
	standbyCh := make(chan *Service)
	go func() {
		for leader := range set.Leaders() {
			if leader.Addr == set.SelfAddr {
				set.Close()
				standbyCh <- leader
				return
			}
		}
	}()
	return standbyCh, nil
}

func (c *Client) RegisterWithAttributes(name, addr string, attributes map[string]string) error {
	args := &agent.Args{
		Name:  name,
		Addr:  addr,
		Attrs: attributes,
	}
	var ret string
	err := c.client.Call("Agent.Register", args, &ret)
	if err != nil {
		return errors.New("discover: register failed: " + err.Error())
	}
	done := make(chan struct{})
	c.l.Lock()
	c.heartbeats[args.Addr] = done
	c.expandedAddrs[args.Addr] = ret
	c.names[args.Addr] = name
	c.l.Unlock()
	go func() {
		ticker := time.NewTicker(agent.HeartbeatIntervalSecs * time.Second) // TODO: add jitter
		defer ticker.Stop()
		for {
			select {
			case <-ticker.C:
				// TODO: log error here
				c.client.Call("Agent.Heartbeat", &agent.Args{
					Name: name,
					Addr: args.Addr,
				}, &struct{}{})
			case <-done:
				return
			}
		}
	}()
	return nil
}

func (c *Client) Unregister(name, addr string) error {
	args := &agent.Args{
		Name: name,
		Addr: addr,
	}
	c.l.Lock()
	close(c.heartbeats[args.Addr])
	delete(c.heartbeats, args.Addr)
	c.l.Unlock()
	err := c.client.Call("Agent.Unregister", args, &struct{}{})
	if err != nil {
		return errors.New("discover: unregister failed: " + err.Error())
	}
	return nil
}

func (c *Client) UnregisterAll() error {
	c.l.Lock()
	addrs := make([]string, 0, len(c.heartbeats))
	names := make([]string, 0, len(c.heartbeats))
	for addr, _ := range c.heartbeats {
		addrs = append(addrs, addr)
		names = append(names, c.names[addr])
	}
	c.l.Unlock()
	for i := range addrs {
		err := c.Unregister(names[i], addrs[i])
		if err != nil {
			return err
		}
	}
	return nil
}

var defaultClient *Client

func Connect(addr string) (err error) {
	if addr == "" {
		defaultClient, err = NewClient()
		return
	}
	defaultClient, err = NewClientUsingAddress(addr)
	return
}

func ensureDefaultConnected() error {
	if defaultClient == nil {
		return Connect("")
	}
	return nil
}

func NewServiceSet(name string) (*ServiceSet, error) {
	if err := ensureDefaultConnected(); err != nil {
		return nil, err
	}
	return defaultClient.NewServiceSet(name)
}

func Services(name string, timeout time.Duration) ([]*Service, error) {
	if err := ensureDefaultConnected(); err != nil {
		return nil, err
	}
	return defaultClient.Services(name, timeout)
}

func Register(name, addr string) error {
	if err := ensureDefaultConnected(); err != nil {
		return err
	}
	return defaultClient.Register(name, addr)
}

func RegisterWithSet(name, addr string, attributes map[string]string) (*ServiceSet, error) {
	if err := ensureDefaultConnected(); err != nil {
		return nil, err
	}
	return defaultClient.RegisterWithSet(name, addr, attributes)
}

func RegisterAndStandby(name, addr string, attributes map[string]string) (chan *Service, error) {
	if err := ensureDefaultConnected(); err != nil {
		return nil, err
	}
	return defaultClient.RegisterAndStandby(name, addr, attributes)
}

func RegisterWithAttributes(name, addr string, attributes map[string]string) error {
	if err := ensureDefaultConnected(); err != nil {
		return err
	}
	return defaultClient.RegisterWithAttributes(name, addr, attributes)
}

func Unregister(name, addr string) error {
	if err := ensureDefaultConnected(); err != nil {
		return err
	}
	return defaultClient.Unregister(name, addr)
}

func UnregisterAll() error {
	if err := ensureDefaultConnected(); err != nil {
		return err
	}
	return defaultClient.UnregisterAll()
}
<commit_msg>discoverd/client: lock it up<commit_after>package discoverd

import (
	"errors"
	"net"
	"os"
	"sort"
	"sync"
	"time"

	"github.com/flynn/discoverd/agent"
	"github.com/flynn/rpcplus"
)

type Service struct {
	Created uint
	Name    string
	Host    string
	Port    string
	Addr    string
	Attrs   map[string]string
}

type ServiceSet struct {
	l        sync.Mutex
	services map[string]*Service
	filters  map[string]string
	watches  map[chan *agent.ServiceUpdate]bool
	call     *rpcplus.Call
	self     *Service
	SelfAddr string
}

func copyService(service *Service) *Service {
	s := *service
	s.Attrs = make(map[string]string, len(service.Attrs))
	for k, v := range service.Attrs {
		s.Attrs[k] = v
	}
	return &s
}

func makeServiceSet(call *rpcplus.Call) *ServiceSet {
	return &ServiceSet{
		services: make(map[string]*Service),
		filters:  make(map[string]string),
		watches:  make(map[chan *agent.ServiceUpdate]bool),
		call:     call,
	}
}

func (s *ServiceSet) bind(updates chan *agent.ServiceUpdate) chan struct{} {
	// current is an event when enough service updates have been
	// received to bring us to "current" state (when subscribed)
	current := make(chan struct{})
	go func() {
		isCurrent := false
		for update := range updates {
			if update.Addr == "" && update.Name == "" && !isCurrent {
				close(current)
				isCurrent = true
				continue
			}
			s.l.Lock()
			if s.filters != nil && !s.matchFilters(update.Attrs) {
				s.l.Unlock()
				continue
			}
			if s.SelfAddr != update.Addr && update.Online {
				if _, exists := s.services[update.Addr]; !exists {
					host, port, _ := net.SplitHostPort(update.Addr)
					s.services[update.Addr] = &Service{
						Name:    update.Name,
						Addr:    update.Addr,
						Host:    host,
						Port:    port,
						Created: update.Created,
					}
				}
				s.services[update.Addr].Attrs = update.Attrs
			} else {
				if _, exists := s.services[update.Addr]; exists {
					delete(s.services, update.Addr)
				} else {
					if s.SelfAddr == update.Addr {
						s.l.Unlock()
						s.updateWatches(update)
					} else {
						s.l.Unlock()
					}
					continue
				}
			}
			s.l.Unlock()
			s.updateWatches(update)
		}
		s.closeWatches()
	}()
	return current
}

func (s *ServiceSet) updateWatches(update *agent.ServiceUpdate) {
	s.l.Lock()
	watches := make(map[chan *agent.ServiceUpdate]bool, len(s.watches))
	for k, v := range s.watches {
		watches[k] = v
	}
	s.l.Unlock()
	for ch, once := range watches {
		select {
		case ch <- update:
		case <-time.After(time.Millisecond):
		}
		if once {
			close(ch)
			s.l.Lock()
			delete(s.watches, ch)
			s.l.Unlock()
		}
	}
}

func (s *ServiceSet) closeWatches() {
	s.l.Lock()
	watches := make(map[chan *agent.ServiceUpdate]bool, len(s.watches))
	for k, v := range s.watches {
		watches[k] = v
	}
	s.l.Unlock()
	for ch := range watches {
		close(ch)
	}
}

func (s *ServiceSet) matchFilters(attrs map[string]string) bool {
	for key, value := range s.filters {
		if attrs[key] != value {
			return false
		}
	}
	return true
}

func (s *ServiceSet) Leader() *Service {
	services := s.Services()
	if len(services) > 0 {
		if s.self != nil && services[0].Created > s.self.Created {
			return s.self
		}
		return services[0]
	}
	if s.self != nil {
		return s.self
	}
	return nil
}

func (s *ServiceSet) Leaders() chan *Service {
	leaders := make(chan *Service)
	updates := s.Watch(false, false)
	go func() {
		leader := s.Leader()
		leaders <- leader
		for update := range updates {
			if !update.Online && update.Addr == leader.Addr {
				leader = s.Leader()
				leaders <- leader
			}
		}
	}()
	return leaders
}

type serviceByAge []*Service

func (a serviceByAge) Len() int           { return len(a) }
func (a serviceByAge) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a serviceByAge) Less(i, j int) bool { return a[i].Created < a[j].Created }

func (s *ServiceSet) Services() []*Service {
	s.l.Lock()
	defer s.l.Unlock()
	list := make([]*Service, 0, len(s.services))
	for _, service := range s.services {
		list = append(list, copyService(service))
	}
	if len(list) > 0 {
		sort.Sort(serviceByAge(list))
	}
	return list
}

func (s *ServiceSet) Addrs() []string {
	list := make([]string, 0, len(s.services))
	for _, service := range s.Services() {
		list = append(list, service.Addr)
	}
	return list
}

func (s *ServiceSet) Select(attrs map[string]string) []*Service {
	s.l.Lock()
	defer s.l.Unlock()
	list := make([]*Service, 0, len(s.services))
outer:
	for _, service := range s.services {
		for key, value := range attrs {
			if service.Attrs[key] != value {
				continue outer
			}
		}
		list = append(list, service)
	}
	return list
}

func (s *ServiceSet) Filter(attrs map[string]string) {
	s.l.Lock()
	defer s.l.Unlock()
	s.filters = attrs
	for key, service := range s.services {
		if !s.matchFilters(service.Attrs) {
			delete(s.services, key)
		}
	}
}

func (s *ServiceSet) Watch(bringCurrent bool, fireOnce bool) chan *agent.ServiceUpdate {
	s.l.Lock()
	defer s.l.Unlock()
	var updates chan *agent.ServiceUpdate
	if bringCurrent {
		updates = make(chan *agent.ServiceUpdate, len(s.services))
		for _, service := range s.services {
			updates <- &agent.ServiceUpdate{
				Name:    service.Name,
				Addr:    service.Addr,
				Online:  true,
				Attrs:   service.Attrs,
				Created: service.Created,
			}
		}
	} else {
		updates = make(chan *agent.ServiceUpdate)
	}
	s.watches[updates] = fireOnce
	return updates
}

func (s *ServiceSet) Unwatch(ch chan *agent.ServiceUpdate) {
	s.l.Lock()
	defer s.l.Unlock()
	close(ch)
	delete(s.watches, ch)
}

func (s *ServiceSet) Close() error {
	return s.call.CloseStream()
}

type Client struct {
	l             sync.Mutex
	client        *rpcplus.Client
	heartbeats    map[string]chan struct{}
	expandedAddrs map[string]string
	names         map[string]string
}

func NewClient() (*Client, error) {
	addr := os.Getenv("DISCOVERD")
	if addr == "" {
		addr = "127.0.0.1:1111"
	}
	return NewClientUsingAddress(addr)
}

func NewClientUsingAddress(addr string) (*Client, error) {
	client, err := rpcplus.DialHTTP("tcp", addr)
	return &Client{
		client:        client,
		heartbeats:    make(map[string]chan struct{}),
		expandedAddrs: make(map[string]string),
		names:         make(map[string]string),
	}, err
}

func (c *Client) NewServiceSet(name string) (*ServiceSet, error) {
	updates := make(chan *agent.ServiceUpdate)
	call := c.client.StreamGo("Agent.Subscribe", &agent.Args{
		Name: name,
	}, updates)
	set := makeServiceSet(call)
	<-set.bind(updates)
	return set, nil
}

func (c *Client) Services(name string, timeout time.Duration) ([]*Service, error) {
	set, err := c.NewServiceSet(name)
	if err != nil {
		return nil, err
	}
	defer set.Close()
	select {
	case <-set.Watch(true, true):
		return set.Services(), nil
	case <-time.After(timeout):
		return nil, errors.New("discover: timeout exceeded")
	}
}

func (c *Client) Register(name, addr string) error {
	return c.RegisterWithAttributes(name, addr, nil)
}

func (c *Client) RegisterWithSet(name, addr string, attributes map[string]string) (*ServiceSet, error) {
	err := c.RegisterWithAttributes(name, addr, attributes)
	if err != nil {
		return nil, err
	}
	set, err := c.NewServiceSet(name)
	if err != nil {
		c.Unregister(name, addr)
		return nil, err
	}
	set.l.Lock()
	c.l.Lock()
	set.SelfAddr = c.expandedAddrs[addr]
	c.l.Unlock()
	set.l.Unlock()
	updates := set.Watch(true, false)
	for update := range updates {
		if update.Addr == set.SelfAddr {
			set.Unwatch(updates)
			break
		}
	}
	set.l.Lock()
	set.self = set.services[set.SelfAddr]
	delete(set.services, set.SelfAddr)
	set.l.Unlock()
	return set, nil
}

func (c *Client) RegisterAndStandby(name, addr string, attributes map[string]string) (chan *Service, error) {
	set, err := c.RegisterWithSet(name, addr, attributes)
	if err != nil {
		return nil, err
	}
	standbyCh := make(chan *Service)
	go func() {
		for leader := range set.Leaders() {
			if leader.Addr == set.SelfAddr {
				set.Close()
				standbyCh <- leader
				return
			}
		}
	}()
	return standbyCh, nil
}

func (c *Client) RegisterWithAttributes(name, addr string, attributes map[string]string) error {
	args := &agent.Args{
		Name:  name,
		Addr:  addr,
		Attrs: attributes,
	}
	var ret string
	err := c.client.Call("Agent.Register", args, &ret)
	if err != nil {
		return errors.New("discover: register failed: " + err.Error())
	}
	done := make(chan struct{})
	c.l.Lock()
	c.heartbeats[args.Addr] = done
	c.expandedAddrs[args.Addr] = ret
	c.names[args.Addr] = name
	c.l.Unlock()
	go func() {
		ticker := time.NewTicker(agent.HeartbeatIntervalSecs * time.Second) // TODO: add jitter
		defer ticker.Stop()
		for {
			select {
			case <-ticker.C:
				// TODO: log error here
				c.client.Call("Agent.Heartbeat", &agent.Args{
					Name: name,
					Addr: args.Addr,
				}, &struct{}{})
			case <-done:
				return
			}
		}
	}()
	return nil
}

func (c *Client) Unregister(name, addr string) error {
	args := &agent.Args{
		Name: name,
		Addr: addr,
	}
	c.l.Lock()
	close(c.heartbeats[args.Addr])
	delete(c.heartbeats, args.Addr)
	c.l.Unlock()
	err := c.client.Call("Agent.Unregister", args, &struct{}{})
	if err != nil {
		return errors.New("discover: unregister failed: " + err.Error())
	}
	return nil
}

func (c *Client) UnregisterAll() error {
	c.l.Lock()
	addrs := make([]string, 0, len(c.heartbeats))
	names := make([]string, 0, len(c.heartbeats))
	for addr, _ := range c.heartbeats {
		addrs = append(addrs, addr)
		names = append(names, c.names[addr])
	}
	c.l.Unlock()
	for i := range addrs {
		err := c.Unregister(names[i], addrs[i])
		if err != nil {
			return err
		}
	}
	return nil
}

var defaultClient *Client
var defaultEnsureLock = &sync.Mutex{}

func Connect(addr string) (err error) {
	if addr == "" {
		defaultClient, err = NewClient()
		return
	}
	defaultClient, err = NewClientUsingAddress(addr)
	return
}

func ensureDefaultConnected() error {
	defaultEnsureLock.Lock()
	defer defaultEnsureLock.Unlock()
	if defaultClient == nil {
		return Connect("")
	}
	return nil
}

func NewServiceSet(name string) (*ServiceSet, error) {
	if err := ensureDefaultConnected(); err != nil {
		return nil, err
	}
	return defaultClient.NewServiceSet(name)
}

func Services(name string, timeout time.Duration) ([]*Service, error) {
	if err := ensureDefaultConnected(); err != nil {
		return nil, err
	}
	return defaultClient.Services(name, timeout)
}

func Register(name, addr string) error {
	if err := ensureDefaultConnected(); err != nil {
		return err
	}
	return defaultClient.Register(name, addr)
}

func RegisterWithSet(name, addr string, attributes map[string]string) (*ServiceSet, error) {
	if err := ensureDefaultConnected(); err != nil {
		return nil, err
	}
	return defaultClient.RegisterWithSet(name, addr, attributes)
}

func RegisterAndStandby(name, addr string, attributes map[string]string) (chan *Service, error) {
	if err := ensureDefaultConnected(); err != nil {
		return nil, err
	}
	return defaultClient.RegisterAndStandby(name, addr, attributes)
}

func RegisterWithAttributes(name, addr string, attributes map[string]string) error {
	if err := ensureDefaultConnected(); err != nil {
		return err
	}
	return defaultClient.RegisterWithAttributes(name, addr, attributes)
}

func Unregister(name, addr string) error {
	if err := ensureDefaultConnected(); err != nil {
		return err
	}
	return defaultClient.Unregister(name, addr)
}

func UnregisterAll() error {
	if err := ensureDefaultConnected(); err != nil {
		return err
	}
	return defaultClient.UnregisterAll()
}
<|endoftext|>


<commit_before>// Package ionic provides a direct representation of the endpoints and objects
// within the Ion Channel API
package ionic

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"

	"github.com/ion-channel/ionic/pagination"
)

const (
	maxIdleConns        = 25
	maxIdleConnsPerHost = 25
	maxPagingLimit      = 100
)

// IonClient represnets a communication layer with the Ion Channel API
type IonClient struct {
	baseURL *url.URL
	client  *http.Client
}

// New takes the base URL of the API and returns a client for talking to the API
// and an error if any issues instantiating the client are encountered
func New(baseURL string) (*IonClient, error) {
	c := &http.Client{
		Transport: &http.Transport{
			MaxIdleConnsPerHost: maxIdleConnsPerHost,
			MaxIdleConns:        maxIdleConns,
		},
	}

	return NewWithClient(baseURL, c)
}

// NewWithClient takes the base URL of the API and an existing HTTP client.  It
// returns a client for talking to the API and an error if any issues
// instantiating the client are encountered
func NewWithClient(baseURL string, client *http.Client) (*IonClient, error) {
	u, err := url.Parse(baseURL)
	if err != nil {
		return nil, fmt.Errorf("cannot instantiate new ion client: %v", err.Error())
	}

	ic := &IonClient{
		baseURL: u,
		client:  client,
	}

	return ic, nil
}

func (ic *IonClient) createURL(endpoint string, params *url.Values, page *pagination.Pagination) *url.URL {
	u := *ic.baseURL
	u.Path = endpoint

	vals := &url.Values{}
	if params != nil {
		vals = params
	}

	if page != nil {
		page.AddParams(vals)
	}

	u.RawQuery = vals.Encode()
	return &u
}

func (ic *IonClient) do(method, endpoint, token string, params *url.Values, payload bytes.Buffer, headers http.Header, page *pagination.Pagination) (json.RawMessage, error) {
	if page == nil || page.Limit > 0 {
		ir, err := ic._do(method, endpoint, token, params, payload, headers, page)
		if err != nil {
			return nil, err
		}

		return ir.Data, nil
	}

	page = pagination.New(0, maxPagingLimit)
	var data json.RawMessage
	data = append(data, []byte("[")...)

	total := 1
	for page.Offset < total {
		ir, err := ic._do(method, endpoint, token, params, payload, headers, page)
		if err != nil {
			return nil, fmt.Errorf("trouble paging from API: %v", err.Error())
		}
		data = append(data, ir.Data[1:len(ir.Data)-1]...)
		data = append(data, []byte(",")...)
		page.Up()
		total = ir.Meta.TotalCount
	}

	data = append(data[:len(data)-1], []byte("]")...)
	return data, nil
}

func (ic *IonClient) _do(method, endpoint, token string, params *url.Values, payload bytes.Buffer, headers http.Header, page *pagination.Pagination) (*IonResponse, error) {
	u := ic.createURL(endpoint, params, page)

	req, err := http.NewRequest(strings.ToUpper(method), u.String(), &payload)
	if err != nil {
		return nil, fmt.Errorf("failed to create request: %v", err.Error())
	}

	if headers != nil {
		req.Header = headers
	}

	if token != "" {
		req.Header.Add("Authorization", fmt.Sprintf("Bearer %v", token))
	}

	resp, err := ic.client.Do(req)
	if err != nil {
		return nil, fmt.Errorf("failed http request: %v", err.Error())
	}
	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("failed to read response body: %v", err.Error())
	}

	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		return nil, fmt.Errorf("bad response from API: %v, Body: %v", resp.Status, string(body))
	}

	var ir IonResponse
	err = json.Unmarshal(body, &ir)
	if err != nil {
		return nil, fmt.Errorf("malformed response: %v", err.Error())
	}

	return &ir, nil
}

// Delete takes an endpoint, token, params, and headers to pass as a delete call to the
// API.  It will return a json RawMessage for the response and any errors it
// encounters with the API.
func (ic *IonClient) Delete(endpoint, token string, params *url.Values, headers http.Header) (json.RawMessage, error) {
	return ic.do("DELETE", endpoint, token, params, bytes.Buffer{}, headers, nil)
}

// Get takes an endpoint, token, params, headers, and pagination params to pass as a
// get call to the API.  It will return a json RawMessage for the response and
// any errors it encounters with the API.
func (ic *IonClient) Get(endpoint, token string, params *url.Values, headers http.Header, page *pagination.Pagination) (json.RawMessage, error) {
	return ic.do("GET", endpoint, token, params, bytes.Buffer{}, headers, page)
}

// Post takes an endpoint, token, params, payload, and headers to pass as a post call
// to the API.  It will return a json RawMessage for the response and any errors
// it encounters with the API.
func (ic *IonClient) Post(endpoint, token string, params *url.Values, payload bytes.Buffer, headers http.Header) (json.RawMessage, error) {
	return ic.do("POST", endpoint, token, params, payload, headers, nil)
}

// Put takes an endpoint, token, params, payload, and headers to pass as a put call to
// the API.  It will return a json RawMessage for the response and any errors it
// encounters with the API.
func (ic *IonClient) Put(endpoint, token string, params *url.Values, payload bytes.Buffer, headers http.Header) (json.RawMessage, error) {
	return ic.do("PUT", endpoint, token, params, payload, headers, nil)
}
<commit_msg>remove extra logging bits<commit_after>// Package ionic provides a direct representation of the endpoints and objects
// within the Ion Channel API
package ionic

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"

	"github.com/ion-channel/ionic/pagination"
)

const (
	maxIdleConns        = 25
	maxIdleConnsPerHost = 25
	maxPagingLimit      = 100
)

// IonClient represnets a communication layer with the Ion Channel API
type IonClient struct {
	baseURL *url.URL
	client  *http.Client
}

// New takes the base URL of the API and returns a client for talking to the API
// and an error if any issues instantiating the client are encountered
func New(baseURL string) (*IonClient, error) {
	c := &http.Client{
		Transport: &http.Transport{
			MaxIdleConnsPerHost: maxIdleConnsPerHost,
			MaxIdleConns:        maxIdleConns,
		},
	}

	return NewWithClient(baseURL, c)
}

// NewWithClient takes the base URL of the API and an existing HTTP client.  It
// returns a client for talking to the API and an error if any issues
// instantiating the client are encountered
func NewWithClient(baseURL string, client *http.Client) (*IonClient, error) {
	u, err := url.Parse(baseURL)
	if err != nil {
		return nil, fmt.Errorf("cannot instantiate new ion client: %v", err.Error())
	}

	ic := &IonClient{
		baseURL: u,
		client:  client,
	}

	return ic, nil
}

func (ic *IonClient) createURL(endpoint string, params *url.Values, page *pagination.Pagination) *url.URL {
	u := *ic.baseURL
	u.Path = endpoint

	vals := &url.Values{}
	if params != nil {
		vals = params
	}

	if page != nil {
		page.AddParams(vals)
	}

	u.RawQuery = vals.Encode()
	return &u
}

func (ic *IonClient) do(method, endpoint, token string, params *url.Values, payload bytes.Buffer, headers http.Header, page *pagination.Pagination) (json.RawMessage, error) {
	if page == nil || page.Limit > 0 {
		ir, err := ic._do(method, endpoint, token, params, payload, headers, page)
		if err != nil {
			return nil, err
		}

		return ir.Data, nil
	}

	page = pagination.New(0, maxPagingLimit)
	var data json.RawMessage
	data = append(data, []byte("[")...)

	total := 1
	for page.Offset < total {
		ir, err := ic._do(method, endpoint, token, params, payload, headers, page)
		if err != nil {
			return nil, fmt.Errorf("trouble paging from API: %v", err.Error())
		}
		data = append(data, ir.Data[1:len(ir.Data)-1]...)
		data = append(data, []byte(",")...)
		page.Up()
		total = ir.Meta.TotalCount
	}

	data = append(data[:len(data)-1], []byte("]")...)
	return data, nil
}

func (ic *IonClient) _do(method, endpoint, token string, params *url.Values, payload bytes.Buffer, headers http.Header, page *pagination.Pagination) (*IonResponse, error) {
	u := ic.createURL(endpoint, params, page)

	req, err := http.NewRequest(strings.ToUpper(method), u.String(), &payload)
	if err != nil {
		return nil, fmt.Errorf("failed to create request: %v", err.Error())
	}

	if headers != nil {
		req.Header = headers
	}

	if token != "" {
		req.Header.Add("Authorization", fmt.Sprintf("Bearer %v", token))
	}

	resp, err := ic.client.Do(req)
	if err != nil {
		return nil, fmt.Errorf("failed http request: %v", err.Error())
	}
	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("failed to read response body: %v", err.Error())
	}

	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		return nil, fmt.Errorf("error response from API: %v", resp.Status)
	}

	var ir IonResponse
	err = json.Unmarshal(body, &ir)
	if err != nil {
		return nil, fmt.Errorf("malformed response: %v", err.Error())
	}

	return &ir, nil
}

// Delete takes an endpoint, token, params, and headers to pass as a delete call to the
// API.  It will return a json RawMessage for the response and any errors it
// encounters with the API.
func (ic *IonClient) Delete(endpoint, token string, params *url.Values, headers http.Header) (json.RawMessage, error) {
	return ic.do("DELETE", endpoint, token, params, bytes.Buffer{}, headers, nil)
}

// Get takes an endpoint, token, params, headers, and pagination params to pass as a
// get call to the API.  It will return a json RawMessage for the response and
// any errors it encounters with the API.
func (ic *IonClient) Get(endpoint, token string, params *url.Values, headers http.Header, page *pagination.Pagination) (json.RawMessage, error) {
	return ic.do("GET", endpoint, token, params, bytes.Buffer{}, headers, page)
}

// Post takes an endpoint, token, params, payload, and headers to pass as a post call
// to the API.  It will return a json RawMessage for the response and any errors
// it encounters with the API.
func (ic *IonClient) Post(endpoint, token string, params *url.Values, payload bytes.Buffer, headers http.Header) (json.RawMessage, error) {
	return ic.do("POST", endpoint, token, params, payload, headers, nil)
}

// Put takes an endpoint, token, params, payload, and headers to pass as a put call to
// the API.  It will return a json RawMessage for the response and any errors it
// encounters with the API.
func (ic *IonClient) Put(endpoint, token string, params *url.Values, payload bytes.Buffer, headers http.Header) (json.RawMessage, error) {
	return ic.do("PUT", endpoint, token, params, payload, headers, nil)
}
<|endoftext|>


<commit_before>package gobacklog

import (
	"bytes"
	"encoding/json"
	// "fmt"
	"errors"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"strings"
)

// Verbose is bool
var Verbose = false

// BacklogErrorResponse is error model
type BacklogErrorResponse struct {
	Errors BacklogErrorSlice `json:"errors"`
}

// BacklogError is error model
// +gen * slice:"Where,Count,SortBy,GroupBy[string],Select[string]"
type BacklogError struct {
	Message  string `json:"message,omitempty"`
	Code     int    `json:"code,omitempty"`
	MoreInfo string `json:"moreInfo,omitempty"`
}

// error returns Errors
func (e *BacklogErrorResponse) error() error {
	if len(e.Errors) == 0 {
		return nil
	}

	s := e.Errors.SelectString(func(b *BacklogError) string {
		return b.Message
	})

	return errors.New(strings.Join(s, ", "))
}

// HTTP interface of HTTP METHODS's methods
type HTTP interface {
	Get()
	Post()
	Put()
	Delete()
}

// Client is
type Client struct {
	BaseURL    string
	HTTPClient *http.Client
	APIKey     string
}

// NewClient returns Backlog HTTP Client
func NewClient(baseURL, APIKey string) *Client {
	if strings.HasSuffix(baseURL, "/") {
		baseURL = baseURL[0 : len(baseURL)-1]
	}
	s := &Client{
		BaseURL: baseURL,
		APIKey:  APIKey,
	}

	return s
}

// Get GET method
func (c *Client) Get(endpoint string, params url.Values) ([]byte, error) {
	return c.execute("GET", endpoint, params)
}

// Post POST method
func (c *Client) Post(endpoint string, params url.Values) ([]byte, error) {
	return c.execute("POST", endpoint, params)
}

// Put PUT method
func (c *Client) Put(endpoint string, params url.Values) ([]byte, error) {
	return c.execute("PUT", endpoint, params)
}

// Delete DELETE method
func (c *Client) Delete(endpoint string, params url.Values) ([]byte, error) {
	return c.execute("DELETE", endpoint, params)
}

func (c *Client) appendAPIKey(URL string) string {
	return URL + "?apiKey=" + c.APIKey
}

func (c *Client) buildBody(params map[string]string) url.Values {
	body := url.Values{}
	for k := range params {
		body.Add(k, params[k])
	}
	return body
}

func (c *Client) buildURL(baseURL, endpoint string, params map[string]string) string {
	query := make([]string, len(params))
	for k := range params {
		query = append(query, k+"="+params[k])
	}
	return c.appendAPIKey(c.BaseURL+endpoint) + "&" + strings.Join(query, "&")
}

func (c *Client) buildURLWithValues(baseURL, endpoint string, params url.Values) string {
	return c.appendAPIKey(c.BaseURL+endpoint) + "&" + params.Encode()
}

func (c *Client) parseBody(resp *http.Response) ([]byte, error) {
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		if Verbose {
			log.Println(err)
		}

		return []byte(``), err
	}

	if Verbose {
		log.Printf("[DEBUG] resp: %#+v", resp)
		log.Printf("[DEBUG] body: %v", string(body))
	}

	if resp.StatusCode != 200 {
		var er BacklogErrorResponse
		json.Unmarshal(body, &er)
		return []byte(``), er.error()
	}

	return body, nil
}

func (c *Client) execute(method, endpoint string, params url.Values) ([]byte, error) {
	resp, err := c.executeReturnsResponse(method, endpoint, params)

	if err != nil {
		return []byte(``), err
	}

	return c.parseBody(resp)
}

func (c *Client) executeReturnsResponse(method, endpoint string, params url.Values) (resp *http.Response, err error) {
	if c.HTTPClient == nil {
		c.HTTPClient = http.DefaultClient
	}

	var (
		req        *http.Request
		requestErr error
	)

	if method != "GET" {
		req, requestErr = http.NewRequest(method,
			c.appendAPIKey(c.BaseURL+endpoint),
			bytes.NewBufferString(params.Encode()),
		)
		req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
	} else {
		req, requestErr = http.NewRequest(method,
			c.buildURLWithValues(c.BaseURL, endpoint, params),
			nil,
		)
	}

	if requestErr != nil {
		panic(requestErr)
	}

	return c.HTTPClient.Do(req)
}
<commit_msg>remove trailing “&”<commit_after>package gobacklog

import (
	"bytes"
	"encoding/json"
	// "fmt"
	"errors"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"strings"
)

// Verbose is bool
var Verbose = false

// BacklogErrorResponse is error model
type BacklogErrorResponse struct {
	Errors BacklogErrorSlice `json:"errors"`
}

// BacklogError is error model
// +gen * slice:"Where,Count,SortBy,GroupBy[string],Select[string]"
type BacklogError struct {
	Message  string `json:"message,omitempty"`
	Code     int    `json:"code,omitempty"`
	MoreInfo string `json:"moreInfo,omitempty"`
}

// error returns Errors
func (e *BacklogErrorResponse) error() error {
	if len(e.Errors) == 0 {
		return nil
	}

	s := e.Errors.SelectString(func(b *BacklogError) string {
		return b.Message
	})

	return errors.New(strings.Join(s, ", "))
}

// HTTP interface of HTTP METHODS's methods
type HTTP interface {
	Get()
	Post()
	Put()
	Delete()
}

// Client is
type Client struct {
	BaseURL    string
	HTTPClient *http.Client
	APIKey     string
}

// NewClient returns Backlog HTTP Client
func NewClient(baseURL, APIKey string) *Client {
	if strings.HasSuffix(baseURL, "/") {
		baseURL = baseURL[0 : len(baseURL)-1]
	}
	s := &Client{
		BaseURL: baseURL,
		APIKey:  APIKey,
	}

	return s
}

// Get GET method
func (c *Client) Get(endpoint string, params url.Values) ([]byte, error) {
	return c.execute("GET", endpoint, params)
}

// Post POST method
func (c *Client) Post(endpoint string, params url.Values) ([]byte, error) {
	return c.execute("POST", endpoint, params)
}

// Put PUT method
func (c *Client) Put(endpoint string, params url.Values) ([]byte, error) {
	return c.execute("PUT", endpoint, params)
}

// Delete DELETE method
func (c *Client) Delete(endpoint string, params url.Values) ([]byte, error) {
	return c.execute("DELETE", endpoint, params)
}

func (c *Client) appendAPIKey(URL string) string {
	return URL + "?apiKey=" + c.APIKey
}

func (c *Client) buildBody(params map[string]string) url.Values {
	body := url.Values{}
	for k := range params {
		body.Add(k, params[k])
	}
	return body
}

func (c *Client) buildURL(baseURL, endpoint string, params map[string]string) string {
	query := make([]string, len(params))
	for k := range params {
		query = append(query, k+"="+params[k])
	}
	return c.appendAPIKey(c.BaseURL+endpoint) + "&" + strings.Join(query, "&")
}

func (c *Client) buildURLWithValues(baseURL, endpoint string, params url.Values) string {
	encodedParamsString := params.Encode()
	if len(encodedParamsString) == 0 {
		return c.appendAPIKey(c.BaseURL + endpoint)
	}
	return c.appendAPIKey(c.BaseURL+endpoint) + "&" + encodedParamsString
}

func (c *Client) parseBody(resp *http.Response) ([]byte, error) {
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		if Verbose {
			log.Println(err)
		}

		return []byte(``), err
	}

	if Verbose {
		log.Printf("[DEBUG] resp: %#+v", resp)
		log.Printf("[DEBUG] body: %v", string(body))
	}

	if resp.StatusCode != 200 {
		var er BacklogErrorResponse
		json.Unmarshal(body, &er)
		return []byte(``), er.error()
	}

	return body, nil
}

func (c *Client) execute(method, endpoint string, params url.Values) ([]byte, error) {
	resp, err := c.executeReturnsResponse(method, endpoint, params)

	if err != nil {
		return []byte(``), err
	}

	return c.parseBody(resp)
}

func (c *Client) executeReturnsResponse(method, endpoint string, params url.Values) (resp *http.Response, err error) {
	if c.HTTPClient == nil {
		c.HTTPClient = http.DefaultClient
	}

	var (
		req        *http.Request
		requestErr error
	)

	if method != "GET" {
		req, requestErr = http.NewRequest(method,
			c.appendAPIKey(c.BaseURL+endpoint),
			bytes.NewBufferString(params.Encode()),
		)
		req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
	} else {
		req, requestErr = http.NewRequest(method,
			c.buildURLWithValues(c.BaseURL, endpoint, params),
			nil,
		)
	}

	if requestErr != nil {
		panic(requestErr)
	}

	return c.HTTPClient.Do(req)
}
<|endoftext|>


<commit_before>package twitch

import (
	"bufio"
	"fmt"
	"net"
	"net/textproto"
	"strings"
	"time"
)

const (
	IRCTWITCH = "irc.chat.twitch.tv:6667"
)

type User struct {
	Username    string
	DisplayName string
	UserType    string
	Color       string
	Badges      map[string]int
}

type Message struct {
	Type   msgType
	Time   time.Time
	Action bool
	Emotes []*emote
	Tags   map[string]string
	Text   string
}

type Client struct {
	ircAddress            string
	ircUser               string
	ircToken              string
	connection            *net.Conn
	connActive            bool
	onNewMessage          func(channel string, user User, message Message)
	onNewRoomstateMessage func(channel string, user User, message Message)
	onNewClearchatMessage func(channel string, user User, message Message)
}

func NewClient(username, oauth string) *Client {
	return &Client{
		ircUser:    username,
		ircToken:   oauth,
		ircAddress: IRCTWITCH,
	}
}

func (c *Client) SetIrcAddress(address string) {
	c.ircAddress = address
}

func (c *Client) Say(channel, text string) {
	c.send(fmt.Sprintf("PRIVMSG #%s :%s", channel, text))
}

func (c *Client) Connect() error {
	for {
		conn, err := net.Dial("tcp", c.ircAddress)
		c.connection = &conn
		if err != nil {
			fmt.Println(conn)
			return err
		}

		go c.setupConnection()

		err = c.readConnection(conn)
		if err != nil {
			fmt.Println("connection read error, reconnecting...")
			continue
		}
	}
	return nil
}

func (c *Client) readConnection(conn net.Conn) error {
	reader := bufio.NewReader(conn)
	tp := textproto.NewReader(reader)
	for {
		line, err := tp.ReadLine()
		if err != nil {
			return err
		}
		messages := strings.Split(line, "\r\n")
		if len(messages) == 0 {
			continue
		}
		for _, msg := range messages {
			if !c.connActive && strings.HasPrefix(msg, ":tmi.twitch.tv 001") {
				c.connActive = true
			}
			c.handleLine(msg)
		}
	}
}

func (c *Client) setupConnection() {
	fmt.Fprint(*c.connection, fmt.Sprintf("PASS %s\r\n", c.ircToken))
	fmt.Fprint(*c.connection, fmt.Sprintf("NICK %s\r\n", c.ircUser))
	fmt.Fprint(*c.connection, "CAP REQ :twitch.tv/tags\r\n")
	fmt.Fprint(*c.connection, "CAP REQ :twitch.tv/commands\r\n")
}

func (c *Client) send(line string) {
	if !c.connActive {
		time.Sleep(time.Second * 1)
		c.send(line)
		return
	}
	fmt.Fprint(*c.connection, line+"\r\n")
}

func (c *Client) handleLine(line string) {
	if strings.HasPrefix(line, "PING") {
		c.send(fmt.Sprintf(strings.Replace(line, "PING", "PONG", 1)))
	}
	if strings.HasPrefix(line, "@") {
		message := parseMessage(line)

		Channel := message.Channel

		User := &User{
			Username:    message.Username,
			DisplayName: message.DisplayName,
			UserType:    message.UserType,
			Color:       message.Color,
			Badges:      message.Badges,
		}

		clientMessage := &Message{
			Type:   message.Type,
			Time:   message.Time,
			Action: message.Action,
			Emotes: message.Emotes,
			Tags:   message.Tags,
			Text:   message.Text,
		}

		switch message.Type {
		case PRIVMSG:
			c.onNewMessage(Channel, *User, *clientMessage)
			break
		case ROOMSTATE:
			c.onNewRoomstateMessage(Channel, *User, *clientMessage)
			break
		case CLEARCHAT:
			c.onNewClearchatMessage(Channel, *User, *clientMessage)
			break
		}
	}
}

func (c *Client) OnNewMessage(callback func(channel string, user User, message Message)) {
	c.onNewMessage = callback
}

func (c *Client) OnNewRoomstateMessage(callback func(channel string, user User, message Message)) {
	c.onNewRoomstateMessage = callback
}

func (c *Client) OnNewClearchatMessage(callback func(channel string, user User, message Message)) {
	c.onNewClearchatMessage = callback
}

func (c *Client) Join(channel string) {
	go c.send(fmt.Sprintf("JOIN #%s", channel))
}
<commit_msg>check if callback is implemented otherwise don't try to call the func<commit_after>package twitch

import (
	"bufio"
	"fmt"
	"net"
	"net/textproto"
	"strings"
	"time"
)

const (
	IRCTWITCH = "irc.chat.twitch.tv:6667"
)

type User struct {
	Username    string
	DisplayName string
	UserType    string
	Color       string
	Badges      map[string]int
}

type Message struct {
	Type   msgType
	Time   time.Time
	Action bool
	Emotes []*emote
	Tags   map[string]string
	Text   string
}

type Client struct {
	ircAddress            string
	ircUser               string
	ircToken              string
	connection            *net.Conn
	connActive            bool
	onNewMessage          func(channel string, user User, message Message)
	onNewRoomstateMessage func(channel string, user User, message Message)
	onNewClearchatMessage func(channel string, user User, message Message)
}

func NewClient(username, oauth string) *Client {
	return &Client{
		ircUser:    username,
		ircToken:   oauth,
		ircAddress: IRCTWITCH,
	}
}

func (c *Client) SetIrcAddress(address string) {
	c.ircAddress = address
}

func (c *Client) Say(channel, text string) {
	c.send(fmt.Sprintf("PRIVMSG #%s :%s", channel, text))
}

func (c *Client) Connect() error {
	for {
		conn, err := net.Dial("tcp", c.ircAddress)
		c.connection = &conn
		if err != nil {
			fmt.Println(conn)
			return err
		}

		go c.setupConnection()

		err = c.readConnection(conn)
		if err != nil {
			fmt.Println("connection read error, reconnecting...")
			continue
		}
	}
	return nil
}

func (c *Client) readConnection(conn net.Conn) error {
	reader := bufio.NewReader(conn)
	tp := textproto.NewReader(reader)
	for {
		line, err := tp.ReadLine()
		if err != nil {
			return err
		}
		messages := strings.Split(line, "\r\n")
		if len(messages) == 0 {
			continue
		}
		for _, msg := range messages {
			if !c.connActive && strings.HasPrefix(msg, ":tmi.twitch.tv 001") {
				c.connActive = true
			}
			c.handleLine(msg)
		}
	}
}

func (c *Client) setupConnection() {
	fmt.Fprint(*c.connection, fmt.Sprintf("PASS %s\r\n", c.ircToken))
	fmt.Fprint(*c.connection, fmt.Sprintf("NICK %s\r\n", c.ircUser))
	fmt.Fprint(*c.connection, "CAP REQ :twitch.tv/tags\r\n")
	fmt.Fprint(*c.connection, "CAP REQ :twitch.tv/commands\r\n")
}

func (c *Client) send(line string) {
	if !c.connActive {
		time.Sleep(time.Second * 1)
		c.send(line)
		return
	}
	fmt.Fprint(*c.connection, line+"\r\n")
}

func (c *Client) handleLine(line string) {
	if strings.HasPrefix(line, "PING") {
		c.send(fmt.Sprintf(strings.Replace(line, "PING", "PONG", 1)))
	}
	if strings.HasPrefix(line, "@") {
		message := parseMessage(line)

		Channel := message.Channel

		User := &User{
			Username:    message.Username,
			DisplayName: message.DisplayName,
			UserType:    message.UserType,
			Color:       message.Color,
			Badges:      message.Badges,
		}

		clientMessage := &Message{
			Type:   message.Type,
			Time:   message.Time,
			Action: message.Action,
			Emotes: message.Emotes,
			Tags:   message.Tags,
			Text:   message.Text,
		}

		switch message.Type {
		case PRIVMSG:
			if c.OnNewMessage != nil {
				c.onNewMessage(Channel, *User, *clientMessage)
			}
			break
		case ROOMSTATE:
			if c.onNewRoomstateMessage != nil {
				c.onNewRoomstateMessage(Channel, *User, *clientMessage)
			}
		case CLEARCHAT:
			if c.onNewRoomstateMessage != nil {
				c.onNewClearchatMessage(Channel, *User, *clientMessage)
			}
		}
	}
}

func (c *Client) OnNewMessage(callback func(channel string, user User, message Message)) {
	c.onNewMessage = callback
}

func (c *Client) OnNewRoomstateMessage(callback func(channel string, user User, message Message)) {
	c.onNewRoomstateMessage = callback
}

func (c *Client) OnNewClearchatMessage(callback func(channel string, user User, message Message)) {
	c.onNewClearchatMessage = callback
}

func (c *Client) Join(channel string) {
	go c.send(fmt.Sprintf("JOIN #%s", channel))
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	ss "github.com/lixin9311/simplevpn/securesocket"
	"github.com/lixin9311/simplevpn/tap"
	"io"
	"log"
	"net"
)

func runClient(conf *Config) error {
	cipher, err := ss.NewCipher(conf.User.Method, conf.User.Password)
	if err != nil {
		return err
	}
	ifce, err := tap.NewTAP()
	if err != nil {
		log.Println("Failed to create TAP device:", err)
		return err
	}
	defer ifce.Close()
	ip, ip_mask, err := net.ParseCIDR(conf.Server.Ip + "/32")
	if err != nil {
		log.Println("Failed to parse ip:", err)
		return err
	}
	ip_mask.IP = ip
	err = tap.Bypass(ip_mask)
	if err != nil {
		log.Println("[Client]: Failed to bypass server address from route:", err)
		return err
	}
	defer tap.Unbypass()
	// reg with server
	conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", conf.Server.Ip, conf.Server.Port))
	if err != nil {
		return err
	}
	secureconn := ss.NewConn(conn, cipher.Copy())
	c := ss.NewPacketStreamConn(secureconn)
	defer c.Close()
	auth := new(Auth)
	auth.Type = Auth_Hello
	mac := ifce.MacAddr()
	auth.MacAddr = mac[:]
	data, err := auth.Marshal()
	if err != nil {
		log.Println("[Client]: Failed to marshal data:", err)
		return err
	}
	_, err = c.Write(data)
	if err != nil {
		log.Println("[Client]: Failed to write socket: ", err)
		return err
	}
	buf := make([]byte, 2048)
	n, err := c.Read(buf)
	if err != nil {
		log.Println("[Client]: Failed to recieve config: ", err)
		return err
	}
	err = auth.Unmarshal(buf[:n])
	if err != nil {
		log.Println("[Client]: Failed to decode recieved config: ", err)
		return err
	}
	if auth.Type != Auth_Welcome {
		return fmt.Errorf("[Client]: Unexpected response type: %s.", Auth_MessageType_name[int32(auth.Type)])
	}
	ip, ip_mask, err = net.ParseCIDR(auth.IP)
	if err != nil {
		log.Println("[Client]: Failed to parse CIDR from response:", err)
		return err
	}
	ip_mask.IP = ip
	err = ifce.SetIP(ip_mask)
	if err != nil {
		log.Println("Failed to set IP address:", err)
		return err
	}
	ip, ip_mask, err = net.ParseCIDR("0.0.0.0/1")
	if err != nil {
		log.Println("Failed to parse address:", err)
	}
	ip_mask.IP = ip
	ip = net.ParseIP(auth.GateWay)
	err = ifce.AddRoute(ip, ip_mask)
	if err != nil {
		log.Println("Failed to set default route, please manually fix that:", err)
	}
	ip, ip_mask, err = net.ParseCIDR("128.0.0.0/1")
	if err != nil {
		log.Println("Failed to parse address:", err)
	}
	ip_mask.IP = ip
	ip = net.ParseIP(auth.GateWay)
	err = ifce.AddRoute(ip, ip_mask)
	if err != nil {
		log.Println("Failed to set default route, please manually fix that", err)
	}
	go PipeThenClose(c, ifce)
	PipeThenClose(ifce, c)
	return nil
}

func PipeThenClose(src, dst io.ReadWriteCloser) {
	defer dst.Close()
	buf := make([]byte, 1522)
	for {
		n, err := src.Read(buf)
		// read may return EOF with n > 0
		// should always process n > 0 bytes before handling error
		if n > 0 {
			// Note: avoid overwrite err returned by Read.
			if _, err := dst.Write(buf[0:n]); err != nil {
				log.Println("write:", err)
				break
			}
		}
		if err != nil {
			// Always "use of closed network connection", but no easy way to
			// identify this specific error. So just leave the error along for now.
			// More info here: https://code.google.com/p/go/issues/detail?id=4373
			/*
				if bool(Debug) && err != io.EOF {
					Debug.Println("read:", err)
				}
			*/
			break
		}
	}
}
<commit_msg>Remove default gateway setting when exit.<commit_after>package main

import (
	"fmt"
	ss "github.com/lixin9311/simplevpn/securesocket"
	"github.com/lixin9311/simplevpn/tap"
	"io"
	"log"
	"net"
)

func runClient(conf *Config) error {
	cipher, err := ss.NewCipher(conf.User.Method, conf.User.Password)
	if err != nil {
		return err
	}
	ifce, err := tap.NewTAP()
	if err != nil {
		log.Println("Failed to create TAP device:", err)
		return err
	}
	defer ifce.Close()
	ip, ip_mask, err := net.ParseCIDR(conf.Server.Ip + "/32")
	if err != nil {
		log.Println("Failed to parse ip:", err)
		return err
	}
	ip_mask.IP = ip
	err = tap.Bypass(ip_mask)
	if err != nil {
		log.Println("[Client]: Failed to bypass server address from route:", err)
		return err
	}
	defer tap.Unbypass()
	// reg with server
	conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", conf.Server.Ip, conf.Server.Port))
	if err != nil {
		return err
	}
	secureconn := ss.NewConn(conn, cipher.Copy())
	c := ss.NewPacketStreamConn(secureconn)
	defer c.Close()
	auth := new(Auth)
	auth.Type = Auth_Hello
	mac := ifce.MacAddr()
	auth.MacAddr = mac[:]
	data, err := auth.Marshal()
	if err != nil {
		log.Println("[Client]: Failed to marshal data:", err)
		return err
	}
	_, err = c.Write(data)
	if err != nil {
		log.Println("[Client]: Failed to write socket: ", err)
		return err
	}
	buf := make([]byte, 2048)
	n, err := c.Read(buf)
	if err != nil {
		log.Println("[Client]: Failed to recieve config: ", err)
		return err
	}
	err = auth.Unmarshal(buf[:n])
	if err != nil {
		log.Println("[Client]: Failed to decode recieved config: ", err)
		return err
	}
	if auth.Type != Auth_Welcome {
		return fmt.Errorf("[Client]: Unexpected response type: %s.", Auth_MessageType_name[int32(auth.Type)])
	}
	ip, ip_mask, err = net.ParseCIDR(auth.IP)
	if err != nil {
		log.Println("[Client]: Failed to parse CIDR from response:", err)
		return err
	}
	ip_mask.IP = ip
	err = ifce.SetIP(ip_mask)
	if err != nil {
		log.Println("Failed to set IP address:", err)
		return err
	}
	ip, ip_mask, err = net.ParseCIDR("0.0.0.0/1")
	if err != nil {
		log.Println("Failed to parse address:", err)
	}
	ip_mask.IP = ip
	ip = net.ParseIP(auth.GateWay)
	err = ifce.AddRoute(ip, ip_mask)
	if err != nil {
		log.Println("Failed to set default route, please manually fix that:", err)
	}
	ip, ip_mask, err = net.ParseCIDR("128.0.0.0/1")
	if err != nil {
		log.Println("Failed to parse address:", err)
	}
	ip_mask.IP = ip
	ip = net.ParseIP(auth.GateWay)
	err = ifce.AddRoute(ip, ip_mask)
	if err != nil {
		log.Println("Failed to set default route, please manually fix that", err)
	}
	defer func() {
		ip, ip_mask, _ = net.ParseCIDR("0.0.0.0/1")
		ip_mask.IP = ip
		ip = net.ParseIP(auth.GateWay)
		ifce.DelRoute(ip, ip_mask)
		ip, ip_mask, _ = net.ParseCIDR("128.0.0.0/1")
		ip_mask.IP = ip
		ip = net.ParseIP(auth.GateWay)
		ifce.DelRoute(ip, ip_mask)
	}()
	go PipeThenClose(c, ifce)
	PipeThenClose(ifce, c)
	return nil
}

func PipeThenClose(src, dst io.ReadWriteCloser) {
	defer dst.Close()
	buf := make([]byte, 1522)
	for {
		n, err := src.Read(buf)
		// read may return EOF with n > 0
		// should always process n > 0 bytes before handling error
		if n > 0 {
			// Note: avoid overwrite err returned by Read.
			if _, err := dst.Write(buf[0:n]); err != nil {
				log.Println("write:", err)
				break
			}
		}
		if err != nil {
			// Always "use of closed network connection", but no easy way to
			// identify this specific error. So just leave the error along for now.
			// More info here: https://code.google.com/p/go/issues/detail?id=4373
			/*
				if bool(Debug) && err != io.EOF {
					Debug.Println("read:", err)
				}
			*/
			break
		}
	}
}
<|endoftext|>


<commit_before>package echo

import (
	"encoding/json"
	"encoding/xml"
	"errors"
	"fmt"
	"net/http"
	"reflect"
	"strconv"
	"strings"
	"time"
	"unicode"

	"github.com/webx-top/tagfast"
	"github.com/webx-top/validation"
)

var DefaultHtmlFilter = func(v string) (r string) {
	return v
}

type (
	// Binder is the interface that wraps the Bind method.
	Binder interface {
		Bind(interface{}, Context) error
		MustBind(interface{}, Context) error
	}
	binder struct {
		*Echo
	}
)

func (b *binder) MustBind(i interface{}, c Context) (err error) {
	r := c.Request()
	body := r.Body()
	if body == nil {
		err = NewHTTPError(http.StatusBadRequest, "Request body can't be nil")
		return
	}
	defer body.Close()
	ct := r.Header().Get(HeaderContentType)
	err = ErrUnsupportedMediaType
	if strings.HasPrefix(ct, MIMEApplicationJSON) {
		err = json.NewDecoder(body).Decode(i)
	} else if strings.HasPrefix(ct, MIMEApplicationXML) {
		err = xml.NewDecoder(body).Decode(i)
	} else if strings.HasPrefix(ct, MIMEApplicationForm) {
		err = b.structMap(i, r.PostForm().All())
	} else if strings.Contains(ct, MIMEMultipartForm) {
		err = b.structMap(i, r.Form().All())
	}
	return
}

func (b *binder) Bind(i interface{}, c Context) (err error) {
	err = b.MustBind(i, c)
	if err == ErrUnsupportedMediaType {
		err = nil
	}
	return
}

// StructMap function mapping params to controller's properties
func (b *binder) structMap(m interface{}, data map[string][]string) error {
	return NamedStructMap(b.Echo, m, data, ``)
}

// SplitJSON user[name][test]
func SplitJSON(s string) ([]string, error) {
	var res []string
	var begin, end int
	var isleft bool
	for i, r := range s {
		switch r {
		case '[':
			isleft = true
			if i > 0 && s[i-1] != ']' {
				if begin == end {
					return nil, errors.New(`unknow character`)
				}
				res = append(res, s[begin:end+1])
			}
			begin = i + 1
			end = begin
		case ']':
			if !isleft {
				return nil, errors.New(`unknow character`)
			}
			isleft = false
			if begin != end {
				res = append(res, s[begin:end+1])
				begin = i + 1
				end = begin
			}
		default:
			end = i
		}
		if i == len(s)-1 && begin != end {
			res = append(res, s[begin:end+1])
		}
	}
	return res, nil
}

func NamedStructMap(e *Echo, m interface{}, data map[string][]string, topName string) error {
	vc := reflect.ValueOf(m)
	tc := reflect.TypeOf(m)

	switch tc.Kind() {
	case reflect.Struct:
	case reflect.Ptr:
		vc = vc.Elem()
		tc = tc.Elem()
	}
	var validator *validation.Validation
	for k, t := range data {

		if k == `` || k[0] == '_' {
			continue
		}

		if topName != `` {
			if !strings.HasPrefix(k, topName) {
				continue
			}
			k = k[len(topName)+1:]
		}

		v := t[0]
		names := strings.Split(k, `.`)
		var err error
		length := len(names)
		if length == 1 && strings.HasSuffix(k, `]`) {
			names, err = SplitJSON(k)
			if err != nil {
				e.Logger().Warnf(`Unrecognize form key %v %v`, k, err)
				continue
			}
			length = len(names)
		}
		value := vc
		typev := tc
		for i, name := range names {
			name = strings.Title(name)

			//不是最后一个元素
			if i != length-1 {
				if value.Kind() != reflect.Struct {
					e.Logger().Warnf(`arg error, value kind is %v`, value.Kind())
					break
				}
				value = value.FieldByName(name)
				if !value.IsValid() {
					e.Logger().Warnf(`(%v value is not valid %v)`, name, value)
					break
				}
				if !value.CanSet() {
					e.Logger().Warnf(`can not set %v -> %v`, name, value.Interface())
					break
				}
				if value.Kind() == reflect.Ptr {
					if value.IsNil() {
						value.Set(reflect.New(value.Type().Elem()))
					}
					value = value.Elem()
				}
				typev = value.Type()
				f, _ := typev.FieldByName(name)
				if tagfast.Value(tc, f, `form_options`) == `-` {
					break
				}
			} else {
				if value.Kind() != reflect.Struct {
					e.Logger().Warnf(`arg error, value %v kind is %v`, name, value.Kind())
					break
				}
				tv := value.FieldByName(name)
				if !tv.IsValid() {
					break
				}
				if !tv.CanSet() {
					e.Logger().Warnf(`can not set %v to %v`, k, tv)
					break
				}
				f, _ := typev.FieldByName(name)
				if tagfast.Value(tc, f, `form_options`) == `-` {
					break
				}
				if tv.Kind() == reflect.Ptr {
					tv.Set(reflect.New(tv.Type().Elem()))
					tv = tv.Elem()
				}

				var l interface{}
				switch k := tv.Kind(); k {
				case reflect.String:
					switch tagfast.Value(tc, f, `form_filter`) {
					case `html`:
						v = DefaultHtmlFilter(v)
					}
					l = v
					tv.Set(reflect.ValueOf(l))
				case reflect.Bool:
					l = (v != `false` && v != `0` && v != ``)
					tv.Set(reflect.ValueOf(l))
				case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32:
					dateformat := tagfast.Value(tc, f, `form_format`)
					if dateformat != `` {
						t, err := time.Parse(dateformat, v)
						if err != nil {
							e.Logger().Warnf(`arg %v as int: %v`, v, err)
							l = int(0)
						} else {
							l = int(t.Unix())
						}
					} else {
						x, err := strconv.Atoi(v)
						if err != nil {
							e.Logger().Warnf(`arg %v as int: %v`, v, err)
						}
						l = x
					}
					tv.Set(reflect.ValueOf(l))
				case reflect.Int64:
					dateformat := tagfast.Value(tc, f, `form_format`)
					if dateformat != `` {
						t, err := time.Parse(dateformat, v)
						if err != nil {
							e.Logger().Warnf(`arg %v as int64: %v`, v, err)
							l = int64(0)
						} else {
							l = t.Unix()
						}
					} else {
						x, err := strconv.ParseInt(v, 10, 64)
						if err != nil {
							e.Logger().Warnf(`arg %v as int64: %v`, v, err)
						}
						l = x
					}
					tv.Set(reflect.ValueOf(l))
				case reflect.Float32, reflect.Float64:
					x, err := strconv.ParseFloat(v, 64)
					if err != nil {
						e.Logger().Warnf(`arg %v as float64: %v`, v, err)
					}
					l = x
					tv.Set(reflect.ValueOf(l))
				case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
					dateformat := tagfast.Value(tc, f, `form_format`)
					if dateformat != `` {
						t, err := time.Parse(dateformat, v)
						if err != nil {
							e.Logger().Warnf(`arg %v as uint: %v`, v, err)
							l = uint64(0)
						} else {
							l = uint64(t.Unix())
						}
					} else {
						x, err := strconv.ParseUint(v, 10, 64)
						if err != nil {
							e.Logger().Warnf(`arg %v as uint: %v`, v, err)
						}
						l = x
					}
					tv.Set(reflect.ValueOf(l))
				case reflect.Struct:
					if tvf, ok := tv.Interface().(FromConversion); ok {
						err := tvf.FromString(v)
						if err != nil {
							e.Logger().Warnf(`struct %v invoke FromString faild`, tvf)
						}
					} else if tv.Type().String() == `time.Time` {
						x, err := time.Parse(`2006-01-02 15:04:05.000 -0700`, v)
						if err != nil {
							x, err = time.Parse(`2006-01-02 15:04:05`, v)
							if err != nil {
								x, err = time.Parse(`2006-01-02`, v)
								if err != nil {
									e.Logger().Warnf(`unsupported time format %v, %v`, v, err)
								}
							}
						}
						l = x
						tv.Set(reflect.ValueOf(l))
					} else {
						e.Logger().Warn(`can not set an struct which is not implement Fromconversion interface`)
					}
				case reflect.Ptr:
					e.Logger().Warn(`can not set an ptr of ptr`)
				case reflect.Slice, reflect.Array:
					tt := tv.Type().Elem()
					tk := tt.Kind()

					if tv.IsNil() {
						tv.Set(reflect.MakeSlice(tv.Type(), len(t), len(t)))
					}

					for i, s := range t {
						var err error
						switch tk {
						case reflect.Int, reflect.Int16, reflect.Int32, reflect.Int8, reflect.Int64:
							var v int64
							v, err = strconv.ParseInt(s, 10, tt.Bits())
							if err == nil {
								tv.Index(i).SetInt(v)
							}
						case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
							var v uint64
							v, err = strconv.ParseUint(s, 10, tt.Bits())
							if err == nil {
								tv.Index(i).SetUint(v)
							}
						case reflect.Float32, reflect.Float64:
							var v float64
							v, err = strconv.ParseFloat(s, tt.Bits())
							if err == nil {
								tv.Index(i).SetFloat(v)
							}
						case reflect.Bool:
							var v bool
							v, err = strconv.ParseBool(s)
							if err == nil {
								tv.Index(i).SetBool(v)
							}
						case reflect.String:
							tv.Index(i).SetString(s)
						case reflect.Complex64, reflect.Complex128:
							// TODO:
							err = fmt.Errorf(`unsupported slice element type %v`, tk.String())
						default:
							err = fmt.Errorf(`unsupported slice element type %v`, tk.String())
						}
						if err != nil {
							e.Logger().Warnf(`slice error: %v, %v`, name, err)
						}
					}
				default:
					break
				}
				valid := tagfast.Value(tc, f, `valid`)
				if len(valid) > 0 {
					if validator == nil {
						validator = validation.New()
					}
					ok, err := validator.ValidSimple(name, fmt.Sprintf(`%v`, l), valid)
					if !ok {
						return validator.Errors[0].WithField()
					}
					if err != nil {
						e.Logger().Warn(err)
					}
				}
			}
		}
	}
	return nil
}

// FromConversion a struct implements this interface can be convert from request param to a struct
type FromConversion interface {
	FromString(content string) error
}

// ToConversion a struct implements this interface can be convert from struct to template variable
// Not Implemented
type ToConversion interface {
	ToString() string
}

var (
	DefaultFieldNameFormatter = func(topName, fieldName string) string {
		var fName string
		if len(topName) == 0 {
			fName = fieldName
		} else {
			fName = topName + "." + fieldName
		}
		return fName
	}
	LowerCaseFirstFieldNameFormatter = func(topName, fieldName string) string {
		var fName string
		s := []rune(fieldName)
		if len(s) > 0 {
			s[0] = unicode.ToLower(s[0])
			fieldName = string(s)
		}
		if len(topName) == 0 {
			fName = fieldName
		} else {
			fName = topName + "." + fieldName
		}
		return fName
	}
)

func StructToForm(ctx Context, m interface{}, topName string, fieldNameFormatter func(string, string) string) {
	vc := reflect.ValueOf(m)
	tc := reflect.TypeOf(m)

	switch tc.Kind() {
	case reflect.Struct:
	case reflect.Ptr:
		vc = vc.Elem()
		tc = tc.Elem()
	}
	l := tc.NumField()
	f := ctx.Request().Form()
	if fieldNameFormatter == nil {
		fieldNameFormatter = DefaultFieldNameFormatter
	}

	for i := 0; i < l; i++ {
		fVal := vc.Field(i)
		fTyp := tc.Field(i)

		fName := fieldNameFormatter(topName, fTyp.Name)
		if !fVal.CanInterface() {
			continue
		}
		switch fTyp.Type.String() {
		case "time.Time":
			if t, y := fVal.Interface().(time.Time); y {
				dateformat := tagfast.Value(tc, fTyp, `form_format`)
				if dateformat != `` {
					f.Add(fName, t.Format(dateformat))
				} else {
					f.Add(fName, t.Format(`2006-01-02 15:04:05`))
				}
			}
		case "struct":
			StructToForm(ctx, fVal.Interface(), fName, fieldNameFormatter)
		default:
			f.Add(fName, fmt.Sprint(fVal.Interface()))
		}
	}
}
<commit_msg>update<commit_after>package echo

import (
	"encoding/json"
	"encoding/xml"
	"errors"
	"fmt"
	"net/http"
	"reflect"
	"strconv"
	"strings"
	"time"
	"unicode"

	"github.com/webx-top/tagfast"
	"github.com/webx-top/validation"
)

var DefaultHtmlFilter = func(v string) (r string) {
	return v
}

type (
	// Binder is the interface that wraps the Bind method.
	Binder interface {
		Bind(interface{}, Context) error
		MustBind(interface{}, Context) error
	}
	binder struct {
		*Echo
	}
)

func (b *binder) MustBind(i interface{}, c Context) (err error) {
	r := c.Request()
	body := r.Body()
	if body == nil {
		err = NewHTTPError(http.StatusBadRequest, "Request body can't be nil")
		return
	}
	defer body.Close()
	ct := r.Header().Get(HeaderContentType)
	err = ErrUnsupportedMediaType
	if strings.HasPrefix(ct, MIMEApplicationJSON) {
		err = json.NewDecoder(body).Decode(i)
	} else if strings.HasPrefix(ct, MIMEApplicationXML) {
		err = xml.NewDecoder(body).Decode(i)
	} else if strings.HasPrefix(ct, MIMEApplicationForm) {
		err = b.structMap(i, r.PostForm().All())
	} else if strings.Contains(ct, MIMEMultipartForm) {
		err = b.structMap(i, r.Form().All())
	}
	return
}

func (b *binder) Bind(i interface{}, c Context) (err error) {
	err = b.MustBind(i, c)
	if err == ErrUnsupportedMediaType {
		err = nil
	}
	return
}

// StructMap function mapping params to controller's properties
func (b *binder) structMap(m interface{}, data map[string][]string) error {
	return NamedStructMap(b.Echo, m, data, ``)
}

// SplitJSON user[name][test]
func SplitJSON(s string) ([]string, error) {
	var res []string
	var begin, end int
	var isleft bool
	for i, r := range s {
		switch r {
		case '[':
			isleft = true
			if i > 0 && s[i-1] != ']' {
				if begin == end {
					return nil, errors.New(`unknow character`)
				}
				res = append(res, s[begin:end+1])
			}
			begin = i + 1
			end = begin
		case ']':
			if !isleft {
				return nil, errors.New(`unknow character`)
			}
			isleft = false
			if begin != end {
				res = append(res, s[begin:end+1])
				begin = i + 1
				end = begin
			}
		default:
			end = i
		}
		if i == len(s)-1 && begin != end {
			res = append(res, s[begin:end+1])
		}
	}
	return res, nil
}

func NamedStructMap(e *Echo, m interface{}, data map[string][]string, topName string) error {
	vc := reflect.ValueOf(m)
	tc := reflect.TypeOf(m)

	switch tc.Kind() {
	case reflect.Struct:
	case reflect.Ptr:
		vc = vc.Elem()
		tc = tc.Elem()
	}
	var validator *validation.Validation
	for k, t := range data {

		if k == `` || k[0] == '_' {
			continue
		}

		if topName != `` {
			if !strings.HasPrefix(k, topName) {
				continue
			}
			k = k[len(topName)+1:]
		}

		v := t[0]
		names := strings.Split(k, `.`)
		var err error
		length := len(names)
		if length == 1 && strings.HasSuffix(k, `]`) {
			names, err = SplitJSON(k)
			if err != nil {
				e.Logger().Warnf(`Unrecognize form key %v %v`, k, err)
				continue
			}
			length = len(names)
		}
		value := vc
		typev := tc
		for i, name := range names {
			name = strings.Title(name)

			//不是最后一个元素
			if i != length-1 {
				if value.Kind() != reflect.Struct {
					e.Logger().Warnf(`arg error, value kind is %v`, value.Kind())
					break
				}
				value = value.FieldByName(name)
				if !value.IsValid() {
					e.Logger().Warnf(`(%v value is not valid %v)`, name, value)
					break
				}
				if !value.CanSet() {
					e.Logger().Warnf(`can not set %v -> %v`, name, value.Interface())
					break
				}
				if value.Kind() == reflect.Ptr {
					if value.IsNil() {
						value.Set(reflect.New(value.Type().Elem()))
					}
					value = value.Elem()
				}
				typev = value.Type()
				f, _ := typev.FieldByName(name)
				if tagfast.Value(tc, f, `form_options`) == `-` {
					break
				}
			} else {
				if value.Kind() != reflect.Struct {
					e.Logger().Warnf(`arg error, value %v kind is %v`, name, value.Kind())
					break
				}
				tv := value.FieldByName(name)
				if !tv.IsValid() {
					break
				}
				if !tv.CanSet() {
					e.Logger().Warnf(`can not set %v to %v`, k, tv)
					break
				}
				f, _ := typev.FieldByName(name)
				if tagfast.Value(tc, f, `form_options`) == `-` {
					break
				}
				if tv.Kind() == reflect.Ptr {
					tv.Set(reflect.New(tv.Type().Elem()))
					tv = tv.Elem()
				}

				var l interface{}
				switch k := tv.Kind(); k {
				case reflect.String:
					switch tagfast.Value(tc, f, `form_filter`) {
					case `html`:
						v = DefaultHtmlFilter(v)
					}
					l = v
					tv.Set(reflect.ValueOf(l))
				case reflect.Bool:
					l = (v != `false` && v != `0` && v != ``)
					tv.Set(reflect.ValueOf(l))
				case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32:
					dateformat := tagfast.Value(tc, f, `form_format`)
					if dateformat != `` {
						t, err := time.Parse(dateformat, v)
						if err != nil {
							e.Logger().Warnf(`arg %v as int: %v`, v, err)
							l = int(0)
						} else {
							l = int(t.Unix())
						}
					} else {
						x, err := strconv.Atoi(v)
						if err != nil {
							e.Logger().Warnf(`arg %v as int: %v`, v, err)
						}
						l = x
					}
					tv.Set(reflect.ValueOf(l))
				case reflect.Int64:
					dateformat := tagfast.Value(tc, f, `form_format`)
					if dateformat != `` {
						t, err := time.Parse(dateformat, v)
						if err != nil {
							e.Logger().Warnf(`arg %v as int64: %v`, v, err)
							l = int64(0)
						} else {
							l = t.Unix()
						}
					} else {
						x, err := strconv.ParseInt(v, 10, 64)
						if err != nil {
							e.Logger().Warnf(`arg %v as int64: %v`, v, err)
						}
						l = x
					}
					tv.Set(reflect.ValueOf(l))
				case reflect.Float32, reflect.Float64:
					x, err := strconv.ParseFloat(v, 64)
					if err != nil {
						e.Logger().Warnf(`arg %v as float64: %v`, v, err)
					}
					l = x
					tv.Set(reflect.ValueOf(l))
				case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
					dateformat := tagfast.Value(tc, f, `form_format`)
					if dateformat != `` {
						t, err := time.Parse(dateformat, v)
						if err != nil {
							e.Logger().Warnf(`arg %v as uint: %v`, v, err)
							l = uint64(0)
						} else {
							l = uint64(t.Unix())
						}
					} else {
						x, err := strconv.ParseUint(v, 10, 64)
						if err != nil {
							e.Logger().Warnf(`arg %v as uint: %v`, v, err)
						}
						l = x
					}
					tv.Set(reflect.ValueOf(l))
				case reflect.Struct:
					if tvf, ok := tv.Interface().(FromConversion); ok {
						err := tvf.FromString(v)
						if err != nil {
							e.Logger().Warnf(`struct %v invoke FromString faild`, tvf)
						}
					} else if tv.Type().String() == `time.Time` {
						x, err := time.Parse(`2006-01-02 15:04:05.000 -0700`, v)
						if err != nil {
							x, err = time.Parse(`2006-01-02 15:04:05`, v)
							if err != nil {
								x, err = time.Parse(`2006-01-02`, v)
								if err != nil {
									e.Logger().Warnf(`unsupported time format %v, %v`, v, err)
								}
							}
						}
						l = x
						tv.Set(reflect.ValueOf(l))
					} else {
						e.Logger().Warn(`can not set an struct which is not implement Fromconversion interface`)
					}
				case reflect.Ptr:
					e.Logger().Warn(`can not set an ptr of ptr`)
				case reflect.Slice, reflect.Array:
					tt := tv.Type().Elem()
					tk := tt.Kind()

					if tv.IsNil() {
						tv.Set(reflect.MakeSlice(tv.Type(), len(t), len(t)))
					}

					for i, s := range t {
						var err error
						switch tk {
						case reflect.Int, reflect.Int16, reflect.Int32, reflect.Int8, reflect.Int64:
							var v int64
							v, err = strconv.ParseInt(s, 10, tt.Bits())
							if err == nil {
								tv.Index(i).SetInt(v)
							}
						case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
							var v uint64
							v, err = strconv.ParseUint(s, 10, tt.Bits())
							if err == nil {
								tv.Index(i).SetUint(v)
							}
						case reflect.Float32, reflect.Float64:
							var v float64
							v, err = strconv.ParseFloat(s, tt.Bits())
							if err == nil {
								tv.Index(i).SetFloat(v)
							}
						case reflect.Bool:
							var v bool
							v, err = strconv.ParseBool(s)
							if err == nil {
								tv.Index(i).SetBool(v)
							}
						case reflect.String:
							tv.Index(i).SetString(s)
						case reflect.Complex64, reflect.Complex128:
							// TODO:
							err = fmt.Errorf(`unsupported slice element type %v`, tk.String())
						default:
							err = fmt.Errorf(`unsupported slice element type %v`, tk.String())
						}
						if err != nil {
							e.Logger().Warnf(`slice error: %v, %v`, name, err)
						}
					}
				default:
					break
				}
				valid := tagfast.Value(tc, f, `valid`)
				if len(valid) > 0 {
					if validator == nil {
						validator = validation.New()
					}
					ok, err := validator.ValidSimple(name, fmt.Sprintf(`%v`, l), valid)
					if !ok {
						return validator.Errors[0].WithField()
					}
					if err != nil {
						e.Logger().Warn(err)
					}
				}
			}
		}
	}
	return nil
}

// FromConversion a struct implements this interface can be convert from request param to a struct
type FromConversion interface {
	FromString(content string) error
}

// ToConversion a struct implements this interface can be convert from struct to template variable
// Not Implemented
type ToConversion interface {
	ToString() string
}

type FieldNameFormatter func(topName, fieldName string) string

var (
	DefaultFieldNameFormatter FieldNameFormatter = func(topName, fieldName string) string {
		var fName string
		if len(topName) == 0 {
			fName = fieldName
		} else {
			fName = topName + "." + fieldName
		}
		return fName
	}
	LowerCaseFirstLetter FieldNameFormatter = func(topName, fieldName string) string {
		var fName string
		s := []rune(fieldName)
		if len(s) > 0 {
			s[0] = unicode.ToLower(s[0])
			fieldName = string(s)
		}
		if len(topName) == 0 {
			fName = fieldName
		} else {
			fName = topName + "." + fieldName
		}
		return fName
	}
)

func StructToForm(ctx Context, m interface{}, topName string, fieldNameFormatter FieldNameFormatter) {
	vc := reflect.ValueOf(m)
	tc := reflect.TypeOf(m)

	switch tc.Kind() {
	case reflect.Struct:
	case reflect.Ptr:
		vc = vc.Elem()
		tc = tc.Elem()
	}
	l := tc.NumField()
	f := ctx.Request().Form()
	if fieldNameFormatter == nil {
		fieldNameFormatter = DefaultFieldNameFormatter
	}

	for i := 0; i < l; i++ {
		fVal := vc.Field(i)
		fTyp := tc.Field(i)

		fName := fieldNameFormatter(topName, fTyp.Name)
		if !fVal.CanInterface() {
			continue
		}
		switch fTyp.Type.String() {
		case "time.Time":
			if t, y := fVal.Interface().(time.Time); y {
				dateformat := tagfast.Value(tc, fTyp, `form_format`)
				if dateformat != `` {
					f.Add(fName, t.Format(dateformat))
				} else {
					f.Add(fName, t.Format(`2006-01-02 15:04:05`))
				}
			}
		case "struct":
			StructToForm(ctx, fVal.Interface(), fName, fieldNameFormatter)
		default:
			f.Add(fName, fmt.Sprint(fVal.Interface()))
		}
	}
}
<|endoftext|>