generated from ipfs/ipfs-repository-template
-
Notifications
You must be signed in to change notification settings - Fork 114
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge commits from ipfs/kubo/prepare-gateway-extraction
- Loading branch information
Showing
41 changed files
with
6,741 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,193 @@ | ||
package corehttp | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"net" | ||
"net/http" | ||
"os" | ||
"strconv" | ||
"strings" | ||
|
||
version "github.com/ipfs/kubo" | ||
oldcmds "github.com/ipfs/kubo/commands" | ||
"github.com/ipfs/kubo/core" | ||
corecommands "github.com/ipfs/kubo/core/commands" | ||
|
||
cmds "github.com/ipfs/go-ipfs-cmds" | ||
cmdsHttp "github.com/ipfs/go-ipfs-cmds/http" | ||
path "github.com/ipfs/go-path" | ||
config "github.com/ipfs/kubo/config" | ||
) | ||
|
||
var ( | ||
errAPIVersionMismatch = errors.New("api version mismatch") | ||
) | ||
|
||
const originEnvKey = "API_ORIGIN" | ||
const originEnvKeyDeprecate = `You are using the ` + originEnvKey + `ENV Variable. | ||
This functionality is deprecated, and will be removed in future versions. | ||
Instead, try either adding headers to the config, or passing them via | ||
cli arguments: | ||
ipfs config API.HTTPHeaders --json '{"Access-Control-Allow-Origin": ["*"]}' | ||
ipfs daemon | ||
` | ||
|
||
// APIPath is the path at which the API is mounted. | ||
const APIPath = "/api/v0" | ||
|
||
var defaultLocalhostOrigins = []string{ | ||
"http://127.0.0.1:<port>", | ||
"https://127.0.0.1:<port>", | ||
"http://[::1]:<port>", | ||
"https://[::1]:<port>", | ||
"http://localhost:<port>", | ||
"https://localhost:<port>", | ||
} | ||
|
||
var companionBrowserExtensionOrigins = []string{ | ||
"chrome-extension://nibjojkomfdiaoajekhjakgkdhaomnch", // ipfs-companion | ||
"chrome-extension://hjoieblefckbooibpepigmacodalfndh", // ipfs-companion-beta | ||
} | ||
|
||
func addCORSFromEnv(c *cmdsHttp.ServerConfig) { | ||
origin := os.Getenv(originEnvKey) | ||
if origin != "" { | ||
log.Warn(originEnvKeyDeprecate) | ||
c.AppendAllowedOrigins(origin) | ||
} | ||
} | ||
|
||
func addHeadersFromConfig(c *cmdsHttp.ServerConfig, nc *config.Config) { | ||
log.Info("Using API.HTTPHeaders:", nc.API.HTTPHeaders) | ||
|
||
if acao := nc.API.HTTPHeaders[cmdsHttp.ACAOrigin]; acao != nil { | ||
c.SetAllowedOrigins(acao...) | ||
} | ||
if acam := nc.API.HTTPHeaders[cmdsHttp.ACAMethods]; acam != nil { | ||
c.SetAllowedMethods(acam...) | ||
} | ||
for _, v := range nc.API.HTTPHeaders[cmdsHttp.ACACredentials] { | ||
c.SetAllowCredentials(strings.ToLower(v) == "true") | ||
} | ||
|
||
c.Headers = make(map[string][]string, len(nc.API.HTTPHeaders)+1) | ||
|
||
// Copy these because the config is shared and this function is called | ||
// in multiple places concurrently. Updating these in-place *is* racy. | ||
for h, v := range nc.API.HTTPHeaders { | ||
h = http.CanonicalHeaderKey(h) | ||
switch h { | ||
case cmdsHttp.ACAOrigin, cmdsHttp.ACAMethods, cmdsHttp.ACACredentials: | ||
// these are handled by the CORs library. | ||
default: | ||
c.Headers[h] = v | ||
} | ||
} | ||
c.Headers["Server"] = []string{"kubo/" + version.CurrentVersionNumber} | ||
} | ||
|
||
func addCORSDefaults(c *cmdsHttp.ServerConfig) { | ||
// always safelist certain origins | ||
c.AppendAllowedOrigins(defaultLocalhostOrigins...) | ||
c.AppendAllowedOrigins(companionBrowserExtensionOrigins...) | ||
|
||
// by default, use GET, PUT, POST | ||
if len(c.AllowedMethods()) == 0 { | ||
c.SetAllowedMethods(http.MethodGet, http.MethodPost, http.MethodPut) | ||
} | ||
} | ||
|
||
func patchCORSVars(c *cmdsHttp.ServerConfig, addr net.Addr) { | ||
|
||
// we have to grab the port from an addr, which may be an ip6 addr. | ||
// TODO: this should take multiaddrs and derive port from there. | ||
port := "" | ||
if tcpaddr, ok := addr.(*net.TCPAddr); ok { | ||
port = strconv.Itoa(tcpaddr.Port) | ||
} else if udpaddr, ok := addr.(*net.UDPAddr); ok { | ||
port = strconv.Itoa(udpaddr.Port) | ||
} | ||
|
||
// we're listening on tcp/udp with ports. ("udp!?" you say? yeah... it happens...) | ||
oldOrigins := c.AllowedOrigins() | ||
newOrigins := make([]string, len(oldOrigins)) | ||
for i, o := range oldOrigins { | ||
// TODO: allow replacing <host>. tricky, ip4 and ip6 and hostnames... | ||
if port != "" { | ||
o = strings.Replace(o, "<port>", port, -1) | ||
} | ||
newOrigins[i] = o | ||
} | ||
c.SetAllowedOrigins(newOrigins...) | ||
} | ||
|
||
func commandsOption(cctx oldcmds.Context, command *cmds.Command, allowGet bool) ServeOption { | ||
return func(n *core.IpfsNode, l net.Listener, mux *http.ServeMux) (*http.ServeMux, error) { | ||
|
||
cfg := cmdsHttp.NewServerConfig() | ||
cfg.AllowGet = allowGet | ||
corsAllowedMethods := []string{http.MethodPost} | ||
if allowGet { | ||
corsAllowedMethods = append(corsAllowedMethods, http.MethodGet) | ||
} | ||
|
||
cfg.SetAllowedMethods(corsAllowedMethods...) | ||
cfg.APIPath = APIPath | ||
rcfg, err := n.Repo.Config() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
addHeadersFromConfig(cfg, rcfg) | ||
addCORSFromEnv(cfg) | ||
addCORSDefaults(cfg) | ||
patchCORSVars(cfg, l.Addr()) | ||
|
||
cmdHandler := cmdsHttp.NewHandler(&cctx, command, cfg) | ||
mux.Handle(APIPath+"/", cmdHandler) | ||
return mux, nil | ||
} | ||
} | ||
|
||
// CommandsOption constructs a ServerOption for hooking the commands into the | ||
// HTTP server. It will NOT allow GET requests. | ||
func CommandsOption(cctx oldcmds.Context) ServeOption { | ||
return commandsOption(cctx, corecommands.Root, false) | ||
} | ||
|
||
// CommandsROOption constructs a ServerOption for hooking the read-only commands | ||
// into the HTTP server. It will allow GET requests. | ||
func CommandsROOption(cctx oldcmds.Context) ServeOption { | ||
return commandsOption(cctx, corecommands.RootRO, true) | ||
} | ||
|
||
// CheckVersionOption returns a ServeOption that checks whether the client ipfs version matches. Does nothing when the user agent string does not contain `/kubo/` or `/go-ipfs/` | ||
func CheckVersionOption() ServeOption { | ||
daemonVersion := version.ApiVersion | ||
|
||
return ServeOption(func(n *core.IpfsNode, l net.Listener, parent *http.ServeMux) (*http.ServeMux, error) { | ||
mux := http.NewServeMux() | ||
parent.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { | ||
if strings.HasPrefix(r.URL.Path, APIPath) { | ||
cmdqry := r.URL.Path[len(APIPath):] | ||
pth := path.SplitList(cmdqry) | ||
|
||
// backwards compatibility to previous version check | ||
if len(pth) >= 2 && pth[1] != "version" { | ||
clientVersion := r.UserAgent() | ||
// skips check if client is not kubo (go-ipfs) | ||
if (strings.Contains(clientVersion, "/go-ipfs/") || strings.Contains(clientVersion, "/kubo/")) && daemonVersion != clientVersion { | ||
http.Error(w, fmt.Sprintf("%s (%s != %s)", errAPIVersionMismatch, daemonVersion, clientVersion), http.StatusBadRequest) | ||
return | ||
} | ||
} | ||
} | ||
|
||
mux.ServeHTTP(w, r) | ||
}) | ||
|
||
return mux, nil | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
/* | ||
Package corehttp provides utilities for the webui, gateways, and other | ||
high-level HTTP interfaces to IPFS. | ||
*/ | ||
package corehttp | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"net" | ||
"net/http" | ||
"time" | ||
|
||
logging "github.com/ipfs/go-log" | ||
core "github.com/ipfs/kubo/core" | ||
"github.com/jbenet/goprocess" | ||
periodicproc "github.com/jbenet/goprocess/periodic" | ||
ma "github.com/multiformats/go-multiaddr" | ||
manet "github.com/multiformats/go-multiaddr/net" | ||
) | ||
|
||
var log = logging.Logger("core/server") | ||
|
||
// shutdownTimeout is the timeout after which we'll stop waiting for hung | ||
// commands to return on shutdown. | ||
const shutdownTimeout = 30 * time.Second | ||
|
||
// ServeOption registers any HTTP handlers it provides on the given mux. | ||
// It returns the mux to expose to future options, which may be a new mux if it | ||
// is interested in mediating requests to future options, or the same mux | ||
// initially passed in if not. | ||
type ServeOption func(*core.IpfsNode, net.Listener, *http.ServeMux) (*http.ServeMux, error) | ||
|
||
// makeHandler turns a list of ServeOptions into a http.Handler that implements | ||
// all of the given options, in order. | ||
func makeHandler(n *core.IpfsNode, l net.Listener, options ...ServeOption) (http.Handler, error) { | ||
topMux := http.NewServeMux() | ||
mux := topMux | ||
for _, option := range options { | ||
var err error | ||
mux, err = option(n, l, mux) | ||
if err != nil { | ||
return nil, err | ||
} | ||
} | ||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
// ServeMux does not support requests with CONNECT method, | ||
// so we need to handle them separately | ||
// https://golang.org/src/net/http/request.go#L111 | ||
if r.Method == http.MethodConnect { | ||
w.WriteHeader(http.StatusOK) | ||
return | ||
} | ||
topMux.ServeHTTP(w, r) | ||
}) | ||
return handler, nil | ||
} | ||
|
||
// ListenAndServe runs an HTTP server listening at |listeningMultiAddr| with | ||
// the given serve options. The address must be provided in multiaddr format. | ||
// | ||
// TODO intelligently parse address strings in other formats so long as they | ||
// unambiguously map to a valid multiaddr. e.g. for convenience, ":8080" should | ||
// map to "/ip4/0.0.0.0/tcp/8080". | ||
func ListenAndServe(n *core.IpfsNode, listeningMultiAddr string, options ...ServeOption) error { | ||
addr, err := ma.NewMultiaddr(listeningMultiAddr) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
list, err := manet.Listen(addr) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// we might have listened to /tcp/0 - let's see what we are listing on | ||
addr = list.Multiaddr() | ||
fmt.Printf("API server listening on %s\n", addr) | ||
|
||
return Serve(n, manet.NetListener(list), options...) | ||
} | ||
|
||
// Serve accepts incoming HTTP connections on the listener and pass them | ||
// to ServeOption handlers. | ||
func Serve(node *core.IpfsNode, lis net.Listener, options ...ServeOption) error { | ||
// make sure we close this no matter what. | ||
defer lis.Close() | ||
|
||
handler, err := makeHandler(node, lis, options...) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
addr, err := manet.FromNetAddr(lis.Addr()) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
select { | ||
case <-node.Process.Closing(): | ||
return fmt.Errorf("failed to start server, process closing") | ||
default: | ||
} | ||
|
||
server := &http.Server{ | ||
Handler: handler, | ||
} | ||
|
||
var serverError error | ||
serverProc := node.Process.Go(func(p goprocess.Process) { | ||
serverError = server.Serve(lis) | ||
}) | ||
|
||
// wait for server to exit. | ||
select { | ||
case <-serverProc.Closed(): | ||
// if node being closed before server exits, close server | ||
case <-node.Process.Closing(): | ||
log.Infof("server at %s terminating...", addr) | ||
|
||
warnProc := periodicproc.Tick(5*time.Second, func(_ goprocess.Process) { | ||
log.Infof("waiting for server at %s to terminate...", addr) | ||
}) | ||
|
||
// This timeout shouldn't be necessary if all of our commands | ||
// are obeying their contexts but we should have *some* timeout. | ||
ctx, cancel := context.WithTimeout(context.Background(), shutdownTimeout) | ||
defer cancel() | ||
err := server.Shutdown(ctx) | ||
|
||
// Should have already closed but we still need to wait for it | ||
// to set the error. | ||
<-serverProc.Closed() | ||
serverError = err | ||
|
||
warnProc.Close() | ||
} | ||
|
||
log.Infof("server at %s terminated", addr) | ||
return serverError | ||
} |
Oops, something went wrong.