Add swagger-ui to gaiacli rest-server

This commit is contained in:
HaoyangLiu 2018-09-02 20:09:43 +08:00
parent d214952450
commit 79c0bc40c8
27 changed files with 2602 additions and 14 deletions

9
Gopkg.lock generated
View File

@ -304,6 +304,14 @@
pruneopts = "UT"
revision = "05ee40e3a273f7245e8777337fc7b46e533a9a92"
[[projects]]
digest = "1:ea0700160aca4ef099f4e06686a665a87691f4248dddd40796925eda2e46bd64"
name = "github.com/rakyll/statik"
packages = ["fs"]
pruneopts = "UT"
revision = "aa8a7b1baecd0f31a436bf7956fcdcc609a83035"
version = "v0.1.4"
[[projects]]
digest = "1:c4556a44e350b50a490544d9b06e9fba9c286c21d6c0e47f54f3a9214597298c"
name = "github.com/rcrowley/go-metrics"
@ -638,6 +646,7 @@
"github.com/gorilla/mux",
"github.com/mattn/go-isatty",
"github.com/pkg/errors",
"github.com/rakyll/statik/fs",
"github.com/spf13/cobra",
"github.com/spf13/pflag",
"github.com/spf13/viper",

View File

@ -67,6 +67,10 @@
name = "github.com/zondax/ledger-goclient"
revision = "4296ee5701e945f9b3a7dbe51f402e0b9be57259"
[[constraint]]
name = "github.com/rakyll/statik"
version = "=v0.1.4"
[prune]
go-tests = true
unused-packages = true

View File

@ -259,3 +259,55 @@ func SeedRequestHandler(w http.ResponseWriter, r *http.Request) {
seed := getSeed(algo)
w.Write([]byte(seed))
}
// RecoverKeyBody is recover key request REST body
type RecoverKeyBody struct {
Password string `json:"password"`
Seed string `json:"seed"`
}
// RecoverResuestHandler is the handler of creating seed in swagger rest server
func RecoverResuestHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
name := vars["name"]
var m RecoverKeyBody
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&m)
if err != nil {
w.WriteHeader(400)
w.Write([]byte(err.Error()))
return
}
kb, err := GetKeyBase()
if err != nil {
w.WriteHeader(500)
w.Write([]byte(err.Error()))
return
}
info, err := kb.CreateKey(name, m.Seed, m.Password)
if err != nil {
w.WriteHeader(500)
w.Write([]byte(err.Error()))
return
}
keyOutput, err := Bech32KeyOutput(info)
if err != nil {
w.WriteHeader(500)
w.Write([]byte(err.Error()))
return
}
bz, err := cdc.MarshalJSON(keyOutput)
if err != nil {
w.WriteHeader(500)
w.Write([]byte(err.Error()))
return
}
w.Write(bz)
}

View File

@ -33,7 +33,8 @@ func Commands() *cobra.Command {
func RegisterRoutes(r *mux.Router) {
r.HandleFunc("/keys", QueryKeysRequestHandler).Methods("GET")
r.HandleFunc("/keys", AddNewKeyRequestHandler).Methods("POST")
r.HandleFunc("/keys/seed", SeedRequestHandler).Methods("GET")
r.HandleFunc("/keys/{name}/recover", RecoverResuestHandler).Methods("POST")
r.HandleFunc("/keys/{name}/sign", SignResuest).Methods("POST")
r.HandleFunc("/keys/{name}", GetKeyRequestHandler).Methods("GET")
r.HandleFunc("/keys/{name}", UpdateKeyRequestHandler).Methods("PUT")
r.HandleFunc("/keys/{name}", DeleteKeyRequestHandler).Methods("DELETE")

90
client/keys/sign.go Normal file
View File

@ -0,0 +1,90 @@
package keys
import (
"encoding/json"
"net/http"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"encoding/base64"
"fmt"
"github.com/gorilla/mux"
)
const (
flagFrom = "from"
flagPassword = "password"
flagTx = "tx"
)
func init() {
keySignCmd.Flags().String(flagFrom, "", "Name of private key with which to sign")
keySignCmd.Flags().String(flagPassword, "", "Password of private key")
keySignCmd.Flags().String(flagTx, "", "Base64 encoded tx data for sign")
}
var keySignCmd = &cobra.Command{
Use: "sign",
Short: "Sign user specified data",
Long: `Sign user data with specified key and password`,
RunE: func(cmd *cobra.Command, args []string) error {
name := viper.GetString(flagFrom)
password := viper.GetString(flagPassword)
tx := viper.GetString(flagTx)
decodedTx, err := base64.StdEncoding.DecodeString(tx)
if err != nil {
return err
}
kb, err := GetKeyBase()
if err != nil {
return err
}
sig, _, err := kb.Sign(name, password, decodedTx)
if err != nil {
return err
}
encoded := base64.StdEncoding.EncodeToString(sig)
fmt.Println(string(encoded))
return nil
},
}
type keySignBody struct {
Tx []byte `json:"tx_bytes"`
Password string `json:"password"`
}
// SignResuest is the handler of creating seed in swagger rest server
func SignResuest(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
name := vars["name"]
var m keySignBody
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&m)
if err != nil {
w.WriteHeader(400)
w.Write([]byte(err.Error()))
return
}
kb, err := GetKeyBase()
if err != nil {
w.WriteHeader(500)
w.Write([]byte(err.Error()))
return
}
sig, _, err := kb.Sign(name, m.Password, m.Tx)
if err != nil {
w.WriteHeader(400)
w.Write([]byte(err.Error()))
return
}
encoded := base64.StdEncoding.EncodeToString(sig)
w.Write([]byte(encoded))
}

View File

@ -42,9 +42,12 @@ func TestKeys(t *testing.T) {
cleanup, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{addr})
defer cleanup()
// get seed
// TODO Do we really need this endpoint?
res, body := Request(t, port, "GET", "/keys/seed", nil)
// recover key
recoverKeyURL := fmt.Sprintf("/keys/%s/recover", "test_recover")
seedRecover := "divorce meat banana embody near until uncover wait uniform capital crawl test praise cloud foil monster garbage hedgehog wrong skate there bonus box odor"
passwordRecover := "1234567890"
jsonStrRecover := []byte(fmt.Sprintf(`{"seed":"%s", "password":"%s"}`, seedRecover, passwordRecover))
res, body := Request(t, port, "POST", recoverKeyURL, jsonStrRecover)
require.Equal(t, http.StatusOK, res.StatusCode, body)
reg, err := regexp.Compile(`([a-z]+ ){12}`)
require.Nil(t, err)
@ -75,7 +78,7 @@ func TestKeys(t *testing.T) {
// existing keys
res, body = Request(t, port, "GET", "/keys", nil)
require.Equal(t, http.StatusOK, res.StatusCode, body)
var m [2]keys.KeyOutput
var m [3]keys.KeyOutput
err = cdc.UnmarshalJSON([]byte(body), &m)
require.Nil(t, err)
@ -235,7 +238,7 @@ func TestCoinSend(t *testing.T) {
someFakeAddr := sdk.AccAddress(bz)
// query empty
res, body := Request(t, port, "GET", fmt.Sprintf("/accounts/%s", someFakeAddr), nil)
res, body := Request(t, port, "GET", fmt.Sprintf("/auth/accounts/%s", someFakeAddr), nil)
require.Equal(t, http.StatusNoContent, res.StatusCode, body)
acc := getAccount(t, port, addr)
@ -723,7 +726,7 @@ func TestProposalsQuery(t *testing.T) {
//_____________________________________________________________________________
// get the account to get the sequence
func getAccount(t *testing.T, port string, addr sdk.AccAddress) auth.Account {
res, body := Request(t, port, "GET", fmt.Sprintf("/accounts/%s", addr), nil)
res, body := Request(t, port, "GET", fmt.Sprintf("/auth/accounts/%s", addr), nil)
require.Equal(t, http.StatusOK, res.StatusCode, body)
var acc auth.Account
err := cdc.UnmarshalJSON([]byte(body), &acc)
@ -771,7 +774,7 @@ func doSendWithGas(t *testing.T, port, seed, name, password string, addr sdk.Acc
"chain_id":"%s"
}`, gasStr, gasAdjustmentStr, name, password, accnum, sequence, coinbz, chainID))
res, body = Request(t, port, "POST", fmt.Sprintf("/accounts/%s/send%v", receiveAddr, queryStr), jsonStr)
res, body = Request(t, port, "POST", fmt.Sprintf("/bank/%s/transfers%v", receiveAddr, queryStr), jsonStr)
return
}

View File

@ -17,6 +17,7 @@ import (
slashing "github.com/cosmos/cosmos-sdk/x/slashing/client/rest"
stake "github.com/cosmos/cosmos-sdk/x/stake/client/rest"
"github.com/gorilla/mux"
"github.com/rakyll/statik/fs"
"github.com/spf13/cobra"
"github.com/spf13/viper"
cmn "github.com/tendermint/tendermint/libs/common"
@ -37,12 +38,20 @@ func ServeCommand(cdc *wire.Codec) *cobra.Command {
Short: "Start LCD (light-client daemon), a local REST server",
RunE: func(cmd *cobra.Command, args []string) error {
listenAddr := viper.GetString(flagListenAddr)
handler := createHandler(cdc)
router := createHandler(cdc)
statikFS, err := fs.New()
if err != nil {
panic(err)
}
staticServer := http.FileServer(statikFS)
router.PathPrefix("/swaggerui/").Handler(http.StripPrefix("/swaggerui/", staticServer))
logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "rest-server")
maxOpen := viper.GetInt(flagMaxOpenConnections)
listener, err := tmserver.StartHTTPServer(
listenAddr, handler, logger,
listenAddr, router, logger,
tmserver.Config{MaxOpenConnections: maxOpen},
)
if err != nil {
@ -71,7 +80,7 @@ func ServeCommand(cdc *wire.Codec) *cobra.Command {
return cmd
}
func createHandler(cdc *wire.Codec) http.Handler {
func createHandler(cdc *wire.Codec) *mux.Router {
r := mux.NewRouter()
kb, err := keys.GetKeyBase() //XXX

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 445 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,60 @@
<!-- HTML for static distribution bundle build -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Swagger UI</title>
<link rel="stylesheet" type="text/css" href="swagger-ui.css" >
<link rel="icon" type="image/png" href="favicon-32x32.png" sizes="32x32" />
<link rel="icon" type="image/png" href="favicon-16x16.png" sizes="16x16" />
<style>
html
{
box-sizing: border-box;
overflow: -moz-scrollbars-vertical;
overflow-y: scroll;
}
*,
*:before,
*:after
{
box-sizing: inherit;
}
body
{
margin:0;
background: #fafafa;
}
</style>
</head>
<body>
<div id="swagger-ui"></div>
<script src="swagger-ui-bundle.js"> </script>
<script src="swagger-ui-standalone-preset.js"> </script>
<script>
window.onload = function() {
// Build a system
const ui = SwaggerUIBundle({
url: "./swagger.json",
dom_id: '#swagger-ui',
deepLinking: true,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout"
})
window.ui = ui
}
</script>
</body>
</html>

View File

@ -0,0 +1,67 @@
<!doctype html>
<html lang="en-US">
<body onload="run()">
</body>
</html>
<script>
'use strict';
function run () {
var oauth2 = window.opener.swaggerUIRedirectOauth2;
var sentState = oauth2.state;
var redirectUrl = oauth2.redirectUrl;
var isValid, qp, arr;
if (/code|token|error/.test(window.location.hash)) {
qp = window.location.hash.substring(1);
} else {
qp = location.search.substring(1);
}
arr = qp.split("&")
arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';})
qp = qp ? JSON.parse('{' + arr.join() + '}',
function (key, value) {
return key === "" ? value : decodeURIComponent(value)
}
) : {}
isValid = qp.state === sentState
if ((
oauth2.auth.schema.get("flow") === "accessCode"||
oauth2.auth.schema.get("flow") === "authorizationCode"
) && !oauth2.auth.code) {
if (!isValid) {
oauth2.errCb({
authId: oauth2.auth.name,
source: "auth",
level: "warning",
message: "Authorization may be unsafe, passed state was changed in server Passed state wasn't returned from auth server"
});
}
if (qp.code) {
delete oauth2.state;
oauth2.auth.code = qp.code;
oauth2.callback({auth: oauth2.auth, redirectUrl: redirectUrl});
} else {
let oauthErrorMsg
if (qp.error) {
oauthErrorMsg = "["+qp.error+"]: " +
(qp.error_description ? qp.error_description+ ". " : "no accessCode received from the server. ") +
(qp.error_uri ? "More info: "+qp.error_uri : "");
}
oauth2.errCb({
authId: oauth2.auth.name,
source: "auth",
level: "error",
message: oauthErrorMsg || "[Authorization failed]: no accessCode received from the server"
});
}
} else {
oauth2.callback({auth: oauth2.auth, token: qp, isValid: isValid, redirectUrl: redirectUrl});
}
window.close();
}
</script>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
{"version":3,"sources":[],"names":[],"mappings":"","file":"swagger-ui.css","sourceRoot":""}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -18,6 +18,7 @@ import (
slashingcmd "github.com/cosmos/cosmos-sdk/x/slashing/client/cli"
stakecmd "github.com/cosmos/cosmos-sdk/x/stake/client/cli"
_ "github.com/cosmos/cosmos-sdk/client/lcd/statik"
"github.com/cosmos/cosmos-sdk/cmd/gaia/app"
)

View File

@ -6,6 +6,7 @@ import (
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/keys"
"github.com/cosmos/cosmos-sdk/client/lcd"
_ "github.com/cosmos/cosmos-sdk/client/lcd/statik"
"github.com/cosmos/cosmos-sdk/client/rpc"
"github.com/cosmos/cosmos-sdk/client/tx"
"github.com/cosmos/cosmos-sdk/examples/basecoin/app"

View File

@ -17,7 +17,7 @@ import (
// register REST routes
func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *wire.Codec, storeName string) {
r.HandleFunc(
"/accounts/{address}",
"/auth/accounts/{address}",
QueryAccountRequestHandlerFn(storeName, cdc, authcmd.GetAccountDecoder(cdc), cliCtx),
).Methods("GET")
}

View File

@ -0,0 +1,68 @@
package rest
import (
"fmt"
"net/http"
"github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/cosmos-sdk/client/utils"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/wire"
"github.com/cosmos/cosmos-sdk/x/auth"
authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli"
"github.com/gorilla/mux"
)
// register REST routes
func registerQueryRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *wire.Codec, storeName string) {
r.HandleFunc(
"/bank/balances/{address}",
QueryAccountRequestHandlerFn(storeName, cdc, authcmd.GetAccountDecoder(cdc), cliCtx),
).Methods("GET")
}
// query balances Handler
func QueryAccountRequestHandlerFn(
storeName string, cdc *wire.Codec,
decoder auth.AccountDecoder, cliCtx context.CLIContext,
) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
bech32addr := vars["address"]
addr, err := sdk.AccAddressFromBech32(bech32addr)
if err != nil {
utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return
}
res, err := cliCtx.QueryStore(auth.AddressStoreKey(addr), storeName)
if err != nil {
utils.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("couldn't query account. Error: %s", err.Error()))
return
}
// the query will return empty if there is no data for this account
if len(res) == 0 {
w.WriteHeader(http.StatusNoContent)
return
}
// decode the value
account, err := decoder(res)
if err != nil {
utils.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("couldn't parse query result. Result: %s. Error: %s", res, err.Error()))
return
}
// print out whole account
output, err := cdc.MarshalJSON(account.GetCoins())
if err != nil {
utils.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("couldn't marshall query result. Error: %s", err.Error()))
return
}
w.Write(output)
}
}

View File

@ -0,0 +1,14 @@
package rest
import (
"github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/cosmos-sdk/crypto/keys"
"github.com/cosmos/cosmos-sdk/wire"
"github.com/gorilla/mux"
)
// RegisterRoutes registers bank-related REST handlers to a router
func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *wire.Codec, kb keys.Keybase) {
registerQueryRoutes(cliCtx, r, cdc, "acc")
RegisterSendTxRoutes(cliCtx, r, cdc, kb)
}

View File

@ -18,8 +18,8 @@ import (
)
// RegisterRoutes - Central function to define routes that get registered by the main application
func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *wire.Codec, kb keys.Keybase) {
r.HandleFunc("/accounts/{address}/send", SendRequestHandlerFn(cdc, kb, cliCtx)).Methods("POST")
func RegisterSendTxRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *wire.Codec, kb keys.Keybase) {
r.HandleFunc("/bank/{address}/transfers", SendRequestHandlerFn(cdc, kb, cliCtx)).Methods("POST")
}
type sendBody struct {