http access: fix regression and improve

- correct slash handling and path matching in manifest
- fix subpath match and use deepest path instead of longest for matching
- http POST: give key as http response instead of fmt.Printf
- improved logging
This commit is contained in:
zelig 2015-05-30 11:06:45 +01:00
parent 38d1bdf688
commit 021182f822
3 changed files with 53 additions and 31 deletions

View file

@ -300,35 +300,35 @@ func (self *Api) getPath(uri string) (reader SectionReader, mimeType string, sta
var size int var size int
size, err = manifestReader.Read(manifestData) size, err = manifestReader.Read(manifestData)
if int64(size) < manifestReader.Size() { if int64(size) < manifestReader.Size() {
dpaLogger.Debugf("Swarm: Manifest for '%s' not found.", uri) dpaLogger.Debugf("Swarm: Manifest for '%s' not found (uri: '%s')", path[:pos], uri)
if err == nil { if err == nil {
err = fmt.Errorf("Manifest retrieval cut short: %v &lt; %v", size, manifestReader.Size()) err = fmt.Errorf("Manifest retrieval cut short: %v &lt; %v", size, manifestReader.Size())
} }
return return
} }
dpaLogger.Debugf("Swarm: Manifest for '%s' retrieved", uri) dpaLogger.Debugf("Swarm: Manifest for '%s' retrieved", path[:pos])
man := manifest{} man := manifest{}
err = json.Unmarshal(manifestData, &man) err = json.Unmarshal(manifestData, &man)
if err != nil { if err != nil {
err = fmt.Errorf("Manifest for '%s' is malformed: %v", uri, err) err = fmt.Errorf("Manifest for '%s' is malformed: %v", path[:pos], err)
dpaLogger.Debugf("Swarm: %v", err) dpaLogger.Debugf("Swarm: %v", err)
return return
} }
dpaLogger.Debugf("Swarm: Manifest for '%s' has %d entries. Retrieving entry for '%s'", uri, len(man.Entries), path) dpaLogger.Debugf("Swarm: Manifest for '%s' has %d entries. Match path '%s'", path[:pos], len(man.Entries), path[pos:])
// retrieve entry that matches path from manifest entries // retrieve entry that matches path from manifest entries
var entry *manifestEntry entry, hop := man.getEntry(path[pos:])
entry, pos = man.getEntry(path)
if entry == nil { if entry == nil {
err = fmt.Errorf("Content for '%s' not found.", uri) err = fmt.Errorf("Path '%s' not found on manifest '%s'", path[:pos], path[pos:])
return return
} }
// check hash of entry // check hash of entry
if !hashMatcher.MatchString(entry.Hash) { if !hashMatcher.MatchString(entry.Hash) {
err = fmt.Errorf("Incorrect hash '%064x' for '%s'", entry.Hash, uri) err = fmt.Errorf("Incorrect hash '%064x' for path '%s' on manifest for '%s')", entry.Hash, path[pos:], path[:pos])
return return
} }
key = common.Hex2Bytes(entry.Hash) key = common.Hex2Bytes(entry.Hash)
@ -349,7 +349,7 @@ func (self *Api) getPath(uri string) (reader SectionReader, mimeType string, sta
} }
// otherwise continue along the path with manifest resolution // otherwise continue along the path with manifest resolution
path = path[pos:] pos += hop
} }
return return
} }

View file

@ -4,22 +4,24 @@ A simple http server interface to Swarm
package bzz package bzz
import ( import (
"fmt" "bytes"
"io" "io"
"net/http" "net/http"
"regexp" "regexp"
"sync" "sync"
"time" "time"
"github.com/ethereum/go-ethereum/common"
) )
const ( const (
notFoundStatus = 404
rawType = "application/octet-stream" rawType = "application/octet-stream"
) )
var ( var (
rawUrl = regexp.MustCompile("^/+raw/*") rawUrl = regexp.MustCompile("^/+raw/*")
trailingSlashes = regexp.MustCompile("/+$") trailingSlashes = regexp.MustCompile("/+$")
forever = time.Unix(0, 0)
) )
type sequentialReader struct { type sequentialReader struct {
@ -40,8 +42,9 @@ func startHttpServer(api *Api, port string) {
} }
func handler(w http.ResponseWriter, r *http.Request, api *Api) { func handler(w http.ResponseWriter, r *http.Request, api *Api) {
dpaLogger.Debugf("request URL: '%s' Host: '%s', Path: '%s'", r.RequestURI, r.URL.Host, r.URL.Path) requestURL := r.URL
uri := r.URL.Path dpaLogger.Debugf("Swarm: HTTP request URL: '%s', Host: '%s', Path: '%s', Referer: '%s', Accept: '%s'", r.RequestURI, requestURL.Host, requestURL.Path, r.Referer(), r.Header.Get("Accept"))
uri := requestURL.Path
var raw bool var raw bool
path := rawUrl.ReplaceAllStringFunc(uri, func(string) string { path := rawUrl.ReplaceAllStringFunc(uri, func(string) string {
raw = true raw = true
@ -50,7 +53,6 @@ func handler(w http.ResponseWriter, r *http.Request, api *Api) {
switch { switch {
case r.Method == "POST": case r.Method == "POST":
dpaLogger.Debugf("request URL Host: '%s', Path: '%s'", r.URL.Host, r.URL.Path)
if raw { if raw {
dpaLogger.Debugf("Swarm: POST request received.") dpaLogger.Debugf("Swarm: POST request received.")
key, err := api.dpa.Store(io.NewSectionReader(&sequentialReader{ key, err := api.dpa.Store(io.NewSectionReader(&sequentialReader{
@ -58,7 +60,8 @@ func handler(w http.ResponseWriter, r *http.Request, api *Api) {
ahead: make(map[int64]chan bool), ahead: make(map[int64]chan bool),
}, 0, r.ContentLength), nil) }, 0, r.ContentLength), nil)
if err == nil { if err == nil {
fmt.Fprintf(w, "%064x", key) w.Header().Set("Content-Type", "text/plain")
http.ServeContent(w, r, "", time.Now(), bytes.NewReader([]byte(common.Bytes2Hex(key))))
dpaLogger.Debugf("Swarm: Content for '%064x' stored", key) dpaLogger.Debugf("Swarm: Content for '%064x' stored", key)
} else { } else {
http.Error(w, err.Error(), http.StatusBadRequest) http.Error(w, err.Error(), http.StatusBadRequest)
@ -70,7 +73,6 @@ func handler(w http.ResponseWriter, r *http.Request, api *Api) {
} }
case r.Method == "GET" || r.Method == "HEAD": case r.Method == "GET" || r.Method == "HEAD":
dpaLogger.Debugf("request URL Host: '%s', Path: '%s'", r.URL.Host, uri)
path = trailingSlashes.ReplaceAllString(path, "") path = trailingSlashes.ReplaceAllString(path, "")
if raw { if raw {
dpaLogger.Debugf("Swarm: Raw GET request '%s' received", uri) dpaLogger.Debugf("Swarm: Raw GET request '%s' received", uri)
@ -88,14 +90,14 @@ func handler(w http.ResponseWriter, r *http.Request, api *Api) {
dpaLogger.Debugf("Swarm: Reading %d bytes.", reader.Size()) dpaLogger.Debugf("Swarm: Reading %d bytes.", reader.Size())
// setting mime type // setting mime type
qv := r.URL.Query() qv := requestURL.Query()
mimeType := qv.Get("content_type") mimeType := qv.Get("content_type")
if mimeType == "" { if mimeType == "" {
mimeType = rawType mimeType = rawType
} }
w.Header().Set("Content-Type", mimeType) w.Header().Set("Content-Type", mimeType)
http.ServeContent(w, r, uri, time.Unix(0, 0), reader) http.ServeContent(w, r, uri, forever, reader)
dpaLogger.Debugf("Swarm: Serve raw content '%s' (%d bytes) as '%s'", uri, reader.Size(), mimeType) dpaLogger.Debugf("Swarm: Serve raw content '%s' (%d bytes) as '%s'", uri, reader.Size(), mimeType)
// retrieve path via manifest // retrieve path via manifest
@ -121,9 +123,12 @@ func handler(w http.ResponseWriter, r *http.Request, api *Api) {
w.Header().Set("Content-Type", mimeType) w.Header().Set("Content-Type", mimeType)
if status > 0 { if status > 0 {
w.WriteHeader(status) w.WriteHeader(status)
} else {
status = 200
} }
dpaLogger.Debugf("Swarm: Served '%s' (%d bytes) as '%s' (status code: %v)", uri, reader.Size(), mimeType, w.Header()) dpaLogger.Debugf("Swarm: Served '%s' (%d bytes) as '%s' (status code: %v)", uri, reader.Size(), mimeType, status)
http.ServeContent(w, r, uri, time.Unix(0, 0), reader)
http.ServeContent(w, r, path, forever, reader)
} }
default: default:

View file

@ -24,24 +24,41 @@ type manifestEntry struct {
Status int `json:"status"` Status int `json:"status"`
} }
func (self *manifest) getEntry(path string) (entry *manifestEntry, pos int) { // path must not have any leading slashes
func (self *manifest) getEntry(path string) (matchingEntry *manifestEntry, matchlength int) {
var pos, depth, maxdepth int
var entry *manifestEntry
// path := leadingSlashes.ReplaceAllString(fullpath, "")
// iterate over entries matching paths to the target
// due to redundant slashes, it is NOT the longest match but the match with
// the highest depth is chosen
// this gives thse matches in case of trailing slashes:
// "a/" -> "a/" not "a" and "a" matches "a" not "a/" if both exist
// "a" never matches "a/" but "a/" matches a
for _, entry = range self.Entries { for _, entry = range self.Entries {
entryPath := leadingSlashes.ReplaceAllString(entry.Path, "") entryPath := leadingSlashes.ReplaceAllString(entry.Path, "")
pos = len(entryPath) pos = len(entryPath)
if len(path) >= pos && path[:pos] == entryPath { depth = len(slashes.Split(entryPath, -1))
var n int if len(path) >= pos && path[:pos] == entryPath && (depth > maxdepth || depth == maxdepth && pos > matchlength) {
var hop int
// hop and chop leading hashes of the continuation
if len(path) > pos { if len(path) > pos {
chopped := leadingSlashes.ReplaceAllString(path[pos:], "") chopped := leadingSlashes.ReplaceAllString(path[pos:], "")
n = len(path) - pos - len(chopped) hop = len(path) - pos - len(chopped)
} }
if n > 0 || pos == 0 || path[pos-1] == '/' { // check if pos actually ends a subpath "ab" matches on "" not on "a"
pos += n if hop > 0 || pos == len(path) || pos == 0 || path[pos-1] == '/' {
dpaLogger.Debugf("Swarm: '%s' matches '%s'.", path, entry.Path) matchlength = pos + hop
return maxdepth = depth
} matchingEntry = entry
} }
} }
entry = nil }
dpaLogger.Debugf("Path '%s' on manifest not found.", path) if matchingEntry != nil {
dpaLogger.Debugf("Swarm: '%s' matches '%s'.", path, matchingEntry.Path)
} else {
dpaLogger.Debugf("Path '%s' not found on manifest ", path)
}
return return
} }