mirror of
https://github.com/ipfs/kubo.git
synced 2025-07-01 02:30:39 +08:00
@ -24,7 +24,10 @@ const (
|
|||||||
ipnsMountKwd = "mount-ipns"
|
ipnsMountKwd = "mount-ipns"
|
||||||
// apiAddrKwd = "address-api"
|
// apiAddrKwd = "address-api"
|
||||||
// swarmAddrKwd = "address-swarm"
|
// swarmAddrKwd = "address-swarm"
|
||||||
|
|
||||||
originEnvKey = "API_ORIGIN"
|
originEnvKey = "API_ORIGIN"
|
||||||
|
|
||||||
|
webuiPath = "/ipfs/QmTWvqK9dYvqjAMAcCeUun8b45Fwu7wPhEN9B9TsGbkXfJ"
|
||||||
)
|
)
|
||||||
|
|
||||||
var daemonCmd = &cmds.Command{
|
var daemonCmd = &cmds.Command{
|
||||||
@ -108,6 +111,16 @@ func daemonFunc(req cmds.Request) (interface{}, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var gatewayMaddr ma.Multiaddr
|
||||||
|
if len(cfg.Addresses.Gateway) > 0 {
|
||||||
|
// ignore error for gateway address
|
||||||
|
// if there is an error (invalid address), then don't run the gateway
|
||||||
|
gatewayMaddr, _ = ma.NewMultiaddr(cfg.Addresses.Gateway)
|
||||||
|
if gatewayMaddr == nil {
|
||||||
|
log.Errorf("Invalid gateway address: %s", cfg.Addresses.Gateway)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// mount if the user provided the --mount flag
|
// mount if the user provided the --mount flag
|
||||||
mount, _, err := req.Option(mountKwd).Bool()
|
mount, _, err := req.Option(mountKwd).Bool()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -138,32 +151,54 @@ func daemonFunc(req cmds.Request) (interface{}, error) {
|
|||||||
fmt.Printf("IPNS mounted at: %s\n", nsdir)
|
fmt.Printf("IPNS mounted at: %s\n", nsdir)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if gatewayMaddr != nil {
|
||||||
|
listenAndServeGateway(node, gatewayMaddr)
|
||||||
|
}
|
||||||
|
|
||||||
return nil, listenAndServeAPI(node, req, apiMaddr)
|
return nil, listenAndServeAPI(node, req, apiMaddr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func listenAndServeAPI(node *core.IpfsNode, req cmds.Request, addr ma.Multiaddr) error {
|
func listenAndServeAPI(node *core.IpfsNode, req cmds.Request, addr ma.Multiaddr) error {
|
||||||
|
origin := os.Getenv(originEnvKey)
|
||||||
|
cmdHandler := cmdsHttp.NewHandler(*req.Context(), commands.Root, origin)
|
||||||
|
gateway, err := NewGatewayHandler(node)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
mux.Handle(cmdsHttp.ApiPath+"/", cmdHandler)
|
||||||
|
mux.Handle("/ipfs/", gateway)
|
||||||
|
mux.Handle("/webui/", &redirectHandler{webuiPath})
|
||||||
|
return listenAndServe("API", node, addr, mux)
|
||||||
|
}
|
||||||
|
|
||||||
|
// the gateway also listens on its own address:port in addition to the API listener
|
||||||
|
func listenAndServeGateway(node *core.IpfsNode, addr ma.Multiaddr) error {
|
||||||
|
gateway, err := NewGatewayHandler(node)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
mux.Handle("/ipfs/", gateway)
|
||||||
|
return listenAndServe("gateway", node, addr, mux)
|
||||||
|
}
|
||||||
|
|
||||||
|
func listenAndServe(name string, node *core.IpfsNode, addr ma.Multiaddr, mux *http.ServeMux) error {
|
||||||
_, host, err := manet.DialArgs(addr)
|
_, host, err := manet.DialArgs(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
origin := os.Getenv(originEnvKey)
|
|
||||||
|
|
||||||
server := manners.NewServer()
|
server := manners.NewServer()
|
||||||
mux := http.NewServeMux()
|
|
||||||
cmdHandler := cmdsHttp.NewHandler(*req.Context(), commands.Root, origin)
|
|
||||||
mux.Handle(cmdsHttp.ApiPath+"/", cmdHandler)
|
|
||||||
|
|
||||||
ifpsHandler := &ipfsHandler{node}
|
|
||||||
mux.Handle("/ipfs/", ifpsHandler)
|
|
||||||
|
|
||||||
// if the server exits beforehand
|
// if the server exits beforehand
|
||||||
var serverError error
|
var serverError error
|
||||||
serverExited := make(chan struct{})
|
serverExited := make(chan struct{})
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
fmt.Printf("daemon listening on %s\n", addr)
|
fmt.Printf("%s server listening on %s\n", name, addr)
|
||||||
serverError = server.ListenAndServe(host, mux)
|
serverError = server.ListenAndServe(host, mux)
|
||||||
close(serverExited)
|
close(serverExited)
|
||||||
}()
|
}()
|
||||||
@ -174,11 +209,19 @@ func listenAndServeAPI(node *core.IpfsNode, req cmds.Request, addr ma.Multiaddr)
|
|||||||
|
|
||||||
// if node being closed before server exits, close server
|
// if node being closed before server exits, close server
|
||||||
case <-node.Closing():
|
case <-node.Closing():
|
||||||
log.Infof("daemon at %s terminating...", addr)
|
log.Infof("server at %s terminating...", addr)
|
||||||
server.Shutdown <- true
|
server.Shutdown <- true
|
||||||
<-serverExited // now, DO wait until server exits
|
<-serverExited // now, DO wait until server exit
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Infof("daemon at %s terminated", addr)
|
log.Infof("server at %s terminated", addr)
|
||||||
return serverError
|
return serverError
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type redirectHandler struct {
|
||||||
|
path string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *redirectHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
http.Redirect(w, r, i.path, 302)
|
||||||
|
}
|
||||||
|
210
cmd/ipfs/gatewayHandler.go
Normal file
210
cmd/ipfs/gatewayHandler.go
Normal file
@ -0,0 +1,210 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"html/template"
|
||||||
|
"io"
|
||||||
|
"mime"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go.net/context"
|
||||||
|
mh "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multihash"
|
||||||
|
|
||||||
|
core "github.com/jbenet/go-ipfs/core"
|
||||||
|
"github.com/jbenet/go-ipfs/importer"
|
||||||
|
chunk "github.com/jbenet/go-ipfs/importer/chunk"
|
||||||
|
dag "github.com/jbenet/go-ipfs/merkledag"
|
||||||
|
"github.com/jbenet/go-ipfs/routing"
|
||||||
|
uio "github.com/jbenet/go-ipfs/unixfs/io"
|
||||||
|
u "github.com/jbenet/go-ipfs/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
type gateway interface {
|
||||||
|
ResolvePath(string) (*dag.Node, error)
|
||||||
|
NewDagFromReader(io.Reader) (*dag.Node, error)
|
||||||
|
AddNodeToDAG(nd *dag.Node) (u.Key, error)
|
||||||
|
NewDagReader(nd *dag.Node) (io.Reader, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// shortcut for templating
|
||||||
|
type webHandler map[string]interface{}
|
||||||
|
|
||||||
|
// struct for directory listing
|
||||||
|
type directoryItem struct {
|
||||||
|
Size uint64
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// gatewayHandler is a HTTP handler that serves IPFS objects (accessible by default at /ipfs/<path>)
|
||||||
|
// (it serves requests like GET /ipfs/QmVRzPKPzNtSrEzBFm2UZfxmPAgnaLke4DMcerbsGGSaFe/link)
|
||||||
|
type gatewayHandler struct {
|
||||||
|
node *core.IpfsNode
|
||||||
|
dirList *template.Template
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGatewayHandler(node *core.IpfsNode) (*gatewayHandler, error) {
|
||||||
|
i := &gatewayHandler{
|
||||||
|
node: node,
|
||||||
|
}
|
||||||
|
err := i.loadTemplate()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return i, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the directroy list template
|
||||||
|
func (i *gatewayHandler) loadTemplate() error {
|
||||||
|
t, err := template.New("dir").Parse(listingTemplate)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
i.dirList = t
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *gatewayHandler) ResolvePath(path string) (*dag.Node, error) {
|
||||||
|
return i.node.Resolver.ResolvePath(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *gatewayHandler) NewDagFromReader(r io.Reader) (*dag.Node, error) {
|
||||||
|
return importer.BuildDagFromReader(
|
||||||
|
r, i.node.DAG, i.node.Pinning.GetManual(), chunk.DefaultSplitter)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *gatewayHandler) AddNodeToDAG(nd *dag.Node) (u.Key, error) {
|
||||||
|
return i.node.DAG.Add(nd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *gatewayHandler) NewDagReader(nd *dag.Node) (io.Reader, error) {
|
||||||
|
return uio.NewDagReader(nd, i.node.DAG)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *gatewayHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
path := r.URL.Path[5:]
|
||||||
|
log := log.Prefix("serving %s", path)
|
||||||
|
|
||||||
|
nd, err := i.ResolvePath(path)
|
||||||
|
if err != nil {
|
||||||
|
if err == routing.ErrNotFound {
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
} else if err == context.DeadlineExceeded {
|
||||||
|
w.WriteHeader(http.StatusRequestTimeout)
|
||||||
|
} else {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Error(err)
|
||||||
|
w.Write([]byte(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
extensionIndex := strings.LastIndex(path, ".")
|
||||||
|
if extensionIndex != -1 {
|
||||||
|
extension := path[extensionIndex:]
|
||||||
|
mimeType := mime.TypeByExtension(extension)
|
||||||
|
if len(mimeType) > 0 {
|
||||||
|
w.Header().Add("Content-Type", mimeType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dr, err := i.NewDagReader(nd)
|
||||||
|
if err == nil {
|
||||||
|
io.Copy(w, dr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != uio.ErrIsDir {
|
||||||
|
// not a directory and still an error
|
||||||
|
internalWebError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debug("listing directory")
|
||||||
|
if path[len(path)-1:] != "/" {
|
||||||
|
log.Debug("missing trailing slash, redirect")
|
||||||
|
http.Redirect(w, r, "/ipfs/"+path+"/", 307)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// storage for directory listing
|
||||||
|
var dirListing []directoryItem
|
||||||
|
// loop through files
|
||||||
|
for _, link := range nd.Links {
|
||||||
|
if link.Name != "index.html" {
|
||||||
|
dirListing = append(dirListing, directoryItem{link.Size, link.Name})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debug("found index")
|
||||||
|
// return index page instead.
|
||||||
|
nd, err := i.ResolvePath(path + "/index.html")
|
||||||
|
if err != nil {
|
||||||
|
internalWebError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dr, err := i.NewDagReader(nd)
|
||||||
|
if err != nil {
|
||||||
|
internalWebError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write to request
|
||||||
|
io.Copy(w, dr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// template and return directory listing
|
||||||
|
hndlr := webHandler{"listing": dirListing, "path": path}
|
||||||
|
if err := i.dirList.Execute(w, hndlr); err != nil {
|
||||||
|
internalWebError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *gatewayHandler) postHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
nd, err := i.NewDagFromReader(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
log.Error(err)
|
||||||
|
w.Write([]byte(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
k, err := i.AddNodeToDAG(nd)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
log.Error(err)
|
||||||
|
w.Write([]byte(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: return json representation of list instead
|
||||||
|
w.WriteHeader(http.StatusCreated)
|
||||||
|
w.Write([]byte(mh.Multihash(k).B58String()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// return a 500 error and log
|
||||||
|
func internalWebError(w http.ResponseWriter, err error) {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
w.Write([]byte(err.Error()))
|
||||||
|
log.Error("%s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Directory listing template
|
||||||
|
var listingTemplate = `
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>{{ .path }}</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h2>Index of {{ .path }}</h2>
|
||||||
|
<ul>
|
||||||
|
<li><a href="./..">..</a></li>
|
||||||
|
{{ range $item := .listing }}
|
||||||
|
<li><a href="./{{ $item.Name }}">{{ $item.Name }}</a> - {{ $item.Size }} bytes</li>
|
||||||
|
{{ end }}
|
||||||
|
</ul>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`
|
@ -1,99 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go.net/context"
|
|
||||||
mh "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multihash"
|
|
||||||
|
|
||||||
core "github.com/jbenet/go-ipfs/core"
|
|
||||||
"github.com/jbenet/go-ipfs/importer"
|
|
||||||
chunk "github.com/jbenet/go-ipfs/importer/chunk"
|
|
||||||
dag "github.com/jbenet/go-ipfs/merkledag"
|
|
||||||
"github.com/jbenet/go-ipfs/routing"
|
|
||||||
uio "github.com/jbenet/go-ipfs/unixfs/io"
|
|
||||||
u "github.com/jbenet/go-ipfs/util"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ipfs interface {
|
|
||||||
ResolvePath(string) (*dag.Node, error)
|
|
||||||
NewDagFromReader(io.Reader) (*dag.Node, error)
|
|
||||||
AddNodeToDAG(nd *dag.Node) (u.Key, error)
|
|
||||||
NewDagReader(nd *dag.Node) (io.Reader, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ipfsHandler is a HTTP handler that serves IPFS objects (accessible by default at /ipfs/<path>)
|
|
||||||
// (it serves requests like GET /ipfs/QmVRzPKPzNtSrEzBFm2UZfxmPAgnaLke4DMcerbsGGSaFe/link)
|
|
||||||
type ipfsHandler struct {
|
|
||||||
node *core.IpfsNode
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *ipfsHandler) ResolvePath(path string) (*dag.Node, error) {
|
|
||||||
return i.node.Resolver.ResolvePath(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *ipfsHandler) NewDagFromReader(r io.Reader) (*dag.Node, error) {
|
|
||||||
return importer.BuildDagFromReader(
|
|
||||||
r, i.node.DAG, i.node.Pinning.GetManual(), chunk.DefaultSplitter)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *ipfsHandler) AddNodeToDAG(nd *dag.Node) (u.Key, error) {
|
|
||||||
return i.node.DAG.Add(nd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *ipfsHandler) NewDagReader(nd *dag.Node) (io.Reader, error) {
|
|
||||||
return uio.NewDagReader(nd, i.node.DAG)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *ipfsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
||||||
path := r.URL.Path[5:]
|
|
||||||
|
|
||||||
nd, err := i.ResolvePath(path)
|
|
||||||
if err != nil {
|
|
||||||
if err == routing.ErrNotFound {
|
|
||||||
w.WriteHeader(http.StatusNotFound)
|
|
||||||
} else if err == context.DeadlineExceeded {
|
|
||||||
w.WriteHeader(http.StatusRequestTimeout)
|
|
||||||
} else {
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Error(err)
|
|
||||||
w.Write([]byte(err.Error()))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
dr, err := i.NewDagReader(nd)
|
|
||||||
if err != nil {
|
|
||||||
// TODO: return json object containing the tree data if it's a directory (err == ErrIsDir)
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
log.Error(err)
|
|
||||||
w.Write([]byte(err.Error()))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
io.Copy(w, dr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *ipfsHandler) postHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
nd, err := i.NewDagFromReader(r.Body)
|
|
||||||
if err != nil {
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
log.Error(err)
|
|
||||||
w.Write([]byte(err.Error()))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
k, err := i.AddNodeToDAG(nd)
|
|
||||||
if err != nil {
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
log.Error(err)
|
|
||||||
w.Write([]byte(err.Error()))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO: return json representation of list instead
|
|
||||||
w.WriteHeader(http.StatusCreated)
|
|
||||||
w.Write([]byte(mh.Multihash(k).B58String()))
|
|
||||||
}
|
|
@ -42,6 +42,7 @@ type Datastore struct {
|
|||||||
type Addresses struct {
|
type Addresses struct {
|
||||||
Swarm []string // addresses for the swarm network
|
Swarm []string // addresses for the swarm network
|
||||||
API string // address for the local API (RPC)
|
API string // address for the local API (RPC)
|
||||||
|
Gateway string // address to listen on for IPFS HTTP object gateway
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mounts stores the (string) mount points
|
// Mounts stores the (string) mount points
|
||||||
|
@ -88,7 +88,7 @@ test_launch_ipfs_daemon() {
|
|||||||
|
|
||||||
test_expect_success FUSE "'ipfs daemon' output looks good" '
|
test_expect_success FUSE "'ipfs daemon' output looks good" '
|
||||||
IPFS_PID=$! &&
|
IPFS_PID=$! &&
|
||||||
echo "daemon listening on /ip4/127.0.0.1/tcp/5001" >expected &&
|
echo "API server listening on /ip4/127.0.0.1/tcp/5001" >expected &&
|
||||||
test_cmp_repeat_10_sec expected actual ||
|
test_cmp_repeat_10_sec expected actual ||
|
||||||
fsh cat daemon_err
|
fsh cat daemon_err
|
||||||
'
|
'
|
||||||
|
Reference in New Issue
Block a user