<commit_before>/*
Copyright 2016 The Kubernetes Authors 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 client

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

	"github.com/kubernetes/helm/pkg/version"
)

const (
	// DefaultHTTPTimeout is the default HTTP timeout.
	DefaultHTTPTimeout = time.Second * 10
	// DefaultHTTPProtocol is the default HTTP Protocol (http, https).
	DefaultHTTPProtocol = "http"
)

// Client is a DM client.
type Client struct {
	// Timeout on HTTP connections.
	HTTPTimeout time.Duration
	// Transport
	Transport http.RoundTripper
	// Debug enables http logging
	Debug bool
	// Base URL for remote service
	baseURL *url.URL
}

// NewClient creates a new DM client. Host name is required.
func NewClient(host string) *Client {
	url, _ := DefaultServerURL(host)

	return &Client{
		HTTPTimeout: DefaultHTTPTimeout,
		baseURL:     url,
		Transport:   http.DefaultTransport,
	}
}

// SetDebug enables debug mode which logs http
func (c *Client) SetDebug(enable bool) *Client {
	c.Debug = enable
	return c
}

// transport wraps client transport if debug is enabled
func (c *Client) transport() http.RoundTripper {
	if c.Debug {
		return NewDebugTransport(c.Transport)
	}
	return c.Transport
}

// SetTransport sets a custom Transport. Defaults to http.DefaultTransport
func (c *Client) SetTransport(tr http.RoundTripper) *Client {
	c.Transport = tr
	return c
}

// SetTimeout sets a timeout for http connections
func (c *Client) SetTimeout(seconds int) *Client {
	c.HTTPTimeout = time.Duration(seconds) * time.Second
	return c
}

// url constructs the URL.
func (c *Client) url(rawurl string) (string, error) {
	u, err := url.Parse(rawurl)
	if err != nil {
		return "", err
	}
	return c.baseURL.ResolveReference(u).String(), nil
}

func (c *Client) agent() string {
	return fmt.Sprintf("helm/%s", version.Version)
}

// Get calls GET on an endpoint and decodes the response
func (c *Client) Get(endpoint string, v interface{}) (*Response, error) {
	return c.Exec(c.NewRequest("GET", endpoint, nil), &v)
}

// Post calls POST on an endpoint and decodes the response
func (c *Client) Post(endpoint string, payload, v interface{}) (*Response, error) {
	return c.Exec(c.NewRequest("POST", endpoint, payload), &v)
}

// Delete calls DELETE on an endpoint and decodes the response
func (c *Client) Delete(endpoint string, v interface{}) (*Response, error) {
	return c.Exec(c.NewRequest("DELETE", endpoint, nil), &v)
}

// NewRequest creates a new client request
func (c *Client) NewRequest(method, endpoint string, payload interface{}) *Request {
	u, err := c.url(endpoint)
	if err != nil {
		return &Request{err: err}
	}

	body := prepareBody(payload)
	req, err := http.NewRequest(method, u, body)

	req.Header.Set("User-Agent", c.agent())
	req.Header.Set("Accept", "application/json")

	return &Request{req, err}
}

func prepareBody(payload interface{}) io.Reader {
	var body io.Reader
	switch t := payload.(type) {
	default:
		//FIXME: panic is only for development
		panic(fmt.Sprintf("unexpected type %T\n", t))
	case io.Reader:
		body = t
	case []byte:
		body = bytes.NewBuffer(t)
	case nil:
	}
	return body
}

// Exec sends a request and decodes the response
func (c *Client) Exec(req *Request, v interface{}) (*Response, error) {
	return c.Result(c.Do(req), &v)
}

// Result checks status code and decodes a response body
func (c *Client) Result(resp *Response, v interface{}) (*Response, error) {
	switch {
	case resp.err != nil:
		return resp, resp
	case resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices:
		return resp, resp.HTTPError()
	}

	return resp, decodeResponse(resp, v)
}

// Do send a request and returns a response
func (c *Client) Do(req *Request) *Response {
	if req.err != nil {
		return &Response{err: req}
	}

	client := &http.Client{
		Timeout:   c.HTTPTimeout,
		Transport: c.transport(),
	}
	resp, err := client.Do(req.Request)
	return &Response{resp, err}
}

// CallService is a low-level function for making an API call.
//
// This calls the service and then unmarshals the returned data into dest.
func (c *Client) CallService(path, method string, dest interface{}, reader io.Reader) error {
	u, err := c.url(path)
	if err != nil {
		return err
	}

	resp, err := c.callHTTP(u, method, reader)
	if err != nil {
		return err
	}
	if err := json.Unmarshal([]byte(resp), dest); err != nil {
		return fmt.Errorf("Failed to parse JSON response from service: %s", resp)
	}
	return nil
}

// callHTTP is  a low-level primitive for executing HTTP operations.
func (c *Client) callHTTP(path, method string, reader io.Reader) (string, error) {
	request, err := http.NewRequest(method, path, reader)

	request.Header.Set("User-Agent", c.agent())
	request.Header.Set("Accept", "application/json")
	request.Header.Add("Content-Type", "application/json")

	client := &http.Client{
		Timeout:   c.HTTPTimeout,
		Transport: c.transport(),
	}

	response, err := client.Do(request)
	if err != nil {
		return "", err
	}

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

	s := response.StatusCode
	if s < http.StatusOK || s >= http.StatusMultipleChoices {
		return "", &HTTPError{StatusCode: s, Message: string(body), URL: request.URL}
	}

	return string(body), nil
}

// DefaultServerURL converts a host, host:port, or URL string to the default base server API path
// to use with a Client
func DefaultServerURL(host string) (*url.URL, error) {
	if host == "" {
		return nil, fmt.Errorf("host must be a URL or a host:port pair")
	}
	base := host
	hostURL, err := url.Parse(base)
	if err != nil {
		return nil, err
	}
	if hostURL.Scheme == "" {
		hostURL, err = url.Parse(DefaultHTTPProtocol + "://" + base)
		if err != nil {
			return nil, err
		}
	}
	if len(hostURL.Path) > 0 && !strings.HasSuffix(hostURL.Path, "/") {
		hostURL.Path = hostURL.Path + "/"
	}

	return hostURL, nil
}

// Request wraps http.Request to include error
type Request struct {
	*http.Request
	err error
}

// Error implements the error interface.
func (r *Request) Error() string {
	return fmt.Sprint("%s", r.err)
}

// Response wraps http.Response to include error
type Response struct {
	*http.Response
	err error
}

// Error implements the error interface.
func (r *Response) Error() string {
	return fmt.Sprint("%s", r.err)
}

// HTTPError creates a new HTTPError from response
func (r *Response) HTTPError() error {
	defer r.Body.Close()
	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		return err
	}
	return &HTTPError{
		StatusCode: r.StatusCode,
		Message:    string(body),
		URL:        r.Request.URL,
	}
}

// HTTPError is an error caused by an unexpected HTTP status code.
//
// The StatusCode will not necessarily be a 4xx or 5xx. Any unexpected code
// may be returned.
type HTTPError struct {
	StatusCode int
	Message    string
	URL        *url.URL
}

// Error implements the error interface.
func (e *HTTPError) Error() string {
	return e.Message
}

// String implmenets the io.Stringer interface.
func (e *HTTPError) String() string {
	return e.Error()
}

func decodeResponse(resp *Response, v interface{}) error {
	defer resp.Body.Close()
	if resp.Body == nil {
		return nil
	}
	if err := json.NewDecoder(resp.Body).Decode(v); err != nil {
		return fmt.Errorf("Failed to parse JSON response from service")
	}
	return nil
}
<commit_msg>ref(client): use anonymous embedded error<commit_after>/*
Copyright 2016 The Kubernetes Authors 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 client

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

	"github.com/kubernetes/helm/pkg/version"
)

const (
	// DefaultHTTPTimeout is the default HTTP timeout.
	DefaultHTTPTimeout = time.Second * 10
	// DefaultHTTPProtocol is the default HTTP Protocol (http, https).
	DefaultHTTPProtocol = "http"
)

// Client is a DM client.
type Client struct {
	// Timeout on HTTP connections.
	HTTPTimeout time.Duration
	// Transport
	Transport http.RoundTripper
	// Debug enables http logging
	Debug bool
	// Base URL for remote service
	baseURL *url.URL
}

// NewClient creates a new DM client. Host name is required.
func NewClient(host string) *Client {
	url, _ := DefaultServerURL(host)

	return &Client{
		HTTPTimeout: DefaultHTTPTimeout,
		baseURL:     url,
		Transport:   http.DefaultTransport,
	}
}

// SetDebug enables debug mode which logs http
func (c *Client) SetDebug(enable bool) *Client {
	c.Debug = enable
	return c
}

// transport wraps client transport if debug is enabled
func (c *Client) transport() http.RoundTripper {
	if c.Debug {
		return NewDebugTransport(c.Transport)
	}
	return c.Transport
}

// SetTransport sets a custom Transport. Defaults to http.DefaultTransport
func (c *Client) SetTransport(tr http.RoundTripper) *Client {
	c.Transport = tr
	return c
}

// SetTimeout sets a timeout for http connections
func (c *Client) SetTimeout(seconds int) *Client {
	c.HTTPTimeout = time.Duration(seconds) * time.Second
	return c
}

// url constructs the URL.
func (c *Client) url(rawurl string) (string, error) {
	u, err := url.Parse(rawurl)
	if err != nil {
		return "", err
	}
	return c.baseURL.ResolveReference(u).String(), nil
}

func (c *Client) agent() string {
	return fmt.Sprintf("helm/%s", version.Version)
}

// Get calls GET on an endpoint and decodes the response
func (c *Client) Get(endpoint string, v interface{}) (*Response, error) {
	return c.Exec(c.NewRequest("GET", endpoint, nil), &v)
}

// Post calls POST on an endpoint and decodes the response
func (c *Client) Post(endpoint string, payload, v interface{}) (*Response, error) {
	return c.Exec(c.NewRequest("POST", endpoint, payload), &v)
}

// Delete calls DELETE on an endpoint and decodes the response
func (c *Client) Delete(endpoint string, v interface{}) (*Response, error) {
	return c.Exec(c.NewRequest("DELETE", endpoint, nil), &v)
}

// NewRequest creates a new client request
func (c *Client) NewRequest(method, endpoint string, payload interface{}) *Request {
	u, err := c.url(endpoint)
	if err != nil {
		return &Request{error: err}
	}

	body := prepareBody(payload)
	req, err := http.NewRequest(method, u, body)

	req.Header.Set("User-Agent", c.agent())
	req.Header.Set("Accept", "application/json")

	return &Request{req, err}
}

func prepareBody(payload interface{}) io.Reader {
	var body io.Reader
	switch t := payload.(type) {
	default:
		//FIXME: panic is only for development
		panic(fmt.Sprintf("unexpected type %T\n", t))
	case io.Reader:
		body = t
	case []byte:
		body = bytes.NewBuffer(t)
	case nil:
	}
	return body
}

// Exec sends a request and decodes the response
func (c *Client) Exec(req *Request, v interface{}) (*Response, error) {
	return c.Result(c.Do(req), &v)
}

// Result checks status code and decodes a response body
func (c *Client) Result(resp *Response, v interface{}) (*Response, error) {
	switch {
	case resp.error != nil:
		return resp, resp
	case resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices:
		return resp, resp.HTTPError()
	}

	return resp, decodeResponse(resp, v)
}

// Do send a request and returns a response
func (c *Client) Do(req *Request) *Response {
	if req.error != nil {
		return &Response{error: req}
	}

	client := &http.Client{
		Timeout:   c.HTTPTimeout,
		Transport: c.transport(),
	}
	resp, err := client.Do(req.Request)
	return &Response{resp, err}
}

// CallService is a low-level function for making an API call.
//
// This calls the service and then unmarshals the returned data into dest.
func (c *Client) CallService(path, method string, dest interface{}, reader io.Reader) error {
	u, err := c.url(path)
	if err != nil {
		return err
	}

	resp, err := c.callHTTP(u, method, reader)
	if err != nil {
		return err
	}
	if err := json.Unmarshal([]byte(resp), dest); err != nil {
		return fmt.Errorf("Failed to parse JSON response from service: %s", resp)
	}
	return nil
}

// callHTTP is  a low-level primitive for executing HTTP operations.
func (c *Client) callHTTP(path, method string, reader io.Reader) (string, error) {
	request, err := http.NewRequest(method, path, reader)

	request.Header.Set("User-Agent", c.agent())
	request.Header.Set("Accept", "application/json")
	request.Header.Add("Content-Type", "application/json")

	client := &http.Client{
		Timeout:   c.HTTPTimeout,
		Transport: c.transport(),
	}

	response, err := client.Do(request)
	if err != nil {
		return "", err
	}

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

	s := response.StatusCode
	if s < http.StatusOK || s >= http.StatusMultipleChoices {
		return "", &HTTPError{StatusCode: s, Message: string(body), URL: request.URL}
	}

	return string(body), nil
}

// DefaultServerURL converts a host, host:port, or URL string to the default base server API path
// to use with a Client
func DefaultServerURL(host string) (*url.URL, error) {
	if host == "" {
		return nil, fmt.Errorf("host must be a URL or a host:port pair")
	}
	base := host
	hostURL, err := url.Parse(base)
	if err != nil {
		return nil, err
	}
	if hostURL.Scheme == "" {
		hostURL, err = url.Parse(DefaultHTTPProtocol + "://" + base)
		if err != nil {
			return nil, err
		}
	}
	if len(hostURL.Path) > 0 && !strings.HasSuffix(hostURL.Path, "/") {
		hostURL.Path = hostURL.Path + "/"
	}

	return hostURL, nil
}

// Request wraps http.Request to include error
type Request struct {
	*http.Request
	error
}

// Response wraps http.Response to include error
type Response struct {
	*http.Response
	error
}

// HTTPError creates a new HTTPError from response
func (r *Response) HTTPError() error {
	defer r.Body.Close()
	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		return err
	}
	return &HTTPError{
		StatusCode: r.StatusCode,
		Message:    string(body),
		URL:        r.Request.URL,
	}
}

// HTTPError is an error caused by an unexpected HTTP status code.
//
// The StatusCode will not necessarily be a 4xx or 5xx. Any unexpected code
// may be returned.
type HTTPError struct {
	StatusCode int
	Message    string
	URL        *url.URL
}

// Error implements the error interface.
func (e *HTTPError) Error() string {
	return e.Message
}

// String implmenets the io.Stringer interface.
func (e *HTTPError) String() string {
	return e.Error()
}

func decodeResponse(resp *Response, v interface{}) error {
	defer resp.Body.Close()
	if resp.Body == nil {
		return nil
	}
	if err := json.NewDecoder(resp.Body).Decode(v); err != nil {
		return fmt.Errorf("Failed to parse JSON response from service")
	}
	return nil
}
<|endoftext|>


<commit_before>// Copyright 2017 The OpenPitrix Authors. All rights reserved.
// Use of this source code is governed by a Apache license
// that can be found in the LICENSE file.

package repo

import (
	"fmt"
	"log"
	"net"

	"github.com/golang/protobuf/proto"
	pbempty "github.com/golang/protobuf/ptypes/empty"
	context "golang.org/x/net/context"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	_ "google.golang.org/grpc/grpclog/glogger"

	"openpitrix.io/openpitrix/pkg/config"
	db "openpitrix.io/openpitrix/pkg/db/repo"
	"openpitrix.io/openpitrix/pkg/logger"
	pb "openpitrix.io/openpitrix/pkg/service.pb"
)

func Main(cfg *config.Config) {
	cfg.ActiveGlogFlags()

	logger.Printf("Database %s://tcp(%s:%d)/%s\n", cfg.DB.Type, cfg.DB.Host, cfg.DB.Port, cfg.DB.DbName)
	logger.Printf("Repo service start http://%s:%d\n", cfg.Repo.Host, cfg.Repo.Port)

	lis, err := net.Listen("tcp", fmt.Sprintf(":%d", cfg.Repo.Port))
	if err != nil {
		log.Fatalf("failed to listen: %v", err)
	}

	grpcServer := grpc.NewServer()
	pb.RegisterRepoServiceServer(grpcServer, NewRepoServer(&cfg.DB))

	if err = grpcServer.Serve(lis); err != nil {
		log.Fatal(err)
	}
}

type RepoServer struct {
	db *db.RepoDatabase
}

func NewRepoServer(cfg *config.Database) *RepoServer {
	db, err := db.OpenRepoDatabase(cfg)
	if err != nil {
		log.Fatal(err)
	}

	return &RepoServer{
		db: db,
	}
}

func (p *RepoServer) GetRepo(ctx context.Context, args *pb.RepoId) (reply *pb.Repo, err error) {
	result, err := p.db.GetRepo(ctx, args.GetId())
	if err != nil {
		return nil, grpc.Errorf(codes.Internal, "GetRepo: %v", err)
	}
	if result == nil {
		return nil, grpc.Errorf(codes.NotFound, "Repo Id %s not exist", args.GetId())
	}
	reply = To_proto_Repo(nil, result)
	return
}

func (p *RepoServer) GetRepoList(ctx context.Context, args *pb.RepoListRequest) (reply *pb.RepoListResponse, err error) {
	result, err := p.db.GetRepoList(ctx)
	if err != nil {
		return nil, grpc.Errorf(codes.Internal, "GetRepoList: %v", err)
	}

	items := To_proto_RepoList(result, int(args.GetPageNumber()), int(args.GetPageSize()))
	reply = &pb.RepoListResponse{
		Items:       items,
		TotalItems:  proto.Int32(int32(len(result))),
		TotalPages:  proto.Int32(int32((len(result) + int(args.GetPageSize()) - 1) / int(args.GetPageSize()))),
		PageSize:    proto.Int32(args.GetPageSize()),
		CurrentPage: proto.Int32(int32(len(result)/int(args.GetPageSize())) + 1),
	}

	return
}
func (p *RepoServer) CreateRepo(ctx context.Context, args *pb.Repo) (reply *pbempty.Empty, err error) {
	err = p.db.CreateRepo(ctx, To_database_Repo(nil, args))
	if err != nil {
		return nil, grpc.Errorf(codes.Internal, "CreateRepo: %v", err)
	}

	reply = &pbempty.Empty{}
	return
}

func (p *RepoServer) UpdateRepo(ctx context.Context, args *pb.Repo) (reply *pbempty.Empty, err error) {
	err = p.db.UpdateRepo(ctx, To_database_Repo(nil, args))
	if err != nil {
		return nil, grpc.Errorf(codes.Internal, "UpdateRepo: %v", err)
	}

	reply = &pbempty.Empty{}
	return
}

func (p *RepoServer) DeleteRepo(ctx context.Context, args *pb.RepoId) (reply *pbempty.Empty, err error) {
	err = p.db.DeleteRepo(ctx, args.GetId())
	if err != nil {
		return nil, grpc.Errorf(codes.Internal, "DeleteRepo: %v", err)
	}

	reply = &pbempty.Empty{}
	return
}
<commit_msg>Update main.go<commit_after>// Copyright 2017 The OpenPitrix Authors. All rights reserved.
// Use of this source code is governed by a Apache license
// that can be found in the LICENSE file.

package repo

import (
	"fmt"
	"log"
	"net"

	"github.com/golang/protobuf/proto"
	pbempty "github.com/golang/protobuf/ptypes/empty"
	context "golang.org/x/net/context"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	_ "google.golang.org/grpc/grpclog/glogger"

	"openpitrix.io/openpitrix/pkg/config"
	db "openpitrix.io/openpitrix/pkg/db/repo"
	"openpitrix.io/openpitrix/pkg/logger"
	pb "openpitrix.io/openpitrix/pkg/service.pb"
)

func Main(cfg *config.Config) {
	cfg.ActiveGlogFlags()

	logger.Printf("Database %s://tcp(%s:%d)/%s\n", cfg.DB.Type, cfg.DB.Host, cfg.DB.Port, cfg.DB.DbName)
	logger.Printf("Repo service start http://%s:%d\n", cfg.Repo.Host, cfg.Repo.Port)

	lis, err := net.Listen("tcp", fmt.Sprintf(":%d", cfg.Repo.Port))
	if err != nil {
		log.Fatalf("failed to listen: %v", err)
	}

	grpcServer := grpc.NewServer()
	pb.RegisterRepoServiceServer(grpcServer, NewRepoServer(&cfg.DB))

	if err = grpcServer.Serve(lis); err != nil {
		log.Fatal(err)
	}
}

type RepoServer struct {
	db *db.RepoDatabase
}

func NewRepoServer(cfg *config.Database) *RepoServer {
	db, err := db.OpenRepoDatabase(cfg)
	if err != nil {
		log.Fatal(err)
	}

	return &RepoServer{
		db: db,
	}
}

func (p *RepoServer) GetRepo(ctx context.Context, args *pb.RepoId) (reply *pb.Repo, err error) {
	result, err := p.db.GetRepo(ctx, args.GetId())
	if err != nil {
		return nil, grpc.Errorf(codes.Internal, "GetRepo: %v", err)
	}
	if result == nil {
		return nil, grpc.Errorf(codes.NotFound, "Repo Id %s does not exist", args.GetId())
	}
	reply = To_proto_Repo(nil, result)
	return
}

func (p *RepoServer) GetRepoList(ctx context.Context, args *pb.RepoListRequest) (reply *pb.RepoListResponse, err error) {
	result, err := p.db.GetRepoList(ctx)
	if err != nil {
		return nil, grpc.Errorf(codes.Internal, "GetRepoList: %v", err)
	}

	items := To_proto_RepoList(result, int(args.GetPageNumber()), int(args.GetPageSize()))
	reply = &pb.RepoListResponse{
		Items:       items,
		TotalItems:  proto.Int32(int32(len(result))),
		TotalPages:  proto.Int32(int32((len(result) + int(args.GetPageSize()) - 1) / int(args.GetPageSize()))),
		PageSize:    proto.Int32(args.GetPageSize()),
		CurrentPage: proto.Int32(int32(len(result)/int(args.GetPageSize())) + 1),
	}

	return
}
func (p *RepoServer) CreateRepo(ctx context.Context, args *pb.Repo) (reply *pbempty.Empty, err error) {
	err = p.db.CreateRepo(ctx, To_database_Repo(nil, args))
	if err != nil {
		return nil, grpc.Errorf(codes.Internal, "CreateRepo: %v", err)
	}

	reply = &pbempty.Empty{}
	return
}

func (p *RepoServer) UpdateRepo(ctx context.Context, args *pb.Repo) (reply *pbempty.Empty, err error) {
	err = p.db.UpdateRepo(ctx, To_database_Repo(nil, args))
	if err != nil {
		return nil, grpc.Errorf(codes.Internal, "UpdateRepo: %v", err)
	}

	reply = &pbempty.Empty{}
	return
}

func (p *RepoServer) DeleteRepo(ctx context.Context, args *pb.RepoId) (reply *pbempty.Empty, err error) {
	err = p.db.DeleteRepo(ctx, args.GetId())
	if err != nil {
		return nil, grpc.Errorf(codes.Internal, "DeleteRepo: %v", err)
	}

	reply = &pbempty.Empty{}
	return
}
<|endoftext|>


<commit_before>package humanize

import (
	"fmt"
	"math"
	"strconv"
	"strings"
	"time"
)

func TimeSince(a time.Time) string {
	return formatDiff(diff(a, time.Now()))
}

func StringToSeconds(s string) (int, error) {
	if strings.HasSuffix(s, "m") {
		num := strings.TrimSuffix("m", s)
		integer, err := strconv.Atoi(num)

		return integer * 60, err
	}

	if strings.HasSuffix(s, "s") {
		num := strings.TrimSuffix("s", s)
		integer, err := strconv.Atoi(num)

		return integer, err
	}

	integer, err := strconv.Atoi(s)

	return integer, err
}

func SecondsToString(s int) string {
	if s < 60 {
		return fmt.Sprint(s)
	}
	if s == 60 {
		return "1m"
	}
	if s%60 == 0 {
		return fmt.Sprint(s / 60)
	}

	floored := math.Floor(float64(s) / 60)
	rest := float64(s) - (floored * 60)

	return fmt.Sprintf("%fm %fs", floored, rest)
}

func formatDiff(years, months, days, hours, mins, secs int) string {
	since := ""
	if years > 0 {
		switch years {
		case 1:
			since += fmt.Sprintf("%d year ", years)
		default:
			since += fmt.Sprintf("%d years ", years)
		}
	}
	if months > 0 {
		switch months {
		case 1:
			since += fmt.Sprintf("%d month ", months)
		default:
			since += fmt.Sprintf("%d months ", months)
		}
	}
	if days > 0 {
		switch days {
		case 1:
			since += fmt.Sprintf("%d day ", days)
		default:
			since += fmt.Sprintf("%d days ", days)
		}
	}
	if hours > 0 {
		switch hours {
		case 1:
			since += fmt.Sprintf("%d hour ", hours)
		default:
			since += fmt.Sprintf("%d hours ", hours)
		}
	}
	if mins > 0 && days == 0 && months == 0 && years == 0 {
		switch mins {
		case 1:
			since += fmt.Sprintf("%d min ", mins)
		default:
			since += fmt.Sprintf("%d mins ", mins)
		}
	}
	if secs > 0 && days == 0 && months == 0 && years == 0 && hours == 0 {
		switch secs {
		case 1:
			since += fmt.Sprintf("%d sec ", secs)
		default:
			since += fmt.Sprintf("%d secs ", secs)
		}
	}
	return strings.TrimSpace(since)
}

func diff(a, b time.Time) (year, month, day, hour, min, sec int) {
	if a.After(b) {
		a, b = b, a
	}
	y1, M1, d1 := a.Date()
	y2, M2, d2 := b.Date()

	h1, m1, s1 := a.Clock()
	h2, m2, s2 := b.Clock()

	year = int(y2 - y1)
	month = int(M2 - M1)
	day = int(d2 - d1)
	hour = int(h2 - h1)
	min = int(m2 - m1)
	sec = int(s2 - s1)

	// Normalize negative values
	if sec < 0 {
		sec += 60
		min--
	}
	if min < 0 {
		min += 60
		hour--
	}
	if hour < 0 {
		hour += 24
		day--
	}
	if day < 0 {
		// days in month:
		t := time.Date(y1, M1, 32, 0, 0, 0, 0, time.UTC)
		day += 32 - t.Day()
		month--
	}
	if month < 0 {
		month += 12
		year--
	}
	return
}
<commit_msg>fix time parsing<commit_after>package humanize

import (
	"fmt"
	"math"
	"strconv"
	"strings"
	"time"
)

func TimeSince(a time.Time) string {
	return formatDiff(diff(a, time.Now()))
}

func StringToSeconds(s string) (int, error) {
	if strings.HasSuffix(s, "m") {
		num := strings.TrimSuffix(s, "m")
		integer, err := strconv.Atoi(num)

		return integer * 60, err
	}

	if strings.HasSuffix(s, "s") {
		num := strings.TrimSuffix(s, "s")
		integer, err := strconv.Atoi(num)

		return integer, err
	}

	integer, err := strconv.Atoi(s)

	return integer, err
}

func SecondsToString(s int) string {
	if s < 60 {
		return fmt.Sprint(s)
	}
	if s%60 == 0 {
		return fmt.Sprintf("%dm", s/60)
	}

	floored := math.Floor(float64(s) / 60)
	rest := float64(s) - (floored * 60)

	return fmt.Sprintf("%fm %fs", floored, rest)
}

func formatDiff(years, months, days, hours, mins, secs int) string {
	since := ""
	if years > 0 {
		switch years {
		case 1:
			since += fmt.Sprintf("%d year ", years)
		default:
			since += fmt.Sprintf("%d years ", years)
		}
	}
	if months > 0 {
		switch months {
		case 1:
			since += fmt.Sprintf("%d month ", months)
		default:
			since += fmt.Sprintf("%d months ", months)
		}
	}
	if days > 0 {
		switch days {
		case 1:
			since += fmt.Sprintf("%d day ", days)
		default:
			since += fmt.Sprintf("%d days ", days)
		}
	}
	if hours > 0 {
		switch hours {
		case 1:
			since += fmt.Sprintf("%d hour ", hours)
		default:
			since += fmt.Sprintf("%d hours ", hours)
		}
	}
	if mins > 0 && days == 0 && months == 0 && years == 0 {
		switch mins {
		case 1:
			since += fmt.Sprintf("%d min ", mins)
		default:
			since += fmt.Sprintf("%d mins ", mins)
		}
	}
	if secs > 0 && days == 0 && months == 0 && years == 0 && hours == 0 {
		switch secs {
		case 1:
			since += fmt.Sprintf("%d sec ", secs)
		default:
			since += fmt.Sprintf("%d secs ", secs)
		}
	}
	return strings.TrimSpace(since)
}

func diff(a, b time.Time) (year, month, day, hour, min, sec int) {
	if a.After(b) {
		a, b = b, a
	}
	y1, M1, d1 := a.Date()
	y2, M2, d2 := b.Date()

	h1, m1, s1 := a.Clock()
	h2, m2, s2 := b.Clock()

	year = int(y2 - y1)
	month = int(M2 - M1)
	day = int(d2 - d1)
	hour = int(h2 - h1)
	min = int(m2 - m1)
	sec = int(s2 - s1)

	// Normalize negative values
	if sec < 0 {
		sec += 60
		min--
	}
	if min < 0 {
		min += 60
		hour--
	}
	if hour < 0 {
		hour += 24
		day--
	}
	if day < 0 {
		// days in month:
		t := time.Date(y1, M1, 32, 0, 0, 0, 0, time.UTC)
		day += 32 - t.Day()
		month--
	}
	if month < 0 {
		month += 12
		year--
	}
	return
}
<|endoftext|>


<commit_before>// Copyright 2016-2017 Authors of Cilium
//
// 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 labels

import (
	"encoding/json"
	"fmt"
	"os"
	"regexp"
	"strings"
	"sync"

	"github.com/cilium/cilium/common"

	log "github.com/sirupsen/logrus"
)

var (
	validLabelPrefixesMU sync.RWMutex
	validLabelPrefixes   *labelPrefixCfg // Label prefixes used to filter from all labels
)

const (
	// LPCfgFileVersion represents the version of a Label Prefix Configuration File
	LPCfgFileVersion = 1
)

// LabelPrefix is the cilium's representation of a container label.
type LabelPrefix struct {
	// Ignore if true will cause this prefix to be ignored insted of being accepted
	Ignore bool   `json:"invert"`
	Prefix string `json:"prefix"`
	Source string `json:"source"`
	expr   *regexp.Regexp
}

// String returns a human readable representation of the LabelPrefix
func (p LabelPrefix) String() string {
	s := fmt.Sprintf("%s:%s", p.Source, p.Prefix)
	if p.Ignore {
		s = "!" + s
	}

	return s
}

// matches returns true and the length of the matched section if the label is
// matched by the LabelPrefix. The Ignore flag has no effect at this point.
func (p LabelPrefix) matches(l *Label) (bool, int) {
	if p.Source != "" && p.Source != l.Source {
		return false, 0
	}

	// If no regular expression is available, fall back to prefix matching
	if p.expr == nil {
		return strings.HasPrefix(l.Key, p.Prefix), len(p.Prefix)
	}

	res := p.expr.FindStringIndex(l.Key)

	// No match if regexp was not found
	if res == nil {
		return false, 0
	}

	// Otherwise match if match was found at start of key
	return res[0] == 0, res[1]
}

// parseLabelPrefix returns a LabelPrefix created from the string label parameter.
func parseLabelPrefix(label string) (*LabelPrefix, error) {
	labelPrefix := LabelPrefix{}
	t := strings.SplitN(label, ":", 2)
	if len(t) > 1 {
		labelPrefix.Source = t[0]
		labelPrefix.Prefix = t[1]
	} else {
		labelPrefix.Prefix = label
	}

	if labelPrefix.Prefix[0] == '!' {
		labelPrefix.Ignore = true
		labelPrefix.Prefix = labelPrefix.Prefix[1:]
	}

	r, err := regexp.Compile(labelPrefix.Prefix)
	if err != nil {
		return nil, fmt.Errorf("Unable to compile regexp: %s", err)
	}
	labelPrefix.expr = r

	return &labelPrefix, nil
}

// ParseLabelPrefixCfg parses valid label prefixes from a file and from a slice
// of valid prefixes. Both are optional. If both are provided, both list are
// appended together.
func ParseLabelPrefixCfg(prefixes []string, file string) error {
	cfg, err := readLabelPrefixCfgFrom(file)
	if err != nil {
		return fmt.Errorf("Unable to read label prefix file: %s", err)
	}

	for _, label := range prefixes {
		p, err := parseLabelPrefix(label)
		if err != nil {
			return err
		}

		if !p.Ignore {
			cfg.whitelist = true
		}

		cfg.LabelPrefixes = append(cfg.LabelPrefixes, p)
	}

	validLabelPrefixes = cfg

	log.Info("Valid label prefix configuration:")
	for _, l := range validLabelPrefixes.LabelPrefixes {
		log.Infof(" - %s", l)
	}

	return nil
}

// labelPrefixCfg is the label prefix configuration to filter labels of started
// containers.
type labelPrefixCfg struct {
	Version       int            `json:"version"`
	LabelPrefixes []*LabelPrefix `json:"valid-prefixes"`
	// whitelist if true, indicates that an inclusive rule has to match
	// in order for the label to be considered
	whitelist bool
}

// defaultLabelPrefixCfg returns a default LabelPrefixCfg using the latest
// LPCfgFileVersion
func defaultLabelPrefixCfg() *labelPrefixCfg {
	cfg := &labelPrefixCfg{
		Version:       LPCfgFileVersion,
		LabelPrefixes: []*LabelPrefix{},
	}

	expressions := []string{
		K8sNamespaceLabel,                                          // include io.kubernetes.pod.namspace
		"!io.kubernetes",                                           // ignore all other io.kubernetes labels
		"!.*kubernetes.io",                                         // ignore all other kubernetes.io labels (annotation.*.k8s.io)
		"!pod-template-hash",                                       // ignore pod-template-hash
		"!controller-revision-hash",                                // ignore controller-revision-hash
		"!annotation." + common.CiliumK8sAnnotationPrefix,          // ignore all cilium annotations
		"!annotation." + common.CiliumIdentityAnnotationDeprecated, // ignore all cilium annotations
	}

	for _, e := range expressions {
		p, err := parseLabelPrefix(e)
		if err != nil {
			msg := fmt.Sprintf("BUG: Unable to parse default label prefix '%s': %s", e, err)
			panic(msg)
		}
		cfg.LabelPrefixes = append(cfg.LabelPrefixes, p)
	}

	return cfg
}

// readLabelPrefixCfgFrom reads a label prefix configuration file from fileName. If the
// version is not supported by us it returns an error.
func readLabelPrefixCfgFrom(fileName string) (*labelPrefixCfg, error) {
	// if not file is specified, the default is empty
	if fileName == "" {
		return defaultLabelPrefixCfg(), nil
	}

	f, err := os.Open(fileName)
	if err != nil {
		return nil, err
	}
	defer f.Close()
	lpc := labelPrefixCfg{}
	err = json.NewDecoder(f).Decode(&lpc)
	if err != nil {
		return nil, err
	}
	if lpc.Version != LPCfgFileVersion {
		return nil, fmt.Errorf("unsupported version %d", lpc.Version)
	}
	for _, lp := range lpc.LabelPrefixes {
		if lp.Prefix == "" {
			return nil, fmt.Errorf("invalid label prefix file: prefix was empty")
		}
		if lp.Source == "" {
			return nil, fmt.Errorf("invalid label prefix file: source was empty")
		}
		if !lp.Ignore {
			lpc.whitelist = true
		}
	}
	return &lpc, nil
}

func (cfg *labelPrefixCfg) filterLabels(lbls Labels) (identityLabels, informationLabels Labels) {
	validLabelPrefixesMU.RLock()
	defer validLabelPrefixesMU.RUnlock()

	identityLabels = Labels{}
	informationLabels = Labels{}
	for k, v := range lbls {
		included, ignored := 0, 0

		for _, p := range cfg.LabelPrefixes {
			if m, len := p.matches(v); m {
				if p.Ignore {
					// save length of shortest matching ignore
					if ignored == 0 || len < ignored {
						ignored = len
					}
				} else {
					// save length of longest matching include
					if len > included {
						included = len
					}
				}
			}
		}

		// A label is accepted if :
		// - No inclusive LabelPrefix (Ignore flag not set) is
		//   configured and label is not ignored.
		// - An inclusive LabelPrefix matches the label
		// - If both an inclusive and ignore LabelPrefix match, the
		//   label is accepted if the matching section in the label
		//   is greater than the ignored matching section in label,
		//   e.g. when evaluating the label foo.bar, the prefix rules
		//   {!foo, foo.bar} will cause the label to be accepted
		//   because the inclusive prefix matches over a longer section.
		if (!cfg.whitelist && ignored == 0) || included > ignored {
			// Just want to make sure we don't have labels deleted in
			// on side and disappearing in the other side...
			identityLabels[k] = v.DeepCopy()
		} else {
			informationLabels[k] = v.DeepCopy()
		}
	}
	return identityLabels, informationLabels
}

// FilterLabels returns Labels from the given labels that have the same source and the
// same prefix as one of lpc valid prefixes, as well as labels that do not match
// the aforementioned filtering criteria.
func FilterLabels(lbls Labels) (identityLabels, informationLabels Labels) {
	return validLabelPrefixes.filterLabels(lbls)
}
<commit_msg>pkg/labels: Filter out pod-template-generation label<commit_after>// Copyright 2016-2017 Authors of Cilium
//
// 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 labels

import (
	"encoding/json"
	"fmt"
	"os"
	"regexp"
	"strings"
	"sync"

	"github.com/cilium/cilium/common"

	log "github.com/sirupsen/logrus"
)

var (
	validLabelPrefixesMU sync.RWMutex
	validLabelPrefixes   *labelPrefixCfg // Label prefixes used to filter from all labels
)

const (
	// LPCfgFileVersion represents the version of a Label Prefix Configuration File
	LPCfgFileVersion = 1
)

// LabelPrefix is the cilium's representation of a container label.
type LabelPrefix struct {
	// Ignore if true will cause this prefix to be ignored insted of being accepted
	Ignore bool   `json:"invert"`
	Prefix string `json:"prefix"`
	Source string `json:"source"`
	expr   *regexp.Regexp
}

// String returns a human readable representation of the LabelPrefix
func (p LabelPrefix) String() string {
	s := fmt.Sprintf("%s:%s", p.Source, p.Prefix)
	if p.Ignore {
		s = "!" + s
	}

	return s
}

// matches returns true and the length of the matched section if the label is
// matched by the LabelPrefix. The Ignore flag has no effect at this point.
func (p LabelPrefix) matches(l *Label) (bool, int) {
	if p.Source != "" && p.Source != l.Source {
		return false, 0
	}

	// If no regular expression is available, fall back to prefix matching
	if p.expr == nil {
		return strings.HasPrefix(l.Key, p.Prefix), len(p.Prefix)
	}

	res := p.expr.FindStringIndex(l.Key)

	// No match if regexp was not found
	if res == nil {
		return false, 0
	}

	// Otherwise match if match was found at start of key
	return res[0] == 0, res[1]
}

// parseLabelPrefix returns a LabelPrefix created from the string label parameter.
func parseLabelPrefix(label string) (*LabelPrefix, error) {
	labelPrefix := LabelPrefix{}
	t := strings.SplitN(label, ":", 2)
	if len(t) > 1 {
		labelPrefix.Source = t[0]
		labelPrefix.Prefix = t[1]
	} else {
		labelPrefix.Prefix = label
	}

	if labelPrefix.Prefix[0] == '!' {
		labelPrefix.Ignore = true
		labelPrefix.Prefix = labelPrefix.Prefix[1:]
	}

	r, err := regexp.Compile(labelPrefix.Prefix)
	if err != nil {
		return nil, fmt.Errorf("Unable to compile regexp: %s", err)
	}
	labelPrefix.expr = r

	return &labelPrefix, nil
}

// ParseLabelPrefixCfg parses valid label prefixes from a file and from a slice
// of valid prefixes. Both are optional. If both are provided, both list are
// appended together.
func ParseLabelPrefixCfg(prefixes []string, file string) error {
	cfg, err := readLabelPrefixCfgFrom(file)
	if err != nil {
		return fmt.Errorf("Unable to read label prefix file: %s", err)
	}

	for _, label := range prefixes {
		p, err := parseLabelPrefix(label)
		if err != nil {
			return err
		}

		if !p.Ignore {
			cfg.whitelist = true
		}

		cfg.LabelPrefixes = append(cfg.LabelPrefixes, p)
	}

	validLabelPrefixes = cfg

	log.Info("Valid label prefix configuration:")
	for _, l := range validLabelPrefixes.LabelPrefixes {
		log.Infof(" - %s", l)
	}

	return nil
}

// labelPrefixCfg is the label prefix configuration to filter labels of started
// containers.
type labelPrefixCfg struct {
	Version       int            `json:"version"`
	LabelPrefixes []*LabelPrefix `json:"valid-prefixes"`
	// whitelist if true, indicates that an inclusive rule has to match
	// in order for the label to be considered
	whitelist bool
}

// defaultLabelPrefixCfg returns a default LabelPrefixCfg using the latest
// LPCfgFileVersion
func defaultLabelPrefixCfg() *labelPrefixCfg {
	cfg := &labelPrefixCfg{
		Version:       LPCfgFileVersion,
		LabelPrefixes: []*LabelPrefix{},
	}

	expressions := []string{
		K8sNamespaceLabel,                                          // include io.kubernetes.pod.namspace
		"!io.kubernetes",                                           // ignore all other io.kubernetes labels
		"!.*kubernetes.io",                                         // ignore all other kubernetes.io labels (annotation.*.k8s.io)
		"!pod-template-generation",                                 // ignore pod-template-generation
		"!pod-template-hash",                                       // ignore pod-template-hash
		"!controller-revision-hash",                                // ignore controller-revision-hash
		"!annotation." + common.CiliumK8sAnnotationPrefix,          // ignore all cilium annotations
		"!annotation." + common.CiliumIdentityAnnotationDeprecated, // ignore all cilium annotations
	}

	for _, e := range expressions {
		p, err := parseLabelPrefix(e)
		if err != nil {
			msg := fmt.Sprintf("BUG: Unable to parse default label prefix '%s': %s", e, err)
			panic(msg)
		}
		cfg.LabelPrefixes = append(cfg.LabelPrefixes, p)
	}

	return cfg
}

// readLabelPrefixCfgFrom reads a label prefix configuration file from fileName. If the
// version is not supported by us it returns an error.
func readLabelPrefixCfgFrom(fileName string) (*labelPrefixCfg, error) {
	// if not file is specified, the default is empty
	if fileName == "" {
		return defaultLabelPrefixCfg(), nil
	}

	f, err := os.Open(fileName)
	if err != nil {
		return nil, err
	}
	defer f.Close()
	lpc := labelPrefixCfg{}
	err = json.NewDecoder(f).Decode(&lpc)
	if err != nil {
		return nil, err
	}
	if lpc.Version != LPCfgFileVersion {
		return nil, fmt.Errorf("unsupported version %d", lpc.Version)
	}
	for _, lp := range lpc.LabelPrefixes {
		if lp.Prefix == "" {
			return nil, fmt.Errorf("invalid label prefix file: prefix was empty")
		}
		if lp.Source == "" {
			return nil, fmt.Errorf("invalid label prefix file: source was empty")
		}
		if !lp.Ignore {
			lpc.whitelist = true
		}
	}
	return &lpc, nil
}

func (cfg *labelPrefixCfg) filterLabels(lbls Labels) (identityLabels, informationLabels Labels) {
	validLabelPrefixesMU.RLock()
	defer validLabelPrefixesMU.RUnlock()

	identityLabels = Labels{}
	informationLabels = Labels{}
	for k, v := range lbls {
		included, ignored := 0, 0

		for _, p := range cfg.LabelPrefixes {
			if m, len := p.matches(v); m {
				if p.Ignore {
					// save length of shortest matching ignore
					if ignored == 0 || len < ignored {
						ignored = len
					}
				} else {
					// save length of longest matching include
					if len > included {
						included = len
					}
				}
			}
		}

		// A label is accepted if :
		// - No inclusive LabelPrefix (Ignore flag not set) is
		//   configured and label is not ignored.
		// - An inclusive LabelPrefix matches the label
		// - If both an inclusive and ignore LabelPrefix match, the
		//   label is accepted if the matching section in the label
		//   is greater than the ignored matching section in label,
		//   e.g. when evaluating the label foo.bar, the prefix rules
		//   {!foo, foo.bar} will cause the label to be accepted
		//   because the inclusive prefix matches over a longer section.
		if (!cfg.whitelist && ignored == 0) || included > ignored {
			// Just want to make sure we don't have labels deleted in
			// on side and disappearing in the other side...
			identityLabels[k] = v.DeepCopy()
		} else {
			informationLabels[k] = v.DeepCopy()
		}
	}
	return identityLabels, informationLabels
}

// FilterLabels returns Labels from the given labels that have the same source and the
// same prefix as one of lpc valid prefixes, as well as labels that do not match
// the aforementioned filtering criteria.
func FilterLabels(lbls Labels) (identityLabels, informationLabels Labels) {
	return validLabelPrefixes.filterLabels(lbls)
}
<|endoftext|>


<commit_before>package req

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

// M represents the request params.
type M map[string]string

// Request provides much easier useage than http.Request
type Request struct {
	url    string
	params M
	req    *http.Request
	resp   *http.Response
	body   []byte
}

// Param set single param to the request.
func (r *Request) Param(k, v string) *Request {
	r.params[k] = v
	return r
}

// Params set multiple params to the request.
func (r *Request) Params(params M) *Request {
	for k, v := range params {
		r.params[k] = v
	}
	return r
}

// Header set the request header.
func (r *Request) Header(k, v string) *Request {
	r.req.Header.Set(k, v)
	return r
}

func (r *Request) Headers(params M) *Request {
	for k, v := range params {
		r.req.Header.Set(k, v)
	}
	return r
}

// Body set the request body,support string and []byte.
func (r *Request) Body(body interface{}) *Request {
	switch v := body.(type) {
	case string:
		bf := bytes.NewBufferString(v)
		r.req.Body = ioutil.NopCloser(bf)
		r.req.ContentLength = int64(len(v))
	case []byte:
		bf := bytes.NewBuffer(v)
		r.req.Body = ioutil.NopCloser(bf)
		r.req.ContentLength = int64(len(v))
	}
	return r
}

// Bytes execute the request and get the response body as []byte.
func (r *Request) Bytes() (data []byte, err error) {
	if r.body != nil { // in case multiple call
		data = r.body
		return
	}
	resp, err := r.Response()
	if err != nil {
		return
	}
	defer resp.Body.Close()
	r.body, err = ioutil.ReadAll(resp.Body)
	if err != nil {
		return
	}
	data = r.body
	return
}

func (r *Request) MustBytes() []byte {
	data, err := r.Bytes()
	if err != nil {
		panic(err)
	}
	return data
}

// String execute the request and get the response body as string.
func (r *Request) String() (s string, err error) {
	data, err := r.Bytes()
	if err != nil {
		return
	}
	s = string(data)
	return
}

func (r *Request) MustString() string {
	s, err := r.String()
	if err != nil {
		panic(err)
	}
	return s
}

// String execute the request and get the response body unmarshal to json.
func (r *Request) ToJson(v interface{}) (err error) {
	data, err := r.Bytes()
	if err != nil {
		return err
	}
	return json.Unmarshal(data, v)
}

// String execute the request and get the response body unmarshal to xml.
func (r *Request) ToXml(v interface{}) (err error) {
	data, err := r.Bytes()
	if err != nil {
		return err
	}
	return xml.Unmarshal(data, v)
}

// Response execute the request and get the response.
func (r *Request) Response() (resp *http.Response, err error) {
	if r.resp != nil { // provent multiple call
		resp = r.resp
		return
	}
	// handle request params
	if len(r.params) > 0 {
		var buf bytes.Buffer
		for k, v := range r.params {
			buf.WriteString(url.QueryEscape(k))
			buf.WriteByte('=')
			buf.WriteString(url.QueryEscape(v))
			buf.WriteByte('&')
		}
		p := buf.String()
		p = p[0 : len(p)-1]
		switch r.req.Method {
		case "GET":
			if strings.Index(r.url, "?") != -1 {
				r.url += "&" + p
			} else {
				r.url += "?" + p
			}
		case "POST":
			if r.req.Body == nil {
				r.Header("Content-Type", "application/x-www-form-urlencoded")
				r.Body(p)
			}
		}
	}
	// set url
	u, err := url.Parse(r.url)
	if err != nil {
		return
	}
	r.req.URL = u
	resp, err = http.DefaultClient.Do(r.req)
	return
}

func (r *Request) MustResponse() *http.Response {
	resp, err := r.Response()
	if err != nil {
		panic(err)
	}
	return resp
}

// Get returns *Request with GET method.
func Get(url string) *Request {
	return newRequest(url, "GET")
}

// Get returns *Request with POST method.
func Post(url string) *Request {
	return newRequest(url, "POST")
}

func newRequest(url, method string) *Request {
	return &Request{
		url:    url,
		params: M{},
		req: &http.Request{
			Method:     method,
			Header:     make(http.Header),
			Proto:      "HTTP/1.1",
			ProtoMajor: 1,
			ProtoMinor: 1,
		},
	}
}
<commit_msg>add Url() and BodyBytes() BodyString()<commit_after>package req

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

// M represents the request params.
type M map[string]string

// Request provides much easier useage than http.Request
type Request struct {
	url      string
	params   M
	req      *http.Request
	resp     *http.Response
	done     bool
	respBody []byte
	reqBody  []byte
}

// Param set single param to the request.
func (r *Request) Param(k, v string) *Request {
	r.params[k] = v
	return r
}

// Params set multiple params to the request.
func (r *Request) Params(params M) *Request {
	for k, v := range params {
		r.params[k] = v
	}
	return r
}

// Header set the request header.
func (r *Request) Header(k, v string) *Request {
	r.req.Header.Set(k, v)
	return r
}

func (r *Request) Headers(params M) *Request {
	for k, v := range params {
		r.req.Header.Set(k, v)
	}
	return r
}

// Body set the request body,support string and []byte.
func (r *Request) Body(body interface{}) *Request {
	switch v := body.(type) {
	case string:
		bf := bytes.NewBufferString(v)
		r.req.Body = ioutil.NopCloser(bf)
		r.req.ContentLength = int64(len(v))
		r.reqBody = []byte(v)
	case []byte:
		bf := bytes.NewBuffer(v)
		r.req.Body = ioutil.NopCloser(bf)
		r.req.ContentLength = int64(len(v))
		r.reqBody = v
	}
	return r
}

func (r *Request) BodyBytes() []byte {
	return r.reqBody
}

func (r *Request) BodyString() string {
	return string(r.reqBody)
}

// Bytes execute the request and get the response body as []byte.
func (r *Request) Bytes() (data []byte, err error) {
	if r.respBody != nil { // in case multiple call
		data = r.respBody
		return
	}
	resp, err := r.Response()
	if err != nil {
		return
	}
	defer resp.Body.Close()
	r.respBody, err = ioutil.ReadAll(resp.Body)
	if err != nil {
		return
	}
	data = r.respBody
	return
}

func (r *Request) MustBytes() []byte {
	data, err := r.Bytes()
	if err != nil {
		panic(err)
	}
	return data
}

// String execute the request and get the response body as string.
func (r *Request) String() (s string, err error) {
	data, err := r.Bytes()
	if err != nil {
		return
	}
	s = string(data)
	return
}

func (r *Request) MustString() string {
	s, err := r.String()
	if err != nil {
		panic(err)
	}
	return s
}

// String execute the request and get the response body unmarshal to json.
func (r *Request) ToJson(v interface{}) (err error) {
	data, err := r.Bytes()
	if err != nil {
		return err
	}
	err = json.Unmarshal(data, v)
	return
}

// String execute the request and get the response body unmarshal to xml.
func (r *Request) ToXml(v interface{}) (err error) {
	data, err := r.Bytes()
	if err != nil {
		return err
	}
	err = xml.Unmarshal(data, v)
	return
}

func (r *Request) getParamBody() string {
	var buf bytes.Buffer
	for k, v := range r.params {
		buf.WriteString(url.QueryEscape(k))
		buf.WriteByte('=')
		buf.WriteString(url.QueryEscape(v))
		buf.WriteByte('&')
	}
	p := buf.String()
	p = p[0 : len(p)-1]
	return p
}

func (r *Request) buildGetUrl() string {
	ret := r.url
	p := r.getParamBody()
	if strings.Index(r.url, "?") != -1 {
		ret += "&" + p
	} else {
		ret += "?" + p
	}
	return ret
}

func (r *Request) setParamBody() {
	r.Header("Content-Type", "application/x-www-form-urlencoded")
	r.Body(r.getParamBody())
}

func (r *Request) Url() string {
	if r.req.Method != "GET" || r.done {
		return r.url
	}
	return r.buildGetUrl() //GET method and did not send request yet.
}

// Response execute the request and get the response.
func (r *Request) Response() (resp *http.Response, err error) {
	if r.resp != nil { // provent multiple call
		resp = r.resp
		return
	}
	// handle request params
	if len(r.params) > 0 {
		switch r.req.Method {
		case "GET":
			r.url = r.buildGetUrl()
		case "POST":
			if r.req.Body == nil {
				r.setParamBody()
			}
		}
	}
	// set url
	u, err := url.Parse(r.url)
	if err != nil {
		return
	}
	r.req.URL = u
	resp, err = http.DefaultClient.Do(r.req)
	r.done = true
	return
}

func (r *Request) MustResponse() *http.Response {
	resp, err := r.Response()
	if err != nil {
		panic(err)
	}
	return resp
}

// Get returns *Request with GET method.
func Get(url string) *Request {
	return newRequest(url, "GET")
}

// Get returns *Request with POST method.
func Post(url string) *Request {
	return newRequest(url, "POST")
}

func newRequest(url, method string) *Request {
	return &Request{
		url:    url,
		params: M{},
		req: &http.Request{
			Method:     method,
			Header:     make(http.Header),
			Proto:      "HTTP/1.1",
			ProtoMajor: 1,
			ProtoMinor: 1,
		},
	}
}
<|endoftext|>


<commit_before>package thruster

type Thruster struct{}
<commit_msg>Document Thruster<commit_after>package thruster

// A Thruster is a small part with a toggle trigger that
// applies a simple impulse to the ship's physics body.
type Thruster struct{}
<|endoftext|>


<commit_before>// Copyright 2016-2017 Authors of Cilium
//
// 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 api

// L4Proto is a layer 4 protocol name
type L4Proto string

const (
	ProtoTCP L4Proto = "TCP"
	ProtoUDP L4Proto = "UDP"
	ProtoAny L4Proto = "ANY"
)

// PortProtocol specifies an L4 port with an optional transport protocol
type PortProtocol struct {
	// Port is an L4 port number. For now the string will be strictly
	// parsed as a single uint16. In the future, this field may support
	// ranges in the form "1024-2048
	Port string `json:"port"`

	// Protocol is the L4 protocol. If omitted or empty, any protocol
	// matches. Accepted values: "TCP", "UDP", ""/"ANY"
	//
	// Matching on ICMP is not supported.
	//
	// +optional
	Protocol L4Proto `json:"protocol,omitempty"`
}

// PortRule is a list of ports/protocol combinations with optional Layer 7
// rules which must be met.
type PortRule struct {
	// Ports is a list of L4 port/protocol
	//
	// If omitted or empty but RedirectPort is set, then all ports of the
	// endpoint subject to either the ingress or egress rule are being
	// redirected.
	//
	// +optional
	Ports []PortProtocol `json:"ports,omitempty"`

	// Rules is a list of additional port level rules which must be met in
	// order for the PortRule to allow the traffic. If omitted or empty,
	// no layer 7 rules are enforced.
	//
	// +optional
	Rules *L7Rules `json:"rules,omitempty"`
}

// L7Rules is a union of port level rule types. Mixing of different port
// level rule types is disallowed, so exactly one of the following must be set.
// If none are specified, then no additional port level rules are applied.
type L7Rules struct {
	// HTTP specific rules.
	//
	// +optional
	HTTP []PortRuleHTTP `json:"http,omitempty"`

	// Kafka-specific rules.
	//
	// +optional
	Kafka []PortRuleKafka `json:"kafka,omitempty"`

	// DNS-specific rules.
	//
	// +optional
	DNS []PortRuleDNS `json:"dns,omitempty"`

	// Name of the L7 protocol for which the Key-value pair rules apply
	//
	// +optional
	L7Proto string `json:"l7proto,omitempty"`

	// Key-value pair rules
	//
	// +optional
	L7 []PortRuleL7 `json:"l7,omitempty"`
}

// Len returns the total number of rules inside `L7Rules`.
// Returns 0 if nil.
func (rules *L7Rules) Len() int {
	if rules == nil {
		return 0
	}
	return len(rules.HTTP) + len(rules.Kafka) + len(rules.DNS) + len(rules.L7)
}

// IsEmpty returns whether the `L7Rules` is nil or contains nil rules.
func (rules *L7Rules) IsEmpty() bool {
	return rules == nil || (rules.HTTP == nil && rules.Kafka == nil && rules.DNS == nil && rules.L7 == nil)
}
<commit_msg>policy/api: Add helper for PortProtocol supersets<commit_after>// Copyright 2016-2019 Authors of Cilium
//
// 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 api

// L4Proto is a layer 4 protocol name
type L4Proto string

const (
	ProtoTCP L4Proto = "TCP"
	ProtoUDP L4Proto = "UDP"
	ProtoAny L4Proto = "ANY"
)

// PortProtocol specifies an L4 port with an optional transport protocol
type PortProtocol struct {
	// Port is an L4 port number. For now the string will be strictly
	// parsed as a single uint16. In the future, this field may support
	// ranges in the form "1024-2048
	Port string `json:"port"`

	// Protocol is the L4 protocol. If omitted or empty, any protocol
	// matches. Accepted values: "TCP", "UDP", ""/"ANY"
	//
	// Matching on ICMP is not supported.
	//
	// +optional
	Protocol L4Proto `json:"protocol,omitempty"`
}

// Covers returns true if the ports and protocol specified in the received
// PortProtocol are equal to or a superset of the ports and protocol in 'other'.
func (p PortProtocol) Covers(other PortProtocol) bool {
	if p.Port != other.Port {
		return false
	}
	if p.Protocol != other.Protocol {
		return p.Protocol == "" || p.Protocol == ProtoAny
	}
	return true
}

// PortRule is a list of ports/protocol combinations with optional Layer 7
// rules which must be met.
type PortRule struct {
	// Ports is a list of L4 port/protocol
	//
	// If omitted or empty but RedirectPort is set, then all ports of the
	// endpoint subject to either the ingress or egress rule are being
	// redirected.
	//
	// +optional
	Ports []PortProtocol `json:"ports,omitempty"`

	// Rules is a list of additional port level rules which must be met in
	// order for the PortRule to allow the traffic. If omitted or empty,
	// no layer 7 rules are enforced.
	//
	// +optional
	Rules *L7Rules `json:"rules,omitempty"`
}

// L7Rules is a union of port level rule types. Mixing of different port
// level rule types is disallowed, so exactly one of the following must be set.
// If none are specified, then no additional port level rules are applied.
type L7Rules struct {
	// HTTP specific rules.
	//
	// +optional
	HTTP []PortRuleHTTP `json:"http,omitempty"`

	// Kafka-specific rules.
	//
	// +optional
	Kafka []PortRuleKafka `json:"kafka,omitempty"`

	// DNS-specific rules.
	//
	// +optional
	DNS []PortRuleDNS `json:"dns,omitempty"`

	// Name of the L7 protocol for which the Key-value pair rules apply
	//
	// +optional
	L7Proto string `json:"l7proto,omitempty"`

	// Key-value pair rules
	//
	// +optional
	L7 []PortRuleL7 `json:"l7,omitempty"`
}

// Len returns the total number of rules inside `L7Rules`.
// Returns 0 if nil.
func (rules *L7Rules) Len() int {
	if rules == nil {
		return 0
	}
	return len(rules.HTTP) + len(rules.Kafka) + len(rules.DNS) + len(rules.L7)
}

// IsEmpty returns whether the `L7Rules` is nil or contains nil rules.
func (rules *L7Rules) IsEmpty() bool {
	return rules == nil || (rules.HTTP == nil && rules.Kafka == nil && rules.DNS == nil && rules.L7 == nil)
}
<|endoftext|>


<commit_before>/*
Copyright 2018 The Knative 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 queue

import (
	"context"
	"errors"
	"fmt"
	"math"
	"sync"
	"sync/atomic"
)

var (
	// ErrUpdateCapacity indicates that the capacity could not be updated as wished.
	ErrUpdateCapacity = errors.New("failed to add all capacity to the breaker")
	// ErrRelease indicates that release was called more often than acquire.
	ErrRelease = errors.New("semaphore release error: returned tokens must be <= acquired tokens")
	// ErrRequestQueueFull indicates the breaker queue depth was exceeded.
	ErrRequestQueueFull = errors.New("pending request queue full")
)

// MaxBreakerCapacity is the largest valid value for the MaxConcurrency value of BreakerParams.
// This is limited by the maximum size of a chan struct{} in the current implementation.
const MaxBreakerCapacity = math.MaxInt32

// BreakerParams defines the parameters of the breaker.
type BreakerParams struct {
	QueueDepth      int
	MaxConcurrency  int
	InitialCapacity int
}

// Breaker is a component that enforces a concurrency limit on the
// execution of a function. It also maintains a queue of function
// executions in excess of the concurrency limit. Function call attempts
// beyond the limit of the queue are failed immediately.
type Breaker struct {
	inFlight   int64
	totalSlots int64
	sem        *semaphore
}

// NewBreaker creates a Breaker with the desired queue depth,
// concurrency limit and initial capacity.
func NewBreaker(params BreakerParams) *Breaker {
	if params.QueueDepth <= 0 {
		panic(fmt.Sprintf("Queue depth must be greater than 0. Got %v.", params.QueueDepth))
	}
	if params.MaxConcurrency < 0 {
		panic(fmt.Sprintf("Max concurrency must be 0 or greater. Got %v.", params.MaxConcurrency))
	}
	if params.InitialCapacity < 0 || params.InitialCapacity > params.MaxConcurrency {
		panic(fmt.Sprintf("Initial capacity must be between 0 and max concurrency. Got %v.", params.InitialCapacity))
	}
	sem := newSemaphore(params.MaxConcurrency, params.InitialCapacity)
	return &Breaker{
		totalSlots: int64(params.QueueDepth + params.MaxConcurrency),
		sem:        sem,
	}
}

// tryAcquirePending tries to acquire a slot on the pending "queue".
func (b *Breaker) tryAcquirePending() bool {
	// This is an atomic version of:
	//
	// if inFlight == totalSlots {
	//   return false
	// } else {
	//   inFlight++
	//   return true
	// }
	//
	// We can't just use an atomic increment as we need to check if we're
	// "allowed" to increment first. Since a Load and a CompareAndSwap are
	// not done atomically, we need to retry until the CompareAndSwap succeeds
	// (it fails if we're raced to it) or if we don't fulfill the condition
	// anymore.
	for {
		cur := atomic.LoadInt64(&b.inFlight)
		if cur == b.totalSlots {
			return false
		}
		if atomic.CompareAndSwapInt64(&b.inFlight, cur, cur+1) {
			return true
		}
	}
}

// releasePending releases a slot on the pending "queue".
func (b *Breaker) releasePending() {
	atomic.AddInt64(&b.inFlight, -1)
}

// Reserve reserves an execution slot in the breaker, to permit
// richer semantics in the caller.
// The caller on success must execute the callback when done with work.
func (b *Breaker) Reserve(ctx context.Context) (func(), bool) {
	if !b.tryAcquirePending() {
		return nil, false
	}

	if !b.sem.tryAcquire() {
		b.releasePending()
		return nil, false
	}
	return func() {
		b.sem.release()
		b.releasePending()
	}, true
}

// Maybe conditionally executes thunk based on the Breaker concurrency
// and queue parameters. If the concurrency limit and queue capacity are
// already consumed, Maybe returns immediately without calling thunk. If
// the thunk was executed, Maybe returns true, else false.
func (b *Breaker) Maybe(ctx context.Context, thunk func()) error {
	if !b.tryAcquirePending() {
		return ErrRequestQueueFull
	}

	defer b.releasePending()

	// Wait for capacity in the active queue.
	if err := b.sem.acquire(ctx); err != nil {
		return err
	}
	// Defer releasing capacity in the active.
	// It's safe to ignore the error returned by release since we
	// make sure the semaphore is only manipulated here and acquire
	// + release calls are equally paired.
	defer b.sem.release()

	// Do the thing.
	thunk()
	// Report success
	return nil
}

// InFlight returns the number of requests currently in flight in this breaker.
func (b *Breaker) InFlight() int {
	return int(atomic.LoadInt64(&b.inFlight))
}

// UpdateConcurrency updates the maximum number of in-flight requests.
func (b *Breaker) UpdateConcurrency(size int) error {
	return b.sem.updateCapacity(size)
}

// Capacity returns the number of allowed in-flight requests on this breaker.
func (b *Breaker) Capacity() int {
	return b.sem.Capacity()
}

// newSemaphore creates a semaphore with the desired maximal and initial capacity.
// Maximal capacity is the size of the buffered channel, it defines maximum number of tokens
// in the rotation. Attempting to add more capacity then the max will result in error.
// Initial capacity is the initial number of free tokens.
func newSemaphore(maxCapacity, initialCapacity int) *semaphore {
	queue := make(chan struct{}, maxCapacity)
	sem := &semaphore{queue: queue}
	if initialCapacity > 0 {
		sem.updateCapacity(initialCapacity)
	}
	return sem
}

// semaphore is an implementation of a semaphore based on Go channels.
// The presence of elements in the `queue` buffered channel correspond to available tokens.
// Hence the max number of tokens to hand out equals to the size of the channel.
// `capacity` defines the current number of tokens in the rotation.
type semaphore struct {
	queue    chan struct{}
	reducers int
	capacity int
	mux      sync.RWMutex
}

// tryAcquire receives the token from the semaphore if there's one
// otherwise an error is returned.
func (s *semaphore) tryAcquire() bool {
	select {
	case <-s.queue:
		return true
	default:
		return false
	}
}

// acquire receives the token from the semaphore, potentially blocking.
func (s *semaphore) acquire(ctx context.Context) error {
	select {
	case <-s.queue:
		return nil
	case <-ctx.Done():
		return ctx.Err()
	}
}

// release potentially puts the token back to the queue.
// If the semaphore capacity was reduced in between and is not yet reflected,
// we remove the tokens from the rotation instead of returning them back.
func (s *semaphore) release() error {
	s.mux.Lock()
	defer s.mux.Unlock()

	if s.reducers > 0 {
		s.capacity--
		s.reducers--
		return nil
	}

	// We want to make sure releasing a token is always non-blocking.
	select {
	case s.queue <- struct{}{}:
		return nil
	default:
		// This only happens if release is called more often than acquire.
		return ErrRelease
	}
}

// updateCapacity updates the capacity of the semaphore to the desired
// size.
func (s *semaphore) updateCapacity(size int) error {
	if size < 0 || size > cap(s.queue) {
		return ErrUpdateCapacity
	}

	s.mux.Lock()
	defer s.mux.Unlock()

	if s.effectiveCapacity() == size {
		return nil
	}

	// Add capacity until we reach size, potentially consuming
	// outstanding reducers first.
	for s.effectiveCapacity() < size {
		if s.reducers > 0 {
			s.reducers--
		} else {
			select {
			case s.queue <- struct{}{}:
				s.capacity++
			default:
				// This indicates that we're operating close to
				// MaxCapacity and returned more tokens than we
				// acquired.
				return ErrUpdateCapacity
			}
		}
	}

	// Reduce capacity until we reach size, potentially adding
	// new reducers if the queue channel is empty because of
	// requests in-flight.
	for s.effectiveCapacity() > size {
		select {
		case <-s.queue:
			s.capacity--
		default:
			s.reducers++
		}
	}

	return nil
}

// effectiveCapacity is the capacity with reducers taken into account.
// `mux` must be held to call it.
func (s *semaphore) effectiveCapacity() int {
	return s.capacity - s.reducers
}

// Capacity is the effective capacity after taking reducers into account.
func (s *semaphore) Capacity() int {
	s.mux.RLock()
	defer s.mux.RUnlock()

	return s.effectiveCapacity()
}
<commit_msg>Remove an allocation from breaker reserve (#8851)<commit_after>/*
Copyright 2018 The Knative 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 queue

import (
	"context"
	"errors"
	"fmt"
	"math"
	"sync"
	"sync/atomic"
)

var (
	// ErrUpdateCapacity indicates that the capacity could not be updated as wished.
	ErrUpdateCapacity = errors.New("failed to add all capacity to the breaker")
	// ErrRelease indicates that release was called more often than acquire.
	ErrRelease = errors.New("semaphore release error: returned tokens must be <= acquired tokens")
	// ErrRequestQueueFull indicates the breaker queue depth was exceeded.
	ErrRequestQueueFull = errors.New("pending request queue full")
)

// MaxBreakerCapacity is the largest valid value for the MaxConcurrency value of BreakerParams.
// This is limited by the maximum size of a chan struct{} in the current implementation.
const MaxBreakerCapacity = math.MaxInt32

// BreakerParams defines the parameters of the breaker.
type BreakerParams struct {
	QueueDepth      int
	MaxConcurrency  int
	InitialCapacity int
}

// Breaker is a component that enforces a concurrency limit on the
// execution of a function. It also maintains a queue of function
// executions in excess of the concurrency limit. Function call attempts
// beyond the limit of the queue are failed immediately.
type Breaker struct {
	inFlight   int64
	totalSlots int64
	sem        *semaphore

	// release is the callback function returned to callers by Reserve to
	// allow the reservation made by Reserve to be released.
	release func()
}

// NewBreaker creates a Breaker with the desired queue depth,
// concurrency limit and initial capacity.
func NewBreaker(params BreakerParams) *Breaker {
	if params.QueueDepth <= 0 {
		panic(fmt.Sprintf("Queue depth must be greater than 0. Got %v.", params.QueueDepth))
	}
	if params.MaxConcurrency < 0 {
		panic(fmt.Sprintf("Max concurrency must be 0 or greater. Got %v.", params.MaxConcurrency))
	}
	if params.InitialCapacity < 0 || params.InitialCapacity > params.MaxConcurrency {
		panic(fmt.Sprintf("Initial capacity must be between 0 and max concurrency. Got %v.", params.InitialCapacity))
	}

	b := &Breaker{
		totalSlots: int64(params.QueueDepth + params.MaxConcurrency),
		sem:        newSemaphore(params.MaxConcurrency, params.InitialCapacity),
	}

	// Allocating the closure returned by Reserve here avoids an allocation in Reserve.
	b.release = func() {
		b.sem.release()
		b.releasePending()
	}

	return b
}

// tryAcquirePending tries to acquire a slot on the pending "queue".
func (b *Breaker) tryAcquirePending() bool {
	// This is an atomic version of:
	//
	// if inFlight == totalSlots {
	//   return false
	// } else {
	//   inFlight++
	//   return true
	// }
	//
	// We can't just use an atomic increment as we need to check if we're
	// "allowed" to increment first. Since a Load and a CompareAndSwap are
	// not done atomically, we need to retry until the CompareAndSwap succeeds
	// (it fails if we're raced to it) or if we don't fulfill the condition
	// anymore.
	for {
		cur := atomic.LoadInt64(&b.inFlight)
		if cur == b.totalSlots {
			return false
		}
		if atomic.CompareAndSwapInt64(&b.inFlight, cur, cur+1) {
			return true
		}
	}
}

// releasePending releases a slot on the pending "queue".
func (b *Breaker) releasePending() {
	atomic.AddInt64(&b.inFlight, -1)
}

// Reserve reserves an execution slot in the breaker, to permit
// richer semantics in the caller.
// The caller on success must execute the callback when done with work.
func (b *Breaker) Reserve(ctx context.Context) (func(), bool) {
	if !b.tryAcquirePending() {
		return nil, false
	}

	if !b.sem.tryAcquire() {
		b.releasePending()
		return nil, false
	}

	return b.release, true
}

// Maybe conditionally executes thunk based on the Breaker concurrency
// and queue parameters. If the concurrency limit and queue capacity are
// already consumed, Maybe returns immediately without calling thunk. If
// the thunk was executed, Maybe returns true, else false.
func (b *Breaker) Maybe(ctx context.Context, thunk func()) error {
	if !b.tryAcquirePending() {
		return ErrRequestQueueFull
	}

	defer b.releasePending()

	// Wait for capacity in the active queue.
	if err := b.sem.acquire(ctx); err != nil {
		return err
	}
	// Defer releasing capacity in the active.
	// It's safe to ignore the error returned by release since we
	// make sure the semaphore is only manipulated here and acquire
	// + release calls are equally paired.
	defer b.sem.release()

	// Do the thing.
	thunk()
	// Report success
	return nil
}

// InFlight returns the number of requests currently in flight in this breaker.
func (b *Breaker) InFlight() int {
	return int(atomic.LoadInt64(&b.inFlight))
}

// UpdateConcurrency updates the maximum number of in-flight requests.
func (b *Breaker) UpdateConcurrency(size int) error {
	return b.sem.updateCapacity(size)
}

// Capacity returns the number of allowed in-flight requests on this breaker.
func (b *Breaker) Capacity() int {
	return b.sem.Capacity()
}

// newSemaphore creates a semaphore with the desired maximal and initial capacity.
// Maximal capacity is the size of the buffered channel, it defines maximum number of tokens
// in the rotation. Attempting to add more capacity then the max will result in error.
// Initial capacity is the initial number of free tokens.
func newSemaphore(maxCapacity, initialCapacity int) *semaphore {
	queue := make(chan struct{}, maxCapacity)
	sem := &semaphore{queue: queue}
	if initialCapacity > 0 {
		sem.updateCapacity(initialCapacity)
	}
	return sem
}

// semaphore is an implementation of a semaphore based on Go channels.
// The presence of elements in the `queue` buffered channel correspond to available tokens.
// Hence the max number of tokens to hand out equals to the size of the channel.
// `capacity` defines the current number of tokens in the rotation.
type semaphore struct {
	queue    chan struct{}
	reducers int
	capacity int
	mux      sync.RWMutex
}

// tryAcquire receives the token from the semaphore if there's one
// otherwise an error is returned.
func (s *semaphore) tryAcquire() bool {
	select {
	case <-s.queue:
		return true
	default:
		return false
	}
}

// acquire receives the token from the semaphore, potentially blocking.
func (s *semaphore) acquire(ctx context.Context) error {
	select {
	case <-s.queue:
		return nil
	case <-ctx.Done():
		return ctx.Err()
	}
}

// release potentially puts the token back to the queue.
// If the semaphore capacity was reduced in between and is not yet reflected,
// we remove the tokens from the rotation instead of returning them back.
func (s *semaphore) release() error {
	s.mux.Lock()
	defer s.mux.Unlock()

	if s.reducers > 0 {
		s.capacity--
		s.reducers--
		return nil
	}

	// We want to make sure releasing a token is always non-blocking.
	select {
	case s.queue <- struct{}{}:
		return nil
	default:
		// This only happens if release is called more often than acquire.
		return ErrRelease
	}
}

// updateCapacity updates the capacity of the semaphore to the desired
// size.
func (s *semaphore) updateCapacity(size int) error {
	if size < 0 || size > cap(s.queue) {
		return ErrUpdateCapacity
	}

	s.mux.Lock()
	defer s.mux.Unlock()

	if s.effectiveCapacity() == size {
		return nil
	}

	// Add capacity until we reach size, potentially consuming
	// outstanding reducers first.
	for s.effectiveCapacity() < size {
		if s.reducers > 0 {
			s.reducers--
		} else {
			select {
			case s.queue <- struct{}{}:
				s.capacity++
			default:
				// This indicates that we're operating close to
				// MaxCapacity and returned more tokens than we
				// acquired.
				return ErrUpdateCapacity
			}
		}
	}

	// Reduce capacity until we reach size, potentially adding
	// new reducers if the queue channel is empty because of
	// requests in-flight.
	for s.effectiveCapacity() > size {
		select {
		case <-s.queue:
			s.capacity--
		default:
			s.reducers++
		}
	}

	return nil
}

// effectiveCapacity is the capacity with reducers taken into account.
// `mux` must be held to call it.
func (s *semaphore) effectiveCapacity() int {
	return s.capacity - s.reducers
}

// Capacity is the effective capacity after taking reducers into account.
func (s *semaphore) Capacity() int {
	s.mux.RLock()
	defer s.mux.RUnlock()

	return s.effectiveCapacity()
}
<|endoftext|>


<commit_before>package at

import (
	"context"
	"errors"
	"time"
)

// ErrTimeAlreadyPassed is returned when a timestamp in the past is passed to
// Schedule
var ErrTimeAlreadyPassed = errors.New("at: can't schedule in the past")

// Schedule queues a job to run at the specific time
func Schedule(ctx context.Context, ts time.Time, fn func(ctx context.Context)) error {
	duration := ts.Sub(time.Now())
	if int64(duration) < 0 {
		return ErrTimeAlreadyPassed
	}

	go func(ctx context.Context, duration time.Duration) {
		select {
		case <-ctx.Done():
			// timed out / cancelled
		case <-time.After(duration):
			fn(ctx)
		}
	}(ctx, duration)
	return nil
}
<commit_msg>update documentation<commit_after>package at

import (
	"context"
	"errors"
	"time"
)

// ErrTimeAlreadyPassed is returned when a timestamp in the past is passed to
// Schedule
var ErrTimeAlreadyPassed = errors.New("at: can't schedule in the past")

// Schedule queues a job to run within 1 second after the specified time
func Schedule(ctx context.Context, ts time.Time, fn func(ctx context.Context)) error {
	duration := ts.Sub(time.Now())
	if int64(duration) < 0 {
		return ErrTimeAlreadyPassed
	}

	go func(ctx context.Context, duration time.Duration) {
		select {
		case <-ctx.Done():
			// timed out / cancelled
		case <-time.After(duration):
			fn(ctx)
		}
	}(ctx, duration)
	return nil
}
<|endoftext|>


<commit_before>package txtdirect

import (
	"net/http"
	"net/http/httptest"
	"testing"
)

func TestParsePlaceholders(t *testing.T) {
	tests := []struct {
		url       string
		requested string
		expected  string
	}{
		{
			"example.com{uri}",
			"https://example.com/about/test/file.html?query=string&another=value#even-a-fragment",
			"example.com/about/test/file.html?query=string&another=value#even-a-fragment",
		},
		{
			"example.com/{~test}",
			"https://example.com/?test=test",
			"example.com/test",
		},
		{
			"example.com/{>Test}",
			"https://example.com/?test=test",
			"example.com/test-header",
		},
		{
			"example.com/{?test}",
			"https://example.com/?test=test",
			"example.com/test",
		},
		{
			"example.com{dir}",
			"https://example.com/directory/test",
			"example.com/directory/",
		},
		{
			"example.com/directory/{file}",
			"https://example.com/directory/file.pdf",
			"example.com/directory/file.pdf",
		},
		{
			"example.com/{host}",
			"https://project.example.com:8080",
			"example.com/project.example.com:8080",
		},
		{
			"example.com/{hostonly}",
			"https://project.example.com",
			"example.com/project.example.com",
		},
		{
			"example.com/{method}",
			"https://example.com",
			"example.com/GET",
		},
		{
			"example.com{path}",
			"https://example.com/this-is-a-test-path/api/v1/",
			"example.com/this-is-a-test-path/api/v1/",
		},
		{
			"example.com{path_escaped}",
			"https://example.com/path_escaped-test/api/v1",
			"example.com%2Fpath_escaped-test%2Fapi%2Fv1",
		},
		{
			"example.com:{port}",
			"https://example.com:8080",
			"example.com:8080",
		},
		{
			"example.com/test/{query}",
			"https://example.com/test/?querykey=queryvalue&anotherquerykey=anothervalue",
			"example.com/test/querykey=queryvalue&anotherquerykey=anothervalue",
		},
		{
			"example.com/test/{query_escaped}",
			"https://example.com/test/?querykey=queryvalue&anotherquerykey=anothervalue",
			"example.com/test/querykey%3Dqueryvalue%26anotherquerykey%3Danothervalue",
		},
		{
			"about.example.com/{label1}",
			"https://about.example.com",
			"about.example.com/about",
		},
		{
			"about.example.com/{label2}",
			"https://about.example.com",
			"about.example.com/example",
		},
		{
			"about.example.com/{label3}",
			"https://about.example.com/com",
			"about.example.com/com",
		},
	}
	for _, test := range tests {
		req := httptest.NewRequest("GET", test.requested, nil)
		req.AddCookie(&http.Cookie{Name: "test", Value: "test"})
		req.Header.Add("Test", "test-header")
		result, err := parsePlaceholders(test.url, req)
		if err != nil {
			t.Fatal(err)
		}
		if result != test.expected {
			t.Errorf("Expected %s, got %s", test.expected, result)
		}
	}
}

func TestParsePlaceholdersFails(t *testing.T) {
	tests := []struct {
		url       string
		requested string
	}{
		{
			"example.com/{label0}",
			"https://example.com/test",
		},
		{
			"example.com/{label9000}",
			"https://example.com/test",
		},
	}
	for _, test := range tests {
		req := httptest.NewRequest("GET", test.requested, nil)
		_, err := parsePlaceholders(test.url, req)
		if err == nil {
			t.Errorf("Expected error, got nil")
		}
	}
}
<commit_msg>Add {uri_escaped} placeholder test<commit_after>package txtdirect

import (
	"net/http"
	"net/http/httptest"
	"testing"
)

func TestParsePlaceholders(t *testing.T) {
	tests := []struct {
		url       string
		requested string
		expected  string
	}{
		{
			"example.com{uri}",
			"https://example.com/about/test/file.html?query=string&another=value#even-a-fragment",
			"example.com/about/test/file.html?query=string&another=value#even-a-fragment",
		},
		{
			"example.com{uri_escaped}",
			"https://example.com/about/test/file.html?query=string&another=value#even-a-fragment",
			"example.com%2Fabout%2Ftest%2Ffile.html%3Fquery%3Dstring%26another%3Dvalue%23even-a-fragment",
		},
		{
			"example.com/{~test}",
			"https://example.com/?test=test",
			"example.com/test",
		},
		{
			"example.com/{>Test}",
			"https://example.com/?test=test",
			"example.com/test-header",
		},
		{
			"example.com/{?test}",
			"https://example.com/?test=test",
			"example.com/test",
		},
		{
			"example.com{dir}",
			"https://example.com/directory/test",
			"example.com/directory/",
		},
		{
			"example.com/directory/{file}",
			"https://example.com/directory/file.pdf",
			"example.com/directory/file.pdf",
		},
		{
			"example.com/{host}",
			"https://project.example.com:8080",
			"example.com/project.example.com:8080",
		},
		{
			"example.com/{hostonly}",
			"https://project.example.com",
			"example.com/project.example.com",
		},
		{
			"example.com/{method}",
			"https://example.com",
			"example.com/GET",
		},
		{
			"example.com{path}",
			"https://example.com/this-is-a-test-path/api/v1/",
			"example.com/this-is-a-test-path/api/v1/",
		},
		{
			"example.com{path_escaped}",
			"https://example.com/path_escaped-test/api/v1",
			"example.com%2Fpath_escaped-test%2Fapi%2Fv1",
		},
		{
			"example.com:{port}",
			"https://example.com:8080",
			"example.com:8080",
		},
		{
			"example.com/test/{query}",
			"https://example.com/test/?querykey=queryvalue&anotherquerykey=anothervalue",
			"example.com/test/querykey=queryvalue&anotherquerykey=anothervalue",
		},
		{
			"example.com/test/{query_escaped}",
			"https://example.com/test/?querykey=queryvalue&anotherquerykey=anothervalue",
			"example.com/test/querykey%3Dqueryvalue%26anotherquerykey%3Danothervalue",
		},
		{
			"about.example.com/{label1}",
			"https://about.example.com",
			"about.example.com/about",
		},
		{
			"about.example.com/{label2}",
			"https://about.example.com",
			"about.example.com/example",
		},
		{
			"about.example.com/{label3}",
			"https://about.example.com/com",
			"about.example.com/com",
		},
	}
	for _, test := range tests {
		req := httptest.NewRequest("GET", test.requested, nil)
		req.AddCookie(&http.Cookie{Name: "test", Value: "test"})
		req.Header.Add("Test", "test-header")
		result, err := parsePlaceholders(test.url, req)
		if err != nil {
			t.Fatal(err)
		}
		if result != test.expected {
			t.Errorf("Expected %s, got %s", test.expected, result)
		}
	}
}

func TestParsePlaceholdersFails(t *testing.T) {
	tests := []struct {
		url       string
		requested string
	}{
		{
			"example.com/{label0}",
			"https://example.com/test",
		},
		{
			"example.com/{label9000}",
			"https://example.com/test",
		},
	}
	for _, test := range tests {
		req := httptest.NewRequest("GET", test.requested, nil)
		_, err := parsePlaceholders(test.url, req)
		if err == nil {
			t.Errorf("Expected error, got nil")
		}
	}
}
<|endoftext|>


<commit_before>package rpc

import (
	"errors"
	"fmt"
	"net/url"
	"os"
	"sync"
	"time"

	"code.google.com/p/go.net/websocket"
	"github.com/koding/kite/dnode"
)

const redialDurationStart = 1 * time.Second
const redialDurationMax = 60 * time.Second

// Dial is a helper for creating a Client for just calling methods on the server.
// Do not use it if you want to handle methods on client side. Instead create a
// new Client, register your methods on Client.Dnode then call Client.Dial().
func Dial(url string, reconnect bool) (*Client, error) {
	c := NewClient()
	c.Reconnect = reconnect

	err := c.Dial(url)
	if err != nil {
		return nil, err
	}

	return c, nil
}

// Client is a dnode RPC client.
type Client struct {
	// Websocket connection
	Conn *websocket.Conn

	// Websocket connection options.
	Config *websocket.Config

	// Dnode message processor.
	dnode *dnode.Dnode

	// A space for saving/reading extra properties about this client.
	properties map[string]interface{}

	// Should we reconnect if disconnected?
	Reconnect bool

	// Time to wait before redial connection.
	redialDuration time.Duration

	// on connect/disconnect handlers are invoked after every
	// connect/disconnect.
	onConnectHandlers    []func()
	onDisconnectHandlers []func()

	// For protecting access over OnConnect and OnDisconnect handlers.
	m sync.RWMutex
}

// NewClient returns a pointer to new Client.
// You need to call Dial() before interacting with the Server.
func NewClient() *Client {
	// Must send an "Origin" header. Does not checked on server.
	origin, _ := url.Parse("")

	config := &websocket.Config{
		Version: websocket.ProtocolVersionHybi13,
		Origin:  origin,
		// Location will be set when dialing.
	}

	c := &Client{
		properties:     make(map[string]interface{}),
		redialDuration: redialDurationStart,
		Config:         config,
	}

	c.dnode = dnode.New(c)
	return c
}

func (c *Client) SetWrappers(wrapMethodArgs, wrapCallbackArgs dnode.Wrapper, runMethod, runCallback dnode.Runner, onError func(error)) {
	c.dnode.WrapMethodArgs = wrapMethodArgs
	c.dnode.WrapCallbackArgs = wrapCallbackArgs
	c.dnode.RunMethod = runMethod
	c.dnode.RunCallback = runCallback
	c.dnode.OnError = onError
}

// Dial connects to the dnode server on "url" and starts a goroutine
// that processes incoming messages.
//
// Do not forget to register your handlers on Client.Dnode
// before calling Dial() to prevent race conditions.
func (c *Client) Dial(serverURL string) error {
	var err error

	if c.Config.Location, err = url.Parse(serverURL); err != nil {
		return err
	}

	if err = c.dial(); err != nil {
		return err
	}

	go c.run()

	return nil
}

// dial makes a single Dial() and run onConnectHandlers if connects.
func (c *Client) dial() error {
	ws, err := websocket.DialConfig(c.Config)
	if err != nil {
		return err
	}

	// We are connected
	c.Conn = ws

	// Reset the wait time.
	c.redialDuration = redialDurationStart

	// Must be run in a goroutine because a handler may wait a response from
	// server.
	go c.callOnConnectHandlers()

	return nil
}

// DialForever connects to the server in background.
// If the connection drops, it reconnects again.
func (c *Client) DialForever(serverURL string) (connected chan bool, err error) {
	c.Reconnect = true

	connected = make(chan bool, 1)

	if c.Config.Location, err = url.Parse(serverURL); err != nil {
		return
	}

	go c.dialForever(connected)

	return
}

func (c *Client) dialForever(connectNotifyChan chan bool) {
	for c.dial() != nil {
		if !c.Reconnect {
			return
		}

		c.sleep()
	}

	close(connectNotifyChan) // This is executed only once.

	go c.run()
}

// run consumes incoming dnode messages. Reconnects if necessary.
func (c *Client) run() (err error) {
	for {
	running:
		err = c.dnode.Run()
		c.callOnDisconnectHandlers()
	dialAgain:
		if !c.Reconnect {
			break
		}

		err = c.dial()
		if err != nil {
			c.sleep()
			goto dialAgain
		}

		goto running
	}

	return err
}

// sleep is used to wait for a while between dial retries.
// Each time it is called the redialDuration is incremented.
func (c *Client) sleep() {
	time.Sleep(c.redialDuration)

	c.redialDuration *= 2
	if c.redialDuration > redialDurationMax {
		c.redialDuration = redialDurationMax
	}
}

// Close closes the underlying websocket connection.
func (c *Client) Close() {
	c.Reconnect = false
	if c.Conn != nil {
		c.Conn.Close()
	}
}

func (c *Client) Send(msg []byte) error {
	if os.Getenv("DNODE_PRINT_SEND") != "" {
		fmt.Fprintf(os.Stderr, "\nSending: %s\n", string(msg))
	}

	if c.Conn == nil {
		return errors.New("not connected")
	}

	return websocket.Message.Send(c.Conn, string(msg))
}

func (c *Client) Receive() ([]byte, error) {
	if c.Conn == nil {
		return nil, errors.New("not connected")
	}

	var msg []byte
	err := websocket.Message.Receive(c.Conn, &msg)

	if os.Getenv("DNODE_PRINT_RECV") != "" {
		fmt.Fprintf(os.Stderr, "\nReceived: %s\n", string(msg))
	}

	return msg, err
}

func (c *Client) RemoveCallback(id uint64) {
	c.dnode.RemoveCallback(id)
}

// RemoteAddr returns the host:port as string if server connection.
func (c *Client) RemoteAddr() string {
	if c.Conn.IsServerConn() {
		return c.Conn.Request().RemoteAddr
	}
	return ""
}

func (c *Client) Properties() map[string]interface{} {
	return c.properties
}

// Call calls a method with args on the dnode server.
func (c *Client) Call(method string, args ...interface{}) (map[string]dnode.Path, error) {
	return c.dnode.Call(method, args...)
}

// OnConnect registers a function to run on client connect.
func (c *Client) OnConnect(handler func()) {
	c.m.Lock()
	c.onConnectHandlers = append(c.onConnectHandlers, handler)
	c.m.Unlock()
}

// OnDisconnect registers a function to run on client disconnect.
func (c *Client) OnDisconnect(handler func()) {
	c.m.Lock()
	c.onDisconnectHandlers = append(c.onDisconnectHandlers, handler)
	c.m.Unlock()
}

// callOnConnectHandlers runs the registered connect handlers.
func (c *Client) callOnConnectHandlers() {
	c.m.RLock()
	for _, handler := range c.onConnectHandlers {
		func() {
			defer recover()
			handler()
		}()
	}
	c.m.RUnlock()
}

// callOnDisconnectHandlers runs the registered disconnect handlers.
func (c *Client) callOnDisconnectHandlers() {
	c.m.RLock()
	for _, handler := range c.onDisconnectHandlers {
		func() {
			defer recover()
			handler()
		}()
	}
	c.m.RUnlock()
}
<commit_msg>If no port number in host, append 80 or 443 depending on the scheme<commit_after>package rpc

import (
	"errors"
	"fmt"
	"net"
	"net/url"
	"os"
	"strings"
	"sync"
	"time"

	"code.google.com/p/go.net/websocket"
	"github.com/koding/kite/dnode"
)

const redialDurationStart = 1 * time.Second
const redialDurationMax = 60 * time.Second

// Dial is a helper for creating a Client for just calling methods on the server.
// Do not use it if you want to handle methods on client side. Instead create a
// new Client, register your methods on Client.Dnode then call Client.Dial().
func Dial(url string, reconnect bool) (*Client, error) {
	c := NewClient()
	c.Reconnect = reconnect

	err := c.Dial(url)
	if err != nil {
		return nil, err
	}

	return c, nil
}

// Client is a dnode RPC client.
type Client struct {
	// Websocket connection
	Conn *websocket.Conn

	// Websocket connection options.
	Config *websocket.Config

	// Dnode message processor.
	dnode *dnode.Dnode

	// A space for saving/reading extra properties about this client.
	properties map[string]interface{}

	// Should we reconnect if disconnected?
	Reconnect bool

	// Time to wait before redial connection.
	redialDuration time.Duration

	// on connect/disconnect handlers are invoked after every
	// connect/disconnect.
	onConnectHandlers    []func()
	onDisconnectHandlers []func()

	// For protecting access over OnConnect and OnDisconnect handlers.
	m sync.RWMutex
}

// NewClient returns a pointer to new Client.
// You need to call Dial() before interacting with the Server.
func NewClient() *Client {
	// Must send an "Origin" header. Does not checked on server.
	origin, _ := url.Parse("")

	config := &websocket.Config{
		Version: websocket.ProtocolVersionHybi13,
		Origin:  origin,
		// Location will be set when dialing.
	}

	c := &Client{
		properties:     make(map[string]interface{}),
		redialDuration: redialDurationStart,
		Config:         config,
	}

	c.dnode = dnode.New(c)
	return c
}

func (c *Client) SetWrappers(wrapMethodArgs, wrapCallbackArgs dnode.Wrapper, runMethod, runCallback dnode.Runner, onError func(error)) {
	c.dnode.WrapMethodArgs = wrapMethodArgs
	c.dnode.WrapCallbackArgs = wrapCallbackArgs
	c.dnode.RunMethod = runMethod
	c.dnode.RunCallback = runCallback
	c.dnode.OnError = onError
}

// Dial connects to the dnode server on "url" and starts a goroutine
// that processes incoming messages.
//
// Do not forget to register your handlers on Client.Dnode
// before calling Dial() to prevent race conditions.
func (c *Client) Dial(serverURL string) error {
	var err error

	if c.Config.Location, err = url.Parse(serverURL); err != nil {
		return err
	}

	if err = c.dial(); err != nil {
		return err
	}

	go c.run()

	return nil
}

// dial makes a single Dial() and run onConnectHandlers if connects.
func (c *Client) dial() error {
	// If no port number in host, append 80 or 443 depending on the scheme
	_, _, err := net.SplitHostPort(c.Config.Location.Host)
	if err != nil {
		if err2, ok := err.(*net.AddrError); ok && err2.Err == "missing port in address" {
			var port string
			switch c.Config.Location.Scheme {
			case "ws":
				port = "80"
			case "wss":
				port = "443"
			default:
				panic("unexpected scheme: " + c.Config.Location.Scheme)
			}
			c.Config.Location.Host = net.JoinHostPort(strings.TrimRight(err2.Addr, ":"), port)
		} else {
			return err // Other kind of error
		}
	}

	ws, err := websocket.DialConfig(c.Config)
	if err != nil {
		return err
	}

	// We are connected
	c.Conn = ws

	// Reset the wait time.
	c.redialDuration = redialDurationStart

	// Must be run in a goroutine because a handler may wait a response from
	// server.
	go c.callOnConnectHandlers()

	return nil
}

// DialForever connects to the server in background.
// If the connection drops, it reconnects again.
func (c *Client) DialForever(serverURL string) (connected chan bool, err error) {
	c.Reconnect = true

	connected = make(chan bool, 1)

	if c.Config.Location, err = url.Parse(serverURL); err != nil {
		return
	}

	go c.dialForever(connected)

	return
}

func (c *Client) dialForever(connectNotifyChan chan bool) {
	for c.dial() != nil {
		if !c.Reconnect {
			return
		}

		c.sleep()
	}

	close(connectNotifyChan) // This is executed only once.

	go c.run()
}

// run consumes incoming dnode messages. Reconnects if necessary.
func (c *Client) run() (err error) {
	for {
	running:
		err = c.dnode.Run()
		c.callOnDisconnectHandlers()
	dialAgain:
		if !c.Reconnect {
			break
		}

		err = c.dial()
		if err != nil {
			c.sleep()
			goto dialAgain
		}

		goto running
	}

	return err
}

// sleep is used to wait for a while between dial retries.
// Each time it is called the redialDuration is incremented.
func (c *Client) sleep() {
	time.Sleep(c.redialDuration)

	c.redialDuration *= 2
	if c.redialDuration > redialDurationMax {
		c.redialDuration = redialDurationMax
	}
}

// Close closes the underlying websocket connection.
func (c *Client) Close() {
	c.Reconnect = false
	if c.Conn != nil {
		c.Conn.Close()
	}
}

func (c *Client) Send(msg []byte) error {
	if os.Getenv("DNODE_PRINT_SEND") != "" {
		fmt.Fprintf(os.Stderr, "\nSending: %s\n", string(msg))
	}

	if c.Conn == nil {
		return errors.New("not connected")
	}

	return websocket.Message.Send(c.Conn, string(msg))
}

func (c *Client) Receive() ([]byte, error) {
	if c.Conn == nil {
		return nil, errors.New("not connected")
	}

	var msg []byte
	err := websocket.Message.Receive(c.Conn, &msg)

	if os.Getenv("DNODE_PRINT_RECV") != "" {
		fmt.Fprintf(os.Stderr, "\nReceived: %s\n", string(msg))
	}

	return msg, err
}

func (c *Client) RemoveCallback(id uint64) {
	c.dnode.RemoveCallback(id)
}

// RemoteAddr returns the host:port as string if server connection.
func (c *Client) RemoteAddr() string {
	if c.Conn.IsServerConn() {
		return c.Conn.Request().RemoteAddr
	}
	return ""
}

func (c *Client) Properties() map[string]interface{} {
	return c.properties
}

// Call calls a method with args on the dnode server.
func (c *Client) Call(method string, args ...interface{}) (map[string]dnode.Path, error) {
	return c.dnode.Call(method, args...)
}

// OnConnect registers a function to run on client connect.
func (c *Client) OnConnect(handler func()) {
	c.m.Lock()
	c.onConnectHandlers = append(c.onConnectHandlers, handler)
	c.m.Unlock()
}

// OnDisconnect registers a function to run on client disconnect.
func (c *Client) OnDisconnect(handler func()) {
	c.m.Lock()
	c.onDisconnectHandlers = append(c.onDisconnectHandlers, handler)
	c.m.Unlock()
}

// callOnConnectHandlers runs the registered connect handlers.
func (c *Client) callOnConnectHandlers() {
	c.m.RLock()
	for _, handler := range c.onConnectHandlers {
		func() {
			defer recover()
			handler()
		}()
	}
	c.m.RUnlock()
}

// callOnDisconnectHandlers runs the registered disconnect handlers.
func (c *Client) callOnDisconnectHandlers() {
	c.m.RLock()
	for _, handler := range c.onDisconnectHandlers {
		func() {
			defer recover()
			handler()
		}()
	}
	c.m.RUnlock()
}
<|endoftext|>