Unify handling and error handling of pages in public interface

pull/56/head
Martin Boehm 2018-08-24 16:17:43 +02:00
parent ad5ddbd029
commit 7d708ef868
5 changed files with 220 additions and 117 deletions

View File

@ -2,6 +2,22 @@ package api
import "math/big" import "math/big"
type ApiError struct {
Text string
Public bool
}
func (e *ApiError) Error() string {
return e.Text
}
func NewApiError(s string, public bool) error {
return &ApiError{
Text: s,
Public: public,
}
}
type ScriptSig struct { type ScriptSig struct {
Hex string `json:"hex"` Hex string `json:"hex"`
Asm string `json:"asm,omitempty"` Asm string `json:"asm,omitempty"`

View File

@ -4,10 +4,11 @@ import (
"blockbook/bchain" "blockbook/bchain"
"blockbook/common" "blockbook/common"
"blockbook/db" "blockbook/db"
"errors"
"math/big" "math/big"
"time"
"github.com/golang/glog" "github.com/golang/glog"
"github.com/juju/errors"
) )
// Worker is handle to api worker // Worker is handle to api worker
@ -35,13 +36,13 @@ func NewWorker(db *db.RocksDB, chain bchain.BlockChain, txCache *db.TxCache, is
func (w *Worker) GetTransaction(txid string, bestheight uint32, spendingTx bool) (*Tx, error) { func (w *Worker) GetTransaction(txid string, bestheight uint32, spendingTx bool) (*Tx, error) {
bchainTx, height, err := w.txCache.GetTransaction(txid, bestheight) bchainTx, height, err := w.txCache.GetTransaction(txid, bestheight)
if err != nil { if err != nil {
return nil, err return nil, errors.Annotatef(err, "txCache.GetTransaction %v", txid)
} }
var blockhash string var blockhash string
if bchainTx.Confirmations > 0 { if bchainTx.Confirmations > 0 {
blockhash, err = w.db.GetBlockHash(height) blockhash, err = w.db.GetBlockHash(height)
if err != nil { if err != nil {
return nil, err return nil, errors.Annotatef(err, "GetBlockHash %v", height)
} }
} }
var valInSat, valOutSat, feesSat big.Int var valInSat, valOutSat, feesSat big.Int
@ -57,7 +58,7 @@ func (w *Worker) GetTransaction(txid string, bestheight uint32, spendingTx bool)
if bchainVin.Txid != "" { if bchainVin.Txid != "" {
otx, _, err := w.txCache.GetTransaction(bchainVin.Txid, bestheight) otx, _, err := w.txCache.GetTransaction(bchainVin.Txid, bestheight)
if err != nil { if err != nil {
return nil, err return nil, errors.Annotatef(err, "txCache.GetTransaction %v", bchainVin.Txid)
} }
if len(otx.Vout) > int(vin.Vout) { if len(otx.Vout) > int(vin.Vout) {
vout := &otx.Vout[vin.Vout] vout := &otx.Vout[vin.Vout]
@ -220,28 +221,28 @@ func (w *Worker) txFromTxAddress(txid string, ta *db.TxAddresses, bi *db.BlockIn
} }
// GetAddress computes address value and gets transactions for given address // GetAddress computes address value and gets transactions for given address
func (w *Worker) GetAddress(address string, page int, txsOnPage int) (*Address, error) { func (w *Worker) GetAddress(address string, page int, txsOnPage int, onlyTxids bool) (*Address, error) {
glog.Info(address, " start") start := time.Now()
ba, err := w.db.GetAddressBalance(address) ba, err := w.db.GetAddressBalance(address)
if err != nil { if err != nil {
return nil, err return nil, errors.Annotatef(err, "GetAddressBalance %v", address)
} }
if ba == nil { if ba == nil {
return nil, errors.New("Address not found") return nil, NewApiError("Address not found", true)
} }
txc, err := w.getAddressTxids(address, false) txc, err := w.getAddressTxids(address, false)
txc = UniqueTxidsInReverse(txc)
if err != nil { if err != nil {
return nil, err return nil, errors.Annotatef(err, "getAddressTxids %v false", address)
} }
txc = UniqueTxidsInReverse(txc)
txm, err := w.getAddressTxids(address, true) txm, err := w.getAddressTxids(address, true)
if err != nil { if err != nil {
return nil, err return nil, errors.Annotatef(err, "getAddressTxids %v true", address)
} }
txm = UniqueTxidsInReverse(txm) txm = UniqueTxidsInReverse(txm)
bestheight, _, err := w.db.GetBestBlock() bestheight, _, err := w.db.GetBestBlock()
if err != nil { if err != nil {
return nil, err return nil, errors.Annotatef(err, "GetBestBlock")
} }
// paging // paging
if page < 0 { if page < 0 {
@ -268,7 +269,7 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int) (*Address,
tx, err := w.GetTransaction(tx, bestheight, false) tx, err := w.GetTransaction(tx, bestheight, false)
// mempool transaction may fail // mempool transaction may fail
if err != nil { if err != nil {
glog.Error("GetTransaction ", tx, ": ", err) glog.Error("GetTransaction in mempool ", tx, ": ", err)
} else { } else {
uBalSat.Sub(tx.getAddrVoutValue(address), tx.getAddrVinValue(address)) uBalSat.Sub(tx.getAddrVoutValue(address), tx.getAddrVinValue(address))
txs[txi] = tx txs[txi] = tx
@ -282,7 +283,7 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int) (*Address,
txid := txc[i] txid := txc[i]
ta, err := w.db.GetTxAddresses(txid) ta, err := w.db.GetTxAddresses(txid)
if err != nil { if err != nil {
return nil, err return nil, errors.Annotatef(err, "GetTxAddresses %v", txid)
} }
if ta == nil { if ta == nil {
glog.Warning("DB inconsistency: tx ", txid, ": not found in txAddresses") glog.Warning("DB inconsistency: tx ", txid, ": not found in txAddresses")
@ -290,7 +291,7 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int) (*Address,
} }
bi, err := w.db.GetBlockInfo(ta.Height) bi, err := w.db.GetBlockInfo(ta.Height)
if err != nil { if err != nil {
return nil, err return nil, errors.Annotatef(err, "GetBlockInfo %v", ta.Height)
} }
if bi == nil { if bi == nil {
glog.Warning("DB inconsistency: block height ", ta.Height, ": not found in db") glog.Warning("DB inconsistency: block height ", ta.Height, ": not found in db")
@ -312,7 +313,7 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int) (*Address,
TotalPages: totalPages, TotalPages: totalPages,
TxsOnPage: txsOnPage, TxsOnPage: txsOnPage,
} }
glog.Info(address, " finished") glog.Info(address, " finished in ", time.Since(start))
return r, nil return r, nil
} }

View File

@ -55,6 +55,8 @@ var (
syncWorkers = flag.Int("workers", 8, "number of workers to process blocks") syncWorkers = flag.Int("workers", 8, "number of workers to process blocks")
dryRun = flag.Bool("dryrun", false, "do not index blocks, only download") dryRun = flag.Bool("dryrun", false, "do not index blocks, only download")
debugMode = flag.Bool("debug", false, "debug mode, return more verbose errors, reload templates on each request")
internalBinding = flag.String("internal", "", "internal http server binding [address]:port, (default no internal server)") internalBinding = flag.String("internal", "", "internal http server binding [address]:port, (default no internal server)")
publicBinding = flag.String("public", "", "public http server binding [address]:port[/path], (default no public server)") publicBinding = flag.String("public", "", "public http server binding [address]:port[/path], (default no public server)")
@ -130,7 +132,7 @@ func main() {
chanOsSignal = make(chan os.Signal, 1) chanOsSignal = make(chan os.Signal, 1)
signal.Notify(chanOsSignal, syscall.SIGHUP, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGTERM) signal.Notify(chanOsSignal, syscall.SIGHUP, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGTERM)
glog.Infof("Blockbook: %+v", common.GetVersionInfo()) glog.Infof("Blockbook: %+v, debug mode %v", common.GetVersionInfo(), *debugMode)
if *prof != "" { if *prof != "" {
go func() { go func() {
@ -270,7 +272,7 @@ func main() {
var publicServer *server.PublicServer var publicServer *server.PublicServer
if *publicBinding != "" { if *publicBinding != "" {
publicServer, err = server.NewPublicServer(*publicBinding, *certFiles, index, chain, txCache, *explorerURL, metrics, internalState) publicServer, err = server.NewPublicServer(*publicBinding, *certFiles, index, chain, txCache, *explorerURL, metrics, internalState, *debugMode)
if err != nil { if err != nil {
glog.Error("socketio: ", err) glog.Error("socketio: ", err)
return return

View File

@ -10,6 +10,8 @@ import (
"fmt" "fmt"
"html/template" "html/template"
"net/http" "net/http"
"reflect"
"runtime"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@ -35,12 +37,12 @@ type PublicServer struct {
explorerURL string explorerURL string
metrics *common.Metrics metrics *common.Metrics
is *common.InternalState is *common.InternalState
txTpl *template.Template templates []*template.Template
addressTpl *template.Template debug bool
} }
// NewPublicServer creates new public server http interface to blockbook and returns its handle // NewPublicServer creates new public server http interface to blockbook and returns its handle
func NewPublicServer(binding string, certFiles string, db *db.RocksDB, chain bchain.BlockChain, txCache *db.TxCache, explorerURL string, metrics *common.Metrics, is *common.InternalState) (*PublicServer, error) { func NewPublicServer(binding string, certFiles string, db *db.RocksDB, chain bchain.BlockChain, txCache *db.TxCache, explorerURL string, metrics *common.Metrics, is *common.InternalState, debugMode bool) (*PublicServer, error) {
api, err := api.NewWorker(db, chain, txCache, is) api, err := api.NewWorker(db, chain, txCache, is)
if err != nil { if err != nil {
@ -72,55 +74,34 @@ func NewPublicServer(binding string, certFiles string, db *db.RocksDB, chain bch
explorerURL: explorerURL, explorerURL: explorerURL,
metrics: metrics, metrics: metrics,
is: is, is: is,
debug: debugMode,
} }
// favicon // favicon
serveMux.Handle(path+"favicon.ico", http.FileServer(http.Dir("./static/"))) serveMux.Handle(path+"favicon.ico", http.FileServer(http.Dir("./static/")))
// support for tests of socket.io interface // support for tests of socket.io interface
serveMux.Handle(path+"test.html", http.FileServer(http.Dir("./static/"))) serveMux.Handle(path+"test.html", http.FileServer(http.Dir("./static/")))
// redirect to Bitcore for details of transaction // redirect to wallet requests for tx and address, possibly to external site
serveMux.HandleFunc(path+"tx/", s.txRedirect) serveMux.HandleFunc(path+"tx/", s.txRedirect)
serveMux.HandleFunc(path+"address/", s.addressRedirect) serveMux.HandleFunc(path+"address/", s.addressRedirect)
// explorer // explorer
serveMux.HandleFunc(path+"explorer/tx/", s.explorerTx) serveMux.HandleFunc(path+"explorer/tx/", s.htmlTemplateHandler(s.explorerTx))
serveMux.HandleFunc(path+"explorer/address/", s.explorerAddress) serveMux.HandleFunc(path+"explorer/address/", s.htmlTemplateHandler(s.explorerAddress))
serveMux.Handle(path+"static/", http.StripPrefix("/static/", http.FileServer(http.Dir("./static/")))) serveMux.Handle(path+"static/", http.StripPrefix("/static/", http.FileServer(http.Dir("./static/"))))
// API calls // API calls
serveMux.HandleFunc(path+"api/block-index/", s.apiBlockIndex) serveMux.HandleFunc(path+"api/block-index/", s.jsonHandler(s.apiBlockIndex))
serveMux.HandleFunc(path+"api/tx/", s.apiTx) serveMux.HandleFunc(path+"api/tx/", s.jsonHandler(s.apiTx))
serveMux.HandleFunc(path+"api/address/", s.apiAddress) serveMux.HandleFunc(path+"api/address/", s.jsonHandler(s.apiAddress))
// handle socket.io // handle socket.io
serveMux.Handle(path+"socket.io/", socketio.GetHandler()) serveMux.Handle(path+"socket.io/", socketio.GetHandler())
// default handler // default handler
serveMux.HandleFunc(path, s.index) serveMux.HandleFunc(path, s.index)
s.txTpl, s.addressTpl = parseTemplates() s.templates = parseTemplates()
return s, nil return s, nil
} }
func parseTemplates() (txTpl, addressTpl *template.Template) {
templateFuncMap := template.FuncMap{
"formatUnixTime": formatUnixTime,
"formatAmount": formatAmount,
"setTxToTemplateData": setTxToTemplateData,
"stringInSlice": stringInSlice,
}
txTpl = template.Must(template.New("tx").Funcs(templateFuncMap).ParseFiles("./static/templates/tx.html", "./static/templates/txdetail.html", "./static/templates/base.html"))
addressTpl = template.Must(template.New("address").Funcs(templateFuncMap).ParseFiles("./static/templates/address.html", "./static/templates/txdetail.html", "./static/templates/base.html"))
return
}
func formatUnixTime(ut int64) string {
return time.Unix(ut, 0).Format(time.RFC1123)
}
// for now return the string as it is
// in future could be used to do coin specific formatting
func formatAmount(a string) string {
return a
}
// Run starts the server // Run starts the server
func (s *PublicServer) Run() error { func (s *PublicServer) Run() error {
if s.certFiles == "" { if s.certFiles == "" {
@ -153,6 +134,20 @@ func (s *PublicServer) OnNewTxAddr(txid string, addr string) {
s.socketio.OnNewTxAddr(txid, addr) s.socketio.OnNewTxAddr(txid, addr)
} }
func (s *PublicServer) txRedirect(w http.ResponseWriter, r *http.Request) {
if s.explorerURL != "" {
http.Redirect(w, r, joinURL(s.explorerURL, r.URL.Path), 302)
s.metrics.ExplorerViews.With(common.Labels{"action": "tx"}).Inc()
}
}
func (s *PublicServer) addressRedirect(w http.ResponseWriter, r *http.Request) {
if s.explorerURL != "" {
http.Redirect(w, r, joinURL(s.explorerURL, r.URL.Path), 302)
s.metrics.ExplorerViews.With(common.Labels{"action": "address"}).Inc()
}
}
func splitBinding(binding string) (addr string, path string) { func splitBinding(binding string) (addr string, path string) {
i := strings.Index(binding, "/") i := strings.Index(binding, "/")
if i >= 0 { if i >= 0 {
@ -171,34 +166,154 @@ func joinURL(base string, part string) string {
return part return part
} }
func (s *PublicServer) txRedirect(w http.ResponseWriter, r *http.Request) { func getFunctionName(i interface{}) string {
if s.explorerURL != "" { return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()
http.Redirect(w, r, joinURL(s.explorerURL, r.URL.Path), 302) }
s.metrics.ExplorerViews.With(common.Labels{"action": "tx"}).Inc()
func (s *PublicServer) jsonHandler(handler func(r *http.Request) (interface{}, error)) func(w http.ResponseWriter, r *http.Request) {
type jsonError struct {
Error string `json:"error"`
}
return func(w http.ResponseWriter, r *http.Request) {
var data interface{}
var err error
defer func() {
if e := recover(); e != nil {
glog.Error(getFunctionName(handler), " recovered from panic: ", e)
if s.debug {
data = jsonError{fmt.Sprint("Internal server error: recovered from panic ", e)}
} else {
data = jsonError{"Internal server error"}
}
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
json.NewEncoder(w).Encode(data)
}()
data, err = handler(r)
if err != nil || data == nil {
if apiErr, ok := err.(*api.ApiError); ok {
data = jsonError{apiErr.Error()}
} else {
if err != nil {
glog.Error(getFunctionName(handler), " error: ", err)
}
if s.debug {
data = jsonError{fmt.Sprintf("Internal server error: %v, data %+v", err, data)}
} else {
data = jsonError{"Internal server error"}
}
}
}
} }
} }
func (s *PublicServer) addressRedirect(w http.ResponseWriter, r *http.Request) { func (s *PublicServer) newTemplateData() *TemplateData {
if s.explorerURL != "" { return &TemplateData{
http.Redirect(w, r, joinURL(s.explorerURL, r.URL.Path), 302) CoinName: s.is.Coin,
s.metrics.ExplorerViews.With(common.Labels{"action": "address"}).Inc() CoinShortcut: s.is.CoinShortcut,
} }
} }
func (s *PublicServer) newTemplateDataWithError(text string) *TemplateData {
return &TemplateData{
CoinName: s.is.Coin,
CoinShortcut: s.is.CoinShortcut,
Error: &api.ApiError{Text: text},
}
}
func (s *PublicServer) htmlTemplateHandler(handler func(r *http.Request) (tpl, *TemplateData, error)) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
var t tpl
var data *TemplateData
var err error
defer func() {
if e := recover(); e != nil {
glog.Error(getFunctionName(handler), " recovered from panic: ", e)
t = errorTpl
if s.debug {
data = s.newTemplateDataWithError(fmt.Sprint("Internal server error: recovered from panic ", e))
} else {
data = s.newTemplateDataWithError("Internal server error")
}
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
if err := s.templates[t].ExecuteTemplate(w, "base.html", data); err != nil {
glog.Error(err)
}
}()
if s.debug {
// reload templates on each request
// to reflect changes during development
s.templates = parseTemplates()
}
t, data, err = handler(r)
if err != nil || data == nil {
t = errorTpl
if apiErr, ok := err.(*api.ApiError); ok {
data = s.newTemplateData()
data.Error = apiErr
} else {
if err != nil {
glog.Error(getFunctionName(handler), " error: ", err)
}
if s.debug {
data = s.newTemplateDataWithError(fmt.Sprintf("Internal server error: %v, data %+v", err, data))
} else {
data = s.newTemplateDataWithError("Internal server error")
}
}
}
}
}
type tpl int
const (
errorTpl = tpl(iota)
txTpl
addressTpl
)
type TemplateData struct { type TemplateData struct {
CoinName string CoinName string
CoinShortcut string CoinShortcut string
Address *api.Address Address *api.Address
AddrStr string AddrStr string
Tx *api.Tx Tx *api.Tx
Error *api.ApiError
} }
func parseTemplates() []*template.Template {
templateFuncMap := template.FuncMap{
"formatUnixTime": formatUnixTime,
"formatAmount": formatAmount,
"setTxToTemplateData": setTxToTemplateData,
"stringInSlice": stringInSlice,
}
t := make([]*template.Template, 3)
t[errorTpl] = template.Must(template.New("tx").Funcs(templateFuncMap).ParseFiles("./static/templates/error.html", "./static/templates/base.html"))
t[txTpl] = template.Must(template.New("tx").Funcs(templateFuncMap).ParseFiles("./static/templates/tx.html", "./static/templates/txdetail.html", "./static/templates/base.html"))
t[addressTpl] = template.Must(template.New("address").Funcs(templateFuncMap).ParseFiles("./static/templates/address.html", "./static/templates/txdetail.html", "./static/templates/base.html"))
return t
}
func formatUnixTime(ut int64) string {
return time.Unix(ut, 0).Format(time.RFC1123)
}
// for now return the string as it is
// in future could be used to do coin specific formatting
func formatAmount(a string) string {
return a
}
// called from template to support txdetail.html functionality
func setTxToTemplateData(td *TemplateData, tx *api.Tx) *TemplateData { func setTxToTemplateData(td *TemplateData, tx *api.Tx) *TemplateData {
td.Tx = tx td.Tx = tx
return td return td
} }
func (s *PublicServer) explorerTx(w http.ResponseWriter, r *http.Request) { func (s *PublicServer) explorerTx(r *http.Request) (tpl, *TemplateData, error) {
var tx *api.Tx var tx *api.Tx
if i := strings.LastIndexByte(r.URL.Path, '/'); i > 0 { if i := strings.LastIndexByte(r.URL.Path, '/'); i > 0 {
txid := r.URL.Path[i+1:] txid := r.URL.Path[i+1:]
@ -207,26 +322,15 @@ func (s *PublicServer) explorerTx(w http.ResponseWriter, r *http.Request) {
tx, err = s.api.GetTransaction(txid, bestheight, true) tx, err = s.api.GetTransaction(txid, bestheight, true)
} }
if err != nil { if err != nil {
glog.Error(err) return errorTpl, nil, err
} }
} }
w.Header().Set("Content-Type", "text/html; charset=utf-8") data := s.newTemplateData()
data.Tx = tx
// temporarily reread the template on each request return txTpl, data, nil
// to reflect changes during development
s.txTpl, s.addressTpl = parseTemplates()
data := &TemplateData{
CoinName: s.is.Coin,
CoinShortcut: s.is.CoinShortcut,
Tx: tx,
}
if err := s.txTpl.ExecuteTemplate(w, "base.html", data); err != nil {
glog.Error(err)
}
} }
func (s *PublicServer) explorerAddress(w http.ResponseWriter, r *http.Request) { func (s *PublicServer) explorerAddress(r *http.Request) (tpl, *TemplateData, error) {
var address *api.Address var address *api.Address
var err error var err error
if i := strings.LastIndexByte(r.URL.Path, '/'); i > 0 { if i := strings.LastIndexByte(r.URL.Path, '/'); i > 0 {
@ -235,27 +339,15 @@ func (s *PublicServer) explorerAddress(w http.ResponseWriter, r *http.Request) {
page = 0 page = 0
} }
addrID := r.URL.Path[i+1:] addrID := r.URL.Path[i+1:]
address, err = s.api.GetAddress(addrID, page, txsOnPage) address, err = s.api.GetAddress(addrID, page, txsOnPage, false)
if err != nil { if err != nil {
glog.Error(err) return errorTpl, nil, err
// TODO return error.html
} }
} }
w.Header().Set("Content-Type", "text/html; charset=utf-8") data := s.newTemplateData()
data.AddrStr = address.AddrStr
// temporarily reread the template on each request data.Address = address
// to reflect changes during development return addressTpl, data, nil
s.txTpl, s.addressTpl = parseTemplates()
data := &TemplateData{
CoinName: s.is.Coin,
CoinShortcut: s.is.CoinShortcut,
AddrStr: address.AddrStr,
Address: address,
}
if err := s.addressTpl.ExecuteTemplate(w, "base.html", data); err != nil {
glog.Error(err)
}
} }
type resAboutBlockbookPublic struct { type resAboutBlockbookPublic struct {
@ -272,6 +364,7 @@ type resAboutBlockbookPublic struct {
About string `json:"about"` About string `json:"about"`
} }
// TODO - this is temporary, return html status page
func (s *PublicServer) index(w http.ResponseWriter, r *http.Request) { func (s *PublicServer) index(w http.ResponseWriter, r *http.Request) {
vi := common.GetVersionInfo() vi := common.GetVersionInfo()
ss, bh, st := s.is.GetSyncState() ss, bh, st := s.is.GetSyncState()
@ -297,7 +390,7 @@ func (s *PublicServer) index(w http.ResponseWriter, r *http.Request) {
w.Write(buf) w.Write(buf)
} }
func (s *PublicServer) apiBlockIndex(w http.ResponseWriter, r *http.Request) { func (s *PublicServer) apiBlockIndex(r *http.Request) (interface{}, error) {
type resBlockIndex struct { type resBlockIndex struct {
BlockHash string `json:"blockHash"` BlockHash string `json:"blockHash"`
About string `json:"about"` About string `json:"about"`
@ -317,17 +410,15 @@ func (s *PublicServer) apiBlockIndex(w http.ResponseWriter, r *http.Request) {
} }
if err != nil { if err != nil {
glog.Error(err) glog.Error(err)
} else { return nil, err
r := resBlockIndex{
BlockHash: hash,
About: blockbookAbout,
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
json.NewEncoder(w).Encode(r)
} }
return resBlockIndex{
BlockHash: hash,
About: blockbookAbout,
}, nil
} }
func (s *PublicServer) apiTx(w http.ResponseWriter, r *http.Request) { func (s *PublicServer) apiTx(r *http.Request) (interface{}, error) {
var tx *api.Tx var tx *api.Tx
var err error var err error
if i := strings.LastIndexByte(r.URL.Path, '/'); i > 0 { if i := strings.LastIndexByte(r.URL.Path, '/'); i > 0 {
@ -335,17 +426,12 @@ func (s *PublicServer) apiTx(w http.ResponseWriter, r *http.Request) {
bestheight, _, err := s.db.GetBestBlock() bestheight, _, err := s.db.GetBestBlock()
if err == nil { if err == nil {
tx, err = s.api.GetTransaction(txid, bestheight, true) tx, err = s.api.GetTransaction(txid, bestheight, true)
} else {
glog.Error(err)
} }
} }
if err == nil { return tx, err
w.Header().Set("Content-Type", "application/json; charset=utf-8")
json.NewEncoder(w).Encode(tx)
}
} }
func (s *PublicServer) apiAddress(w http.ResponseWriter, r *http.Request) { func (s *PublicServer) apiAddress(r *http.Request) (interface{}, error) {
var address *api.Address var address *api.Address
var err error var err error
if i := strings.LastIndexByte(r.URL.Path, '/'); i > 0 { if i := strings.LastIndexByte(r.URL.Path, '/'); i > 0 {
@ -354,13 +440,7 @@ func (s *PublicServer) apiAddress(w http.ResponseWriter, r *http.Request) {
page = 0 page = 0
} }
addrID := r.URL.Path[i+1:] addrID := r.URL.Path[i+1:]
address, err = s.api.GetAddress(addrID, page, txsInAPI) address, err = s.api.GetAddress(addrID, page, txsInAPI, true)
if err != nil {
glog.Error(err)
}
}
if err == nil {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
json.NewEncoder(w).Encode(address)
} }
return address, err
} }

View File

@ -0,0 +1,4 @@
{{define "specific"}}
<h1>Error</h1>
<h3>{{.Error.Text}}</h3>
{{end}}