Skip to content

Commit

Permalink
Add readonly api to gateway
Browse files Browse the repository at this point in the history
Issue ipfs#1322

Added the ability to expose individual commands to a read-only style
handler that can be used on any http interface.

Added the read-only handler to the gateway.
  • Loading branch information
travisperson committed Jun 18, 2015
1 parent fa23022 commit fa319e7
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 6 deletions.
4 changes: 2 additions & 2 deletions cmd/ipfs/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ func serveHTTPApi(req cmds.Request) (error, <-chan error) {
var opts = []corehttp.ServeOption{
corehttp.CommandsOption(*req.Context()),
corehttp.WebUIOption,
apiGw.ServeOption(),
apiGw.ServeOption(nil),
corehttp.VersionOption(),
defaultMux("/debug/vars"),
defaultMux("/debug/pprof/"),
Expand Down Expand Up @@ -339,7 +339,7 @@ func serveHTTPGateway(req cmds.Request) (error, <-chan error) {
var opts = []corehttp.ServeOption{
corehttp.VersionOption(),
corehttp.IPNSHostnameOption(),
corehttp.GatewayOption(writable),
corehttp.GatewayOption(writable, req.Context()),
}

if len(cfg.Gateway.RootRedirect) > 0 {
Expand Down
44 changes: 43 additions & 1 deletion commands/http/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context"

cmds "github.com/ipfs/go-ipfs/commands"
commands "github.com/ipfs/go-ipfs/core/commands"
u "github.com/ipfs/go-ipfs/util"
)

Expand All @@ -21,6 +22,7 @@ var log = u.Logger("commands/http")
type internalHandler struct {
ctx cmds.Context
root *cmds.Command
readOnly bool
}

// The Handler struct is funny because we want to wrap our internal handler
Expand All @@ -47,14 +49,18 @@ var mimeTypes = map[string]string{
cmds.Text: "text/plain",
}

var readOnlyCmds = map[*cmds.Command]bool{
commands.RefsCmd: true,
}

func NewHandler(ctx cmds.Context, root *cmds.Command, allowedOrigin string) *Handler {
// allow whitelisted origins (so we can make API requests from the browser)
if len(allowedOrigin) > 0 {
log.Info("Allowing API requests from origin: " + allowedOrigin)
}

// Create a handler for the API.
internal := internalHandler{ctx, root}
internal := internalHandler{ctx, root, false}

// Create a CORS object for wrapping the internal handler.
c := cors.New(cors.Options{
Expand All @@ -71,6 +77,32 @@ func NewHandler(ctx cmds.Context, root *cmds.Command, allowedOrigin string) *Han
return &Handler{internal, c.Handler(internal)}
}

func NewReadOnlyHandler(ctx cmds.Context, root *cmds.Command, allowedOrigin string) *Handler {
// allow whitelisted origins (so we can make API requests from the browser)
if len(allowedOrigin) > 0 {
log.Info("Allowing API requests from origin: " + allowedOrigin)
}

// Create a handler for the API.
internal := internalHandler{ctx, root, true}

// Create a CORS object for wrapping the internal handler.
c := cors.New(cors.Options{
AllowedMethods: []string{"GET"},

// use AllowOriginFunc instead of AllowedOrigins because we want to be
// restrictive by default.
AllowOriginFunc: func(origin string) bool {
return (allowedOrigin == "*") || (origin == allowedOrigin)
},
})

fmt.Println("Read Only API")

// Wrap the internal handler with CORS handling-middleware.
return &Handler{internal, c.Handler(internal)}
}

func (i internalHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
log.Debug("Incoming API request: ", r.URL)

Expand All @@ -89,6 +121,7 @@ func (i internalHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}

req, err := Parse(r, i.root)

if err != nil {
if err == ErrNotFound {
w.WriteHeader(http.StatusNotFound)
Expand All @@ -99,6 +132,15 @@ func (i internalHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return
}

if i.readOnly == true {
if _, ok := readOnlyCmds[req.Command()]; !ok {
// Or a 404?
w.WriteHeader(http.StatusForbidden)
w.Write([]byte("You may not execute this request on the read-only api."))
return
}
}

// get the node's context to pass into the commands.
node, err := i.ctx.GetNode()
if err != nil {
Expand Down
18 changes: 15 additions & 3 deletions core/corehttp/gateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ import (
"fmt"
"net/http"
"sync"
"os"

commands "github.com/ipfs/go-ipfs/commands"
cmdsHttp "github.com/ipfs/go-ipfs/commands/http"
corecommands "github.com/ipfs/go-ipfs/core/commands"
core "github.com/ipfs/go-ipfs/core"
id "github.com/ipfs/go-ipfs/p2p/protocol/identify"
)
Expand All @@ -25,26 +29,34 @@ func NewGateway(conf GatewayConfig) *Gateway {
}
}

func (g *Gateway) ServeOption() ServeOption {
func (g *Gateway) ServeOption(cctx * commands.Context) ServeOption {
return func(n *core.IpfsNode, mux *http.ServeMux) (*http.ServeMux, error) {
gateway, err := newGatewayHandler(n, g.Config)
if err != nil {
return nil, err
}
mux.Handle("/ipfs/", gateway)
mux.Handle("/ipns/", gateway)

if cctx != nil {
origin := os.Getenv(originEnvKey)
cmdHandler := cmdsHttp.NewReadOnlyHandler(*cctx, corecommands.Root, origin)
mux.Handle(cmdsHttp.ApiPath+"/", cmdHandler)
}
return mux, nil
}
}

func GatewayOption(writable bool) ServeOption {

func GatewayOption(writable bool, cctx * commands.Context) ServeOption {
g := NewGateway(GatewayConfig{
Writable: writable,
BlockList: &BlockList{},
})
return g.ServeOption()
return g.ServeOption(cctx)
}


func VersionOption() ServeOption {
return func(n *core.IpfsNode, mux *http.ServeMux) (*http.ServeMux, error) {
mux.HandleFunc("/version", func(w http.ResponseWriter, r *http.Request) {
Expand Down

0 comments on commit fa319e7

Please sign in to comment.