<commit_before>package router551

import (
	"github.com/go51/container551"
	"github.com/go51/string551"
	"regexp"
	"runtime"
)

type routerMethod int

const (
	GET     routerMethod = 1
	POST    routerMethod = 2
	PUT     routerMethod = 4
	DELETE  routerMethod = 8
	COMMAND routerMethod = 16
	UNKNOWN routerMethod = 32
)

func (rm routerMethod) String() string {
	switch rm {
	case GET:
		return "GET"
	case POST:
		return "POST"
	case PUT:
		return "PUT"
	case DELETE:
		return "DELETE"
	case COMMAND:
		return "COMMAND"
	}
	return "UNKNOWN"
}

type ActionFunc func(c *container551.Container) interface{}

type Router struct {
	get     map[string]*route
	post    map[string]*route
	put     map[string]*route
	delete  map[string]*route
	command map[string]*route
}

var routerInstance *Router

func Load() *Router {
	if routerInstance != nil {
		return routerInstance
	}

	routerInstance = &Router{
		get:     make(map[string]*route, 0),
		post:    make(map[string]*route, 0),
		put:     make(map[string]*route, 0),
		delete:  make(map[string]*route, 0),
		command: make(map[string]*route, 0),
	}

	return routerInstance
}

type route struct {
	name        string
	pattern     string
	regex       *regexp.Regexp
	packageName string
	action      ActionFunc
	keys        []string
}

func (r *route) Name() string {
	return r.name
}

func (r *route) Keys() []string {
	return r.keys
}

func (r *route) Action() ActionFunc {
	return r.action
}

func (r *route) PackageName() string {
	return r.packageName
}

func (r *Router) Add(method routerMethod, name, pattern string, action ActionFunc) {

	pc, _, _, _ := runtime.Caller(1)
	path := runtime.FuncForPC(pc).Name()

	route := &route{
		name:        name,
		pattern:     pattern,
		regex:       nil,
		packageName: r.getPackageName(path),
		action:      action,
		keys:        []string{},
	}

	if method != COMMAND {
		route.keys = r.getKeys(pattern)
		regexPattern := pattern
		for i := 0; i < len(route.keys); i++ {
			regexPattern = string551.Replace(regexPattern, ":"+route.keys[i]+":", ".*")
		}
		route.regex = regexp.MustCompile("^" + regexPattern + "$")
	}

	if method&GET == GET {
		r.addGet(name, route)
	}
	if method&POST == POST {
		r.addPost(name, route)
	}
	if method&PUT == PUT {
		r.addPut(name, route)
	}
	if method&DELETE == DELETE {
		r.addDelete(name, route)
	}
	if method&COMMAND == COMMAND {
		r.addCommand(name, route)
	}
}

func (r *Router) addGet(name string, route *route) {
	r.get[name] = route
}

func (r *Router) addPost(name string, route *route) {
	r.post[name] = route
}

func (r *Router) addPut(name string, route *route) {
	r.put[name] = route
}

func (r *Router) addDelete(name string, route *route) {
	r.delete[name] = route
}

func (r *Router) addCommand(name string, route *route) {
	r.command[name] = route
}

func (r *Router) getKeys(pattern string) []string {
	keys := []string{}

	patternBytes := string551.StringToBytes(pattern)
	coron := false

	keyBytes := []byte{}

	for i := 0; i < len(patternBytes); i++ {
		if patternBytes[i] == 0x3A {
			// 0x3A => ":"
			if !coron {
				keyBytes = []byte{}
				coron = true
			} else {
				keys = append(keys, string551.BytesToString(keyBytes))
				coron = false
			}
		} else {
			if coron {
				keyBytes = append(keyBytes, patternBytes[i])
			}
		}
	}

	return keys
}

var regexpControllerPattern *regexp.Regexp

func (r *Router) getPackageName(path string) string {
	if regexpControllerPattern == nil {
		regexpControllerPattern = regexp.MustCompile(`\/\w+\.`)
	}

	packageName := regexpControllerPattern.FindString(path)

	return packageName[1 : len(packageName)-1]
}

func (r *Router) FindRouteByName(method, name string) *route {
	if method == GET.String() {
		return r.get[name]
	}
	if method == POST.String() {
		return r.post[name]
	}
	if method == PUT.String() {
		return r.put[name]
	}
	if method == DELETE.String() {
		return r.delete[name]
	}
	if method == COMMAND.String() {
		return r.command[name]
	}

	return nil
}

func (r *Router) FindRouteByPathMatch(method, path string) *route {
	routes := r.getRoutes(method)

	for _, route := range routes {
		if route.pattern == path {
			return route
		}
	}

	for _, route := range routes {
		if route.pattern == "/" {
			continue
		}
		if route.regex.MatchString(path) {
			return route
		}
	}

	return nil
}

func (r *Router) getRoutes(method string) map[string]*route {
	switch method {
	case GET.String():
		return r.get
	case POST.String():
		return r.post
	case PUT.String():
		return r.put
	case DELETE.String():
		return r.delete
	case COMMAND.String():
		return r.command
	}

	return make(map[string]*route)
}

func (r *Router) Url(name string, parameter ...string) string {

	var route *route = nil

	if r.get[name] != nil {
		route = r.get[name]
	} else if r.post[name] != nil {
		route = r.post[name]
	} else if r.put[name] != nil {
		route = r.put[name]
	} else if r.delete[name] != nil {
		route = r.delete[name]
	}

	if route == nil {
		// Not Found
		return "#"
	}

	if len(parameter) != 0 && len(parameter) != len(route.keys) {
		// No Match
		return "#"
	}

	//----------------------------------------
	// Match
	//----------------------------------------
	if len(parameter) == 0 {
		// No paramter
		return route.pattern
	}

	url := route.pattern
	keys := route.keys
	for i := 0; i < len(parameter); i++ {
		url = string551.Replace(url, ":"+keys[i]+":", parameter[i])

	}

	return url
}
<commit_msg>refs #24 add に packageName を追加<commit_after>package router551

import (
	"github.com/go51/container551"
	"github.com/go51/string551"
	"regexp"
	"runtime"
)

type routerMethod int

const (
	GET     routerMethod = 1
	POST    routerMethod = 2
	PUT     routerMethod = 4
	DELETE  routerMethod = 8
	COMMAND routerMethod = 16
	UNKNOWN routerMethod = 32
)

func (rm routerMethod) String() string {
	switch rm {
	case GET:
		return "GET"
	case POST:
		return "POST"
	case PUT:
		return "PUT"
	case DELETE:
		return "DELETE"
	case COMMAND:
		return "COMMAND"
	}
	return "UNKNOWN"
}

type ActionFunc func(c *container551.Container) interface{}

type Router struct {
	get     map[string]*route
	post    map[string]*route
	put     map[string]*route
	delete  map[string]*route
	command map[string]*route
}

var routerInstance *Router

func Load() *Router {
	if routerInstance != nil {
		return routerInstance
	}

	routerInstance = &Router{
		get:     make(map[string]*route, 0),
		post:    make(map[string]*route, 0),
		put:     make(map[string]*route, 0),
		delete:  make(map[string]*route, 0),
		command: make(map[string]*route, 0),
	}

	return routerInstance
}

type route struct {
	name        string
	pattern     string
	regex       *regexp.Regexp
	packageName string
	action      ActionFunc
	keys        []string
}

func (r *route) Name() string {
	return r.name
}

func (r *route) Keys() []string {
	return r.keys
}

func (r *route) Action() ActionFunc {
	return r.action
}

func (r *route) PackageName() string {
	return r.packageName
}

func (r *Router) Add(packageName string, method routerMethod, name, pattern string, action ActionFunc) {

	route := &route{
		name:        name,
		pattern:     pattern,
		regex:       nil,
		packageName: packageName,
		action:      action,
		keys:        []string{},
	}

	if method != COMMAND {
		route.keys = r.getKeys(pattern)
		regexPattern := pattern
		for i := 0; i < len(route.keys); i++ {
			regexPattern = string551.Replace(regexPattern, ":"+route.keys[i]+":", ".*")
		}
		route.regex = regexp.MustCompile("^" + regexPattern + "$")
	}

	if method&GET == GET {
		r.addGet(name, route)
	}
	if method&POST == POST {
		r.addPost(name, route)
	}
	if method&PUT == PUT {
		r.addPut(name, route)
	}
	if method&DELETE == DELETE {
		r.addDelete(name, route)
	}
	if method&COMMAND == COMMAND {
		r.addCommand(name, route)
	}
}

func (r *Router) addGet(name string, route *route) {
	r.get[name] = route
}

func (r *Router) addPost(name string, route *route) {
	r.post[name] = route
}

func (r *Router) addPut(name string, route *route) {
	r.put[name] = route
}

func (r *Router) addDelete(name string, route *route) {
	r.delete[name] = route
}

func (r *Router) addCommand(name string, route *route) {
	r.command[name] = route
}

func (r *Router) getKeys(pattern string) []string {
	keys := []string{}

	patternBytes := string551.StringToBytes(pattern)
	coron := false

	keyBytes := []byte{}

	for i := 0; i < len(patternBytes); i++ {
		if patternBytes[i] == 0x3A {
			// 0x3A => ":"
			if !coron {
				keyBytes = []byte{}
				coron = true
			} else {
				keys = append(keys, string551.BytesToString(keyBytes))
				coron = false
			}
		} else {
			if coron {
				keyBytes = append(keyBytes, patternBytes[i])
			}
		}
	}

	return keys
}

var regexpControllerPattern *regexp.Regexp

func (r *Router) getPackageName(path string) string {
	if regexpControllerPattern == nil {
		regexpControllerPattern = regexp.MustCompile(`\/\w+\.`)
	}

	packageName := regexpControllerPattern.FindString(path)

	return packageName[1 : len(packageName)-1]
}

func (r *Router) FindRouteByName(method, name string) *route {
	if method == GET.String() {
		return r.get[name]
	}
	if method == POST.String() {
		return r.post[name]
	}
	if method == PUT.String() {
		return r.put[name]
	}
	if method == DELETE.String() {
		return r.delete[name]
	}
	if method == COMMAND.String() {
		return r.command[name]
	}

	return nil
}

func (r *Router) FindRouteByPathMatch(method, path string) *route {
	routes := r.getRoutes(method)

	for _, route := range routes {
		if route.pattern == path {
			return route
		}
	}

	for _, route := range routes {
		if route.pattern == "/" {
			continue
		}
		if route.regex.MatchString(path) {
			return route
		}
	}

	return nil
}

func (r *Router) getRoutes(method string) map[string]*route {
	switch method {
	case GET.String():
		return r.get
	case POST.String():
		return r.post
	case PUT.String():
		return r.put
	case DELETE.String():
		return r.delete
	case COMMAND.String():
		return r.command
	}

	return make(map[string]*route)
}

func (r *Router) Url(name string, parameter ...string) string {

	var route *route = nil

	if r.get[name] != nil {
		route = r.get[name]
	} else if r.post[name] != nil {
		route = r.post[name]
	} else if r.put[name] != nil {
		route = r.put[name]
	} else if r.delete[name] != nil {
		route = r.delete[name]
	}

	if route == nil {
		// Not Found
		return "#"
	}

	if len(parameter) != 0 && len(parameter) != len(route.keys) {
		// No Match
		return "#"
	}

	//----------------------------------------
	// Match
	//----------------------------------------
	if len(parameter) == 0 {
		// No paramter
		return route.pattern
	}

	url := route.pattern
	keys := route.keys
	for i := 0; i < len(parameter); i++ {
		url = string551.Replace(url, ":"+keys[i]+":", parameter[i])

	}

	return url
}
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2013, 2014 Conformal Systems LLC <info@conformal.com>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

// This file implements the websocket client connection to a bitcoin RPC
// server.

package main

import (
	"code.google.com/p/go.net/websocket"
	"encoding/hex"
	"encoding/json"
	"errors"
	"github.com/conformal/btcjson"
	"github.com/conformal/btcutil"
	"github.com/conformal/btcwire"
	"github.com/conformal/btcws"
)

// ServerConn is an interface representing a client connection to a bitcoin RPC
// server.
type ServerConn interface {
	// SendRequest sends a bitcoin RPC request, returning a channel to
	// read the reply.  A channel is used so both synchronous and
	// asynchronous RPC can be supported.
	SendRequest(request *ServerRequest) chan RPCResponse
}

// ErrBtcdDisconnected describes an error where an operation cannot
// successfully complete due to btcwallet not being connected to
// btcd.
var ErrBtcdDisconnected = btcjson.Error{
	Code:    -1,
	Message: "btcd disconnected",
}

// BtcdRPCConn is a type managing a client connection to a btcd RPC server
// over websockets.
type BtcdRPCConn struct {
	ws         *websocket.Conn
	addRequest chan *AddRPCRequest
	closed     chan struct{}
}

// Ensure that BtcdRPCConn can be used as an RPCConn.
var _ ServerConn = &BtcdRPCConn{}

// NewBtcdRPCConn creates a new RPC connection from a btcd websocket
// connection to btcd.
func NewBtcdRPCConn(ws *websocket.Conn) *BtcdRPCConn {
	conn := &BtcdRPCConn{
		ws:         ws,
		addRequest: make(chan *AddRPCRequest),
		closed:     make(chan struct{}),
	}
	return conn
}

// SendRequest sends an RPC request and returns a channel to read the response's
// result and error.  Part of the RPCConn interface.
func (btcd *BtcdRPCConn) SendRequest(request *ServerRequest) chan RPCResponse {
	select {
	case <-btcd.closed:
		// The connection has closed, so instead of adding and sending
		// a request, return a channel that just replies with the
		// error for a disconnected btcd.
		responseChan := make(chan RPCResponse, 1)
		response := &ServerResponse{
			err: &ErrBtcdDisconnected,
		}
		responseChan <- response
		return responseChan

	default:
		addRequest := &AddRPCRequest{
			Request:      request,
			ResponseChan: make(chan chan RPCResponse, 1),
		}
		btcd.addRequest <- addRequest
		return <-addRequest.ResponseChan
	}
}

// Connected returns whether the connection remains established to the RPC
// server.
//
// This function probably should be removed, as any checks for confirming
// the connection are no longer valid after the check and may result in
// races.
func (btcd *BtcdRPCConn) Connected() bool {
	select {
	case <-btcd.closed:
		return false

	default:
		return true
	}
}

// Close forces closing the current btcd connection.
func (btcd *BtcdRPCConn) Close() {
	select {
	case <-btcd.closed:
	default:
		close(btcd.closed)
	}
}

// AddRPCRequest is used to add an RPCRequest to the pool of requests
// being manaaged by a btcd RPC connection.
type AddRPCRequest struct {
	Request      *ServerRequest
	ResponseChan chan chan RPCResponse
}

// send performs the actual send of the marshaled request over the btcd
// websocket connection.
func (btcd *BtcdRPCConn) send(rpcrequest *ServerRequest) error {
	// btcjson.Cmds define their own MarshalJSON which returns an error
	// to satisify the json.Marshaler interface, but will never error.
	mrequest, _ := rpcrequest.request.MarshalJSON()
	return websocket.Message.Send(btcd.ws, mrequest)
}

type receivedResponse struct {
	id    uint64
	raw   string
	reply *btcjson.Reply
}

// Start starts the goroutines required to send RPC requests and listen for
// replies.
func (btcd *BtcdRPCConn) Start() {
	done := btcd.closed
	responses := make(chan *receivedResponse)

	// Maintain a map of JSON IDs to RPCRequests currently being waited on.
	go func() {
		m := make(map[uint64]*ServerRequest)
		for {
			select {
			case addrequest := <-btcd.addRequest:
				rpcrequest := addrequest.Request
				m[rpcrequest.request.Id().(uint64)] = rpcrequest

				if err := btcd.send(rpcrequest); err != nil {
					// Connection lost.
					btcd.ws.Close()
					close(done)
				}

				addrequest.ResponseChan <- rpcrequest.response

			case recvResponse := <-responses:
				rpcrequest, ok := m[recvResponse.id]
				if !ok {
					log.Warnf("Received unexpected btcd response")
					continue
				}
				delete(m, recvResponse.id)

				// If no result var was set, create and send
				// send the response unmarshaled by the json
				// package.
				if rpcrequest.result == nil {
					response := &ServerResponse{
						result: recvResponse.reply.Result,
						err:    recvResponse.reply.Error,
					}
					rpcrequest.response <- response
					continue
				}

				// A return var was set, so unmarshal again
				// into the var before sending the response.
				r := &btcjson.Reply{
					Result: rpcrequest.result,
				}
				json.Unmarshal([]byte(recvResponse.raw), &r)
				response := &ServerResponse{
					result: r.Result,
					err:    r.Error,
				}
				rpcrequest.response <- response

			case <-done:
				for _, request := range m {
					response := &ServerResponse{
						err: &ErrBtcdDisconnected,
					}
					request.response <- response
				}
				return
			}
		}
	}()

	// Listen for replies/notifications from btcd, and decide how to handle them.
	go func() {
		// Idea: instead of reading btcd messages from just one websocket
		// connection, maybe use two so the same connection isn't used
		// for both notifications and responses?  Should make handling
		// must faster as unnecessary unmarshal attempts could be avoided.

		for {
			var m string
			if err := websocket.Message.Receive(btcd.ws, &m); err != nil {
				log.Debugf("Cannot receive btcd message: %v", err)
				close(done)
				return
			}

			// Try notifications (requests with nil ids) first.
			n, err := unmarshalNotification(m)
			if err == nil {
				svrNtfns <- n
				continue
			}

			// Must be a response.
			r, err := unmarshalResponse(m)
			if err == nil {
				responses <- r
				continue
			}

			// Not sure what was received but it isn't correct.
			log.Warnf("Received invalid message from btcd")
		}
	}()
}

// unmarshalResponse attempts to unmarshal a marshaled JSON-RPC
// response.
func unmarshalResponse(s string) (*receivedResponse, error) {
	var r btcjson.Reply
	if err := json.Unmarshal([]byte(s), &r); err != nil {
		return nil, err
	}

	// Check for a valid ID.
	if r.Id == nil {
		return nil, errors.New("id is nil")
	}
	fid, ok := (*r.Id).(float64)
	if !ok {
		return nil, errors.New("id is not a number")
	}
	response := &receivedResponse{
		id:    uint64(fid),
		raw:   s,
		reply: &r,
	}
	return response, nil
}

// unmarshalNotification attempts to unmarshal a marshaled JSON-RPC
// notification (Request with a nil or no ID).
func unmarshalNotification(s string) (btcjson.Cmd, error) {
	req, err := btcjson.ParseMarshaledCmd([]byte(s))
	if err != nil {
		return nil, err
	}

	if req.Id() != nil {
		return nil, errors.New("id is non-nil")
	}

	return req, nil
}

// GetBestBlockResult holds the result of a getbestblock response.
//
// TODO(jrick): shove this in btcws.
type GetBestBlockResult struct {
	Hash   string `json:"hash"`
	Height int32  `json:"height"`
}

// GetBestBlock gets both the block height and hash of the best block
// in the main chain.
func GetBestBlock(rpc ServerConn) (*GetBestBlockResult, *btcjson.Error) {
	cmd := btcws.NewGetBestBlockCmd(<-NewJSONID)
	request := NewServerRequest(cmd, new(GetBestBlockResult))
	response := <-rpc.SendRequest(request)
	if response.Error() != nil {
		return nil, response.Error()
	}
	return response.Result().(*GetBestBlockResult), nil
}

// GetBlock requests details about a block with the given hash.
func GetBlock(rpc ServerConn, blockHash string) (*btcjson.BlockResult, *btcjson.Error) {
	// NewGetBlockCmd cannot fail with no optargs, so omit the check.
	cmd, _ := btcjson.NewGetBlockCmd(<-NewJSONID, blockHash)
	request := NewServerRequest(cmd, new(btcjson.BlockResult))
	response := <-rpc.SendRequest(request)
	if response.Error() != nil {
		return nil, response.Error()
	}
	return response.Result().(*btcjson.BlockResult), nil
}

// GetCurrentNet requests the network a bitcoin RPC server is running on.
func GetCurrentNet(rpc ServerConn) (btcwire.BitcoinNet, *btcjson.Error) {
	cmd := btcws.NewGetCurrentNetCmd(<-NewJSONID)
	request := NewServerRequest(cmd, nil)
	response := <-rpc.SendRequest(request)
	if response.Error() != nil {
		return 0, response.Error()
	}
	return btcwire.BitcoinNet(uint32(response.Result().(float64))), nil
}

// NotifyBlocks requests blockconnected and blockdisconnected notifications.
func NotifyBlocks(rpc ServerConn) *btcjson.Error {
	cmd := btcws.NewNotifyBlocksCmd(<-NewJSONID)
	request := NewServerRequest(cmd, nil)
	response := <-rpc.SendRequest(request)
	return response.Error()
}

// NotifyNewTXs requests notifications for new transactions that spend
// to any of the addresses in addrs.
func NotifyNewTXs(rpc ServerConn, addrs []string) *btcjson.Error {
	cmd := btcws.NewNotifyNewTXsCmd(<-NewJSONID, addrs)
	request := NewServerRequest(cmd, nil)
	response := <-rpc.SendRequest(request)
	return response.Error()
}

// NotifySpent requests notifications for when a transaction is processed which
// spends op.
func NotifySpent(rpc ServerConn, op *btcwire.OutPoint) *btcjson.Error {
	cmd := btcws.NewNotifySpentCmd(<-NewJSONID, op)
	request := NewServerRequest(cmd, nil)
	response := <-rpc.SendRequest(request)
	return response.Error()
}

// Rescan requests a blockchain rescan for transactions to any number of
// addresses and notifications to inform wallet about such transactions.
func Rescan(rpc ServerConn, beginBlock int32, addrs map[string]struct{}) *btcjson.Error {
	// NewRescanCmd cannot fail with no optargs, so omit the check.
	cmd, _ := btcws.NewRescanCmd(<-NewJSONID, beginBlock, addrs)
	request := NewServerRequest(cmd, nil)
	response := <-rpc.SendRequest(request)
	return response.Error()
}

// SendRawTransaction sends a hex-encoded transaction for relay.
func SendRawTransaction(rpc ServerConn, hextx string) (txid string, error *btcjson.Error) {
	// NewSendRawTransactionCmd cannot fail, so omit the check.
	cmd, _ := btcjson.NewSendRawTransactionCmd(<-NewJSONID, hextx)
	request := NewServerRequest(cmd, new(string))
	response := <-rpc.SendRequest(request)
	if response.Error() != nil {
		return "", response.Error()
	}
	return *response.Result().(*string), nil
}

// GetRawTransaction sends the non-verbose version of a getrawtransaction
// request to receive the serialized transaction referenced by txsha.  If
// successful, the transaction is decoded and returned as a btcutil.Tx.
func GetRawTransaction(rpc ServerConn, txsha *btcwire.ShaHash) (*btcutil.Tx, *btcjson.Error) {
	// NewGetRawTransactionCmd cannot fail with no optargs.
	cmd, _ := btcjson.NewGetRawTransactionCmd(<-NewJSONID, txsha.String())
	request := NewServerRequest(cmd, new(string))
	response := <-rpc.SendRequest(request)
	if response.Error() != nil {
		return nil, response.Error()
	}
	hextx := *response.Result().(*string)
	serializedTx, err := hex.DecodeString(hextx)
	if err != nil {
		return nil, &btcjson.ErrDecodeHexString
	}
	utx, err := btcutil.NewTxFromBytes(serializedTx)
	if err != nil {
		return nil, &btcjson.ErrDeserialization
	}
	return utx, nil
}

// VerboseGetRawTransaction sends the verbose version of a getrawtransaction
// request to receive details about a transaction.
func VerboseGetRawTransaction(rpc ServerConn, txsha *btcwire.ShaHash) (*btcjson.TxRawResult, *btcjson.Error) {
	// NewGetRawTransactionCmd cannot fail with a single optarg.
	cmd, _ := btcjson.NewGetRawTransactionCmd(<-NewJSONID, txsha.String(), 1)
	request := NewServerRequest(cmd, new(btcjson.TxRawResult))
	response := <-rpc.SendRequest(request)
	if response.Error() != nil {
		return nil, response.Error()
	}
	return response.Result().(*btcjson.TxRawResult), nil
}
<commit_msg>Improve btcd connection management.<commit_after>/*
 * Copyright (c) 2013, 2014 Conformal Systems LLC <info@conformal.com>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

// This file implements the websocket client connection to a bitcoin RPC
// server.

package main

import (
	"code.google.com/p/go.net/websocket"
	"encoding/hex"
	"encoding/json"
	"errors"
	"github.com/conformal/btcjson"
	"github.com/conformal/btcutil"
	"github.com/conformal/btcwire"
	"github.com/conformal/btcws"
)

// ServerConn is an interface representing a client connection to a bitcoin RPC
// server.
type ServerConn interface {
	// SendRequest sends a bitcoin RPC request, returning a channel to
	// read the reply.  A channel is used so both synchronous and
	// asynchronous RPC can be supported.
	SendRequest(request *ServerRequest) chan RPCResponse
}

// ErrBtcdDisconnected describes an error where an operation cannot
// successfully complete due to btcwallet not being connected to
// btcd.
var ErrBtcdDisconnected = btcjson.Error{
	Code:    -1,
	Message: "btcd disconnected",
}

// BtcdRPCConn is a type managing a client connection to a btcd RPC server
// over websockets.
type BtcdRPCConn struct {
	ws         *websocket.Conn
	addRequest chan *AddRPCRequest
	closed     chan struct{}
}

// Ensure that BtcdRPCConn can be used as an RPCConn.
var _ ServerConn = &BtcdRPCConn{}

// NewBtcdRPCConn creates a new RPC connection from a btcd websocket
// connection to btcd.
func NewBtcdRPCConn(ws *websocket.Conn) *BtcdRPCConn {
	conn := &BtcdRPCConn{
		ws:         ws,
		addRequest: make(chan *AddRPCRequest),
		closed:     make(chan struct{}),
	}
	return conn
}

// SendRequest sends an RPC request and returns a channel to read the response's
// result and error.  Part of the RPCConn interface.
func (btcd *BtcdRPCConn) SendRequest(request *ServerRequest) chan RPCResponse {
	select {
	case <-btcd.closed:
		// The connection has closed, so instead of adding and sending
		// a request, return a channel that just replies with the
		// error for a disconnected btcd.
		responseChan := make(chan RPCResponse, 1)
		response := &ServerResponse{
			err: &ErrBtcdDisconnected,
		}
		responseChan <- response
		return responseChan

	default:
		addRequest := &AddRPCRequest{
			Request:      request,
			ResponseChan: make(chan chan RPCResponse, 1),
		}
		btcd.addRequest <- addRequest
		return <-addRequest.ResponseChan
	}
}

// Connected returns whether the connection remains established to the RPC
// server.
//
// This function probably should be removed, as any checks for confirming
// the connection are no longer valid after the check and may result in
// races.
func (btcd *BtcdRPCConn) Connected() bool {
	select {
	case <-btcd.closed:
		return false

	default:
		return true
	}
}

// Close forces closing the current btcd connection.
func (btcd *BtcdRPCConn) Close() {
	select {
	case <-btcd.closed:
	default:
		close(btcd.closed)
	}
}

// AddRPCRequest is used to add an RPCRequest to the pool of requests
// being manaaged by a btcd RPC connection.
type AddRPCRequest struct {
	Request      *ServerRequest
	ResponseChan chan chan RPCResponse
}

// send performs the actual send of the marshaled request over the btcd
// websocket connection.
func (btcd *BtcdRPCConn) send(rpcrequest *ServerRequest) error {
	// btcjson.Cmds define their own MarshalJSON which returns an error
	// to satisify the json.Marshaler interface, but will never error.
	mrequest, _ := rpcrequest.request.MarshalJSON()
	return websocket.Message.Send(btcd.ws, mrequest)
}

type receivedResponse struct {
	id    uint64
	raw   string
	reply *btcjson.Reply
}

// Start starts the goroutines required to send RPC requests and listen for
// replies.
func (btcd *BtcdRPCConn) Start() {
	done := btcd.closed
	responses := make(chan *receivedResponse)

	// Maintain a map of JSON IDs to RPCRequests currently being waited on.
	go func() {
		m := make(map[uint64]*ServerRequest)
		for {
			select {
			case addrequest := <-btcd.addRequest:
				rpcrequest := addrequest.Request
				m[rpcrequest.request.Id().(uint64)] = rpcrequest

				if err := btcd.send(rpcrequest); err != nil {
					// Connection lost.
					log.Infof("Cannot complete btcd websocket send: %v",
						err)
					btcd.ws.Close()
					close(done)
				}

				addrequest.ResponseChan <- rpcrequest.response

			case recvResponse, ok := <-responses:
				if !ok {
					responses = nil
					close(done)
					break
				}
				rpcrequest, ok := m[recvResponse.id]
				if !ok {
					log.Warnf("Received unexpected btcd response")
					continue
				}
				delete(m, recvResponse.id)

				// If no result var was set, create and send
				// send the response unmarshaled by the json
				// package.
				if rpcrequest.result == nil {
					response := &ServerResponse{
						result: recvResponse.reply.Result,
						err:    recvResponse.reply.Error,
					}
					rpcrequest.response <- response
					continue
				}

				// A return var was set, so unmarshal again
				// into the var before sending the response.
				r := &btcjson.Reply{
					Result: rpcrequest.result,
				}
				json.Unmarshal([]byte(recvResponse.raw), &r)
				response := &ServerResponse{
					result: r.Result,
					err:    r.Error,
				}
				rpcrequest.response <- response

			case <-done:
				response := &ServerResponse{
					err: &ErrBtcdDisconnected,
				}
				for _, request := range m {
					request.response <- response
				}
				return
			}
		}
	}()

	// Listen for replies/notifications from btcd, and decide how to handle them.
	go func() {
		// Idea: instead of reading btcd messages from just one websocket
		// connection, maybe use two so the same connection isn't used
		// for both notifications and responses?  Should make handling
		// must faster as unnecessary unmarshal attempts could be avoided.

		for {
			var m string
			if err := websocket.Message.Receive(btcd.ws, &m); err != nil {
				log.Infof("Cannot receive btcd websocket message: %v",
					err)
				btcd.ws.Close()
				close(responses)
				return
			}

			// Try notifications (requests with nil ids) first.
			n, err := unmarshalNotification(m)
			if err == nil {
				svrNtfns <- n
				continue
			}

			// Must be a response.
			r, err := unmarshalResponse(m)
			if err == nil {
				responses <- r
				continue
			}

			// Not sure what was received but it isn't correct.
			log.Warnf("Received invalid message from btcd")
		}
	}()
}

// unmarshalResponse attempts to unmarshal a marshaled JSON-RPC
// response.
func unmarshalResponse(s string) (*receivedResponse, error) {
	var r btcjson.Reply
	if err := json.Unmarshal([]byte(s), &r); err != nil {
		return nil, err
	}

	// Check for a valid ID.
	if r.Id == nil {
		return nil, errors.New("id is nil")
	}
	fid, ok := (*r.Id).(float64)
	if !ok {
		return nil, errors.New("id is not a number")
	}
	response := &receivedResponse{
		id:    uint64(fid),
		raw:   s,
		reply: &r,
	}
	return response, nil
}

// unmarshalNotification attempts to unmarshal a marshaled JSON-RPC
// notification (Request with a nil or no ID).
func unmarshalNotification(s string) (btcjson.Cmd, error) {
	req, err := btcjson.ParseMarshaledCmd([]byte(s))
	if err != nil {
		return nil, err
	}

	if req.Id() != nil {
		return nil, errors.New("id is non-nil")
	}

	return req, nil
}

// GetBestBlockResult holds the result of a getbestblock response.
//
// TODO(jrick): shove this in btcws.
type GetBestBlockResult struct {
	Hash   string `json:"hash"`
	Height int32  `json:"height"`
}

// GetBestBlock gets both the block height and hash of the best block
// in the main chain.
func GetBestBlock(rpc ServerConn) (*GetBestBlockResult, *btcjson.Error) {
	cmd := btcws.NewGetBestBlockCmd(<-NewJSONID)
	request := NewServerRequest(cmd, new(GetBestBlockResult))
	response := <-rpc.SendRequest(request)
	if response.Error() != nil {
		return nil, response.Error()
	}
	return response.Result().(*GetBestBlockResult), nil
}

// GetBlock requests details about a block with the given hash.
func GetBlock(rpc ServerConn, blockHash string) (*btcjson.BlockResult, *btcjson.Error) {
	// NewGetBlockCmd cannot fail with no optargs, so omit the check.
	cmd, _ := btcjson.NewGetBlockCmd(<-NewJSONID, blockHash)
	request := NewServerRequest(cmd, new(btcjson.BlockResult))
	response := <-rpc.SendRequest(request)
	if response.Error() != nil {
		return nil, response.Error()
	}
	return response.Result().(*btcjson.BlockResult), nil
}

// GetCurrentNet requests the network a bitcoin RPC server is running on.
func GetCurrentNet(rpc ServerConn) (btcwire.BitcoinNet, *btcjson.Error) {
	cmd := btcws.NewGetCurrentNetCmd(<-NewJSONID)
	request := NewServerRequest(cmd, nil)
	response := <-rpc.SendRequest(request)
	if response.Error() != nil {
		return 0, response.Error()
	}
	return btcwire.BitcoinNet(uint32(response.Result().(float64))), nil
}

// NotifyBlocks requests blockconnected and blockdisconnected notifications.
func NotifyBlocks(rpc ServerConn) *btcjson.Error {
	cmd := btcws.NewNotifyBlocksCmd(<-NewJSONID)
	request := NewServerRequest(cmd, nil)
	response := <-rpc.SendRequest(request)
	return response.Error()
}

// NotifyNewTXs requests notifications for new transactions that spend
// to any of the addresses in addrs.
func NotifyNewTXs(rpc ServerConn, addrs []string) *btcjson.Error {
	cmd := btcws.NewNotifyNewTXsCmd(<-NewJSONID, addrs)
	request := NewServerRequest(cmd, nil)
	response := <-rpc.SendRequest(request)
	return response.Error()
}

// NotifySpent requests notifications for when a transaction is processed which
// spends op.
func NotifySpent(rpc ServerConn, op *btcwire.OutPoint) *btcjson.Error {
	cmd := btcws.NewNotifySpentCmd(<-NewJSONID, op)
	request := NewServerRequest(cmd, nil)
	response := <-rpc.SendRequest(request)
	return response.Error()
}

// Rescan requests a blockchain rescan for transactions to any number of
// addresses and notifications to inform wallet about such transactions.
func Rescan(rpc ServerConn, beginBlock int32, addrs map[string]struct{}) *btcjson.Error {
	// NewRescanCmd cannot fail with no optargs, so omit the check.
	cmd, _ := btcws.NewRescanCmd(<-NewJSONID, beginBlock, addrs)
	request := NewServerRequest(cmd, nil)
	response := <-rpc.SendRequest(request)
	return response.Error()
}

// SendRawTransaction sends a hex-encoded transaction for relay.
func SendRawTransaction(rpc ServerConn, hextx string) (txid string, error *btcjson.Error) {
	// NewSendRawTransactionCmd cannot fail, so omit the check.
	cmd, _ := btcjson.NewSendRawTransactionCmd(<-NewJSONID, hextx)
	request := NewServerRequest(cmd, new(string))
	response := <-rpc.SendRequest(request)
	if response.Error() != nil {
		return "", response.Error()
	}
	return *response.Result().(*string), nil
}

// GetRawTransaction sends the non-verbose version of a getrawtransaction
// request to receive the serialized transaction referenced by txsha.  If
// successful, the transaction is decoded and returned as a btcutil.Tx.
func GetRawTransaction(rpc ServerConn, txsha *btcwire.ShaHash) (*btcutil.Tx, *btcjson.Error) {
	// NewGetRawTransactionCmd cannot fail with no optargs.
	cmd, _ := btcjson.NewGetRawTransactionCmd(<-NewJSONID, txsha.String())
	request := NewServerRequest(cmd, new(string))
	response := <-rpc.SendRequest(request)
	if response.Error() != nil {
		return nil, response.Error()
	}
	hextx := *response.Result().(*string)
	serializedTx, err := hex.DecodeString(hextx)
	if err != nil {
		return nil, &btcjson.ErrDecodeHexString
	}
	utx, err := btcutil.NewTxFromBytes(serializedTx)
	if err != nil {
		return nil, &btcjson.ErrDeserialization
	}
	return utx, nil
}

// VerboseGetRawTransaction sends the verbose version of a getrawtransaction
// request to receive details about a transaction.
func VerboseGetRawTransaction(rpc ServerConn, txsha *btcwire.ShaHash) (*btcjson.TxRawResult, *btcjson.Error) {
	// NewGetRawTransactionCmd cannot fail with a single optarg.
	cmd, _ := btcjson.NewGetRawTransactionCmd(<-NewJSONID, txsha.String(), 1)
	request := NewServerRequest(cmd, new(btcjson.TxRawResult))
	response := <-rpc.SendRequest(request)
	if response.Error() != nil {
		return nil, response.Error()
	}
	return response.Result().(*btcjson.TxRawResult), nil
}
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2013, 2014 Conformal Systems LLC <info@conformal.com>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

// This file implements the websocket client connection to a bitcoin RPC
// server.

package main

import (
	"code.google.com/p/go.net/websocket"
	"encoding/hex"
	"encoding/json"
	"errors"
	"github.com/conformal/btcjson"
	"github.com/conformal/btcutil"
	"github.com/conformal/btcwire"
	"github.com/conformal/btcws"
	"io"
)

// ServerConn is an interface representing a client connection to a bitcoin RPC
// server.
type ServerConn interface {
	// SendRequest sends a bitcoin RPC request, returning a channel to
	// read the reply.  A channel is used so both synchronous and
	// asynchronous RPC can be supported.
	SendRequest(request *ServerRequest) chan RawRPCResponse
}

// ErrBtcdDisconnected describes an error where an operation cannot
// successfully complete due to btcwallet not being connected to
// btcd.
var ErrBtcdDisconnected = btcjson.Error{
	Code:    -1,
	Message: "btcd disconnected",
}

// ErrBtcdDisconnectedRaw is the raw JSON encoding of ErrBtcdDisconnected.
var ErrBtcdDisconnectedRaw = json.RawMessage(`{"code":-1,"message":"btcd disconnected"}`)

// BtcdRPCConn is a type managing a client connection to a btcd RPC server
// over websockets.
type BtcdRPCConn struct {
	ws         *websocket.Conn
	addRequest chan *AddRPCRequest
	closed     chan struct{}
}

// Ensure that BtcdRPCConn can be used as an RPCConn.
var _ ServerConn = &BtcdRPCConn{}

// NewBtcdRPCConn creates a new RPC connection from a btcd websocket
// connection to btcd.
func NewBtcdRPCConn(ws *websocket.Conn) *BtcdRPCConn {
	conn := &BtcdRPCConn{
		ws:         ws,
		addRequest: make(chan *AddRPCRequest),
		closed:     make(chan struct{}),
	}
	return conn
}

// SendRequest sends an RPC request and returns a channel to read the response's
// result and error.  Part of the RPCConn interface.
func (btcd *BtcdRPCConn) SendRequest(request *ServerRequest) chan RawRPCResponse {
	select {
	case <-btcd.closed:
		// The connection has closed, so instead of adding and sending
		// a request, return a channel that just replies with the
		// error for a disconnected btcd.
		responseChan := make(chan RawRPCResponse, 1)
		responseChan <- RawRPCResponse{Error: &ErrBtcdDisconnectedRaw}
		return responseChan

	default:
		addRequest := &AddRPCRequest{
			Request:      request,
			ResponseChan: make(chan chan RawRPCResponse, 1),
		}
		btcd.addRequest <- addRequest
		return <-addRequest.ResponseChan
	}
}

// Connected returns whether the connection remains established to the RPC
// server.
//
// This function probably should be removed, as any checks for confirming
// the connection are no longer valid after the check and may result in
// races.
func (btcd *BtcdRPCConn) Connected() bool {
	select {
	case <-btcd.closed:
		return false

	default:
		return true
	}
}

// Close forces closing the current btcd connection.
func (btcd *BtcdRPCConn) Close() {
	select {
	case <-btcd.closed:
	default:
		close(btcd.closed)
	}
}

// AddRPCRequest is used to add an RPCRequest to the pool of requests
// being manaaged by a btcd RPC connection.
type AddRPCRequest struct {
	Request      *ServerRequest
	ResponseChan chan chan RawRPCResponse
}

// send performs the actual send of the marshaled request over the btcd
// websocket connection.
func (btcd *BtcdRPCConn) send(rpcrequest *ServerRequest) error {
	// btcjson.Cmds define their own MarshalJSON which returns an error
	// to satisify the json.Marshaler interface, but will never error.
	mrequest, _ := rpcrequest.request.MarshalJSON()
	return websocket.Message.Send(btcd.ws, mrequest)
}

// Start starts the goroutines required to send RPC requests and listen for
// replies.
func (btcd *BtcdRPCConn) Start() {
	done := btcd.closed
	responses := make(chan RawRPCResponse)

	// Maintain a map of JSON IDs to RPCRequests currently being waited on.
	go func() {
		m := make(map[uint64]*ServerRequest)
		for {
			select {
			case addrequest := <-btcd.addRequest:
				rpcrequest := addrequest.Request
				m[rpcrequest.request.Id().(uint64)] = rpcrequest

				if err := btcd.send(rpcrequest); err != nil {
					// Connection lost.
					log.Infof("Cannot complete btcd websocket send: %v",
						err)
					btcd.ws.Close()
					close(done)
				}

				addrequest.ResponseChan <- rpcrequest.response

			case rawResponse, ok := <-responses:
				if !ok {
					responses = nil
					close(done)
					break
				}
				rpcrequest, ok := m[*rawResponse.Id]
				if !ok {
					log.Warnf("Received unexpected btcd response")
					continue
				}
				delete(m, *rawResponse.Id)

				rpcrequest.response <- rawResponse

				/*
					//rpcrequest.result
					var jsonErr *btcjson.Error
					if rawResponse.result != nil {
					}
					err := json.Unmarshal([]byte(*rawResponse.result), &result)
					err := json.Unmarshal([]byte(*rawResponse.error), &error)

					rawResult := recvResponse
					rawError := recvResponse
					response := &ServerResponse{
						result: r.Result,
						err:    jsonErr
					}
					rpcrequest.response <- response
				*/

				/*
					// If no result var was set, create and send
					// send the response unmarshaled by the json
					// package.

					if rpcrequest.result == nil {
						response := &ServerResponse{
							result: recvResponse.reply.Result,
							err:    recvResponse.reply.Error,
						}
						rpcrequest.response <- response
						continue
					}

					// A return var was set, so unmarshal again
					// into the var before sending the response.
				*/

			case <-done:
				resp := RawRPCResponse{Error: &ErrBtcdDisconnectedRaw}
				for _, request := range m {
					request.response <- resp
				}
				return
			}
		}
	}()

	// Listen for replies/notifications from btcd, and decide how to handle them.
	go func() {
		// Idea: instead of reading btcd messages from just one websocket
		// connection, maybe use two so the same connection isn't used
		// for both notifications and responses?  Should make handling
		// must faster as unnecessary unmarshal attempts could be avoided.

		for {
			var m string
			if err := websocket.Message.Receive(btcd.ws, &m); err != nil {
				// Log warning if btcd did not disconnect.
				if err != io.EOF {
					log.Infof("Cannot receive btcd websocket message: %v",
						err)
				}
				btcd.ws.Close()
				close(responses)
				return
			}

			// Try notifications (requests with nil ids) first.
			n, err := unmarshalNotification(m)
			if err == nil {
				svrNtfns <- n
				continue
			}

			// Must be a response.
			r, err := unmarshalResponse(m)
			if err == nil {
				responses <- r
				continue
			}

			// Not sure what was received but it isn't correct.
			log.Warnf("Received invalid message from btcd")
		}
	}()
}

// unmarshalResponse attempts to unmarshal a marshaled JSON-RPC response.
func unmarshalResponse(s string) (RawRPCResponse, error) {
	var r RawRPCResponse
	if err := json.Unmarshal([]byte(s), &r); err != nil {
		return r, err
	}

	// Check for a valid ID.
	if r.Id == nil {
		return r, errors.New("id is null")
	}
	return r, nil
}

// unmarshalNotification attempts to unmarshal a marshaled JSON-RPC
// notification (Request with a nil or no ID).
func unmarshalNotification(s string) (btcjson.Cmd, error) {
	req, err := btcjson.ParseMarshaledCmd([]byte(s))
	if err != nil {
		return nil, err
	}

	if req.Id() != nil {
		return nil, errors.New("id is non-nil")
	}

	return req, nil
}

// GetBestBlock gets both the block height and hash of the best block
// in the main chain.
func GetBestBlock(rpc ServerConn) (*btcws.GetBestBlockResult, *btcjson.Error) {
	cmd := btcws.NewGetBestBlockCmd(<-NewJSONID)
	response := <-rpc.SendRequest(NewServerRequest(cmd))

	var resultData btcws.GetBestBlockResult
	_, jsonErr := response.FinishUnmarshal(&resultData)
	if jsonErr != nil {
		return nil, jsonErr
	}
	return &resultData, nil
}

// GetBlock requests details about a block with the given hash.
func GetBlock(rpc ServerConn, blockHash string) (*btcjson.BlockResult, *btcjson.Error) {
	// NewGetBlockCmd cannot fail with no optargs, so omit the check.
	cmd, _ := btcjson.NewGetBlockCmd(<-NewJSONID, blockHash)
	response := <-rpc.SendRequest(NewServerRequest(cmd))

	var resultData btcjson.BlockResult
	_, jsonErr := response.FinishUnmarshal(&resultData)
	if jsonErr != nil {
		return nil, jsonErr
	}
	return &resultData, nil
}

// GetCurrentNet requests the network a bitcoin RPC server is running on.
func GetCurrentNet(rpc ServerConn) (btcwire.BitcoinNet, *btcjson.Error) {
	cmd := btcws.NewGetCurrentNetCmd(<-NewJSONID)
	response := <-rpc.SendRequest(NewServerRequest(cmd))

	var resultData uint32
	_, jsonErr := response.FinishUnmarshal(&resultData)
	if jsonErr != nil {
		return 0, jsonErr
	}
	return btcwire.BitcoinNet(resultData), nil
}

// NotifyBlocks requests blockconnected and blockdisconnected notifications.
func NotifyBlocks(rpc ServerConn) *btcjson.Error {
	cmd := btcws.NewNotifyBlocksCmd(<-NewJSONID)
	response := <-rpc.SendRequest(NewServerRequest(cmd))
	_, jsonErr := response.FinishUnmarshal(nil)
	return jsonErr
}

// NotifyNewTXs requests notifications for new transactions that spend
// to any of the addresses in addrs.
func NotifyNewTXs(rpc ServerConn, addrs []string) *btcjson.Error {
	cmd := btcws.NewNotifyNewTXsCmd(<-NewJSONID, addrs)
	response := <-rpc.SendRequest(NewServerRequest(cmd))
	_, jsonErr := response.FinishUnmarshal(nil)
	return jsonErr
}

// NotifySpent requests notifications for when a transaction is processed which
// spends op.
func NotifySpent(rpc ServerConn, op *btcwire.OutPoint) *btcjson.Error {
	cmd := btcws.NewNotifySpentCmd(<-NewJSONID, op)
	response := <-rpc.SendRequest(NewServerRequest(cmd))
	_, jsonErr := response.FinishUnmarshal(nil)
	return jsonErr
}

// Rescan requests a blockchain rescan for transactions to any number of
// addresses and notifications to inform wallet about such transactions.
func Rescan(rpc ServerConn, beginBlock int32, addrs []string,
	outpoints []*btcwire.OutPoint) *btcjson.Error {

	// NewRescanCmd cannot fail with no optargs, so omit the check.
	cmd, _ := btcws.NewRescanCmd(<-NewJSONID, beginBlock, addrs, outpoints)
	response := <-rpc.SendRequest(NewServerRequest(cmd))
	_, jsonErr := response.FinishUnmarshal(nil)
	return jsonErr
}

// SendRawTransaction sends a hex-encoded transaction for relay.
func SendRawTransaction(rpc ServerConn, hextx string) (txid string, error *btcjson.Error) {
	// NewSendRawTransactionCmd cannot fail, so omit the check.
	cmd, _ := btcjson.NewSendRawTransactionCmd(<-NewJSONID, hextx)
	response := <-rpc.SendRequest(NewServerRequest(cmd))

	var resultData string
	_, jsonErr := response.FinishUnmarshal(&resultData)
	if jsonErr != nil {
		return "", jsonErr
	}
	return resultData, nil
}

// GetRawTransaction sends the non-verbose version of a getrawtransaction
// request to receive the serialized transaction referenced by txsha.  If
// successful, the transaction is decoded and returned as a btcutil.Tx.
func GetRawTransaction(rpc ServerConn, txsha *btcwire.ShaHash) (*btcutil.Tx, *btcjson.Error) {
	// NewGetRawTransactionCmd cannot fail with no optargs.
	cmd, _ := btcjson.NewGetRawTransactionCmd(<-NewJSONID, txsha.String())
	response := <-rpc.SendRequest(NewServerRequest(cmd))

	var resultData string
	_, jsonErr := response.FinishUnmarshal(&resultData)
	if jsonErr != nil {
		return nil, jsonErr
	}
	serializedTx, err := hex.DecodeString(resultData)
	if err != nil {
		return nil, &btcjson.ErrDecodeHexString
	}
	utx, err := btcutil.NewTxFromBytes(serializedTx)
	if err != nil {
		return nil, &btcjson.ErrDeserialization
	}
	return utx, nil
}

// VerboseGetRawTransaction sends the verbose version of a getrawtransaction
// request to receive details about a transaction.
func VerboseGetRawTransaction(rpc ServerConn, txsha *btcwire.ShaHash) (*btcjson.TxRawResult, *btcjson.Error) {
	// NewGetRawTransactionCmd cannot fail with a single optarg.
	cmd, _ := btcjson.NewGetRawTransactionCmd(<-NewJSONID, txsha.String(), 1)
	response := <-rpc.SendRequest(NewServerRequest(cmd))

	var resultData btcjson.TxRawResult
	_, jsonErr := response.FinishUnmarshal(&resultData)
	if jsonErr != nil {
		return nil, jsonErr
	}
	return &resultData, nil
}
<commit_msg>Fixes for btcws API changes.<commit_after>/*
 * Copyright (c) 2013, 2014 Conformal Systems LLC <info@conformal.com>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

// This file implements the websocket client connection to a bitcoin RPC
// server.

package main

import (
	"code.google.com/p/go.net/websocket"
	"encoding/hex"
	"encoding/json"
	"errors"
	"github.com/conformal/btcjson"
	"github.com/conformal/btcutil"
	"github.com/conformal/btcwire"
	"github.com/conformal/btcws"
	"io"
)

// ServerConn is an interface representing a client connection to a bitcoin RPC
// server.
type ServerConn interface {
	// SendRequest sends a bitcoin RPC request, returning a channel to
	// read the reply.  A channel is used so both synchronous and
	// asynchronous RPC can be supported.
	SendRequest(request *ServerRequest) chan RawRPCResponse
}

// ErrBtcdDisconnected describes an error where an operation cannot
// successfully complete due to btcwallet not being connected to
// btcd.
var ErrBtcdDisconnected = btcjson.Error{
	Code:    -1,
	Message: "btcd disconnected",
}

// ErrBtcdDisconnectedRaw is the raw JSON encoding of ErrBtcdDisconnected.
var ErrBtcdDisconnectedRaw = json.RawMessage(`{"code":-1,"message":"btcd disconnected"}`)

// BtcdRPCConn is a type managing a client connection to a btcd RPC server
// over websockets.
type BtcdRPCConn struct {
	ws         *websocket.Conn
	addRequest chan *AddRPCRequest
	closed     chan struct{}
}

// Ensure that BtcdRPCConn can be used as an RPCConn.
var _ ServerConn = &BtcdRPCConn{}

// NewBtcdRPCConn creates a new RPC connection from a btcd websocket
// connection to btcd.
func NewBtcdRPCConn(ws *websocket.Conn) *BtcdRPCConn {
	conn := &BtcdRPCConn{
		ws:         ws,
		addRequest: make(chan *AddRPCRequest),
		closed:     make(chan struct{}),
	}
	return conn
}

// SendRequest sends an RPC request and returns a channel to read the response's
// result and error.  Part of the RPCConn interface.
func (btcd *BtcdRPCConn) SendRequest(request *ServerRequest) chan RawRPCResponse {
	select {
	case <-btcd.closed:
		// The connection has closed, so instead of adding and sending
		// a request, return a channel that just replies with the
		// error for a disconnected btcd.
		responseChan := make(chan RawRPCResponse, 1)
		responseChan <- RawRPCResponse{Error: &ErrBtcdDisconnectedRaw}
		return responseChan

	default:
		addRequest := &AddRPCRequest{
			Request:      request,
			ResponseChan: make(chan chan RawRPCResponse, 1),
		}
		btcd.addRequest <- addRequest
		return <-addRequest.ResponseChan
	}
}

// Connected returns whether the connection remains established to the RPC
// server.
//
// This function probably should be removed, as any checks for confirming
// the connection are no longer valid after the check and may result in
// races.
func (btcd *BtcdRPCConn) Connected() bool {
	select {
	case <-btcd.closed:
		return false

	default:
		return true
	}
}

// Close forces closing the current btcd connection.
func (btcd *BtcdRPCConn) Close() {
	select {
	case <-btcd.closed:
	default:
		close(btcd.closed)
	}
}

// AddRPCRequest is used to add an RPCRequest to the pool of requests
// being manaaged by a btcd RPC connection.
type AddRPCRequest struct {
	Request      *ServerRequest
	ResponseChan chan chan RawRPCResponse
}

// send performs the actual send of the marshaled request over the btcd
// websocket connection.
func (btcd *BtcdRPCConn) send(rpcrequest *ServerRequest) error {
	// btcjson.Cmds define their own MarshalJSON which returns an error
	// to satisify the json.Marshaler interface, but will never error.
	mrequest, _ := rpcrequest.request.MarshalJSON()
	return websocket.Message.Send(btcd.ws, mrequest)
}

// Start starts the goroutines required to send RPC requests and listen for
// replies.
func (btcd *BtcdRPCConn) Start() {
	done := btcd.closed
	responses := make(chan RawRPCResponse)

	// Maintain a map of JSON IDs to RPCRequests currently being waited on.
	go func() {
		m := make(map[uint64]*ServerRequest)
		for {
			select {
			case addrequest := <-btcd.addRequest:
				rpcrequest := addrequest.Request
				m[rpcrequest.request.Id().(uint64)] = rpcrequest

				if err := btcd.send(rpcrequest); err != nil {
					// Connection lost.
					log.Infof("Cannot complete btcd websocket send: %v",
						err)
					btcd.ws.Close()
					close(done)
				}

				addrequest.ResponseChan <- rpcrequest.response

			case rawResponse, ok := <-responses:
				if !ok {
					responses = nil
					close(done)
					break
				}
				rpcrequest, ok := m[*rawResponse.Id]
				if !ok {
					log.Warnf("Received unexpected btcd response")
					continue
				}
				delete(m, *rawResponse.Id)

				rpcrequest.response <- rawResponse

				/*
					//rpcrequest.result
					var jsonErr *btcjson.Error
					if rawResponse.result != nil {
					}
					err := json.Unmarshal([]byte(*rawResponse.result), &result)
					err := json.Unmarshal([]byte(*rawResponse.error), &error)

					rawResult := recvResponse
					rawError := recvResponse
					response := &ServerResponse{
						result: r.Result,
						err:    jsonErr
					}
					rpcrequest.response <- response
				*/

				/*
					// If no result var was set, create and send
					// send the response unmarshaled by the json
					// package.

					if rpcrequest.result == nil {
						response := &ServerResponse{
							result: recvResponse.reply.Result,
							err:    recvResponse.reply.Error,
						}
						rpcrequest.response <- response
						continue
					}

					// A return var was set, so unmarshal again
					// into the var before sending the response.
				*/

			case <-done:
				resp := RawRPCResponse{Error: &ErrBtcdDisconnectedRaw}
				for _, request := range m {
					request.response <- resp
				}
				return
			}
		}
	}()

	// Listen for replies/notifications from btcd, and decide how to handle them.
	go func() {
		// Idea: instead of reading btcd messages from just one websocket
		// connection, maybe use two so the same connection isn't used
		// for both notifications and responses?  Should make handling
		// must faster as unnecessary unmarshal attempts could be avoided.

		for {
			var m string
			if err := websocket.Message.Receive(btcd.ws, &m); err != nil {
				// Log warning if btcd did not disconnect.
				if err != io.EOF {
					log.Infof("Cannot receive btcd websocket message: %v",
						err)
				}
				btcd.ws.Close()
				close(responses)
				return
			}

			// Try notifications (requests with nil ids) first.
			n, err := unmarshalNotification(m)
			if err == nil {
				svrNtfns <- n
				continue
			}

			// Must be a response.
			r, err := unmarshalResponse(m)
			if err == nil {
				responses <- r
				continue
			}

			// Not sure what was received but it isn't correct.
			log.Warnf("Received invalid message from btcd")
		}
	}()
}

// unmarshalResponse attempts to unmarshal a marshaled JSON-RPC response.
func unmarshalResponse(s string) (RawRPCResponse, error) {
	var r RawRPCResponse
	if err := json.Unmarshal([]byte(s), &r); err != nil {
		return r, err
	}

	// Check for a valid ID.
	if r.Id == nil {
		return r, errors.New("id is null")
	}
	return r, nil
}

// unmarshalNotification attempts to unmarshal a marshaled JSON-RPC
// notification (Request with a nil or no ID).
func unmarshalNotification(s string) (btcjson.Cmd, error) {
	req, err := btcjson.ParseMarshaledCmd([]byte(s))
	if err != nil {
		return nil, err
	}

	if req.Id() != nil {
		return nil, errors.New("id is non-nil")
	}

	return req, nil
}

// GetBestBlock gets both the block height and hash of the best block
// in the main chain.
func GetBestBlock(rpc ServerConn) (*btcws.GetBestBlockResult, *btcjson.Error) {
	cmd := btcws.NewGetBestBlockCmd(<-NewJSONID)
	response := <-rpc.SendRequest(NewServerRequest(cmd))

	var resultData btcws.GetBestBlockResult
	_, jsonErr := response.FinishUnmarshal(&resultData)
	if jsonErr != nil {
		return nil, jsonErr
	}
	return &resultData, nil
}

// GetBlock requests details about a block with the given hash.
func GetBlock(rpc ServerConn, blockHash string) (*btcjson.BlockResult, *btcjson.Error) {
	// NewGetBlockCmd cannot fail with no optargs, so omit the check.
	cmd, _ := btcjson.NewGetBlockCmd(<-NewJSONID, blockHash)
	response := <-rpc.SendRequest(NewServerRequest(cmd))

	var resultData btcjson.BlockResult
	_, jsonErr := response.FinishUnmarshal(&resultData)
	if jsonErr != nil {
		return nil, jsonErr
	}
	return &resultData, nil
}

// GetCurrentNet requests the network a bitcoin RPC server is running on.
func GetCurrentNet(rpc ServerConn) (btcwire.BitcoinNet, *btcjson.Error) {
	cmd := btcws.NewGetCurrentNetCmd(<-NewJSONID)
	response := <-rpc.SendRequest(NewServerRequest(cmd))

	var resultData uint32
	_, jsonErr := response.FinishUnmarshal(&resultData)
	if jsonErr != nil {
		return 0, jsonErr
	}
	return btcwire.BitcoinNet(resultData), nil
}

// NotifyBlocks requests blockconnected and blockdisconnected notifications.
func NotifyBlocks(rpc ServerConn) *btcjson.Error {
	cmd := btcws.NewNotifyBlocksCmd(<-NewJSONID)
	response := <-rpc.SendRequest(NewServerRequest(cmd))
	_, jsonErr := response.FinishUnmarshal(nil)
	return jsonErr
}

// NotifyNewTXs requests notifications for new transactions that spend
// to any of the addresses in addrs.
func NotifyNewTXs(rpc ServerConn, addrs []string) *btcjson.Error {
	cmd := btcws.NewNotifyNewTXsCmd(<-NewJSONID, addrs)
	response := <-rpc.SendRequest(NewServerRequest(cmd))
	_, jsonErr := response.FinishUnmarshal(nil)
	return jsonErr
}

// NotifySpent requests notifications for when a transaction is processed which
// spends op.
func NotifySpent(rpc ServerConn, outpoint *btcwire.OutPoint) *btcjson.Error {
	op := btcws.NewOutPointFromWire(outpoint)
	cmd := btcws.NewNotifySpentCmd(<-NewJSONID, op)
	response := <-rpc.SendRequest(NewServerRequest(cmd))
	_, jsonErr := response.FinishUnmarshal(nil)
	return jsonErr
}

// Rescan requests a blockchain rescan for transactions to any number of
// addresses and notifications to inform wallet about such transactions.
func Rescan(rpc ServerConn, beginBlock int32, addrs []string,
	outpoints []*btcwire.OutPoint) *btcjson.Error {

	// NewRescanCmd cannot fail with no optargs, so omit the check.
	ops := make([]btcws.OutPoint, len(outpoints))
	for i := range outpoints {
		ops[i] = *btcws.NewOutPointFromWire(outpoints[i])
	}
	cmd, _ := btcws.NewRescanCmd(<-NewJSONID, beginBlock, addrs, ops)
	response := <-rpc.SendRequest(NewServerRequest(cmd))
	_, jsonErr := response.FinishUnmarshal(nil)
	return jsonErr
}

// SendRawTransaction sends a hex-encoded transaction for relay.
func SendRawTransaction(rpc ServerConn, hextx string) (txid string, error *btcjson.Error) {
	// NewSendRawTransactionCmd cannot fail, so omit the check.
	cmd, _ := btcjson.NewSendRawTransactionCmd(<-NewJSONID, hextx)
	response := <-rpc.SendRequest(NewServerRequest(cmd))

	var resultData string
	_, jsonErr := response.FinishUnmarshal(&resultData)
	if jsonErr != nil {
		return "", jsonErr
	}
	return resultData, nil
}

// GetRawTransaction sends the non-verbose version of a getrawtransaction
// request to receive the serialized transaction referenced by txsha.  If
// successful, the transaction is decoded and returned as a btcutil.Tx.
func GetRawTransaction(rpc ServerConn, txsha *btcwire.ShaHash) (*btcutil.Tx, *btcjson.Error) {
	// NewGetRawTransactionCmd cannot fail with no optargs.
	cmd, _ := btcjson.NewGetRawTransactionCmd(<-NewJSONID, txsha.String())
	response := <-rpc.SendRequest(NewServerRequest(cmd))

	var resultData string
	_, jsonErr := response.FinishUnmarshal(&resultData)
	if jsonErr != nil {
		return nil, jsonErr
	}
	serializedTx, err := hex.DecodeString(resultData)
	if err != nil {
		return nil, &btcjson.ErrDecodeHexString
	}
	utx, err := btcutil.NewTxFromBytes(serializedTx)
	if err != nil {
		return nil, &btcjson.ErrDeserialization
	}
	return utx, nil
}

// VerboseGetRawTransaction sends the verbose version of a getrawtransaction
// request to receive details about a transaction.
func VerboseGetRawTransaction(rpc ServerConn, txsha *btcwire.ShaHash) (*btcjson.TxRawResult, *btcjson.Error) {
	// NewGetRawTransactionCmd cannot fail with a single optarg.
	cmd, _ := btcjson.NewGetRawTransactionCmd(<-NewJSONID, txsha.String(), 1)
	response := <-rpc.SendRequest(NewServerRequest(cmd))

	var resultData btcjson.TxRawResult
	_, jsonErr := response.FinishUnmarshal(&resultData)
	if jsonErr != nil {
		return nil, jsonErr
	}
	return &resultData, nil
}
<|endoftext|>


<commit_before>package bot

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"strconv"
	"strings"
	"time"

	"github.com/nlopes/slack"

	"github.com/keel-hq/keel/approvals"
	"github.com/keel-hq/keel/provider/kubernetes"
	"github.com/keel-hq/keel/types"

	log "github.com/Sirupsen/logrus"
)

const (
	removeApprovalPrefix = "rm approval"
)

var (
	botEventTextToResponse = map[string][]string{
		"help": {
			`Here's a list of supported commands`,
			`- "get deployments" -> get a list of all deployments`,
			`- "get approvals" -> get a list of approvals`,
			`- "rm approval <approval identifier>" -> remove approval`,
			`- "approve <approval identifier>" -> approve update request`,
			`- "reject <approval identifier>" -> reject update request`,
			// `- "get deployments all" -> get a list of all deployments`,
			// `- "describe deployment <deployment>" -> get details for specified deployment`,
		},
	}

	// static bot commands can be used straight away
	staticBotCommands = map[string]bool{
		"get deployments": true,
		"get approvals":   true,
	}

	// dynamic bot command prefixes have to be matched
	dynamicBotCommandPrefixes = []string{removeApprovalPrefix}

	approvalResponseKeyword = "approve"
	rejectResponseKeyword   = "reject"
)

// SlackImplementer - implementes slack HTTP functionality, used to
// send messages with attachments
type SlackImplementer interface {
	PostMessage(channel, text string, params slack.PostMessageParameters) (string, string, error)
}

// approvalResponse - used to track approvals once vote begins
type approvalResponse struct {
	User   string
	Status types.ApprovalStatus
	Text   string
}

// Bot - main slack bot container
type Bot struct {
	id   string // bot id
	name string // bot name

	users map[string]string

	msgPrefix string

	slackClient *slack.Client
	slackRTM    *slack.RTM

	slackHTTPClient SlackImplementer

	approvalsRespCh chan *approvalResponse

	approvalsManager approvals.Manager
	approvalsChannel string // slack approvals channel name

	k8sImplementer kubernetes.Implementer

	ctx context.Context
}

// New - create new bot instance
func New(name, token, approvalsChannel string, k8sImplementer kubernetes.Implementer, approvalsManager approvals.Manager) *Bot {
	client := slack.New(token)

	bot := &Bot{
		slackClient:      client,
		slackHTTPClient:  client,
		k8sImplementer:   k8sImplementer,
		name:             name,
		approvalsManager: approvalsManager,
		approvalsChannel: approvalsChannel,
		approvalsRespCh:  make(chan *approvalResponse), // don't add buffer to make it blocking
	}

	return bot
}

// Start - start bot
func (b *Bot) Start(ctx context.Context) error {

	// setting root context
	b.ctx = ctx

	users, err := b.slackClient.GetUsers()
	if err != nil {
		return err
	}

	b.users = map[string]string{}

	for _, user := range users {
		switch user.Name {
		case b.name:
			if user.IsBot {
				b.id = user.ID
			}
		default:
			continue
		}
	}
	if b.id == "" {
		return errors.New("could not find bot in the list of names, check if the bot is called \"" + b.name + "\" ")
	}

	b.msgPrefix = strings.ToLower("<@" + b.id + ">")

	// processing messages coming from slack RTM client
	go b.startInternal()

	// processing slack approval responses
	go b.processApprovalResponses()

	// subscribing for approval requests
	go b.subscribeForApprovals()

	return nil
}

func (b *Bot) startInternal() error {
	b.slackRTM = b.slackClient.NewRTM()

	go b.slackRTM.ManageConnection()

	for {
		select {
		case <-b.ctx.Done():
			return nil

		case msg := <-b.slackRTM.IncomingEvents:
			switch ev := msg.Data.(type) {
			case *slack.HelloEvent:
				// Ignore hello

			case *slack.ConnectedEvent:
				// fmt.Println("Infos:", ev.Info)
				// fmt.Println("Connection counter:", ev.ConnectionCount)
				// Replace #general with your Channel ID
				// b.slackRTM.SendMessage(b.slackRTM.NewOutgoingMessage("Hello world", "#general"))

			case *slack.MessageEvent:
				b.handleMessage(ev)
			case *slack.PresenceChangeEvent:
				// fmt.Printf("Presence Change: %v\n", ev)

			// case *slack.LatencyReport:
			// 	fmt.Printf("Current latency: %v\n", ev.Value)

			case *slack.RTMError:
				fmt.Printf("Error: %s\n", ev.Error())

			case *slack.InvalidAuthEvent:
				fmt.Printf("Invalid credentials")
				return fmt.Errorf("invalid credentials")

			default:

				// Ignore other events..
				// fmt.Printf("Unexpected: %v\n", msg.Data)
			}
		}
	}
}

func (b *Bot) postMessage(title, message, color string, fields []slack.AttachmentField) error {
	params := slack.NewPostMessageParameters()
	params.Username = b.name

	params.Attachments = []slack.Attachment{
		slack.Attachment{
			Fallback: message,
			Color:    color,
			Fields:   fields,
			Footer:   "https://keel.sh",
			Ts:       json.Number(strconv.Itoa(int(time.Now().Unix()))),
		},
	}

	_, _, err := b.slackHTTPClient.PostMessage(b.approvalsChannel, "", params)
	if err != nil {
		log.WithFields(log.Fields{
			"error": err,
		}).Error("bot.postMessage: failed to send message")
	}
	return err
}

func (b *Bot) isApproval(event *slack.MessageEvent, eventText string) (resp *approvalResponse, ok bool) {
	// only accepting approvals from approvals channel
	if event.Channel != b.approvalsChannel {
		return nil, false
	}
	if strings.HasPrefix(strings.ToLower(eventText), approvalResponseKeyword) {
		return &approvalResponse{
			User:   event.User,
			Status: types.ApprovalStatusApproved,
			Text:   eventText,
		}, true
	}

	if strings.HasPrefix(strings.ToLower(eventText), rejectResponseKeyword) {
		return &approvalResponse{
			User:   event.User,
			Status: types.ApprovalStatusRejected,
			Text:   eventText,
		}, true
	}

	return nil, false
}

func (b *Bot) handleMessage(event *slack.MessageEvent) {
	if event.BotID != "" || event.User == "" || event.SubType == "bot_message" {
		log.WithFields(log.Fields{
			"event_bot_ID":  event.BotID,
			"event_user":    event.User,
			"event_subtype": event.SubType,
		}).Info("handleMessage: ignoring message")
		return
	}

	eventText := strings.Trim(strings.ToLower(event.Text), " \n\r")

	if !b.isBotMessage(event, eventText) {
		return
	}

	eventText = b.trimBot(eventText)
	approval, ok := b.isApproval(event, eventText)
	if ok {
		b.approvalsRespCh <- approval
		return
	}

	// Responses that are just a canned string response
	if responseLines, ok := botEventTextToResponse[eventText]; ok {
		response := strings.Join(responseLines, "\n")
		b.respond(event, formatAsSnippet(response))
		return
	}

	if b.isCommand(event, eventText) {
		b.handleCommand(event, eventText)
		return
	}

	log.WithFields(log.Fields{
		"name":      b.name,
		"bot_id":    b.id,
		"command":   eventText,
		"untrimmed": strings.Trim(strings.ToLower(event.Text), " \n\r"),
	}).Debug("handleMessage: bot couldn't recognise command")
}

func (b *Bot) isCommand(event *slack.MessageEvent, eventText string) bool {
	if staticBotCommands[eventText] {
		return true
	}

	for _, prefix := range dynamicBotCommandPrefixes {
		if strings.HasPrefix(eventText, prefix) {
			return true
		}
	}

	return false
}

func (b *Bot) handleCommand(event *slack.MessageEvent, eventText string) {
	switch eventText {
	case "get deployments":
		log.Info("getting deployments")
		response := b.deploymentsResponse(Filter{})
		b.respond(event, formatAsSnippet(response))
		return
	case "get approvals":
		response := b.approvalsResponse()
		b.respond(event, formatAsSnippet(response))
		return
	}

	// handle dynamic commands
	if strings.HasPrefix(eventText, removeApprovalPrefix) {
		b.respond(event, formatAsSnippet(b.removeApprovalHandler(strings.TrimSpace(strings.TrimPrefix(eventText, removeApprovalPrefix)))))
		return
	}

	log.Info("command not found")
}

func (b *Bot) respond(event *slack.MessageEvent, response string) {
	b.slackRTM.SendMessage(b.slackRTM.NewOutgoingMessage(response, event.Channel))
}

func (b *Bot) isBotMessage(event *slack.MessageEvent, eventText string) bool {
	prefixes := []string{
		b.msgPrefix,
		"keel",
	}

	for _, p := range prefixes {
		if strings.HasPrefix(eventText, p) {
			return true
		}
	}

	// Direct message channels always starts with 'D'
	return strings.HasPrefix(event.Channel, "D")
}

func (b *Bot) trimBot(msg string) string {
	msg = strings.Replace(msg, strings.ToLower(b.msgPrefix), "", 1)
	msg = strings.TrimPrefix(msg, b.name)
	msg = strings.Trim(msg, " :\n")

	return msg
}

func formatAsSnippet(response string) string {
	return "```" + response + "```"
}
<commit_msg>checking channel by ID<commit_after>package bot

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"strconv"
	"strings"
	"time"

	"github.com/nlopes/slack"

	"github.com/keel-hq/keel/approvals"
	"github.com/keel-hq/keel/provider/kubernetes"
	"github.com/keel-hq/keel/types"

	log "github.com/Sirupsen/logrus"
)

const (
	removeApprovalPrefix = "rm approval"
)

var (
	botEventTextToResponse = map[string][]string{
		"help": {
			`Here's a list of supported commands`,
			`- "get deployments" -> get a list of all deployments`,
			`- "get approvals" -> get a list of approvals`,
			`- "rm approval <approval identifier>" -> remove approval`,
			`- "approve <approval identifier>" -> approve update request`,
			`- "reject <approval identifier>" -> reject update request`,
			// `- "get deployments all" -> get a list of all deployments`,
			// `- "describe deployment <deployment>" -> get details for specified deployment`,
		},
	}

	// static bot commands can be used straight away
	staticBotCommands = map[string]bool{
		"get deployments": true,
		"get approvals":   true,
	}

	// dynamic bot command prefixes have to be matched
	dynamicBotCommandPrefixes = []string{removeApprovalPrefix}

	approvalResponseKeyword = "approve"
	rejectResponseKeyword   = "reject"
)

// SlackImplementer - implementes slack HTTP functionality, used to
// send messages with attachments
type SlackImplementer interface {
	PostMessage(channel, text string, params slack.PostMessageParameters) (string, string, error)
}

// approvalResponse - used to track approvals once vote begins
type approvalResponse struct {
	User   string
	Status types.ApprovalStatus
	Text   string
}

// Bot - main slack bot container
type Bot struct {
	id   string // bot id
	name string // bot name

	users map[string]string

	msgPrefix string

	slackClient *slack.Client
	slackRTM    *slack.RTM

	slackHTTPClient SlackImplementer

	approvalsRespCh chan *approvalResponse

	approvalsManager approvals.Manager
	approvalsChannel string // slack approvals channel name

	k8sImplementer kubernetes.Implementer

	ctx context.Context
}

// New - create new bot instance
func New(name, token, approvalsChannel string, k8sImplementer kubernetes.Implementer, approvalsManager approvals.Manager) *Bot {
	client := slack.New(token)

	bot := &Bot{
		slackClient:      client,
		slackHTTPClient:  client,
		k8sImplementer:   k8sImplementer,
		name:             name,
		approvalsManager: approvalsManager,
		approvalsChannel: approvalsChannel,
		approvalsRespCh:  make(chan *approvalResponse), // don't add buffer to make it blocking
	}

	return bot
}

// Start - start bot
func (b *Bot) Start(ctx context.Context) error {

	// setting root context
	b.ctx = ctx

	users, err := b.slackClient.GetUsers()
	if err != nil {
		return err
	}

	b.users = map[string]string{}

	for _, user := range users {
		switch user.Name {
		case b.name:
			if user.IsBot {
				b.id = user.ID
			}
		default:
			continue
		}
	}
	if b.id == "" {
		return errors.New("could not find bot in the list of names, check if the bot is called \"" + b.name + "\" ")
	}

	b.msgPrefix = strings.ToLower("<@" + b.id + ">")

	// processing messages coming from slack RTM client
	go b.startInternal()

	// processing slack approval responses
	go b.processApprovalResponses()

	// subscribing for approval requests
	go b.subscribeForApprovals()

	return nil
}

func (b *Bot) startInternal() error {
	b.slackRTM = b.slackClient.NewRTM()

	go b.slackRTM.ManageConnection()

	for {
		select {
		case <-b.ctx.Done():
			return nil

		case msg := <-b.slackRTM.IncomingEvents:
			switch ev := msg.Data.(type) {
			case *slack.HelloEvent:
				// Ignore hello

			case *slack.ConnectedEvent:
				// fmt.Println("Infos:", ev.Info)
				// fmt.Println("Connection counter:", ev.ConnectionCount)
				// Replace #general with your Channel ID
				// b.slackRTM.SendMessage(b.slackRTM.NewOutgoingMessage("Hello world", "#general"))

			case *slack.MessageEvent:
				b.handleMessage(ev)
			case *slack.PresenceChangeEvent:
				// fmt.Printf("Presence Change: %v\n", ev)

			// case *slack.LatencyReport:
			// 	fmt.Printf("Current latency: %v\n", ev.Value)

			case *slack.RTMError:
				fmt.Printf("Error: %s\n", ev.Error())

			case *slack.InvalidAuthEvent:
				fmt.Printf("Invalid credentials")
				return fmt.Errorf("invalid credentials")

			default:

				// Ignore other events..
				// fmt.Printf("Unexpected: %v\n", msg.Data)
			}
		}
	}
}

func (b *Bot) postMessage(title, message, color string, fields []slack.AttachmentField) error {
	params := slack.NewPostMessageParameters()
	params.Username = b.name

	params.Attachments = []slack.Attachment{
		slack.Attachment{
			Fallback: message,
			Color:    color,
			Fields:   fields,
			Footer:   "https://keel.sh",
			Ts:       json.Number(strconv.Itoa(int(time.Now().Unix()))),
		},
	}

	_, _, err := b.slackHTTPClient.PostMessage(b.approvalsChannel, "", params)
	if err != nil {
		log.WithFields(log.Fields{
			"error": err,
		}).Error("bot.postMessage: failed to send message")
	}
	return err
}

func (b *Bot) isApproval(event *slack.MessageEvent, eventText string) (resp *approvalResponse, ok bool) {

	// only accepting approvals from approvals channel
	if !b.isApprovalsChannel(event) {
		return nil, false
	}
	if strings.HasPrefix(strings.ToLower(eventText), approvalResponseKeyword) {
		return &approvalResponse{
			User:   event.User,
			Status: types.ApprovalStatusApproved,
			Text:   eventText,
		}, true
	}

	if strings.HasPrefix(strings.ToLower(eventText), rejectResponseKeyword) {
		return &approvalResponse{
			User:   event.User,
			Status: types.ApprovalStatusRejected,
			Text:   eventText,
		}, true
	}

	return nil, false
}

// TODO(k): cache results in a map or get this info on startup. Although
// if channel was then recreated (unlikely), we would miss results
func (b *Bot) isApprovalsChannel(event *slack.MessageEvent) bool {
	for _, ch := range b.slackRTM.GetInfo().Channels {
		if ch.ID == event.Channel && ch.Name == b.approvalsChannel {
			return true
		}
	}
	return false
}

func (b *Bot) handleMessage(event *slack.MessageEvent) {
	if event.BotID != "" || event.User == "" || event.SubType == "bot_message" {
		log.WithFields(log.Fields{
			"event_bot_ID":  event.BotID,
			"event_user":    event.User,
			"event_subtype": event.SubType,
		}).Info("handleMessage: ignoring message")
		return
	}

	eventText := strings.Trim(strings.ToLower(event.Text), " \n\r")

	if !b.isBotMessage(event, eventText) {
		return
	}

	eventText = b.trimBot(eventText)
	approval, ok := b.isApproval(event, eventText)
	if ok {
		b.approvalsRespCh <- approval
		return
	}

	// Responses that are just a canned string response
	if responseLines, ok := botEventTextToResponse[eventText]; ok {
		response := strings.Join(responseLines, "\n")
		b.respond(event, formatAsSnippet(response))
		return
	}

	if b.isCommand(event, eventText) {
		b.handleCommand(event, eventText)
		return
	}

	log.WithFields(log.Fields{
		"name":      b.name,
		"bot_id":    b.id,
		"command":   eventText,
		"untrimmed": strings.Trim(strings.ToLower(event.Text), " \n\r"),
	}).Debug("handleMessage: bot couldn't recognise command")
}

func (b *Bot) isCommand(event *slack.MessageEvent, eventText string) bool {
	if staticBotCommands[eventText] {
		return true
	}

	for _, prefix := range dynamicBotCommandPrefixes {
		if strings.HasPrefix(eventText, prefix) {
			return true
		}
	}

	return false
}

func (b *Bot) handleCommand(event *slack.MessageEvent, eventText string) {
	switch eventText {
	case "get deployments":
		log.Info("getting deployments")
		response := b.deploymentsResponse(Filter{})
		b.respond(event, formatAsSnippet(response))
		return
	case "get approvals":
		response := b.approvalsResponse()
		b.respond(event, formatAsSnippet(response))
		return
	}

	// handle dynamic commands
	if strings.HasPrefix(eventText, removeApprovalPrefix) {
		b.respond(event, formatAsSnippet(b.removeApprovalHandler(strings.TrimSpace(strings.TrimPrefix(eventText, removeApprovalPrefix)))))
		return
	}

	log.Info("command not found")
}

func (b *Bot) respond(event *slack.MessageEvent, response string) {
	b.slackRTM.SendMessage(b.slackRTM.NewOutgoingMessage(response, event.Channel))
}

func (b *Bot) isBotMessage(event *slack.MessageEvent, eventText string) bool {
	prefixes := []string{
		b.msgPrefix,
		"keel",
	}

	for _, p := range prefixes {
		if strings.HasPrefix(eventText, p) {
			return true
		}
	}

	// Direct message channels always starts with 'D'
	return strings.HasPrefix(event.Channel, "D")
}

func (b *Bot) trimBot(msg string) string {
	msg = strings.Replace(msg, strings.ToLower(b.msgPrefix), "", 1)
	msg = strings.TrimPrefix(msg, b.name)
	msg = strings.Trim(msg, " :\n")

	return msg
}

func formatAsSnippet(response string) string {
	return "```" + response + "```"
}
<|endoftext|>


<commit_before>package server

import (
	"bytes"
	"context"
	"fmt"
	"reflect"
	"strings"

	"github.com/micro/go-micro/broker"
	"github.com/micro/go-micro/codec"
	"github.com/micro/go-micro/metadata"
	"github.com/micro/go-micro/registry"
)

const (
	subSig = "func(context.Context, interface{}) error"
)

type handler struct {
	method  reflect.Value
	reqType reflect.Type
	ctxType reflect.Type
}

type subscriber struct {
	topic      string
	rcvr       reflect.Value
	typ        reflect.Type
	subscriber interface{}
	handlers   []*handler
	endpoints  []*registry.Endpoint
	opts       SubscriberOptions
}

func newSubscriber(topic string, sub interface{}, opts ...SubscriberOption) Subscriber {
	options := SubscriberOptions{
		AutoAck: true,
	}

	for _, o := range opts {
		o(&options)
	}

	var endpoints []*registry.Endpoint
	var handlers []*handler

	if typ := reflect.TypeOf(sub); typ.Kind() == reflect.Func {
		h := &handler{
			method: reflect.ValueOf(sub),
		}

		switch typ.NumIn() {
		case 1:
			h.reqType = typ.In(0)
		case 2:
			h.ctxType = typ.In(0)
			h.reqType = typ.In(1)
		}

		handlers = append(handlers, h)

		endpoints = append(endpoints, &registry.Endpoint{
			Name:    "Func",
			Request: extractSubValue(typ),
			Metadata: map[string]string{
				"topic":      topic,
				"subscriber": "true",
			},
		})
	} else {
		hdlr := reflect.ValueOf(sub)
		name := reflect.Indirect(hdlr).Type().Name()

		for m := 0; m < typ.NumMethod(); m++ {
			method := typ.Method(m)
			h := &handler{
				method: method.Func,
			}

			switch method.Type.NumIn() {
			case 2:
				h.reqType = method.Type.In(1)
			case 3:
				h.ctxType = method.Type.In(1)
				h.reqType = method.Type.In(2)
			}

			handlers = append(handlers, h)

			endpoints = append(endpoints, &registry.Endpoint{
				Name:    name + "." + method.Name,
				Request: extractSubValue(method.Type),
				Metadata: map[string]string{
					"topic":      topic,
					"subscriber": "true",
				},
			})
		}
	}

	return &subscriber{
		rcvr:       reflect.ValueOf(sub),
		typ:        reflect.TypeOf(sub),
		topic:      topic,
		subscriber: sub,
		handlers:   handlers,
		endpoints:  endpoints,
		opts:       options,
	}
}

func validateSubscriber(sub Subscriber) error {
	typ := reflect.TypeOf(sub.Subscriber())
	var argType reflect.Type

	if typ.Kind() == reflect.Func {
		name := "Func"
		switch typ.NumIn() {
		case 2:
			argType = typ.In(1)
		default:
			return fmt.Errorf("subscriber %v takes wrong number of args: %v required signature %s", name, typ.NumIn(), subSig)
		}
		if !isExportedOrBuiltinType(argType) {
			return fmt.Errorf("subscriber %v argument type not exported: %v", name, argType)
		}
		if typ.NumOut() != 1 {
			return fmt.Errorf("subscriber %v has wrong number of outs: %v require signature %s",
				name, typ.NumOut(), subSig)
		}
		if returnType := typ.Out(0); returnType != typeOfError {
			return fmt.Errorf("subscriber %v returns %v not error", name, returnType.String())
		}
	} else {
		hdlr := reflect.ValueOf(sub.Subscriber())
		name := reflect.Indirect(hdlr).Type().Name()

		for m := 0; m < typ.NumMethod(); m++ {
			method := typ.Method(m)

			switch method.Type.NumIn() {
			case 3:
				argType = method.Type.In(2)
			default:
				return fmt.Errorf("subscriber %v.%v takes wrong number of args: %v required signature %s",
					name, method.Name, method.Type.NumIn(), subSig)
			}

			if !isExportedOrBuiltinType(argType) {
				return fmt.Errorf("%v argument type not exported: %v", name, argType)
			}
			if method.Type.NumOut() != 1 {
				return fmt.Errorf(
					"subscriber %v.%v has wrong number of outs: %v require signature %s",
					name, method.Name, method.Type.NumOut(), subSig)
			}
			if returnType := method.Type.Out(0); returnType != typeOfError {
				return fmt.Errorf("subscriber %v.%v returns %v not error", name, method.Name, returnType.String())
			}
		}
	}

	return nil
}

func (s *rpcServer) createSubHandler(sb *subscriber, opts Options) broker.Handler {
	return func(p broker.Publication) error {
		msg := p.Message()

		// get codec
		ct := msg.Header["Content-Type"]

		// default content type
		if len(ct) == 0 {
			msg.Header["Content-Type"] = DefaultContentType
			ct = DefaultContentType
		}

		// get codec
		cf, err := s.newCodec(ct)
		if err != nil {
			return err
		}

		// copy headers
		hdr := make(map[string]string)
		for k, v := range msg.Header {
			hdr[k] = v
		}

		// create context
		ctx := metadata.NewContext(context.Background(), hdr)

		results := make(chan error, len(sb.handlers))

		for i := 0; i < len(sb.handlers); i++ {
			handler := sb.handlers[i]

			var isVal bool
			var req reflect.Value

			if handler.reqType.Kind() == reflect.Ptr {
				req = reflect.New(handler.reqType.Elem())
			} else {
				req = reflect.New(handler.reqType)
				isVal = true
			}
			if isVal {
				req = req.Elem()
			}

			b := &buffer{bytes.NewBuffer(msg.Body)}
			co := cf(b)
			defer co.Close()

			if err := co.ReadHeader(&codec.Message{}, codec.Publication); err != nil {
				return err
			}

			if err := co.ReadBody(req.Interface()); err != nil {
				return err
			}

			fn := func(ctx context.Context, msg Message) error {
				var vals []reflect.Value
				if sb.typ.Kind() != reflect.Func {
					vals = append(vals, sb.rcvr)
				}
				if handler.ctxType != nil {
					vals = append(vals, reflect.ValueOf(ctx))
				}

				vals = append(vals, reflect.ValueOf(msg.Payload()))

				returnValues := handler.method.Call(vals)
				if err := returnValues[0].Interface(); err != nil {
					return err.(error)
				}
				return nil
			}

			for i := len(opts.SubWrappers); i > 0; i-- {
				fn = opts.SubWrappers[i-1](fn)
			}

			s.wg.Add(1)
			go func() {
				defer s.wg.Done()
				results <- fn(ctx, &rpcMessage{
					topic:       sb.topic,
					contentType: ct,
					payload:     req.Interface(),
				})
			}()
		}

		var errors []string

		for i := 0; i < len(sb.handlers); i++ {
			if err := <-results; err != nil {
				errors = append(errors, err.Error())
			}
		}

		if len(errors) > 0 {
			return fmt.Errorf("subscriber error: %s", strings.Join(errors, "\n"))
		}

		return nil
	}
}

func (s *subscriber) Topic() string {
	return s.topic
}

func (s *subscriber) Subscriber() interface{} {
	return s.subscriber
}

func (s *subscriber) Endpoints() []*registry.Endpoint {
	return s.endpoints
}

func (s *subscriber) Options() SubscriberOptions {
	return s.opts
}
<commit_msg>fix waitgroup bug which crashes subscriber<commit_after>package server

import (
	"bytes"
	"context"
	"fmt"
	"reflect"
	"strings"

	"github.com/micro/go-micro/broker"
	"github.com/micro/go-micro/codec"
	"github.com/micro/go-micro/metadata"
	"github.com/micro/go-micro/registry"
)

const (
	subSig = "func(context.Context, interface{}) error"
)

type handler struct {
	method  reflect.Value
	reqType reflect.Type
	ctxType reflect.Type
}

type subscriber struct {
	topic      string
	rcvr       reflect.Value
	typ        reflect.Type
	subscriber interface{}
	handlers   []*handler
	endpoints  []*registry.Endpoint
	opts       SubscriberOptions
}

func newSubscriber(topic string, sub interface{}, opts ...SubscriberOption) Subscriber {
	options := SubscriberOptions{
		AutoAck: true,
	}

	for _, o := range opts {
		o(&options)
	}

	var endpoints []*registry.Endpoint
	var handlers []*handler

	if typ := reflect.TypeOf(sub); typ.Kind() == reflect.Func {
		h := &handler{
			method: reflect.ValueOf(sub),
		}

		switch typ.NumIn() {
		case 1:
			h.reqType = typ.In(0)
		case 2:
			h.ctxType = typ.In(0)
			h.reqType = typ.In(1)
		}

		handlers = append(handlers, h)

		endpoints = append(endpoints, &registry.Endpoint{
			Name:    "Func",
			Request: extractSubValue(typ),
			Metadata: map[string]string{
				"topic":      topic,
				"subscriber": "true",
			},
		})
	} else {
		hdlr := reflect.ValueOf(sub)
		name := reflect.Indirect(hdlr).Type().Name()

		for m := 0; m < typ.NumMethod(); m++ {
			method := typ.Method(m)
			h := &handler{
				method: method.Func,
			}

			switch method.Type.NumIn() {
			case 2:
				h.reqType = method.Type.In(1)
			case 3:
				h.ctxType = method.Type.In(1)
				h.reqType = method.Type.In(2)
			}

			handlers = append(handlers, h)

			endpoints = append(endpoints, &registry.Endpoint{
				Name:    name + "." + method.Name,
				Request: extractSubValue(method.Type),
				Metadata: map[string]string{
					"topic":      topic,
					"subscriber": "true",
				},
			})
		}
	}

	return &subscriber{
		rcvr:       reflect.ValueOf(sub),
		typ:        reflect.TypeOf(sub),
		topic:      topic,
		subscriber: sub,
		handlers:   handlers,
		endpoints:  endpoints,
		opts:       options,
	}
}

func validateSubscriber(sub Subscriber) error {
	typ := reflect.TypeOf(sub.Subscriber())
	var argType reflect.Type

	if typ.Kind() == reflect.Func {
		name := "Func"
		switch typ.NumIn() {
		case 2:
			argType = typ.In(1)
		default:
			return fmt.Errorf("subscriber %v takes wrong number of args: %v required signature %s", name, typ.NumIn(), subSig)
		}
		if !isExportedOrBuiltinType(argType) {
			return fmt.Errorf("subscriber %v argument type not exported: %v", name, argType)
		}
		if typ.NumOut() != 1 {
			return fmt.Errorf("subscriber %v has wrong number of outs: %v require signature %s",
				name, typ.NumOut(), subSig)
		}
		if returnType := typ.Out(0); returnType != typeOfError {
			return fmt.Errorf("subscriber %v returns %v not error", name, returnType.String())
		}
	} else {
		hdlr := reflect.ValueOf(sub.Subscriber())
		name := reflect.Indirect(hdlr).Type().Name()

		for m := 0; m < typ.NumMethod(); m++ {
			method := typ.Method(m)

			switch method.Type.NumIn() {
			case 3:
				argType = method.Type.In(2)
			default:
				return fmt.Errorf("subscriber %v.%v takes wrong number of args: %v required signature %s",
					name, method.Name, method.Type.NumIn(), subSig)
			}

			if !isExportedOrBuiltinType(argType) {
				return fmt.Errorf("%v argument type not exported: %v", name, argType)
			}
			if method.Type.NumOut() != 1 {
				return fmt.Errorf(
					"subscriber %v.%v has wrong number of outs: %v require signature %s",
					name, method.Name, method.Type.NumOut(), subSig)
			}
			if returnType := method.Type.Out(0); returnType != typeOfError {
				return fmt.Errorf("subscriber %v.%v returns %v not error", name, method.Name, returnType.String())
			}
		}
	}

	return nil
}

func (s *rpcServer) createSubHandler(sb *subscriber, opts Options) broker.Handler {
	return func(p broker.Publication) error {
		msg := p.Message()

		// get codec
		ct := msg.Header["Content-Type"]

		// default content type
		if len(ct) == 0 {
			msg.Header["Content-Type"] = DefaultContentType
			ct = DefaultContentType
		}

		// get codec
		cf, err := s.newCodec(ct)
		if err != nil {
			return err
		}

		// copy headers
		hdr := make(map[string]string)
		for k, v := range msg.Header {
			hdr[k] = v
		}

		// create context
		ctx := metadata.NewContext(context.Background(), hdr)

		results := make(chan error, len(sb.handlers))

		for i := 0; i < len(sb.handlers); i++ {
			handler := sb.handlers[i]

			var isVal bool
			var req reflect.Value

			if handler.reqType.Kind() == reflect.Ptr {
				req = reflect.New(handler.reqType.Elem())
			} else {
				req = reflect.New(handler.reqType)
				isVal = true
			}
			if isVal {
				req = req.Elem()
			}

			b := &buffer{bytes.NewBuffer(msg.Body)}
			co := cf(b)
			defer co.Close()

			if err := co.ReadHeader(&codec.Message{}, codec.Publication); err != nil {
				return err
			}

			if err := co.ReadBody(req.Interface()); err != nil {
				return err
			}

			fn := func(ctx context.Context, msg Message) error {
				var vals []reflect.Value
				if sb.typ.Kind() != reflect.Func {
					vals = append(vals, sb.rcvr)
				}
				if handler.ctxType != nil {
					vals = append(vals, reflect.ValueOf(ctx))
				}

				vals = append(vals, reflect.ValueOf(msg.Payload()))

				returnValues := handler.method.Call(vals)
				if err := returnValues[0].Interface(); err != nil {
					return err.(error)
				}
				return nil
			}

			for i := len(opts.SubWrappers); i > 0; i-- {
				fn = opts.SubWrappers[i-1](fn)
			}

			if s.wg != nil {
				s.wg.Add(1)
			}

			go func() {
				if s.wg != nil {
					defer s.wg.Done()
				}

				results <- fn(ctx, &rpcMessage{
					topic:       sb.topic,
					contentType: ct,
					payload:     req.Interface(),
				})
			}()
		}

		var errors []string

		for i := 0; i < len(sb.handlers); i++ {
			if err := <-results; err != nil {
				errors = append(errors, err.Error())
			}
		}

		if len(errors) > 0 {
			return fmt.Errorf("subscriber error: %s", strings.Join(errors, "\n"))
		}

		return nil
	}
}

func (s *subscriber) Topic() string {
	return s.topic
}

func (s *subscriber) Subscriber() interface{} {
	return s.subscriber
}

func (s *subscriber) Endpoints() []*registry.Endpoint {
	return s.endpoints
}

func (s *subscriber) Options() SubscriberOptions {
	return s.opts
}
<|endoftext|>