<commit_before>package terraform

import (
	"fmt"

	"github.com/hashicorp/terraform/config"
	"github.com/hashicorp/terraform/dag"
)

// ResourceCountTransformer is a GraphTransformer that expands the count
// out for a specific resource.
type ResourceCountTransformer struct {
	Resource *config.Resource
	Destroy  bool
}

func (t *ResourceCountTransformer) Transform(g *Graph) error {
	// Expand the resource count
	count, err := t.Resource.Count()
	if err != nil {
		return err
	}

	// Don't allow the count to be negative
	if count < 0 {
		return fmt.Errorf("negative count: %d", count)
	}

	// For each count, build and add the node
	nodes := make([]dag.Vertex, count)
	for i := 0; i < count; i++ {
		// Set the index. If our count is 1 we special case it so that
		// we handle the "resource.0" and "resource" boundary properly.
		index := i
		if count == 1 {
			index = -1
		}

		// Save the node for later so we can do connections. Make the
		// proper node depending on if we're just a destroy node or if
		// were a regular node.
		var node dag.Vertex = &graphNodeExpandedResource{
			Index:    index,
			Resource: t.Resource,
		}
		if t.Destroy {
			node = &graphNodeExpandedResourceDestroy{
				graphNodeExpandedResource: node.(*graphNodeExpandedResource),
			}
		}

		// Add the node now
		nodes[i] = node
		g.Add(nodes[i])
	}

	// Make the dependency connections
	for _, n := range nodes {
		// Connect the dependents. We ignore the return value for missing
		// dependents since that should've been caught at a higher level.
		g.ConnectDependent(n)
	}

	return nil
}

type graphNodeExpandedResource struct {
	Index    int
	Resource *config.Resource
}

func (n *graphNodeExpandedResource) Name() string {
	if n.Index == -1 {
		return n.Resource.Id()
	}

	return fmt.Sprintf("%s #%d", n.Resource.Id(), n.Index)
}

// GraphNodeDependable impl.
func (n *graphNodeExpandedResource) DependableName() []string {
	return []string{
		n.Resource.Id(),
		n.stateId(),
	}
}

// GraphNodeDependent impl.
func (n *graphNodeExpandedResource) DependentOn() []string {
	config := &GraphNodeConfigResource{Resource: n.Resource}
	return config.DependentOn()
}

// GraphNodeProviderConsumer
func (n *graphNodeExpandedResource) ProvidedBy() []string {
	return []string{resourceProvider(n.Resource.Type)}
}

// GraphNodeEvalable impl.
func (n *graphNodeExpandedResource) EvalTree() EvalNode {
	// Build the resource. If we aren't part of a multi-resource, then
	// we still consider ourselves as count index zero.
	index := n.Index
	if index < 0 {
		index = 0
	}
	resource := &Resource{CountIndex: index}

	// Shared node for interpolation of configuration
	interpolateNode := &EvalInterpolate{
		Config:   n.Resource.RawConfig,
		Resource: resource,
	}

	seq := &EvalSequence{Nodes: make([]EvalNode, 0, 5)}

	// Validate the resource
	vseq := &EvalSequence{Nodes: make([]EvalNode, 0, 5)}
	vseq.Nodes = append(vseq.Nodes, &EvalValidateResource{
		Provider:     &EvalGetProvider{Name: n.ProvidedBy()[0]},
		Config:       interpolateNode,
		ResourceName: n.Resource.Name,
		ResourceType: n.Resource.Type,
	})

	// Validate all the provisioners
	for _, p := range n.Resource.Provisioners {
		vseq.Nodes = append(vseq.Nodes, &EvalValidateProvisioner{
			Provisioner: &EvalGetProvisioner{Name: p.Type},
			Config: &EvalInterpolate{
				Config: p.RawConfig, Resource: resource},
		})
	}

	// Add the validation operations
	seq.Nodes = append(seq.Nodes, &EvalOpFilter{
		Ops:  []walkOperation{walkValidate},
		Node: vseq,
	})

	// Build instance info
	info := &InstanceInfo{Id: n.stateId(), Type: n.Resource.Type}
	seq.Nodes = append(seq.Nodes, &EvalInstanceInfo{Info: info})

	// Refresh the resource
	seq.Nodes = append(seq.Nodes, &EvalOpFilter{
		Ops: []walkOperation{walkRefresh},
		Node: &EvalWriteState{
			Name:         n.stateId(),
			ResourceType: n.Resource.Type,
			Dependencies: n.DependentOn(),
			State: &EvalRefresh{
				Info:     info,
				Provider: &EvalGetProvider{Name: n.ProvidedBy()[0]},
				State:    &EvalReadState{Name: n.stateId()},
			},
		},
	})

	// Diff the resource
	var diff InstanceDiff
	seq.Nodes = append(seq.Nodes, &EvalOpFilter{
		Ops: []walkOperation{walkPlan},
		Node: &EvalSequence{
			Nodes: []EvalNode{
				&EvalWriteState{
					Name:         n.stateId(),
					ResourceType: n.Resource.Type,
					Dependencies: n.DependentOn(),
					State: &EvalDiff{
						Info:     info,
						Config:   interpolateNode,
						Provider: &EvalGetProvider{Name: n.ProvidedBy()[0]},
						State:    &EvalReadState{Name: n.stateId()},
						Output:   &diff,
					},
				},
				&EvalWriteDiff{
					Name: n.stateId(),
					Diff: &diff,
				},
			},
		},
	})

	return seq
}

// stateId is the name used for the state key
func (n *graphNodeExpandedResource) stateId() string {
	if n.Index == -1 {
		return n.Resource.Id()
	}

	return fmt.Sprintf("%s.%d", n.Resource.Id(), n.Index)
}

// graphNodeExpandedResourceDestroy represents an expanded resource that
// is to be destroyed.
type graphNodeExpandedResourceDestroy struct {
	*graphNodeExpandedResource
}

func (n *graphNodeExpandedResourceDestroy) Name() string {
	return fmt.Sprintf("%s (destroy)", n.graphNodeExpandedResource.Name())
}

// GraphNodeEvalable impl.
func (n *graphNodeExpandedResourceDestroy) EvalTree() EvalNode {
	// TODO: We need an eval tree that destroys when there is a
	// RequiresNew.
	return EvalNoop{}
}
<commit_msg>terraform: get proper state representation of expanded resources<commit_after>package terraform

import (
	"fmt"

	"github.com/hashicorp/terraform/config"
	"github.com/hashicorp/terraform/dag"
)

// ResourceCountTransformer is a GraphTransformer that expands the count
// out for a specific resource.
type ResourceCountTransformer struct {
	Resource *config.Resource
	Destroy  bool
}

func (t *ResourceCountTransformer) Transform(g *Graph) error {
	// Expand the resource count
	count, err := t.Resource.Count()
	if err != nil {
		return err
	}

	// Don't allow the count to be negative
	if count < 0 {
		return fmt.Errorf("negative count: %d", count)
	}

	// For each count, build and add the node
	nodes := make([]dag.Vertex, count)
	for i := 0; i < count; i++ {
		// Set the index. If our count is 1 we special case it so that
		// we handle the "resource.0" and "resource" boundary properly.
		index := i
		if count == 1 {
			index = -1
		}

		// Save the node for later so we can do connections. Make the
		// proper node depending on if we're just a destroy node or if
		// were a regular node.
		var node dag.Vertex = &graphNodeExpandedResource{
			Index:    index,
			Resource: t.Resource,
		}
		if t.Destroy {
			node = &graphNodeExpandedResourceDestroy{
				graphNodeExpandedResource: node.(*graphNodeExpandedResource),
			}
		}

		// Add the node now
		nodes[i] = node
		g.Add(nodes[i])
	}

	// Make the dependency connections
	for _, n := range nodes {
		// Connect the dependents. We ignore the return value for missing
		// dependents since that should've been caught at a higher level.
		g.ConnectDependent(n)
	}

	return nil
}

type graphNodeExpandedResource struct {
	Index    int
	Resource *config.Resource
}

func (n *graphNodeExpandedResource) Name() string {
	if n.Index == -1 {
		return n.Resource.Id()
	}

	return fmt.Sprintf("%s #%d", n.Resource.Id(), n.Index)
}

// GraphNodeDependable impl.
func (n *graphNodeExpandedResource) DependableName() []string {
	return []string{
		n.Resource.Id(),
		n.stateId(),
	}
}

// GraphNodeDependent impl.
func (n *graphNodeExpandedResource) DependentOn() []string {
	config := &GraphNodeConfigResource{Resource: n.Resource}
	return config.DependentOn()
}

// GraphNodeProviderConsumer
func (n *graphNodeExpandedResource) ProvidedBy() []string {
	return []string{resourceProvider(n.Resource.Type)}
}

// GraphNodeEvalable impl.
func (n *graphNodeExpandedResource) EvalTree() EvalNode {
	// Build the resource. If we aren't part of a multi-resource, then
	// we still consider ourselves as count index zero.
	index := n.Index
	if index < 0 {
		index = 0
	}
	resource := &Resource{CountIndex: index}

	// Shared node for interpolation of configuration
	interpolateNode := &EvalInterpolate{
		Config:   n.Resource.RawConfig,
		Resource: resource,
	}

	seq := &EvalSequence{Nodes: make([]EvalNode, 0, 5)}

	// Validate the resource
	vseq := &EvalSequence{Nodes: make([]EvalNode, 0, 5)}
	vseq.Nodes = append(vseq.Nodes, &EvalValidateResource{
		Provider:     &EvalGetProvider{Name: n.ProvidedBy()[0]},
		Config:       interpolateNode,
		ResourceName: n.Resource.Name,
		ResourceType: n.Resource.Type,
	})

	// Validate all the provisioners
	for _, p := range n.Resource.Provisioners {
		vseq.Nodes = append(vseq.Nodes, &EvalValidateProvisioner{
			Provisioner: &EvalGetProvisioner{Name: p.Type},
			Config: &EvalInterpolate{
				Config: p.RawConfig, Resource: resource},
		})
	}

	// Add the validation operations
	seq.Nodes = append(seq.Nodes, &EvalOpFilter{
		Ops:  []walkOperation{walkValidate},
		Node: vseq,
	})

	// Build instance info
	info := &InstanceInfo{Id: n.stateId(), Type: n.Resource.Type}
	seq.Nodes = append(seq.Nodes, &EvalInstanceInfo{Info: info})

	// Refresh the resource
	seq.Nodes = append(seq.Nodes, &EvalOpFilter{
		Ops: []walkOperation{walkRefresh},
		Node: &EvalWriteState{
			Name:         n.stateId(),
			ResourceType: n.Resource.Type,
			Dependencies: n.DependentOn(),
			State: &EvalRefresh{
				Info:     info,
				Provider: &EvalGetProvider{Name: n.ProvidedBy()[0]},
				State:    &EvalReadState{Name: n.stateId()},
			},
		},
	})

	// Diff the resource
	var diff InstanceDiff
	seq.Nodes = append(seq.Nodes, &EvalOpFilter{
		Ops: []walkOperation{walkPlan},
		Node: &EvalSequence{
			Nodes: []EvalNode{
				&EvalWriteState{
					Name:         n.stateId(),
					ResourceType: n.Resource.Type,
					Dependencies: n.DependentOn(),
					State: &EvalDiff{
						Info:     info,
						Config:   interpolateNode,
						Provider: &EvalGetProvider{Name: n.ProvidedBy()[0]},
						State:    &EvalReadState{Name: n.stateId()},
						Output:   &diff,
					},
				},
				&EvalWriteDiff{
					Name: n.stateId(),
					Diff: &diff,
				},
			},
		},
	})

	return seq
}

// stateId is the name used for the state key
func (n *graphNodeExpandedResource) stateId() string {
	if n.Index == -1 {
		return n.Resource.Id()
	}

	return fmt.Sprintf("%s.%d", n.Resource.Id(), n.Index)
}

// GraphNodeStateRepresentative impl.
func (n *graphNodeExpandedResource) StateId() []string {
	return []string{n.stateId()}
}

// graphNodeExpandedResourceDestroy represents an expanded resource that
// is to be destroyed.
type graphNodeExpandedResourceDestroy struct {
	*graphNodeExpandedResource
}

func (n *graphNodeExpandedResourceDestroy) Name() string {
	return fmt.Sprintf("%s (destroy)", n.graphNodeExpandedResource.Name())
}

// GraphNodeEvalable impl.
func (n *graphNodeExpandedResourceDestroy) EvalTree() EvalNode {
	// TODO: We need an eval tree that destroys when there is a
	// RequiresNew.
	return EvalNoop{}
}
<|endoftext|>


<commit_before>package api

import (
	"crypto/tls"
	"net"
	"net/http"
	"sync"
	"time"

	"github.com/kr/secureheader"
	log "github.com/sirupsen/logrus"
	cmn "github.com/tendermint/tmlibs/common"

	"github.com/bytom/accesstoken"
	"github.com/bytom/blockchain"
	cfg "github.com/bytom/config"
	"github.com/bytom/dashboard"
	"github.com/bytom/errors"
	"github.com/bytom/net/http/authn"
	"github.com/bytom/net/http/httpjson"
	"github.com/bytom/net/http/static"
	"github.com/bytom/protocol"
	"github.com/bytom/wallet"
)

var (
	errNotAuthenticated = errors.New("not authenticated")
	httpReadTimeout     = 2 * time.Minute
	httpWriteTimeout    = time.Hour
)

const (
	// SUCCESS indicates the rpc calling is successful.
	SUCCESS = "success"
	// FAIL indicated the rpc calling is failed.
	FAIL               = "fail"
	crosscoreRPCPrefix = "/rpc/"
)

// Response describes the response standard.
type Response struct {
	Status string      `json:"status,omitempty"`
	Msg    string      `json:"msg,omitempty"`
	Data   interface{} `json:"data,omitempty"`
}

//NewSuccessResponse success response
func NewSuccessResponse(data interface{}) Response {
	return Response{Status: SUCCESS, Data: data}
}

//NewErrorResponse error response
func NewErrorResponse(err error) Response {
	return Response{Status: FAIL, Msg: err.Error()}
}

type waitHandler struct {
	h  http.Handler
	wg sync.WaitGroup
}

func (wh *waitHandler) Set(h http.Handler) {
	wh.h = h
	wh.wg.Done()
}

func (wh *waitHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	wh.wg.Wait()
	wh.h.ServeHTTP(w, req)
}

// API is the scheduling center for server
type API struct {
	bcr          *blockchain.BlockchainReactor
	wallet       *wallet.Wallet
	accessTokens *accesstoken.CredentialStore
	chain        *protocol.Chain
	server       *http.Server
	handler      http.Handler
}

func (a *API) initServer(config *cfg.Config) {
	// The waitHandler accepts incoming requests, but blocks until its underlying
	// handler is set, when the second phase is complete.
	var coreHandler waitHandler
	var handler http.Handler

	coreHandler.wg.Add(1)
	mux := http.NewServeMux()
	mux.Handle("/", &coreHandler)

	handler = mux
	if config.Auth.Disable == false {
		handler = AuthHandler(handler, a.accessTokens)
	}
	handler = RedirectHandler(handler)

	secureheader.DefaultConfig.PermitClearLoopback = true
	secureheader.DefaultConfig.HTTPSRedirect = false
	secureheader.DefaultConfig.Next = handler

	a.server = &http.Server{
		// Note: we should not set TLSConfig here;
		// we took care of TLS with the listener in maybeUseTLS.
		Handler:      secureheader.DefaultConfig,
		ReadTimeout:  httpReadTimeout,
		WriteTimeout: httpWriteTimeout,
		// Disable HTTP/2 for now until the Go implementation is more stable.
		// https://github.com/golang/go/issues/16450
		// https://github.com/golang/go/issues/17071
		TLSNextProto: map[string]func(*http.Server, *tls.Conn, http.Handler){},
	}

	coreHandler.Set(a)
}

// StartServer start the server
func (a *API) StartServer(address string) {
	log.WithField("api address:", address).Info("Rpc listen")
	listener, err := net.Listen("tcp", address)
	if err != nil {
		cmn.Exit(cmn.Fmt("Failed to register tcp port: %v", err))
	}

	// The `Serve` call has to happen in its own goroutine because
	// it's blocking and we need to proceed to the rest of the core setup after
	// we call it.
	go func() {
		if err := a.server.Serve(listener); err != nil {
			log.WithField("error", errors.Wrap(err, "Serve")).Error("Rpc server")
		}
	}()
}

// NewAPI create and initialize the API
func NewAPI(bcr *blockchain.BlockchainReactor, wallet *wallet.Wallet, chain *protocol.Chain, config *cfg.Config, token *accesstoken.CredentialStore) *API {
	api := &API{
		bcr:          bcr,
		wallet:       wallet,
		chain:        chain,
		accessTokens: token,
	}
	api.buildHandler()
	api.initServer(config)

	return api
}

func (a *API) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
	a.handler.ServeHTTP(rw, req)
}

// buildHandler is in charge of all the rpc handling.
func (a *API) buildHandler() {
	walletEnable := false
	m := http.NewServeMux()
	if a.wallet != nil {
		walletEnable = true

		m.Handle("/create-account", jsonHandler(a.createAccount))
		m.Handle("/update-account-tags", jsonHandler(a.updateAccountTags))
		m.Handle("/list-accounts", jsonHandler(a.listAccounts))
		m.Handle("/delete-account", jsonHandler(a.deleteAccount))

		m.Handle("/create-account-receiver", jsonHandler(a.createAccountReceiver))
		m.Handle("/list-addresses", jsonHandler(a.listAddresses))
		m.Handle("/validate-address", jsonHandler(a.validateAddress))

		m.Handle("/create-asset", jsonHandler(a.createAsset))
		m.Handle("/update-asset-alias", jsonHandler(a.updateAssetAlias))
		m.Handle("/update-asset-tags", jsonHandler(a.updateAssetTags))
		m.Handle("/list-assets", jsonHandler(a.listAssets))

		m.Handle("/create-key", jsonHandler(a.pseudohsmCreateKey))
		m.Handle("/list-keys", jsonHandler(a.pseudohsmListKeys))
		m.Handle("/delete-key", jsonHandler(a.pseudohsmDeleteKey))
		m.Handle("/reset-key-password", jsonHandler(a.pseudohsmResetPassword))

		m.Handle("/export-private-key", jsonHandler(a.walletExportKey))
		m.Handle("/import-private-key", jsonHandler(a.walletImportKey))
		m.Handle("/import-key-progress", jsonHandler(a.keyImportProgress))

		m.Handle("/build-transaction", jsonHandler(a.build))
		m.Handle("/sign-transaction", jsonHandler(a.pseudohsmSignTemplates))
		m.Handle("/submit-transaction", jsonHandler(a.submit))
		m.Handle("/sign-submit-transaction", jsonHandler(a.signSubmit))
		m.Handle("/get-transaction", jsonHandler(a.getTransaction))
		m.Handle("/list-transactions", jsonHandler(a.listTransactions))

		m.Handle("/list-balances", jsonHandler(a.listBalances))
		m.Handle("/list-unspent-outputs", jsonHandler(a.listUnspentOutputs))
	} else {
		log.Warn("Please enable wallet")
	}

	m.Handle("/", alwaysError(errors.New("not Found")))
	m.Handle("/error", jsonHandler(a.walletError))

	m.Handle("/info", jsonHandler(a.bcr.Info))
	m.Handle("/net-info", jsonHandler(a.getNetInfo))

	m.Handle("/create-access-token", jsonHandler(a.createAccessToken))
	m.Handle("/list-access-tokens", jsonHandler(a.listAccessTokens))
	m.Handle("/delete-access-token", jsonHandler(a.deleteAccessToken))
	m.Handle("/check-access-token", jsonHandler(a.checkAccessToken))

	m.Handle("/create-transaction-feed", jsonHandler(a.createTxFeed))
	m.Handle("/get-transaction-feed", jsonHandler(a.getTxFeed))
	m.Handle("/update-transaction-feed", jsonHandler(a.updateTxFeed))
	m.Handle("/delete-transaction-feed", jsonHandler(a.deleteTxFeed))
	m.Handle("/list-transaction-feeds", jsonHandler(a.listTxFeeds))

	m.Handle("/block-hash", jsonHandler(a.getBestBlockHash))
	m.Handle("/get-block-header-by-hash", jsonHandler(a.getBlockHeaderByHash))
	m.Handle("/get-block-header-by-height", jsonHandler(a.getBlockHeaderByHeight))
	m.Handle("/get-block", jsonHandler(a.getBlock))
	m.Handle("/get-block-count", jsonHandler(a.getBlockCount))
	m.Handle("/get-block-transactions-count-by-hash", jsonHandler(a.getBlockTransactionsCountByHash))
	m.Handle("/get-block-transactions-count-by-height", jsonHandler(a.getBlockTransactionsCountByHeight))

	m.Handle("/is-mining", jsonHandler(a.isMining))
	m.Handle("/gas-rate", jsonHandler(a.gasRate))
	m.Handle("/getwork", jsonHandler(a.getWork))
	m.Handle("/submitwork", jsonHandler(a.submitWork))

	handler := latencyHandler(m, walletEnable)
	handler = maxBytesHandler(handler) // TODO(tessr): consider moving this to non-core specific mux
	handler = webAssetsHandler(handler)

	a.handler = handler
}

func maxBytesHandler(h http.Handler) http.Handler {
	const maxReqSize = 1e7 // 10MB
	return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		// A block can easily be bigger than maxReqSize, but everything
		// else should be pretty small.
		if req.URL.Path != crosscoreRPCPrefix+"signer/sign-block" {
			req.Body = http.MaxBytesReader(w, req.Body, maxReqSize)
		}
		h.ServeHTTP(w, req)
	})
}

// json Handler
func jsonHandler(f interface{}) http.Handler {
	h, err := httpjson.Handler(f, errorFormatter.Write)
	if err != nil {
		panic(err)
	}
	return h
}

// error Handler
func alwaysError(err error) http.Handler {
	return jsonHandler(func() error { return err })
}

func webAssetsHandler(next http.Handler) http.Handler {
	mux := http.NewServeMux()
	mux.Handle("/dashboard/", http.StripPrefix("/dashboard/", static.Handler{
		Assets:  dashboard.Files,
		Default: "index.html",
	}))
	mux.Handle("/", next)

	return mux
}

// AuthHandler access token auth Handler
func AuthHandler(handler http.Handler, accessTokens *accesstoken.CredentialStore) http.Handler {
	authenticator := authn.NewAPI(accessTokens)

	return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
		// TODO(tessr): check that this path exists; return early if this path isn't legit
		req, err := authenticator.Authenticate(req)
		if err != nil {
			log.WithField("error", errors.Wrap(err, "Serve")).Error("Authenticate fail")
			err = errors.Sub(errNotAuthenticated, err)
			errorFormatter.Write(req.Context(), rw, err)
			return
		}
		handler.ServeHTTP(rw, req)
	})
}

// RedirectHandler redirect to dashboard handler
func RedirectHandler(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		if req.URL.Path == "/" {
			http.Redirect(w, req, "/dashboard/", http.StatusFound)
			return
		}
		next.ServeHTTP(w, req)
	})
}

// latencyHandler take latency for the request url path, and redirect url path to wait-disable when wallet is closed
func latencyHandler(m *http.ServeMux, walletEnable bool) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		// latency for the request url path
		if l := latency(m, req); l != nil {
			defer l.RecordSince(time.Now())
		}

		// when the wallet is not been opened and the url path is not been found, redirect url path to error
		walletRedirectHandler(m, walletEnable, w, req)

		m.ServeHTTP(w, req)
	})
}

// walletRedirectHandler redirect to error when the wallet is closed
func walletRedirectHandler(m *http.ServeMux, walletEnable bool, w http.ResponseWriter, req *http.Request) {
	if _, pattern := m.Handler(req); pattern != req.URL.Path && !walletEnable {
		url := req.URL
		url.Path = "/error"
		http.Redirect(w, req, url.String(), http.StatusOK)
	}
}
<commit_msg>fix warning: "http: multiple response.WriteHeader calls" (#490)<commit_after>package api

import (
	"crypto/tls"
	"net"
	"net/http"
	"sync"
	"time"

	"github.com/kr/secureheader"
	log "github.com/sirupsen/logrus"
	cmn "github.com/tendermint/tmlibs/common"

	"github.com/bytom/accesstoken"
	"github.com/bytom/blockchain"
	cfg "github.com/bytom/config"
	"github.com/bytom/dashboard"
	"github.com/bytom/errors"
	"github.com/bytom/net/http/authn"
	"github.com/bytom/net/http/httpjson"
	"github.com/bytom/net/http/static"
	"github.com/bytom/protocol"
	"github.com/bytom/wallet"
)

var (
	errNotAuthenticated = errors.New("not authenticated")
	httpReadTimeout     = 2 * time.Minute
	httpWriteTimeout    = time.Hour
)

const (
	// SUCCESS indicates the rpc calling is successful.
	SUCCESS = "success"
	// FAIL indicated the rpc calling is failed.
	FAIL               = "fail"
	crosscoreRPCPrefix = "/rpc/"
)

// Response describes the response standard.
type Response struct {
	Status string      `json:"status,omitempty"`
	Msg    string      `json:"msg,omitempty"`
	Data   interface{} `json:"data,omitempty"`
}

//NewSuccessResponse success response
func NewSuccessResponse(data interface{}) Response {
	return Response{Status: SUCCESS, Data: data}
}

//NewErrorResponse error response
func NewErrorResponse(err error) Response {
	return Response{Status: FAIL, Msg: err.Error()}
}

type waitHandler struct {
	h  http.Handler
	wg sync.WaitGroup
}

func (wh *waitHandler) Set(h http.Handler) {
	wh.h = h
	wh.wg.Done()
}

func (wh *waitHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	wh.wg.Wait()
	wh.h.ServeHTTP(w, req)
}

// API is the scheduling center for server
type API struct {
	bcr          *blockchain.BlockchainReactor
	wallet       *wallet.Wallet
	accessTokens *accesstoken.CredentialStore
	chain        *protocol.Chain
	server       *http.Server
	handler      http.Handler
}

func (a *API) initServer(config *cfg.Config) {
	// The waitHandler accepts incoming requests, but blocks until its underlying
	// handler is set, when the second phase is complete.
	var coreHandler waitHandler
	var handler http.Handler

	coreHandler.wg.Add(1)
	mux := http.NewServeMux()
	mux.Handle("/", &coreHandler)

	handler = mux
	if config.Auth.Disable == false {
		handler = AuthHandler(handler, a.accessTokens)
	}
	handler = RedirectHandler(handler)

	secureheader.DefaultConfig.PermitClearLoopback = true
	secureheader.DefaultConfig.HTTPSRedirect = false
	secureheader.DefaultConfig.Next = handler

	a.server = &http.Server{
		// Note: we should not set TLSConfig here;
		// we took care of TLS with the listener in maybeUseTLS.
		Handler:      secureheader.DefaultConfig,
		ReadTimeout:  httpReadTimeout,
		WriteTimeout: httpWriteTimeout,
		// Disable HTTP/2 for now until the Go implementation is more stable.
		// https://github.com/golang/go/issues/16450
		// https://github.com/golang/go/issues/17071
		TLSNextProto: map[string]func(*http.Server, *tls.Conn, http.Handler){},
	}

	coreHandler.Set(a)
}

// StartServer start the server
func (a *API) StartServer(address string) {
	log.WithField("api address:", address).Info("Rpc listen")
	listener, err := net.Listen("tcp", address)
	if err != nil {
		cmn.Exit(cmn.Fmt("Failed to register tcp port: %v", err))
	}

	// The `Serve` call has to happen in its own goroutine because
	// it's blocking and we need to proceed to the rest of the core setup after
	// we call it.
	go func() {
		if err := a.server.Serve(listener); err != nil {
			log.WithField("error", errors.Wrap(err, "Serve")).Error("Rpc server")
		}
	}()
}

// NewAPI create and initialize the API
func NewAPI(bcr *blockchain.BlockchainReactor, wallet *wallet.Wallet, chain *protocol.Chain, config *cfg.Config, token *accesstoken.CredentialStore) *API {
	api := &API{
		bcr:          bcr,
		wallet:       wallet,
		chain:        chain,
		accessTokens: token,
	}
	api.buildHandler()
	api.initServer(config)

	return api
}

func (a *API) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
	a.handler.ServeHTTP(rw, req)
}

// buildHandler is in charge of all the rpc handling.
func (a *API) buildHandler() {
	walletEnable := false
	m := http.NewServeMux()
	if a.wallet != nil {
		walletEnable = true

		m.Handle("/create-account", jsonHandler(a.createAccount))
		m.Handle("/update-account-tags", jsonHandler(a.updateAccountTags))
		m.Handle("/list-accounts", jsonHandler(a.listAccounts))
		m.Handle("/delete-account", jsonHandler(a.deleteAccount))

		m.Handle("/create-account-receiver", jsonHandler(a.createAccountReceiver))
		m.Handle("/list-addresses", jsonHandler(a.listAddresses))
		m.Handle("/validate-address", jsonHandler(a.validateAddress))

		m.Handle("/create-asset", jsonHandler(a.createAsset))
		m.Handle("/update-asset-alias", jsonHandler(a.updateAssetAlias))
		m.Handle("/update-asset-tags", jsonHandler(a.updateAssetTags))
		m.Handle("/list-assets", jsonHandler(a.listAssets))

		m.Handle("/create-key", jsonHandler(a.pseudohsmCreateKey))
		m.Handle("/list-keys", jsonHandler(a.pseudohsmListKeys))
		m.Handle("/delete-key", jsonHandler(a.pseudohsmDeleteKey))
		m.Handle("/reset-key-password", jsonHandler(a.pseudohsmResetPassword))

		m.Handle("/export-private-key", jsonHandler(a.walletExportKey))
		m.Handle("/import-private-key", jsonHandler(a.walletImportKey))
		m.Handle("/import-key-progress", jsonHandler(a.keyImportProgress))

		m.Handle("/build-transaction", jsonHandler(a.build))
		m.Handle("/sign-transaction", jsonHandler(a.pseudohsmSignTemplates))
		m.Handle("/submit-transaction", jsonHandler(a.submit))
		m.Handle("/sign-submit-transaction", jsonHandler(a.signSubmit))
		m.Handle("/get-transaction", jsonHandler(a.getTransaction))
		m.Handle("/list-transactions", jsonHandler(a.listTransactions))

		m.Handle("/list-balances", jsonHandler(a.listBalances))
		m.Handle("/list-unspent-outputs", jsonHandler(a.listUnspentOutputs))
	} else {
		log.Warn("Please enable wallet")
	}

	m.Handle("/", alwaysError(errors.New("not Found")))
	m.Handle("/error", jsonHandler(a.walletError))

	m.Handle("/info", jsonHandler(a.bcr.Info))
	m.Handle("/net-info", jsonHandler(a.getNetInfo))

	m.Handle("/create-access-token", jsonHandler(a.createAccessToken))
	m.Handle("/list-access-tokens", jsonHandler(a.listAccessTokens))
	m.Handle("/delete-access-token", jsonHandler(a.deleteAccessToken))
	m.Handle("/check-access-token", jsonHandler(a.checkAccessToken))

	m.Handle("/create-transaction-feed", jsonHandler(a.createTxFeed))
	m.Handle("/get-transaction-feed", jsonHandler(a.getTxFeed))
	m.Handle("/update-transaction-feed", jsonHandler(a.updateTxFeed))
	m.Handle("/delete-transaction-feed", jsonHandler(a.deleteTxFeed))
	m.Handle("/list-transaction-feeds", jsonHandler(a.listTxFeeds))

	m.Handle("/block-hash", jsonHandler(a.getBestBlockHash))
	m.Handle("/get-block-header-by-hash", jsonHandler(a.getBlockHeaderByHash))
	m.Handle("/get-block-header-by-height", jsonHandler(a.getBlockHeaderByHeight))
	m.Handle("/get-block", jsonHandler(a.getBlock))
	m.Handle("/get-block-count", jsonHandler(a.getBlockCount))
	m.Handle("/get-block-transactions-count-by-hash", jsonHandler(a.getBlockTransactionsCountByHash))
	m.Handle("/get-block-transactions-count-by-height", jsonHandler(a.getBlockTransactionsCountByHeight))

	m.Handle("/is-mining", jsonHandler(a.isMining))
	m.Handle("/gas-rate", jsonHandler(a.gasRate))
	m.Handle("/getwork", jsonHandler(a.getWork))
	m.Handle("/submitwork", jsonHandler(a.submitWork))

	handler := latencyHandler(m, walletEnable)
	handler = walletRedirectHandler(handler)
	handler = maxBytesHandler(handler) // TODO(tessr): consider moving this to non-core specific mux
	handler = webAssetsHandler(handler)

	a.handler = handler
}

func maxBytesHandler(h http.Handler) http.Handler {
	const maxReqSize = 1e7 // 10MB
	return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		// A block can easily be bigger than maxReqSize, but everything
		// else should be pretty small.
		if req.URL.Path != crosscoreRPCPrefix+"signer/sign-block" {
			req.Body = http.MaxBytesReader(w, req.Body, maxReqSize)
		}
		h.ServeHTTP(w, req)
	})
}

// json Handler
func jsonHandler(f interface{}) http.Handler {
	h, err := httpjson.Handler(f, errorFormatter.Write)
	if err != nil {
		panic(err)
	}
	return h
}

// error Handler
func alwaysError(err error) http.Handler {
	return jsonHandler(func() error { return err })
}

func webAssetsHandler(next http.Handler) http.Handler {
	mux := http.NewServeMux()
	mux.Handle("/dashboard/", http.StripPrefix("/dashboard/", static.Handler{
		Assets:  dashboard.Files,
		Default: "index.html",
	}))
	mux.Handle("/", next)

	return mux
}

// AuthHandler access token auth Handler
func AuthHandler(handler http.Handler, accessTokens *accesstoken.CredentialStore) http.Handler {
	authenticator := authn.NewAPI(accessTokens)

	return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
		// TODO(tessr): check that this path exists; return early if this path isn't legit
		req, err := authenticator.Authenticate(req)
		if err != nil {
			log.WithField("error", errors.Wrap(err, "Serve")).Error("Authenticate fail")
			err = errors.Sub(errNotAuthenticated, err)
			errorFormatter.Write(req.Context(), rw, err)
			return
		}
		handler.ServeHTTP(rw, req)
	})
}

// RedirectHandler redirect to dashboard handler
func RedirectHandler(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		if req.URL.Path == "/" {
			http.Redirect(w, req, "/dashboard/", http.StatusFound)
			return
		}
		next.ServeHTTP(w, req)
	})
}

// latencyHandler take latency for the request url path, and redirect url path to wait-disable when wallet is closed
func latencyHandler(m *http.ServeMux, walletEnable bool) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		// latency for the request url path
		if l := latency(m, req); l != nil {
			defer l.RecordSince(time.Now())
		}

		// when the wallet is not been opened and the url path is not been found, modify url path to error,
		// and after redirecting to the new url in func walletRedirectHandler
		if _, pattern := m.Handler(req); pattern != req.URL.Path && !walletEnable {
			req.URL.Path = "/error"
		}

		m.ServeHTTP(w, req)
	})
}

// walletRedirectHandler redirect to error when the wallet is closed
func walletRedirectHandler(handler http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		if req.URL.Path == "/error" {
			http.Redirect(w, req, req.URL.String(), http.StatusOK)
			return
		}
		handler.ServeHTTP(w, req)
	})
}
<|endoftext|>


<commit_before>package api

import (
	"crypto/tls"
	"net"
	"net/http"
	"sync"
	"time"

	"github.com/kr/secureheader"
	log "github.com/sirupsen/logrus"
	cmn "github.com/tendermint/tmlibs/common"

	"github.com/bytom/accesstoken"
	"github.com/bytom/blockchain/txfeed"
	cfg "github.com/bytom/config"
	"github.com/bytom/dashboard"
	"github.com/bytom/errors"
	"github.com/bytom/mining/cpuminer"
	"github.com/bytom/mining/miningpool"
	"github.com/bytom/net/http/authn"
	"github.com/bytom/net/http/gzip"
	"github.com/bytom/net/http/httpjson"
	"github.com/bytom/net/http/static"
	"github.com/bytom/netsync"
	"github.com/bytom/protocol"
	"github.com/bytom/wallet"
)

var (
	errNotAuthenticated = errors.New("not authenticated")
	httpReadTimeout     = 2 * time.Minute
	httpWriteTimeout    = time.Hour
)

const (
	// SUCCESS indicates the rpc calling is successful.
	SUCCESS = "success"
	// FAIL indicated the rpc calling is failed.
	FAIL               = "fail"
	crosscoreRPCPrefix = "/rpc/"
)

// Response describes the response standard.
type Response struct {
	Status      string      `json:"status,omitempty"`
	Code        string      `json:"code,omitempty"`
	Msg         string      `json:"msg,omitempty"`
	ErrorDetail string      `json:"error_detail,omitempty"`
	Data        interface{} `json:"data,omitempty"`
}

//NewSuccessResponse success response
func NewSuccessResponse(data interface{}) Response {
	return Response{Status: SUCCESS, Data: data}
}

//NewErrorResponse error response
func NewErrorResponse(err error) Response {
	root := errors.Root(err)
	if info, ok := respErrFormatter[root]; ok {
		return Response{
			Status:      FAIL,
			Code:        info.ChainCode,
			Msg:         info.Message,
			ErrorDetail: errors.Detail(err),
		}
	}
	return Response{
		Status:      FAIL,
		Msg:         errors.Detail(err),
		ErrorDetail: errors.Detail(err),
	}
}

type waitHandler struct {
	h  http.Handler
	wg sync.WaitGroup
}

func (wh *waitHandler) Set(h http.Handler) {
	wh.h = h
	wh.wg.Done()
}

func (wh *waitHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	wh.wg.Wait()
	wh.h.ServeHTTP(w, req)
}

// API is the scheduling center for server
type API struct {
	sync          *netsync.SyncManager
	wallet        *wallet.Wallet
	accessTokens  *accesstoken.CredentialStore
	chain         *protocol.Chain
	server        *http.Server
	handler       http.Handler
	txFeedTracker *txfeed.Tracker
	cpuMiner      *cpuminer.CPUMiner
	miningPool    *miningpool.MiningPool
}

func (a *API) initServer(config *cfg.Config) {
	// The waitHandler accepts incoming requests, but blocks until its underlying
	// handler is set, when the second phase is complete.
	var coreHandler waitHandler
	var handler http.Handler

	coreHandler.wg.Add(1)
	mux := http.NewServeMux()
	mux.Handle("/", &coreHandler)

	handler = mux
	if config.Auth.Disable == false {
		handler = AuthHandler(handler, a.accessTokens)
	}
	handler = RedirectHandler(handler)

	secureheader.DefaultConfig.PermitClearLoopback = true
	secureheader.DefaultConfig.HTTPSRedirect = false
	secureheader.DefaultConfig.Next = handler

	a.server = &http.Server{
		// Note: we should not set TLSConfig here;
		// we took care of TLS with the listener in maybeUseTLS.
		Handler:      secureheader.DefaultConfig,
		ReadTimeout:  httpReadTimeout,
		WriteTimeout: httpWriteTimeout,
		// Disable HTTP/2 for now until the Go implementation is more stable.
		// https://github.com/golang/go/issues/16450
		// https://github.com/golang/go/issues/17071
		TLSNextProto: map[string]func(*http.Server, *tls.Conn, http.Handler){},
	}

	coreHandler.Set(a)
}

// StartServer start the server
func (a *API) StartServer(address string) {
	log.WithField("api address:", address).Info("Rpc listen")
	listener, err := net.Listen("tcp", address)
	if err != nil {
		cmn.Exit(cmn.Fmt("Failed to register tcp port: %v", err))
	}

	// The `Serve` call has to happen in its own goroutine because
	// it's blocking and we need to proceed to the rest of the core setup after
	// we call it.
	go func() {
		if err := a.server.Serve(listener); err != nil {
			log.WithField("error", errors.Wrap(err, "Serve")).Error("Rpc server")
		}
	}()
}

// NewAPI create and initialize the API
func NewAPI(sync *netsync.SyncManager, wallet *wallet.Wallet, txfeeds *txfeed.Tracker, cpuMiner *cpuminer.CPUMiner, miningPool *miningpool.MiningPool, chain *protocol.Chain, config *cfg.Config, token *accesstoken.CredentialStore) *API {
	api := &API{
		sync:          sync,
		wallet:        wallet,
		chain:         chain,
		accessTokens:  token,
		txFeedTracker: txfeeds,
		cpuMiner:      cpuMiner,
		miningPool:    miningPool,
	}
	api.buildHandler()
	api.initServer(config)

	return api
}

func (a *API) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
	a.handler.ServeHTTP(rw, req)
}

// buildHandler is in charge of all the rpc handling.
func (a *API) buildHandler() {
	walletEnable := false
	m := http.NewServeMux()
	if a.wallet != nil {
		walletEnable = true

		m.Handle("/create-account", jsonHandler(a.createAccount))
		m.Handle("/list-accounts", jsonHandler(a.listAccounts))
		m.Handle("/delete-account", jsonHandler(a.deleteAccount))

		m.Handle("/create-account-receiver", jsonHandler(a.createAccountReceiver))
		m.Handle("/list-addresses", jsonHandler(a.listAddresses))
		m.Handle("/validate-address", jsonHandler(a.validateAddress))

		m.Handle("/create-asset", jsonHandler(a.createAsset))
		m.Handle("/update-asset-alias", jsonHandler(a.updateAssetAlias))
		m.Handle("/get-asset", jsonHandler(a.getAsset))
		m.Handle("/list-assets", jsonHandler(a.listAssets))

		m.Handle("/create-key", jsonHandler(a.pseudohsmCreateKey))
		m.Handle("/list-keys", jsonHandler(a.pseudohsmListKeys))
		m.Handle("/delete-key", jsonHandler(a.pseudohsmDeleteKey))
		m.Handle("/reset-key-password", jsonHandler(a.pseudohsmResetPassword))
		m.Handle("/sign-message", jsonHandler(a.signMessage))

		m.Handle("/build-transaction", jsonHandler(a.build))
		m.Handle("/sign-transaction", jsonHandler(a.pseudohsmSignTemplates))

		m.Handle("/get-transaction", jsonHandler(a.getTransaction))
		m.Handle("/list-transactions", jsonHandler(a.listTransactions))

		m.Handle("/list-balances", jsonHandler(a.listBalances))
		m.Handle("/list-unspent-outputs", jsonHandler(a.listUnspentOutputs))

		m.Handle("/backup-wallet", jsonHandler(a.backupWalletImage))
		m.Handle("/restore-wallet", jsonHandler(a.restoreWalletImage))
	} else {
		log.Warn("Please enable wallet")
	}

	m.Handle("/", alwaysError(errors.New("not Found")))
	m.Handle("/error", jsonHandler(a.walletError))

	m.Handle("/create-access-token", jsonHandler(a.createAccessToken))
	m.Handle("/list-access-tokens", jsonHandler(a.listAccessTokens))
	m.Handle("/delete-access-token", jsonHandler(a.deleteAccessToken))
	m.Handle("/check-access-token", jsonHandler(a.checkAccessToken))

	m.Handle("/create-transaction-feed", jsonHandler(a.createTxFeed))
	m.Handle("/get-transaction-feed", jsonHandler(a.getTxFeed))
	m.Handle("/update-transaction-feed", jsonHandler(a.updateTxFeed))
	m.Handle("/delete-transaction-feed", jsonHandler(a.deleteTxFeed))
	m.Handle("/list-transaction-feeds", jsonHandler(a.listTxFeeds))

	m.Handle("/submit-transaction", jsonHandler(a.submit))
	m.Handle("/estimate-transaction-gas", jsonHandler(a.estimateTxGas))

	m.Handle("/get-unconfirmed-transaction", jsonHandler(a.getUnconfirmedTx))
	m.Handle("/list-unconfirmed-transactions", jsonHandler(a.listUnconfirmedTxs))
	m.Handle("/decode-raw-transaction", jsonHandler(a.decodeRawTransaction))

	m.Handle("/get-block-hash", jsonHandler(a.getBestBlockHash))
	m.Handle("/get-block-header", jsonHandler(a.getBlockHeader))
	m.Handle("/get-block", jsonHandler(a.getBlock))
	m.Handle("/get-block-count", jsonHandler(a.getBlockCount))
	m.Handle("/get-difficulty", jsonHandler(a.getDifficulty))
	m.Handle("/get-hash-rate", jsonHandler(a.getHashRate))

	m.Handle("/is-mining", jsonHandler(a.isMining))
	m.Handle("/set-mining", jsonHandler(a.setMining))

	m.Handle("/get-work", jsonHandler(a.getWork))
	m.Handle("/get-work-json", jsonHandler(a.getWorkJSON))
	m.Handle("/submit-work", jsonHandler(a.submitWork))
	m.Handle("/submit-work-json", jsonHandler(a.submitWorkJSON))

	m.Handle("/verify-message", jsonHandler(a.verifyMessage))
	m.Handle("/decode-program", jsonHandler(a.decodeProgram))

	m.Handle("/gas-rate", jsonHandler(a.gasRate))
	m.Handle("/net-info", jsonHandler(a.getNetInfo))

	handler := latencyHandler(m, walletEnable)
	handler = maxBytesHandler(handler) // TODO(tessr): consider moving this to non-core specific mux
	handler = webAssetsHandler(handler)
	handler = gzip.Handler{Handler: handler}

	a.handler = handler
}

func maxBytesHandler(h http.Handler) http.Handler {
	const maxReqSize = 1e7 // 10MB
	return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		// A block can easily be bigger than maxReqSize, but everything
		// else should be pretty small.
		if req.URL.Path != crosscoreRPCPrefix+"signer/sign-block" {
			req.Body = http.MaxBytesReader(w, req.Body, maxReqSize)
		}
		h.ServeHTTP(w, req)
	})
}

// json Handler
func jsonHandler(f interface{}) http.Handler {
	h, err := httpjson.Handler(f, errorFormatter.Write)
	if err != nil {
		panic(err)
	}
	return h
}

// error Handler
func alwaysError(err error) http.Handler {
	return jsonHandler(func() error { return err })
}

func webAssetsHandler(next http.Handler) http.Handler {
	mux := http.NewServeMux()
	mux.Handle("/dashboard/", http.StripPrefix("/dashboard/", static.Handler{
		Assets:  dashboard.Files,
		Default: "index.html",
	}))
	mux.Handle("/", next)

	return mux
}

// AuthHandler access token auth Handler
func AuthHandler(handler http.Handler, accessTokens *accesstoken.CredentialStore) http.Handler {
	authenticator := authn.NewAPI(accessTokens)

	return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
		// TODO(tessr): check that this path exists; return early if this path isn't legit
		req, err := authenticator.Authenticate(req)
		if err != nil {
			log.WithField("error", errors.Wrap(err, "Serve")).Error("Authenticate fail")
			err = errors.Sub(errNotAuthenticated, err)
			errorFormatter.Write(req.Context(), rw, err)
			return
		}
		handler.ServeHTTP(rw, req)
	})
}

// RedirectHandler redirect to dashboard handler
func RedirectHandler(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		if req.URL.Path == "/" {
			http.Redirect(w, req, "/dashboard/", http.StatusFound)
			return
		}
		next.ServeHTTP(w, req)
	})
}

// latencyHandler take latency for the request url path, and redirect url path to wait-disable when wallet is closed
func latencyHandler(m *http.ServeMux, walletEnable bool) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		// latency for the request url path
		if l := latency(m, req); l != nil {
			defer l.RecordSince(time.Now())
		}

		// when the wallet is not been opened and the url path is not been found, modify url path to error,
		// and redirect handler to error
		if _, pattern := m.Handler(req); pattern != req.URL.Path && !walletEnable {
			req.URL.Path = "/error"
			walletRedirectHandler(w, req)
			return
		}

		m.ServeHTTP(w, req)
	})
}

// walletRedirectHandler redirect to error when the wallet is closed
func walletRedirectHandler(w http.ResponseWriter, req *http.Request) {
	h := http.RedirectHandler(req.URL.String(), http.StatusMovedPermanently)
	h.ServeHTTP(w, req)
}
<commit_msg>fix map error for "hash of unhashable type"<commit_after>package api

import (
	"crypto/tls"
	"net"
	"net/http"
	"sync"
	"time"

	"github.com/kr/secureheader"
	log "github.com/sirupsen/logrus"
	cmn "github.com/tendermint/tmlibs/common"

	"github.com/bytom/accesstoken"
	"github.com/bytom/blockchain/txfeed"
	cfg "github.com/bytom/config"
	"github.com/bytom/dashboard"
	"github.com/bytom/errors"
	"github.com/bytom/mining/cpuminer"
	"github.com/bytom/mining/miningpool"
	"github.com/bytom/net/http/authn"
	"github.com/bytom/net/http/gzip"
	"github.com/bytom/net/http/httpjson"
	"github.com/bytom/net/http/static"
	"github.com/bytom/netsync"
	"github.com/bytom/protocol"
	"github.com/bytom/wallet"
)

var (
	errNotAuthenticated = errors.New("not authenticated")
	httpReadTimeout     = 2 * time.Minute
	httpWriteTimeout    = time.Hour
)

const (
	// SUCCESS indicates the rpc calling is successful.
	SUCCESS = "success"
	// FAIL indicated the rpc calling is failed.
	FAIL               = "fail"
	crosscoreRPCPrefix = "/rpc/"
)

// Response describes the response standard.
type Response struct {
	Status      string      `json:"status,omitempty"`
	Code        string      `json:"code,omitempty"`
	Msg         string      `json:"msg,omitempty"`
	ErrorDetail string      `json:"error_detail,omitempty"`
	Data        interface{} `json:"data,omitempty"`
}

//NewSuccessResponse success response
func NewSuccessResponse(data interface{}) Response {
	return Response{Status: SUCCESS, Data: data}
}

//FormatErrResp format error response
func FormatErrResp(err error) (response Response) {
	response = Response{Status: FAIL}
	root := errors.Root(err)
	// Some types cannot be used as map keys, for example slices.
	// If an error's underlying type is one of these, don't panic.
	// Just treat it like any other missing entry.
	defer func() {
		if err := recover(); err != nil {
			response.ErrorDetail = ""
		}
	}()

	if info, ok := respErrFormatter[root]; ok {
		response.Code = info.ChainCode
		response.Msg = info.Message
		response.ErrorDetail = errors.Detail(err)
	}
	return response
}

//NewErrorResponse error response
func NewErrorResponse(err error) Response {
	response := FormatErrResp(err)
	if response.Msg == "" {
		response.Msg = err.Error()
	}
	return response
}

type waitHandler struct {
	h  http.Handler
	wg sync.WaitGroup
}

func (wh *waitHandler) Set(h http.Handler) {
	wh.h = h
	wh.wg.Done()
}

func (wh *waitHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	wh.wg.Wait()
	wh.h.ServeHTTP(w, req)
}

// API is the scheduling center for server
type API struct {
	sync          *netsync.SyncManager
	wallet        *wallet.Wallet
	accessTokens  *accesstoken.CredentialStore
	chain         *protocol.Chain
	server        *http.Server
	handler       http.Handler
	txFeedTracker *txfeed.Tracker
	cpuMiner      *cpuminer.CPUMiner
	miningPool    *miningpool.MiningPool
}

func (a *API) initServer(config *cfg.Config) {
	// The waitHandler accepts incoming requests, but blocks until its underlying
	// handler is set, when the second phase is complete.
	var coreHandler waitHandler
	var handler http.Handler

	coreHandler.wg.Add(1)
	mux := http.NewServeMux()
	mux.Handle("/", &coreHandler)

	handler = mux
	if config.Auth.Disable == false {
		handler = AuthHandler(handler, a.accessTokens)
	}
	handler = RedirectHandler(handler)

	secureheader.DefaultConfig.PermitClearLoopback = true
	secureheader.DefaultConfig.HTTPSRedirect = false
	secureheader.DefaultConfig.Next = handler

	a.server = &http.Server{
		// Note: we should not set TLSConfig here;
		// we took care of TLS with the listener in maybeUseTLS.
		Handler:      secureheader.DefaultConfig,
		ReadTimeout:  httpReadTimeout,
		WriteTimeout: httpWriteTimeout,
		// Disable HTTP/2 for now until the Go implementation is more stable.
		// https://github.com/golang/go/issues/16450
		// https://github.com/golang/go/issues/17071
		TLSNextProto: map[string]func(*http.Server, *tls.Conn, http.Handler){},
	}

	coreHandler.Set(a)
}

// StartServer start the server
func (a *API) StartServer(address string) {
	log.WithField("api address:", address).Info("Rpc listen")
	listener, err := net.Listen("tcp", address)
	if err != nil {
		cmn.Exit(cmn.Fmt("Failed to register tcp port: %v", err))
	}

	// The `Serve` call has to happen in its own goroutine because
	// it's blocking and we need to proceed to the rest of the core setup after
	// we call it.
	go func() {
		if err := a.server.Serve(listener); err != nil {
			log.WithField("error", errors.Wrap(err, "Serve")).Error("Rpc server")
		}
	}()
}

// NewAPI create and initialize the API
func NewAPI(sync *netsync.SyncManager, wallet *wallet.Wallet, txfeeds *txfeed.Tracker, cpuMiner *cpuminer.CPUMiner, miningPool *miningpool.MiningPool, chain *protocol.Chain, config *cfg.Config, token *accesstoken.CredentialStore) *API {
	api := &API{
		sync:          sync,
		wallet:        wallet,
		chain:         chain,
		accessTokens:  token,
		txFeedTracker: txfeeds,
		cpuMiner:      cpuMiner,
		miningPool:    miningPool,
	}
	api.buildHandler()
	api.initServer(config)

	return api
}

func (a *API) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
	a.handler.ServeHTTP(rw, req)
}

// buildHandler is in charge of all the rpc handling.
func (a *API) buildHandler() {
	walletEnable := false
	m := http.NewServeMux()
	if a.wallet != nil {
		walletEnable = true

		m.Handle("/create-account", jsonHandler(a.createAccount))
		m.Handle("/list-accounts", jsonHandler(a.listAccounts))
		m.Handle("/delete-account", jsonHandler(a.deleteAccount))

		m.Handle("/create-account-receiver", jsonHandler(a.createAccountReceiver))
		m.Handle("/list-addresses", jsonHandler(a.listAddresses))
		m.Handle("/validate-address", jsonHandler(a.validateAddress))

		m.Handle("/create-asset", jsonHandler(a.createAsset))
		m.Handle("/update-asset-alias", jsonHandler(a.updateAssetAlias))
		m.Handle("/get-asset", jsonHandler(a.getAsset))
		m.Handle("/list-assets", jsonHandler(a.listAssets))

		m.Handle("/create-key", jsonHandler(a.pseudohsmCreateKey))
		m.Handle("/list-keys", jsonHandler(a.pseudohsmListKeys))
		m.Handle("/delete-key", jsonHandler(a.pseudohsmDeleteKey))
		m.Handle("/reset-key-password", jsonHandler(a.pseudohsmResetPassword))
		m.Handle("/sign-message", jsonHandler(a.signMessage))

		m.Handle("/build-transaction", jsonHandler(a.build))
		m.Handle("/sign-transaction", jsonHandler(a.pseudohsmSignTemplates))

		m.Handle("/get-transaction", jsonHandler(a.getTransaction))
		m.Handle("/list-transactions", jsonHandler(a.listTransactions))

		m.Handle("/list-balances", jsonHandler(a.listBalances))
		m.Handle("/list-unspent-outputs", jsonHandler(a.listUnspentOutputs))

		m.Handle("/backup-wallet", jsonHandler(a.backupWalletImage))
		m.Handle("/restore-wallet", jsonHandler(a.restoreWalletImage))
	} else {
		log.Warn("Please enable wallet")
	}

	m.Handle("/", alwaysError(errors.New("not Found")))
	m.Handle("/error", jsonHandler(a.walletError))

	m.Handle("/create-access-token", jsonHandler(a.createAccessToken))
	m.Handle("/list-access-tokens", jsonHandler(a.listAccessTokens))
	m.Handle("/delete-access-token", jsonHandler(a.deleteAccessToken))
	m.Handle("/check-access-token", jsonHandler(a.checkAccessToken))

	m.Handle("/create-transaction-feed", jsonHandler(a.createTxFeed))
	m.Handle("/get-transaction-feed", jsonHandler(a.getTxFeed))
	m.Handle("/update-transaction-feed", jsonHandler(a.updateTxFeed))
	m.Handle("/delete-transaction-feed", jsonHandler(a.deleteTxFeed))
	m.Handle("/list-transaction-feeds", jsonHandler(a.listTxFeeds))

	m.Handle("/submit-transaction", jsonHandler(a.submit))
	m.Handle("/estimate-transaction-gas", jsonHandler(a.estimateTxGas))

	m.Handle("/get-unconfirmed-transaction", jsonHandler(a.getUnconfirmedTx))
	m.Handle("/list-unconfirmed-transactions", jsonHandler(a.listUnconfirmedTxs))
	m.Handle("/decode-raw-transaction", jsonHandler(a.decodeRawTransaction))

	m.Handle("/get-block-hash", jsonHandler(a.getBestBlockHash))
	m.Handle("/get-block-header", jsonHandler(a.getBlockHeader))
	m.Handle("/get-block", jsonHandler(a.getBlock))
	m.Handle("/get-block-count", jsonHandler(a.getBlockCount))
	m.Handle("/get-difficulty", jsonHandler(a.getDifficulty))
	m.Handle("/get-hash-rate", jsonHandler(a.getHashRate))

	m.Handle("/is-mining", jsonHandler(a.isMining))
	m.Handle("/set-mining", jsonHandler(a.setMining))

	m.Handle("/get-work", jsonHandler(a.getWork))
	m.Handle("/get-work-json", jsonHandler(a.getWorkJSON))
	m.Handle("/submit-work", jsonHandler(a.submitWork))
	m.Handle("/submit-work-json", jsonHandler(a.submitWorkJSON))

	m.Handle("/verify-message", jsonHandler(a.verifyMessage))
	m.Handle("/decode-program", jsonHandler(a.decodeProgram))

	m.Handle("/gas-rate", jsonHandler(a.gasRate))
	m.Handle("/net-info", jsonHandler(a.getNetInfo))

	handler := latencyHandler(m, walletEnable)
	handler = maxBytesHandler(handler) // TODO(tessr): consider moving this to non-core specific mux
	handler = webAssetsHandler(handler)
	handler = gzip.Handler{Handler: handler}

	a.handler = handler
}

func maxBytesHandler(h http.Handler) http.Handler {
	const maxReqSize = 1e7 // 10MB
	return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		// A block can easily be bigger than maxReqSize, but everything
		// else should be pretty small.
		if req.URL.Path != crosscoreRPCPrefix+"signer/sign-block" {
			req.Body = http.MaxBytesReader(w, req.Body, maxReqSize)
		}
		h.ServeHTTP(w, req)
	})
}

// json Handler
func jsonHandler(f interface{}) http.Handler {
	h, err := httpjson.Handler(f, errorFormatter.Write)
	if err != nil {
		panic(err)
	}
	return h
}

// error Handler
func alwaysError(err error) http.Handler {
	return jsonHandler(func() error { return err })
}

func webAssetsHandler(next http.Handler) http.Handler {
	mux := http.NewServeMux()
	mux.Handle("/dashboard/", http.StripPrefix("/dashboard/", static.Handler{
		Assets:  dashboard.Files,
		Default: "index.html",
	}))
	mux.Handle("/", next)

	return mux
}

// AuthHandler access token auth Handler
func AuthHandler(handler http.Handler, accessTokens *accesstoken.CredentialStore) http.Handler {
	authenticator := authn.NewAPI(accessTokens)

	return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
		// TODO(tessr): check that this path exists; return early if this path isn't legit
		req, err := authenticator.Authenticate(req)
		if err != nil {
			log.WithField("error", errors.Wrap(err, "Serve")).Error("Authenticate fail")
			err = errors.Sub(errNotAuthenticated, err)
			errorFormatter.Write(req.Context(), rw, err)
			return
		}
		handler.ServeHTTP(rw, req)
	})
}

// RedirectHandler redirect to dashboard handler
func RedirectHandler(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		if req.URL.Path == "/" {
			http.Redirect(w, req, "/dashboard/", http.StatusFound)
			return
		}
		next.ServeHTTP(w, req)
	})
}

// latencyHandler take latency for the request url path, and redirect url path to wait-disable when wallet is closed
func latencyHandler(m *http.ServeMux, walletEnable bool) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		// latency for the request url path
		if l := latency(m, req); l != nil {
			defer l.RecordSince(time.Now())
		}

		// when the wallet is not been opened and the url path is not been found, modify url path to error,
		// and redirect handler to error
		if _, pattern := m.Handler(req); pattern != req.URL.Path && !walletEnable {
			req.URL.Path = "/error"
			walletRedirectHandler(w, req)
			return
		}

		m.ServeHTTP(w, req)
	})
}

// walletRedirectHandler redirect to error when the wallet is closed
func walletRedirectHandler(w http.ResponseWriter, req *http.Request) {
	h := http.RedirectHandler(req.URL.String(), http.StatusMovedPermanently)
	h.ServeHTTP(w, req)
}
<|endoftext|>


<commit_before>package api

/*
	This file is all serializable types used in Rio
	to define filesets, WareIDs, packing systems, and storage locations.
*/

import (
	"fmt"
	"strings"
	"time"

	"github.com/polydawn/refmt/obj/atlas"
)

/*
	Ware IDs are content-addressable, cryptographic hashes which uniquely identify
	a "ware" -- a packed filesystem snapshot.
	A ware contains one or more files and directories, and metadata for each.

	Ware IDs are serialized as a string in two parts, separated by a colon --
	for example like "git:f23ae1829" or "tar:WJL8or32vD".
	The first part communicates which kind of packing system computed the hash,
	and the second part is the hash itself.
*/
type WareID struct {
	Type string
	Hash string
}

func ParseWareID(x string) (WareID, error) {
	ss := strings.SplitN(x, ":", 2)
	if len(ss) < 2 {
		return WareID{}, fmt.Errorf("wareIDs must have contain a colon character (they are of form \"<type>:<hash>\")")
	}
	return WareID{ss[0], ss[1]}, nil
}

func (x WareID) String() string {
	return x.Type + ":" + x.Hash
}

var WareID_AtlasEntry = atlas.BuildEntry(WareID{}).Transform().
	TransformMarshal(atlas.MakeMarshalTransformFunc(
		func(x WareID) (string, error) {
			return x.String(), nil
		})).
	TransformUnmarshal(atlas.MakeUnmarshalTransformFunc(
		func(x string) (WareID, error) {
			return ParseWareID(x)
		})).
	Complete()

type AbsPath string // Identifier for output slots.  Coincidentally, a path.

type (
	/*
		WarehouseAddr strings describe a protocol and dial address for talking to
		a storage warehouse.

		The serial format is an opaque string, though they typically resemble
		(and for internal use, are parsed as) URLs.
	*/
	WarehouseAddr string

	/*
		Configuration details for a warehouse.

		Many warehouses don't *need* any configuration; the addr string
		can tell the whole story.  But if you need auth or other fanciness,
		here's the place to specify it.
	*/
	WarehouseCfg struct {
		Auth     string      // auth info, if needed.  usually points to another file.
		Addr     interface{} // additional addr info, for protocols that require it.
		Priority int         // higher is checked first.
	}

	/*
		A suite of warehouses.  A transmat can take the entire set,
		and will select the ones it knows how to use, sort them,
		ping each in parallel, and start fetching from the most preferred
		one (or, from several, if it's a really smart protocol like that).
	*/
	WorkspaceWarehouseCfg map[WarehouseAddr]WarehouseCfg
)

/*
	FilesetFilters define how certain filesystem metadata should be treated
	when packing or unpacking files.

		For each value:
		  If set: use that number;
		    default for pack is to flatten;
		    default for unpack is to respect packed metadata.
		  To keep during pack: set the keep bool.
		If keep is true, the value must be nil or the filter is invalid.
*/
type FilesetFilters struct {
	FlattenUID struct {
		Keep  bool    `json:"keep,omitempty"`
		Value *uint32 `json:"value,omitempty"`
	} `json:"uid"`
	FlattenGID struct {
		Keep  bool    `json:"keep,omitempty"`
		Value *uint32 `json:"value,omitempty"`
	} `json:"gid"`
	FlattenMtime struct {
		Keep  bool       `json:"keep,omitempty"`
		Value *time.Time `json:"value,omitempty"`
	} `json:"mtime"`
}

/*
	For workspace configuration: warehouses may involve auth, so
	we definitely don't want them to require redundant declaration:

		{
			"warehouseName": {
				"auth": {...},
				"addr": "...",
				"prio": "9000", // ??
			},
		}

	Should the warehouse name in that doc simply *be* the addr?  Probably.
	(Although in some cases things may be more complicated.
	For example, for IPFS, that might be bootstrap node addr *list*;
	the warehouseName would still probably want to carry a semantic though:
	it would likely bear a prefix path, or IPNS key, or something.)

	For hitch: we don't generally want to provide auth, nor would
	prioritization overrides be sane, so, there, the warehouse name and
	addr info most definitely default to being one and the same.

	The nature of things as strings alone for hitch would seem to indicate
	that that string should be the join column for everyone:
	repeatr formulas with discovery hint sections should use that string as well.

	For formulas: ahhhhhh.  Here things get truly strange.

	For formulas, you may want to give a warehouse name hint per input path.
	Or, per hash.  (That's roughly the same thing, unless the same hash shows
	up multiple times, which is a rare coincidence we can effectively ignore.)
	Or, per import name.

	Formulas also need the warehouse names *again* for *saving outputs*.
	This is often simpler, because the most sensible user story is to upload things
	to one(!) storage (usually, a local disk...!), and mirror thereafter.
	But what if you have two outputs, one more secret than the other?
	Or two outputs, one of which is simply a totally different pack type
	(perhaps you have a trans-pack-mirror-fanout step -- hah!)?
	Sensible defaults will carry us a long way for most of the time, but
	some defined way to deal with the interesting cases is also needed.

	For formula outputs, then, we have learned something: they *definitely* need
	to be able to specify a warehouseName per path.
	There simply isn't anything else to go by!  Tags don't belong in
	formulas anymore; that's a thing we definitely know now in this
	brave new world with hitch and the clear separation of planners and execution.

	What else do we know about the story for formula inputs?

	  - increasingly, formulas are being treated as halfway to assembly: they're allowed to be a tad uggo.
	  - warehouse info still *absolutely* must go in a top level "discovery" section and be joinable, *not* in inputs.
	  - i guess it coudl be in the imports but that seems wrong, and uncongruent with how outputs could never be.
	  - imports is also a bad idea simply because repeatr itself is still not supposed to *see* imports, really.  merely be tolerant of them being in the same doc as the (rest?  no, *real*) formula.
	  - paths are a red herring, though.  aren't they?  this should be more like "have hash; inquire with db/daemons where to get it"

	But that's a SIGINT leak.

	Let's focus back to SIGINT, then, as a success heuristic.

	In short: that means some sort of naming layer needs to be the root of deciding
	what lookup attempts to make.  (The fact it may give us a chance to save ourself
	from the shittiness of the git protocol's ability to advertise available objects
	is merely "coincidental" (though admittedly also critical).)

	Addntl user story: usually, things come in *pools*.
	There's the public mirror pool of popular upstream stuff.
	There's probably going to be a rix objects pool.  Just makes sense, for budgetary reasons.
	There's probably going to be a *private* pool at your office.
	Git repos each act like their own pools and they're really shits for this.
	And you might have a *private* pool at home for your side projects or consultancy gigs or whatever.

	There's an interesting dichotomy here:
	with *public* objects, you don't really mind over-asking about them;
	with *private* objects (or git), you're quite worried about over-asking.
	(N.b. Even if you mind your SIGINT so much that you don't want to ask your $dayjob
	storage pools at phpcompany about your nodejs asset needs, you solve that by just
	*not listing* that private storage pool in that workspace.  You still have workspaces.)

	(There's also the outlier of non-CAS / single-ware warehouses.  Those are odd enough
	that they can have their own entire dang schema section; we can disregard them here.)

	So maybe this gives us leverage on the safe defaults thing.
	We just ask *all* your pools.
	Unless it's got a specific one listed.

	Voilà;
	you can give explicits for git, so we can literally even;
	you can say nothing at all by default, and get sensible results sans fuss;
	and if you're a company that's paranoid af, you can either generate quite verbose
	override-ridden documents, or, come to think of it, just *don't have* public pools
	in the first place (mirroring is easy by design, after all, so why not?).

*/

/*
	Ok but so how do these all get *stacked up*?

	User story: I have a git repo.  I ran a tool some time ago to 99% pin a formula
	(except for the repo hash, ofc).  That tool did also leave import records.

	- I have my default public workspace stuff, and my default workspace-local warehouse too.
	   These probably pick up 99% of my needs.
	- given the import records and a hitch db, I can select the hitch db's recommended warehouseAddrs.
	- given the hitch db at large, I could actually brute force search for the hash and any warehouses.  (but this would be... unusual to need to do)
	- I probably *do* in fact want to do one of these two things, because they have more present-tense info than when my formula would've been pinned.
	   (remember, gitmodules files suck: checking out an old version and discovering a repo url moved is real bad experience.)
	- My tool could have pinned recommended warehouseAddrs too.  this would help people who can't be arsed to have a hitch db (which is supposed to be a supported, if somewhat odd, way to live).
	   (we should probably do this anyway.  despite the "gitmodules suck" admonishment... *we know what fallback lists* are, so we can do this without downside.)

	So, the effective config gathers all of these things:
	the workspace-wide config entries at whatever priorities you assigned them,
	the hitchdb chips in its current opinions if it and an imports list are present,
	and a warehouseaddr list per input may *also* have been generated by the pin tool.

	Oh, on top of all this we should support rewrite rules at the workspace level.

	So repeatr's CLI, interestingly, needs to pick up on *all* of these.
	Yes indeedy, that may mean having *repeatr invoke hitch*.  (Didn't see that one coming.)
	r2k8s will need it as well (those discovery-config-discover..er... funcs will be
	pretty much identical for the two of them).
*/

/*
	Hitch dealing with this:

	  { "name": "v10.0",
	    "items": {
	      "bin": "tar:woeifjwef",
	      "src": "git:aergoiaj",
	    },
	    "mirrors": {
	      "*": [
	        "https+ca://public.repeatr.io/",
	      ],
	      "src": [
	        "git+https://github.com/asdf/akaka"
	      ],
	    }
	  }

	Note the several points this has to cover:

	  - the presense of multiple items means I might need specific warehouses for
	     some of them *individually*.
	  - we are definitely going to want git things to be items... or replays will
	     often end in sadness: git *always* needs the equiv of "private" pool treatment,
	     and when hitch saves formulas in replay info, it's sans specific warehouses,
	     so you'd better dang well be able to reconstruct it from the imports.

	Also ugh did you notice git can take https transports?  That's really going to
	complicated the "A transmat can take the entire set, and will select the ones
	it knows how to use" rule.

	This mirroring list may belong per catalog rather than per release.
	I'd imagine it's almost always going to be a constant for most of the items
	(a single CA warehouse, never changing), the src git repos also tend not to change,
	and... that's about it, except for single-ware warehouses, which... i think still
	fall into our earlier dismisall of needing their own entire goddamn schema section.
*/
<commit_msg>api: drop glut of notes.<commit_after>package api

/*
	This file is all serializable types used in Rio
	to define filesets, WareIDs, packing systems, and storage locations.
*/

import (
	"fmt"
	"strings"
	"time"

	"github.com/polydawn/refmt/obj/atlas"
)

/*
	Ware IDs are content-addressable, cryptographic hashes which uniquely identify
	a "ware" -- a packed filesystem snapshot.
	A ware contains one or more files and directories, and metadata for each.

	Ware IDs are serialized as a string in two parts, separated by a colon --
	for example like "git:f23ae1829" or "tar:WJL8or32vD".
	The first part communicates which kind of packing system computed the hash,
	and the second part is the hash itself.
*/
type WareID struct {
	Type string
	Hash string
}

func ParseWareID(x string) (WareID, error) {
	ss := strings.SplitN(x, ":", 2)
	if len(ss) < 2 {
		return WareID{}, fmt.Errorf("wareIDs must have contain a colon character (they are of form \"<type>:<hash>\")")
	}
	return WareID{ss[0], ss[1]}, nil
}

func (x WareID) String() string {
	return x.Type + ":" + x.Hash
}

var WareID_AtlasEntry = atlas.BuildEntry(WareID{}).Transform().
	TransformMarshal(atlas.MakeMarshalTransformFunc(
		func(x WareID) (string, error) {
			return x.String(), nil
		})).
	TransformUnmarshal(atlas.MakeUnmarshalTransformFunc(
		func(x string) (WareID, error) {
			return ParseWareID(x)
		})).
	Complete()

type AbsPath string // Identifier for output slots.  Coincidentally, a path.

type (
	/*
		WarehouseAddr strings describe a protocol and dial address for talking to
		a storage warehouse.

		The serial format is an opaque string, though they typically resemble
		(and for internal use, are parsed as) URLs.
	*/
	WarehouseAddr string

	/*
		Configuration details for a warehouse.

		Many warehouses don't *need* any configuration; the addr string
		can tell the whole story.  But if you need auth or other fanciness,
		here's the place to specify it.
	*/
	WarehouseCfg struct {
		Auth     string      // auth info, if needed.  usually points to another file.
		Addr     interface{} // additional addr info, for protocols that require it.
		Priority int         // higher is checked first.
	}

	/*
		A suite of warehouses.  A transmat can take the entire set,
		and will select the ones it knows how to use, sort them,
		ping each in parallel, and start fetching from the most preferred
		one (or, from several, if it's a really smart protocol like that).
	*/
	WorkspaceWarehouseCfg map[WarehouseAddr]WarehouseCfg
)

/*
	FilesetFilters define how certain filesystem metadata should be treated
	when packing or unpacking files.

		For each value:
		  If set: use that number;
		    default for pack is to flatten;
		    default for unpack is to respect packed metadata.
		  To keep during pack: set the keep bool.
		If keep is true, the value must be nil or the filter is invalid.
*/
type FilesetFilters struct {
	FlattenUID struct {
		Keep  bool    `json:"keep,omitempty"`
		Value *uint32 `json:"value,omitempty"`
	} `json:"uid"`
	FlattenGID struct {
		Keep  bool    `json:"keep,omitempty"`
		Value *uint32 `json:"value,omitempty"`
	} `json:"gid"`
	FlattenMtime struct {
		Keep  bool       `json:"keep,omitempty"`
		Value *time.Time `json:"value,omitempty"`
	} `json:"mtime"`
}
<|endoftext|>


<commit_before>package gamadues

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

//APICreds -  The datastructure to hold the API key
type APICreds struct {
	Key string
}

//Gamadeus - The main datastructure holding a pointer to APICreds
type Gamadeus struct {
	Version        string
	APIKey         *APICreds
	SandboxVersion string
	PrimaryURL     string
}

const version = "0.1"
const sandboxversion = "v1.2"
const baseurl = "http://api.sandbox.amadeus.com/"

//NewClient - Obtain a new client pointer to the API
func NewClient(key string) *Gamadeus {
	apiCred := APICreds{key}
	return &Gamadeus{version, &apiCred, sandboxversion, baseurl + sandboxversion}
}

//GetVersion - Get the version information of this library
func (gm *Gamadeus) GetVersion() string {
	return gm.Version
}

func (gm *Gamadeus) makeRequestGet(endPoint string, returnData interface{}) error {
	//This is a test comment. Thanks
	//Testing for the github webhook.
	callURL := gm.PrimaryURL + "/" + endPoint + "&apikey=" + gm.APIKey.Key
	//fmt.Println(callURL)
	res, err := http.Get(callURL)
	defer res.Body.Close()
	if err != nil {
		return err
	}
	jsonDataFromHTTP, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return err
	}
	err = json.Unmarshal([]byte(jsonDataFromHTTP), &returnData)
	if err != nil {
		return err
	}
	if res.StatusCode != 200 {
		return errors.New("Non OK response received")
	}
	return nil
}
<commit_msg>Testing<commit_after>package gamadues

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

//APICreds -  The datastructure to hold the API key
type APICreds struct {
	Key string
}

//Gamadeus - The main datastructure holding a pointer to APICreds
type Gamadeus struct {
	Version        string
	APIKey         *APICreds
	SandboxVersion string
	PrimaryURL     string
}

const version = "0.1"
const sandboxversion = "v1.2"
const baseurl = "http://api.sandbox.amadeus.com/"

//NewClient - Obtain a new client pointer to the API
func NewClient(key string) *Gamadeus {
	apiCred := APICreds{key}
	return &Gamadeus{version, &apiCred, sandboxversion, baseurl + sandboxversion}
}

//GetVersion - Get the version information of this library
func (gm *Gamadeus) GetVersion() string {
	return gm.Version
}

func (gm *Gamadeus) makeRequestGet(endPoint string, returnData interface{}) error {
	//This is a test comment. Thanks
	//Testing for the github webhook. testing
	callURL := gm.PrimaryURL + "/" + endPoint + "&apikey=" + gm.APIKey.Key
	//fmt.Println(callURL)
	res, err := http.Get(callURL)
	defer res.Body.Close()
	if err != nil {
		return err
	}
	jsonDataFromHTTP, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return err
	}
	err = json.Unmarshal([]byte(jsonDataFromHTTP), &returnData)
	if err != nil {
		return err
	}
	if res.StatusCode != 200 {
		return errors.New("Non OK response received")
	}
	return nil
}
<|endoftext|>


<commit_before>package gamadues

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

//APICreds -  The datastructure to hold the API key
type APICreds struct {
	Key string
}

//Gamadeus - The main datastructure holding a pointer to APICreds
type Gamadeus struct {
	Version        string
	APIKey         *APICreds
	SandboxVersion string
	PrimaryURL     string
}

const version = "0.1"
const sandboxversion = "v1.2"
const baseurl = "http://api.sandbox.amadeus.com/"

//NewClient - Obtain a new client pointer to the API
func NewClient(key string) *Gamadeus {
	apiCred := APICreds{key}
	return &Gamadeus{version, &apiCred, sandboxversion, baseurl + sandboxversion}
}

//GetVersion - Get the version information of this library
func (gm *Gamadeus) GetVersion() string {
	return gm.Version
}

func (gm *Gamadeus) makeRequestGet(endPoint string, returnData interface{}) error {
	//This is a test comment. Thanks
	//Testing for the github webhook
	callURL := gm.PrimaryURL + "/" + endPoint + "&apikey=" + gm.APIKey.Key
	//fmt.Println(callURL)
	res, err := http.Get(callURL)
	defer res.Body.Close()
	if err != nil {
		return err
	}
	jsonDataFromHTTP, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return err
	}
	err = json.Unmarshal([]byte(jsonDataFromHTTP), &returnData)
	if err != nil {
		return err
	}
	if res.StatusCode != 200 {
		return errors.New("Non OK response received")
	}
	return nil
}
<commit_msg>testing<commit_after>package gamadues

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

//APICreds -  The datastructure to hold the API key
type APICreds struct {
	Key string
}

//Gamadeus - The main datastructure holding a pointer to APICreds
type Gamadeus struct {
	Version        string
	APIKey         *APICreds
	SandboxVersion string
	PrimaryURL     string
}

const version = "0.1"
const sandboxversion = "v1.2"
const baseurl = "http://api.sandbox.amadeus.com/"

//NewClient - Obtain a new client pointer to the API
func NewClient(key string) *Gamadeus {
	apiCred := APICreds{key}
	return &Gamadeus{version, &apiCred, sandboxversion, baseurl + sandboxversion}
}

//GetVersion - Get the version information of this library
func (gm *Gamadeus) GetVersion() string {
	return gm.Version
}

func (gm *Gamadeus) makeRequestGet(endPoint string, returnData interface{}) error {
	//This is a test comment. Thanks
	//Testing for the github web
	callURL := gm.PrimaryURL + "/" + endPoint + "&apikey=" + gm.APIKey.Key
	//fmt.Println(callURL)
	res, err := http.Get(callURL)
	defer res.Body.Close()
	if err != nil {
		return err
	}
	jsonDataFromHTTP, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return err
	}
	err = json.Unmarshal([]byte(jsonDataFromHTTP), &returnData)
	if err != nil {
		return err
	}
	if res.StatusCode != 200 {
		return errors.New("Non OK response received")
	}
	return nil
}
<|endoftext|>


<commit_before>package browse

import (
	"net/http"
	"text/template"

	"github.com/hacdias/caddy-hugo/tools/templates"
	"github.com/hacdias/caddy-hugo/tools/variables"
	"github.com/mholt/caddy/middleware"
	"github.com/mholt/caddy/middleware/browse"
)

// GET handles the GET method on browse page and shows the files listing Using
// the Browse Caddy middleware.
func GET(w http.ResponseWriter, r *http.Request) (int, error) {
	functions := template.FuncMap{
		"CanBeEdited": templates.CanBeEdited,
		"Defined":     variables.Defined,
	}

	tpl, err := templates.Get(r, functions, "browse")

	if err != nil {
		return http.StatusInternalServerError, err
	}

	// Using Caddy's Browse middleware
	b := browse.Browse{
		Next: middleware.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) {
			return 404, nil
		}),
		Configs: []browse.Config{
			{
				PathScope: "/",
				Root: conf.Path,
				Variables: conf,
				Template:  tpl,
			},
		},
		IgnoreIndexes: true,
	}

	return b.ServeHTTP(w, r)
}
<commit_msg>update for #67<commit_after>package browse

import (
	"net/http"
	"text/template"

	"github.com/hacdias/caddy-hugo/tools/templates"
	"github.com/hacdias/caddy-hugo/tools/variables"
	"github.com/mholt/caddy/middleware"
	"github.com/mholt/caddy/middleware/browse"
)

// GET handles the GET method on browse page and shows the files listing Using
// the Browse Caddy middleware.
func GET(w http.ResponseWriter, r *http.Request) (int, error) {
	functions := template.FuncMap{
		"CanBeEdited": templates.CanBeEdited,
		"Defined":     variables.Defined,
	}

	tpl, err := templates.Get(r, functions, "browse")

	if err != nil {
		return http.StatusInternalServerError, err
	}

	// Using Caddy's Browse middleware
	b := browse.Browse{
		Next: middleware.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) {
			return 404, nil
		}),
		Configs: []browse.Config{
			{
				PathScope: "/",
				Root: http.Dir(conf.Path),
				Variables: conf,
				Template:  tpl,
			},
		},
		IgnoreIndexes: true,
	}

	return b.ServeHTTP(w, r)
}
<|endoftext|>


<commit_before>package middleware

import (
	"github.com/gin-gonic/gin"
	"strings"

	u "github.com/techjanitor/pram-get/utils"
)

// REDIS KEY STRUCTURE
//
// HASH:
// "thread:1:45"
// "post:1:45"
// "tag:1:4"
// "index:1"
// "image:1"
//
// REGULAR:
// "directory:1"
// "tags:1"
// "tagtypes"
// "pram"
// "taginfo:21"

// Cache will check for the key in Redis and serve it. If not found, it will
// take the marshalled JSON from the controller and set it in Redis
func Cache() gin.HandlerFunc {
	return func(c *gin.Context) {
		var result []byte
		var err error

		// Get request path
		path := c.Request.URL.Path

		// Trim leading / from path and split
		params := strings.Split(strings.Trim(path, "/"), "/")

		// Make key from path
		key := redisKey{}
		key.expireKey(params[0])
		key.generateKey(params...)

		// if the request was cached or not
		var cached bool

		// Initialize cache handle
		cache := u.RedisCache

		if key.Hash {
			// Check to see if there is already a key we can serve
			result, err = cache.HGet(key.Key, key.Field)
			if err == u.ErrCacheMiss {
				c.Next()

				// Check if there was an error from the controller
				controllerError, _ := c.Get("controllerError")
				if controllerError != nil {
					c.Abort()
					return
				}

				// Get data from controller
				data := c.MustGet("data").([]byte)

				// Set output to cache
				err = cache.HMSet(key.Key, key.Field, data)
				if err != nil {
					c.Error(err)
					c.Abort()
					return
				}

			} else if err != nil {
				c.Error(err)
				c.Abort()
				return
			}

		}

		if !key.Hash {
			// Check to see if there is already a key we can serve
			result, err = cache.Get(key.Key)
			if err == u.ErrCacheMiss {
				c.Next()

				// Check if there was an error from the controller
				controllerError, _ := c.Get("controllerError")
				if controllerError != nil {
					c.Abort()
					return
				}

				// Get data from controller
				data := c.MustGet("data").([]byte)

				if key.Expire {

					// Set output to cache
					err = cache.SetEx(key.Key, 60, data)
					if err != nil {
						c.Error(err)
						c.Abort()
						return
					}

				} else {

					// Set output to cache
					err = cache.Set(key.Key, data)
					if err != nil {
						c.Error(err)
						c.Abort()
						return
					}

				}

			} else if err != nil {
				c.Error(err)
				c.Abort()
				return
			}

		}

		// Hand off bool to analytics middleware
		c.Set("cached", cached)

		c.Writer.Header().Set("Content-Type", "application/json")
		c.Writer.Write(result)
		c.Abort()

	}

}

type redisKey struct {
	Key    string
	Field  string
	Hash   bool
	Expire bool
}

// Will take the params from the request and turn them into a key
func (r *redisKey) generateKey(params ...string) {

	var keys []string

	switch {
	// keys like pram, directory, and tags
	case len(params) <= 2:
		r.Key = strings.Join(params, ":")
	// index and image
	case len(params) == 3:
		keys = append(keys, params[0], params[1])
		r.Field = params[2]
		r.Hash = true
		r.Key = strings.Join(keys, ":")
	// thread, post, and tag
	case len(params) == 4:
		keys = append(keys, params[0], params[1], params[2])
		r.Field = params[3]
		r.Hash = true
		r.Key = strings.Join(keys, ":")
	}

	return

}

// Check if key should be expired
func (r *redisKey) expireKey(key string) {

	keyList := map[string]bool{
		"pram": true,
	}

	if keyList[strings.ToLower(key)] {
		r.Expire = true
	}

	return

}
<commit_msg>track item kv in analytics<commit_after>package middleware

import (
	"github.com/gin-gonic/gin"
	"strings"

	u "github.com/techjanitor/pram-get/utils"
)

// REDIS KEY STRUCTURE
//
// HASH:
// "thread:1:45"
// "post:1:45"
// "tag:1:4"
// "index:1"
// "image:1"
//
// REGULAR:
// "directory:1"
// "tags:1"
// "tagtypes"
// "pram"
// "taginfo:21"

// Cache will check for the key in Redis and serve it. If not found, it will
// take the marshalled JSON from the controller and set it in Redis
func Cache() gin.HandlerFunc {
	return func(c *gin.Context) {
		var result []byte
		var err error

		// Get request path
		path := c.Request.URL.Path

		// Trim leading / from path and split
		params := strings.Split(strings.Trim(path, "/"), "/")

		// Make key from path
		key := redisKey{}
		key.expireKey(params[0])
		key.generateKey(params...)

		// if the request was cached or not
		cached := true

		// Initialize cache handle
		cache := u.RedisCache

		if key.Hash {
			// Check to see if there is already a key we can serve
			result, err = cache.HGet(key.Key, key.Field)
			if err == u.ErrCacheMiss {
				// set cached to false
				cached = false

				c.Next()

				// Check if there was an error from the controller
				controllerError, _ := c.Get("controllerError")
				if controllerError != nil {
					c.Abort()
					return
				}

				// Get data from controller
				data := c.MustGet("data").([]byte)

				// Set output to cache
				err = cache.HMSet(key.Key, key.Field, data)
				if err != nil {
					c.Error(err)
					c.Abort()
					return
				}

			} else if err != nil {
				c.Error(err)
				c.Abort()
				return
			}

		}

		if !key.Hash {
			// Check to see if there is already a key we can serve
			result, err = cache.Get(key.Key)
			if err == u.ErrCacheMiss {
				// set cached to false
				cached = false
				c.Next()

				// Check if there was an error from the controller
				controllerError, _ := c.Get("controllerError")
				if controllerError != nil {
					c.Abort()
					return
				}

				// Get data from controller
				data := c.MustGet("data").([]byte)

				if key.Expire {

					// Set output to cache
					err = cache.SetEx(key.Key, 60, data)
					if err != nil {
						c.Error(err)
						c.Abort()
						return
					}

				} else {

					// Set output to cache
					err = cache.Set(key.Key, data)
					if err != nil {
						c.Error(err)
						c.Abort()
						return
					}

				}

			} else if err != nil {
				c.Error(err)
				c.Abort()
				return
			}

		}

		// Hand off bool to analytics middleware
		c.Set("cached", cached)

		c.Writer.Header().Set("Content-Type", "application/json")
		c.Writer.Write(result)
		c.Abort()

	}

}

type redisKey struct {
	Key    string
	Field  string
	Hash   bool
	Expire bool
}

// Will take the params from the request and turn them into a key
func (r *redisKey) generateKey(params ...string) {

	var keys []string

	switch {
	// keys like pram, directory, and tags
	case len(params) <= 2:
		r.Key = strings.Join(params, ":")
	// index and image
	case len(params) == 3:
		keys = append(keys, params[0], params[1])
		r.Field = params[2]
		r.Hash = true
		r.Key = strings.Join(keys, ":")
	// thread, post, and tag
	case len(params) == 4:
		keys = append(keys, params[0], params[1], params[2])
		r.Field = params[3]
		r.Hash = true
		r.Key = strings.Join(keys, ":")
	}

	return

}

// Check if key should be expired
func (r *redisKey) expireKey(key string) {

	keyList := map[string]bool{
		"pram": true,
	}

	if keyList[strings.ToLower(key)] {
		r.Expire = true
	}

	return

}
<|endoftext|>