<commit_before>// Copyright 2014 Krishna Raman
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package firmata

import (
  "code.google.com/p/log4go"
  "github.com/tarm/goserial"

  "fmt"
  "io"
  "time"
)

// Arduino Firmata client for golang
type FirmataClient struct {
  serialDev string
  baud      int
  conn      *io.ReadWriteCloser
  Log       *log4go.Logger

  protocolVersion []byte
  firmwareVersion []int
  firmwareName    string

  ready             bool
  analogMappingDone bool
  capabilityDone    bool

  digitalPinState [8]byte

  analogPinsChannelMap map[int]byte
  analogChannelPinsMap map[byte]int
  pinModes             []map[PinMode]interface{}

  valueChan  chan FirmataValue
  serialChan chan string
  spiChan    chan []byte
}

// Creates a new FirmataClient object and connects to the Arduino board
// over specified serial port. This function blocks till a connection is
// succesfullt established and pin mappings are retrieved.
func NewClient(dev string, baud int) (client *FirmataClient, err error) {
  var conn io.ReadWriteCloser

  c := &serial.Config{Name: dev, Baud: baud}
  conn, err = serial.OpenPort(c)
  if err != nil {
    client.Log.Critical(err)
    return
  }

  logger := make(log4go.Logger)
  logger.AddFilter("stdout", log4go.INFO, log4go.NewConsoleLogWriter())
  client = &FirmataClient{
    serialDev: dev,
    baud:      baud,
    conn:      &conn,
    Log:       &logger,
  }
  go client.replyReader()

  conn.Write([]byte{byte(SystemReset)})
  t := time.NewTicker(time.Second)

  for !(client.ready && client.analogMappingDone && client.capabilityDone) {
    select {
    case <-t.C:
      //no-op
    case <-time.After(time.Second * 15):
      client.Log.Critical("No response in 30 seconds. Resetting arduino")
      conn.Write([]byte{byte(SystemReset)})
    case <-time.After(time.Second * 30):
      client.Log.Critical("Unable to initialize connection")
      conn.Close()
      client = nil
    }
  }

  client.Log.Info("Client ready to use")

  return
}

// Close the serial connection to properly clean up after ourselves
// Usage: defer client.Close()
func (c *FirmataClient) Close() {
  (*c.conn).Close()
}

// Sets the Pin mode (input, output, etc.) for the Arduino pin
func (c *FirmataClient) SetPinMode(pin byte, mode PinMode) (err error) {
  if c.pinModes[pin][mode] == nil {
    err = fmt.Errorf("Pin mode %v not supported by pin %v", mode, pin)
    return
  }
  cmd := []byte{byte(SetPinMode), (pin & 0x7F), byte(mode)}
  err = c.sendCommand(cmd)
  return
}

// Specified if a digital Pin should be watched for input.
// Values will be streamed back over a channel which can be retrieved by the GetValues() call
func (c *FirmataClient) EnableDigitalInput(pin uint, val bool) (err error) {
  if pin < 0 || pin > uint(len(c.pinModes)) {
    err = fmt.Errorf("Invalid pin number %v\n", pin)
    return
  }
  port := (pin / 8) & 0x7F
  pin = pin % 8

  if val {
    cmd := []byte{byte(EnableDigitalInput) | byte(port), 0x01}
    err = c.sendCommand(cmd)
  } else {
    cmd := []byte{byte(EnableDigitalInput) | byte(port), 0x00}
    err = c.sendCommand(cmd)
  }

  return
}

// Set the value of a digital pin
func (c *FirmataClient) DigitalWrite(pin uint, val bool) (err error) {
  if pin < 0 || pin > uint(len(c.pinModes)) && c.pinModes[pin][Output] != nil {
    err = fmt.Errorf("Invalid pin number %v\n", pin)
    return
  }
  port := (pin / 8) & 0x7F
  portData := &c.digitalPinState[port]
  pin = pin % 8

  if val {
    (*portData) = (*portData) | (1 << pin)
  } else {
    (*portData) = (*portData) & ^(1 << pin)
  }
  data := to7Bit(*(portData))
  cmd := []byte{byte(DigitalMessage) | byte(port), data[0], data[1]}
  err = c.sendCommand(cmd)
  return
}

// Specified if a analog Pin should be watched for input.
// Values will be streamed back over a channel which can be retrieved by the GetValues() call
func (c *FirmataClient) EnableAnalogInput(pin uint, val bool) (err error) {
  if pin < 0 || pin > uint(len(c.pinModes)) && c.pinModes[pin][Analog] != nil {
    err = fmt.Errorf("Invalid pin number %v\n", pin)
    return
  }

  ch := byte(c.analogPinsChannelMap[int(pin)])
  c.Log.Debug("Enable analog inout on pin %v channel %v", pin, ch)
  if val {
    cmd := []byte{byte(EnableAnalogInput) | ch, 0x01}
    err = c.sendCommand(cmd)
  } else {
    cmd := []byte{byte(EnableAnalogInput) | ch, 0x00}
    err = c.sendCommand(cmd)
  }

  return
}

// Set the value of a analog pin
func (c *FirmataClient) AnalogWrite(pin uint, pinData byte) (err error) {
  if pin < 0 || pin > uint(len(c.pinModes)) && c.pinModes[pin][Analog] != nil {
    err = fmt.Errorf("Invalid pin number %v\n", pin)
    return
  }

  data := to7Bit(pinData)
  cmd := []byte{byte(AnalogMessage) | byte(pin), data[0], data[1]}
  err = c.sendCommand(cmd)
  return
}

func (c *FirmataClient) sendCommand(cmd []byte) (err error) {
  bStr := ""
  for _, b := range cmd {
    bStr = bStr + fmt.Sprintf(" %#2x", b)
  }
  c.Log.Trace("Command send%v\n", bStr)

  _, err = (*c.conn).Write(cmd)
  return
}

// Sets the polling interval in milliseconds for analog pin samples
func (c *FirmataClient) SetAnalogSamplingInterval(ms byte) (err error) {
  data := to7Bit(ms)
  err = c.sendSysEx(SamplingInterval, data[0], data[1])
  return
}

// Get the channel to retrieve analog and digital pin values
func (c *FirmataClient) GetValues() <-chan FirmataValue {
  return c.valueChan
}
<commit_msg>Client update proposal<commit_after>// Copyright 2014 Krishna Raman
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package firmata

import (
	"fmt"
	"github.com/tarm/serial"
	"io"
	"log"
	"os"
	"time"
)

// Arduino Firmata client for golang
type FirmataClient struct {
	serialDev string
	baud      int
	conn      *io.ReadWriteCloser
	Log       *log.Logger

	protocolVersion []byte
	firmwareVersion []int
	firmwareName    string

	ready             bool
	analogMappingDone bool
	capabilityDone    bool

	digitalPinState [8]byte

	analogPinsChannelMap map[int]byte
	analogChannelPinsMap map[byte]int
	pinModes             []map[PinMode]interface{}

	valueChan  chan FirmataValue
	serialChan chan string
	spiChan    chan []byte

	Verbose bool
}

// Creates a new FirmataClient object and connects to the Arduino board
// over specified serial port. This function blocks till a connection is
// succesfullt established and pin mappings are retrieved.
func NewClient(dev string, baud int) (client *FirmataClient, err error) {
	var conn io.ReadWriteCloser

	c := &serial.Config{Name: dev, Baud: baud}
	conn, err = serial.OpenPort(c)
	if err != nil {
		return nil, err
	}

	client = &FirmataClient{
		serialDev: dev,
		baud:      baud,
		conn:      &conn,
		Log:       log.New(os.Stdout, "[go-firmata] ", log.Ltime),
	}
	go client.replyReader()

	conn.Write([]byte{byte(SystemReset)})
	t := time.NewTicker(time.Second)

	for !(client.ready && client.analogMappingDone && client.capabilityDone) {
		select {
		case <-t.C:
			//no-op
		case <-time.After(time.Second * 15):
			client.Log.Print("No response in 30 seconds. Resetting arduino")
			conn.Write([]byte{byte(SystemReset)})
		case <-time.After(time.Second * 30):
			client.Log.Print("Unable to initialize connection")
			conn.Close()
			client = nil
		}
	}

	client.Log.Print("Client ready to use")

	return
}

// Close the serial connection to properly clean up after ourselves
// Usage: defer client.Close()
func (c *FirmataClient) Delay(duration time.Duration) {
	time.Sleep(duration)
}

// Close the serial connection to properly clean up after ourselves
// Usage: defer client.Close()
func (c *FirmataClient) Close() {
	(*c.conn).Close()
}

// Sets the Pin mode (input, output, etc.) for the Arduino pin
func (c *FirmataClient) SetPinMode(pin uint8, mode PinMode) error {
	if c.pinModes[pin][mode] == nil {
		return fmt.Errorf("Pin mode %v not supported by pin %v", mode, pin)
	}
	cmd := []byte{byte(SetPinMode), (pin & 0x7F), byte(mode)}
	if err := c.sendCommand(cmd); err != nil {
		return err
	}
	c.Log.Printf("SetPinMode: pin %d -> %s\r\n", pin, mode)
	return nil
}

// Specified if a digital Pin should be watched for input.
// Values will be streamed back over a channel which can be retrieved by the GetValues() call
func (c *FirmataClient) EnableDigitalInput(pin uint, val bool) (err error) {
	if pin < 0 || pin > uint(len(c.pinModes)) {
		err = fmt.Errorf("Invalid pin number %v\n", pin)
		return
	}
	port := (pin / 8) & 0x7F
	pin = pin % 8

	if val {
		cmd := []byte{byte(EnableDigitalInput) | byte(port), 0x01}
		err = c.sendCommand(cmd)
	} else {
		cmd := []byte{byte(EnableDigitalInput) | byte(port), 0x00}
		err = c.sendCommand(cmd)
	}

	return
}

// Set the value of a digital pin
func (c *FirmataClient) DigitalWrite(pin uint8, val bool) error {
	if pin < 0 || pin > uint8(len(c.pinModes)) && c.pinModes[pin][Output] != nil {
		return fmt.Errorf("Invalid pin number %v\n", pin)
	}
	port := (pin / 8) & 0x7F
	portData := &c.digitalPinState[port]
	pin = pin % 8
	if val {
		(*portData) = (*portData) | (1 << pin)
	} else {
		(*portData) = (*portData) & ^(1 << pin)
	}
	data := to7Bit(*(portData))
	cmd := []byte{byte(DigitalMessage) | byte(port), data[0], data[1]}
	if err := c.sendCommand(cmd); err != nil {
		return err
	}
	c.Log.Printf("DigitalWrite: pin %d -> %t\r\n", pin, val)
	return nil
}

// Specified if a analog Pin should be watched for input.
// Values will be streamed back over a channel which can be retrieved by the GetValues() call
func (c *FirmataClient) EnableAnalogInput(pin uint, val bool) (err error) {
	if pin < 0 || pin > uint(len(c.pinModes)) && c.pinModes[pin][Analog] != nil {
		err = fmt.Errorf("Invalid pin number %v\n", pin)
		return
	}

	ch := byte(c.analogPinsChannelMap[int(pin)])
	c.Log.Printf("Enable analog inout on pin %v channel %v", pin, ch)
	if val {
		cmd := []byte{byte(EnableAnalogInput) | ch, 0x01}
		err = c.sendCommand(cmd)
	} else {
		cmd := []byte{byte(EnableAnalogInput) | ch, 0x00}
		err = c.sendCommand(cmd)
	}

	return
}

// Set the value of a analog pin
func (c *FirmataClient) AnalogWrite(pin uint, pinData byte) (err error) {
	if pin < 0 || pin > uint(len(c.pinModes)) && c.pinModes[pin][Analog] != nil {
		err = fmt.Errorf("Invalid pin number %v\n", pin)
		return
	}

	data := to7Bit(pinData)
	cmd := []byte{byte(AnalogMessage) | byte(pin), data[0], data[1]}
	err = c.sendCommand(cmd)
	return
}

func (c *FirmataClient) sendCommand(cmd []byte) (err error) {
	bStr := ""
	for _, b := range cmd {
		bStr = bStr + fmt.Sprintf(" %#2x", b)
	}

	if c.Verbose {
		c.Log.Printf("Command send%v\n", bStr)
	}

	_, err = (*c.conn).Write(cmd)
	return
}

// Sets the polling interval in milliseconds for analog pin samples
func (c *FirmataClient) SetAnalogSamplingInterval(ms byte) (err error) {
	data := to7Bit(ms)
	err = c.sendSysEx(SamplingInterval, data[0], data[1])
	return
}

// Get the channel to retrieve analog and digital pin values
func (c *FirmataClient) GetValues() <-chan FirmataValue {
	return c.valueChan
}
<|endoftext|>


<commit_before>package atlas

import (
	"crypto/tls"
	"log"
	"net/http"
	"time"
)

// NewClient is the first function to call.
// Yes, it does take multiple config
// and the last one wins.
func NewClient(cfgs ...Config) (*Client, error) {
	client := &Client{}
	for _, cfg := range cfgs {
		client.config = cfg
	}

	// This holds the global options
	client.opts = make(map[string]string)

	// Create and save the http.Client
	return client.addHTTPClient()
}

// HasAPIKey returns whether an API key is stored
func (client *Client) HasAPIKey() (string, bool) {
	if client.config.APIKey == "" {
		return "", false
	}
	return client.config.APIKey, true
}

// call is s shortcut
func (client *Client) call(req *http.Request) (*http.Response, error) {
	return client.client.Do(req)
}

func (client *Client) setupTransport() (*http.Transport, error) {
	/*
	   Proxy code taken from https://github.com/LeoCBS/poc-proxy-https/blob/master/main.go
	   Analyse endPoint to check proxy stuff
	*/
	req, err := http.NewRequest("HEAD", apiEndpoint, nil)
	if err != nil {
		log.Printf("error: transport: %v", err)
		return nil, err
	}

	// Get proxy URL
	proxyURL, err := http.ProxyFromEnvironment(req)
	if err != nil {
		if client.config.Verbose {
			log.Println("no proxy defined")
		}
	}

	if client.config.ProxyAuth != "" {
		req.Header.Set("Proxy-Authorization", client.config.ProxyAuth)
	}

	transport := &http.Transport{
		Proxy:              http.ProxyURL(proxyURL),
		TLSClientConfig:    &tls.Config{InsecureSkipVerify: true},
		ProxyConnectHeader: req.Header,
	}

	return transport, nil
}

func (client *Client) addHTTPClient() (*Client, error) {
	transport, err := client.setupTransport()
	if err != nil {
		log.Fatalf("unable to create httpclient: %v", err)
	}
	client.client = &http.Client{Transport: transport, Timeout: 20 * time.Second}
	return client, err
}

func (client *Client) SetAF(family string) (*Client) {
	return client.SetOption("wantAF", family)
}

func (client *Client) SetFormat(format string) (*Client) {
	return client.SetOption("format", format)
}

func (client *Client) SetInclude(include string) (*Client) {
	return client.SetOption("include", include)
}

func (client *Client) SetOption(name, value string) (*Client) {
	client.opts[name] = value
	return client
}

func (client *Client) mergeGlobalOptions(opts map[string]string) {
	for k, v := range client.opts {
		opts[k] = v
	}
}
<commit_msg> not set empty options.<commit_after>package atlas

import (
	"crypto/tls"
	"log"
	"net/http"
	"time"
)

// NewClient is the first function to call.
// Yes, it does take multiple config
// and the last one wins.
func NewClient(cfgs ...Config) (*Client, error) {
	client := &Client{}
	for _, cfg := range cfgs {
		client.config = cfg
	}

	// This holds the global options
	client.opts = make(map[string]string)

	// Create and save the http.Client
	return client.addHTTPClient()
}

// HasAPIKey returns whether an API key is stored
func (client *Client) HasAPIKey() (string, bool) {
	if client.config.APIKey == "" {
		return "", false
	}
	return client.config.APIKey, true
}

// call is s shortcut
func (client *Client) call(req *http.Request) (*http.Response, error) {
	return client.client.Do(req)
}

func (client *Client) setupTransport() (*http.Transport, error) {
	/*
	   Proxy code taken from https://github.com/LeoCBS/poc-proxy-https/blob/master/main.go
	   Analyse endPoint to check proxy stuff
	*/
	req, err := http.NewRequest("HEAD", apiEndpoint, nil)
	if err != nil {
		log.Printf("error: transport: %v", err)
		return nil, err
	}

	// Get proxy URL
	proxyURL, err := http.ProxyFromEnvironment(req)
	if err != nil {
		if client.config.Verbose {
			log.Println("no proxy defined")
		}
	}

	if client.config.ProxyAuth != "" {
		req.Header.Set("Proxy-Authorization", client.config.ProxyAuth)
	}

	transport := &http.Transport{
		Proxy:              http.ProxyURL(proxyURL),
		TLSClientConfig:    &tls.Config{InsecureSkipVerify: true},
		ProxyConnectHeader: req.Header,
	}

	return transport, nil
}

func (client *Client) addHTTPClient() (*Client, error) {
	transport, err := client.setupTransport()
	if err != nil {
		log.Fatalf("unable to create httpclient: %v", err)
	}
	client.client = &http.Client{Transport: transport, Timeout: 20 * time.Second}
	return client, err
}

func (client *Client) SetAF(family string) (*Client) {
	return client.SetOption("wantAF", family)
}

func (client *Client) SetFormat(format string) (*Client) {
	return client.SetOption("format", format)
}

func (client *Client) SetInclude(include string) (*Client) {
	return client.SetOption("include", include)
}

func (client *Client) SetOption(name, value string) *Client {
	if value != "" {
		client.opts[name] = value
	}
	return client
}

func (client *Client) mergeGlobalOptions(opts map[string]string) {
	for k, v := range client.opts {
		opts[k] = v
	}
}
<|endoftext|>


<commit_before>// Copyright 2014 Tjerk Santegoeds
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package oanda

import (
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net"
	"net/http"
	"net/url"
	"os"
	"strings"
	"time"
)

var debug = "" //"trace"

const (
	defaultDateFormat  = DateFormat("RFC3339")
	defaultContentType = ContentType("application/x-www-form-urlencoded")
)

var (
	DefaultHttpClient = &http.Client{
		Transport: &http.Transport{
			Proxy: http.ProxyFromEnvironment,
			Dial: (&net.Dialer{
				Timeout:   30 * time.Second,
				KeepAlive: 30 * time.Second,
			}).Dial,
			TLSHandshakeTimeout: 10 * time.Second,

			// The number of open connections to the stream server are restricted. Disable support for
			// idle connections.
			MaxIdleConnsPerHost: -1,
		},
	}
)

///////////////////////////////////////////////////////////////////////////////////////////////////
// RequestModifiers

// A requestModifier updates an http.Request before it is passed onto an http.Client for execution.
type requestModifier interface {
	modify(*http.Request)
}

// A TokenAuthenticator adds a Bearer authentication to a request header.
type TokenAuthenticator string

func (a TokenAuthenticator) modify(req *http.Request) {
	req.Header.Set("Authorization", "Bearer "+string(a))
}

// A UsernameAuthenticator adds a username to a request header.
type UsernameAuthenticator string

func (a UsernameAuthenticator) modify(req *http.Request) {
	u := req.URL
	q := u.Query()
	q.Set("username", string(a))
	u.RawQuery = q.Encode()
}

type Environment string

func (e Environment) modify(req *http.Request) {
	u := req.URL
	envStr := string(e)
	if envStr == "sandbox" {
		u.Scheme = "http"
	} else {
		u.Scheme = "https"
	}
	if u.Host == "" {
		u.Host = "api-" + string(e) + ".oanda.com"
	}
}

// A DateFormat add the desired DateTime format to a request header.
type DateFormat string

func (d DateFormat) modify(req *http.Request) {
	req.Header.Set("X-Accept-Datetime-Format", string(d))
}

// A ContentType adds a Content-Type entry to the request header.
type ContentType string

func (c ContentType) modify(req *http.Request) {
	if req.Body != nil {
		req.Header.Set("Content-Type", string(c))
	}
}

///////////////////////////////////////////////////////////////////////////////////////////////////
// Client

type Client struct {
	reqMods   []requestModifier
	accountId int
	*http.Client
}

func (c *Client) AccountId() int { return c.accountId }

// NewFxPracticeClient returns a client instance that connects to Oanda's fxpractice environment. String
// token should be set to the generated personal access token.
//
// See http://developer.oanda.com/docs/v1/auth/ for further information.
func NewFxPracticeClient(token string) (*Client, error) {
	if token == "" {
		return nil, errors.New("No FxPractice access token")
	}
	return NewClient("fxpractice", token, nil)
}

// NewFxTradeClient returns a client instance that connects to Oanda's fxtrade environment. String token
// should be set to the generated personal access token.
//
// See http://developer.oanda.com/docs/v1/auth/ for further information.
func NewFxTradeClient(token string) (*Client, error) {
	if token == "" {
		return nil, errors.New("No FxTrade access token")
	}
	return NewClient("fxtrade", token, nil)
}

// NewSandboxClient returns a client instance that connects to Oanda's fxsandbox environment. Creating a
// client will create a user in the sandbox environment with wich all further calls with be authenticated.
//
// See http://developer.oanda.com/docs/v1/auth/ for further information.
func NewSandboxClient() (*Client, error) {
	return NewClient("sandbox", "", nil)
}

func NewClient(environment string, token string, httpClient *http.Client) (*Client, error) {
	if httpClient == nil {
		httpClient = DefaultHttpClient
	}

	switch environment {
	case "fxpractice":
		return newClient(httpClient, Environment("fxpractice"), TokenAuthenticator(token)), nil
	case "fxtrade":
		return newClient(httpClient, Environment("fxtrade"), TokenAuthenticator(token)), nil
	case "sandbox":
		c := newClient(httpClient, Environment("sandbox"))
		if userName, err := initSandboxAccount(c); err != nil {
			return nil, err
		} else {
			c.reqMods = append(c.reqMods, UsernameAuthenticator(userName))
		}
		return c, nil
	}

	return nil, fmt.Errorf("Invalid Oanda environment %v", environment)
}

// SelectAccount configures an Oanda account.  All trades and orders will be booked under the
// selected account.   Use AccountId 0 to disable account selection.
func (c *Client) SelectAccount(accountId int) {
	c.accountId = accountId
}

// NewRequest creates a new http request.
func (c *Client) NewRequest(method, urlStr string, body io.Reader) (*http.Request, error) {
	req, err := http.NewRequest(method, urlStr, body)
	if err != nil {
		return nil, err
	}
	for _, reqMod := range c.reqMods {
		reqMod.modify(req)
	}
	return req, nil
}

// CancelRequest aborts an in-progress HTTP request.
func (c *Client) CancelRequest(req *http.Request) {
	type canceler interface {
		CancelRequest(*http.Request)
	}
	tr, ok := c.Transport.(canceler)
	if ok {
		tr.CancelRequest(req)
	}
}

///////////////////////////////////////////////////////////////////////////////////////////////////
// PollRequest

// PollRequest represents an http request that can be executed repeatedly.
type PollRequest struct {
	c   *Client
	req *http.Request
}

// Poll repeats the http request with which PollRequest was created.
func (pr *PollRequest) Poll() (*http.Response, error) {
	rsp, err := pr.c.Do(pr.req)
	if err != nil {
		return nil, err
	}
	etag := rsp.Header.Get("ETag")
	if etag != "" {
		pr.req.Header.Set("If-None-Match", etag)
	}
	return rsp, nil
}

func newClient(httpClient *http.Client, reqMod ...requestModifier) *Client {
	c := Client{
		reqMods: []requestModifier{
			defaultDateFormat,
			defaultContentType,
		},
		Client: httpClient,
	}
	c.reqMods = append(c.reqMods, reqMod...)
	return &c
}

// initSandboxAccount creates a new test account in the sandbox environment.
func initSandboxAccount(c *Client) (string, error) {
	v := struct {
		Username  string `json:"username"`
		Password  string `json:"password"`
		AccountId int    `json:"accountId"`
	}{}
	if err := requestAndDecode(c, "POST", "/v1/accounts", nil, &v); err != nil {
		return "", err
	}
	return v.Username, nil
}

type returnCodeChecker interface {
	checkReturnCode() error
}

// ApiError holds error details as returned by the Oanda servers.
type ApiError struct {
	Code     int    `json:"code"`
	Message  string `json:"message"`
	MoreInfo string `json:"moreInfo"`
}

func (ae *ApiError) Error() string {
	return fmt.Sprintf("ApiError{Code: %d, Message: %s, Moreinfo: %s}",
		ae.Code, ae.Message, ae.MoreInfo)
}

func getAndDecode(c *Client, urlStr string, v interface{}) error {
	return requestAndDecode(c, "GET", urlStr, nil, v)
}

func requestAndDecode(c *Client, method, urlStr string, data url.Values, v interface{}) error {
	var rdr io.Reader
	if len(data) > 0 {
		rdr = strings.NewReader(data.Encode())
	}
	req, err := c.NewRequest(method, urlStr, rdr)
	if err != nil {
		return err
	}

	if debug == "trace" {
		fmt.Fprintln(os.Stderr, req)
		fmt.Fprintln(os.Stderr, data)
	}

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

	var body io.Reader = rsp.Body
	if debug == "trace" {
		fmt.Println(os.Stderr, rsp)
		body = io.TeeReader(body, os.Stderr)
	}

	dec := json.NewDecoder(body)
	if rsp.StatusCode < 400 {
		return dec.Decode(v)
	}

	apiErr := ApiError{}
	if err = dec.Decode(&apiErr); err != nil {
		return err
	}
	return &apiErr
}
<commit_msg>Use 2 idle connections as default and provide method CloseIdleConnections.<commit_after>// Copyright 2014 Tjerk Santegoeds
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package oanda

import (
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net"
	"net/http"
	"net/url"
	"os"
	"strings"
	"time"
)

var debug = "" //"trace"

const (
	defaultDateFormat  = DateFormat("RFC3339")
	defaultContentType = ContentType("application/x-www-form-urlencoded")
)

var (
	DefaultHttpClient = &http.Client{
		Transport: &http.Transport{
			Proxy: http.ProxyFromEnvironment,
			Dial: (&net.Dialer{
				Timeout:   30 * time.Second,
				KeepAlive: 30 * time.Second,
			}).Dial,
			TLSHandshakeTimeout: 10 * time.Second,

			// The number of open connections to the stream server are restricted. Disable support for
			// idle connections.
			MaxIdleConnsPerHost: 2,
		},
	}
)

///////////////////////////////////////////////////////////////////////////////////////////////////
// RequestModifiers

// A requestModifier updates an http.Request before it is passed onto an http.Client for execution.
type requestModifier interface {
	modify(*http.Request)
}

// A TokenAuthenticator adds a Bearer authentication to a request header.
type TokenAuthenticator string

func (a TokenAuthenticator) modify(req *http.Request) {
	req.Header.Set("Authorization", "Bearer "+string(a))
}

// A UsernameAuthenticator adds a username to a request header.
type UsernameAuthenticator string

func (a UsernameAuthenticator) modify(req *http.Request) {
	u := req.URL
	q := u.Query()
	q.Set("username", string(a))
	u.RawQuery = q.Encode()
}

type Environment string

func (e Environment) modify(req *http.Request) {
	u := req.URL
	envStr := string(e)
	if envStr == "sandbox" {
		u.Scheme = "http"
	} else {
		u.Scheme = "https"
	}
	if u.Host == "" {
		u.Host = "api-" + string(e) + ".oanda.com"
	}
}

// A DateFormat add the desired DateTime format to a request header.
type DateFormat string

func (d DateFormat) modify(req *http.Request) {
	req.Header.Set("X-Accept-Datetime-Format", string(d))
}

// A ContentType adds a Content-Type entry to the request header.
type ContentType string

func (c ContentType) modify(req *http.Request) {
	if req.Body != nil {
		req.Header.Set("Content-Type", string(c))
	}
}

///////////////////////////////////////////////////////////////////////////////////////////////////
// Client

type Client struct {
	reqMods   []requestModifier
	accountId int
	*http.Client
}

func (c *Client) AccountId() int { return c.accountId }

// NewFxPracticeClient returns a client instance that connects to Oanda's fxpractice environment. String
// token should be set to the generated personal access token.
//
// See http://developer.oanda.com/docs/v1/auth/ for further information.
func NewFxPracticeClient(token string) (*Client, error) {
	if token == "" {
		return nil, errors.New("No FxPractice access token")
	}
	return NewClient("fxpractice", token, nil)
}

// NewFxTradeClient returns a client instance that connects to Oanda's fxtrade environment. String token
// should be set to the generated personal access token.
//
// See http://developer.oanda.com/docs/v1/auth/ for further information.
func NewFxTradeClient(token string) (*Client, error) {
	if token == "" {
		return nil, errors.New("No FxTrade access token")
	}
	return NewClient("fxtrade", token, nil)
}

// NewSandboxClient returns a client instance that connects to Oanda's fxsandbox environment. Creating a
// client will create a user in the sandbox environment with wich all further calls with be authenticated.
//
// See http://developer.oanda.com/docs/v1/auth/ for further information.
func NewSandboxClient() (*Client, error) {
	return NewClient("sandbox", "", nil)
}

func NewClient(environment string, token string, httpClient *http.Client) (*Client, error) {
	if httpClient == nil {
		httpClient = DefaultHttpClient
	}

	switch environment {
	case "fxpractice":
		return newClient(httpClient, Environment("fxpractice"), TokenAuthenticator(token)), nil
	case "fxtrade":
		return newClient(httpClient, Environment("fxtrade"), TokenAuthenticator(token)), nil
	case "sandbox":
		c := newClient(httpClient, Environment("sandbox"))
		if userName, err := initSandboxAccount(c); err != nil {
			return nil, err
		} else {
			c.reqMods = append(c.reqMods, UsernameAuthenticator(userName))
		}
		return c, nil
	}

	return nil, fmt.Errorf("Invalid Oanda environment %v", environment)
}

// SelectAccount configures an Oanda account.  All trades and orders will be booked under the
// selected account.   Use AccountId 0 to disable account selection.
func (c *Client) SelectAccount(accountId int) {
	c.accountId = accountId
}

// NewRequest creates a new http request.
func (c *Client) NewRequest(method, urlStr string, body io.Reader) (*http.Request, error) {
	req, err := http.NewRequest(method, urlStr, body)
	if err != nil {
		return nil, err
	}
	for _, reqMod := range c.reqMods {
		reqMod.modify(req)
	}
	return req, nil
}

// CancelRequest aborts an in-progress HTTP request.
func (c *Client) CancelRequest(req *http.Request) {
	type canceler interface {
		CancelRequest(*http.Request)
	}
	tr, ok := c.Transport.(canceler)
	if ok {
		tr.CancelRequest(req)
	}
}

// Closes all idle connections to the Oanda server.
func (c *Client) CloseIdleConnections() {
	type idleconnectionscloser interface {
		CloseIdleConnections()
	}
	tr, ok := c.Transport.(idleconnectionscloser)
	if ok {
		tr.CloseIdleConnections()
	}
}

///////////////////////////////////////////////////////////////////////////////////////////////////
// PollRequest

// PollRequest represents an http request that can be executed repeatedly.
type PollRequest struct {
	c   *Client
	req *http.Request
}

// Poll repeats the http request with which PollRequest was created.
func (pr *PollRequest) Poll() (*http.Response, error) {
	rsp, err := pr.c.Do(pr.req)
	if err != nil {
		return nil, err
	}
	etag := rsp.Header.Get("ETag")
	if etag != "" {
		pr.req.Header.Set("If-None-Match", etag)
	}
	return rsp, nil
}

func newClient(httpClient *http.Client, reqMod ...requestModifier) *Client {
	c := Client{
		reqMods: []requestModifier{
			defaultDateFormat,
			defaultContentType,
		},
		Client: httpClient,
	}
	c.reqMods = append(c.reqMods, reqMod...)
	return &c
}

// initSandboxAccount creates a new test account in the sandbox environment.
func initSandboxAccount(c *Client) (string, error) {
	v := struct {
		Username  string `json:"username"`
		Password  string `json:"password"`
		AccountId int    `json:"accountId"`
	}{}
	if err := requestAndDecode(c, "POST", "/v1/accounts", nil, &v); err != nil {
		return "", err
	}
	return v.Username, nil
}

type returnCodeChecker interface {
	checkReturnCode() error
}

// ApiError holds error details as returned by the Oanda servers.
type ApiError struct {
	Code     int    `json:"code"`
	Message  string `json:"message"`
	MoreInfo string `json:"moreInfo"`
}

func (ae *ApiError) Error() string {
	return fmt.Sprintf("ApiError{Code: %d, Message: %s, Moreinfo: %s}",
		ae.Code, ae.Message, ae.MoreInfo)
}

func getAndDecode(c *Client, urlStr string, v interface{}) error {
	return requestAndDecode(c, "GET", urlStr, nil, v)
}

func requestAndDecode(c *Client, method, urlStr string, data url.Values, v interface{}) error {
	var rdr io.Reader
	if len(data) > 0 {
		rdr = strings.NewReader(data.Encode())
	}
	req, err := c.NewRequest(method, urlStr, rdr)
	if err != nil {
		return err
	}

	if debug == "trace" {
		fmt.Fprintln(os.Stderr, req)
		fmt.Fprintln(os.Stderr, data)
	}

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

	var body io.Reader = rsp.Body
	if debug == "trace" {
		fmt.Println(os.Stderr, rsp)
		body = io.TeeReader(body, os.Stderr)
	}

	dec := json.NewDecoder(body)
	if rsp.StatusCode < 400 {
		return dec.Decode(v)
	}

	apiErr := ApiError{}
	if err = dec.Decode(&apiErr); err != nil {
		return err
	}
	return &apiErr
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"strings"
	"time"

	"golang.org/x/crypto/ssh"
	"golang.org/x/crypto/ssh/terminal"
)

const MSG_BUFFER int = 50
const MAX_MSG_LENGTH int = 512

const HELP_TEXT string = SYSTEM_MESSAGE_FORMAT + `-> Available commands:
   /about               - About this chat.
   /exit                - Exit the chat.
   /help                - Show this help text.
   /list                - List the users that are currently connected.
   /beep                - Enable BEL notifications on mention.
   /me $ACTION          - Show yourself doing an action.
   /nick $NAME          - Rename yourself to a new name.
   /whois $NAME         - Display information about another connected user.
   /msg $NAME $MESSAGE  - Sends a private message to a user.
` + RESET

const OP_HELP_TEXT string = SYSTEM_MESSAGE_FORMAT + `-> Available operator commands:
<<<<<<< HEAD
   /ban $NAME       - Banish a user from the chat
   /kick $NAME      - Kick em' out.
   /op $NAME        - Promote a user to server operator
   /silence $NAME   - Revoke a user's ability to speak
   /ban $NAME           - Banish a user from the chat
   /kick $NAME          - Kick em' out.
   /op $NAME            - Promote a user to server operator.
   /silence $NAME       - Revoke a user's ability to speak.
` + RESET

const ABOUT_TEXT string = SYSTEM_MESSAGE_FORMAT + `-> ssh-chat is made by @shazow.

   It is a custom ssh server built in Go to serve a chat experience
   instead of a shell.

   Source: https://github.com/shazow/ssh-chat

   For more, visit shazow.net or follow at twitter.com/shazow
` + RESET

const REQUIRED_WAIT time.Duration = time.Second / 2

type Client struct {
	Server        *Server
	Conn          *ssh.ServerConn
	Msg           chan string
	Name          string
	Color         string
	Op            bool
	ready         chan struct{}
	term          *terminal.Terminal
	termWidth     int
	termHeight    int
	silencedUntil time.Time
	lastTX        time.Time
	beepMe        bool
}

func NewClient(server *Server, conn *ssh.ServerConn) *Client {
	return &Client{
		Server: server,
		Conn:   conn,
		Name:   conn.User(),
		Color:  RandomColor256(),
		Msg:    make(chan string, MSG_BUFFER),
		ready:  make(chan struct{}, 1),
		lastTX: time.Now(),
	}
}

func (c *Client) ColoredName() string {
	return ColorString(c.Color, c.Name)
}

func (c *Client) SysMsg(msg string, args ...interface{}) {
	c.Msg <- ContinuousFormat(SYSTEM_MESSAGE_FORMAT, "-> "+fmt.Sprintf(msg, args...))
}

func (c *Client) Write(msg string) {
	c.term.Write([]byte(msg + "\r\n"))
}

func (c *Client) WriteLines(msg []string) {
	for _, line := range msg {
		c.Write(line)
	}
}

func (c *Client) Send(msg string) {
	if len(msg) > MAX_MSG_LENGTH {
		return
	}
	select {
	case c.Msg <- msg:
	default:
		logger.Errorf("Msg buffer full, dropping: %s (%s)", c.Name, c.Conn.RemoteAddr())
		c.Conn.Close()
	}
}

func (c *Client) SendLines(msg []string) {
	for _, line := range msg {
		c.Send(line)
	}
}

func (c *Client) IsSilenced() bool {
	return c.silencedUntil.After(time.Now())
}

func (c *Client) Silence(d time.Duration) {
	c.silencedUntil = time.Now().Add(d)
}

func (c *Client) Resize(width int, height int) error {
	err := c.term.SetSize(width, height)
	if err != nil {
		logger.Errorf("Resize failed: %dx%d", width, height)
		return err
	}
	c.termWidth, c.termHeight = width, height
	return nil
}

func (c *Client) Rename(name string) {
	c.Name = name
	c.term.SetPrompt(fmt.Sprintf("[%s] ", c.ColoredName()))
}

func (c *Client) Fingerprint() string {
	return c.Conn.Permissions.Extensions["fingerprint"]
}

func (c *Client) handleShell(channel ssh.Channel) {
	defer channel.Close()

	// FIXME: This shouldn't live here, need to restructure the call chaining.
	c.Server.Add(c)
	go func() {
		// Block until done, then remove.
		c.Conn.Wait()
		c.Server.Remove(c)
	}()

	go func() {
		for msg := range c.Msg {
			c.Write(msg)
		}
	}()

	for {
		line, err := c.term.ReadLine()
		if err != nil {
			break
		}

		parts := strings.SplitN(line, " ", 3)
		isCmd := strings.HasPrefix(parts[0], "/")

		if isCmd {
			// TODO: Factor this out.
			switch parts[0] {
			case "/test-colors": // Shh, this command is a secret!
				c.Write(ColorString("32", "Lorem ipsum dolor sit amet,"))
				c.Write("consectetur " + ColorString("31;1", "adipiscing") + " elit.")
			case "/exit":
				channel.Close()
			case "/help":
				c.WriteLines(strings.Split(HELP_TEXT, "\n"))
				if c.Server.IsOp(c) {
					c.WriteLines(strings.Split(OP_HELP_TEXT, "\n"))
				}
			case "/about":
				c.WriteLines(strings.Split(ABOUT_TEXT, "\n"))
			case "/uptime":
				c.Write(c.Server.Uptime())
			case "/beep":
				c.beepMe = !c.beepMe
				if c.beepMe {
					c.SysMsg("I'll beep you good.")
				} else {
					c.SysMsg("No more beeps. :(")
				}
			case "/me":
				me := strings.TrimLeft(line, "/me")
				if me == "" {
					me = " is at a loss for words."
				}
				msg := fmt.Sprintf("** %s%s", c.ColoredName(), me)
				if c.IsSilenced() || len(msg) > 1000 {
					c.SysMsg("Message rejected.")
				} else {
					c.Server.Broadcast(msg, nil)
				}
			case "/nick":
				if len(parts) == 2 {
					c.Server.Rename(c, parts[1])
				} else {
					c.SysMsg("Missing $NAME from: /nick $NAME")
				}
			case "/whois":
				if len(parts) == 2 {
					client := c.Server.Who(parts[1])
					if client != nil {
						version := RE_STRIP_TEXT.ReplaceAllString(string(client.Conn.ClientVersion()), "")
						if len(version) > 100 {
							version = "Evil Jerk with a superlong string"
						}
						c.SysMsg("%s is %s via %s", client.ColoredName(), client.Fingerprint(), version)
					} else {
						c.SysMsg("No such name: %s", parts[1])
					}
				} else {
					c.SysMsg("Missing $NAME from: /whois $NAME")
				}
			case "/list":
				names := ""
				nameList := c.Server.List(nil)
				for _, name := range nameList {
					names += c.Server.Who(name).ColoredName() + SYSTEM_MESSAGE_FORMAT + ", "
				}
				if len(names) > 2 {
					names = names[:len(names)-2]
				}
				c.SysMsg("%d connected: %s", len(nameList), names)
			case "/ban":
				if !c.Server.IsOp(c) {
					c.SysMsg("You're not an admin.")
				} else if len(parts) != 2 {
					c.SysMsg("Missing $NAME from: /ban $NAME")
				} else {
					client := c.Server.Who(parts[1])
					if client == nil {
						c.SysMsg("No such name: %s", parts[1])
					} else {
						fingerprint := client.Fingerprint()
						client.SysMsg("Banned by %s.", c.ColoredName())
						c.Server.Ban(fingerprint, nil)
						client.Conn.Close()
						c.Server.Broadcast(fmt.Sprintf("* %s was banned by %s", parts[1], c.ColoredName()), nil)
					}
				}
			case "/op":
				if !c.Server.IsOp(c) {
					c.SysMsg("You're not an admin.")
				} else if len(parts) != 2 {
					c.SysMsg("Missing $NAME from: /op $NAME")
				} else {
					client := c.Server.Who(parts[1])
					if client == nil {
						c.SysMsg("No such name: %s", parts[1])
					} else {
						fingerprint := client.Fingerprint()
						client.SysMsg("Made op by %s.", c.ColoredName())
						c.Server.Op(fingerprint)
					}
				}
			case "/kick":
				if !c.Server.IsOp(c) {
					c.SysMsg("You're not an admin.")
				} else if len(parts) != 2 {
					c.SysMsg("Missing $NAME from: /kick $NAME")
				} else {
					client := c.Server.Who(parts[1])
					if client == nil {
						c.SysMsg("No such name: %s", parts[1])
					} else {
						client.SysMsg("Kicked by %s.", c.ColoredName())
						client.Conn.Close()
						c.Server.Broadcast(fmt.Sprintf("* %s was kicked by %s", parts[1], c.ColoredName()), nil)
					}
				}
			case "/silence":
				if !c.Server.IsOp(c) {
					c.SysMsg("You're not an admin.")
				} else if len(parts) < 2 {
					c.SysMsg("Missing $NAME from: /silence $NAME")
				} else {
					duration := time.Duration(5) * time.Minute
					if len(parts) >= 3 {
						parsedDuration, err := time.ParseDuration(parts[2])
						if err == nil {
							duration = parsedDuration
						}
					}
					client := c.Server.Who(parts[1])
					if client == nil {
						c.SysMsg("No such name: %s", parts[1])
					} else {
						client.Silence(duration)
						client.SysMsg("Silenced for %s by %s.", duration, c.ColoredName())
					}
				}
			case "/msg": /* Send a PM */
				/* Make sure we have a recipient and a message */
				if len(parts) < 2 {
					c.Msg <- fmt.Sprintf("-> Missing $NAME from: /msg $NAME $MESSAGE")
					break
				} else if len(parts) < 3 {
					c.Msg <- fmt.Sprintf("-> Missing $MESSAGE from: /msg $NAME $MESSAGE")
					break
				}
				/* Ask the server to send the message */
				if err := c.Server.Privmsg(parts[1], parts[2], c); nil != err {
					c.Msg <- fmt.Sprintf("Unable to send message to %v: %v", parts[1], err)
				}

			default:
				c.SysMsg("Invalid command: %s", line)
			}
			continue
		}

		msg := fmt.Sprintf("%s: %s", c.ColoredName(), line)
		/* Rate limit */
		if time.Now().Sub(c.lastTX) < REQUIRED_WAIT {
			c.Msg <- fmt.Sprintf("-> Rate limiting in effect.")
			continue
		}
		if c.IsSilenced() || len(msg) > 1000 || len(msg) == 0 {
			c.SysMsg("Message rejected.")
			continue
		}
		c.Server.Broadcast(msg, c)
		c.lastTX = time.Now()
	}

}

func (c *Client) handleChannels(channels <-chan ssh.NewChannel) {
	prompt := fmt.Sprintf("[%s] ", c.ColoredName())

	hasShell := false

	for ch := range channels {
		if t := ch.ChannelType(); t != "session" {
			ch.Reject(ssh.UnknownChannelType, fmt.Sprintf("unknown channel type: %s", t))
			continue
		}

		channel, requests, err := ch.Accept()
		if err != nil {
			logger.Errorf("Could not accept channel: %v", err)
			continue
		}
		defer channel.Close()

		c.term = terminal.NewTerminal(channel, prompt)
		c.term.AutoCompleteCallback = c.Server.AutoCompleteFunction

		for req := range requests {
			var width, height int
			var ok bool

			switch req.Type {
			case "shell":
				if c.term != nil && !hasShell {
					go c.handleShell(channel)
					ok = true
					hasShell = true
				}
			case "pty-req":
				width, height, ok = parsePtyRequest(req.Payload)
				if ok {
					err := c.Resize(width, height)
					ok = err == nil
				}
			case "window-change":
				width, height, ok = parseWinchRequest(req.Payload)
				if ok {
					err := c.Resize(width, height)
					ok = err == nil
				}
			}

			if req.WantReply {
				req.Reply(ok, nil)
			}
		}
	}
}
<commit_msg>OOops merge conflict.<commit_after>package main

import (
	"fmt"
	"strings"
	"time"

	"golang.org/x/crypto/ssh"
	"golang.org/x/crypto/ssh/terminal"
)

const MSG_BUFFER int = 50
const MAX_MSG_LENGTH int = 512

const HELP_TEXT string = SYSTEM_MESSAGE_FORMAT + `-> Available commands:
   /about               - About this chat.
   /exit                - Exit the chat.
   /help                - Show this help text.
   /list                - List the users that are currently connected.
   /beep                - Enable BEL notifications on mention.
   /me $ACTION          - Show yourself doing an action.
   /nick $NAME          - Rename yourself to a new name.
   /whois $NAME         - Display information about another connected user.
   /msg $NAME $MESSAGE  - Sends a private message to a user.
` + RESET

const OP_HELP_TEXT string = SYSTEM_MESSAGE_FORMAT + `-> Available operator commands:
   /ban $NAME       - Banish a user from the chat
   /kick $NAME      - Kick em' out.
   /op $NAME        - Promote a user to server operator
   /silence $NAME   - Revoke a user's ability to speak
   /ban $NAME           - Banish a user from the chat
   /kick $NAME          - Kick em' out.
   /op $NAME            - Promote a user to server operator.
   /silence $NAME       - Revoke a user's ability to speak.
` + RESET

const ABOUT_TEXT string = SYSTEM_MESSAGE_FORMAT + `-> ssh-chat is made by @shazow.

   It is a custom ssh server built in Go to serve a chat experience
   instead of a shell.

   Source: https://github.com/shazow/ssh-chat

   For more, visit shazow.net or follow at twitter.com/shazow
` + RESET

const REQUIRED_WAIT time.Duration = time.Second / 2

type Client struct {
	Server        *Server
	Conn          *ssh.ServerConn
	Msg           chan string
	Name          string
	Color         string
	Op            bool
	ready         chan struct{}
	term          *terminal.Terminal
	termWidth     int
	termHeight    int
	silencedUntil time.Time
	lastTX        time.Time
	beepMe        bool
}

func NewClient(server *Server, conn *ssh.ServerConn) *Client {
	return &Client{
		Server: server,
		Conn:   conn,
		Name:   conn.User(),
		Color:  RandomColor256(),
		Msg:    make(chan string, MSG_BUFFER),
		ready:  make(chan struct{}, 1),
		lastTX: time.Now(),
	}
}

func (c *Client) ColoredName() string {
	return ColorString(c.Color, c.Name)
}

func (c *Client) SysMsg(msg string, args ...interface{}) {
	c.Msg <- ContinuousFormat(SYSTEM_MESSAGE_FORMAT, "-> "+fmt.Sprintf(msg, args...))
}

func (c *Client) Write(msg string) {
	c.term.Write([]byte(msg + "\r\n"))
}

func (c *Client) WriteLines(msg []string) {
	for _, line := range msg {
		c.Write(line)
	}
}

func (c *Client) Send(msg string) {
	if len(msg) > MAX_MSG_LENGTH {
		return
	}
	select {
	case c.Msg <- msg:
	default:
		logger.Errorf("Msg buffer full, dropping: %s (%s)", c.Name, c.Conn.RemoteAddr())
		c.Conn.Close()
	}
}

func (c *Client) SendLines(msg []string) {
	for _, line := range msg {
		c.Send(line)
	}
}

func (c *Client) IsSilenced() bool {
	return c.silencedUntil.After(time.Now())
}

func (c *Client) Silence(d time.Duration) {
	c.silencedUntil = time.Now().Add(d)
}

func (c *Client) Resize(width int, height int) error {
	err := c.term.SetSize(width, height)
	if err != nil {
		logger.Errorf("Resize failed: %dx%d", width, height)
		return err
	}
	c.termWidth, c.termHeight = width, height
	return nil
}

func (c *Client) Rename(name string) {
	c.Name = name
	c.term.SetPrompt(fmt.Sprintf("[%s] ", c.ColoredName()))
}

func (c *Client) Fingerprint() string {
	return c.Conn.Permissions.Extensions["fingerprint"]
}

func (c *Client) handleShell(channel ssh.Channel) {
	defer channel.Close()

	// FIXME: This shouldn't live here, need to restructure the call chaining.
	c.Server.Add(c)
	go func() {
		// Block until done, then remove.
		c.Conn.Wait()
		c.Server.Remove(c)
	}()

	go func() {
		for msg := range c.Msg {
			c.Write(msg)
		}
	}()

	for {
		line, err := c.term.ReadLine()
		if err != nil {
			break
		}

		parts := strings.SplitN(line, " ", 3)
		isCmd := strings.HasPrefix(parts[0], "/")

		if isCmd {
			// TODO: Factor this out.
			switch parts[0] {
			case "/test-colors": // Shh, this command is a secret!
				c.Write(ColorString("32", "Lorem ipsum dolor sit amet,"))
				c.Write("consectetur " + ColorString("31;1", "adipiscing") + " elit.")
			case "/exit":
				channel.Close()
			case "/help":
				c.WriteLines(strings.Split(HELP_TEXT, "\n"))
				if c.Server.IsOp(c) {
					c.WriteLines(strings.Split(OP_HELP_TEXT, "\n"))
				}
			case "/about":
				c.WriteLines(strings.Split(ABOUT_TEXT, "\n"))
			case "/uptime":
				c.Write(c.Server.Uptime())
			case "/beep":
				c.beepMe = !c.beepMe
				if c.beepMe {
					c.SysMsg("I'll beep you good.")
				} else {
					c.SysMsg("No more beeps. :(")
				}
			case "/me":
				me := strings.TrimLeft(line, "/me")
				if me == "" {
					me = " is at a loss for words."
				}
				msg := fmt.Sprintf("** %s%s", c.ColoredName(), me)
				if c.IsSilenced() || len(msg) > 1000 {
					c.SysMsg("Message rejected.")
				} else {
					c.Server.Broadcast(msg, nil)
				}
			case "/nick":
				if len(parts) == 2 {
					c.Server.Rename(c, parts[1])
				} else {
					c.SysMsg("Missing $NAME from: /nick $NAME")
				}
			case "/whois":
				if len(parts) == 2 {
					client := c.Server.Who(parts[1])
					if client != nil {
						version := RE_STRIP_TEXT.ReplaceAllString(string(client.Conn.ClientVersion()), "")
						if len(version) > 100 {
							version = "Evil Jerk with a superlong string"
						}
						c.SysMsg("%s is %s via %s", client.ColoredName(), client.Fingerprint(), version)
					} else {
						c.SysMsg("No such name: %s", parts[1])
					}
				} else {
					c.SysMsg("Missing $NAME from: /whois $NAME")
				}
			case "/list":
				names := ""
				nameList := c.Server.List(nil)
				for _, name := range nameList {
					names += c.Server.Who(name).ColoredName() + SYSTEM_MESSAGE_FORMAT + ", "
				}
				if len(names) > 2 {
					names = names[:len(names)-2]
				}
				c.SysMsg("%d connected: %s", len(nameList), names)
			case "/ban":
				if !c.Server.IsOp(c) {
					c.SysMsg("You're not an admin.")
				} else if len(parts) != 2 {
					c.SysMsg("Missing $NAME from: /ban $NAME")
				} else {
					client := c.Server.Who(parts[1])
					if client == nil {
						c.SysMsg("No such name: %s", parts[1])
					} else {
						fingerprint := client.Fingerprint()
						client.SysMsg("Banned by %s.", c.ColoredName())
						c.Server.Ban(fingerprint, nil)
						client.Conn.Close()
						c.Server.Broadcast(fmt.Sprintf("* %s was banned by %s", parts[1], c.ColoredName()), nil)
					}
				}
			case "/op":
				if !c.Server.IsOp(c) {
					c.SysMsg("You're not an admin.")
				} else if len(parts) != 2 {
					c.SysMsg("Missing $NAME from: /op $NAME")
				} else {
					client := c.Server.Who(parts[1])
					if client == nil {
						c.SysMsg("No such name: %s", parts[1])
					} else {
						fingerprint := client.Fingerprint()
						client.SysMsg("Made op by %s.", c.ColoredName())
						c.Server.Op(fingerprint)
					}
				}
			case "/kick":
				if !c.Server.IsOp(c) {
					c.SysMsg("You're not an admin.")
				} else if len(parts) != 2 {
					c.SysMsg("Missing $NAME from: /kick $NAME")
				} else {
					client := c.Server.Who(parts[1])
					if client == nil {
						c.SysMsg("No such name: %s", parts[1])
					} else {
						client.SysMsg("Kicked by %s.", c.ColoredName())
						client.Conn.Close()
						c.Server.Broadcast(fmt.Sprintf("* %s was kicked by %s", parts[1], c.ColoredName()), nil)
					}
				}
			case "/silence":
				if !c.Server.IsOp(c) {
					c.SysMsg("You're not an admin.")
				} else if len(parts) < 2 {
					c.SysMsg("Missing $NAME from: /silence $NAME")
				} else {
					duration := time.Duration(5) * time.Minute
					if len(parts) >= 3 {
						parsedDuration, err := time.ParseDuration(parts[2])
						if err == nil {
							duration = parsedDuration
						}
					}
					client := c.Server.Who(parts[1])
					if client == nil {
						c.SysMsg("No such name: %s", parts[1])
					} else {
						client.Silence(duration)
						client.SysMsg("Silenced for %s by %s.", duration, c.ColoredName())
					}
				}
			case "/msg": /* Send a PM */
				/* Make sure we have a recipient and a message */
				if len(parts) < 2 {
					c.Msg <- fmt.Sprintf("-> Missing $NAME from: /msg $NAME $MESSAGE")
					break
				} else if len(parts) < 3 {
					c.Msg <- fmt.Sprintf("-> Missing $MESSAGE from: /msg $NAME $MESSAGE")
					break
				}
				/* Ask the server to send the message */
				if err := c.Server.Privmsg(parts[1], parts[2], c); nil != err {
					c.Msg <- fmt.Sprintf("Unable to send message to %v: %v", parts[1], err)
				}

			default:
				c.SysMsg("Invalid command: %s", line)
			}
			continue
		}

		msg := fmt.Sprintf("%s: %s", c.ColoredName(), line)
		/* Rate limit */
		if time.Now().Sub(c.lastTX) < REQUIRED_WAIT {
			c.Msg <- fmt.Sprintf("-> Rate limiting in effect.")
			continue
		}
		if c.IsSilenced() || len(msg) > 1000 || len(msg) == 0 {
			c.SysMsg("Message rejected.")
			continue
		}
		c.Server.Broadcast(msg, c)
		c.lastTX = time.Now()
	}

}

func (c *Client) handleChannels(channels <-chan ssh.NewChannel) {
	prompt := fmt.Sprintf("[%s] ", c.ColoredName())

	hasShell := false

	for ch := range channels {
		if t := ch.ChannelType(); t != "session" {
			ch.Reject(ssh.UnknownChannelType, fmt.Sprintf("unknown channel type: %s", t))
			continue
		}

		channel, requests, err := ch.Accept()
		if err != nil {
			logger.Errorf("Could not accept channel: %v", err)
			continue
		}
		defer channel.Close()

		c.term = terminal.NewTerminal(channel, prompt)
		c.term.AutoCompleteCallback = c.Server.AutoCompleteFunction

		for req := range requests {
			var width, height int
			var ok bool

			switch req.Type {
			case "shell":
				if c.term != nil && !hasShell {
					go c.handleShell(channel)
					ok = true
					hasShell = true
				}
			case "pty-req":
				width, height, ok = parsePtyRequest(req.Payload)
				if ok {
					err := c.Resize(width, height)
					ok = err == nil
				}
			case "window-change":
				width, height, ok = parseWinchRequest(req.Payload)
				if ok {
					err := c.Resize(width, height)
					ok = err == nil
				}
			}

			if req.WantReply {
				req.Reply(ok, nil)
			}
		}
	}
}
<|endoftext|>


<commit_before>package gopherstack

import (
	"crypto/hmac"
	"crypto/sha1"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"strings"
)

type CloudStackClient struct {
	// The http client for communicating
	client *http.Client

	// The base URL of the API
	BaseURL string

	// Credentials
	APIKey string
	Secret string
}

// Creates a new client for communicating with CloudStack
func (cloudstack CloudStackClient) New(apiurl string, apikey string, secret string) *CloudStackClient {
	c := &CloudStackClient{
		client: &http.Client{
			Transport: &http.Transport{
				Proxy: http.ProxyFromEnvironment,
			},
		},
		BaseURL: apiurl,
		APIKey:  apikey,
		Secret:  secret,
	}
	return c
}

func NewRequest(c CloudStackClient, request string, params url.Values) (interface{}, error) {
	client := c.client

	params.Set("apikey", c.APIKey)
	params.Set("command", request)
	params.Set("response", "json")

	// Generate signature for API call
	// * Serialize parameters and sort them by key, done by Encode()
	// * Use byte sequence for '+' character as CloudStack requires this
	// * Convert the entire argument string to lowercase
	// * Calculate HMAC SHA1 of argument string with CloudStack secret
	// * URL encode the string and convert to base64
	s := params.Encode()
	s2 := strings.Replace(s, "+", "%20", -1)
	s3 := strings.ToLower(s2)
	mac := hmac.New(sha1.New, []byte(c.Secret))
	mac.Write([]byte(s3))
	signature := base64.StdEncoding.EncodeToString(mac.Sum(nil))
	signature = url.QueryEscape(signature)

	// Create the final URL before we issue the request
	// For some reason CloudStack refuses to accept '+' as a space character so we byte escape it instead.
	url := c.BaseURL + "?" + s2 + "&signature=" + signature

	log.Printf("Calling %s ", url)

	resp, err := client.Get(url)
	if err != nil {
		return nil, err
	}

	body, err := ioutil.ReadAll(resp.Body)
	resp.Body.Close()
	if err != nil {
		return nil, err
	}

	log.Printf("response from cloudstack: %d - %s", resp.StatusCode, body)
	if resp.StatusCode != 200 {
		err = errors.New(fmt.Sprintf("Received HTTP client/server error from CloudStack: %d", resp.StatusCode))
		return nil, err
	}

	switch request {
	default:
		log.Printf("Unknown request %s", request)
	case "createSSHKeyPair":
		var decodedResponse CreateSshKeyPairResponse
		json.Unmarshal(body, &decodedResponse)
		return decodedResponse, nil

	case "deleteSSHKeyPair":
		var decodedResponse DeleteSshKeyPairResponse
		json.Unmarshal(body, &decodedResponse)
		return decodedResponse, nil

	case "deployVirtualMachine":
		var decodedResponse DeployVirtualMachineResponse
		json.Unmarshal(body, &decodedResponse)
		return decodedResponse, nil

	case "destroyVirtualMachine":
		var decodedResponse DestroyVirtualMachineResponse
		json.Unmarshal(body, &decodedResponse)
		return decodedResponse, nil

	case "stopVirtualMachine":
		var decodedResponse StopVirtualMachineResponse
		json.Unmarshal(body, &decodedResponse)
		return decodedResponse, nil

	case "listVirtualMachines":
		var decodedResponse ListVirtualMachinesResponse
		json.Unmarshal(body, &decodedResponse)
		return decodedResponse, nil

	case "listProjects":
		var decodedResponse ListProjectsResponse
		json.Unmarshal(body, &decodedResponse)
		return decodedResponse, nil

	case "listVolumes":
		var decodedResponse ListVolumesResponse
		json.Unmarshal(body, &decodedResponse)
		return decodedResponse, nil

	case "createTemplate":
		var decodedResponse CreateTemplateResponse
		json.Unmarshal(body, &decodedResponse)
		return decodedResponse, nil

	case "listTemplates":
		var decodedResponse ListTemplatesResponse
		json.Unmarshal(body, &decodedResponse)
		return decodedResponse, nil

	case "listDiskOfferings":
		var decodedResponse ListDiskOfferingsResponse
		json.Unmarshal(body, &decodedResponse)
		return decodedResponse, nil

	case "detachIso":
		var decodedResponse DetachIsoResponse
		json.Unmarshal(body, &decodedResponse)
		return decodedResponse, nil

	case "queryAsyncJobResult":
		var decodedResponse QueryAsyncJobResultResponse
		json.Unmarshal(body, &decodedResponse)
		return decodedResponse, nil

	}

	// only reached with unknown request
	return "", nil
}
<commit_msg>Add possibility to ignore certificate chains<commit_after>package gopherstack

import (
	"crypto/hmac"
	"crypto/sha1"
	"crypto/tls"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"strings"
)

type CloudStackClient struct {
	// The http client for communicating
	client *http.Client

	// The base URL of the API
	BaseURL string

	// Credentials
	APIKey string
	Secret string
}

// Creates a new client for communicating with Cloudstack
func (cloudstack CloudStackClient) New(apiurl string, apikey string, secret string, insecureskipverify bool) *CloudStackClient {
	c := &CloudStackClient{
		client: &http.Client{
			Transport: &http.Transport{
				TLSClientConfig: &tls.Config{InsecureSkipVerify: insecureskipverify},
				Proxy: http.ProxyFromEnvironment,
			},
		},
		BaseURL: apiurl,
		APIKey:  apikey,
		Secret:  secret,
	}
	return c
}

func NewRequest(c CloudStackClient, request string, params url.Values) (interface{}, error) {
	client := c.client

	params.Set("apikey", c.APIKey)
	params.Set("command", request)
	params.Set("response", "json")

	// Generate signature for API call
	// * Serialize parameters and sort them by key, done by Encode()
	// * Use byte sequence for '+' character as Cloudstack requires this
	// * Convert the entire argument string to lowercase
	// * Calculate HMAC SHA1 of argument string with Cloudstack secret
	// * URL encode the string and convert to base64
	s := params.Encode()
	s2 := strings.Replace(s, "+", "%20", -1)
	s3 := strings.ToLower(s2)
	mac := hmac.New(sha1.New, []byte(c.Secret))
	mac.Write([]byte(s3))
	signature := base64.StdEncoding.EncodeToString(mac.Sum(nil))
	signature = url.QueryEscape(signature)

	// Create the final URL before we issue the request
	// For some reason Cloudstack refuses to accept '+' as a space character so we byte escape it instead.
	url := c.BaseURL + "?" + s2 + "&signature=" + signature

	log.Printf("Calling %s ", url)

	resp, err := client.Get(url)
	if err != nil {
		return nil, err
	}

	body, err := ioutil.ReadAll(resp.Body)
	resp.Body.Close()
	if err != nil {
		return nil, err
	}

	log.Printf("Response from Cloudstack: %d - %s", resp.StatusCode, body)
	if resp.StatusCode != 200 {
		err = errors.New(fmt.Sprintf("Received HTTP client/server error from CloudStack: %d", resp.StatusCode))
		return nil, err
	}

	switch request {
	default:
		log.Printf("Unknown request %s", request)
	case "createSSHKeyPair":
		var decodedResponse CreateSshKeyPairResponse
		json.Unmarshal(body, &decodedResponse)
		return decodedResponse, nil

	case "deleteSSHKeyPair":
		var decodedResponse DeleteSshKeyPairResponse
		json.Unmarshal(body, &decodedResponse)
		return decodedResponse, nil

	case "deployVirtualMachine":
		var decodedResponse DeployVirtualMachineResponse
		json.Unmarshal(body, &decodedResponse)
		return decodedResponse, nil

	case "destroyVirtualMachine":
		var decodedResponse DestroyVirtualMachineResponse
		json.Unmarshal(body, &decodedResponse)
		return decodedResponse, nil

	case "stopVirtualMachine":
		var decodedResponse StopVirtualMachineResponse
		json.Unmarshal(body, &decodedResponse)
		return decodedResponse, nil

	case "listVirtualMachines":
		var decodedResponse ListVirtualMachinesResponse
		json.Unmarshal(body, &decodedResponse)
		return decodedResponse, nil

	case "listProjects":
		var decodedResponse ListProjectsResponse
		json.Unmarshal(body, &decodedResponse)
		return decodedResponse, nil

	case "listVolumes":
		var decodedResponse ListVolumesResponse
		json.Unmarshal(body, &decodedResponse)
		return decodedResponse, nil

	case "createTemplate":
		var decodedResponse CreateTemplateResponse
		json.Unmarshal(body, &decodedResponse)
		return decodedResponse, nil

	case "listTemplates":
		var decodedResponse ListTemplatesResponse
		json.Unmarshal(body, &decodedResponse)
		return decodedResponse, nil

	case "listDiskOfferings":
		var decodedResponse ListDiskOfferingsResponse
		json.Unmarshal(body, &decodedResponse)
		return decodedResponse, nil

	case "detachIso":
		var decodedResponse DetachIsoResponse
		json.Unmarshal(body, &decodedResponse)
		return decodedResponse, nil

	case "queryAsyncJobResult":
		var decodedResponse QueryAsyncJobResultResponse
		json.Unmarshal(body, &decodedResponse)
		return decodedResponse, nil

	}

	// only reached with unknown request
	return "", nil
}
<|endoftext|>


<commit_before>/*
Copyright 2016 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package apiserver

import (
	"net/http"
	"strings"

	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/labels"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/serializer"
	"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
	v1listers "k8s.io/client-go/listers/core/v1"

	apiregistrationapi "k8s.io/kube-aggregator/pkg/apis/apiregistration"
	apiregistrationv1alpha1api "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1alpha1"
	informers "k8s.io/kube-aggregator/pkg/client/informers/internalversion/apiregistration/internalversion"
	listers "k8s.io/kube-aggregator/pkg/client/listers/apiregistration/internalversion"
)

// WithAPIs adds the handling for /apis and /apis/<group: -apiregistration.k8s.io>.
func WithAPIs(handler http.Handler, codecs serializer.CodecFactory, informer informers.APIServiceInformer, serviceLister v1listers.ServiceLister, endpointsLister v1listers.EndpointsLister) http.Handler {
	apisHandler := &apisHandler{
		codecs:          codecs,
		lister:          informer.Lister(),
		delegate:        handler,
		serviceLister:   serviceLister,
		endpointsLister: endpointsLister,
	}
	return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		apisHandler.ServeHTTP(w, req)
	})
}

// apisHandler serves the `/apis` endpoint.
// This is registered as a filter so that it never collides with any explictly registered endpoints
type apisHandler struct {
	codecs serializer.CodecFactory
	lister listers.APIServiceLister

	serviceLister   v1listers.ServiceLister
	endpointsLister v1listers.EndpointsLister

	delegate http.Handler
}

var discoveryGroup = metav1.APIGroup{
	Name: apiregistrationapi.GroupName,
	Versions: []metav1.GroupVersionForDiscovery{
		{
			GroupVersion: apiregistrationv1alpha1api.SchemeGroupVersion.String(),
			Version:      apiregistrationv1alpha1api.SchemeGroupVersion.Version,
		},
	},
	PreferredVersion: metav1.GroupVersionForDiscovery{
		GroupVersion: apiregistrationv1alpha1api.SchemeGroupVersion.String(),
		Version:      apiregistrationv1alpha1api.SchemeGroupVersion.Version,
	},
}

func (r *apisHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	// if the URL is for OUR api group, serve it normally
	if strings.HasPrefix(req.URL.Path+"/", "/apis/"+apiregistrationapi.GroupName+"/") {
		r.delegate.ServeHTTP(w, req)
		return
	}
	// don't handle URLs that aren't /apis
	if req.URL.Path != "/apis" && req.URL.Path != "/apis/" {
		r.delegate.ServeHTTP(w, req)
		return
	}

	discoveryGroupList := &metav1.APIGroupList{
		// always add OUR api group to the list first.  Since we'll never have a registered APIService for it
		// and since this is the crux of the API, having this first will give our names priority.  It's good to be king.
		Groups: []metav1.APIGroup{discoveryGroup},
	}

	apiServices, err := r.lister.List(labels.Everything())
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	apiServicesByGroup := apiregistrationapi.SortedByGroup(apiServices)
	for _, apiGroupServers := range apiServicesByGroup {
		// skip the legacy group
		if len(apiGroupServers[0].Spec.Group) == 0 {
			continue
		}
		discoveryGroup := convertToDiscoveryAPIGroup(apiGroupServers, r.serviceLister, r.endpointsLister)
		if discoveryGroup != nil {
			discoveryGroupList.Groups = append(discoveryGroupList.Groups, *discoveryGroup)
		}
	}

	json, err := runtime.Encode(r.codecs.LegacyCodec(), discoveryGroupList)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	if _, err := w.Write(json); err != nil {
		panic(err)
	}
}

// convertToDiscoveryAPIGroup takes apiservices in a single group and returns a discovery compatible object.
// if none of the services are available, it will return nil.
func convertToDiscoveryAPIGroup(apiServices []*apiregistrationapi.APIService, serviceLister v1listers.ServiceLister, endpointsLister v1listers.EndpointsLister) *metav1.APIGroup {
	apiServicesByGroup := apiregistrationapi.SortedByGroup(apiServices)[0]

	var discoveryGroup *metav1.APIGroup

	for _, apiService := range apiServicesByGroup {
		if apiService.Spec.Service != nil {
			// skip any API services without actual services
			if _, err := serviceLister.Services(apiService.Spec.Service.Namespace).Get(apiService.Spec.Service.Name); err != nil {
				continue
			}

			hasActiveEndpoints := false
			endpoints, err := endpointsLister.Endpoints(apiService.Spec.Service.Namespace).Get(apiService.Spec.Service.Name)
			// skip any API services without endpoints
			if err != nil {
				continue
			}
			for _, subset := range endpoints.Subsets {
				if len(subset.Addresses) > 0 {
					hasActiveEndpoints = true
					break
				}
			}
			if !hasActiveEndpoints {
				continue
			}
		}

		// the first APIService which is valid becomes the default
		if discoveryGroup == nil {
			discoveryGroup = &metav1.APIGroup{
				Name: apiService.Spec.Group,
				PreferredVersion: metav1.GroupVersionForDiscovery{
					GroupVersion: apiService.Spec.Group + "/" + apiService.Spec.Version,
					Version:      apiService.Spec.Version,
				},
			}
		}

		discoveryGroup.Versions = append(discoveryGroup.Versions,
			metav1.GroupVersionForDiscovery{
				GroupVersion: apiService.Spec.Group + "/" + apiService.Spec.Version,
				Version:      apiService.Spec.Version,
			},
		)
	}

	return discoveryGroup
}

// apiGroupHandler serves the `/apis/<group>` endpoint.
type apiGroupHandler struct {
	codecs    serializer.CodecFactory
	groupName string

	lister listers.APIServiceLister

	serviceLister   v1listers.ServiceLister
	endpointsLister v1listers.EndpointsLister

	delegate http.Handler
}

func (r *apiGroupHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	// don't handle URLs that aren't /apis/<groupName>
	if req.URL.Path != "/apis/"+r.groupName && req.URL.Path != "/apis/"+r.groupName+"/" {
		r.delegate.ServeHTTP(w, req)
		return
	}

	apiServices, err := r.lister.List(labels.Everything())
	if statusErr, ok := err.(*apierrors.StatusError); ok && err != nil {
		responsewriters.WriteRawJSON(int(statusErr.Status().Code), statusErr.Status(), w)
		return
	}
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	apiServicesForGroup := []*apiregistrationapi.APIService{}
	for _, apiService := range apiServices {
		if apiService.Spec.Group == r.groupName {
			apiServicesForGroup = append(apiServicesForGroup, apiService)
		}
	}

	if len(apiServicesForGroup) == 0 {
		http.Error(w, "", http.StatusNotFound)
		return
	}

	discoveryGroup := convertToDiscoveryAPIGroup(apiServicesForGroup, r.serviceLister, r.endpointsLister)
	if discoveryGroup == nil {
		http.Error(w, "", http.StatusNotFound)
		return
	}
	json, err := runtime.Encode(r.codecs.LegacyCodec(), discoveryGroup)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	if _, err := w.Write(json); err != nil {
		panic(err)
	}
}
<commit_msg>UPSTREAM: 44837: Fix Content-Type error of apis<commit_after>/*
Copyright 2016 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package apiserver

import (
	"net/http"
	"strings"

	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/labels"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apimachinery/pkg/runtime/serializer"
	"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
	v1listers "k8s.io/client-go/listers/core/v1"

	apiregistrationapi "k8s.io/kube-aggregator/pkg/apis/apiregistration"
	apiregistrationv1alpha1api "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1alpha1"
	informers "k8s.io/kube-aggregator/pkg/client/informers/internalversion/apiregistration/internalversion"
	listers "k8s.io/kube-aggregator/pkg/client/listers/apiregistration/internalversion"
)

// WithAPIs adds the handling for /apis and /apis/<group: -apiregistration.k8s.io>.
func WithAPIs(handler http.Handler, codecs serializer.CodecFactory, informer informers.APIServiceInformer, serviceLister v1listers.ServiceLister, endpointsLister v1listers.EndpointsLister) http.Handler {
	apisHandler := &apisHandler{
		codecs:          codecs,
		lister:          informer.Lister(),
		delegate:        handler,
		serviceLister:   serviceLister,
		endpointsLister: endpointsLister,
	}
	return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		apisHandler.ServeHTTP(w, req)
	})
}

// apisHandler serves the `/apis` endpoint.
// This is registered as a filter so that it never collides with any explictly registered endpoints
type apisHandler struct {
	codecs serializer.CodecFactory
	lister listers.APIServiceLister

	serviceLister   v1listers.ServiceLister
	endpointsLister v1listers.EndpointsLister

	delegate http.Handler
}

var discoveryGroup = metav1.APIGroup{
	Name: apiregistrationapi.GroupName,
	Versions: []metav1.GroupVersionForDiscovery{
		{
			GroupVersion: apiregistrationv1alpha1api.SchemeGroupVersion.String(),
			Version:      apiregistrationv1alpha1api.SchemeGroupVersion.Version,
		},
	},
	PreferredVersion: metav1.GroupVersionForDiscovery{
		GroupVersion: apiregistrationv1alpha1api.SchemeGroupVersion.String(),
		Version:      apiregistrationv1alpha1api.SchemeGroupVersion.Version,
	},
}

func (r *apisHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	// if the URL is for OUR api group, serve it normally
	if strings.HasPrefix(req.URL.Path+"/", "/apis/"+apiregistrationapi.GroupName+"/") {
		r.delegate.ServeHTTP(w, req)
		return
	}
	// don't handle URLs that aren't /apis
	if req.URL.Path != "/apis" && req.URL.Path != "/apis/" {
		r.delegate.ServeHTTP(w, req)
		return
	}

	discoveryGroupList := &metav1.APIGroupList{
		// always add OUR api group to the list first.  Since we'll never have a registered APIService for it
		// and since this is the crux of the API, having this first will give our names priority.  It's good to be king.
		Groups: []metav1.APIGroup{discoveryGroup},
	}

	apiServices, err := r.lister.List(labels.Everything())
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	apiServicesByGroup := apiregistrationapi.SortedByGroup(apiServices)
	for _, apiGroupServers := range apiServicesByGroup {
		// skip the legacy group
		if len(apiGroupServers[0].Spec.Group) == 0 {
			continue
		}
		discoveryGroup := convertToDiscoveryAPIGroup(apiGroupServers, r.serviceLister, r.endpointsLister)
		if discoveryGroup != nil {
			discoveryGroupList.Groups = append(discoveryGroupList.Groups, *discoveryGroup)
		}
	}

	responsewriters.WriteObjectNegotiated(r.codecs, schema.GroupVersion{}, w, req, http.StatusOK, discoveryGroupList)
}

// convertToDiscoveryAPIGroup takes apiservices in a single group and returns a discovery compatible object.
// if none of the services are available, it will return nil.
func convertToDiscoveryAPIGroup(apiServices []*apiregistrationapi.APIService, serviceLister v1listers.ServiceLister, endpointsLister v1listers.EndpointsLister) *metav1.APIGroup {
	apiServicesByGroup := apiregistrationapi.SortedByGroup(apiServices)[0]

	var discoveryGroup *metav1.APIGroup

	for _, apiService := range apiServicesByGroup {
		if apiService.Spec.Service != nil {
			// skip any API services without actual services
			if _, err := serviceLister.Services(apiService.Spec.Service.Namespace).Get(apiService.Spec.Service.Name); err != nil {
				continue
			}

			hasActiveEndpoints := false
			endpoints, err := endpointsLister.Endpoints(apiService.Spec.Service.Namespace).Get(apiService.Spec.Service.Name)
			// skip any API services without endpoints
			if err != nil {
				continue
			}
			for _, subset := range endpoints.Subsets {
				if len(subset.Addresses) > 0 {
					hasActiveEndpoints = true
					break
				}
			}
			if !hasActiveEndpoints {
				continue
			}
		}

		// the first APIService which is valid becomes the default
		if discoveryGroup == nil {
			discoveryGroup = &metav1.APIGroup{
				Name: apiService.Spec.Group,
				PreferredVersion: metav1.GroupVersionForDiscovery{
					GroupVersion: apiService.Spec.Group + "/" + apiService.Spec.Version,
					Version:      apiService.Spec.Version,
				},
			}
		}

		discoveryGroup.Versions = append(discoveryGroup.Versions,
			metav1.GroupVersionForDiscovery{
				GroupVersion: apiService.Spec.Group + "/" + apiService.Spec.Version,
				Version:      apiService.Spec.Version,
			},
		)
	}

	return discoveryGroup
}

// apiGroupHandler serves the `/apis/<group>` endpoint.
type apiGroupHandler struct {
	codecs    serializer.CodecFactory
	groupName string

	lister listers.APIServiceLister

	serviceLister   v1listers.ServiceLister
	endpointsLister v1listers.EndpointsLister

	delegate http.Handler
}

func (r *apiGroupHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	// don't handle URLs that aren't /apis/<groupName>
	if req.URL.Path != "/apis/"+r.groupName && req.URL.Path != "/apis/"+r.groupName+"/" {
		r.delegate.ServeHTTP(w, req)
		return
	}

	apiServices, err := r.lister.List(labels.Everything())
	if statusErr, ok := err.(*apierrors.StatusError); ok && err != nil {
		responsewriters.WriteRawJSON(int(statusErr.Status().Code), statusErr.Status(), w)
		return
	}
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	apiServicesForGroup := []*apiregistrationapi.APIService{}
	for _, apiService := range apiServices {
		if apiService.Spec.Group == r.groupName {
			apiServicesForGroup = append(apiServicesForGroup, apiService)
		}
	}

	if len(apiServicesForGroup) == 0 {
		http.Error(w, "", http.StatusNotFound)
		return
	}

	discoveryGroup := convertToDiscoveryAPIGroup(apiServicesForGroup, r.serviceLister, r.endpointsLister)
	if discoveryGroup == nil {
		http.Error(w, "", http.StatusNotFound)
		return
	}
	responsewriters.WriteObjectNegotiated(r.codecs, schema.GroupVersion{}, w, req, http.StatusOK, discoveryGroup)
}
<|endoftext|>


<commit_before>// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package maps

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

	"github.com/googlemaps/google-maps-services-go/internal"
	"golang.org/x/net/context"
	"golang.org/x/net/context/ctxhttp"
<<<<<<< HEAD
	"googlemaps.github.io/maps/internal"
=======
>>>>>>> Giving the right API config to each service
)

// Client may be used to make requests to the Google Maps WebService APIs
type Client struct {
	httpClient        *http.Client
	apiKey            string
	baseURL           string
	clientID          string
	signature         []byte
	requestsPerSecond int
	rateLimiter       chan int
}

// ClientOption is the type of constructor options for NewClient(...).
type ClientOption func(*Client) error

var defaultRequestsPerSecond = 10

// NewClient constructs a new Client which can make requests to the Google Maps WebService APIs.
func NewClient(options ...ClientOption) (*Client, error) {
	c := &Client{requestsPerSecond: defaultRequestsPerSecond}
	WithHTTPClient(&http.Client{})(c)
	for _, option := range options {
		err := option(c)
		if err != nil {
			return nil, err
		}
	}
	if c.apiKey == "" && (c.clientID == "" || len(c.signature) == 0) {
		return nil, errors.New("maps: API Key or Maps for Work credentials missing")
	}

	// Implement a bursty rate limiter.
	// Allow up to 1 second worth of requests to be made at once.
	c.rateLimiter = make(chan int, c.requestsPerSecond)
	// Prefill rateLimiter with 1 seconds worth of requests.
	for i := 0; i < c.requestsPerSecond; i++ {
		c.rateLimiter <- 1
	}
	go func() {
		// Refill rateLimiter continuously
		for range time.Tick(time.Second / time.Duration(c.requestsPerSecond)) {
			c.rateLimiter <- 1
		}
	}()

	return c, nil
}

// WithHTTPClient configures a Maps API client with a http.Client to make requests over.
func WithHTTPClient(c *http.Client) ClientOption {
	return func(client *Client) error {
		if _, ok := c.Transport.(*transport); !ok {
			t := c.Transport
			if t != nil {
				c.Transport = &transport{Base: t}
			} else {
				c.Transport = &transport{Base: http.DefaultTransport}
			}
		}
		client.httpClient = c
		return nil
	}
}

// WithAPIKey configures a Maps API client with an API Key
func WithAPIKey(apiKey string) ClientOption {
	return func(c *Client) error {
		c.apiKey = apiKey
		return nil
	}
}

// WithClientIDAndSignature configures a Maps API client for a Maps for Work application
// The signature is assumed to be URL modified Base64 encoded
func WithClientIDAndSignature(clientID, signature string) ClientOption {
	return func(c *Client) error {
		c.clientID = clientID
		decoded, err := base64.URLEncoding.DecodeString(signature)
		if err != nil {
			return err
		}
		c.signature = decoded
		return nil
	}
}

// WithRateLimit configures the rate limit for back end requests.
// Default is to limit to 10 requests per second.
func WithRateLimit(requestsPerSecond int) ClientOption {
	return func(c *Client) error {
		c.requestsPerSecond = requestsPerSecond
		return nil
	}
}

type apiConfig struct {
	host            string
	path            string
	acceptsClientID bool
}

type apiRequest interface {
	params() url.Values
}

func (c *Client) getJSON(ctx context.Context, config *apiConfig, apiReq apiRequest, resp interface{}) error {
	select {
	case <-ctx.Done():
		return ctx.Err()
	case <-c.rateLimiter:
		// Execute request.
	}

	host := config.host
	if c.baseURL != "" {
		host = c.baseURL
	}
	req, err := http.NewRequest("GET", host+config.path, nil)
	if err != nil {
		return err
	}
	q, err := c.generateAuthQuery(config.path, apiReq.params(), config.acceptsClientID)
	if err != nil {
		return err
	}
	req.URL.RawQuery = q
	httpResp, err := ctxhttp.Do(ctx, c.httpClient, req)
	if err != nil {
		return err
	}
	defer httpResp.Body.Close()

	return json.NewDecoder(httpResp.Body).Decode(resp)
}

func (c *Client) generateAuthQuery(path string, q url.Values, acceptClientID bool) (string, error) {
	if c.apiKey != "" {
		q.Set("key", c.apiKey)
		return q.Encode(), nil
	}
	if acceptClientID {
		return internal.SignURL(path, c.clientID, c.signature, q)
	}
	return "", errors.New("maps: API Key missing")
}

// commonResponse contains the common response fields to most API calls inside
// the Google Maps APIs. This is used internally.
type commonResponse struct {
	// Status contains the status of the request, and may contain debugging
	// information to help you track down why the call failed.
	Status string `json:"status"`

	// ErrorMessage is the explanatory field added when Status is an error.
	ErrorMessage string `json:"error_message"`
}

// StatusError returns an error iff this object has a non-OK Status.
func (c *commonResponse) StatusError() error {
	if c.Status != "OK" {
		return fmt.Errorf("maps: %s - %s", c.Status, c.ErrorMessage)
	}
	return nil
}
<commit_msg>Rebase fixup<commit_after>// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package maps

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

	"golang.org/x/net/context"
	"golang.org/x/net/context/ctxhttp"
	"googlemaps.github.io/maps/internal"
)

// Client may be used to make requests to the Google Maps WebService APIs
type Client struct {
	httpClient        *http.Client
	apiKey            string
	baseURL           string
	clientID          string
	signature         []byte
	requestsPerSecond int
	rateLimiter       chan int
}

// ClientOption is the type of constructor options for NewClient(...).
type ClientOption func(*Client) error

var defaultRequestsPerSecond = 10

// NewClient constructs a new Client which can make requests to the Google Maps WebService APIs.
func NewClient(options ...ClientOption) (*Client, error) {
	c := &Client{requestsPerSecond: defaultRequestsPerSecond}
	WithHTTPClient(&http.Client{})(c)
	for _, option := range options {
		err := option(c)
		if err != nil {
			return nil, err
		}
	}
	if c.apiKey == "" && (c.clientID == "" || len(c.signature) == 0) {
		return nil, errors.New("maps: API Key or Maps for Work credentials missing")
	}

	// Implement a bursty rate limiter.
	// Allow up to 1 second worth of requests to be made at once.
	c.rateLimiter = make(chan int, c.requestsPerSecond)
	// Prefill rateLimiter with 1 seconds worth of requests.
	for i := 0; i < c.requestsPerSecond; i++ {
		c.rateLimiter <- 1
	}
	go func() {
		// Refill rateLimiter continuously
		for range time.Tick(time.Second / time.Duration(c.requestsPerSecond)) {
			c.rateLimiter <- 1
		}
	}()

	return c, nil
}

// WithHTTPClient configures a Maps API client with a http.Client to make requests over.
func WithHTTPClient(c *http.Client) ClientOption {
	return func(client *Client) error {
		if _, ok := c.Transport.(*transport); !ok {
			t := c.Transport
			if t != nil {
				c.Transport = &transport{Base: t}
			} else {
				c.Transport = &transport{Base: http.DefaultTransport}
			}
		}
		client.httpClient = c
		return nil
	}
}

// WithAPIKey configures a Maps API client with an API Key
func WithAPIKey(apiKey string) ClientOption {
	return func(c *Client) error {
		c.apiKey = apiKey
		return nil
	}
}

// WithClientIDAndSignature configures a Maps API client for a Maps for Work application
// The signature is assumed to be URL modified Base64 encoded
func WithClientIDAndSignature(clientID, signature string) ClientOption {
	return func(c *Client) error {
		c.clientID = clientID
		decoded, err := base64.URLEncoding.DecodeString(signature)
		if err != nil {
			return err
		}
		c.signature = decoded
		return nil
	}
}

// WithRateLimit configures the rate limit for back end requests.
// Default is to limit to 10 requests per second.
func WithRateLimit(requestsPerSecond int) ClientOption {
	return func(c *Client) error {
		c.requestsPerSecond = requestsPerSecond
		return nil
	}
}

type apiConfig struct {
	host            string
	path            string
	acceptsClientID bool
}

type apiRequest interface {
	params() url.Values
}

func (c *Client) getJSON(ctx context.Context, config *apiConfig, apiReq apiRequest, resp interface{}) error {
	select {
	case <-ctx.Done():
		return ctx.Err()
	case <-c.rateLimiter:
		// Execute request.
	}

	host := config.host
	if c.baseURL != "" {
		host = c.baseURL
	}
	req, err := http.NewRequest("GET", host+config.path, nil)
	if err != nil {
		return err
	}
	q, err := c.generateAuthQuery(config.path, apiReq.params(), config.acceptsClientID)
	if err != nil {
		return err
	}
	req.URL.RawQuery = q
	httpResp, err := ctxhttp.Do(ctx, c.httpClient, req)
	if err != nil {
		return err
	}
	defer httpResp.Body.Close()

	return json.NewDecoder(httpResp.Body).Decode(resp)
}

func (c *Client) generateAuthQuery(path string, q url.Values, acceptClientID bool) (string, error) {
	if c.apiKey != "" {
		q.Set("key", c.apiKey)
		return q.Encode(), nil
	}
	if acceptClientID {
		return internal.SignURL(path, c.clientID, c.signature, q)
	}
	return "", errors.New("maps: API Key missing")
}

// commonResponse contains the common response fields to most API calls inside
// the Google Maps APIs. This is used internally.
type commonResponse struct {
	// Status contains the status of the request, and may contain debugging
	// information to help you track down why the call failed.
	Status string `json:"status"`

	// ErrorMessage is the explanatory field added when Status is an error.
	ErrorMessage string `json:"error_message"`
}

// StatusError returns an error iff this object has a non-OK Status.
func (c *commonResponse) StatusError() error {
	if c.Status != "OK" {
		return fmt.Errorf("maps: %s - %s", c.Status, c.ErrorMessage)
	}
	return nil
}
<|endoftext|>


<commit_before>// Copyright 2012 Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.

package elastic

import (
	"errors"
	"log"
	"net/http"
	"net/url"
	"sync"
	"time"
)

const (
	// Current version.
	Version = "1.3.1"

	// defaultUrl to be used as base for Elasticsearch requests.
	defaultUrl = "http://localhost:9200"

	// pingDuration is the time to periodically check the Elasticsearch URLs.
	pingDuration = 60 * time.Second
)

var (
	// ErrNoClient is raised when no active Elasticsearch client is available.
	ErrNoClient = errors.New("no active client")
)

// Client is an ElasticSearch client. Create one by calling NewClient.
type Client struct {
	urls []string // urls is a list of all clients for ElasticSearch queries

	c *http.Client // c is the net/http Client to use for requests

	mu        sync.Mutex // mutex for the next two fields
	activeUrl string     // currently active connection url
	hasActive bool       // true if we have an active connection
}

// Creates a new client to work with ElasticSearch.
func NewClient(client *http.Client, urls ...string) (*Client, error) {
	if client == nil {
		return nil, errors.New("client is nil")
	}
	c := &Client{c: client}
	switch len(urls) {
	case 0:
		c.urls = make([]string, 1)
		c.urls[0] = defaultUrl
	case 1:
		c.urls = make([]string, 1)
		c.urls[0] = urls[0]
	default:
		c.urls = urls
	}
	c.pingUrls()
	go c.pinger() // start goroutine periodically ping all clients
	return c, nil
}

// NewRequest creates a new request with the given method and prepends
// the base URL to the path. If no active connection to Elasticsearch
// is available, ErrNoClient is returned.
func (c *Client) NewRequest(method, path string) (*Request, error) {
	if !c.hasActive {
		return nil, ErrNoClient
	}
	return NewRequest(method, c.activeUrl+path)
}

// pinger periodically runs pingUrls.
func (c *Client) pinger() {
	ticker := time.NewTicker(pingDuration)
	for {
		select {
		case <-ticker.C:
			c.pingUrls()
		}
	}
}

// pingUrls iterates through all client URLs. It checks if the client
// is available. It takes the first one available and saves its URL
// in activeUrl. If no client is available, hasActive is set to false
// and NewRequest will fail.
func (c *Client) pingUrls() {
	for _, url_ := range c.urls {
		params := make(url.Values)
		params.Set("timeout", "1")
		req, err := NewRequest("HEAD", url_+"/?"+params.Encode())
		if err == nil {
			res, err := c.c.Do((*http.Request)(req))
			if err == nil {
				defer res.Body.Close()
				if res.StatusCode == http.StatusOK {
					// Everything okay: Update activeUrl and set hasActive to true.
					c.mu.Lock()
					defer c.mu.Unlock()
					if c.activeUrl != "" && c.activeUrl != url_ {
						log.Printf("elastic: switched connection from %s to %s", c.activeUrl, url_)
					}
					c.activeUrl = url_
					c.hasActive = true
					return
				}
			} else {
				log.Printf("elastic: %v", err)
			}
		} else {
			log.Printf("elastic: %v", err)
		}
	}

	// No client available
	c.mu.Lock()
	defer c.mu.Unlock()
	c.hasActive = false
}

// CreateIndex starts the service to create a new index.
func (c *Client) CreateIndex(name string) *CreateIndexService {
	builder := NewCreateIndexService(c)
	builder.Index(name)
	return builder
}

// DeleteIndex starts the service to delete an index.
func (c *Client) DeleteIndex(name string) *DeleteIndexService {
	builder := NewDeleteIndexService(c)
	builder.Index(name)
	return builder
}

// IndexExists allows to check if an index exists.
func (c *Client) IndexExists(name string) *IndexExistsService {
	builder := NewIndexExistsService(c)
	builder.Index(name)
	return builder
}

// Index a document.
func (c *Client) Index() *IndexService {
	builder := NewIndexService(c)
	return builder
}

// Delete a document.
func (c *Client) Delete() *DeleteService {
	builder := NewDeleteService(c)
	return builder
}

// DeleteByQuery deletes documents as found by a query.
func (c *Client) DeleteByQuery() *DeleteByQueryService {
	builder := NewDeleteByQueryService(c)
	return builder
}

// Get a document.
func (c *Client) Get() *GetService {
	builder := NewGetService(c)
	return builder
}

// MultiGet retrieves multiple documents in one roundtrip.
func (c *Client) MultiGet() *MultiGetService {
	builder := NewMultiGetService(c)
	return builder
}

// Exists checks if a document exists.
func (c *Client) Exists() *ExistsService {
	builder := NewExistsService(c)
	return builder
}

// Count documents.
func (c *Client) Count(indices ...string) *CountService {
	builder := NewCountService(c)
	builder.Indices(indices...)
	return builder
}

// Search is the entry point for searches.
func (c *Client) Search(indices ...string) *SearchService {
	builder := NewSearchService(c)
	builder.Indices(indices...)
	return builder
}

// MultiSearch is the entry point for multi searches.
func (c *Client) MultiSearch() *MultiSearchService {
	return NewMultiSearchService(c)
}

// Suggest returns an interface to return suggestions.
func (c *Client) Suggest(indices ...string) *SuggestService {
	builder := NewSuggestService(c)
	builder.Indices(indices...)
	return builder
}

// Scan through documents.
func (c *Client) Scan(indices ...string) *ScanService {
	builder := NewScanService(c)
	builder.Indices(indices...)
	return builder
}

// Optimize asks Elasticsearch to optimize one or more indices.
func (c *Client) Optimize(indices ...string) *OptimizeService {
	builder := NewOptimizeService(c)
	builder.Indices(indices...)
	return builder
}

// Refresh asks Elasticsearch to refresh one or more indices.
func (c *Client) Refresh(indices ...string) *RefreshService {
	builder := NewRefreshService(c)
	builder.Indices(indices...)
	return builder
}

// Flush asks Elasticsearch to free memory from the index and
// flush data to disk.
func (c *Client) Flush() *FlushService {
	builder := NewFlushService(c)
	return builder
}

// Bulk is the entry point to mass insert/update/delete documents.
func (c *Client) Bulk() *BulkService {
	builder := NewBulkService(c)
	return builder
}

// Alias enables the caller to add and/or remove aliases.
func (c *Client) Alias() *AliasService {
	builder := NewAliasService(c)
	return builder
}

// Aliases returns aliases by index name(s).
func (c *Client) Aliases() *AliasesService {
	builder := NewAliasesService(c)
	return builder
}
<commit_msg>changed to support only a single host<commit_after>// Copyright 2012 Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.

package elastic

import (
	"errors"
	"net/http"
	"time"
)

const (
	// Version represents the current version.
	Version = "1.3.1"

	// pingDuration is the time to periodically check the Elasticsearch URLs.
	pingDuration = 60 * time.Second
)

// Client is an ElasticSearch client. Create one by calling NewClient.
type Client struct {
	c    *http.Client // c is the net/http Client to use for requests
	host string       // currently active connection url
}

// NewClient creates a new client to work with ElasticSearch.
func NewClient(client *http.Client, url string) (*Client, error) {
	if client == nil {
		return nil, errors.New("client is nil")
	}
	c := &Client{c: client}
	c.host = url
	return c, nil
}

// NewRequest creates a new request with the given method and prepends
// the base URL to the path. If no active connection to Elasticsearch
// is available, ErrNoClient is returned.
func (c *Client) NewRequest(method, path string) (*Request, error) {
	return NewRequest(method, c.host+path)
}

// CreateIndex starts the service to create a new index.
func (c *Client) CreateIndex(name string) *CreateIndexService {
	builder := NewCreateIndexService(c)
	builder.Index(name)
	return builder
}

// DeleteIndex starts the service to delete an index.
func (c *Client) DeleteIndex(name string) *DeleteIndexService {
	builder := NewDeleteIndexService(c)
	builder.Index(name)
	return builder
}

// IndexExists allows to check if an index exists.
func (c *Client) IndexExists(name string) *IndexExistsService {
	builder := NewIndexExistsService(c)
	builder.Index(name)
	return builder
}

// Index a document.
func (c *Client) Index() *IndexService {
	builder := NewIndexService(c)
	return builder
}

// Delete a document.
func (c *Client) Delete() *DeleteService {
	builder := NewDeleteService(c)
	return builder
}

// DeleteByQuery deletes documents as found by a query.
func (c *Client) DeleteByQuery() *DeleteByQueryService {
	builder := NewDeleteByQueryService(c)
	return builder
}

// Get a document.
func (c *Client) Get() *GetService {
	builder := NewGetService(c)
	return builder
}

// MultiGet retrieves multiple documents in one roundtrip.
func (c *Client) MultiGet() *MultiGetService {
	builder := NewMultiGetService(c)
	return builder
}

// Exists checks if a document exists.
func (c *Client) Exists() *ExistsService {
	builder := NewExistsService(c)
	return builder
}

// Count documents.
func (c *Client) Count(indices ...string) *CountService {
	builder := NewCountService(c)
	builder.Indices(indices...)
	return builder
}

// Search is the entry point for searches.
func (c *Client) Search(indices ...string) *SearchService {
	builder := NewSearchService(c)
	builder.Indices(indices...)
	return builder
}

// MultiSearch is the entry point for multi searches.
func (c *Client) MultiSearch() *MultiSearchService {
	return NewMultiSearchService(c)
}

// Suggest returns an interface to return suggestions.
func (c *Client) Suggest(indices ...string) *SuggestService {
	builder := NewSuggestService(c)
	builder.Indices(indices...)
	return builder
}

// Scan through documents.
func (c *Client) Scan(indices ...string) *ScanService {
	builder := NewScanService(c)
	builder.Indices(indices...)
	return builder
}

// Optimize asks Elasticsearch to optimize one or more indices.
func (c *Client) Optimize(indices ...string) *OptimizeService {
	builder := NewOptimizeService(c)
	builder.Indices(indices...)
	return builder
}

// Refresh asks Elasticsearch to refresh one or more indices.
func (c *Client) Refresh(indices ...string) *RefreshService {
	builder := NewRefreshService(c)
	builder.Indices(indices...)
	return builder
}

// Flush asks Elasticsearch to free memory from the index and
// flush data to disk.
func (c *Client) Flush() *FlushService {
	builder := NewFlushService(c)
	return builder
}

// Bulk is the entry point to mass insert/update/delete documents.
func (c *Client) Bulk() *BulkService {
	builder := NewBulkService(c)
	return builder
}

// Alias enables the caller to add and/or remove aliases.
func (c *Client) Alias() *AliasService {
	builder := NewAliasService(c)
	return builder
}

// Aliases returns aliases by index name(s).
func (c *Client) Aliases() *AliasesService {
	builder := NewAliasesService(c)
	return builder
}
<|endoftext|>