Skip to content

Commit

Permalink
Initial work on a graphql API
Browse files Browse the repository at this point in the history
  • Loading branch information
Arachnid committed Oct 13, 2018
1 parent f951e23 commit 4b53623
Show file tree
Hide file tree
Showing 37 changed files with 5,629 additions and 6 deletions.
8 changes: 8 additions & 0 deletions cmd/geth/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/dashboard"
"github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/ethgraphql"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/params"
whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
Expand Down Expand Up @@ -174,6 +175,13 @@ func makeFullNode(ctx *cli.Context) *node.Node {
utils.RegisterShhService(stack, &cfg.Shh)
}

// Configure GraphQL if required
if ctx.GlobalIsSet(utils.GraphQLEnabledFlag.Name) {
if err := ethgraphql.RegisterGraphQLService(stack, cfg.Node.GraphQLEndpoint(), cfg.Node.HTTPCors, cfg.Node.HTTPVirtualHosts, cfg.Node.HTTPTimeouts); err != nil {
utils.Fatalf("Failed to register the Ethereum service: %v", err)
}
}

// Add the Ethereum Stats daemon if requested.
if cfg.Ethstats.URL != "" {
utils.RegisterEthStatsService(stack, cfg.Ethstats.URL)
Expand Down
3 changes: 3 additions & 0 deletions cmd/geth/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,9 @@ var (
utils.RPCEnabledFlag,
utils.RPCListenAddrFlag,
utils.RPCPortFlag,
utils.GraphQLEnabledFlag,
utils.GraphQLListenAddrFlag,
utils.GraphQLPortFlag,
utils.RPCApiFlag,
utils.WSEnabledFlag,
utils.WSListenAddrFlag,
Expand Down
28 changes: 28 additions & 0 deletions cmd/utils/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,20 @@ var (
Usage: "HTTP-RPC server listening port",
Value: node.DefaultHTTPPort,
}
GraphQLEnabledFlag = cli.BoolFlag{
Name: "graphql",
Usage: "Enable the GraphQL server",
}
GraphQLListenAddrFlag = cli.StringFlag{
Name: "graphqladdr",
Usage: "GraphQL server listening interface",
Value: node.DefaultGraphQLHost,
}
GraphQLPortFlag = cli.IntFlag{
Name: "graphqlport",
Usage: "GraphQL server listening port",
Value: node.DefaultGraphQLPort,
}
RPCCORSDomainFlag = cli.StringFlag{
Name: "rpccorsdomain",
Usage: "Comma separated list of domains from which to accept cross origin requests (browser enforced)",
Expand Down Expand Up @@ -783,6 +797,19 @@ func setHTTP(ctx *cli.Context, cfg *node.Config) {
}
}

// setGraphQL creates the GraphQL listener interface string from the set
// command line flags, returning empty if the GraphQL endpoint is disabled.
func setGraphQL(ctx *cli.Context, cfg *node.Config) {
if ctx.GlobalBool(GraphQLEnabledFlag.Name) && cfg.GraphQLHost == "" {
cfg.GraphQLHost = "127.0.0.1"
if ctx.GlobalIsSet(GraphQLListenAddrFlag.Name) {
cfg.GraphQLHost = ctx.GlobalString(GraphQLListenAddrFlag.Name)
}
}

cfg.GraphQLPort = ctx.GlobalInt(GraphQLPortFlag.Name)
}

// setWS creates the WebSocket RPC listener interface string from the set
// command line flags, returning empty if the HTTP endpoint is disabled.
func setWS(ctx *cli.Context, cfg *node.Config) {
Expand Down Expand Up @@ -970,6 +997,7 @@ func SetNodeConfig(ctx *cli.Context, cfg *node.Config) {
SetP2PConfig(ctx, &cfg.P2P)
setIPC(ctx, cfg)
setHTTP(ctx, cfg)
setGraphQL(ctx, cfg)
setWS(ctx, cfg)
setNodeUserIdent(ctx, cfg)

Expand Down
73 changes: 73 additions & 0 deletions ethgraphql/graphiql.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package ethgraphql

import (
"bytes"
"fmt"
"net/http"
)

// GraphiQL is an in-browser IDE for exploring GraphiQL APIs.
// This handler returns GraphiQL when requested.
//
// For more information, see https://github.com/graphql/graphiql.
type GraphiQL struct{}

func respond(w http.ResponseWriter, body []byte, code int) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.Header().Set("X-Content-Type-Options", "nosniff")
w.WriteHeader(code)
_, _ = w.Write(body)
}

func errorJSON(msg string) []byte {
buf := bytes.Buffer{}
fmt.Fprintf(&buf, `{"error": "%s"}`, msg)
return buf.Bytes()
}

func (h GraphiQL) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.Method != "GET" {
respond(w, errorJSON("only GET requests are supported"), http.StatusMethodNotAllowed)
return
}

w.Write(graphiql)
}

var graphiql = []byte(`
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/graphiql/0.11.11/graphiql.css"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fetch/2.0.3/fetch.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.2.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.2.0/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/graphiql/0.11.11/graphiql.min.js"></script>
</head>
<body style="width: 100%; height: 100%; margin: 0; overflow: hidden;">
<div id="graphiql" style="height: 100vh;">Loading...</div>
<script>
function fetchGQL(params) {
return fetch("/graphql", {
method: "post",
body: JSON.stringify(params),
credentials: "include",
}).then(function (resp) {
return resp.text();
}).then(function (body) {
try {
return JSON.parse(body);
} catch (error) {
return body;
}
});
}
ReactDOM.render(
React.createElement(GraphiQL, {fetcher: fetchGQL}),
document.getElementById("graphiql")
)
</script>
</body>
</html>
`)
Loading

0 comments on commit 4b53623

Please # to comment.