<commit_before>package amazonpa

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/base64"
	"encoding/xml"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"strings"
	"time"
)

// ItemLookupQuery describes the allowed parameters for a ItemLookup request
type ItemLookupQuery struct {
	Condition             string
	IDType                string
	IncludeReviewsSummary string
	ItemIDs               []string
	MerchantID            string
	RelatedItemPage       string
	RelationshipType      string
	SearchIndex           string
	TruncateReviewsAt     string
	VariationPage         string
	ResponseGroups        []string
}

// ItemSearchQuery describes the allowed parameters for a ItemSearch request
type ItemSearchQuery struct {
	Actor                 string
	Artist                string
	AudienceRatings       []string
	Author                string
	Availability          string
	Brand                 string
	BrowseNode            string
	Composer              string
	Condition             string
	Conductor             string
	Director              string
	IncludeReviewsSummary string
	ItemPage              string
	Keywords              string
	Manufacturer          string
	MaximumPrice          string
	MerchantID            string
	MinimumPrice          string
	MinPercentageOff      string
	Orchestra             string
	Power                 string
	Publisher             string
	RelatedItemPage       string
	RelationshipType      string
	SearchIndex           string
	Sort                  string
	Title                 string
	TruncateReviewsAt     string
	VariationPage         string
	ResponseGroups        []string
}

type BrowseNodeLookupQuery struct {
	BrowseNodeID   string
	ResponseGroups []string
}

// Client provides the functions to interact with the API
type Client struct {
	config Config
}

// NewClient returns a new Client
func NewClient(config Config) *Client {
	c := Client{config}

	return &c
}

// NewRequest returns a request with basic parameters
func (client Client) NewRequest(operation string) *Request {

	request := Request{}

	if client.config.Secure {
		request.scheme = "https"
	} else {
		request.scheme = "http"
	}

	request.endpoint = Endpoints[client.config.Region]
	request.endpointURI = EndpointURI

	request.parameters = map[string]string{
		"Service":        "AWSECommerceService",
		"AWSAccessKeyId": client.config.AccessKey,
		"AssociateTag":   client.config.AssociateTag,
		"Version":        "2013-08-01",
		"Operation":      operation,
		"Timestamp":      time.Now().Format(time.RFC3339),
	}

	return &request
}

// SignRequest produces the signature for the given query string
func (client Client) SignRequest(request *Request) {
	signable := fmt.Sprintf("GET\n%s\n%s\n%s", request.endpoint, request.endpointURI, request.QueryString())

	hasher := hmac.New(sha256.New, []byte(client.config.AccessSecret))
	hasher.Write([]byte(signable))

	request.signature = base64.StdEncoding.EncodeToString(hasher.Sum(nil))
}

// ProcessRequest takes a request and queries the API
func (client Client) ProcessRequest(request *Request) ([]byte, error) {

	// Sign the request
	client.SignRequest(request)

	// Execute the HTTP request
	var httpResponse *http.Response
	var err error
	var contents []byte

	requestURL, err := request.SignedURL()

	if err != nil {
		return nil, errors.New("amazonpa: cannot get the signed request URL")
	}

	httpResponse, err = http.Get(requestURL)

	if err != nil {
		return nil, errors.New("amazonpa: error processing the http request")
	}

	contents, err = ioutil.ReadAll(httpResponse.Body)
	httpResponse.Body.Close()

	if err != nil {
		return nil, errors.New("amazonpa: error while reading the server response")
	}

	return contents, nil
}

// ItemLookup performs an ItemLookup request
func (client Client) ItemLookup(query ItemLookupQuery) (*ItemLookupResponse, error) {

	request := client.NewRequest("ItemLookup")

	request.SetParameter("Condition", query.Condition)
	request.SetParameter("IdType", query.IDType)
	request.SetParameter("IncludeReviewsSummary", query.IncludeReviewsSummary)
	request.SetParameter("ItemId", strings.Join(query.ItemIDs, ","))
	request.SetParameter("MerchantId", query.MerchantID)
	request.SetParameter("RelatedItemPage", query.RelatedItemPage)
	request.SetParameter("RelationshipType", query.RelationshipType)
	request.SetParameter("SearchIndex", query.SearchIndex)
	request.SetParameter("TruncateReviewsAt", query.TruncateReviewsAt)
	request.SetParameter("VariationPage", query.VariationPage)
	request.SetParameter("ResponseGroup", strings.Join(query.ResponseGroups, ","))

	xmlData, err := client.ProcessRequest(request)

	if err != nil {
		return nil, err
	}

	var response ItemLookupResponse
	xml.Unmarshal(xmlData, &response)

	if response.Items.Request.IsValid != true {
		return &response, errors.New("amazonpa: request is invalid")
	}

	return &response, nil
}

// ItemLookup performs an ItemLookup request
func (client Client) ItemSearch(query ItemSearchQuery) (*ItemSearchResponse, error) {

	request := client.NewRequest("ItemSearch")

	request.SetParameter("Actor", query.Actor)
	request.SetParameter("Artist", query.Artist)
	request.SetParameter("AudienceRating", strings.Join(query.AudienceRatings, ","))
	request.SetParameter("Author", query.Author)
	request.SetParameter("Availability", query.Availability)
	request.SetParameter("Brand", query.Brand)
	request.SetParameter("BrowseNode", query.BrowseNode)
	request.SetParameter("Composer", query.Composer)
	request.SetParameter("Condition", query.Condition)
	request.SetParameter("Conductor", query.Conductor)
	request.SetParameter("Director", query.Director)
	request.SetParameter("IncludeReviewsSummary", query.IncludeReviewsSummary)
	request.SetParameter("ItemPage", query.ItemPage)
	request.SetParameter("Keywords", query.Keywords)
	request.SetParameter("Manufacturer", query.Manufacturer)
	request.SetParameter("MaximumPrice", query.MaximumPrice)
	request.SetParameter("MerchantId", query.MerchantID)
	request.SetParameter("MinimumPrice", query.MinimumPrice)
	request.SetParameter("MinPercentageOff", query.MinPercentageOff)
	request.SetParameter("Orchestra", query.Orchestra)
	request.SetParameter("Power", query.Power)
	request.SetParameter("Publisher", query.Publisher)
	request.SetParameter("RelatedItemPage", query.RelatedItemPage)
	request.SetParameter("RelationshipType", query.RelationshipType)
	request.SetParameter("SearchIndex", query.SearchIndex)
	request.SetParameter("Sort", query.Sort)
	request.SetParameter("Title", query.Title)
	request.SetParameter("TruncateReviewsAt", query.TruncateReviewsAt)
	request.SetParameter("VariationPage", query.VariationPage)
	request.SetParameter("ResponseGroups", strings.Join(query.ResponseGroups, ","))

	xmlData, err := client.ProcessRequest(request)

	if err != nil {
		return nil, err
	}

	var response ItemSearchResponse
	xml.Unmarshal(xmlData, &response)

	if response.Items.Request.IsValid != true {
		return &response, errors.New("amazonpa: request is invalid")
	}

	return &response, nil
}

// ItemLookup performs an ItemLookup request
func (client Client) BrowseNodeLookup(query BrowseNodeLookupQuery) (*BrowseNodeLookupResponse, error) {

	request := client.NewRequest("BrowseNodeLookup")

	request.SetParameter("BrowseNodeId", query.BrowseNodeID)
	request.SetParameter("ResponseGroup", strings.Join(query.ResponseGroups, ","))

	xmlData, err := client.ProcessRequest(request)

	if err != nil {
		return nil, err
	}

	var response BrowseNodeLookupResponse
	xml.Unmarshal(xmlData, &response)

	if response.BrowseNodes.Request.IsValid != true {
		return &response, errors.New("amazonpa: request is invalid")
	}

	return &response, nil
}
<commit_msg>Fix typo in ItemSearchRequest construction<commit_after>package amazonpa

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/base64"
	"encoding/xml"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"strings"
	"time"
)

// ItemLookupQuery describes the allowed parameters for a ItemLookup request
type ItemLookupQuery struct {
	Condition             string
	IDType                string
	IncludeReviewsSummary string
	ItemIDs               []string
	MerchantID            string
	RelatedItemPage       string
	RelationshipType      string
	SearchIndex           string
	TruncateReviewsAt     string
	VariationPage         string
	ResponseGroups        []string
}

// ItemSearchQuery describes the allowed parameters for a ItemSearch request
type ItemSearchQuery struct {
	Actor                 string
	Artist                string
	AudienceRatings       []string
	Author                string
	Availability          string
	Brand                 string
	BrowseNode            string
	Composer              string
	Condition             string
	Conductor             string
	Director              string
	IncludeReviewsSummary string
	ItemPage              string
	Keywords              string
	Manufacturer          string
	MaximumPrice          string
	MerchantID            string
	MinimumPrice          string
	MinPercentageOff      string
	Orchestra             string
	Power                 string
	Publisher             string
	RelatedItemPage       string
	RelationshipType      string
	SearchIndex           string
	Sort                  string
	Title                 string
	TruncateReviewsAt     string
	VariationPage         string
	ResponseGroups        []string
}

type BrowseNodeLookupQuery struct {
	BrowseNodeID   string
	ResponseGroups []string
}

// Client provides the functions to interact with the API
type Client struct {
	config Config
}

// NewClient returns a new Client
func NewClient(config Config) *Client {
	c := Client{config}

	return &c
}

// NewRequest returns a request with basic parameters
func (client Client) NewRequest(operation string) *Request {

	request := Request{}

	if client.config.Secure {
		request.scheme = "https"
	} else {
		request.scheme = "http"
	}

	request.endpoint = Endpoints[client.config.Region]
	request.endpointURI = EndpointURI

	request.parameters = map[string]string{
		"Service":        "AWSECommerceService",
		"AWSAccessKeyId": client.config.AccessKey,
		"AssociateTag":   client.config.AssociateTag,
		"Version":        "2013-08-01",
		"Operation":      operation,
		"Timestamp":      time.Now().Format(time.RFC3339),
	}

	return &request
}

// SignRequest produces the signature for the given query string
func (client Client) SignRequest(request *Request) {
	signable := fmt.Sprintf("GET\n%s\n%s\n%s", request.endpoint, request.endpointURI, request.QueryString())

	hasher := hmac.New(sha256.New, []byte(client.config.AccessSecret))
	hasher.Write([]byte(signable))

	request.signature = base64.StdEncoding.EncodeToString(hasher.Sum(nil))
}

// ProcessRequest takes a request and queries the API
func (client Client) ProcessRequest(request *Request) ([]byte, error) {

	// Sign the request
	client.SignRequest(request)

	// Execute the HTTP request
	var httpResponse *http.Response
	var err error
	var contents []byte

	requestURL, err := request.SignedURL()

	if err != nil {
		return nil, errors.New("amazonpa: cannot get the signed request URL")
	}

	httpResponse, err = http.Get(requestURL)

	if err != nil {
		return nil, errors.New("amazonpa: error processing the http request")
	}

	contents, err = ioutil.ReadAll(httpResponse.Body)
	httpResponse.Body.Close()

	if err != nil {
		return nil, errors.New("amazonpa: error while reading the server response")
	}

	return contents, nil
}

// ItemLookup performs an ItemLookup request
func (client Client) ItemLookup(query ItemLookupQuery) (*ItemLookupResponse, error) {

	request := client.NewRequest("ItemLookup")

	request.SetParameter("Condition", query.Condition)
	request.SetParameter("IdType", query.IDType)
	request.SetParameter("IncludeReviewsSummary", query.IncludeReviewsSummary)
	request.SetParameter("ItemId", strings.Join(query.ItemIDs, ","))
	request.SetParameter("MerchantId", query.MerchantID)
	request.SetParameter("RelatedItemPage", query.RelatedItemPage)
	request.SetParameter("RelationshipType", query.RelationshipType)
	request.SetParameter("SearchIndex", query.SearchIndex)
	request.SetParameter("TruncateReviewsAt", query.TruncateReviewsAt)
	request.SetParameter("VariationPage", query.VariationPage)
	request.SetParameter("ResponseGroup", strings.Join(query.ResponseGroups, ","))

	xmlData, err := client.ProcessRequest(request)

	if err != nil {
		return nil, err
	}

	var response ItemLookupResponse
	xml.Unmarshal(xmlData, &response)

	if response.Items.Request.IsValid != true {
		return &response, errors.New("amazonpa: request is invalid")
	}

	return &response, nil
}

// ItemLookup performs an ItemLookup request
func (client Client) ItemSearch(query ItemSearchQuery) (*ItemSearchResponse, error) {

	request := client.NewRequest("ItemSearch")

	request.SetParameter("Actor", query.Actor)
	request.SetParameter("Artist", query.Artist)
	request.SetParameter("AudienceRating", strings.Join(query.AudienceRatings, ","))
	request.SetParameter("Author", query.Author)
	request.SetParameter("Availability", query.Availability)
	request.SetParameter("Brand", query.Brand)
	request.SetParameter("BrowseNode", query.BrowseNode)
	request.SetParameter("Composer", query.Composer)
	request.SetParameter("Condition", query.Condition)
	request.SetParameter("Conductor", query.Conductor)
	request.SetParameter("Director", query.Director)
	request.SetParameter("IncludeReviewsSummary", query.IncludeReviewsSummary)
	request.SetParameter("ItemPage", query.ItemPage)
	request.SetParameter("Keywords", query.Keywords)
	request.SetParameter("Manufacturer", query.Manufacturer)
	request.SetParameter("MaximumPrice", query.MaximumPrice)
	request.SetParameter("MerchantId", query.MerchantID)
	request.SetParameter("MinimumPrice", query.MinimumPrice)
	request.SetParameter("MinPercentageOff", query.MinPercentageOff)
	request.SetParameter("Orchestra", query.Orchestra)
	request.SetParameter("Power", query.Power)
	request.SetParameter("Publisher", query.Publisher)
	request.SetParameter("RelatedItemPage", query.RelatedItemPage)
	request.SetParameter("RelationshipType", query.RelationshipType)
	request.SetParameter("SearchIndex", query.SearchIndex)
	request.SetParameter("Sort", query.Sort)
	request.SetParameter("Title", query.Title)
	request.SetParameter("TruncateReviewsAt", query.TruncateReviewsAt)
	request.SetParameter("VariationPage", query.VariationPage)
	request.SetParameter("ResponseGroup", strings.Join(query.ResponseGroups, ","))

	xmlData, err := client.ProcessRequest(request)

	if err != nil {
		return nil, err
	}

	var response ItemSearchResponse
	xml.Unmarshal(xmlData, &response)

	if response.Items.Request.IsValid != true {
		return &response, errors.New("amazonpa: request is invalid")
	}

	return &response, nil
}

// ItemLookup performs an ItemLookup request
func (client Client) BrowseNodeLookup(query BrowseNodeLookupQuery) (*BrowseNodeLookupResponse, error) {

	request := client.NewRequest("BrowseNodeLookup")

	request.SetParameter("BrowseNodeId", query.BrowseNodeID)
	request.SetParameter("ResponseGroup", strings.Join(query.ResponseGroups, ","))

	xmlData, err := client.ProcessRequest(request)

	if err != nil {
		return nil, err
	}

	var response BrowseNodeLookupResponse
	xml.Unmarshal(xmlData, &response)

	if response.BrowseNodes.Request.IsValid != true {
		return &response, errors.New("amazonpa: request is invalid")
	}

	return &response, nil
}
<|endoftext|>


<commit_before>package circuitbreaker

import (
	"io"
	"net/http"
	"net/url"
	"time"
)

// HTTPClient is a wrapper around http.Client that provides circuit breaker capabilities.
//
// By default, the client will use its defaultBreaker. A BreakerLookup function may be
// provided to allow different breakers to be used based on the circumstance. See the
// implementation of NewHostBasedHTTPClient for an example of this.
type HTTPClient struct {
	Client         *http.Client
	BreakerTripped func()
	BreakerReset   func()
	BreakerLookup  func(*HTTPClient, interface{}) CircuitBreaker
	panel          *Panel
}

var defaultBreakerName = "_default"

// NewCircuitBreakerClient provides a circuit breaker wrapper around http.Client.
// It wraps all of the regular http.Client functions. Specifying 0 for timeout will
// give a breaker that does not check for time outs.
func NewHTTPClient(timeout time.Duration, threshold int64, client *http.Client) *HTTPClient {
	breaker := NewTimeoutBreaker(timeout, threshold)
	return NewHTTPClientWithBreaker(breaker, client)
}

// NewHostBasedHTTPClient provides a circuit breaker wrapper around http.Client. This
// client will use one circuit breaker per host parsed from the request URL. This allows
// you to use a single HTTPClient for multiple hosts with one host's breaker not affecting
// the other hosts.
func NewHostBasedHTTPClient(timeout time.Duration, threshold int64, client *http.Client) *HTTPClient {
	brclient := NewHTTPClient(timeout, threshold, client)

	brclient.BreakerLookup = func(c *HTTPClient, val interface{}) CircuitBreaker {
		rawURL := val.(string)
		parsedURL, err := url.Parse(rawURL)
		if err != nil {
			breaker, _ := c.panel.Get(defaultBreakerName)
			return breaker
		}
		host := parsedURL.Host

		cb, ok := c.panel.Get(host)
		if !ok {
			cb = NewTimeoutBreaker(timeout, threshold)
			c.panel.Add(host, cb)
		}

		return cb
	}

	return brclient
}

// NewHTTPClientWithBreaker provides a circuit breaker wrapper around http.Client.
// It wraps all of the regular http.Client functions using the provided CircuitBreaker.
func NewHTTPClientWithBreaker(breaker CircuitBreaker, client *http.Client) *HTTPClient {
	if client == nil {
		client = &http.Client{}
	}

	panel := NewPanel()
	panel.Add(defaultBreakerName, breaker)

	brclient := &HTTPClient{Client: client, panel: NewPanel()}
	brclient.BreakerLookup = func(c *HTTPClient, val interface{}) CircuitBreaker {
		cb, _ := c.panel.Get(defaultBreakerName)
		return cb
	}

	events := breaker.Subscribe()
	go func() {
		event := <-events
		switch event {
		case BreakerTripped:
			brclient.runBreakerTripped()
		case BreakerReset:
			brclient.runBreakerReset()
		}
	}()

	return brclient
}

// Do wraps http.Client Do()
func (c *HTTPClient) Do(req *http.Request) (*http.Response, error) {
	var resp *http.Response
	var err error
	breaker := c.breakerLookup(req.URL.String())
	breaker.Call(func() error {
		resp, err = c.Client.Do(req)
		return err
	})
	return resp, err
}

// Get wraps http.Client Get()
func (c *HTTPClient) Get(url string) (*http.Response, error) {
	var resp *http.Response
	breaker := c.breakerLookup(url)
	err := breaker.Call(func() error {
		aresp, err := c.Client.Get(url)
		resp = aresp
		return err
	})
	return resp, err
}

// Head wraps http.Client Head()
func (c *HTTPClient) Head(url string) (*http.Response, error) {
	var resp *http.Response
	breaker := c.breakerLookup(url)
	err := breaker.Call(func() error {
		aresp, err := c.Client.Head(url)
		resp = aresp
		return err
	})
	return resp, err
}

// Post wraps http.Client Post()
func (c *HTTPClient) Post(url string, bodyType string, body io.Reader) (*http.Response, error) {
	var resp *http.Response
	breaker := c.breakerLookup(url)
	err := breaker.Call(func() error {
		aresp, err := c.Client.Post(url, bodyType, body)
		resp = aresp
		return err
	})
	return resp, err
}

// PostForm wraps http.Client PostForm()
func (c *HTTPClient) PostForm(url string, data url.Values) (*http.Response, error) {
	var resp *http.Response
	breaker := c.breakerLookup(url)
	err := breaker.Call(func() error {
		aresp, err := c.Client.PostForm(url, data)
		resp = aresp
		return err
	})
	return resp, err
}

func (c *HTTPClient) breakerLookup(val interface{}) CircuitBreaker {
	if c.BreakerLookup != nil {
		return c.BreakerLookup(c, val)
	}
	cb, _ := c.panel.Get(defaultBreakerName)
	return cb
}

func (c *HTTPClient) runBreakerTripped() {
	if c.BreakerTripped != nil {
		c.BreakerTripped()
	}
}

func (c *HTTPClient) runBreakerReset() {
	if c.BreakerReset != nil {
		c.BreakerReset()
	}
}
<commit_msg>Expose Panel in client<commit_after>package circuitbreaker

import (
	"io"
	"net/http"
	"net/url"
	"time"
)

// HTTPClient is a wrapper around http.Client that provides circuit breaker capabilities.
//
// By default, the client will use its defaultBreaker. A BreakerLookup function may be
// provided to allow different breakers to be used based on the circumstance. See the
// implementation of NewHostBasedHTTPClient for an example of this.
type HTTPClient struct {
	Client         *http.Client
	BreakerTripped func()
	BreakerReset   func()
	BreakerLookup  func(*HTTPClient, interface{}) CircuitBreaker
	Panel          *Panel
}

var defaultBreakerName = "_default"

// NewCircuitBreakerClient provides a circuit breaker wrapper around http.Client.
// It wraps all of the regular http.Client functions. Specifying 0 for timeout will
// give a breaker that does not check for time outs.
func NewHTTPClient(timeout time.Duration, threshold int64, client *http.Client) *HTTPClient {
	breaker := NewTimeoutBreaker(timeout, threshold)
	return NewHTTPClientWithBreaker(breaker, client)
}

// NewHostBasedHTTPClient provides a circuit breaker wrapper around http.Client. This
// client will use one circuit breaker per host parsed from the request URL. This allows
// you to use a single HTTPClient for multiple hosts with one host's breaker not affecting
// the other hosts.
func NewHostBasedHTTPClient(timeout time.Duration, threshold int64, client *http.Client) *HTTPClient {
	brclient := NewHTTPClient(timeout, threshold, client)

	brclient.BreakerLookup = func(c *HTTPClient, val interface{}) CircuitBreaker {
		rawURL := val.(string)
		parsedURL, err := url.Parse(rawURL)
		if err != nil {
			breaker, _ := c.Panel.Get(defaultBreakerName)
			return breaker
		}
		host := parsedURL.Host

		cb, ok := c.Panel.Get(host)
		if !ok {
			cb = NewTimeoutBreaker(timeout, threshold)
			c.Panel.Add(host, cb)
		}

		return cb
	}

	return brclient
}

// NewHTTPClientWithBreaker provides a circuit breaker wrapper around http.Client.
// It wraps all of the regular http.Client functions using the provided CircuitBreaker.
func NewHTTPClientWithBreaker(breaker CircuitBreaker, client *http.Client) *HTTPClient {
	if client == nil {
		client = &http.Client{}
	}

	panel := NewPanel()
	panel.Add(defaultBreakerName, breaker)

	brclient := &HTTPClient{Client: client, Panel: NewPanel()}
	brclient.BreakerLookup = func(c *HTTPClient, val interface{}) CircuitBreaker {
		cb, _ := c.Panel.Get(defaultBreakerName)
		return cb
	}

	events := breaker.Subscribe()
	go func() {
		event := <-events
		switch event {
		case BreakerTripped:
			brclient.runBreakerTripped()
		case BreakerReset:
			brclient.runBreakerReset()
		}
	}()

	return brclient
}

// Do wraps http.Client Do()
func (c *HTTPClient) Do(req *http.Request) (*http.Response, error) {
	var resp *http.Response
	var err error
	breaker := c.breakerLookup(req.URL.String())
	breaker.Call(func() error {
		resp, err = c.Client.Do(req)
		return err
	})
	return resp, err
}

// Get wraps http.Client Get()
func (c *HTTPClient) Get(url string) (*http.Response, error) {
	var resp *http.Response
	breaker := c.breakerLookup(url)
	err := breaker.Call(func() error {
		aresp, err := c.Client.Get(url)
		resp = aresp
		return err
	})
	return resp, err
}

// Head wraps http.Client Head()
func (c *HTTPClient) Head(url string) (*http.Response, error) {
	var resp *http.Response
	breaker := c.breakerLookup(url)
	err := breaker.Call(func() error {
		aresp, err := c.Client.Head(url)
		resp = aresp
		return err
	})
	return resp, err
}

// Post wraps http.Client Post()
func (c *HTTPClient) Post(url string, bodyType string, body io.Reader) (*http.Response, error) {
	var resp *http.Response
	breaker := c.breakerLookup(url)
	err := breaker.Call(func() error {
		aresp, err := c.Client.Post(url, bodyType, body)
		resp = aresp
		return err
	})
	return resp, err
}

// PostForm wraps http.Client PostForm()
func (c *HTTPClient) PostForm(url string, data url.Values) (*http.Response, error) {
	var resp *http.Response
	breaker := c.breakerLookup(url)
	err := breaker.Call(func() error {
		aresp, err := c.Client.PostForm(url, data)
		resp = aresp
		return err
	})
	return resp, err
}

func (c *HTTPClient) breakerLookup(val interface{}) CircuitBreaker {
	if c.BreakerLookup != nil {
		return c.BreakerLookup(c, val)
	}
	cb, _ := c.Panel.Get(defaultBreakerName)
	return cb
}

func (c *HTTPClient) runBreakerTripped() {
	if c.BreakerTripped != nil {
		c.BreakerTripped()
	}
}

func (c *HTTPClient) runBreakerReset() {
	if c.BreakerReset != nil {
		c.BreakerReset()
	}
}
<|endoftext|>


<commit_before>package greq

import (
	"context"
	"encoding/base64"
	"errors"
	"io/ioutil"
	"net"
	"net/http"
	"net/http/httptrace"
	"net/url"
	"os"
	"strings"
	"sync"
	"time"

	log "github.com/sirupsen/logrus"

	requestwork "github.com/syhlion/requestwork.v2"
)

type Trace struct {
	Url              string        `json:"url"`
	Method           string        `json:"method"`
	Body             string        `json:"body"`
	Param            string        `json:"param"`
	DNSLookup        time.Duration `json:"dns_lookup"`
	TCPConnection    time.Duration `json:"tcp_connection"`
	TLSHandshake     time.Duration `json:"tls_handshake"`
	ServerProcessing time.Duration `json:"server_prcoessing"`
	ContentTransfer  time.Duration `json:"content_transfer"`
	NameLookup       time.Duration `json:"name_lookup"`
	Connect          time.Duration `json:"connect"`
	PreTransfer      time.Duration `json:"pre_transfer"`
	StartTransfer    time.Duration `json:"start_transfer"`
	Total            time.Duration `json:"total"`
}

var ip string

func getExternalIP() (string, error) {
	ifaces, err := net.Interfaces()
	if err != nil {
		return "", err
	}
	for _, iface := range ifaces {
		if iface.Flags&net.FlagUp == 0 {
			continue // interface down
		}
		if iface.Flags&net.FlagLoopback != 0 {
			continue // loopback interface
		}
		addrs, err := iface.Addrs()
		if err != nil {
			return "", err
		}
		for _, addr := range addrs {
			var ip net.IP
			switch v := addr.(type) {
			case *net.IPNet:
				ip = v.IP
			case *net.IPAddr:
				ip = v.IP
			}
			if ip == nil || ip.IsLoopback() {
				continue
			}
			ip = ip.To4()
			if ip == nil {
				continue // not an ipv4 address
			}
			return ip.String(), nil
		}
	}
	return "", errors.New("are you connected to the network?")
}

func init() {
	log.SetFormatter(&log.JSONFormatter{})

	log.SetOutput(os.Stdout)
	log.SetLevel(log.DebugLevel)
	ip, _ = getExternalIP()
}

//New return http client
func New(worker *requestwork.Worker, timeout time.Duration, debug bool) *Client {
	return &Client{
		worker:  worker,
		timeout: timeout,
		headers: make(map[string]string),
		lock:    &sync.RWMutex{},
		debug:   debug,
	}
}

//Client instance
type Client struct {
	worker  *requestwork.Worker
	timeout time.Duration
	headers map[string]string
	host    string
	lock    *sync.RWMutex
	debug   bool
}

//SetBasicAuth  set Basic auth
func (c *Client) SetBasicAuth(username, password string) *Client {
	auth := username + ":" + password
	hash := base64.StdEncoding.EncodeToString([]byte(auth))
	c.lock.Lock()
	defer c.lock.Unlock()
	c.headers["Authorization"] = "Basic " + hash
	return c
}

//SetHeader set http header
func (c *Client) SetHeader(key, value string) *Client {
	key = strings.Title(key)
	c.lock.Lock()
	defer c.lock.Unlock()
	c.headers[key] = value
	return c
}

//SetHost set host
func (c *Client) SetHost(host string) *Client {
	c.lock.Lock()
	defer c.lock.Unlock()

	c.host = host
	return c
}

//Get http method get
func (c *Client) Get(url string, params url.Values) (data []byte, httpstatus int, err error) {
	if params != nil {
		url += "?" + params.Encode()
	}
	req, err := http.NewRequest(http.MethodGet, url, nil)
	return c.resolveRequest(req, params, err)

}

//Post http method post
func (c *Client) Post(url string, params url.Values) (data []byte, httpstatus int, err error) {
	req, err := http.NewRequest(http.MethodPost, url, strings.NewReader(params.Encode()))
	return c.resolveRequest(req, params, err)
}

//Put http method put
func (c *Client) Put(url string, params url.Values) (data []byte, httpstatus int, err error) {
	req, err := http.NewRequest(http.MethodPut, url, strings.NewReader(params.Encode()))
	return c.resolveRequest(req, params, err)
}

//Delete http method Delete
func (c *Client) Delete(url string, params url.Values) (data []byte, httpstatus int, err error) {
	req, err := http.NewRequest(http.MethodDelete, url, strings.NewReader(params.Encode()))
	return c.resolveRequest(req, params, err)
}

func (c *Client) resolveHeaders(req *http.Request) {
	c.lock.RLock()
	c.lock.RUnlock()
	for key, value := range c.headers {
		req.Header.Set(key, value)
	}
	if c.host != "" {
		req.Host = c.host
	}
}

func (c *Client) resolveRequest(req *http.Request, params url.Values, e error) (data []byte, httpstatus int, err error) {
	var (
		body           []byte
		status         int
		trace          *httptrace.ClientTrace
		t0, t3, t4, t5 time.Time
	)
	t0 = time.Now()
	if c.debug {
		var stat Trace
		defer func() {
			stat.Param = params.Encode()
			stat.Url = req.URL.String()
			stat.Method = req.Method
			stat.Body = string(data)
			stat.TCPConnection = t3.Sub(t0)
			stat.ServerProcessing = t4.Sub(t3)
			stat.ContentTransfer = t5.Sub(t4)
			stat.Connect = t3.Sub(t0)
			stat.StartTransfer = t4.Sub(t0)
			stat.Total = t5.Sub(t0)
			log.WithFields(log.Fields{
				"ip":     ip,
				"name":   "syhlion/greq",
				"param":  stat.Param,
				"url":    stat.Url,
				"method": stat.Method,
				//	"body":              stat.Body,
				"tcp_connection":    stat.TCPConnection.String(),
				"server_processing": stat.ServerProcessing.String(),
				"content_transfer":  stat.ContentTransfer.String(),
				"connect":           stat.Connect.String(),
				"start_transfer":    stat.StartTransfer.String(),
				"total":             stat.Total.String(),
			}).Debug("http trace")

		}()
		trace = &httptrace.ClientTrace{
			GotConn:              func(_ httptrace.GotConnInfo) { t3 = time.Now() },
			GotFirstResponseByte: func() { t4 = time.Now() },
		}
		req = req.WithContext(httptrace.WithClientTrace(context.Background(), trace))
	}
	if e != nil {
		return
	}
	ctx, cancel := context.WithTimeout(context.Background(), c.timeout)

	defer cancel()
	c.resolveHeaders(req)

	switch req.Method {
	case "PUT", "POST", "DELETE":
		req.Header.Set("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")
	}

	err = c.worker.Execute(ctx, req, func(resp *http.Response, err error) (er error) {
		if err != nil {
			return err
		}
		var readErr error
		defer func() {
			resp.Body.Close()
			t5 = time.Now()
		}()
		status = resp.StatusCode
		body, readErr = ioutil.ReadAll(resp.Body)
		if readErr != nil {
			return readErr
		}
		return
	})
	if err != nil {
		return
	}
	data = body
	httpstatus = status
	return

}
<commit_msg>add once header method<commit_after>package greq

import (
	"context"
	"encoding/base64"
	"errors"
	"io/ioutil"
	"net"
	"net/http"
	"net/http/httptrace"
	"net/url"
	"os"
	"strings"
	"sync"
	"time"

	log "github.com/sirupsen/logrus"

	requestwork "github.com/syhlion/requestwork.v2"
)

type Trace struct {
	Url              string        `json:"url"`
	Method           string        `json:"method"`
	Body             string        `json:"body"`
	Param            string        `json:"param"`
	DNSLookup        time.Duration `json:"dns_lookup"`
	TCPConnection    time.Duration `json:"tcp_connection"`
	TLSHandshake     time.Duration `json:"tls_handshake"`
	ServerProcessing time.Duration `json:"server_prcoessing"`
	ContentTransfer  time.Duration `json:"content_transfer"`
	NameLookup       time.Duration `json:"name_lookup"`
	Connect          time.Duration `json:"connect"`
	PreTransfer      time.Duration `json:"pre_transfer"`
	StartTransfer    time.Duration `json:"start_transfer"`
	Total            time.Duration `json:"total"`
}

var ip string

func getExternalIP() (string, error) {
	ifaces, err := net.Interfaces()
	if err != nil {
		return "", err
	}
	for _, iface := range ifaces {
		if iface.Flags&net.FlagUp == 0 {
			continue // interface down
		}
		if iface.Flags&net.FlagLoopback != 0 {
			continue // loopback interface
		}
		addrs, err := iface.Addrs()
		if err != nil {
			return "", err
		}
		for _, addr := range addrs {
			var ip net.IP
			switch v := addr.(type) {
			case *net.IPNet:
				ip = v.IP
			case *net.IPAddr:
				ip = v.IP
			}
			if ip == nil || ip.IsLoopback() {
				continue
			}
			ip = ip.To4()
			if ip == nil {
				continue // not an ipv4 address
			}
			return ip.String(), nil
		}
	}
	return "", errors.New("are you connected to the network?")
}

func init() {
	log.SetFormatter(&log.JSONFormatter{})

	log.SetOutput(os.Stdout)
	log.SetLevel(log.DebugLevel)
	ip, _ = getExternalIP()
}

//New return http client
func New(worker *requestwork.Worker, timeout time.Duration, debug bool) *Client {
	return &Client{
		worker:  worker,
		timeout: timeout,
		headers: make(map[string]string),
		lock:    &sync.RWMutex{},
		debug:   debug,
	}
}

//Client instance
type Client struct {
	worker  *requestwork.Worker
	timeout time.Duration
	headers map[string]string
	host    string
	lock    *sync.RWMutex
	debug   bool
}

//SetBasicAuth  set Basic auth
func (c *Client) SetBasicAuth(username, password string) *Client {
	auth := username + ":" + password
	hash := base64.StdEncoding.EncodeToString([]byte(auth))
	c.lock.Lock()
	defer c.lock.Unlock()
	c.headers["Authorization"] = "Basic " + hash
	return c
}

//SetHeader set http header
func (c *Client) SetHeader(key, value string) *Client {
	key = strings.Title(key)
	c.lock.Lock()
	defer c.lock.Unlock()
	c.headers[key] = value
	return c
}

//SetHost set host
func (c *Client) SetHost(host string) *Client {
	c.lock.Lock()
	defer c.lock.Unlock()

	c.host = host
	return c
}

//Get http method get
func (c *Client) Get(url string, params url.Values) (data []byte, httpstatus int, err error) {
	if params != nil {
		url += "?" + params.Encode()
	}
	req, err := http.NewRequest(http.MethodGet, url, nil)
	return c.resolveRequest(req, params, err)

}
func (c *Client) GetWithOnceHeader(url string, params url.Values, headers map[string]string) (data []byte, httpstatus int, err error) {
	req, err := http.NewRequest(http.MethodPost, url, strings.NewReader(params.Encode()))
	if err != nil {
		return
	}
	for key, value := range headers {
		req.Header.Set(key, value)
	}
	return c.resolveRequest(req, params, err)
}

//Post http method post
func (c *Client) Post(url string, params url.Values) (data []byte, httpstatus int, err error) {
	req, err := http.NewRequest(http.MethodPost, url, strings.NewReader(params.Encode()))
	return c.resolveRequest(req, params, err)
}
func (c *Client) PostWithOnceHeader(url string, params url.Values, headers map[string]string) (data []byte, httpstatus int, err error) {
	req, err := http.NewRequest(http.MethodPost, url, strings.NewReader(params.Encode()))
	if err != nil {
		return
	}
	for key, value := range headers {
		req.Header.Set(key, value)
	}
	return c.resolveRequest(req, params, err)
}

//Put http method put
func (c *Client) Put(url string, params url.Values) (data []byte, httpstatus int, err error) {
	req, err := http.NewRequest(http.MethodPut, url, strings.NewReader(params.Encode()))
	return c.resolveRequest(req, params, err)
}
func (c *Client) PutWithOnceHeader(url string, params url.Values, headers map[string]string) (data []byte, httpstatus int, err error) {
	req, err := http.NewRequest(http.MethodPost, url, strings.NewReader(params.Encode()))
	if err != nil {
		return
	}
	for key, value := range headers {
		req.Header.Set(key, value)
	}

	return c.resolveRequest(req, params, err)
}

//Delete http method Delete
func (c *Client) Delete(url string, params url.Values) (data []byte, httpstatus int, err error) {
	req, err := http.NewRequest(http.MethodDelete, url, strings.NewReader(params.Encode()))
	return c.resolveRequest(req, params, err)
}
func (c *Client) DeleteWithOnceHeader(url string, params url.Values, headers map[string]string) (data []byte, httpstatus int, err error) {
	req, err := http.NewRequest(http.MethodPost, url, strings.NewReader(params.Encode()))
	if err != nil {
		return
	}
	for key, value := range headers {
		req.Header.Set(key, value)
	}
	return c.resolveRequest(req, params, err)
}

func (c *Client) resolveHeaders(req *http.Request) {
	c.lock.RLock()
	c.lock.RUnlock()
	for key, value := range c.headers {
		req.Header.Set(key, value)
	}
	if c.host != "" {
		req.Host = c.host
	}
}

func (c *Client) resolveRequest(req *http.Request, params url.Values, e error) (data []byte, httpstatus int, err error) {
	var (
		body           []byte
		status         int
		trace          *httptrace.ClientTrace
		t0, t3, t4, t5 time.Time
	)
	t0 = time.Now()
	if c.debug {
		var stat Trace
		defer func() {
			stat.Param = params.Encode()
			stat.Url = req.URL.String()
			stat.Method = req.Method
			stat.Body = string(data)
			stat.TCPConnection = t3.Sub(t0)
			stat.ServerProcessing = t4.Sub(t3)
			stat.ContentTransfer = t5.Sub(t4)
			stat.Connect = t3.Sub(t0)
			stat.StartTransfer = t4.Sub(t0)
			stat.Total = t5.Sub(t0)
			log.WithFields(log.Fields{
				"ip":     ip,
				"name":   "syhlion/greq",
				"param":  stat.Param,
				"url":    stat.Url,
				"method": stat.Method,
				//	"body":              stat.Body,
				"tcp_connection":    stat.TCPConnection.String(),
				"server_processing": stat.ServerProcessing.String(),
				"content_transfer":  stat.ContentTransfer.String(),
				"connect":           stat.Connect.String(),
				"start_transfer":    stat.StartTransfer.String(),
				"total":             stat.Total.String(),
			}).Debug("http trace")

		}()
		trace = &httptrace.ClientTrace{
			GotConn:              func(_ httptrace.GotConnInfo) { t3 = time.Now() },
			GotFirstResponseByte: func() { t4 = time.Now() },
		}
		req = req.WithContext(httptrace.WithClientTrace(context.Background(), trace))
	}
	if e != nil {
		return
	}
	ctx, cancel := context.WithTimeout(context.Background(), c.timeout)

	defer cancel()
	c.resolveHeaders(req)

	switch req.Method {
	case "PUT", "POST", "DELETE":
		req.Header.Set("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")
	}

	err = c.worker.Execute(ctx, req, func(resp *http.Response, err error) (er error) {
		if err != nil {
			return err
		}
		var readErr error
		defer func() {
			resp.Body.Close()
			t5 = time.Now()
		}()
		status = resp.StatusCode
		body, readErr = ioutil.ReadAll(resp.Body)
		if readErr != nil {
			return readErr
		}
		return
	})
	if err != nil {
		return
	}
	data = body
	httpstatus = status
	return

}
<|endoftext|>


<commit_before>package ams

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"net/http/httputil"
	"net/url"
	"path"
	"runtime"

	"github.com/pkg/errors"
	"golang.org/x/oauth2"
)

const (
	Resource              = "https://rest.media.azure.net"
	APIVersion            = "2.15"
	StorageAPIVersion     = "2017-04-17"
	DataServiceVersion    = "3.0"
	MaxDataServiceVersion = "3.0"
	version               = "0.2.0"
)

var (
	userAgent = fmt.Sprintf("Go/%s (%s-%s) go-ams/%s", runtime.Version(), runtime.GOARCH, runtime.GOOS, version)
)

type Client struct {
	UserAgent string

	baseURL     *url.URL
	tokenSource oauth2.TokenSource

	httpClient *http.Client

	logger *log.Logger
	debug  bool
}

func NewClient(urlStr string, tokenSource oauth2.TokenSource) (*Client, error) {
	if tokenSource == nil {
		return nil, errors.New("missing tokenSource")
	}
	u, err := url.ParseRequestURI(urlStr)
	if err != nil {
		return nil, errors.Wrapf(err, "url parse failed: %s", urlStr)
	}
	defaultLogger := log.New(ioutil.Discard, "", log.LstdFlags)
	return &Client{
		UserAgent:   userAgent,
		baseURL:     u,
		tokenSource: tokenSource,
		httpClient:  http.DefaultClient,
		logger:      defaultLogger,
		debug:       false,
	}, nil
}

func (c *Client) SetDebug(debug bool) {
	c.debug = debug
}

func (c *Client) SetLogger(logger *log.Logger) {
	c.logger = logger
}

func (c *Client) newCommonRequest(ctx context.Context, u *url.URL, method string, option *requestOptions, opts ...requestOption) (*http.Request, error) {
	if err := composeOptions(opts...)(option); err != nil {
		return nil, errors.Wrap(err, "option apply failed")
	}
	if len(option.Params) != 0 {
		q := u.Query()
		mergeValues(q, option.Params)
		u.RawQuery = q.Encode()
	}
	req, err := http.NewRequest(method, u.String(), option.Body)
	if err != nil {
		return nil, err
	}
	req.Header = option.Header
	req.Header.Set("User-Agent", c.UserAgent)

	req = req.WithContext(ctx)
	return req, nil
}

func (c *Client) newRequest(ctx context.Context, method, spath string, opts ...requestOption) (*http.Request, error) {
	option := defaultRequestOption()
	u := *c.baseURL
	u.Path = path.Join(u.Path, spath)

	req, err := c.newCommonRequest(ctx, &u, method, option, opts...)
	if err != nil {
		return nil, err
	}

	token, err := c.tokenSource.Token()
	if err != nil {
		return nil, errors.Wrapf(err, "get access token failed")
	}
	token.SetAuthHeader(req)

	return req, nil
}

func (c *Client) newStorageRequest(ctx context.Context, method string, u url.URL, opts ...requestOption) (*http.Request, error) {
	option := defaultStorageRequestOption()
	return c.newCommonRequest(ctx, &u, method, option, opts...)
}

func (c *Client) do(req *http.Request, expectedCode int, out interface{}) error {
	if c.debug {
		reqDump, err := httputil.DumpRequestOut(req, false)
		if err != nil {
			return errors.Wrap(err, "request dump failed")
		}
		c.logger.Printf("[DEBUG] url = %s", req.URL.String())
		c.logger.Printf("[DEBUG] request header\n%s", string(reqDump))
	}

	resp, err := c.httpClient.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	var body io.Reader
	body = resp.Body

	if c.debug {
		respDump, err := httputil.DumpResponse(resp, false)
		if err != nil {
			return errors.Wrap(err, "response dump failed")
		}
		c.logger.Printf("[DEBUG] url = %s", req.URL.String())
		c.logger.Printf("[DEBUG] response header\n%s", string(respDump))

		var b bytes.Buffer
		if _, err := b.ReadFrom(body); err != nil {
			return errors.Wrap(err, "response body read failed")
		}
		c.logger.Printf("[DEBUG] body\n%s", b.String())

		body = &b
	}

	if err := assertStatusCode(resp, expectedCode); err != nil {
		return err
	}

	if out != nil {
		decoder := json.NewDecoder(body)
		if err := decoder.Decode(out); err != nil {
			return errors.Wrap(err, "response decode failed")
		}

		if c.debug {
			c.logger.Printf("[DEBUG] parsed body\n%#v", out)
		}
	}

	return nil
}

func (c *Client) buildURI(spath string) string {
	u := *c.baseURL
	u.Path = path.Join(u.Path, spath)
	return u.String()
}
<commit_msg>refactor: refactoring client<commit_after>package ams

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"net/http/httputil"
	"net/url"
	"path"
	"runtime"

	"github.com/k0kubun/pp"
	"github.com/pkg/errors"
	"golang.org/x/oauth2"
)

const (
	Resource              = "https://rest.media.azure.net"
	APIVersion            = "2.15"
	StorageAPIVersion     = "2017-04-17"
	DataServiceVersion    = "3.0"
	MaxDataServiceVersion = "3.0"
	version               = "0.2.0"
)

var (
	userAgent = fmt.Sprintf("Go/%s (%s-%s) go-ams/%s", runtime.Version(), runtime.GOARCH, runtime.GOOS, version)
)

type Client struct {
	UserAgent string

	baseURL     *url.URL
	tokenSource oauth2.TokenSource

	httpClient *http.Client

	logger *log.Logger
	debug  bool
}

func NewClient(urlStr string, tokenSource oauth2.TokenSource) (*Client, error) {
	if tokenSource == nil {
		return nil, errors.New("missing tokenSource")
	}
	u, err := url.ParseRequestURI(urlStr)
	if err != nil {
		return nil, errors.Wrapf(err, "url parse failed: %s", urlStr)
	}
	defaultLogger := log.New(ioutil.Discard, "", log.LstdFlags)
	return &Client{
		UserAgent:   userAgent,
		baseURL:     u,
		tokenSource: tokenSource,
		httpClient:  http.DefaultClient,
		logger:      defaultLogger,
		debug:       false,
	}, nil
}

func (c *Client) SetDebug(debug bool) {
	c.debug = debug
}

func (c *Client) SetLogger(logger *log.Logger) {
	c.logger = logger
}

func (c *Client) newCommonRequest(ctx context.Context, u *url.URL, method string, option *requestOptions, opts ...requestOption) (*http.Request, error) {
	if err := composeOptions(opts...)(option); err != nil {
		return nil, errors.Wrap(err, "option apply failed")
	}
	if len(option.Params) != 0 {
		q := u.Query()
		mergeValues(q, option.Params)
		u.RawQuery = q.Encode()
	}
	req, err := http.NewRequest(method, u.String(), option.Body)
	if err != nil {
		return nil, err
	}
	req.Header = option.Header
	req.Header.Set("User-Agent", c.UserAgent)

	req = req.WithContext(ctx)
	return req, nil
}

func (c *Client) newRequest(ctx context.Context, method, spath string, opts ...requestOption) (*http.Request, error) {
	option := defaultRequestOption()
	u := *c.baseURL
	u.Path = path.Join(u.Path, spath)

	req, err := c.newCommonRequest(ctx, &u, method, option, opts...)
	if err != nil {
		return nil, err
	}

	token, err := c.tokenSource.Token()
	if err != nil {
		return nil, errors.Wrapf(err, "get access token failed")
	}
	token.SetAuthHeader(req)

	return req, nil
}

func (c *Client) newStorageRequest(ctx context.Context, method string, u url.URL, opts ...requestOption) (*http.Request, error) {
	option := defaultStorageRequestOption()
	return c.newCommonRequest(ctx, &u, method, option, opts...)
}

func (c *Client) do(req *http.Request, expectedCode int, out interface{}) error {
	if c.debug {
		dump, err := httputil.DumpRequestOut(req, false)
		if err != nil {
			return errors.Wrap(err, "request dump failed")
		}
		c.logger.Print("[DEBUG] request header\n" + string(dump))

		var b []byte
		req.Body, b, err = sniffBody(req.Body)
		if err != nil {
			return errors.Wrap(err, "request body read failed")
		}
		c.logger.Print("[DEBUG] request body\n" + string(b))
	}

	resp, err := c.httpClient.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if c.debug {
		dump, err := httputil.DumpResponse(resp, false)
		if err != nil {
			return errors.Wrap(err, "response dump failed")
		}
		c.logger.Print("[DEBUG] response header\n" + string(dump))

		var b []byte
		resp.Body, b, err = sniffBody(resp.Body)
		if err != nil {
			return errors.Wrap(err, "response body read failed")
		}
		c.logger.Print("[DEBUG] response body\n" + string(b))
	}

	if err := assertStatusCode(resp, expectedCode); err != nil {
		return err
	}

	if out != nil {
		if err := json.NewDecoder(resp.Body).Decode(out); err != nil {
			return errors.Wrap(err, "response decode failed")
		}

		if c.debug {
			c.logger.Print("[DEBUG] parsed body")
			c.logger.Print(pp.Sprint(out))
		}
	}

	return nil
}

func (c *Client) buildURI(spath string) string {
	u := *c.baseURL
	u.Path = path.Join(u.Path, spath)
	return u.String()
}

func sniffBody(r io.ReadCloser) (io.ReadCloser, []byte, error) {
	b, err := ioutil.ReadAll(r)
	if err != nil {
		return r, []byte{}, err
	}
	return ioutil.NopCloser(bytes.NewReader(b)), b, nil
}
<|endoftext|>


<commit_before>package hbn

import "sort"

var empty struct{}

type Client struct {
	totalPages         int
	maxParallelRequest int
	api                api
}

func NewClient() Client {
	return Client{
		totalPages:         1,
		maxParallelRequest: 1,
		api:                NewHttpAPI(),
	}
}

func (c *Client) SetTotalPages(total int) {
	if total > 0 {
		c.totalPages = total
	}
}

func (c *Client) SetMaxParallelRequest(max int) {
	if max > 0 {
		c.maxParallelRequest = max
	}
}

func (c *Client) SetAPI(api api) {
	c.api = api
}

func (c Client) RequestBookmarks(user string) []Bookmark {
	bookmarksChan := make(chan []Bookmark)
	limitChan := make(chan struct{}, c.maxParallelRequest)

	go func() {
		for page := 0; page < c.totalPages; page++ {
			select {
			case limitChan <- empty:
				go func(user string, page int) {
					bookmarksChan <- c.api.RequestBookmarks(user, page)
					<-limitChan
				}(user, page)
			}
		}
	}()

	bookmarks := []Bookmark{}
	for i := 0; i < c.totalPages; i++ {
		bookmarks = append(bookmarks, <-bookmarksChan...)
	}

	return bookmarks
}

func (c Client) RequestFavoriteBookmarks(user string) []Bookmark {
	bookmarksChan := make(chan []Bookmark)
	limitChan := make(chan struct{}, c.maxParallelRequest)

	go func() {
		for page := 0; page < c.totalPages; page++ {
			select {
			case limitChan <- empty:
				go func(user string, page int) {
					bookmarksChan <- c.api.RequestFavoriteBookmarks(user, page)
					<-limitChan
				}(user, page)
			}
		}
	}()

	bookmarks := []Bookmark{}
	for i := 0; i < c.totalPages; i++ {
		bookmarks = append(bookmarks, <-bookmarksChan...)
	}

	return bookmarks
}

func (c Client) RequestHotEntryBookmarks() []Bookmark {
	return c.api.RequestHotEntryBookmarks()
}

func (c Client) RequestSearchBookmarks(query string) []Bookmark {
	return c.api.RequestSearchBookmarks(query)
}

func (c Client) RequestRelatedBookmarks(user string) []Bookmark {
	bookmarksChan := make(chan []Bookmark)
	limitChan := make(chan struct{}, c.maxParallelRequest)

	baseBookmarks := c.RequestBookmarks(user)
	go func() {
		for _, bookmark := range baseBookmarks {
			select {
			case limitChan <- empty:
				go func(url string) {
					bookmarksChan <- c.api.RequestRelatedBookmarks(url)
					<-limitChan
				}(bookmark.URL)
			}
		}
	}()

	bookmarks := []Bookmark{}
	for i := 0; i < len(baseBookmarks); i++ {
		bookmarks = append(bookmarks, <-bookmarksChan...)
	}

	return bookmarks
}

func (c Client) SearchNeighbors(bookmarks []Bookmark) Neighbors {
	ubChan := make(chan map[string]Bookmark)
	limitChan := make(chan struct{}, c.maxParallelRequest)

	go func() {
		for _, bookmark := range bookmarks {
			select {
			case limitChan <- empty:
				go func(url string) {
					ubChan <- c.api.RequestUserBookmark(url)
					<-limitChan
				}(bookmark.URL)
			}
		}
	}()

	// calculate common bookmarks
	cb := map[string][]Bookmark{}
	for i := 0; i < len(bookmarks); i++ {
		for u, b := range <-ubChan {
			cb[u] = append(cb[u], b)
		}
	}

	neighbors := Neighbors{}
	for user, common := range cb {
		neighbors = append(neighbors, Neighbor{
			User:            user,
			Similarity:      float64(len(common)) / float64(len(bookmarks)),
			CommonBookmarks: common,
		})
	}
	sort.Sort(neighbors)

	return neighbors
}
<commit_msg>modify initial max parallel request<commit_after>package hbn

import (
	"runtime"
	"sort"
)

var empty struct{}

type Client struct {
	totalPages         int
	maxParallelRequest int
	api                api
}

func NewClient() Client {
	return Client{
		totalPages:         1,
		maxParallelRequest: runtime.GOMAXPROCS(runtime.NumCPU()),
		api:                NewHttpAPI(),
	}
}

func (c *Client) SetTotalPages(total int) {
	if total > 0 {
		c.totalPages = total
	}
}

func (c *Client) SetMaxParallelRequest(max int) {
	if max > 0 {
		c.maxParallelRequest = max
	}
}

func (c *Client) SetAPI(api api) {
	c.api = api
}

func (c Client) RequestBookmarks(user string) []Bookmark {
	bookmarksChan := make(chan []Bookmark)
	limitChan := make(chan struct{}, c.maxParallelRequest)

	go func() {
		for page := 0; page < c.totalPages; page++ {
			select {
			case limitChan <- empty:
				go func(user string, page int) {
					bookmarksChan <- c.api.RequestBookmarks(user, page)
					<-limitChan
				}(user, page)
			}
		}
	}()

	bookmarks := []Bookmark{}
	for i := 0; i < c.totalPages; i++ {
		bookmarks = append(bookmarks, <-bookmarksChan...)
	}

	return bookmarks
}

func (c Client) RequestFavoriteBookmarks(user string) []Bookmark {
	bookmarksChan := make(chan []Bookmark)
	limitChan := make(chan struct{}, c.maxParallelRequest)

	go func() {
		for page := 0; page < c.totalPages; page++ {
			select {
			case limitChan <- empty:
				go func(user string, page int) {
					bookmarksChan <- c.api.RequestFavoriteBookmarks(user, page)
					<-limitChan
				}(user, page)
			}
		}
	}()

	bookmarks := []Bookmark{}
	for i := 0; i < c.totalPages; i++ {
		bookmarks = append(bookmarks, <-bookmarksChan...)
	}

	return bookmarks
}

func (c Client) RequestHotEntryBookmarks() []Bookmark {
	return c.api.RequestHotEntryBookmarks()
}

func (c Client) RequestSearchBookmarks(query string) []Bookmark {
	return c.api.RequestSearchBookmarks(query)
}

func (c Client) RequestRelatedBookmarks(user string) []Bookmark {
	bookmarksChan := make(chan []Bookmark)
	limitChan := make(chan struct{}, c.maxParallelRequest)

	baseBookmarks := c.RequestBookmarks(user)
	go func() {
		for _, bookmark := range baseBookmarks {
			select {
			case limitChan <- empty:
				go func(url string) {
					bookmarksChan <- c.api.RequestRelatedBookmarks(url)
					<-limitChan
				}(bookmark.URL)
			}
		}
	}()

	bookmarks := []Bookmark{}
	for i := 0; i < len(baseBookmarks); i++ {
		bookmarks = append(bookmarks, <-bookmarksChan...)
	}

	return bookmarks
}

func (c Client) SearchNeighbors(bookmarks []Bookmark) Neighbors {
	ubChan := make(chan map[string]Bookmark)
	limitChan := make(chan struct{}, c.maxParallelRequest)

	go func() {
		for _, bookmark := range bookmarks {
			select {
			case limitChan <- empty:
				go func(url string) {
					ubChan <- c.api.RequestUserBookmark(url)
					<-limitChan
				}(bookmark.URL)
			}
		}
	}()

	// calculate common bookmarks
	cb := map[string][]Bookmark{}
	for i := 0; i < len(bookmarks); i++ {
		for u, b := range <-ubChan {
			cb[u] = append(cb[u], b)
		}
	}

	neighbors := Neighbors{}
	for user, common := range cb {
		neighbors = append(neighbors, Neighbor{
			User:            user,
			Similarity:      float64(len(common)) / float64(len(bookmarks)),
			CommonBookmarks: common,
		})
	}
	sort.Sort(neighbors)

	return neighbors
}
<|endoftext|>


<commit_before>package libovsdb

import (
	"encoding/json"
	"errors"
	"fmt"
	"log"
	"net"
	"sync"

	"github.com/cenk/rpc2"
	"github.com/cenk/rpc2/jsonrpc"
)

type OvsdbClient struct {
	rpcClient *rpc2.Client
	Schema    map[string]DatabaseSchema
	handlers  []NotificationHandler
}

func newOvsdbClient(c *rpc2.Client) *OvsdbClient {
	connections_mutex.Lock()
	defer connections_mutex.Unlock()
	ovs := &OvsdbClient{rpcClient: c, Schema: make(map[string]DatabaseSchema)}
	if connections == nil {
		connections = make(map[*rpc2.Client]*OvsdbClient)
	}
	connections[c] = ovs
	return ovs
}

// Would rather replace this connection map with an OvsdbClient Receiver scoped method
// Unfortunately rpc2 package acts wierd with a receiver scoped method and needs some investigation.
var connections map[*rpc2.Client]*OvsdbClient
var connections_mutex sync.RWMutex

const DEFAULT_ADDR = "127.0.0.1"
const DEFAULT_PORT = 6640

func Dial(network, address string) (*OvsdbClient, error) {
	conn, err := net.Dial(network, address)

	if err != nil {
		return nil, err
	}

	c := rpc2.NewClientWithCodec(jsonrpc.NewJSONCodec(conn))
	c.Handle("echo", echo)
	c.Handle("update", update)
	go c.Run()
	go handleDisconnectNotification(c)

	ovs := newOvsdbClient(c)

	// Process Async Notifications
	dbs, err := ovs.ListDbs()
	if err == nil {
		for _, db := range dbs {
			schema, err := ovs.GetSchema(db)
			if err == nil {
				ovs.Schema[db] = *schema
			} else {
				return nil, err
			}
		}
	}
	return ovs, nil
}

func Connect(ipAddr string, port int) (*OvsdbClient, error) {
	if ipAddr == "" {
		ipAddr = DEFAULT_ADDR
	}

	if port <= 0 {
		port = DEFAULT_PORT
	}

	target := fmt.Sprintf("%s:%d", ipAddr, port)

	return Dial("tcp", target)
}

func (ovs *OvsdbClient) Register(handler NotificationHandler) {
	ovs.handlers = append(ovs.handlers, handler)
}

type NotificationHandler interface {
	// RFC 7047 section 4.1.6 Update Notification
	Update(context interface{}, tableUpdates TableUpdates)

	// RFC 7047 section 4.1.9 Locked Notification
	Locked([]interface{})

	// RFC 7047 section 4.1.10 Stolen Notification
	Stolen([]interface{})

	// RFC 7047 section 4.1.11 Echo Notification
	Echo([]interface{})

	Disconnected(*OvsdbClient)
}

// RFC 7047 : Section 4.1.6 : Echo
func echo(client *rpc2.Client, args []interface{}, reply *[]interface{}) error {
	connections_mutex.RLock()
	defer connections_mutex.RUnlock()
	*reply = args
	if _, ok := connections[client]; ok {
		for _, handler := range connections[client].handlers {
			handler.Echo(nil)
		}
	}
	return nil
}

// RFC 7047 : Update Notification Section 4.1.6
// Processing "params": [<json-value>, <table-updates>]
func update(client *rpc2.Client, params []interface{}, reply *interface{}) error {
	if len(params) < 2 {
		return errors.New("Invalid Update message")
	}
	// Ignore params[0] as we dont use the <json-value> currently for comparison

	raw, ok := params[1].(map[string]interface{})
	if !ok {
		return errors.New("Invalid Update message")
	}
	var rowUpdates map[string]map[string]RowUpdate

	b, err := json.Marshal(raw)
	if err != nil {
		return err
	}
	err = json.Unmarshal(b, &rowUpdates)
	if err != nil {
		return err
	}

	connections_mutex.RLock()
	defer connections_mutex.RUnlock()
	// Update the local DB cache with the tableUpdates
	tableUpdates := getTableUpdatesFromRawUnmarshal(rowUpdates)
	if _, ok := connections[client]; ok {
		for _, handler := range connections[client].handlers {
			handler.Update(params, tableUpdates)
		}
	}

	return nil
}

// RFC 7047 : get_schema
func (ovs OvsdbClient) GetSchema(dbName string) (*DatabaseSchema, error) {
	args := NewGetSchemaArgs(dbName)
	var reply DatabaseSchema
	err := ovs.rpcClient.Call("get_schema", args, &reply)
	if err != nil {
		return nil, err
	} else {
		ovs.Schema[dbName] = reply
	}
	return &reply, err
}

// RFC 7047 : list_dbs
func (ovs OvsdbClient) ListDbs() ([]string, error) {
	var dbs []string
	err := ovs.rpcClient.Call("list_dbs", nil, &dbs)
	if err != nil {
		log.Fatal("ListDbs failure", err)
	}
	return dbs, err
}

// RFC 7047 : transact

func (ovs OvsdbClient) Transact(database string, operation ...Operation) ([]OperationResult, error) {
	var reply []OperationResult
	db, ok := ovs.Schema[database]
	if !ok {
		return nil, errors.New("invalid Database Schema")
	}

	if ok := db.validateOperations(operation...); !ok {
		return nil, errors.New("Validation failed for the operation")
	}

	args := NewTransactArgs(database, operation...)
	err := ovs.rpcClient.Call("transact", args, &reply)
	if err != nil {
		return nil, err
	}
	return reply, nil
}

// Convenience method to monitor every table/column
func (ovs OvsdbClient) MonitorAll(database string, jsonContext interface{}) (*TableUpdates, error) {
	schema, ok := ovs.Schema[database]
	if !ok {
		return nil, errors.New("invalid Database Schema")
	}

	requests := make(map[string]MonitorRequest)
	for table, tableSchema := range schema.Tables {
		var columns []string
		for column, _ := range tableSchema.Columns {
			columns = append(columns, column)
		}
		requests[table] = MonitorRequest{
			Columns: columns,
			Select: MonitorSelect{
				Initial: true,
				Insert:  true,
				Delete:  true,
				Modify:  true,
			}}
	}
	return ovs.Monitor(database, jsonContext, requests)
}

// RFC 7047 : monitor
func (ovs OvsdbClient) Monitor(database string, jsonContext interface{}, requests map[string]MonitorRequest) (*TableUpdates, error) {
	var reply TableUpdates

	args := NewMonitorArgs(database, jsonContext, requests)

	// This totally sucks. Refer to golang JSON issue #6213
	var response map[string]map[string]RowUpdate
	err := ovs.rpcClient.Call("monitor", args, &response)
	if err != nil {
		return nil, err
	}
	reply = getTableUpdatesFromRawUnmarshal(response)
	return &reply, err
}

func getTableUpdatesFromRawUnmarshal(raw map[string]map[string]RowUpdate) TableUpdates {
	var tableUpdates TableUpdates
	tableUpdates.Updates = make(map[string]TableUpdate)
	for table, update := range raw {
		tableUpdate := TableUpdate{update}
		tableUpdates.Updates[table] = tableUpdate
	}
	return tableUpdates
}

func clearConnection(c *rpc2.Client) {
	connections_mutex.RLock()
	defer connections_mutex.RUnlock()
	if _, ok := connections[c]; ok {
		for _, handler := range connections[c].handlers {
			if handler != nil {
				handler.Disconnected(connections[c])
			}
		}
	}
	delete(connections, c)
}

func handleDisconnectNotification(c *rpc2.Client) {
	disconnected := c.DisconnectNotify()
	select {
	case <-disconnected:
		clearConnection(c)
	}
}

func (ovs OvsdbClient) Disconnect() {
	ovs.rpcClient.Close()
	clearConnection(ovs.rpcClient)
}
<commit_msg>Fixed data race condition. Use read/write lock instead of read lock in clearConnection().<commit_after>package libovsdb

import (
	"encoding/json"
	"errors"
	"fmt"
	"log"
	"net"
	"sync"

	"github.com/cenk/rpc2"
	"github.com/cenk/rpc2/jsonrpc"
)

type OvsdbClient struct {
	rpcClient *rpc2.Client
	Schema    map[string]DatabaseSchema
	handlers  []NotificationHandler
}

func newOvsdbClient(c *rpc2.Client) *OvsdbClient {
	connections_mutex.Lock()
	defer connections_mutex.Unlock()
	ovs := &OvsdbClient{rpcClient: c, Schema: make(map[string]DatabaseSchema)}
	if connections == nil {
		connections = make(map[*rpc2.Client]*OvsdbClient)
	}
	connections[c] = ovs
	return ovs
}

// Would rather replace this connection map with an OvsdbClient Receiver scoped method
// Unfortunately rpc2 package acts wierd with a receiver scoped method and needs some investigation.
var connections map[*rpc2.Client]*OvsdbClient
var connections_mutex sync.RWMutex

const DEFAULT_ADDR = "127.0.0.1"
const DEFAULT_PORT = 6640

func Dial(network, address string) (*OvsdbClient, error) {
	conn, err := net.Dial(network, address)

	if err != nil {
		return nil, err
	}

	c := rpc2.NewClientWithCodec(jsonrpc.NewJSONCodec(conn))
	c.Handle("echo", echo)
	c.Handle("update", update)
	go c.Run()
	go handleDisconnectNotification(c)

	ovs := newOvsdbClient(c)

	// Process Async Notifications
	dbs, err := ovs.ListDbs()
	if err == nil {
		for _, db := range dbs {
			schema, err := ovs.GetSchema(db)
			if err == nil {
				ovs.Schema[db] = *schema
			} else {
				return nil, err
			}
		}
	}
	return ovs, nil
}

func Connect(ipAddr string, port int) (*OvsdbClient, error) {
	if ipAddr == "" {
		ipAddr = DEFAULT_ADDR
	}

	if port <= 0 {
		port = DEFAULT_PORT
	}

	target := fmt.Sprintf("%s:%d", ipAddr, port)

	return Dial("tcp", target)
}

func (ovs *OvsdbClient) Register(handler NotificationHandler) {
	ovs.handlers = append(ovs.handlers, handler)
}

type NotificationHandler interface {
	// RFC 7047 section 4.1.6 Update Notification
	Update(context interface{}, tableUpdates TableUpdates)

	// RFC 7047 section 4.1.9 Locked Notification
	Locked([]interface{})

	// RFC 7047 section 4.1.10 Stolen Notification
	Stolen([]interface{})

	// RFC 7047 section 4.1.11 Echo Notification
	Echo([]interface{})

	Disconnected(*OvsdbClient)
}

// RFC 7047 : Section 4.1.6 : Echo
func echo(client *rpc2.Client, args []interface{}, reply *[]interface{}) error {
	connections_mutex.RLock()
	defer connections_mutex.RUnlock()
	*reply = args
	if _, ok := connections[client]; ok {
		for _, handler := range connections[client].handlers {
			handler.Echo(nil)
		}
	}
	return nil
}

// RFC 7047 : Update Notification Section 4.1.6
// Processing "params": [<json-value>, <table-updates>]
func update(client *rpc2.Client, params []interface{}, reply *interface{}) error {
	if len(params) < 2 {
		return errors.New("Invalid Update message")
	}
	// Ignore params[0] as we dont use the <json-value> currently for comparison

	raw, ok := params[1].(map[string]interface{})
	if !ok {
		return errors.New("Invalid Update message")
	}
	var rowUpdates map[string]map[string]RowUpdate

	b, err := json.Marshal(raw)
	if err != nil {
		return err
	}
	err = json.Unmarshal(b, &rowUpdates)
	if err != nil {
		return err
	}

	connections_mutex.RLock()
	defer connections_mutex.RUnlock()
	// Update the local DB cache with the tableUpdates
	tableUpdates := getTableUpdatesFromRawUnmarshal(rowUpdates)
	if _, ok := connections[client]; ok {
		for _, handler := range connections[client].handlers {
			handler.Update(params, tableUpdates)
		}
	}

	return nil
}

// RFC 7047 : get_schema
func (ovs OvsdbClient) GetSchema(dbName string) (*DatabaseSchema, error) {
	args := NewGetSchemaArgs(dbName)
	var reply DatabaseSchema
	err := ovs.rpcClient.Call("get_schema", args, &reply)
	if err != nil {
		return nil, err
	} else {
		ovs.Schema[dbName] = reply
	}
	return &reply, err
}

// RFC 7047 : list_dbs
func (ovs OvsdbClient) ListDbs() ([]string, error) {
	var dbs []string
	err := ovs.rpcClient.Call("list_dbs", nil, &dbs)
	if err != nil {
		log.Fatal("ListDbs failure", err)
	}
	return dbs, err
}

// RFC 7047 : transact

func (ovs OvsdbClient) Transact(database string, operation ...Operation) ([]OperationResult, error) {
	var reply []OperationResult
	db, ok := ovs.Schema[database]
	if !ok {
		return nil, errors.New("invalid Database Schema")
	}

	if ok := db.validateOperations(operation...); !ok {
		return nil, errors.New("Validation failed for the operation")
	}

	args := NewTransactArgs(database, operation...)
	err := ovs.rpcClient.Call("transact", args, &reply)
	if err != nil {
		return nil, err
	}
	return reply, nil
}

// Convenience method to monitor every table/column
func (ovs OvsdbClient) MonitorAll(database string, jsonContext interface{}) (*TableUpdates, error) {
	schema, ok := ovs.Schema[database]
	if !ok {
		return nil, errors.New("invalid Database Schema")
	}

	requests := make(map[string]MonitorRequest)
	for table, tableSchema := range schema.Tables {
		var columns []string
		for column, _ := range tableSchema.Columns {
			columns = append(columns, column)
		}
		requests[table] = MonitorRequest{
			Columns: columns,
			Select: MonitorSelect{
				Initial: true,
				Insert:  true,
				Delete:  true,
				Modify:  true,
			}}
	}
	return ovs.Monitor(database, jsonContext, requests)
}

// RFC 7047 : monitor
func (ovs OvsdbClient) Monitor(database string, jsonContext interface{}, requests map[string]MonitorRequest) (*TableUpdates, error) {
	var reply TableUpdates

	args := NewMonitorArgs(database, jsonContext, requests)

	// This totally sucks. Refer to golang JSON issue #6213
	var response map[string]map[string]RowUpdate
	err := ovs.rpcClient.Call("monitor", args, &response)
	if err != nil {
		return nil, err
	}
	reply = getTableUpdatesFromRawUnmarshal(response)
	return &reply, err
}

func getTableUpdatesFromRawUnmarshal(raw map[string]map[string]RowUpdate) TableUpdates {
	var tableUpdates TableUpdates
	tableUpdates.Updates = make(map[string]TableUpdate)
	for table, update := range raw {
		tableUpdate := TableUpdate{update}
		tableUpdates.Updates[table] = tableUpdate
	}
	return tableUpdates
}

func clearConnection(c *rpc2.Client) {
	connections_mutex.Lock()
	defer connections_mutex.Unlock()
	if _, ok := connections[c]; ok {
		for _, handler := range connections[c].handlers {
			if handler != nil {
				handler.Disconnected(connections[c])
			}
		}
	}
	delete(connections, c)
}

func handleDisconnectNotification(c *rpc2.Client) {
	disconnected := c.DisconnectNotify()
	select {
	case <-disconnected:
		clearConnection(c)
	}
}

func (ovs OvsdbClient) Disconnect() {
	ovs.rpcClient.Close()
	clearConnection(ovs.rpcClient)
}
<|endoftext|>


<commit_before>// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package websocket

import (
	"bufio"
	"bytes"
	"crypto/tls"
	"encoding/base64"
	"errors"
	"io"
	"io/ioutil"
	"net"
	"net/http"
	"net/url"
	"strings"
	"time"
)

// ErrBadHandshake is returned when the server response to opening handshake is
// invalid.
var ErrBadHandshake = errors.New("websocket: bad handshake")

var errInvalidCompression = errors.New("websocket: invalid compression negotiation")

// NewClient creates a new client connection using the given net connection.
// The URL u specifies the host and request URI. Use requestHeader to specify
// the origin (Origin), subprotocols (Sec-WebSocket-Protocol) and cookies
// (Cookie). Use the response.Header to get the selected subprotocol
// (Sec-WebSocket-Protocol) and cookies (Set-Cookie).
//
// If the WebSocket handshake fails, ErrBadHandshake is returned along with a
// non-nil *http.Response so that callers can handle redirects, authentication,
// etc.
//
// Deprecated: Use Dialer instead.
func NewClient(netConn net.Conn, u *url.URL, requestHeader http.Header, readBufSize, writeBufSize int) (c *Conn, response *http.Response, err error) {
	d := Dialer{
		ReadBufferSize:  readBufSize,
		WriteBufferSize: writeBufSize,
		NetDial: func(net, addr string) (net.Conn, error) {
			return netConn, nil
		},
	}
	return d.Dial(u.String(), requestHeader)
}

// A Dialer contains options for connecting to WebSocket server.
type Dialer struct {
	// NetDial specifies the dial function for creating TCP connections. If
	// NetDial is nil, net.Dial is used.
	NetDial func(network, addr string) (net.Conn, error)

	// Proxy specifies a function to return a proxy for a given
	// Request. If the function returns a non-nil error, the
	// request is aborted with the provided error.
	// If Proxy is nil or returns a nil *URL, no proxy is used.
	Proxy func(*http.Request) (*url.URL, error)

	// TLSClientConfig specifies the TLS configuration to use with tls.Client.
	// If nil, the default configuration is used.
	TLSClientConfig *tls.Config

	// HandshakeTimeout specifies the duration for the handshake to complete.
	HandshakeTimeout time.Duration

	// ReadBufferSize and WriteBufferSize specify I/O buffer sizes. If a buffer
	// size is zero, then a useful default size is used. The I/O buffer sizes
	// do not limit the size of the messages that can be sent or received.
	ReadBufferSize, WriteBufferSize int

	// Subprotocols specifies the client's requested subprotocols.
	Subprotocols []string

	// EnableCompression specifies if the client should attempt to negotiate
	// per message compression (RFC 7692). Setting this value to true does not
	// guarantee that compression will be supported. Currently only "no context
	// takeover" modes are supported.
	EnableCompression bool

	// Jar specifies the cookie jar.
	// If Jar is nil, cookies are not sent in requests and ignored
	// in responses.
	Jar http.CookieJar
}

var errMalformedURL = errors.New("malformed ws or wss URL")

// parseURL parses the URL.
//
// This function is a replacement for the standard library url.Parse function.
// In Go 1.4 and earlier, url.Parse loses information from the path.
func parseURL(s string) (*url.URL, error) {
	// From the RFC:
	//
	// ws-URI = "ws:" "//" host [ ":" port ] path [ "?" query ]
	// wss-URI = "wss:" "//" host [ ":" port ] path [ "?" query ]
	var u url.URL
	switch {
	case strings.HasPrefix(s, "ws://"):
		u.Scheme = "ws"
		s = s[len("ws://"):]
	case strings.HasPrefix(s, "wss://"):
		u.Scheme = "wss"
		s = s[len("wss://"):]
	default:
		return nil, errMalformedURL
	}

	if i := strings.Index(s, "?"); i >= 0 {
		u.RawQuery = s[i+1:]
		s = s[:i]
	}

	if i := strings.Index(s, "/"); i >= 0 {
		u.Opaque = s[i:]
		s = s[:i]
	} else {
		u.Opaque = "/"
	}

	u.Host = s

	if strings.Contains(u.Host, "@") {
		// Don't bother parsing user information because user information is
		// not allowed in websocket URIs.
		return nil, errMalformedURL
	}

	return &u, nil
}

func hostPortNoPort(u *url.URL) (hostPort, hostNoPort string) {
	hostPort = u.Host
	hostNoPort = u.Host
	if i := strings.LastIndex(u.Host, ":"); i > strings.LastIndex(u.Host, "]") {
		hostNoPort = hostNoPort[:i]
	} else {
		switch u.Scheme {
		case "wss":
			hostPort += ":443"
		case "https":
			hostPort += ":443"
		default:
			hostPort += ":80"
		}
	}
	return hostPort, hostNoPort
}

// DefaultDialer is a dialer with all fields set to the default zero values.
var DefaultDialer = &Dialer{
	Proxy: http.ProxyFromEnvironment,
}

// Dial creates a new client connection. Use requestHeader to specify the
// origin (Origin), subprotocols (Sec-WebSocket-Protocol) and cookies (Cookie).
// Use the response.Header to get the selected subprotocol
// (Sec-WebSocket-Protocol) and cookies (Set-Cookie).
//
// If the WebSocket handshake fails, ErrBadHandshake is returned along with a
// non-nil *http.Response so that callers can handle redirects, authentication,
// etcetera. The response body may not contain the entire response and does not
// need to be closed by the application.
func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Response, error) {

	if d == nil {
		d = &Dialer{
			Proxy: http.ProxyFromEnvironment,
		}
	}

	challengeKey, err := generateChallengeKey()
	if err != nil {
		return nil, nil, err
	}

	u, err := parseURL(urlStr)
	if err != nil {
		return nil, nil, err
	}

	switch u.Scheme {
	case "ws":
		u.Scheme = "http"
	case "wss":
		u.Scheme = "https"
	default:
		return nil, nil, errMalformedURL
	}

	if u.User != nil {
		// User name and password are not allowed in websocket URIs.
		return nil, nil, errMalformedURL
	}

	req := &http.Request{
		Method:     "GET",
		URL:        u,
		Proto:      "HTTP/1.1",
		ProtoMajor: 1,
		ProtoMinor: 1,
		Header:     make(http.Header),
		Host:       u.Host,
	}

	// Set the cookies present in the cookie jar of the dialer
	if d.Jar != nil {
		for _, cookie := range d.Jar.Cookies(u) {
			req.AddCookie(cookie)
		}
	}

	// Set the request headers using the capitalization for names and values in
	// RFC examples. Although the capitalization shouldn't matter, there are
	// servers that depend on it. The Header.Set method is not used because the
	// method canonicalizes the header names.
	req.Header["Upgrade"] = []string{"websocket"}
	req.Header["Connection"] = []string{"Upgrade"}
	req.Header["Sec-WebSocket-Key"] = []string{challengeKey}
	req.Header["Sec-WebSocket-Version"] = []string{"13"}
	if len(d.Subprotocols) > 0 {
		req.Header["Sec-WebSocket-Protocol"] = []string{strings.Join(d.Subprotocols, ", ")}
	}
	for k, vs := range requestHeader {
		switch {
		case k == "Host":
			if len(vs) > 0 {
				req.Host = vs[0]
			}
		case k == "Upgrade" ||
			k == "Connection" ||
			k == "Sec-Websocket-Key" ||
			k == "Sec-Websocket-Version" ||
			k == "Sec-Websocket-Extensions" ||
			(k == "Sec-Websocket-Protocol" && len(d.Subprotocols) > 0):
			return nil, nil, errors.New("websocket: duplicate header not allowed: " + k)
		default:
			req.Header[k] = vs
		}
	}

	if d.EnableCompression {
		req.Header.Set("Sec-Websocket-Extensions", "permessage-deflate; server_no_context_takeover; client_no_context_takeover")
	}

	hostPort, hostNoPort := hostPortNoPort(u)

	var proxyURL *url.URL
	// Check wether the proxy method has been configured
	if d.Proxy != nil {
		proxyURL, err = d.Proxy(req)
	}
	if err != nil {
		return nil, nil, err
	}

	var targetHostPort string
	if proxyURL != nil {
		targetHostPort, _ = hostPortNoPort(proxyURL)
	} else {
		targetHostPort = hostPort
	}

	var deadline time.Time
	if d.HandshakeTimeout != 0 {
		deadline = time.Now().Add(d.HandshakeTimeout)
	}

	netDial := d.NetDial
	if netDial == nil {
		netDialer := &net.Dialer{Deadline: deadline}
		netDial = netDialer.Dial
	}

	netConn, err := netDial("tcp", targetHostPort)
	if err != nil {
		return nil, nil, err
	}

	defer func() {
		if netConn != nil {
			netConn.Close()
		}
	}()

	if err := netConn.SetDeadline(deadline); err != nil {
		return nil, nil, err
	}

	if proxyURL != nil {
		connectHeader := make(http.Header)
		if user := proxyURL.User; user != nil {
			proxyUser := user.Username()
			if proxyPassword, passwordSet := user.Password(); passwordSet {
				credential := base64.StdEncoding.EncodeToString([]byte(proxyUser + ":" + proxyPassword))
				connectHeader.Set("Proxy-Authorization", "Basic "+credential)
			}
		}
		connectReq := &http.Request{
			Method: "CONNECT",
			URL:    &url.URL{Opaque: hostPort},
			Host:   hostPort,
			Header: connectHeader,
		}

		connectReq.Write(netConn)

		// Read response.
		// Okay to use and discard buffered reader here, because
		// TLS server will not speak until spoken to.
		br := bufio.NewReader(netConn)
		resp, err := http.ReadResponse(br, connectReq)
		if err != nil {
			return nil, nil, err
		}
		if resp.StatusCode != 200 {
			f := strings.SplitN(resp.Status, " ", 2)
			return nil, nil, errors.New(f[1])
		}
	}

	if u.Scheme == "https" {
		cfg := cloneTLSConfig(d.TLSClientConfig)
		if cfg.ServerName == "" {
			cfg.ServerName = hostNoPort
		}
		tlsConn := tls.Client(netConn, cfg)
		netConn = tlsConn
		if err := tlsConn.Handshake(); err != nil {
			return nil, nil, err
		}
		if !cfg.InsecureSkipVerify {
			if err := tlsConn.VerifyHostname(cfg.ServerName); err != nil {
				return nil, nil, err
			}
		}
	}

	conn := newConn(netConn, false, d.ReadBufferSize, d.WriteBufferSize)

	if err := req.Write(netConn); err != nil {
		return nil, nil, err
	}

	resp, err := http.ReadResponse(conn.br, req)
	if err != nil {
		return nil, nil, err
	}

	if d.Jar != nil {
		if rc := resp.Cookies(); len(rc) > 0 {
			d.Jar.SetCookies(u, rc)
		}
	}

	if resp.StatusCode != 101 ||
		!strings.EqualFold(resp.Header.Get("Upgrade"), "websocket") ||
		!strings.EqualFold(resp.Header.Get("Connection"), "upgrade") ||
		resp.Header.Get("Sec-Websocket-Accept") != computeAcceptKey(challengeKey) {
		// Before closing the network connection on return from this
		// function, slurp up some of the response to aid application
		// debugging.
		buf := make([]byte, 1024)
		n, _ := io.ReadFull(resp.Body, buf)
		resp.Body = ioutil.NopCloser(bytes.NewReader(buf[:n]))
		return nil, resp, ErrBadHandshake
	}

	for _, ext := range parseExtensions(req.Header) {
		if ext[""] != "permessage-deflate" {
			continue
		}
		_, snct := ext["server_no_context_takeover"]
		_, cnct := ext["client_no_context_takeover"]
		if !snct || !cnct {
			return nil, resp, errInvalidCompression
		}
		conn.newCompressionWriter = compressNoContextTakeover
		conn.newDecompressionReader = decompressNoContextTakeover
		break
	}

	resp.Body = ioutil.NopCloser(bytes.NewReader([]byte{}))
	conn.subprotocol = resp.Header.Get("Sec-Websocket-Protocol")

	netConn.SetDeadline(time.Time{})
	netConn = nil // to avoid close in defer.
	return conn, resp, nil
}
<commit_msg>enable client compression based on response header<commit_after>// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package websocket

import (
	"bufio"
	"bytes"
	"crypto/tls"
	"encoding/base64"
	"errors"
	"io"
	"io/ioutil"
	"net"
	"net/http"
	"net/url"
	"strings"
	"time"
)

// ErrBadHandshake is returned when the server response to opening handshake is
// invalid.
var ErrBadHandshake = errors.New("websocket: bad handshake")

var errInvalidCompression = errors.New("websocket: invalid compression negotiation")

// NewClient creates a new client connection using the given net connection.
// The URL u specifies the host and request URI. Use requestHeader to specify
// the origin (Origin), subprotocols (Sec-WebSocket-Protocol) and cookies
// (Cookie). Use the response.Header to get the selected subprotocol
// (Sec-WebSocket-Protocol) and cookies (Set-Cookie).
//
// If the WebSocket handshake fails, ErrBadHandshake is returned along with a
// non-nil *http.Response so that callers can handle redirects, authentication,
// etc.
//
// Deprecated: Use Dialer instead.
func NewClient(netConn net.Conn, u *url.URL, requestHeader http.Header, readBufSize, writeBufSize int) (c *Conn, response *http.Response, err error) {
	d := Dialer{
		ReadBufferSize:  readBufSize,
		WriteBufferSize: writeBufSize,
		NetDial: func(net, addr string) (net.Conn, error) {
			return netConn, nil
		},
	}
	return d.Dial(u.String(), requestHeader)
}

// A Dialer contains options for connecting to WebSocket server.
type Dialer struct {
	// NetDial specifies the dial function for creating TCP connections. If
	// NetDial is nil, net.Dial is used.
	NetDial func(network, addr string) (net.Conn, error)

	// Proxy specifies a function to return a proxy for a given
	// Request. If the function returns a non-nil error, the
	// request is aborted with the provided error.
	// If Proxy is nil or returns a nil *URL, no proxy is used.
	Proxy func(*http.Request) (*url.URL, error)

	// TLSClientConfig specifies the TLS configuration to use with tls.Client.
	// If nil, the default configuration is used.
	TLSClientConfig *tls.Config

	// HandshakeTimeout specifies the duration for the handshake to complete.
	HandshakeTimeout time.Duration

	// ReadBufferSize and WriteBufferSize specify I/O buffer sizes. If a buffer
	// size is zero, then a useful default size is used. The I/O buffer sizes
	// do not limit the size of the messages that can be sent or received.
	ReadBufferSize, WriteBufferSize int

	// Subprotocols specifies the client's requested subprotocols.
	Subprotocols []string

	// EnableCompression specifies if the client should attempt to negotiate
	// per message compression (RFC 7692). Setting this value to true does not
	// guarantee that compression will be supported. Currently only "no context
	// takeover" modes are supported.
	EnableCompression bool

	// Jar specifies the cookie jar.
	// If Jar is nil, cookies are not sent in requests and ignored
	// in responses.
	Jar http.CookieJar
}

var errMalformedURL = errors.New("malformed ws or wss URL")

// parseURL parses the URL.
//
// This function is a replacement for the standard library url.Parse function.
// In Go 1.4 and earlier, url.Parse loses information from the path.
func parseURL(s string) (*url.URL, error) {
	// From the RFC:
	//
	// ws-URI = "ws:" "//" host [ ":" port ] path [ "?" query ]
	// wss-URI = "wss:" "//" host [ ":" port ] path [ "?" query ]
	var u url.URL
	switch {
	case strings.HasPrefix(s, "ws://"):
		u.Scheme = "ws"
		s = s[len("ws://"):]
	case strings.HasPrefix(s, "wss://"):
		u.Scheme = "wss"
		s = s[len("wss://"):]
	default:
		return nil, errMalformedURL
	}

	if i := strings.Index(s, "?"); i >= 0 {
		u.RawQuery = s[i+1:]
		s = s[:i]
	}

	if i := strings.Index(s, "/"); i >= 0 {
		u.Opaque = s[i:]
		s = s[:i]
	} else {
		u.Opaque = "/"
	}

	u.Host = s

	if strings.Contains(u.Host, "@") {
		// Don't bother parsing user information because user information is
		// not allowed in websocket URIs.
		return nil, errMalformedURL
	}

	return &u, nil
}

func hostPortNoPort(u *url.URL) (hostPort, hostNoPort string) {
	hostPort = u.Host
	hostNoPort = u.Host
	if i := strings.LastIndex(u.Host, ":"); i > strings.LastIndex(u.Host, "]") {
		hostNoPort = hostNoPort[:i]
	} else {
		switch u.Scheme {
		case "wss":
			hostPort += ":443"
		case "https":
			hostPort += ":443"
		default:
			hostPort += ":80"
		}
	}
	return hostPort, hostNoPort
}

// DefaultDialer is a dialer with all fields set to the default zero values.
var DefaultDialer = &Dialer{
	Proxy: http.ProxyFromEnvironment,
}

// Dial creates a new client connection. Use requestHeader to specify the
// origin (Origin), subprotocols (Sec-WebSocket-Protocol) and cookies (Cookie).
// Use the response.Header to get the selected subprotocol
// (Sec-WebSocket-Protocol) and cookies (Set-Cookie).
//
// If the WebSocket handshake fails, ErrBadHandshake is returned along with a
// non-nil *http.Response so that callers can handle redirects, authentication,
// etcetera. The response body may not contain the entire response and does not
// need to be closed by the application.
func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Response, error) {

	if d == nil {
		d = &Dialer{
			Proxy: http.ProxyFromEnvironment,
		}
	}

	challengeKey, err := generateChallengeKey()
	if err != nil {
		return nil, nil, err
	}

	u, err := parseURL(urlStr)
	if err != nil {
		return nil, nil, err
	}

	switch u.Scheme {
	case "ws":
		u.Scheme = "http"
	case "wss":
		u.Scheme = "https"
	default:
		return nil, nil, errMalformedURL
	}

	if u.User != nil {
		// User name and password are not allowed in websocket URIs.
		return nil, nil, errMalformedURL
	}

	req := &http.Request{
		Method:     "GET",
		URL:        u,
		Proto:      "HTTP/1.1",
		ProtoMajor: 1,
		ProtoMinor: 1,
		Header:     make(http.Header),
		Host:       u.Host,
	}

	// Set the cookies present in the cookie jar of the dialer
	if d.Jar != nil {
		for _, cookie := range d.Jar.Cookies(u) {
			req.AddCookie(cookie)
		}
	}

	// Set the request headers using the capitalization for names and values in
	// RFC examples. Although the capitalization shouldn't matter, there are
	// servers that depend on it. The Header.Set method is not used because the
	// method canonicalizes the header names.
	req.Header["Upgrade"] = []string{"websocket"}
	req.Header["Connection"] = []string{"Upgrade"}
	req.Header["Sec-WebSocket-Key"] = []string{challengeKey}
	req.Header["Sec-WebSocket-Version"] = []string{"13"}
	if len(d.Subprotocols) > 0 {
		req.Header["Sec-WebSocket-Protocol"] = []string{strings.Join(d.Subprotocols, ", ")}
	}
	for k, vs := range requestHeader {
		switch {
		case k == "Host":
			if len(vs) > 0 {
				req.Host = vs[0]
			}
		case k == "Upgrade" ||
			k == "Connection" ||
			k == "Sec-Websocket-Key" ||
			k == "Sec-Websocket-Version" ||
			k == "Sec-Websocket-Extensions" ||
			(k == "Sec-Websocket-Protocol" && len(d.Subprotocols) > 0):
			return nil, nil, errors.New("websocket: duplicate header not allowed: " + k)
		default:
			req.Header[k] = vs
		}
	}

	if d.EnableCompression {
		req.Header.Set("Sec-Websocket-Extensions", "permessage-deflate; server_no_context_takeover; client_no_context_takeover")
	}

	hostPort, hostNoPort := hostPortNoPort(u)

	var proxyURL *url.URL
	// Check wether the proxy method has been configured
	if d.Proxy != nil {
		proxyURL, err = d.Proxy(req)
	}
	if err != nil {
		return nil, nil, err
	}

	var targetHostPort string
	if proxyURL != nil {
		targetHostPort, _ = hostPortNoPort(proxyURL)
	} else {
		targetHostPort = hostPort
	}

	var deadline time.Time
	if d.HandshakeTimeout != 0 {
		deadline = time.Now().Add(d.HandshakeTimeout)
	}

	netDial := d.NetDial
	if netDial == nil {
		netDialer := &net.Dialer{Deadline: deadline}
		netDial = netDialer.Dial
	}

	netConn, err := netDial("tcp", targetHostPort)
	if err != nil {
		return nil, nil, err
	}

	defer func() {
		if netConn != nil {
			netConn.Close()
		}
	}()

	if err := netConn.SetDeadline(deadline); err != nil {
		return nil, nil, err
	}

	if proxyURL != nil {
		connectHeader := make(http.Header)
		if user := proxyURL.User; user != nil {
			proxyUser := user.Username()
			if proxyPassword, passwordSet := user.Password(); passwordSet {
				credential := base64.StdEncoding.EncodeToString([]byte(proxyUser + ":" + proxyPassword))
				connectHeader.Set("Proxy-Authorization", "Basic "+credential)
			}
		}
		connectReq := &http.Request{
			Method: "CONNECT",
			URL:    &url.URL{Opaque: hostPort},
			Host:   hostPort,
			Header: connectHeader,
		}

		connectReq.Write(netConn)

		// Read response.
		// Okay to use and discard buffered reader here, because
		// TLS server will not speak until spoken to.
		br := bufio.NewReader(netConn)
		resp, err := http.ReadResponse(br, connectReq)
		if err != nil {
			return nil, nil, err
		}
		if resp.StatusCode != 200 {
			f := strings.SplitN(resp.Status, " ", 2)
			return nil, nil, errors.New(f[1])
		}
	}

	if u.Scheme == "https" {
		cfg := cloneTLSConfig(d.TLSClientConfig)
		if cfg.ServerName == "" {
			cfg.ServerName = hostNoPort
		}
		tlsConn := tls.Client(netConn, cfg)
		netConn = tlsConn
		if err := tlsConn.Handshake(); err != nil {
			return nil, nil, err
		}
		if !cfg.InsecureSkipVerify {
			if err := tlsConn.VerifyHostname(cfg.ServerName); err != nil {
				return nil, nil, err
			}
		}
	}

	conn := newConn(netConn, false, d.ReadBufferSize, d.WriteBufferSize)

	if err := req.Write(netConn); err != nil {
		return nil, nil, err
	}

	resp, err := http.ReadResponse(conn.br, req)
	if err != nil {
		return nil, nil, err
	}

	if d.Jar != nil {
		if rc := resp.Cookies(); len(rc) > 0 {
			d.Jar.SetCookies(u, rc)
		}
	}

	if resp.StatusCode != 101 ||
		!strings.EqualFold(resp.Header.Get("Upgrade"), "websocket") ||
		!strings.EqualFold(resp.Header.Get("Connection"), "upgrade") ||
		resp.Header.Get("Sec-Websocket-Accept") != computeAcceptKey(challengeKey) {
		// Before closing the network connection on return from this
		// function, slurp up some of the response to aid application
		// debugging.
		buf := make([]byte, 1024)
		n, _ := io.ReadFull(resp.Body, buf)
		resp.Body = ioutil.NopCloser(bytes.NewReader(buf[:n]))
		return nil, resp, ErrBadHandshake
	}

	for _, ext := range parseExtensions(resp.Header) {
		if ext[""] != "permessage-deflate" {
			continue
		}
		_, snct := ext["server_no_context_takeover"]
		_, cnct := ext["client_no_context_takeover"]
		if !snct || !cnct {
			return nil, resp, errInvalidCompression
		}
		conn.newCompressionWriter = compressNoContextTakeover
		conn.newDecompressionReader = decompressNoContextTakeover
		break
	}

	resp.Body = ioutil.NopCloser(bytes.NewReader([]byte{}))
	conn.subprotocol = resp.Header.Get("Sec-Websocket-Protocol")

	netConn.SetDeadline(time.Time{})
	netConn = nil // to avoid close in defer.
	return conn, resp, nil
}
<|endoftext|>


<commit_before>package tpt

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
	"sync"
	"time"

	"github.com/daemonl/tpt.go/tptobjects"
)

// BearerToken is given to an API client to authenticate the
// application
type BearerToken struct {
	Token  string    `json:"access_token"`
	Expiry time.Time `json:"expiry"`
}

// Config represents the url and authentication details for
// connecting to the TPT API
type Config struct {
	Endpoint     string `json:"endpoint"`
	ClientID     string `json:"clientId"`
	ClientSecret string `json:"clientSecret"`
}

// Client is the root of the connection to the TPT API
type Client struct {
	BaseURL *url.URL
	sync.RWMutex
	Config
	*BearerToken
}

// NewClient builds the default client
func NewClient(config Config) (*Client, error) {
	u, err := url.Parse(config.Endpoint)
	if err != nil {
		return nil, err
	}
	return &Client{
		Config:  config,
		BaseURL: u,
	}, nil
}

// New starts a new builder chain
func (c *Client) NewRequest(reqPath string) *Request {
	return NewRequest(*c.BaseURL, reqPath).
		AddHeader("Authorization", "Bearer "+c.BearerToken.Token)

}

// OAuth fetches a new Bearer token from the configured credentials, and sets
// up the client's TokenRequestBuilder
// TODO: This should be automatic, and handle expiry automatically.
func (c *Client) OAuth() error {
	c.Lock()
	defer c.Unlock()

	reqBodyBuf := &bytes.Buffer{}
	if err := json.NewEncoder(reqBodyBuf).Encode(&struct {
		ClientID     string `json:"client_id"`
		ClientSecret string `json:"client_secret"`
		GrantType    string `json:"grant_type"`
	}{
		ClientID:     c.ClientID,
		ClientSecret: c.ClientSecret,
		GrantType:    "client_credentials",
	}); err != nil {
		return err
	}

	resp, err := http.Post(
		c.Endpoint+"/v1/oauth/token",
		"application/json", reqBodyBuf)

	if err != nil {
		return err
	}

	defer resp.Body.Close()
	token := &BearerToken{}
	if err := json.NewDecoder(resp.Body).Decode(token); err != nil {
		return err
	}

	c.BearerToken = token

	return nil
}

// ExchangeUserCode returns gets a user_token in eschange for the code given at
// the end of the client oAuth2 flow. It returns a User object which can be
// used for user authenticated API calls
func (c *Client) ExchangeUserCode(code string) (*User, error) {
	respBody := &struct {
		Token string `json:"user_token"`
	}{}

	err := c.NewRequest("/v1/user/oauth/token").PostJSON(map[string]string{
		"code": code,
	}).DecodeInto(respBody)

	if err != nil {
		return nil, err
	}

	return c.User(respBody.Token), nil
}

// User returns a User object from an oAuth-ish token which can be used for
// user authenticated API calls
func (c *Client) User(token string) *User {
	return &User{
		Token:  token,
		Client: c,
	}
}

/////////////////////////
// Wrapped API Methods //
/////////////////////////

func (c *Client) GetNews(symbol string) (*tptobjects.NewsResponse, error) {
	resp := &tptobjects.NewsResponse{}
	err := c.NewRequest(fmt.Sprintf("/v1/market/symbols/%s/company/news", symbol)).
		DecodeInto(resp)
	return resp, err
}
<commit_msg>minor fixes<commit_after>package tpt

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"net/url"
	"sync"
	"time"

	"github.com/daemonl/tpt.go/tptobjects"
)

// BearerToken is given to an API client to authenticate the
// application
type BearerToken struct {
	Token  string    `json:"access_token"`
	Expiry time.Time `json:"expiry"`
}

// Config represents the url and authentication details for
// connecting to the TPT API
type Config struct {
	Endpoint     string `json:"endpoint"`
	ClientID     string `json:"clientId"`
	ClientSecret string `json:"clientSecret"`
}

// Client is the root of the connection to the TPT API
type Client struct {
	BaseURL *url.URL
	sync.RWMutex
	Config
	*BearerToken
}

// NewClient builds the default client
func NewClient(config Config) (*Client, error) {
	u, err := url.Parse(config.Endpoint)
	if err != nil {
		return nil, err
	}
	return &Client{
		Config:  config,
		BaseURL: u,
	}, nil
}

// NewRequest starts a new builder chain
func (c *Client) NewRequest(reqPath string) *Request {
	return NewRequest(*c.BaseURL, reqPath).
		AddHeader("Authorization", "Bearer "+c.BearerToken.Token)
}

// OAuth fetches a new Bearer token from the configured credentials, and sets
// up the client's TokenRequestBuilder
// TODO: This should be automatic, and handle expiry automatically.
func (c *Client) OAuth() error {
	c.Lock()
	defer c.Unlock()

	reqBodyBuf := &bytes.Buffer{}
	if err := json.NewEncoder(reqBodyBuf).Encode(&struct {
		ClientID     string `json:"client_id"`
		ClientSecret string `json:"client_secret"`
		GrantType    string `json:"grant_type"`
	}{
		ClientID:     c.ClientID,
		ClientSecret: c.ClientSecret,
		GrantType:    "client_credentials",
	}); err != nil {
		return err
	}

	resp, err := http.Post(
		c.Endpoint+"/oauth/token",
		"application/json", reqBodyBuf)

	if err != nil {
		return err
	}
	if resp.StatusCode != 200 {
		return errors.New("Authentication error: " + resp.Status)
	}

	defer resp.Body.Close()
	token := &BearerToken{}
	if err := json.NewDecoder(resp.Body).Decode(token); err != nil {
		return err
	}

	c.BearerToken = token

	return nil
}

// ExchangeUserCode returns gets a user_token in eschange for the code given at
// the end of the client oAuth2 flow. It returns a User object which can be
// used for user authenticated API calls
func (c *Client) ExchangeUserCode(code string) (*User, error) {
	respBody := &struct {
		Token string `json:"user_token"`
	}{}

	err := c.NewRequest("/v1/user/oauth/token").PostJSON(map[string]string{
		"code": code,
	}).DecodeInto(respBody)

	if err != nil {
		return nil, err
	}

	return c.User(respBody.Token), nil
}

// User returns a User object from an oAuth-ish token which can be used for
// user authenticated API calls
func (c *Client) User(token string) *User {
	return &User{
		Token:  token,
		Client: c,
	}
}

/////////////////////////
// Wrapped API Methods //
/////////////////////////

func (c *Client) GetNews(symbol string) (*tptobjects.NewsResponse, error) {
	resp := &tptobjects.NewsResponse{}
	err := c.NewRequest(fmt.Sprintf("/v1/market/symbols/%s/company/news", symbol)).
		DecodeInto(resp)
	return resp, err
}
<|endoftext|>