Skip to content
This repository has been archived by the owner on Jun 20, 2024. It is now read-only.

Commit

Permalink
fix: ensure correct Method and Location on RPC API redirect (#27)
Browse files Browse the repository at this point in the history
  • Loading branch information
hacdias authored Feb 10, 2023
1 parent f7bd5ea commit 521d97c
Show file tree
Hide file tree
Showing 2 changed files with 112 additions and 25 deletions.
86 changes: 86 additions & 0 deletions handler_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package main

import (
"net/http"
"net/http/httptest"
"testing"

"github.com/stretchr/testify/assert"
)

type rpcRedirectTest struct {
url string
location string
status int
}

func TestRPCRedirectsToGateway(t *testing.T) {
rpcHandler := newKuboRPCHandler([]string{"http://example.com"})

tests := []rpcRedirectTest{
{"/api/v0/cat?arg=bafkreifzjut3te2nhyekklss27nh3k72ysco7y32koao5eei66wof36n5e", "/ipfs/bafkreifzjut3te2nhyekklss27nh3k72ysco7y32koao5eei66wof36n5e", http.StatusSeeOther},
{"/api/v0/cat?arg=/ipfs/bafkreifzjut3te2nhyekklss27nh3k72ysco7y32koao5eei66wof36n5e", "/ipfs/bafkreifzjut3te2nhyekklss27nh3k72ysco7y32koao5eei66wof36n5e", http.StatusSeeOther},
{"/api/v0/cat?arg=/ipns/k51qzi5uqu5dgutdk6i1ynyzgkqngpha5xpgia3a5qqp4jsh0u4csozksxel2r", "/ipns/k51qzi5uqu5dgutdk6i1ynyzgkqngpha5xpgia3a5qqp4jsh0u4csozksxel2r", http.StatusSeeOther},

{"/api/v0/dag/get?arg=bafkreifzjut3te2nhyekklss27nh3k72ysco7y32koao5eei66wof36n5e", "/ipfs/bafkreifzjut3te2nhyekklss27nh3k72ysco7y32koao5eei66wof36n5e?format=dag-json", http.StatusSeeOther},
{"/api/v0/dag/get?arg=/ipfs/bafkreifzjut3te2nhyekklss27nh3k72ysco7y32koao5eei66wof36n5e", "/ipfs/bafkreifzjut3te2nhyekklss27nh3k72ysco7y32koao5eei66wof36n5e?format=dag-json", http.StatusSeeOther},
{"/api/v0/dag/get?arg=/ipns/k51qzi5uqu5dgutdk6i1ynyzgkqngpha5xpgia3a5qqp4jsh0u4csozksxel2r", "/ipns/k51qzi5uqu5dgutdk6i1ynyzgkqngpha5xpgia3a5qqp4jsh0u4csozksxel2r?format=dag-json", http.StatusSeeOther},
{"/api/v0/dag/get?arg=bafkreifzjut3te2nhyekklss27nh3k72ysco7y32koao5eei66wof36n5e&output-codec=dag-cbor", "/ipfs/bafkreifzjut3te2nhyekklss27nh3k72ysco7y32koao5eei66wof36n5e?format=dag-cbor", http.StatusSeeOther},

{"/api/v0/dag/export?arg=bafkreifzjut3te2nhyekklss27nh3k72ysco7y32koao5eei66wof36n5e", "/ipfs/bafkreifzjut3te2nhyekklss27nh3k72ysco7y32koao5eei66wof36n5e?format=car", http.StatusSeeOther},
{"/api/v0/dag/export?arg=/ipfs/bafkreifzjut3te2nhyekklss27nh3k72ysco7y32koao5eei66wof36n5e", "/ipfs/bafkreifzjut3te2nhyekklss27nh3k72ysco7y32koao5eei66wof36n5e?format=car", http.StatusSeeOther},
{"/api/v0/dag/export?arg=/ipns/k51qzi5uqu5dgutdk6i1ynyzgkqngpha5xpgia3a5qqp4jsh0u4csozksxel2r", "/ipns/k51qzi5uqu5dgutdk6i1ynyzgkqngpha5xpgia3a5qqp4jsh0u4csozksxel2r?format=car", http.StatusSeeOther},

{"/api/v0/block/get?arg=bafkreifzjut3te2nhyekklss27nh3k72ysco7y32koao5eei66wof36n5e", "/ipfs/bafkreifzjut3te2nhyekklss27nh3k72ysco7y32koao5eei66wof36n5e?format=raw", http.StatusSeeOther},
{"/api/v0/block/get?arg=/ipfs/bafkreifzjut3te2nhyekklss27nh3k72ysco7y32koao5eei66wof36n5e", "/ipfs/bafkreifzjut3te2nhyekklss27nh3k72ysco7y32koao5eei66wof36n5e?format=raw", http.StatusSeeOther},
{"/api/v0/block/get?arg=/ipns/k51qzi5uqu5dgutdk6i1ynyzgkqngpha5xpgia3a5qqp4jsh0u4csozksxel2r", "/ipns/k51qzi5uqu5dgutdk6i1ynyzgkqngpha5xpgia3a5qqp4jsh0u4csozksxel2r?format=raw", http.StatusSeeOther},
}

for _, test := range tests {
for _, method := range []string{http.MethodGet, http.MethodPost} {
req, err := http.NewRequest(method, "http://127.0.0.1"+test.url, nil)
assert.Nil(t, err)
resp := httptest.NewRecorder()
rpcHandler.ServeHTTP(resp, req)

assert.Equal(t, test.status, resp.Code)
assert.Equal(t, test.location, resp.Header().Get("Location"))
}
}
}

func TestRPCRedirectsToKubo(t *testing.T) {
rpcHandler := newKuboRPCHandler([]string{"http://example.com"})

tests := []rpcRedirectTest{
{"/api/v0/name/resolve?arg=some-arg", "http://example.com/api/v0/name/resolve?arg=some-arg", http.StatusTemporaryRedirect},
{"/api/v0/resolve?arg=some-arg", "http://example.com/api/v0/resolve?arg=some-arg", http.StatusTemporaryRedirect},
{"/api/v0/dag/resolve?arg=some-arg", "http://example.com/api/v0/dag/resolve?arg=some-arg", http.StatusTemporaryRedirect},
{"/api/v0/dns?arg=some-arg", "http://example.com/api/v0/dns?arg=some-arg", http.StatusTemporaryRedirect},
}

for _, test := range tests {
for _, method := range []string{http.MethodGet, http.MethodPost} {
req, err := http.NewRequest(method, "http://127.0.0.1"+test.url, nil)
assert.Nil(t, err)
resp := httptest.NewRecorder()
rpcHandler.ServeHTTP(resp, req)

assert.Equal(t, test.status, resp.Code)
assert.Equal(t, test.location, resp.Header().Get("Location"))
}
}
}

func TestRPCNotImplemented(t *testing.T) {
rpcHandler := newKuboRPCHandler([]string{"http://example.com"})

for _, method := range []string{http.MethodGet, http.MethodPost} {
req, err := http.NewRequest(method, "http://127.0.0.1/api/v0/ping", nil)
assert.Nil(t, err)
resp := httptest.NewRecorder()
rpcHandler.ServeHTTP(resp, req)

assert.Equal(t, http.StatusNotImplemented, resp.Code)
}
}
51 changes: 26 additions & 25 deletions handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/ipfs/go-blockservice"
offline "github.com/ipfs/go-ipfs-exchange-offline"
"github.com/ipfs/go-libipfs/gateway"
"github.com/ipfs/interface-go-ipfs-core/path"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
Expand Down Expand Up @@ -60,7 +61,7 @@ func makeGatewayHandler(saturnOrchestrator, saturnLogger string, kuboRPC []strin
mux := http.NewServeMux()
mux.Handle("/ipfs/", gwHandler)
mux.Handle("/ipns/", gwHandler)
mux.Handle("/api/v0/", newAPIHandler(kuboRPC))
mux.Handle("/api/v0/", newKuboRPCHandler(kuboRPC))

// Note: in the future we may want to make this more configurable.
noDNSLink := false
Expand Down Expand Up @@ -102,47 +103,47 @@ func makeGatewayHandler(saturnOrchestrator, saturnLogger string, kuboRPC []strin
}, nil
}

func newAPIHandler(endpoints []string) http.Handler {
func newKuboRPCHandler(endpoints []string) http.Handler {
mux := http.NewServeMux()

// Endpoints that can be redirected to the gateway itself as they can be handled
// by the path gateway.
mux.HandleFunc("/api/v0/cat", func(w http.ResponseWriter, r *http.Request) {
cid := r.URL.Query().Get("arg")
url := fmt.Sprintf("/ipfs/%s", cid)
http.Redirect(w, r, url, http.StatusFound)
})
// by the path gateway. We use 303 See Other here to ensure that the API requests
// are transformed to GET requests to the gateway.
// - https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/303
redirectToGateway := func(format string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
path := path.New(r.URL.Query().Get("arg"))
url := path.String()
if format != "" {
url += "?format=" + format
}
http.Redirect(w, r, url, http.StatusSeeOther)
}
}

mux.HandleFunc("/api/v0/cat", redirectToGateway(""))
mux.HandleFunc("/api/v0/dag/export", redirectToGateway("car"))
mux.HandleFunc("/api/v0/block/get", redirectToGateway("raw"))
mux.HandleFunc("/api/v0/dag/get", func(w http.ResponseWriter, r *http.Request) {
cid := r.URL.Query().Get("arg")
path := path.New(r.URL.Query().Get("arg"))
codec := r.URL.Query().Get("output-codec")
if codec == "" {
codec = "dag-json"
}
url := fmt.Sprintf("/ipfs/%s?format=%s", cid, codec)
http.Redirect(w, r, url, http.StatusFound)
})

mux.HandleFunc("/api/v0/dag/export", func(w http.ResponseWriter, r *http.Request) {
cid := r.URL.Query().Get("arg")
url := fmt.Sprintf("/ipfs/%s?format=car", cid)
http.Redirect(w, r, url, http.StatusFound)
})

mux.HandleFunc("/api/v0/block/get", func(w http.ResponseWriter, r *http.Request) {
cid := r.URL.Query().Get("arg")
url := fmt.Sprintf("/ipfs/%s?format=raw", cid)
http.Redirect(w, r, url, http.StatusFound)
url := fmt.Sprintf("%s?format=%s", path.String(), codec)
http.Redirect(w, r, url, http.StatusSeeOther)
})

// Endpoints that have high traffic volume. We will keep redirecting these
// for now to Kubo endpoints that are able to handle these requests.
// for now to Kubo endpoints that are able to handle these requests. We use
// 307 Temporary Redirect in order to preserve the original HTTP Method.
// - https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/307
s := rand.NewSource(time.Now().Unix())
rand := rand.New(s)
redirectToKubo := func(w http.ResponseWriter, r *http.Request) {
// Naively choose one of the Kubo RPC clients.
endpoint := endpoints[rand.Intn(len(endpoints))]
http.Redirect(w, r, endpoint+r.URL.Path+"?"+r.URL.RawQuery, http.StatusFound)
http.Redirect(w, r, endpoint+r.URL.Path+"?"+r.URL.RawQuery, http.StatusTemporaryRedirect)
}

mux.HandleFunc("/api/v0/name/resolve", redirectToKubo)
Expand Down

0 comments on commit 521d97c

Please sign in to comment.