Commit b299c424 authored by Juan Batiz-Benet's avatar Juan Batiz-Benet

Merge pull request #541 from jbenet/gateway-server

Gateway Server
parents cfbe4400 2c83cd56
......@@ -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 {
_, host, err := manet.DialArgs(addr)
origin := os.Getenv(originEnvKey)
cmdHandler := cmdsHttp.NewHandler(*req.Context(), commands.Root, origin)
gateway, err := NewGatewayHandler(node)
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)
mux.Handle("/ipfs/", gateway)
mux.Handle("/webui/", &redirectHandler{webuiPath})
return listenAndServe("API", node, addr, mux)
}
ifpsHandler := &ipfsHandler{node}
mux.Handle("/ipfs/", ifpsHandler)
// 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
}
server := manners.NewServer()
// 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)
}
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"
......@@ -16,38 +19,70 @@ import (
u "github.com/jbenet/go-ipfs/util"
)
type ipfs interface {
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)
}
// ipfsHandler is a HTTP handler that serves IPFS objects (accessible by default at /ipfs/<path>)
// 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 ipfsHandler struct {
node *core.IpfsNode
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 *ipfsHandler) ResolvePath(path string) (*dag.Node, error) {
func (i *gatewayHandler) ResolvePath(path string) (*dag.Node, error) {
return i.node.Resolver.ResolvePath(path)
}
func (i *ipfsHandler) NewDagFromReader(r io.Reader) (*dag.Node, error) {
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 *ipfsHandler) AddNodeToDAG(nd *dag.Node) (u.Key, error) {
func (i *gatewayHandler) AddNodeToDAG(nd *dag.Node) (u.Key, error) {
return i.node.DAG.Add(nd)
}
func (i *ipfsHandler) NewDagReader(nd *dag.Node) (io.Reader, error) {
func (i *gatewayHandler) NewDagReader(nd *dag.Node) (io.Reader, error) {
return uio.NewDagReader(nd, i.node.DAG)
}
func (i *ipfsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
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 {
......@@ -64,19 +99,68 @@ func (i *ipfsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
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 {
// 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()))
if err == nil {
io.Copy(w, dr)
return
}
io.Copy(w, dr)
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 *ipfsHandler) postHandler(w http.ResponseWriter, r *http.Request) {
func (i *gatewayHandler) postHandler(w http.ResponseWriter, r *http.Request) {
nd, err := i.NewDagFromReader(r.Body)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
......@@ -97,3 +181,30 @@ func (i *ipfsHandler) postHandler(w http.ResponseWriter, r *http.Request) {
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>
`
......@@ -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
'
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment