<commit_before>package main

import (
	"bytes"
	"encoding/json"
	"io"
	"net/http"
	"strings"
	"sync"

	"github.com/tent/tent-client-go"
)

type request struct {
	req     *http.Request
	res     *http.Response
	reqBody *bytes.Buffer
	resBody *bytes.Buffer
}

type roundTripRecorder struct {
	roundTripper http.RoundTripper
	requests     []*request
	mtx          sync.Mutex
}

func (r *roundTripRecorder) RoundTrip(req *http.Request) (*http.Response, error) {
	reqBuf, resBuf := &bytes.Buffer{}, &bytes.Buffer{}
	if req.Body != nil {
		req.Body = readCloser{req.Body, io.TeeReader(req.Body, reqBuf)}
	}
	res, err := r.roundTripper.RoundTrip(req)
	if err != nil {
		return nil, err
	}
	log := &request{req, res, reqBuf, resBuf}
	res.Body = readCloser{res.Body, io.TeeReader(res.Body, resBuf)}
	r.mtx.Lock()
	r.requests = append(r.requests, log)
	r.mtx.Unlock()
	return res, nil
}

type readCloser struct {
	io.Closer
	io.Reader
}

var excludeHeaders = map[string]bool{
	"Host":              true,
	"Content-Length":    true,
	"Transfer-Encoding": true,
	"Trailer":           true,
	"User-Agent":        true,
}

func requestMarkdown(r *request) string {
	buf := &bytes.Buffer{}

	// request headers
	buf.Write([]byte("```text\n"))
	buf.WriteString(r.req.Method)
	buf.WriteByte(' ')

	uri := r.req.URL.RequestURI()
	if strings.HasPrefix(uri, "http") {
		uri = "/" + strings.SplitN(uri[8:], "/", 2)[1]
	}
	buf.WriteString(uri)

	buf.WriteString(" HTTP/1.1\n")
	r.req.Header.WriteSubset(buf, excludeHeaders)
	buf.Write([]byte("\n```\n"))

	// request body
	if r.req.Body != nil {
		buf.Write([]byte("```json\n"))
		json.Indent(buf, r.reqBody.Bytes(), "", "  ")
		buf.Write([]byte("\n```\n"))
	}

	// response headers
	buf.Write([]byte("\n```text\n"))
	buf.WriteString(r.res.Proto)
	buf.WriteByte(' ')
	buf.WriteString(r.res.Status)
	buf.Write([]byte("\n"))
	r.res.Header.WriteSubset(buf, excludeHeaders)
	buf.Write([]byte("\n```\n"))

	// response body
	resBody := r.resBody.Bytes()
	if len(resBody) > 0 {
		buf.Write([]byte("```json\n"))
		json.Indent(buf, resBody, "", "  ")
		buf.Write([]byte("\n```\n"))
	}

	return buf.String()
}

func getRequests() []*request {
	t := tent.HTTP.Transport.(*roundTripRecorder)
	reqs := t.requests
	t.requests = t.requests[:0]
	return reqs
}
<commit_msg>Fence plaintext bodies properly<commit_after>package main

import (
	"bytes"
	"encoding/json"
	"io"
	"net/http"
	"strings"
	"sync"

	"github.com/tent/tent-client-go"
)

type request struct {
	req     *http.Request
	res     *http.Response
	reqBody *bytes.Buffer
	resBody *bytes.Buffer
}

type roundTripRecorder struct {
	roundTripper http.RoundTripper
	requests     []*request
	mtx          sync.Mutex
}

func (r *roundTripRecorder) RoundTrip(req *http.Request) (*http.Response, error) {
	reqBuf, resBuf := &bytes.Buffer{}, &bytes.Buffer{}
	if req.Body != nil {
		req.Body = readCloser{req.Body, io.TeeReader(req.Body, reqBuf)}
	}
	res, err := r.roundTripper.RoundTrip(req)
	if err != nil {
		return nil, err
	}
	log := &request{req, res, reqBuf, resBuf}
	res.Body = readCloser{res.Body, io.TeeReader(res.Body, resBuf)}
	r.mtx.Lock()
	r.requests = append(r.requests, log)
	r.mtx.Unlock()
	return res, nil
}

type readCloser struct {
	io.Closer
	io.Reader
}

var excludeHeaders = map[string]bool{
	"Host":              true,
	"Content-Length":    true,
	"Transfer-Encoding": true,
	"Trailer":           true,
	"User-Agent":        true,
}

func requestMarkdown(r *request) string {
	buf := &bytes.Buffer{}

	// request headers
	buf.Write([]byte("```text\n"))
	buf.WriteString(r.req.Method)
	buf.WriteByte(' ')

	uri := r.req.URL.RequestURI()
	if strings.HasPrefix(uri, "http") {
		uri = "/" + strings.SplitN(uri[8:], "/", 2)[1]
	}
	buf.WriteString(uri)

	buf.WriteString(" HTTP/1.1\n")
	r.req.Header.WriteSubset(buf, excludeHeaders)
	buf.Write([]byte("\n```\n"))

	// request body
	if r.req.Body != nil {
		fence(buf, r.reqBody.Bytes())
	}

	// response headers
	buf.Write([]byte("\n```text\n"))
	buf.WriteString(r.res.Proto)
	buf.WriteByte(' ')
	buf.WriteString(r.res.Status)
	buf.Write([]byte("\n"))
	r.res.Header.WriteSubset(buf, excludeHeaders)
	buf.Write([]byte("\n```\n"))

	// response body
	resBody := r.resBody.Bytes()
	if len(resBody) > 0 {
		fence(buf, resBody)
	}

	return buf.String()
}

func fence(buf *bytes.Buffer, data []byte) {
	if len(data) > 0 && data[0] == '{' {
		buf.Write([]byte("```json\n"))
		json.Indent(buf, data, "", "  ")
	} else {
		buf.Write([]byte("```text\n"))
		buf.Write(data)
	}
	buf.Write([]byte("\n```\n"))
}

func getRequests() []*request {
	t := tent.HTTP.Transport.(*roundTripRecorder)
	reqs := t.requests
	t.requests = t.requests[:0]
	return reqs
}
<|endoftext|>


<commit_before>package ext

import (
	"encoding/json"
	"io"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"os"
)

func PerformHttpRequest(r http.Handler, method, path string, header http.Header, reqBody io.Reader) *httptest.ResponseRecorder {
	req, _ := http.NewRequest(method, path, reqBody)
	if header != nil {
		req.Header = header
	}
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)
	return w
}

func DecodeJson(r io.Reader) map[string]interface{} {
	var m map[string]interface{}
	decoder := json.NewDecoder(r)
	err := decoder.Decode(&m)
	if err == nil && len(m) > 0 {
		return m
	}
	return nil
}

func ReadMockFile(filepath string) string {
	file, err := os.Open(filepath) // For read access.
	if err != nil {
		return ""
	}
	defer file.Close()

	data, err := ioutil.ReadAll(file)
	if err != nil {
		return ""
	}
	sdata := string(data)
	return sdata
}
<commit_msg>DecodeJSON return error<commit_after>package ext

import (
	"encoding/json"
	"errors"
	"io"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"os"
)

func PerformHttpRequest(r http.Handler, method, path string, header http.Header, reqBody io.Reader) *httptest.ResponseRecorder {
	req, _ := http.NewRequest(method, path, reqBody)
	if header != nil {
		req.Header = header
	}
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)
	return w
}

func DecodeJSON(r io.Reader) (map[string]interface{}, error) {
	var m map[string]interface{}
	decoder := json.NewDecoder(r)
	err := decoder.Decode(&m)
	if err != nil {
		return nil, err
	}

	if len(m) <= 0 {
		return nil, errors.New("ext.DecodeJSON: no content")
	}

	return m, nil
}

func ReadMockFile(filepath string) string {
	file, err := os.Open(filepath) // For read access.
	if err != nil {
		return ""
	}
	defer file.Close()

	data, err := ioutil.ReadAll(file)
	if err != nil {
		return ""
	}
	sdata := string(data)
	return sdata
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"net"
	"net/http"
	"sort"
	"strconv"
	"strings"
	"time"

	"github.com/litl/galaxy/utils"

	"github.com/litl/galaxy/log"
	gotoolslog "github.com/mailgun/gotools-log"
	"github.com/mailgun/vulcan"
	"github.com/mailgun/vulcan/endpoint"
	"github.com/mailgun/vulcan/loadbalance/roundrobin"
	"github.com/mailgun/vulcan/location/httploc"
	"github.com/mailgun/vulcan/request"
	"github.com/mailgun/vulcan/route"
	"github.com/mailgun/vulcan/route/hostroute"
)

var (
	httpRouter *HTTPRouter
)

type RequestLogger struct{}

type HTTPRouter struct {
	router    *hostroute.HostRouter
	balancers map[string]*roundrobin.RoundRobin
}

func (r *RequestLogger) ObserveRequest(req request.Request) {}

func (r *RequestLogger) ObserveResponse(req request.Request, a request.Attempt) {
	err := ""
	statusCode := ""
	if a.GetError() != nil {
		err = " err=" + a.GetError().Error()
	}

	if a.GetResponse() != nil {
		statusCode = " status=" + strconv.FormatInt(int64(a.GetResponse().StatusCode), 10)
	}

	log.Printf("id=%d method=%s clientIp=%s url=%s backend=%s%s duration=%s%s",
		req.GetId(),
		req.GetHttpRequest().Method,
		req.GetHttpRequest().RemoteAddr,
		req.GetHttpRequest().Host+req.GetHttpRequest().RequestURI,
		a.GetEndpoint(),
		statusCode, a.GetDuration(), err)
}

func NewHTTPRouter() *HTTPRouter {
	return &HTTPRouter{
		balancers: make(map[string]*roundrobin.RoundRobin),
	}
}

func (s *HTTPRouter) AddBackend(name, vhost, url string) error {

	var err error
	balancer := s.balancers[vhost]

	if balancer == nil {
		// Create a round robin load balancer with some endpoints
		balancer, err = roundrobin.NewRoundRobin()
		if err != nil {
			return err
		}

		// Create a http location with the load balancer we've just added
		loc, err := httploc.NewLocationWithOptions(name, balancer,
			httploc.Options{
				TrustForwardHeader: true,
			})
		if err != nil {
			return err
		}
		loc.GetObserverChain().Add("logger", &RequestLogger{})

		s.router.SetRouter(vhost, &route.ConstRouter{Location: loc})
		log.Printf("Starting HTTP listener for %s", vhost)
		s.balancers[vhost] = balancer
	}

	// Already registered?
	if balancer.FindEndpointByUrl(url) != nil {
		return nil
	}
	endpoint := endpoint.MustParseUrl(url)
	log.Printf("Adding HTTP endpoint %s to %s", endpoint.GetUrl(), vhost)
	err = balancer.AddEndpoint(endpoint)
	if err != nil {
		return err
	}
	return nil
}

func (s *HTTPRouter) RemoveBackend(vhost, url string) error {
	balancer := s.balancers[vhost]
	if balancer == nil {
		return nil
	}

	endpoint := balancer.FindEndpointByUrl(url)
	if endpoint == nil {
		return nil
	}
	log.Printf("Removing HTTP endpoint %s from %s ", endpoint.GetUrl(), vhost)
	balancer.RemoveEndpoint(endpoint)

	endpoints := balancer.GetEndpoints()
	if len(endpoints) == 0 {
		s.RemoveRouter(vhost)
	}
	return nil
}

// Remove all backends for vhost that are not listed in addrs
func (s *HTTPRouter) RemoveBackends(vhost string, addrs []string) {
	// Remove backends that are no longer registered

	balancer := s.balancers[vhost]
	if balancer == nil {
		return
	}

	endpoints := balancer.GetEndpoints()
	for _, endpoint := range endpoints {
		if !utils.StringInSlice(endpoint.GetUrl().String(), addrs) {
			s.RemoveBackend(vhost, endpoint.GetUrl().String())
		}
	}
}

// Removes a virtual host router
func (s *HTTPRouter) RemoveRouter(vhost string) {
	log.Printf("Removing balancer for %s", vhost)
	delete(s.balancers, vhost)
	s.router.RemoveRouter(vhost)
}

func (s *HTTPRouter) adminHandler(w http.ResponseWriter, r *http.Request) {
	if len(s.balancers) == 0 {
		w.WriteHeader(503)
		return
	}

	keys := make([]string, 0, len(s.balancers))
	for key := range s.balancers {
		keys = append(keys, key)
	}
	sort.Strings(keys)

	for _, k := range keys {
		balancer := s.balancers[k]
		endpoints := balancer.GetEndpoints()
		fmt.Fprintf(w, "%s\n", k)
		for _, endpoint := range endpoints {
			fmt.Fprintf(w, "  %s\t%d\t%d\t%0.2f\n", endpoint.GetUrl(), endpoint.GetOriginalWeight(), endpoint.GetEffectiveWeight(), endpoint.GetMeter().GetRate())
		}
	}
}

func (s *HTTPRouter) statusHandler(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		var err error
		host := r.Host
		if strings.Contains(host, ":") {
			host, _, err = net.SplitHostPort(r.Host)
			if err != nil {
				log.Warnf("%s", err)
				h.ServeHTTP(w, r)
				return
			}
		}

		if _, exists := s.balancers[host]; !exists {
			s.adminHandler(w, r)
			return
		}
		h.ServeHTTP(w, r)
	})
}

func (s *HTTPRouter) Start() {

	// init the vulcan logging
	gotoolslog.Init([]*gotoolslog.LogConfig{
		&gotoolslog.LogConfig{Name: "console"},
	})

	log.Printf("HTTP server listening at %s", listenAddr)

	s.router = hostroute.NewHostRouter()

	proxy, err := vulcan.NewProxy(s.router)
	if err != nil {
		log.Fatalf("ERROR: %s", err)
	}

	// Proxy acts as http handler:
	server := &http.Server{
		Addr:           listenAddr,
		Handler:        s.statusHandler(proxy),
		ReadTimeout:    60 * time.Second,
		WriteTimeout:   60 * time.Second,
		MaxHeaderBytes: 1 << 20,
	}

	log.Errorf("%s", server.ListenAndServe())
}

func startHTTPServer() {
	defer wg.Done()
	httpRouter = NewHTTPRouter()
	httpRouter.Start()
}
<commit_msg>Skip blank vhost entries<commit_after>package main

import (
	"fmt"
	"net"
	"net/http"
	"sort"
	"strconv"
	"strings"
	"time"

	"github.com/litl/galaxy/utils"

	"github.com/litl/galaxy/log"
	gotoolslog "github.com/mailgun/gotools-log"
	"github.com/mailgun/vulcan"
	"github.com/mailgun/vulcan/endpoint"
	"github.com/mailgun/vulcan/loadbalance/roundrobin"
	"github.com/mailgun/vulcan/location/httploc"
	"github.com/mailgun/vulcan/request"
	"github.com/mailgun/vulcan/route"
	"github.com/mailgun/vulcan/route/hostroute"
)

var (
	httpRouter *HTTPRouter
)

type RequestLogger struct{}

type HTTPRouter struct {
	router    *hostroute.HostRouter
	balancers map[string]*roundrobin.RoundRobin
}

func (r *RequestLogger) ObserveRequest(req request.Request) {}

func (r *RequestLogger) ObserveResponse(req request.Request, a request.Attempt) {
	err := ""
	statusCode := ""
	if a.GetError() != nil {
		err = " err=" + a.GetError().Error()
	}

	if a.GetResponse() != nil {
		statusCode = " status=" + strconv.FormatInt(int64(a.GetResponse().StatusCode), 10)
	}

	log.Printf("id=%d method=%s clientIp=%s url=%s backend=%s%s duration=%s%s",
		req.GetId(),
		req.GetHttpRequest().Method,
		req.GetHttpRequest().RemoteAddr,
		req.GetHttpRequest().Host+req.GetHttpRequest().RequestURI,
		a.GetEndpoint(),
		statusCode, a.GetDuration(), err)
}

func NewHTTPRouter() *HTTPRouter {
	return &HTTPRouter{
		balancers: make(map[string]*roundrobin.RoundRobin),
	}
}

func (s *HTTPRouter) AddBackend(name, vhost, url string) error {

	if vhost == "" || url == "" {
		return nil
	}

	var err error
	balancer := s.balancers[vhost]

	if balancer == nil {
		// Create a round robin load balancer with some endpoints
		balancer, err = roundrobin.NewRoundRobin()
		if err != nil {
			return err
		}

		// Create a http location with the load balancer we've just added
		loc, err := httploc.NewLocationWithOptions(name, balancer,
			httploc.Options{
				TrustForwardHeader: true,
			})
		if err != nil {
			return err
		}
		loc.GetObserverChain().Add("logger", &RequestLogger{})

		s.router.SetRouter(vhost, &route.ConstRouter{Location: loc})
		log.Printf("Starting HTTP listener for %s", vhost)
		s.balancers[vhost] = balancer
	}

	// Already registered?
	if balancer.FindEndpointByUrl(url) != nil {
		return nil
	}
	endpoint := endpoint.MustParseUrl(url)
	log.Printf("Adding HTTP endpoint %s to %s", endpoint.GetUrl(), vhost)
	err = balancer.AddEndpoint(endpoint)
	if err != nil {
		return err
	}
	return nil
}

func (s *HTTPRouter) RemoveBackend(vhost, url string) error {

	if vhost == "" || url == "" {
		return nil
	}

	balancer := s.balancers[vhost]
	if balancer == nil {
		return nil
	}

	endpoint := balancer.FindEndpointByUrl(url)
	if endpoint == nil {
		return nil
	}
	log.Printf("Removing HTTP endpoint %s from %s ", endpoint.GetUrl(), vhost)
	balancer.RemoveEndpoint(endpoint)

	endpoints := balancer.GetEndpoints()
	if len(endpoints) == 0 {
		s.RemoveRouter(vhost)
	}
	return nil
}

// Remove all backends for vhost that are not listed in addrs
func (s *HTTPRouter) RemoveBackends(vhost string, addrs []string) {

	if vhost == "" {
		return
	}

	// Remove backends that are no longer registered

	balancer := s.balancers[vhost]
	if balancer == nil {
		return
	}

	endpoints := balancer.GetEndpoints()
	for _, endpoint := range endpoints {
		if !utils.StringInSlice(endpoint.GetUrl().String(), addrs) {
			s.RemoveBackend(vhost, endpoint.GetUrl().String())
		}
	}
}

// Removes a virtual host router
func (s *HTTPRouter) RemoveRouter(vhost string) {
	if vhost == "" {
		return
	}

	log.Printf("Removing balancer for %s", vhost)
	delete(s.balancers, vhost)
	s.router.RemoveRouter(vhost)
}

func (s *HTTPRouter) adminHandler(w http.ResponseWriter, r *http.Request) {
	if len(s.balancers) == 0 {
		w.WriteHeader(503)
		return
	}

	keys := make([]string, 0, len(s.balancers))
	for key := range s.balancers {
		keys = append(keys, key)
	}
	sort.Strings(keys)

	for _, k := range keys {
		balancer := s.balancers[k]
		endpoints := balancer.GetEndpoints()
		fmt.Fprintf(w, "%s\n", k)
		for _, endpoint := range endpoints {
			fmt.Fprintf(w, "  %s\t%d\t%d\t%0.2f\n", endpoint.GetUrl(), endpoint.GetOriginalWeight(), endpoint.GetEffectiveWeight(), endpoint.GetMeter().GetRate())
		}
	}
}

func (s *HTTPRouter) statusHandler(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		var err error
		host := r.Host
		if strings.Contains(host, ":") {
			host, _, err = net.SplitHostPort(r.Host)
			if err != nil {
				log.Warnf("%s", err)
				h.ServeHTTP(w, r)
				return
			}
		}

		if _, exists := s.balancers[host]; !exists {
			s.adminHandler(w, r)
			return
		}
		h.ServeHTTP(w, r)
	})
}

func (s *HTTPRouter) Start() {

	// init the vulcan logging
	gotoolslog.Init([]*gotoolslog.LogConfig{
		&gotoolslog.LogConfig{Name: "console"},
	})

	log.Printf("HTTP server listening at %s", listenAddr)

	s.router = hostroute.NewHostRouter()

	proxy, err := vulcan.NewProxy(s.router)
	if err != nil {
		log.Fatalf("ERROR: %s", err)
	}

	// Proxy acts as http handler:
	server := &http.Server{
		Addr:           listenAddr,
		Handler:        s.statusHandler(proxy),
		ReadTimeout:    60 * time.Second,
		WriteTimeout:   60 * time.Second,
		MaxHeaderBytes: 1 << 20,
	}

	log.Errorf("%s", server.ListenAndServe())
}

func startHTTPServer() {
	defer wg.Done()
	httpRouter = NewHTTPRouter()
	httpRouter.Start()
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"crypto/tls"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"strconv"
	"strings"
	"sync"
	"time"
)

var (
	invalidContnetSize = errors.New("invalid content size")
)

type LengthError error
type ConnectError error
type ReceiveError error
type ExceptionError error
type ResponseError error

type HttpWorker struct {
	config *Config
	start  *sync.WaitGroup
	stop   chan bool

	client    *http.Client
	jobs      chan *http.Request
	collector chan *Record
	readBuf   *bytes.Buffer
}

func NewHttpWorker(config *Config, start *sync.WaitGroup, stop chan bool, jobs chan *http.Request, collector chan *Record) *HttpWorker {
	return &HttpWorker{config, start, stop, NewClient(config), jobs, collector, bytes.NewBuffer(make([]byte, 0, bytes.MinRead))}
}

func (h *HttpWorker) Run() {
	h.start.Done()
	h.start.Wait()

	for job := range h.jobs {

		asyncResult := h.send(job)
		timeout := time.NewTimer(time.Duration(MAX_RESPONSE_TIMEOUT) * time.Second)

		select {
		case record := <-asyncResult:
			h.collector <- record

		case <-timeout.C:
			h.collector <- &Record{Error: errors.New("execution timeout")}

		case <-h.stop:
			timeout.Stop()
			return
		}
		timeout.Stop()
	}
}

func (h *HttpWorker) send(request *http.Request) (asyncResult chan *Record) {

	asyncResult = make(chan *Record)
	go func() {
		record := &Record{}
		sw := &StopWatch{}
		sw.Start()

		var contentSize int64

		defer func() {
			if r := recover(); r != nil {
				if Err, ok := r.(error); ok {
					record.Error = Err
				} else {
					record.Error = ExceptionError(errors.New(fmt.Sprint(r)))
				}

			} else {
				record.contentSize = contentSize
				record.responseTime = sw.Elapsed
			}

			if record.Error != nil {
				TraceException(record.Error)
			}

			asyncResult <- record
		}()

		resp, err := h.client.Do(request)
		if err != nil {
			record.Error = ConnectError(err)
			return
		} else {
			defer resp.Body.Close()

			if resp.StatusCode < 200 || resp.StatusCode > 300 {
				record.Error = ResponseError(err)
				return
			}
			defer h.readBuf.Reset()
			contentSize, err := h.readBuf.ReadFrom(resp.Body)
			if err != nil {
				record.Error = ReceiveError(err)
				return
			}

			expectedContentSize := 0
			headerContentSize := resp.Header.Get("Content-Length")

			if headerContentSize != "" {
				expectedContentSize, _ = strconv.Atoi(headerContentSize)
			} else {
				expectedContentSize = h.config.contentSize
			}

			if int64(expectedContentSize) != contentSize {
				record.Error = LengthError(invalidContnetSize)
				return
			}

		}

		sw.Stop()
	}()
	return asyncResult
}

func detectHost(config *Config) error {
	var err error
	defer func() {
		if r := recover(); r != nil {
			TraceException(r)
		}
	}()

	client := NewClient(config)
	reqeust, err := NewHttpRequest(config)
	if err != nil {
		return err
	}

	resp, err := client.Do(reqeust)

	if err != nil {
		return err
	} else {

		defer resp.Body.Close()
		body, _ := ioutil.ReadAll(resp.Body)

		// TODO: place on another context
		config.serverName = resp.Header.Get("Server")
		headerContentSize := resp.Header.Get("Content-Length")

		if headerContentSize != "" {
			config.contentSize, _ = strconv.Atoi(headerContentSize)
		} else {
			config.contentSize = len(body)
		}
	}

	return nil
}

func NewClient(config *Config) *http.Client {

	// skip certification check for self-signed certificates
	tlsconfig := &tls.Config{
		InsecureSkipVerify: true,
	}

	// TODO: timeout control
	// TODO: tcp options
	// TODO: monitor tcp metrics
	transport := &http.Transport{
		DisableCompression: !config.gzip,
		DisableKeepAlives:  !config.keepAlive,
		TLSClientConfig:    tlsconfig,
	}

	client := &http.Client{Transport: transport}
	return client
}

func NewHttpRequest(config *Config) (*http.Request, error) {

	var body io.Reader
	var err error

	if (config.method == "POST" || config.method == "PUT") && config.bodyFile != nil {

		body = bytes.NewReader(config.bodyFile)
	}

	request, err := http.NewRequest(config.method, config.url, body)

	if err != nil {
		return nil, err
	}

	if body != nil && config.contentType == "text/plain" {
		request.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	} else {
		request.Header.Set("Content-Type", config.contentType)
	}

	request.Header.Set("User-Agent", config.userAgent)

	if config.keepAlive {
		request.Header.Set("Connection", "keep-alive")
	}

	for _, header := range config.headers {
		pair := strings.Split(header, ":")
		request.Header.Add(pair[0], pair[1])
	}

	for _, cookie := range config.cookies {
		pair := strings.Split(cookie, "=")
		c := &http.Cookie{Name: pair[0], Value: pair[1]}
		request.AddCookie(c)
	}

	if config.basicAuthentication != "" {
		pair := strings.Split(config.basicAuthentication, ":")
		request.SetBasicAuth(pair[0], pair[1])
	}

	return request, err
}
<commit_msg>finetine readbuffer size<commit_after>package main

import (
	"bytes"
	"crypto/tls"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"strconv"
	"strings"
	"sync"
	"time"
)

var (
	invalidContnetSize = errors.New("invalid content size")
)

type LengthError error
type ConnectError error
type ReceiveError error
type ExceptionError error
type ResponseError error

type HttpWorker struct {
	config *Config
	start  *sync.WaitGroup
	stop   chan bool

	client    *http.Client
	jobs      chan *http.Request
	collector chan *Record
	readBuf   *bytes.Buffer
}

func NewHttpWorker(config *Config, start *sync.WaitGroup, stop chan bool, jobs chan *http.Request, collector chan *Record) *HttpWorker {
	return &HttpWorker{config, start, stop, NewClient(config), jobs, collector, bytes.NewBuffer(make([]byte, 0, config.contentSize+bytes.MinRead))}
}

func (h *HttpWorker) Run() {
	h.start.Done()
	h.start.Wait()

	for job := range h.jobs {

		asyncResult := h.send(job)
		timeout := time.NewTimer(time.Duration(MAX_RESPONSE_TIMEOUT) * time.Second)

		select {
		case record := <-asyncResult:
			h.collector <- record

		case <-timeout.C:
			h.collector <- &Record{Error: errors.New("execution timeout")}

		case <-h.stop:
			timeout.Stop()
			return
		}
		timeout.Stop()
	}
}

func (h *HttpWorker) send(request *http.Request) (asyncResult chan *Record) {

	asyncResult = make(chan *Record)
	go func() {
		record := &Record{}
		sw := &StopWatch{}
		sw.Start()

		var contentSize int64

		defer func() {
			if r := recover(); r != nil {
				if Err, ok := r.(error); ok {
					record.Error = Err
				} else {
					record.Error = ExceptionError(errors.New(fmt.Sprint(r)))
				}

			} else {
				record.contentSize = contentSize
				record.responseTime = sw.Elapsed
			}

			if record.Error != nil {
				TraceException(record.Error)
			}

			asyncResult <- record
		}()

		resp, err := h.client.Do(request)
		if err != nil {
			record.Error = ConnectError(err)
			return
		} else {
			defer resp.Body.Close()

			if resp.StatusCode < 200 || resp.StatusCode > 300 {
				record.Error = ResponseError(err)
				return
			}

			defer h.readBuf.Reset()
			contentSize, err := h.readBuf.ReadFrom(resp.Body)
			if err != nil {
				record.Error = ReceiveError(err)
				return
			}

			expectedContentSize := 0
			headerContentSize := resp.Header.Get("Content-Length")
			if headerContentSize != "" {
				expectedContentSize, _ = strconv.Atoi(headerContentSize)
			} else {
				expectedContentSize = h.config.contentSize
			}

			if int64(expectedContentSize) != contentSize {
				record.Error = LengthError(invalidContnetSize)
				return
			}

		}

		sw.Stop()
	}()
	return asyncResult
}

func detectHost(config *Config) error {
	var err error
	defer func() {
		if r := recover(); r != nil {
			TraceException(r)
		}
	}()

	client := NewClient(config)
	reqeust, err := NewHttpRequest(config)
	if err != nil {
		return err
	}

	resp, err := client.Do(reqeust)

	if err != nil {
		return err
	} else {

		defer resp.Body.Close()
		body, _ := ioutil.ReadAll(resp.Body)

		// TODO: place on another context
		config.serverName = resp.Header.Get("Server")
		headerContentSize := resp.Header.Get("Content-Length")

		if headerContentSize != "" {
			config.contentSize, _ = strconv.Atoi(headerContentSize)
		} else {
			config.contentSize = len(body)
		}
	}

	return nil
}

func NewClient(config *Config) *http.Client {

	// skip certification check for self-signed certificates
	tlsconfig := &tls.Config{
		InsecureSkipVerify: true,
	}

	// TODO: timeout control
	// TODO: tcp options
	// TODO: monitor tcp metrics
	transport := &http.Transport{
		DisableCompression: !config.gzip,
		DisableKeepAlives:  !config.keepAlive,
		TLSClientConfig:    tlsconfig,
	}

	client := &http.Client{Transport: transport}
	return client
}

func NewHttpRequest(config *Config) (*http.Request, error) {

	var body io.Reader
	var err error

	if (config.method == "POST" || config.method == "PUT") && config.bodyFile != nil {

		body = bytes.NewReader(config.bodyFile)
	}

	request, err := http.NewRequest(config.method, config.url, body)

	if err != nil {
		return nil, err
	}

	if body != nil && config.contentType == "text/plain" {
		request.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	} else {
		request.Header.Set("Content-Type", config.contentType)
	}

	request.Header.Set("User-Agent", config.userAgent)

	if config.keepAlive {
		request.Header.Set("Connection", "keep-alive")
	}

	for _, header := range config.headers {
		pair := strings.Split(header, ":")
		request.Header.Add(pair[0], pair[1])
	}

	for _, cookie := range config.cookies {
		pair := strings.Split(cookie, "=")
		c := &http.Cookie{Name: pair[0], Value: pair[1]}
		request.AddCookie(c)
	}

	if config.basicAuthentication != "" {
		pair := strings.Split(config.basicAuthentication, ":")
		request.SetBasicAuth(pair[0], pair[1])
	}

	return request, err
}
<|endoftext|>


<commit_before>package main

import (
	"io"
	"log"
	"net/http"
)

//curl -v http://web.archive.org/web/20160513214322/http:/justpaste.it/u8j7//
//*   Trying 207.241.224.26...
//* TCP_NODELAY set
//* Connected to web.archive.org (207.241.224.26) port 80 (#0)
//> GET /web/20160513214322/http:/justpaste.it/u8j7// HTTP/1.1
//> Host: web.archive.org
//> User-Agent: curl/7.55.1
//> Accept: */*
//>
//< HTTP/1.1 200 OK
//< Server: nginx/1.15.5
//< Date: Sat, 05 Jan 2019 00:49:43 GMT
//< Content-Type: text/html; charset=UTF-8
//< Content-Length: 35579
//< Connection: keep-alive
//< X-Archive-Orig-content-length: -1
//< X-Archive-Orig-strict-transport-security: max-age=15552000; includeSubDomains; preload
//< X-Archive-Orig-x-content-type-options: nosniff
//< X-Archive-Orig-set-cookie: PHPSESSID=ht1e342e9re2dvetp0o6n7e8oe8pbs3jdh31lpqd3ne7iuubo7j0; path=/; HttpOnly
//< X-Archive-Orig-expires: Thu, 19 Nov 1981 08:52:00 GMT
//< X-Archive-Orig-vary: Accept-Encoding
//< X-Archive-Orig-server: cloudflare-nginx
//< X-Archive-Orig-connection: close
//< X-Archive-Orig-pragma: no-cache
//< X-Archive-Orig-cache-control: no-store, no-cache, must-revalidate
//< X-Archive-Orig-date: Fri, 13 May 2016 21:43:22 GMT
//< X-Archive-Orig-cf-ray: 2a29491c158a1207-SJC
//< X-Archive-Guessed-Content-Type: text/html
//< X-Archive-Guessed-Charset: utf-8
//< Memento-Datetime: Fri, 13 May 2016 21:43:22 GMT
//< Link: <https://justpaste.it/u8j7>; rel="original", <http://web.archive.org/web/timemap/link/https://justpaste.it/u8j7>; rel="timemap"; type="application/link-format", <http://web.archive.org/web/https://justpaste.it/u8j7>; rel="timegate", <http://web.archive.org/web/20160513214322/https://justpaste.it/u8j7>; rel="first memento"; datetime="Fri, 13 May 2016 21:43:22 GMT", <http://web.archive.org/web/20160513214322/https://justpaste.it/u8j7>; rel="memento"; datetime="Fri, 13 May 2016 21:43:22 GMT", <http://web.archive.org/web/20160513214322/https://justpaste.it/u8j7>; rel="last memento"; datetime="Fri, 13 May 2016 21:43:22 GMT"
//< Content-Security-Policy: default-src 'self' 'unsafe-eval' 'unsafe-inline' data: blob: archive.org web.archive.org analytics.archive.org pragma.archivelab.org
//< X-Archive-Src: liveweb-20160514001702/live-20160513205105-wwwb-app11.us.archive.org.warc.gz
//< X-App-Server: wwwb-app12
//< X-ts: ----
//< X-location: All
//< X-Cache-Key: httpweb.archive.org/web/20160513214322/http:/justpaste.it/u8j7//CA
//< X-Page-Cache: MISS
//<

func main() {
	// Hello world, the web server

	helloHandler := func(w http.ResponseWriter, req *http.Request) {
        //w.Header().Set("Content-Type", "text/html; charset=utf-8")
        w.Header().Set("Link", `<https://justpaste.it/u8j7>; rel="original", <http://web.archive.org/web/timemap/link/https://justpaste.it/u8j7>; rel="timemap"; type="application/link-format", <http://web.archive.org/web/https://justpaste.it/u8j7>; rel="timegate", <http://web.archive.org/web/20160513214322/https://justpaste.it/u8j7>; rel="first memento"; datetime="Fri, 13 May 2016 21:43:22 GMT", <http://web.archive.org/web/20160513214322/https://justpaste.it/u8j7>; rel="memento"; datetime="Fri, 13 May 2016 21:43:22 GMT", <http://web.archive.org/web/20160513214322/https://justpaste.it/u8j7>; rel="last memento"; datetime="Fri, 13 May 2016 21:43:22 GMT"`)
        w.Header().Set("X-Cache-Key", `httpweb.archive.org/web/20160513214322/http:/justpaste.it/u8j7//CA`)
        w.Header().Set("X-Archive-Orig-vary", "Accept-Encoding")
        w.Header().Set("X-location", "All")
		io.WriteString(w, req.RequestURI + "\n")
	}

	http.HandleFunc("/", helloHandler)
	log.Fatal(http.ListenAndServe(":8081", nil))
}
<commit_msg>http skip cleanPath<commit_after>package main

import (
	"io"
	"log"
	"net/http"
)

//curl -v http://web.archive.org/web/20160513214322/http:/justpaste.it/u8j7//
//*   Trying 207.241.224.26...
//* TCP_NODELAY set
//* Connected to web.archive.org (207.241.224.26) port 80 (#0)
//> GET /web/20160513214322/http:/justpaste.it/u8j7// HTTP/1.1
//> Host: web.archive.org
//> User-Agent: curl/7.55.1
//> Accept: */*
//>
//< HTTP/1.1 200 OK
//< Server: nginx/1.15.5
//< Date: Sat, 05 Jan 2019 00:49:43 GMT
//< Content-Type: text/html; charset=UTF-8
//< Content-Length: 35579
//< Connection: keep-alive
//< X-Archive-Orig-content-length: -1
//< X-Archive-Orig-strict-transport-security: max-age=15552000; includeSubDomains; preload
//< X-Archive-Orig-x-content-type-options: nosniff
//< X-Archive-Orig-set-cookie: PHPSESSID=ht1e342e9re2dvetp0o6n7e8oe8pbs3jdh31lpqd3ne7iuubo7j0; path=/; HttpOnly
//< X-Archive-Orig-expires: Thu, 19 Nov 1981 08:52:00 GMT
//< X-Archive-Orig-vary: Accept-Encoding
//< X-Archive-Orig-server: cloudflare-nginx
//< X-Archive-Orig-connection: close
//< X-Archive-Orig-pragma: no-cache
//< X-Archive-Orig-cache-control: no-store, no-cache, must-revalidate
//< X-Archive-Orig-date: Fri, 13 May 2016 21:43:22 GMT
//< X-Archive-Orig-cf-ray: 2a29491c158a1207-SJC
//< X-Archive-Guessed-Content-Type: text/html
//< X-Archive-Guessed-Charset: utf-8
//< Memento-Datetime: Fri, 13 May 2016 21:43:22 GMT
//< Link: <https://justpaste.it/u8j7>; rel="original", <http://web.archive.org/web/timemap/link/https://justpaste.it/u8j7>; rel="timemap"; type="application/link-format", <http://web.archive.org/web/https://justpaste.it/u8j7>; rel="timegate", <http://web.archive.org/web/20160513214322/https://justpaste.it/u8j7>; rel="first memento"; datetime="Fri, 13 May 2016 21:43:22 GMT", <http://web.archive.org/web/20160513214322/https://justpaste.it/u8j7>; rel="memento"; datetime="Fri, 13 May 2016 21:43:22 GMT", <http://web.archive.org/web/20160513214322/https://justpaste.it/u8j7>; rel="last memento"; datetime="Fri, 13 May 2016 21:43:22 GMT"
//< Content-Security-Policy: default-src 'self' 'unsafe-eval' 'unsafe-inline' data: blob: archive.org web.archive.org analytics.archive.org pragma.archivelab.org
//< X-Archive-Src: liveweb-20160514001702/live-20160513205105-wwwb-app11.us.archive.org.warc.gz
//< X-App-Server: wwwb-app12
//< X-ts: ----
//< X-location: All
//< X-Cache-Key: httpweb.archive.org/web/20160513214322/http:/justpaste.it/u8j7//CA
//< X-Page-Cache: MISS
//<

func main1() {
	// Hello world, the web server

	helloHandler := func(w http.ResponseWriter, req *http.Request) {
        //w.Header().Set("Content-Type", "text/html; charset=utf-8")
//        w.Header().Set("Link", `<https://justpaste.it/u8j7>; rel="original", <http://web.archive.org/web/timemap/link/https://justpaste.it/u8j7>; rel="timemap"; type="application/link-format", <http://web.archive.org/web/https://justpaste.it/u8j7>; rel="timegate", <http://web.archive.org/web/20160513214322/https://justpaste.it/u8j7>; rel="first memento"; datetime="Fri, 13 May 2016 21:43:22 GMT", <http://web.archive.org/web/20160513214322/https://justpaste.it/u8j7>; rel="memento"; datetime="Fri, 13 May 2016 21:43:22 GMT", <http://web.archive.org/web/20160513214322/https://justpaste.it/u8j7>; rel="last memento"; datetime="Fri, 13 May 2016 21:43:22 GMT"`)
//        w.Header().Set("X-Cache-Key", `httpweb.archive.org/web/20160513214322/http:/justpaste.it/u8j7//CA`)
//        w.Header().Set("X-Archive-Orig-vary", "Accept-Encoding")
//        w.Header().Set("X-location", "All")
		io.WriteString(w, req.RequestURI + "\n")
	}

	http.HandleFunc("/web", helloHandler)
	log.Fatal(http.ListenAndServe(":8081", nil))
}

type MyMux struct {
//    http.ServeMux
}

func (m *MyMux)ServeHTTP(w http.ResponseWriter, req *http.Request) {
    io.WriteString(w, req.RequestURI + "\n")
}

/*
func (m *MyMux)Handler(r *http.Request) (h http.Handler, pattern string) {
    println("MyMux)Handler")
    return http.HandlerFunc(m.ServeHTTP), "/"
}
*/

func main() {
	http.ListenAndServe(":8081", new(MyMux))
}
<|endoftext|>


<commit_before>package main

import (
	"crypto/tls"
	"errors"
	"io"
	"log"
	"math/rand"
	"net"
	"net/http"
	"net/http/httputil"
	"path"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/coreos/go-etcd/etcd"
	"github.com/flynn/go-discoverd"
)

type HTTPFrontend struct {
	Addr      string
	TLSAddr   string
	TLSConfig *tls.Config

	mtx      sync.RWMutex
	domains  map[string]*httpServer
	services map[string]*httpServer

	etcdPrefix string

	etcd      EtcdClient
	discoverd DiscoverdClient
}

type EtcdClient interface {
	Create(key string, value string, ttl uint64) (*etcd.Response, error)
	Get(key string, sort, recursive bool) (*etcd.Response, error)
	Watch(prefix string, waitIndex uint64, recursive bool, receiver chan *etcd.Response, stop chan bool) (*etcd.Response, error)
}

type DiscoverdClient interface {
	NewServiceSet(string) (discoverd.ServiceSet, error)
}

func NewHTTPFrontend(addr string, etcdc EtcdClient, discoverdc DiscoverdClient) *HTTPFrontend {
	return &HTTPFrontend{
		Addr:       addr,
		etcd:       etcdc,
		etcdPrefix: "/strowger/http/",
		discoverd:  discoverdc,
		domains:    make(map[string]*httpServer),
		services:   make(map[string]*httpServer),
	}
}

func (s *HTTPFrontend) AddHTTPDomain(domain string, service string, certs [][]byte, key []byte) error {
	return s.addDomain(domain, service, true)
}

var ErrDomainExists = errors.New("strowger: domain exists with different service")

func (s *HTTPFrontend) addDomain(domain string, service string, persist bool) error {
	s.mtx.Lock()
	defer s.mtx.Unlock()

	if server, ok := s.domains[domain]; ok {
		if server.name != service {
			return ErrDomainExists
		}
		return nil
	}

	server := s.services[service]
	if server == nil {
		services, err := s.discoverd.NewServiceSet(service)
		if err != nil {
			return err
		}
		server = &httpServer{name: service, services: services}
	}
	if persist {
		if _, err := s.etcd.Create(s.etcdPrefix+domain+"/service", service, 0); err != nil {
			return err
		}
	}
	// TODO: set cert/key data if provided

	server.refs++
	s.domains[domain] = server
	s.services[service] = server
	// TODO: TLS config

	log.Println("Add service", service, "to domain", domain)

	return nil
}

func (s *HTTPFrontend) RemoveHTTPDomain(domain string) {
	s.mtx.Lock()
	defer s.mtx.Unlock()
	server := s.domains[domain]
	if server == nil {
		return
	}
	delete(s.domains, domain)
	server.refs++
	if server.refs <= 0 {
		// TODO: close service set stream
		delete(s.services, server.name)
	}
	// TODO: persist
}

func (s *HTTPFrontend) syncDatabase() {
	var since uint64
	data, err := s.etcd.Get(s.etcdPrefix, false, true)
	if e, ok := err.(*etcd.EtcdError); ok && e.ErrorCode == 100 {
		// key not found, ignore
		goto watch
	}
	if err != nil {
		log.Fatal(err)
		return
	}
	since = data.EtcdIndex
	for _, node := range data.Node.Nodes {
		if !node.Dir {
			continue
		}
		domain := path.Base(node.Key)
		serviceRes, err := s.etcd.Get(node.Key+"/service", false, false)
		if err != nil {
			log.Fatal(err)
		}
		if err := s.addDomain(domain, serviceRes.Node.Value, false); err != nil {
			log.Fatal(err)
		}
	}

watch:
	stream := make(chan *etcd.Response)
	stop := make(chan bool)
	// TODO: store stop
	go s.etcd.Watch(s.etcdPrefix, since, false, stream, stop)
	for res := range stream {
		if res.Node.Dir || path.Base(res.Node.Key) == "service" {
			continue
		}
		domain := path.Base(path.Dir(res.Node.Key))
		s.mtx.Lock()
		_, exists := s.domains[domain]
		s.mtx.Unlock()
		if !exists {
			if err := s.addDomain(domain, res.Node.Value, false); err != nil {
				// TODO: log error
			}
		}
	}
	// TODO: handle delete
	log.Println("done watching etcd")
}

func (s *HTTPFrontend) serve() {
	l, err := net.Listen("tcp", s.Addr)
	if err != nil {
		// TODO: log error
		return
	}
	for {
		conn, err := l.Accept()
		if err != nil {
			// TODO: log error
			break
		}
		go s.handle(conn)
	}
}

func (s *HTTPFrontend) serveTLS() {
	l, err := net.Listen("tcp", s.TLSAddr)
	if err != nil {
		// TODO: log error
		return
	}
	for {
		conn, err := l.Accept()
		if err != nil {
			// TODO: log error
			break
		}
		go s.handle(conn)
	}
}

func (s *HTTPFrontend) handle(conn net.Conn) {
	defer conn.Close()
	sc := httputil.NewServerConn(conn, nil)
	req, err := sc.Read()
	if err != nil {
		if err != httputil.ErrPersistEOF {
			// TODO: log error
		}
		return
	}

	s.mtx.RLock()
	// TODO: handle wildcard domains
	backend := s.domains[req.Host]
	s.mtx.RUnlock()
	log.Println(req, backend)
	if backend == nil {
		// TODO: return 404
		return
	}
	_, tls := conn.(*tls.Conn)
	backend.handle(req, sc, tls)
}

type httpServer struct {
	name     string
	services discoverd.ServiceSet
	refs     int
}

func (s *httpServer) getBackend() *httputil.ClientConn {
	for _, addr := range shuffle(s.services.Addrs()) {
		// TODO: set connection timeout
		backend, err := net.Dial("tcp", addr)
		if err != nil {
			// TODO: log error
			// TODO: limit number of backends tried
			// TODO: temporarily quarantine failing backends
			log.Println("backend error", err)
			continue
		}
		return httputil.NewClientConn(backend, nil)
	}
	// TODO: log no backends found error
	return nil
}

func (s *httpServer) handle(req *http.Request, sc *httputil.ServerConn, tls bool) {
	req.Header.Set("X-Request-Start", strconv.FormatInt(time.Now().UnixNano()/int64(time.Millisecond), 10))
	backend := s.getBackend()
	if backend == nil {
		// TODO: Return 503
		log.Println("no backend found")
		return
	}
	defer backend.Close()

	for {
		if req.Method != "GET" && req.Method != "POST" && req.Method != "HEAD" &&
			req.Method != "OPTIONS" && req.Method != "PUT" && req.Method != "DELETE" && req.Method != "TRACE" {
			// TODO: return 405
		}

		req.Proto = "HTTP/1.1"
		req.ProtoMajor = 1
		req.ProtoMinor = 1
		delete(req.Header, "Te")
		delete(req.Header, "Transfer-Encoding")

		if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
			// If we aren't the first proxy retain prior
			// X-Forwarded-For information as a comma+space
			// separated list and fold multiple headers into one.
			if prior, ok := req.Header["X-Forwarded-For"]; ok {
				clientIP = strings.Join(prior, ", ") + ", " + clientIP
			}
			req.Header.Set("X-Forwarded-For", clientIP)
		}
		if tls {
			req.Header.Set("X-Forwarded-Proto", "https")
		} else {
			req.Header.Set("X-Forwarded-Proto", "http")
		}
		// TODO: Set X-Forwarded-Port

		if err := backend.Write(req); err != nil {
			log.Println("server write err:", err)
			return
		}
		res, err := backend.Read(req)
		if res != nil {
			if err := sc.Write(req, res); err != nil {
				if err != io.EOF && err != httputil.ErrPersistEOF {
					log.Println("client write err:", err)
					// TODO: log error
				}
				return
			}
		}
		if err != nil {
			if err != io.EOF && err != httputil.ErrPersistEOF {
				log.Println("server read err:", err)
				// TODO: log error
				// TODO: Return 502
			}
			return
		}

		// TODO: Proxy HTTP CONNECT? (example: Go RPC over HTTP)
		if res.StatusCode == http.StatusSwitchingProtocols {
			serverW, serverR := backend.Hijack()
			clientW, clientR := sc.Hijack()
			defer serverW.Close()
			done := make(chan struct{})
			go func() {
				serverR.WriteTo(clientW)
				close(done)
			}()
			clientR.WriteTo(serverW)
			<-done
			return
		}

		// TODO: http pipelining
		req, err = sc.Read()
		if err != nil {
			if err != io.EOF && err != httputil.ErrPersistEOF {
				log.Println("client read err:", err)
			}
			return
		}
		req.Header.Set("X-Request-Start", strconv.FormatInt(time.Now().UnixNano()/int64(time.Millisecond), 10))
	}
}

func shuffle(s []string) []string {
	for i := len(s) - 1; i > 0; i-- {
		j := rand.Intn(i + 1)
		s[i], s[j] = s[j], s[i]
	}
	return s
}

func init() {
	rand.Seed(time.Now().UnixNano())
}
<commit_msg>router: Merge pull request #5 from miracle2k/errors<commit_after>package main

import (
	"bytes"
	"crypto/tls"
	"errors"
	"io"
	"io/ioutil"
	"log"
	"math/rand"
	"net"
	"net/http"
	"net/http/httputil"
	"path"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/coreos/go-etcd/etcd"
	"github.com/flynn/go-discoverd"
)

type HTTPFrontend struct {
	Addr      string
	TLSAddr   string
	TLSConfig *tls.Config

	mtx      sync.RWMutex
	domains  map[string]*httpServer
	services map[string]*httpServer

	etcdPrefix string

	etcd      EtcdClient
	discoverd DiscoverdClient
}

type EtcdClient interface {
	Create(key string, value string, ttl uint64) (*etcd.Response, error)
	Get(key string, sort, recursive bool) (*etcd.Response, error)
	Watch(prefix string, waitIndex uint64, recursive bool, receiver chan *etcd.Response, stop chan bool) (*etcd.Response, error)
}

type DiscoverdClient interface {
	NewServiceSet(string) (discoverd.ServiceSet, error)
}

func NewHTTPFrontend(addr string, etcdc EtcdClient, discoverdc DiscoverdClient) *HTTPFrontend {
	return &HTTPFrontend{
		Addr:       addr,
		etcd:       etcdc,
		etcdPrefix: "/strowger/http/",
		discoverd:  discoverdc,
		domains:    make(map[string]*httpServer),
		services:   make(map[string]*httpServer),
	}
}

func (s *HTTPFrontend) AddHTTPDomain(domain string, service string, certs [][]byte, key []byte) error {
	return s.addDomain(domain, service, true)
}

var ErrDomainExists = errors.New("strowger: domain exists with different service")

func (s *HTTPFrontend) addDomain(domain string, service string, persist bool) error {
	s.mtx.Lock()
	defer s.mtx.Unlock()

	if server, ok := s.domains[domain]; ok {
		if server.name != service {
			return ErrDomainExists
		}
		return nil
	}

	server := s.services[service]
	if server == nil {
		services, err := s.discoverd.NewServiceSet(service)
		if err != nil {
			return err
		}
		server = &httpServer{name: service, services: services}
	}
	if persist {
		if _, err := s.etcd.Create(s.etcdPrefix+domain+"/service", service, 0); err != nil {
			return err
		}
	}
	// TODO: set cert/key data if provided

	server.refs++
	s.domains[domain] = server
	s.services[service] = server
	// TODO: TLS config

	log.Println("Add service", service, "to domain", domain)

	return nil
}

func (s *HTTPFrontend) RemoveHTTPDomain(domain string) {
	s.mtx.Lock()
	defer s.mtx.Unlock()
	server := s.domains[domain]
	if server == nil {
		return
	}
	delete(s.domains, domain)
	server.refs++
	if server.refs <= 0 {
		// TODO: close service set stream
		delete(s.services, server.name)
	}
	// TODO: persist
}

func (s *HTTPFrontend) syncDatabase() {
	var since uint64
	data, err := s.etcd.Get(s.etcdPrefix, false, true)
	if e, ok := err.(*etcd.EtcdError); ok && e.ErrorCode == 100 {
		// key not found, ignore
		goto watch
	}
	if err != nil {
		log.Fatal(err)
		return
	}
	since = data.EtcdIndex
	for _, node := range data.Node.Nodes {
		if !node.Dir {
			continue
		}
		domain := path.Base(node.Key)
		serviceRes, err := s.etcd.Get(node.Key+"/service", false, false)
		if err != nil {
			log.Fatal(err)
		}
		if err := s.addDomain(domain, serviceRes.Node.Value, false); err != nil {
			log.Fatal(err)
		}
	}

watch:
	stream := make(chan *etcd.Response)
	stop := make(chan bool)
	// TODO: store stop
	go s.etcd.Watch(s.etcdPrefix, since, false, stream, stop)
	for res := range stream {
		if res.Node.Dir || path.Base(res.Node.Key) == "service" {
			continue
		}
		domain := path.Base(path.Dir(res.Node.Key))
		s.mtx.Lock()
		_, exists := s.domains[domain]
		s.mtx.Unlock()
		if !exists {
			if err := s.addDomain(domain, res.Node.Value, false); err != nil {
				// TODO: log error
			}
		}
	}
	// TODO: handle delete
	log.Println("done watching etcd")
}

func (s *HTTPFrontend) serve() {
	l, err := net.Listen("tcp", s.Addr)
	if err != nil {
		// TODO: log error
		return
	}
	for {
		conn, err := l.Accept()
		if err != nil {
			// TODO: log error
			break
		}
		go s.handle(conn)
	}
}

func (s *HTTPFrontend) serveTLS() {
	l, err := net.Listen("tcp", s.TLSAddr)
	if err != nil {
		// TODO: log error
		return
	}
	for {
		conn, err := l.Accept()
		if err != nil {
			// TODO: log error
			break
		}
		go s.handle(conn)
	}
}

func fail(sc *httputil.ServerConn, req *http.Request, code int, msg string) {
	resp := &http.Response{
		StatusCode:    code,
		ProtoMajor:    1,
		ProtoMinor:    0,
		Request:       req,
		Body:          ioutil.NopCloser(bytes.NewBufferString(msg)),
		ContentLength: int64(len(msg)),
	}
	sc.Write(req, resp)
}

func (s *HTTPFrontend) handle(conn net.Conn) {
	defer conn.Close()
	sc := httputil.NewServerConn(conn, nil)
	req, err := sc.Read()
	if err != nil {
		if err != httputil.ErrPersistEOF {
			// TODO: log error
		}
		return
	}

	s.mtx.RLock()
	// TODO: handle wildcard domains
	backend := s.domains[req.Host]
	s.mtx.RUnlock()
	log.Println(req, backend)
	if backend == nil {
		fail(sc, req, 404, "Not Found")
		return
	}
	_, tls := conn.(*tls.Conn)
	backend.handle(req, sc, tls)
}

type httpServer struct {
	name     string
	services discoverd.ServiceSet
	refs     int
}

func (s *httpServer) getBackend() *httputil.ClientConn {
	for _, addr := range shuffle(s.services.Addrs()) {
		// TODO: set connection timeout
		backend, err := net.Dial("tcp", addr)
		if err != nil {
			// TODO: log error
			// TODO: limit number of backends tried
			// TODO: temporarily quarantine failing backends
			log.Println("backend error", err)
			continue
		}
		return httputil.NewClientConn(backend, nil)
	}
	// TODO: log no backends found error
	return nil
}

func (s *httpServer) handle(req *http.Request, sc *httputil.ServerConn, tls bool) {
	req.Header.Set("X-Request-Start", strconv.FormatInt(time.Now().UnixNano()/int64(time.Millisecond), 10))
	backend := s.getBackend()
	if backend == nil {
		log.Println("no backend found")
		fail(sc, req, 503, "Service Unavailable")
		return
	}
	defer backend.Close()

	for {
		if req.Method != "GET" && req.Method != "POST" && req.Method != "HEAD" &&
			req.Method != "OPTIONS" && req.Method != "PUT" && req.Method != "DELETE" && req.Method != "TRACE" {
			fail(sc, req, 405, "Method not allowed")
			return
		}

		req.Proto = "HTTP/1.1"
		req.ProtoMajor = 1
		req.ProtoMinor = 1
		delete(req.Header, "Te")
		delete(req.Header, "Transfer-Encoding")

		if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
			// If we aren't the first proxy retain prior
			// X-Forwarded-For information as a comma+space
			// separated list and fold multiple headers into one.
			if prior, ok := req.Header["X-Forwarded-For"]; ok {
				clientIP = strings.Join(prior, ", ") + ", " + clientIP
			}
			req.Header.Set("X-Forwarded-For", clientIP)
		}
		if tls {
			req.Header.Set("X-Forwarded-Proto", "https")
		} else {
			req.Header.Set("X-Forwarded-Proto", "http")
		}
		// TODO: Set X-Forwarded-Port

		if err := backend.Write(req); err != nil {
			log.Println("server write err:", err)
			return
		}
		res, err := backend.Read(req)
		if res != nil {
			if err := sc.Write(req, res); err != nil {
				if err != io.EOF && err != httputil.ErrPersistEOF {
					log.Println("client write err:", err)
					// TODO: log error
				}
				return
			}
		}
		if err != nil {
			if err != io.EOF && err != httputil.ErrPersistEOF {
				log.Println("server read err:", err)
				// TODO: log error
				fail(sc, req, 502, "Bad Gateway")
			}
			return
		}

		// TODO: Proxy HTTP CONNECT? (example: Go RPC over HTTP)
		if res.StatusCode == http.StatusSwitchingProtocols {
			serverW, serverR := backend.Hijack()
			clientW, clientR := sc.Hijack()
			defer serverW.Close()
			done := make(chan struct{})
			go func() {
				serverR.WriteTo(clientW)
				close(done)
			}()
			clientR.WriteTo(serverW)
			<-done
			return
		}

		// TODO: http pipelining
		req, err = sc.Read()
		if err != nil {
			if err != io.EOF && err != httputil.ErrPersistEOF {
				log.Println("client read err:", err)
			}
			return
		}
		req.Header.Set("X-Request-Start", strconv.FormatInt(time.Now().UnixNano()/int64(time.Millisecond), 10))
	}
}

func shuffle(s []string) []string {
	for i := len(s) - 1; i > 0; i-- {
		j := rand.Intn(i + 1)
		s[i], s[j] = s[j], s[i]
	}
	return s
}

func init() {
	rand.Seed(time.Now().UnixNano())
}
<|endoftext|>


<commit_before>package main

var edition = "ce"
var version = "v1.1.2"
<commit_msg>release: v1.1.3<commit_after>package main

var edition = "ce"
var version = "v1.1.3"
<|endoftext|>


<commit_before>package api

import (
    "encoding/json"
    "fmt"
    "io/ioutil"
    "net/http"
    "path"
    "strconv"
    "strings"

    log "github.com/Sirupsen/logrus"
    "github.com/daolinet/daolinet/model"
    "github.com/gorilla/mux"
    "github.com/samalba/dockerclient"
)

func (a *Api) resetContainer(w http.ResponseWriter, r *http.Request) {
    if err := r.ParseForm(); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
    }

    var data = map[string]string{}
    if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    node, ok := data["node"]
    oldId := mux.Vars(r)["id"]

    info, err := a.client.InspectContainer(oldId)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    if info.State.Running {
        err := a.client.StopContainer(info.Id, 5)
        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }
    }

    err = a.client.RenameContainer(info.Id, info.Name + "old")
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    config := &dockerclient.ContainerConfig{}
    config.Tty = info.Config.Tty
    config.Env = info.Config.Env
    config.Cmd = info.Config.Cmd
    config.Image = info.Config.Image

    // Add swarm filters, only constraint node filter.
    if ok {
        if config.Env == nil {
            config.Env = []string{}
        }
        swarmNode := fmt.Sprintf("constraint:node==%s", node)
        config.Env = append(config.Env, swarmNode)
    }

    netMode := info.HostConfig.NetworkMode
    net := info.NetworkSettings.Networks[netMode]
    if net != nil {
        config.MacAddress = net.MacAddress
        config.HostConfig = dockerclient.HostConfig{
            NetworkMode: netMode,
        }
        endpointsConfig := map[string]*dockerclient.EndpointSettings {
            netMode: &dockerclient.EndpointSettings{
                IPAMConfig: &dockerclient.EndpointIPAMConfig {
                   IPv4Address: net.IPAddress,
                },
                //IPAddress: net.IPAddress,
                //MacAddress: net.MacAddress,
            },
        }

        config.NetworkingConfig = dockerclient.NetworkingConfig{
            EndpointsConfig: endpointsConfig,
        }
    }

    // data, _ := json.Marshal(config)
    // log.Warn(string(data))
    hostConfig := &dockerclient.HostConfig{
        NetworkMode: netMode,
    }
    newId, err := a.client.CreateContainer(config, info.Name, nil)
    if err != nil {
        a.client.StartContainer(info.Id, hostConfig)
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    err = a.client.RemoveContainer(info.Id, true, false)
    if err != nil {
        log.Warnf("Remove container: %v", err)
    }

    go func() {
        client := newClientAndScheme(a.client.TLSConfig)
        url := fmt.Sprintf("%s/v1/containers/%s", a.ofcUrl, info.Id)
        resp, err := client.Post(url, "application/json", nil)
        if err != nil {
            log.Warnf("Remove container from openflow controller: %v", err)
        }

	if resp != nil && resp.Body != nil {
            resp.Body.Close()
        }
    }()

    go a.resetContainerById(info.Id, newId)

    err = a.client.StartContainer(newId, hostConfig)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    w.Write([]byte(newId))
}

func (a *Api) resetContainerById(oldId , newId string) {
    // Reset old container firewall to new.
    firewalls, err := a.store.List(pathNameFirewall)
    if err != nil {
        log.Errorf("error to get all firewalls: %v", err)
    } else {
        for _, fw := range firewalls {
            var firewall model.Firewall
            err := json.Unmarshal(fw.Value, &firewall)
            if err != nil {
                log.Errorf("error unmarshal firewall: %v", err)
                continue
            }
            if firewall.Container == oldId {
                firewall.Container = newId
                value, err := json.Marshal(firewall)
                if err != nil {
                    log.Errorf("json marshal error: %v", err)
                    continue
                }
                if err := a.store.Put(fw.Key, value, nil); err != nil {
                    log.Errorf("error to update container firewall by name: %v", err)
                    continue
                }
                nodeurl := path.Join(pathNodeFirewall, firewall.DatapathID, strconv.Itoa(firewall.GatewayPort))
                if err := a.store.Put(nodeurl, value, nil); err != nil {
                    log.Errorf("error to update container firewall by node: %v", err)
                    continue
                }
            }
        }
    }

    // Reset old container policy to new.
    policies, err := a.store.List(pathPolicy)
    if err != nil {
        log.Errorf("error to get all policies : %v", err)
    } else {
        for _, policy := range policies {
            peer := strings.Split(policy.Key, "/")
            parts := strings.Split(peer[len(peer)-1], ":")
            if len(parts) != 2 {
                log.Error(ErrPolicyFormat.Error())
                continue
            }
            if oldId == parts[0] || oldId == parts[1] {
                if err := a.store.Delete(policy.Key); err != nil {
                    log.Warnf("error deleting policy: %v", err)
                }
                if oldId == parts[0] {
                    parts[0] = newId
                } else {
                    parts[1] = newId
                }

                var key string
                if strings.Compare(parts[0], parts[1]) > 0 {
                    key = fmt.Sprintf("%s:%s", parts[1], parts[0])
                } else {
                    key = fmt.Sprintf("%s:%s", parts[0], parts[1])
                }

                if err := a.store.Put(path.Join(pathPolicy, key), policy.Value, nil); err != nil {
                    log.Errorf("error to save policy: %v", err)
                }
            }
        }
    }
}

func (a *Api) showContainer(w http.ResponseWriter, r *http.Request) {
    container := mux.Vars(r)["id"]
    info, err := a.client.InspectContainer(container)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    client := newClientAndScheme(a.client.TLSConfig)
    url := fmt.Sprintf("%s/v1/containers/%s", a.ofcUrl, info.Id)
    resp, err := client.Get(url)
    if err != nil {
        log.Warnf("Get container from openflow controller: %v", err)
    }

    ofResult := map[string]string{}
    if resp != nil && resp.Body != nil {
        body, err := ioutil.ReadAll(resp.Body)
        if err != nil {
            log.Warnf("read body: %v", err)
        }
        if resp.StatusCode >= 400 {
            log.Warnf("Get container from openflow controller: %s", string(body))
        } else {
            if err := json.Unmarshal(body, &ofResult); err != nil {
                log.Warnf("unmarshal body from openflow controller: %v", err)
            }
        }
        resp.Body.Close()
    }
    closeIdleConnections(client)
    data := []map[string]string{}
    for key, value := range info.NetworkSettings.Networks {
        newValue := map[string]string{}
        newValue["Id"] = info.Id
        newValue["NetworkName"] = key
        ipAddress := fmt.Sprintf("%s/%d", value.IPAddress, value.IPPrefixLen)
        newValue["IPAddress"] = ipAddress
        newValue["MacAddress"] = value.MacAddress
        newValue["Gateway"] = value.Gateway

        var vIP string = ""
        if value.MacAddress == ofResult["MacAddress"] {
            vIP = ofResult["UIPAddress"]
        }
        newValue["VIPAddress"] = vIP
        data = append(data, newValue)
    }
    if err := json.NewEncoder(w).Encode(data); err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
}
<commit_msg>fixed flow.<commit_after>package api

import (
    "encoding/json"
    "fmt"
    "io/ioutil"
    "net/http"
    "path"
    "strconv"
    "strings"

    log "github.com/Sirupsen/logrus"
    "github.com/daolinet/daolinet/model"
    "github.com/gorilla/mux"
    "github.com/samalba/dockerclient"
)

func (a *Api) resetContainer(w http.ResponseWriter, r *http.Request) {
    if err := r.ParseForm(); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
    }

    var data = map[string]string{}
    if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    node, ok := data["node"]
    oldId := mux.Vars(r)["id"]

    info, err := a.client.InspectContainer(oldId)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    if info.State.Running {
        err := a.client.StopContainer(info.Id, 5)
        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }
    }

    err = a.client.RenameContainer(info.Id, info.Name + "old")
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    config := &dockerclient.ContainerConfig{}
    config.Tty = info.Config.Tty
    config.Env = info.Config.Env
    config.Cmd = info.Config.Cmd
    config.Image = info.Config.Image

    // Add swarm filters, only constraint node filter.
    if ok {
        if config.Env == nil {
            config.Env = []string{}
        }
        swarmNode := fmt.Sprintf("constraint:node==%s", node)
        config.Env = append(config.Env, swarmNode)
    }

    netMode := info.HostConfig.NetworkMode
    net := info.NetworkSettings.Networks[netMode]
    if net != nil {
        config.MacAddress = net.MacAddress
        config.HostConfig = dockerclient.HostConfig{
            NetworkMode: netMode,
        }
        endpointsConfig := map[string]*dockerclient.EndpointSettings {
            netMode: &dockerclient.EndpointSettings{
                IPAMConfig: &dockerclient.EndpointIPAMConfig {
                   IPv4Address: net.IPAddress,
                },
                //IPAddress: net.IPAddress,
                //MacAddress: net.MacAddress,
            },
        }

        config.NetworkingConfig = dockerclient.NetworkingConfig{
            EndpointsConfig: endpointsConfig,
        }
    }

    // data, _ := json.Marshal(config)
    // log.Warn(string(data))
    hostConfig := &dockerclient.HostConfig{
        NetworkMode: netMode,
    }
    newId, err := a.client.CreateContainer(config, info.Name, nil)
    if err != nil {
        a.client.StartContainer(info.Id, hostConfig)
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    go func() {
        err = a.client.RemoveContainer(info.Id, true, false)
        if err != nil {
            log.Warnf("Remove container: %v", err)
        }

        client := newClientAndScheme(a.client.TLSConfig)
        url := fmt.Sprintf("%s/v1/containers/%s", a.ofcUrl, info.Id)
        resp, err := client.Post(url, "application/json", nil)
        if err != nil {
            log.Warnf("Remove container from openflow controller: %v", err)
        }

	if resp != nil && resp.Body != nil {
            resp.Body.Close()
        }
    }()

    go a.resetContainerById(info.Id, newId)

    err = a.client.StartContainer(newId, hostConfig)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    w.Write([]byte(newId))
}

func (a *Api) resetContainerById(oldId , newId string) {
    // Reset old container firewall to new.
    firewalls, err := a.store.List(pathNameFirewall)
    if err != nil {
        log.Errorf("error to get all firewalls: %v", err)
    } else {
        for _, fw := range firewalls {
            var firewall model.Firewall
            err := json.Unmarshal(fw.Value, &firewall)
            if err != nil {
                log.Errorf("error unmarshal firewall: %v", err)
                continue
            }
            if firewall.Container == oldId {
                firewall.Container = newId
                value, err := json.Marshal(firewall)
                if err != nil {
                    log.Errorf("json marshal error: %v", err)
                    continue
                }
                if err := a.store.Put(fw.Key, value, nil); err != nil {
                    log.Errorf("error to update container firewall by name: %v", err)
                    continue
                }
                nodeurl := path.Join(pathNodeFirewall, firewall.DatapathID, strconv.Itoa(firewall.GatewayPort))
                if err := a.store.Put(nodeurl, value, nil); err != nil {
                    log.Errorf("error to update container firewall by node: %v", err)
                    continue
                }
            }
        }
    }

    // Reset old container policy to new.
    policies, err := a.store.List(pathPolicy)
    if err != nil {
        log.Errorf("error to get all policies : %v", err)
    } else {
        for _, policy := range policies {
            peer := strings.Split(policy.Key, "/")
            parts := strings.Split(peer[len(peer)-1], ":")
            if len(parts) != 2 {
                log.Error(ErrPolicyFormat.Error())
                continue
            }
            if oldId == parts[0] || oldId == parts[1] {
                if err := a.store.Delete(policy.Key); err != nil {
                    log.Warnf("error deleting policy: %v", err)
                }
                if oldId == parts[0] {
                    parts[0] = newId
                } else {
                    parts[1] = newId
                }

                var key string
                if strings.Compare(parts[0], parts[1]) > 0 {
                    key = fmt.Sprintf("%s:%s", parts[1], parts[0])
                } else {
                    key = fmt.Sprintf("%s:%s", parts[0], parts[1])
                }

                if err := a.store.Put(path.Join(pathPolicy, key), policy.Value, nil); err != nil {
                    log.Errorf("error to save policy: %v", err)
                }
            }
        }
    }
}

func (a *Api) showContainer(w http.ResponseWriter, r *http.Request) {
    container := mux.Vars(r)["id"]
    info, err := a.client.InspectContainer(container)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    client := newClientAndScheme(a.client.TLSConfig)
    url := fmt.Sprintf("%s/v1/containers/%s", a.ofcUrl, info.Id)
    resp, err := client.Get(url)
    if err != nil {
        log.Warnf("Get container from openflow controller: %v", err)
    }

    ofResult := map[string]string{}
    if resp != nil && resp.Body != nil {
        body, err := ioutil.ReadAll(resp.Body)
        if err != nil {
            log.Warnf("read body: %v", err)
        }
        if resp.StatusCode >= 400 {
            log.Warnf("Get container from openflow controller: %s", string(body))
        } else {
            if err := json.Unmarshal(body, &ofResult); err != nil {
                log.Warnf("unmarshal body from openflow controller: %v", err)
            }
        }
        resp.Body.Close()
    }
    closeIdleConnections(client)
    data := []map[string]string{}
    for key, value := range info.NetworkSettings.Networks {
        newValue := map[string]string{}
        newValue["Id"] = info.Id
        newValue["NetworkName"] = key
        ipAddress := fmt.Sprintf("%s/%d", value.IPAddress, value.IPPrefixLen)
        newValue["IPAddress"] = ipAddress
        newValue["MacAddress"] = value.MacAddress
        newValue["Gateway"] = value.Gateway

        var vIP string = ""
        if value.MacAddress == ofResult["MacAddress"] {
            vIP = ofResult["UIPAddress"]
        }
        newValue["VIPAddress"] = vIP
        data = append(data, newValue)
    }
    if err := json.NewEncoder(w).Encode(data); err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
}
<|endoftext|>


<commit_before>// Copyright (c) 2016 Tigera, Inc. All rights reserved.

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

package config_test

import (
	. "github.com/projectcalico/felix/go/felix/config"

	. "github.com/onsi/ginkgo/extensions/table"
	. "github.com/onsi/gomega"
	"net"
	"reflect"
)

var _ = DescribeTable("Config parsing",
	func(key, value string, expected interface{}) {
		config := New()
		oldVal := reflect.ValueOf(config).Elem().FieldByName(key).Interface()
		config.UpdateFrom(map[string]string{key: value},
			EnvironmentVariable)
		newVal := reflect.ValueOf(config).Elem().FieldByName(key).Interface()
		Expect(oldVal).NotTo(Equal(newVal))
		Expect(newVal).To(Equal(expected))
	},

	Entry("FelixHostname", "FelixHostname", "hostname", "hostname"),
	Entry("FelixHostname FQDN", "FelixHostname", "hostname.foo.bar.com", "hostname.foo.bar.com"),
	Entry("FelixHostname as IP", "FelixHostname", "1.2.3.4", "1.2.3.4"),

	Entry("EtcdAddr IP", "EtcdAddr", "10.0.0.1:1234", "10.0.0.1:1234"),
	Entry("EtcdAddr host", "EtcdAddr", "host:1234", "host:1234"),
	Entry("EtcdScheme", "EtcdScheme", "https", "https"),

	// Etcd key files will be tested for existence, skipping for now.

	Entry("EtcdEndpoints HTTP", "EtcdEndpoints",
		"http://127.0.0.1:1234, http://host:2345",
		[]string{"http://127.0.0.1:1234/", "http://host:2345/"}),
	Entry("EtcdEndpoints HTTPS", "EtcdEndpoints",
		"https://127.0.0.1:1234/, https://host:2345",
		[]string{"https://127.0.0.1:1234/", "https://host:2345/"}),

	Entry("StartupCleanupDelay 12", "StartupCleanupDelay", "12", int(12)),
	Entry("StartupCleanupDelay 0", "StartupCleanupDelay", "0", int(0)),
	Entry("PeriodicResyncInterval 1500", "PeriodicResyncInterval", "1500", int(1500)),
	Entry("PeriodicResyncInterval 0", "PeriodicResyncInterval", "0", int(0)),
	Entry("HostInterfacePollInterval", "HostInterfacePollInterval", "11", int(11)),
	Entry("HostInterfacePollInterval", "HostInterfacePollInterval", "0", int(0)),

	Entry("InterfacePrefix", "InterfacePrefix", "tap", "tap"),
	Entry("InterfacePrefix list", "InterfacePrefix", "tap,cali", "tap,cali"),

	Entry("ChainInsertMode", "ChainInsertMode", "insert", "insert"),
	Entry("ChainInsertMode insert", "ChainInsertMode", "insert", "insert"),
	Entry("ChainInsertMode append", "ChainInsertMode", "append", "append"),

	Entry("DefaultEndpointToHostAction", "DefaultEndpointToHostAction",
		"RETURN", "RETURN"),
	Entry("DefaultEndpointToHostAction", "DefaultEndpointToHostAction",
		"ACCEPT", "ACCEPT"),

	Entry("DropActionOverride", "DropActionOverride",
		"ACCEPT", "ACCEPT"),
	Entry("DropActionOverride norm", "DropActionOverride",
		"accept", "ACCEPT"),
	Entry("DropActionOverride LOG-and-ACCEPT", "DropActionOverride",
		"LOG-and-ACCEPT", "LOG-and-ACCEPT"),
	Entry("DropActionOverride log-and-accept", "DropActionOverride",
		"log-and-accept", "LOG-and-ACCEPT"),
	Entry("DropActionOverride log-and-drop", "DropActionOverride",
		"log-and-drop", "LOG-and-DROP"),

	Entry("LogFilePath", "LogFilePath", "/tmp/felix.log", "/tmp/felix.log"),

	Entry("LogSeverityFile", "LogSeverityFile", "debug", "DEBUG"),
	Entry("LogSeverityFile", "LogSeverityFile", "warning", "WARNING"),
	Entry("LogSeverityFile", "LogSeverityFile", "error", "ERROR"),
	Entry("LogSeverityFile", "LogSeverityFile", "critical", "CRITICAL"),

	Entry("LogSeverityScreen", "LogSeverityScreen", "debug", "DEBUG"),
	Entry("LogSeverityScreen", "LogSeverityScreen", "warning", "WARNING"),
	Entry("LogSeverityScreen", "LogSeverityScreen", "error", "ERROR"),
	Entry("LogSeverityScreen", "LogSeverityScreen", "critical", "CRITICAL"),

	Entry("LogSeveritySys", "LogSeveritySys", "debug", "DEBUG"),
	Entry("LogSeveritySys", "LogSeveritySys", "warning", "WARNING"),
	Entry("LogSeveritySys", "LogSeveritySys", "error", "ERROR"),
	Entry("LogSeveritySys", "LogSeveritySys", "critical", "CRITICAL"),

	Entry("IpInIpEnabled", "IpInIpEnabled", "true", true),
	Entry("IpInIpEnabled", "IpInIpEnabled", "y", true),
	Entry("IpInIpEnabled", "IpInIpEnabled", "True", true),

	Entry("IpInIpMtu", "IpInIpMtu", "1234", int(1234)),
	Entry("IpInIpTunnelAddr", "IpInIpTunnelAddr",
		"10.0.0.1", net.ParseIP("10.0.0.1")),

	Entry("ReportingIntervalSecs", "ReportingIntervalSecs", "31", int(31)),
	Entry("ReportingTTLSecs", "ReportingTTLSecs", "91", int(91)),

	Entry("EndpointReportingEnabled", "EndpointReportingEnabled",
		"true", true),
	Entry("EndpointReportingEnabled", "EndpointReportingEnabled",
		"yes", true),
	Entry("EndpointReportingDelaySecs", "EndpointReportingDelaySecs",
		"10", float64(10)),

	Entry("MaxIpsetSize", "MaxIpsetSize", "12345", int(12345)),
	Entry("IptablesMarkMask", "IptablesMarkMask", "0xf0f0", uint32(0xf0f0)),

	Entry("PrometheusMetricsEnabled", "PrometheusMetricsEnabled", "true", true),
	Entry("PrometheusMetricsPort", "PrometheusMetricsPort", "1234", int(1234)),

	Entry("FailsafeInboundHostPorts", "FailsafeInboundHostPorts", "1,2,3,4", []int{1, 2, 3, 4}),
	Entry("FailsafeOutboundHostPorts", "FailsafeOutboundHostPorts", "1,2,3,4", []int{1, 2, 3, 4}),
)
<commit_msg>Fix tests<commit_after>// Copyright (c) 2016 Tigera, Inc. All rights reserved.

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

package config_test

import (
	. "github.com/projectcalico/felix/go/felix/config"

	. "github.com/onsi/ginkgo/extensions/table"
	. "github.com/onsi/gomega"
	"net"
	"reflect"
)

var _ = DescribeTable("Config parsing",
	func(key, value string, expected interface{}) {
		config := New()
		oldVal := reflect.ValueOf(config).Elem().FieldByName(key).Interface()
		config.UpdateFrom(map[string]string{key: value},
			EnvironmentVariable)
		newVal := reflect.ValueOf(config).Elem().FieldByName(key).Interface()
		Expect(oldVal).NotTo(Equal(newVal))
		Expect(newVal).To(Equal(expected))
	},

	Entry("FelixHostname", "FelixHostname", "hostname", "hostname"),
	Entry("FelixHostname FQDN", "FelixHostname", "hostname.foo.bar.com", "hostname.foo.bar.com"),
	Entry("FelixHostname as IP", "FelixHostname", "1.2.3.4", "1.2.3.4"),

	Entry("EtcdAddr IP", "EtcdAddr", "10.0.0.1:1234", "10.0.0.1:1234"),
	Entry("EtcdAddr host", "EtcdAddr", "host:1234", "host:1234"),
	Entry("EtcdScheme", "EtcdScheme", "https", "https"),

	// Etcd key files will be tested for existence, skipping for now.

	Entry("EtcdEndpoints HTTP", "EtcdEndpoints",
		"http://127.0.0.1:1234, http://host:2345",
		[]string{"http://127.0.0.1:1234/", "http://host:2345/"}),
	Entry("EtcdEndpoints HTTPS", "EtcdEndpoints",
		"https://127.0.0.1:1234/, https://host:2345",
		[]string{"https://127.0.0.1:1234/", "https://host:2345/"}),

	Entry("StartupCleanupDelay 12", "StartupCleanupDelay", "12", int(12)),
	Entry("StartupCleanupDelay 0", "StartupCleanupDelay", "0", int(0)),
	Entry("PeriodicResyncInterval 1500", "PeriodicResyncInterval", "1500", int(1500)),
	Entry("PeriodicResyncInterval 0", "PeriodicResyncInterval", "0", int(0)),
	Entry("HostInterfacePollInterval", "HostInterfacePollInterval", "11", int(11)),
	Entry("HostInterfacePollInterval", "HostInterfacePollInterval", "0", int(0)),

	Entry("InterfacePrefix", "InterfacePrefix", "tap", "tap"),
	Entry("InterfacePrefix list", "InterfacePrefix", "tap,cali", "tap,cali"),

	Entry("ChainInsertMode", "ChainInsertMode", "", "insert"),
	Entry("ChainInsertMode append", "ChainInsertMode", "append", "append"),

	Entry("DefaultEndpointToHostAction", "DefaultEndpointToHostAction",
		"RETURN", "RETURN"),
	Entry("DefaultEndpointToHostAction", "DefaultEndpointToHostAction",
		"ACCEPT", "ACCEPT"),

	Entry("DropActionOverride", "DropActionOverride",
		"ACCEPT", "ACCEPT"),
	Entry("DropActionOverride norm", "DropActionOverride",
		"accept", "ACCEPT"),
	Entry("DropActionOverride LOG-and-ACCEPT", "DropActionOverride",
		"LOG-and-ACCEPT", "LOG-and-ACCEPT"),
	Entry("DropActionOverride log-and-accept", "DropActionOverride",
		"log-and-accept", "LOG-and-ACCEPT"),
	Entry("DropActionOverride log-and-drop", "DropActionOverride",
		"log-and-drop", "LOG-and-DROP"),

	Entry("LogFilePath", "LogFilePath", "/tmp/felix.log", "/tmp/felix.log"),

	Entry("LogSeverityFile", "LogSeverityFile", "debug", "DEBUG"),
	Entry("LogSeverityFile", "LogSeverityFile", "warning", "WARNING"),
	Entry("LogSeverityFile", "LogSeverityFile", "error", "ERROR"),
	Entry("LogSeverityFile", "LogSeverityFile", "critical", "CRITICAL"),

	Entry("LogSeverityScreen", "LogSeverityScreen", "debug", "DEBUG"),
	Entry("LogSeverityScreen", "LogSeverityScreen", "warning", "WARNING"),
	Entry("LogSeverityScreen", "LogSeverityScreen", "error", "ERROR"),
	Entry("LogSeverityScreen", "LogSeverityScreen", "critical", "CRITICAL"),

	Entry("LogSeveritySys", "LogSeveritySys", "debug", "DEBUG"),
	Entry("LogSeveritySys", "LogSeveritySys", "warning", "WARNING"),
	Entry("LogSeveritySys", "LogSeveritySys", "error", "ERROR"),
	Entry("LogSeveritySys", "LogSeveritySys", "critical", "CRITICAL"),

	Entry("IpInIpEnabled", "IpInIpEnabled", "true", true),
	Entry("IpInIpEnabled", "IpInIpEnabled", "y", true),
	Entry("IpInIpEnabled", "IpInIpEnabled", "True", true),

	Entry("IpInIpMtu", "IpInIpMtu", "1234", int(1234)),
	Entry("IpInIpTunnelAddr", "IpInIpTunnelAddr",
		"10.0.0.1", net.ParseIP("10.0.0.1")),

	Entry("ReportingIntervalSecs", "ReportingIntervalSecs", "31", int(31)),
	Entry("ReportingTTLSecs", "ReportingTTLSecs", "91", int(91)),

	Entry("EndpointReportingEnabled", "EndpointReportingEnabled",
		"true", true),
	Entry("EndpointReportingEnabled", "EndpointReportingEnabled",
		"yes", true),
	Entry("EndpointReportingDelaySecs", "EndpointReportingDelaySecs",
		"10", float64(10)),

	Entry("MaxIpsetSize", "MaxIpsetSize", "12345", int(12345)),
	Entry("IptablesMarkMask", "IptablesMarkMask", "0xf0f0", uint32(0xf0f0)),

	Entry("PrometheusMetricsEnabled", "PrometheusMetricsEnabled", "true", true),
	Entry("PrometheusMetricsPort", "PrometheusMetricsPort", "1234", int(1234)),

	Entry("FailsafeInboundHostPorts", "FailsafeInboundHostPorts", "1,2,3,4", []int{1, 2, 3, 4}),
	Entry("FailsafeOutboundHostPorts", "FailsafeOutboundHostPorts", "1,2,3,4", []int{1, 2, 3, 4}),
)
<|endoftext|>


<commit_before>/*
Copyright 2017 Google Inc.

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 grpcvtctlclient contains the gRPC version of the vtctl client protocol
package grpcvtctlclient

import (
	"time"

	"github.com/youtube/vitess/go/vt/logutil"
	"github.com/youtube/vitess/go/vt/vtctl/vtctlclient"
	"golang.org/x/net/context"
	"google.golang.org/grpc"

	logutilpb "github.com/youtube/vitess/go/vt/proto/logutil"
	vtctldatapb "github.com/youtube/vitess/go/vt/proto/vtctldata"
	vtctlservicepb "github.com/youtube/vitess/go/vt/proto/vtctlservice"
)

type gRPCVtctlClient struct {
	cc *grpc.ClientConn
	c  vtctlservicepb.VtctlClient
}

func gRPCVtctlClientFactory(addr string, dialTimeout time.Duration) (vtctlclient.VtctlClient, error) {
	// create the RPC client
	cc, err := grpc.Dial(addr, grpc.WithInsecure(), grpc.WithBlock(), grpc.WithTimeout(dialTimeout))
	if err != nil {
		return nil, err
	}
	c := vtctlservicepb.NewVtctlClient(cc)

	return &gRPCVtctlClient{
		cc: cc,
		c:  c,
	}, nil
}

type eventStreamAdapter struct {
	stream vtctlservicepb.Vtctl_ExecuteVtctlCommandClient
}

func (e *eventStreamAdapter) Recv() (*logutilpb.Event, error) {
	le, err := e.stream.Recv()
	if err != nil {
		return nil, err
	}
	return le.Event, nil
}

// ExecuteVtctlCommand is part of the VtctlClient interface
func (client *gRPCVtctlClient) ExecuteVtctlCommand(ctx context.Context, args []string, actionTimeout time.Duration) (logutil.EventStream, error) {
	query := &vtctldatapb.ExecuteVtctlCommandRequest{
		Args:          args,
		ActionTimeout: int64(actionTimeout.Nanoseconds()),
	}

	stream, err := client.c.ExecuteVtctlCommand(ctx, query)
	if err != nil {
		return nil, err
	}
	return &eventStreamAdapter{stream}, nil
}

// Close is part of the VtctlClient interface
func (client *gRPCVtctlClient) Close() {
	client.cc.Close()
}

func init() {
	vtctlclient.RegisterFactory("grpc", gRPCVtctlClientFactory)
}
<commit_msg>Add TLS flags to vtctlclient.<commit_after>/*
Copyright 2017 Google Inc.

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 grpcvtctlclient contains the gRPC version of the vtctl client protocol
package grpcvtctlclient

import (
	"flag"
	"time"

	"github.com/youtube/vitess/go/vt/logutil"
	"github.com/youtube/vitess/go/vt/servenv/grpcutils"
	"github.com/youtube/vitess/go/vt/vtctl/vtctlclient"
	"golang.org/x/net/context"
	"google.golang.org/grpc"

	logutilpb "github.com/youtube/vitess/go/vt/proto/logutil"
	vtctldatapb "github.com/youtube/vitess/go/vt/proto/vtctldata"
	vtctlservicepb "github.com/youtube/vitess/go/vt/proto/vtctlservice"
)

var (
	cert = flag.String("vtctld_grpc_cert", "", "the cert to use to connect")
	key  = flag.String("vtctld_grpc_key", "", "the key to use to connect")
	ca   = flag.String("vtctld_grpc_ca", "", "the server ca to use to validate servers when connecting")
	name = flag.String("vtctld_grpc_server_name", "", "the server name to use to validate server certificate")
)

type gRPCVtctlClient struct {
	cc *grpc.ClientConn
	c  vtctlservicepb.VtctlClient
}

func gRPCVtctlClientFactory(addr string, dialTimeout time.Duration) (vtctlclient.VtctlClient, error) {
	opt, err := grpcutils.ClientSecureDialOption(*cert, *key, *ca, *name)
	if err != nil {
		return nil, err
	}
	// create the RPC client
	cc, err := grpc.Dial(addr, opt, grpc.WithBlock(), grpc.WithTimeout(dialTimeout))
	if err != nil {
		return nil, err
	}
	c := vtctlservicepb.NewVtctlClient(cc)

	return &gRPCVtctlClient{
		cc: cc,
		c:  c,
	}, nil
}

type eventStreamAdapter struct {
	stream vtctlservicepb.Vtctl_ExecuteVtctlCommandClient
}

func (e *eventStreamAdapter) Recv() (*logutilpb.Event, error) {
	le, err := e.stream.Recv()
	if err != nil {
		return nil, err
	}
	return le.Event, nil
}

// ExecuteVtctlCommand is part of the VtctlClient interface
func (client *gRPCVtctlClient) ExecuteVtctlCommand(ctx context.Context, args []string, actionTimeout time.Duration) (logutil.EventStream, error) {
	query := &vtctldatapb.ExecuteVtctlCommandRequest{
		Args:          args,
		ActionTimeout: int64(actionTimeout.Nanoseconds()),
	}

	stream, err := client.c.ExecuteVtctlCommand(ctx, query)
	if err != nil {
		return nil, err
	}
	return &eventStreamAdapter{stream}, nil
}

// Close is part of the VtctlClient interface
func (client *gRPCVtctlClient) Close() {
	client.cc.Close()
}

func init() {
	vtctlclient.RegisterFactory("grpc", gRPCVtctlClientFactory)
}
<|endoftext|>


<commit_before>package server

import (
	"encoding/json"
	"fmt"
	"reflect"

	"github.com/b2aio/typhon/errors"
	log "github.com/cihub/seelog"
	"github.com/golang/protobuf/proto"
)

type Endpoint struct {
	Name     string
	Handler  func(Request) (proto.Message, error)
	Request  interface{}
	Response interface{}
}

func (e *Endpoint) HandleRequest(req Request) (proto.Message, error) {
	var err error

	// @todo check that `Request` and `Response` are set in RegisterEndpoint

	if e.Request != nil {
		body := cloneTypedPtr(e.Request).(proto.Message)

		if req.ContentType() == "application/x-protobuf" {
			err = proto.Unmarshal(req.Payload(), body)
		} else {
			err = json.Unmarshal(req.Payload(), body)
		}
		if err != nil {
			return nil, errors.Wrap(fmt.Errorf("Could not unmarshal request"))
		}

		req.SetBody(body)
	}

	log.Debugf("%s.%s handler received request: %+v", req.Service(), e.Name, req.Body())

	resp, err := e.Handler(req)

	if err != nil {
		err = enrichError(err, req, e)
		log.Errorf("%s.%s handler error: %s", req.Service(), e.Name, err.Error())
	} else {
		log.Debugf("%s.%s handler response: %+v", req.Service(), e.Name, resp)
	}

	return resp, err
	// @todo return error if e.Response is set and doesn't match
}

// cloneTypedPtr takes a pointer of any type and returns a pointer to
// to a newly allocated instance of that same type.
// This allows us to write generic unmarshalling code that is independent
// of a endpoint's expected message type. This way we can handle
// unmarshalling errors outside of the actual endpoint handler methods.
func cloneTypedPtr(reqType interface{}) interface{} {
	// http://play.golang.org/p/MJOc3g7t23
	// `reflect.New` gives us a `reflect.Value`, using type of `reflect.TypeIf(e.RequestType()).Elem()` (a struct type)
	// and that struct's zero value for a value.
	// `reflectValue.Interface()` puts the type and value back together into an interface type
	reflectValue := reflect.New(reflect.TypeOf(reqType).Elem())
	return reflectValue.Interface()
}

// enrichError converts an error interface into *errors.Error and attaches
// lots of information to it.
// NOTE: if the error came from somewhere down the stack, it isn't modified
// @todo once the server context gives us a parent request and trace id, we can store even more information in the error!
func enrichError(err error, ctx Request, endpoint *Endpoint) *errors.Error {
	wrappedErr := errors.Wrap(err)

	// @todo an error will probably have a source_request_id or something that we can use to
	// more reliably make sure this information is only attached once, as the error travels up the service stack
	if wrappedErr.PrivateContext["service"] == "" {
		wrappedErr.PrivateContext["service"] = ctx.Service()
	}
	if wrappedErr.PrivateContext["endpoint"] == "" {
		wrappedErr.PrivateContext["endpoint"] = endpoint.Name
	}
	return wrappedErr
}
<commit_msg>Handle unmarshaling nil requests<commit_after>package server

import (
	"encoding/json"
	"fmt"
	"reflect"

	"github.com/b2aio/typhon/errors"
	log "github.com/cihub/seelog"
	"github.com/golang/protobuf/proto"
)

type Endpoint struct {
	Name     string
	Handler  func(Request) (proto.Message, error)
	Request  interface{}
	Response interface{}
}

func (e *Endpoint) HandleRequest(req Request) (proto.Message, error) {
	var err error

	// @todo check that `Request` and `Response` are set in RegisterEndpoint

	if e.Request != nil {
		body := cloneTypedPtr(e.Request).(proto.Message)
		if err := unmarshalRequest(req, body); err != nil {
			return nil, err
		}
		req.SetBody(body)
	}

	log.Debugf("%s.%s handler received request: %+v", req.Service(), e.Name, req.Body())

	resp, err := e.Handler(req)

	if err != nil {
		err = enrichError(err, req, e)
		log.Errorf("%s.%s handler error: %s", req.Service(), e.Name, err.Error())
	} else {
		log.Debugf("%s.%s handler response: %+v", req.Service(), e.Name, resp)
	}

	return resp, err
	// @todo return error if e.Response is set and doesn't match
}

// cloneTypedPtr takes a pointer of any type and returns a pointer to
// to a newly allocated instance of that same type.
// This allows us to write generic unmarshalling code that is independent
// of a endpoint's expected message type. This way we can handle
// unmarshalling errors outside of the actual endpoint handler methods.
func cloneTypedPtr(reqType interface{}) interface{} {
	// http://play.golang.org/p/MJOc3g7t23
	// `reflect.New` gives us a `reflect.Value`, using type of `reflect.TypeIf(e.RequestType()).Elem()` (a struct type)
	// and that struct's zero value for a value.
	// `reflectValue.Interface()` puts the type and value back together into an interface type
	reflectValue := reflect.New(reflect.TypeOf(reqType).Elem())
	return reflectValue.Interface()
}

// enrichError converts an error interface into *errors.Error and attaches
// lots of information to it.
// NOTE: if the error came from somewhere down the stack, it isn't modified
// @todo once the server context gives us a parent request and trace id, we can store even more information in the error!
func enrichError(err error, ctx Request, endpoint *Endpoint) *errors.Error {
	wrappedErr := errors.Wrap(err)

	// @todo an error will probably have a source_request_id or something that we can use to
	// more reliably make sure this information is only attached once, as the error travels up the service stack
	if wrappedErr.PrivateContext["service"] == "" {
		wrappedErr.PrivateContext["service"] = ctx.Service()
	}
	if wrappedErr.PrivateContext["endpoint"] == "" {
		wrappedErr.PrivateContext["endpoint"] = endpoint.Name
	}
	return wrappedErr
}

// unmarshalRequest payload into the body based on content-type
func unmarshalRequest(req Request, body proto.Message) (err error) {
	if len(req.Payload()) == 0 {
		return nil
	}

	if req.ContentType() == "application/x-protobuf" {
		return proto.Unmarshal(req.Payload(), body)
	} else {
		return json.Unmarshal(req.Payload(), body)
	}

	return errors.Wrap(fmt.Errorf("Could not unmarshal request"))
}
<|endoftext|>