OSDN Git Service

make some fields of BlockchainReactor public to facilitate separate API (#477)
[bytom/bytom.git] / api / api.go
1 package api
2
3 import (
4         "crypto/tls"
5         "net"
6         "net/http"
7         "sync"
8         "time"
9
10         "github.com/kr/secureheader"
11         log "github.com/sirupsen/logrus"
12         cmn "github.com/tendermint/tmlibs/common"
13
14         "github.com/bytom/accesstoken"
15         "github.com/bytom/blockchain"
16         cfg "github.com/bytom/config"
17         "github.com/bytom/dashboard"
18         "github.com/bytom/errors"
19         "github.com/bytom/net/http/authn"
20         "github.com/bytom/net/http/httpjson"
21         "github.com/bytom/net/http/static"
22         "github.com/bytom/protocol"
23         "github.com/bytom/wallet"
24 )
25
26 var (
27         errNotAuthenticated = errors.New("not authenticated")
28         httpReadTimeout     = 2 * time.Minute
29         httpWriteTimeout    = time.Hour
30 )
31
32 const (
33         // SUCCESS indicates the rpc calling is successful.
34         SUCCESS = "success"
35         // FAIL indicated the rpc calling is failed.
36         FAIL               = "fail"
37         crosscoreRPCPrefix = "/rpc/"
38 )
39
40 // Response describes the response standard.
41 type Response struct {
42         Status string      `json:"status,omitempty"`
43         Msg    string      `json:"msg,omitempty"`
44         Data   interface{} `json:"data,omitempty"`
45 }
46
47 //NewSuccessResponse success response
48 func NewSuccessResponse(data interface{}) Response {
49         return Response{Status: SUCCESS, Data: data}
50 }
51
52 //NewErrorResponse error response
53 func NewErrorResponse(err error) Response {
54         return Response{Status: FAIL, Msg: err.Error()}
55 }
56
57 type waitHandler struct {
58         h  http.Handler
59         wg sync.WaitGroup
60 }
61
62 func (wh *waitHandler) Set(h http.Handler) {
63         wh.h = h
64         wh.wg.Done()
65 }
66
67 func (wh *waitHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
68         wh.wg.Wait()
69         wh.h.ServeHTTP(w, req)
70 }
71
72 type API struct {
73         bcr     *blockchain.BlockchainReactor
74         wallet  *wallet.Wallet
75         chain   *protocol.Chain
76         server  *http.Server
77         handler http.Handler
78 }
79
80 func (a *API) initServer(config *cfg.Config) {
81         // The waitHandler accepts incoming requests, but blocks until its underlying
82         // handler is set, when the second phase is complete.
83         var coreHandler waitHandler
84         coreHandler.wg.Add(1)
85         mux := http.NewServeMux()
86         mux.Handle("/", &coreHandler)
87
88         var handler http.Handler = mux
89
90         if config.Auth.Disable == false {
91                 handler = AuthHandler(handler, a.wallet.Tokens)
92         }
93         handler = RedirectHandler(handler)
94
95         secureheader.DefaultConfig.PermitClearLoopback = true
96         secureheader.DefaultConfig.HTTPSRedirect = false
97         secureheader.DefaultConfig.Next = handler
98
99         a.server = &http.Server{
100                 // Note: we should not set TLSConfig here;
101                 // we took care of TLS with the listener in maybeUseTLS.
102                 Handler:      secureheader.DefaultConfig,
103                 ReadTimeout:  httpReadTimeout,
104                 WriteTimeout: httpWriteTimeout,
105                 // Disable HTTP/2 for now until the Go implementation is more stable.
106                 // https://github.com/golang/go/issues/16450
107                 // https://github.com/golang/go/issues/17071
108                 TLSNextProto: map[string]func(*http.Server, *tls.Conn, http.Handler){},
109         }
110
111         coreHandler.Set(a)
112 }
113
114 func (a *API) StartServer(address string) {
115         log.WithField("api address:", address).Info("Rpc listen")
116         listener, err := net.Listen("tcp", address)
117         if err != nil {
118                 cmn.Exit(cmn.Fmt("Failed to register tcp port: %v", err))
119         }
120
121         // The `Serve` call has to happen in its own goroutine because
122         // it's blocking and we need to proceed to the rest of the core setup after
123         // we call it.
124         go func() {
125                 if err := a.server.Serve(listener); err != nil {
126                         log.WithField("error", errors.Wrap(err, "Serve")).Error("Rpc server")
127                 }
128         }()
129 }
130
131 func NewAPI(bcr *blockchain.BlockchainReactor, wallet *wallet.Wallet, chain *protocol.Chain, config *cfg.Config) *API {
132         api := &API{
133                 bcr:    bcr,
134                 wallet: wallet,
135                 chain:  chain,
136         }
137         api.buildHandler()
138         api.initServer(config)
139
140         return api
141 }
142
143 func (a *API) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
144         a.handler.ServeHTTP(rw, req)
145 }
146
147 // buildHandler is in charge of all the rpc handling.
148 func (a *API) buildHandler() {
149         m := http.NewServeMux()
150         if a.wallet != nil && a.wallet.AccountMgr != nil && a.wallet.AssetReg != nil {
151                 m.Handle("/create-account", jsonHandler(a.createAccount))
152                 m.Handle("/update-account-tags", jsonHandler(a.updateAccountTags))
153                 m.Handle("/create-account-receiver", jsonHandler(a.createAccountReceiver))
154                 m.Handle("/list-accounts", jsonHandler(a.listAccounts))
155                 m.Handle("/list-addresses", jsonHandler(a.listAddresses))
156                 m.Handle("/delete-account", jsonHandler(a.deleteAccount))
157                 m.Handle("/validate-address", jsonHandler(a.validateAddress))
158
159                 m.Handle("/create-asset", jsonHandler(a.createAsset))
160                 m.Handle("/update-asset-alias", jsonHandler(a.updateAssetAlias))
161                 m.Handle("/update-asset-tags", jsonHandler(a.updateAssetTags))
162                 m.Handle("/list-assets", jsonHandler(a.listAssets))
163
164                 m.Handle("/create-key", jsonHandler(a.pseudohsmCreateKey))
165                 m.Handle("/list-keys", jsonHandler(a.pseudohsmListKeys))
166                 m.Handle("/delete-key", jsonHandler(a.pseudohsmDeleteKey))
167                 m.Handle("/reset-key-password", jsonHandler(a.pseudohsmResetPassword))
168
169                 m.Handle("/get-transaction", jsonHandler(a.getTransaction))
170                 m.Handle("/list-transactions", jsonHandler(a.listTransactions))
171                 m.Handle("/list-balances", jsonHandler(a.listBalances))
172         } else {
173                 log.Warn("Please enable wallet")
174         }
175
176         m.Handle("/", alwaysError(errors.New("not Found")))
177
178         m.Handle("/build-transaction", jsonHandler(a.build))
179         m.Handle("/sign-transaction", jsonHandler(a.pseudohsmSignTemplates))
180         m.Handle("/submit-transaction", jsonHandler(a.submit))
181         m.Handle("/sign-submit-transaction", jsonHandler(a.signSubmit))
182
183         m.Handle("/create-transaction-feed", jsonHandler(a.createTxFeed))
184         m.Handle("/get-transaction-feed", jsonHandler(a.getTxFeed))
185         m.Handle("/update-transaction-feed", jsonHandler(a.updateTxFeed))
186         m.Handle("/delete-transaction-feed", jsonHandler(a.deleteTxFeed))
187         m.Handle("/list-transaction-feeds", jsonHandler(a.listTxFeeds))
188         m.Handle("/list-unspent-outputs", jsonHandler(a.listUnspentOutputs))
189         m.Handle("/info", jsonHandler(a.bcr.Info))
190
191         m.Handle("/create-access-token", jsonHandler(a.createAccessToken))
192         m.Handle("/list-access-tokens", jsonHandler(a.listAccessTokens))
193         m.Handle("/delete-access-token", jsonHandler(a.deleteAccessToken))
194         m.Handle("/check-access-token", jsonHandler(a.checkAccessToken))
195
196         m.Handle("/block-hash", jsonHandler(a.getBestBlockHash))
197
198         m.Handle("/export-private-key", jsonHandler(a.walletExportKey))
199         m.Handle("/import-private-key", jsonHandler(a.walletImportKey))
200         m.Handle("/import-key-progress", jsonHandler(a.keyImportProgress))
201
202         m.Handle("/get-block-header-by-hash", jsonHandler(a.getBlockHeaderByHash))
203         m.Handle("/get-block-header-by-height", jsonHandler(a.getBlockHeaderByHeight))
204         m.Handle("/get-block", jsonHandler(a.getBlock))
205         m.Handle("/get-block-count", jsonHandler(a.getBlockCount))
206         m.Handle("/get-block-transactions-count-by-hash", jsonHandler(a.getBlockTransactionsCountByHash))
207         m.Handle("/get-block-transactions-count-by-height", jsonHandler(a.getBlockTransactionsCountByHeight))
208
209         m.Handle("/net-info", jsonHandler(a.getNetInfo))
210
211         m.Handle("/is-mining", jsonHandler(a.isMining))
212         m.Handle("/gas-rate", jsonHandler(a.gasRate))
213         m.Handle("/getwork", jsonHandler(a.getWork))
214         m.Handle("/submitwork", jsonHandler(a.submitWork))
215
216         latencyHandler := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
217                 if l := latency(m, req); l != nil {
218                         defer l.RecordSince(time.Now())
219                 }
220                 m.ServeHTTP(w, req)
221         })
222         handler := maxBytesHandler(latencyHandler) // TODO(tessr): consider moving this to non-core specific mux
223         handler = webAssetsHandler(handler)
224
225         a.handler = handler
226 }
227
228 func maxBytesHandler(h http.Handler) http.Handler {
229         const maxReqSize = 1e7 // 10MB
230         return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
231                 // A block can easily be bigger than maxReqSize, but everything
232                 // else should be pretty small.
233                 if req.URL.Path != crosscoreRPCPrefix+"signer/sign-block" {
234                         req.Body = http.MaxBytesReader(w, req.Body, maxReqSize)
235                 }
236                 h.ServeHTTP(w, req)
237         })
238 }
239
240 // json Handler
241 func jsonHandler(f interface{}) http.Handler {
242         h, err := httpjson.Handler(f, errorFormatter.Write)
243         if err != nil {
244                 panic(err)
245         }
246         return h
247 }
248
249 // error Handler
250 func alwaysError(err error) http.Handler {
251         return jsonHandler(func() error { return err })
252 }
253
254 func webAssetsHandler(next http.Handler) http.Handler {
255         mux := http.NewServeMux()
256         mux.Handle("/dashboard/", http.StripPrefix("/dashboard/", static.Handler{
257                 Assets:  dashboard.Files,
258                 Default: "index.html",
259         }))
260         mux.Handle("/", next)
261
262         return mux
263 }
264
265 //AuthHandler access token auth Handler
266 func AuthHandler(handler http.Handler, accessTokens *accesstoken.CredentialStore) http.Handler {
267         authenticator := authn.NewAPI(accessTokens)
268
269         return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
270                 // TODO(tessr): check that this path exists; return early if this path isn't legit
271                 req, err := authenticator.Authenticate(req)
272                 if err != nil {
273                         log.WithField("error", errors.Wrap(err, "Serve")).Error("Authenticate fail")
274                         err = errors.Sub(errNotAuthenticated, err)
275                         errorFormatter.Write(req.Context(), rw, err)
276                         return
277                 }
278                 handler.ServeHTTP(rw, req)
279         })
280 }
281
282 func RedirectHandler(next http.Handler) http.Handler {
283         return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
284                 if req.URL.Path == "/" {
285                         http.Redirect(w, req, "/dashboard/", http.StatusFound)
286                         return
287                 }
288                 next.ServeHTTP(w, req)
289         })
290 }