mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-21 20:26:41 +00:00
215 lines
6.1 KiB
Go
215 lines
6.1 KiB
Go
/*
|
|
A simple http server interface to Swarm
|
|
*/
|
|
package bzz
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"github.com/ethereum/go-ethereum/ethutil"
|
|
"io"
|
|
"net/http"
|
|
"regexp"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
port = ":8500"
|
|
manifest_type = "application/bzz-manifest+json"
|
|
)
|
|
|
|
var (
|
|
uriMatcher = regexp.MustCompile("^/raw/[0-9A-Fa-f]{64}(?:/[a-z]+/[-+0-9a-z]+)?$")
|
|
manifestMatcher = regexp.MustCompile("^/[0-9A-Fa-f]{64}")
|
|
hashMatcher = regexp.MustCompile("^[0-9A-Fa-f]{64}$")
|
|
)
|
|
|
|
type sequentialReader struct {
|
|
reader io.Reader
|
|
pos int64
|
|
ahead map[int64](chan bool)
|
|
lock sync.Mutex
|
|
}
|
|
|
|
type manifestEntry struct {
|
|
Path string
|
|
Hash string
|
|
Content_type string
|
|
Status int16
|
|
}
|
|
|
|
func (self *sequentialReader) ReadAt(target []byte, off int64) (n int, err error) {
|
|
self.lock.Lock()
|
|
// assert self.pos <= off
|
|
if self.pos > off {
|
|
dpaLogger.Errorf("Swarm: non-sequential read attempted from sequentialReader; %d > %d",
|
|
self.pos, off)
|
|
panic("Non-sequential read attempt")
|
|
}
|
|
if self.pos != off {
|
|
dpaLogger.Debugf("Swarm: deferred read in POST at position %d, offset %d.",
|
|
self.pos, off)
|
|
wait := make(chan bool)
|
|
self.ahead[off] = wait
|
|
self.lock.Unlock()
|
|
if <-wait {
|
|
// failed read behind
|
|
n = 0
|
|
err = io.ErrUnexpectedEOF
|
|
return
|
|
}
|
|
self.lock.Lock()
|
|
}
|
|
localPos := 0
|
|
for localPos < len(target) {
|
|
n, err = self.reader.Read(target[localPos:])
|
|
localPos += n
|
|
dpaLogger.Debugf("Swarm: Read %d bytes into buffer size %d from POST, error %v.",
|
|
n, len(target), err)
|
|
if err != nil {
|
|
dpaLogger.Debugf("Swarm: POST stream's reading terminated with %v.", err)
|
|
for i := range self.ahead {
|
|
self.ahead[i] <- true
|
|
delete(self.ahead, i)
|
|
}
|
|
self.lock.Unlock()
|
|
return localPos, err
|
|
}
|
|
self.pos += int64(n)
|
|
}
|
|
wait := self.ahead[self.pos]
|
|
if wait != nil {
|
|
dpaLogger.Debugf("Swarm: deferred read in POST at position %d triggered.",
|
|
self.pos)
|
|
delete(self.ahead, self.pos)
|
|
close(wait)
|
|
}
|
|
self.lock.Unlock()
|
|
return localPos, err
|
|
}
|
|
|
|
func handler(w http.ResponseWriter, r *http.Request, dpa *DPA) {
|
|
uri := r.RequestURI
|
|
switch {
|
|
case r.Method == "POST":
|
|
if uri == "/raw" {
|
|
dpaLogger.Debugf("Swarm: POST request received.")
|
|
key, err := dpa.Store(io.NewSectionReader(&sequentialReader{
|
|
reader: r.Body,
|
|
ahead: make(map[int64]chan bool),
|
|
}, 0, r.ContentLength), nil)
|
|
if err == nil {
|
|
fmt.Fprintf(w, "%064x", key)
|
|
dpaLogger.Debugf("Swarm: Object %064x stored", key)
|
|
} else {
|
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
}
|
|
} else {
|
|
http.Error(w, "No POST to "+uri+" allowed.", http.StatusBadRequest)
|
|
}
|
|
case r.Method == "GET":
|
|
if uriMatcher.MatchString(uri) {
|
|
dpaLogger.Debugf("Swarm: Raw GET request %s received", uri)
|
|
name := uri[5:69]
|
|
key := ethutil.Hex2Bytes(name)
|
|
reader := dpa.Retrieve(key)
|
|
dpaLogger.Debugf("Swarm: Reading %d bytes.", reader.Size())
|
|
mimeType := "application/octet-stream"
|
|
if len(uri) > 70 {
|
|
mimeType = uri[70:]
|
|
}
|
|
w.Header().Set("Content-Type", mimeType)
|
|
http.ServeContent(w, r, name, time.Unix(0, 0), reader)
|
|
dpaLogger.Debugf("Swarm: Object %s returned.", name)
|
|
} else if manifestMatcher.MatchString(uri) {
|
|
dpaLogger.Debugf("Swarm: Structured GET request %s received.", uri)
|
|
name := uri[1:65]
|
|
path := uri[65:] // typically begins with a /
|
|
dpaLogger.Debugf("Swarm: path \"%s\" requested.", path)
|
|
key := ethutil.Hex2Bytes(name)
|
|
MANIFEST_RESOLUTION:
|
|
for {
|
|
manifestReader := dpa.Retrieve(key)
|
|
// TODO check size for oversized manifests
|
|
manifest := make([]byte, manifestReader.Size())
|
|
size, err := manifestReader.Read(manifest)
|
|
if int64(size) < manifestReader.Size() {
|
|
dpaLogger.Debugf("Swarm: Manifest %s not found.", name)
|
|
if err == nil {
|
|
http.Error(w, "Manifest retrieval cut short: "+string(size)+" "+string(manifestReader.Size()),
|
|
http.StatusNotFound)
|
|
} else {
|
|
http.Error(w, err.Error(), http.StatusNotFound)
|
|
}
|
|
return
|
|
}
|
|
dpaLogger.Debugf("Swarm: Manifest %s retrieved.", name)
|
|
manifestEntries := make([]manifestEntry, 0)
|
|
err = json.Unmarshal(manifest, &manifestEntries)
|
|
if err != nil {
|
|
dpaLogger.Debugf("Swarm: Manifest %s is malformed.", name)
|
|
http.Error(w, err.Error(), http.StatusNotFound)
|
|
return
|
|
} else {
|
|
dpaLogger.Debugf("Swarm: Manifest %s has %d entries.", name, len(manifestEntries))
|
|
}
|
|
var mimeType string
|
|
key = nil
|
|
prefix := 0
|
|
status := int16(404)
|
|
MANIFEST_ENTRIES:
|
|
for _, entry := range manifestEntries {
|
|
if !hashMatcher.MatchString(entry.Hash) {
|
|
// hash is mandatory
|
|
continue MANIFEST_ENTRIES
|
|
}
|
|
if entry.Content_type == "" {
|
|
// content type defaults to manifest
|
|
entry.Content_type = manifest_type
|
|
}
|
|
if entry.Status == 0 {
|
|
// status defaults to 200
|
|
entry.Status = 200
|
|
}
|
|
pathLen := len(entry.Path)
|
|
if len(path) >= pathLen && path[:pathLen] == entry.Path && prefix <= pathLen {
|
|
dpaLogger.Debugf("Swarm: \"%s\" matches \"%s\".", path, entry.Path)
|
|
prefix = pathLen
|
|
key = ethutil.Hex2Bytes(entry.Hash)
|
|
dpaLogger.Debugf("Swarm: Payload hash %064x", key)
|
|
mimeType = entry.Content_type
|
|
status = entry.Status
|
|
}
|
|
}
|
|
if key == nil {
|
|
http.Error(w, "Object "+uri+" not found.", http.StatusNotFound)
|
|
break MANIFEST_RESOLUTION
|
|
} else if mimeType != manifest_type {
|
|
w.Header().Set("Content-Type", mimeType)
|
|
dpaLogger.Debugf("Swarm: HTTP Status %d", status)
|
|
w.WriteHeader(int(status))
|
|
reader := dpa.Retrieve(key)
|
|
dpaLogger.Debugf("Swarm: Reading %d bytes.", reader.Size())
|
|
http.ServeContent(w, r, name, time.Unix(0, 0), reader)
|
|
dpaLogger.Debugf("Swarm: Served %s as %s.", mimeType, uri)
|
|
break MANIFEST_RESOLUTION
|
|
} else {
|
|
path = path[prefix:]
|
|
// continue with manifest resolution
|
|
}
|
|
}
|
|
} else {
|
|
http.Error(w, "Object "+uri+" not found.", http.StatusNotFound)
|
|
}
|
|
default:
|
|
http.Error(w, "Method "+r.Method+" is not supported.", http.StatusMethodNotAllowed)
|
|
}
|
|
}
|
|
|
|
func StartHttpServer(dpa *DPA) {
|
|
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
|
handler(w, r, dpa)
|
|
})
|
|
http.ListenAndServe(port, nil)
|
|
}
|