mirror of
https://github.com/ipfs/kubo.git
synced 2025-06-30 18:13:54 +08:00
@ -24,7 +24,10 @@ const (
|
||||
ipnsMountKwd = "mount-ipns"
|
||||
// apiAddrKwd = "address-api"
|
||||
// swarmAddrKwd = "address-swarm"
|
||||
|
||||
originEnvKey = "API_ORIGIN"
|
||||
|
||||
webuiPath = "/ipfs/QmTWvqK9dYvqjAMAcCeUun8b45Fwu7wPhEN9B9TsGbkXfJ"
|
||||
)
|
||||
|
||||
var daemonCmd = &cmds.Command{
|
||||
@ -108,6 +111,16 @@ func daemonFunc(req cmds.Request) (interface{}, error) {
|
||||
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, _, err := req.Option(mountKwd).Bool()
|
||||
if err != nil {
|
||||
@ -138,32 +151,54 @@ func daemonFunc(req cmds.Request) (interface{}, error) {
|
||||
fmt.Printf("IPNS mounted at: %s\n", nsdir)
|
||||
}
|
||||
|
||||
if gatewayMaddr != nil {
|
||||
listenAndServeGateway(node, gatewayMaddr)
|
||||
}
|
||||
|
||||
return nil, listenAndServeAPI(node, req, apiMaddr)
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
origin := os.Getenv(originEnvKey)
|
||||
|
||||
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
|
||||
var serverError error
|
||||
serverExited := make(chan struct{})
|
||||
|
||||
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)
|
||||
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
|
||||
case <-node.Closing():
|
||||
log.Infof("daemon at %s terminating...", addr)
|
||||
log.Infof("server at %s terminating...", addr)
|
||||
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
|
||||
}
|
||||
|
||||
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()))
|
||||
}
|
@ -40,8 +40,9 @@ type Datastore struct {
|
||||
|
||||
// Addresses stores the (string) multiaddr addresses for the node.
|
||||
type Addresses struct {
|
||||
Swarm []string // addresses for the swarm network
|
||||
API string // address for the local API (RPC)
|
||||
Swarm []string // addresses for the swarm network
|
||||
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
|
||||
|
@ -88,7 +88,7 @@ test_launch_ipfs_daemon() {
|
||||
|
||||
test_expect_success FUSE "'ipfs daemon' output looks good" '
|
||||
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 ||
|
||||
fsh cat daemon_err
|
||||
'
|
||||
|
Reference in New Issue
Block a user