github.com/murrekatt/go-ethereum@v1.5.8-0.20170123175102-fc52f2c007fb/swarm/api/http/server.go (about)

     1  // Copyright 2016 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  /*
    18  A simple http server interface to Swarm
    19  */
    20  package http
    21  
    22  import (
    23  	"bytes"
    24  	"io"
    25  	"net/http"
    26  	"regexp"
    27  	"strings"
    28  	"sync"
    29  	"time"
    30  
    31  	"github.com/ethereum/go-ethereum/common"
    32  	"github.com/ethereum/go-ethereum/logger"
    33  	"github.com/ethereum/go-ethereum/logger/glog"
    34  	"github.com/ethereum/go-ethereum/swarm/api"
    35  	"github.com/rs/cors"
    36  )
    37  
    38  const (
    39  	rawType = "application/octet-stream"
    40  )
    41  
    42  var (
    43  	// accepted protocols: bzz (traditional), bzzi (immutable) and bzzr (raw)
    44  	bzzPrefix       = regexp.MustCompile("^/+bzz[ir]?:/+")
    45  	trailingSlashes = regexp.MustCompile("/+$")
    46  	rootDocumentUri = regexp.MustCompile("^/+bzz[i]?:/+[^/]+$")
    47  	// forever         = func() time.Time { return time.Unix(0, 0) }
    48  	forever = time.Now
    49  )
    50  
    51  type sequentialReader struct {
    52  	reader io.Reader
    53  	pos    int64
    54  	ahead  map[int64](chan bool)
    55  	lock   sync.Mutex
    56  }
    57  
    58  // Server is the basic configuration needs for the HTTP server and also
    59  // includes CORS settings.
    60  type Server struct {
    61  	Addr       string
    62  	CorsString string
    63  }
    64  
    65  // browser API for registering bzz url scheme handlers:
    66  // https://developer.mozilla.org/en/docs/Web-based_protocol_handlers
    67  // electron (chromium) api for registering bzz url scheme handlers:
    68  // https://github.com/atom/electron/blob/master/docs/api/protocol.md
    69  
    70  // starts up http server
    71  func StartHttpServer(api *api.Api, server *Server) {
    72  	serveMux := http.NewServeMux()
    73  	serveMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
    74  		handler(w, r, api)
    75  	})
    76  	var allowedOrigins []string
    77  	for _, domain := range strings.Split(server.CorsString, ",") {
    78  		allowedOrigins = append(allowedOrigins, strings.TrimSpace(domain))
    79  	}
    80  	c := cors.New(cors.Options{
    81  		AllowedOrigins: allowedOrigins,
    82  		AllowedMethods: []string{"POST", "GET", "DELETE", "PATCH", "PUT"},
    83  		MaxAge:         600,
    84  	})
    85  	hdlr := c.Handler(serveMux)
    86  
    87  	go http.ListenAndServe(server.Addr, hdlr)
    88  	glog.V(logger.Info).Infof("Swarm HTTP proxy started on localhost:%s", server.Addr)
    89  }
    90  
    91  func handler(w http.ResponseWriter, r *http.Request, a *api.Api) {
    92  	requestURL := r.URL
    93  	// This is wrong
    94  	//	if requestURL.Host == "" {
    95  	//		var err error
    96  	//		requestURL, err = url.Parse(r.Referer() + requestURL.String())
    97  	//		if err != nil {
    98  	//			http.Error(w, err.Error(), http.StatusBadRequest)
    99  	//			return
   100  	//		}
   101  	//	}
   102  	glog.V(logger.Debug).Infof("HTTP %s request URL: '%s', Host: '%s', Path: '%s', Referer: '%s', Accept: '%s'", r.Method, r.RequestURI, requestURL.Host, requestURL.Path, r.Referer(), r.Header.Get("Accept"))
   103  	uri := requestURL.Path
   104  	var raw, nameresolver bool
   105  	var proto string
   106  
   107  	// HTTP-based URL protocol handler
   108  	glog.V(logger.Debug).Infof("BZZ request URI: '%s'", uri)
   109  
   110  	path := bzzPrefix.ReplaceAllStringFunc(uri, func(p string) string {
   111  		proto = p
   112  		return ""
   113  	})
   114  
   115  	// protocol identification (ugly)
   116  	if proto == "" {
   117  		if glog.V(logger.Error) {
   118  			glog.Errorf(
   119  				"[BZZ] Swarm: Protocol error in request `%s`.",
   120  				uri,
   121  			)
   122  			http.Error(w, "Invalid request URL: need access protocol (bzz:/, bzzr:/, bzzi:/) as first element in path.", http.StatusBadRequest)
   123  			return
   124  		}
   125  	}
   126  	if len(proto) > 4 {
   127  		raw = proto[1:5] == "bzzr"
   128  		nameresolver = proto[1:5] != "bzzi"
   129  	}
   130  
   131  	glog.V(logger.Debug).Infof(
   132  		"[BZZ] Swarm: %s request over protocol %s '%s' received.",
   133  		r.Method, proto, path,
   134  	)
   135  
   136  	switch {
   137  	case r.Method == "POST" || r.Method == "PUT":
   138  		if r.Header.Get("content-length") == "" {
   139  			http.Error(w, "Missing Content-Length header in request.", http.StatusBadRequest)
   140  			return
   141  		}
   142  		key, err := a.Store(io.LimitReader(r.Body, r.ContentLength), r.ContentLength, nil)
   143  		if err == nil {
   144  			glog.V(logger.Debug).Infof("Content for %v stored", key.Log())
   145  		} else {
   146  			http.Error(w, err.Error(), http.StatusBadRequest)
   147  			return
   148  		}
   149  		if r.Method == "POST" {
   150  			if raw {
   151  				w.Header().Set("Content-Type", "text/plain")
   152  				http.ServeContent(w, r, "", time.Now(), bytes.NewReader([]byte(common.Bytes2Hex(key))))
   153  			} else {
   154  				http.Error(w, "No POST to "+uri+" allowed.", http.StatusBadRequest)
   155  				return
   156  			}
   157  		} else {
   158  			// PUT
   159  			if raw {
   160  				http.Error(w, "No PUT to /raw allowed.", http.StatusBadRequest)
   161  				return
   162  			} else {
   163  				path = api.RegularSlashes(path)
   164  				mime := r.Header.Get("Content-Type")
   165  				// TODO proper root hash separation
   166  				glog.V(logger.Debug).Infof("Modify '%s' to store %v as '%s'.", path, key.Log(), mime)
   167  				newKey, err := a.Modify(path, common.Bytes2Hex(key), mime, nameresolver)
   168  				if err == nil {
   169  					glog.V(logger.Debug).Infof("Swarm replaced manifest by '%s'", newKey)
   170  					w.Header().Set("Content-Type", "text/plain")
   171  					http.ServeContent(w, r, "", time.Now(), bytes.NewReader([]byte(newKey)))
   172  				} else {
   173  					http.Error(w, "PUT to "+path+"failed.", http.StatusBadRequest)
   174  					return
   175  				}
   176  			}
   177  		}
   178  	case r.Method == "DELETE":
   179  		if raw {
   180  			http.Error(w, "No DELETE to /raw allowed.", http.StatusBadRequest)
   181  			return
   182  		} else {
   183  			path = api.RegularSlashes(path)
   184  			glog.V(logger.Debug).Infof("Delete '%s'.", path)
   185  			newKey, err := a.Modify(path, "", "", nameresolver)
   186  			if err == nil {
   187  				glog.V(logger.Debug).Infof("Swarm replaced manifest by '%s'", newKey)
   188  				w.Header().Set("Content-Type", "text/plain")
   189  				http.ServeContent(w, r, "", time.Now(), bytes.NewReader([]byte(newKey)))
   190  			} else {
   191  				http.Error(w, "DELETE to "+path+"failed.", http.StatusBadRequest)
   192  				return
   193  			}
   194  		}
   195  	case r.Method == "GET" || r.Method == "HEAD":
   196  		path = trailingSlashes.ReplaceAllString(path, "")
   197  		if raw {
   198  			// resolving host
   199  			key, err := a.Resolve(path, nameresolver)
   200  			if err != nil {
   201  				glog.V(logger.Error).Infof("%v", err)
   202  				http.Error(w, err.Error(), http.StatusBadRequest)
   203  				return
   204  			}
   205  
   206  			// retrieving content
   207  			reader := a.Retrieve(key)
   208  			quitC := make(chan bool)
   209  			size, err := reader.Size(quitC)
   210  			if err != nil {
   211  				glog.V(logger.Debug).Infof("Could not determine size: %v", err.Error())
   212  				//An error on call to Size means we don't have the root chunk
   213  				http.Error(w, err.Error(), http.StatusNotFound)
   214  				return
   215  			}
   216  			glog.V(logger.Debug).Infof("Reading %d bytes.", size)
   217  
   218  			// setting mime type
   219  			qv := requestURL.Query()
   220  			mimeType := qv.Get("content_type")
   221  			if mimeType == "" {
   222  				mimeType = rawType
   223  			}
   224  
   225  			w.Header().Set("Content-Type", mimeType)
   226  			http.ServeContent(w, r, uri, forever(), reader)
   227  			glog.V(logger.Debug).Infof("Serve raw content '%s' (%d bytes) as '%s'", uri, size, mimeType)
   228  
   229  			// retrieve path via manifest
   230  		} else {
   231  			glog.V(logger.Debug).Infof("Structured GET request '%s' received.", uri)
   232  			// add trailing slash, if missing
   233  			if rootDocumentUri.MatchString(uri) {
   234  				http.Redirect(w, r, path+"/", http.StatusFound)
   235  				return
   236  			}
   237  			reader, mimeType, status, err := a.Get(path, nameresolver)
   238  			if err != nil {
   239  				if _, ok := err.(api.ErrResolve); ok {
   240  					glog.V(logger.Debug).Infof("%v", err)
   241  					status = http.StatusBadRequest
   242  				} else {
   243  					glog.V(logger.Debug).Infof("error retrieving '%s': %v", uri, err)
   244  					status = http.StatusNotFound
   245  				}
   246  				http.Error(w, err.Error(), status)
   247  				return
   248  			}
   249  			// set mime type and status headers
   250  			w.Header().Set("Content-Type", mimeType)
   251  			if status > 0 {
   252  				w.WriteHeader(status)
   253  			} else {
   254  				status = 200
   255  			}
   256  			quitC := make(chan bool)
   257  			size, err := reader.Size(quitC)
   258  			if err != nil {
   259  				glog.V(logger.Debug).Infof("Could not determine size: %v", err.Error())
   260  				//An error on call to Size means we don't have the root chunk
   261  				http.Error(w, err.Error(), http.StatusNotFound)
   262  				return
   263  			}
   264  			glog.V(logger.Debug).Infof("Served '%s' (%d bytes) as '%s' (status code: %v)", uri, size, mimeType, status)
   265  
   266  			http.ServeContent(w, r, path, forever(), reader)
   267  
   268  		}
   269  	default:
   270  		http.Error(w, "Method "+r.Method+" is not supported.", http.StatusMethodNotAllowed)
   271  	}
   272  }
   273  
   274  func (self *sequentialReader) ReadAt(target []byte, off int64) (n int, err error) {
   275  	self.lock.Lock()
   276  	// assert self.pos <= off
   277  	if self.pos > off {
   278  		glog.V(logger.Error).Infof("non-sequential read attempted from sequentialReader; %d > %d",
   279  			self.pos, off)
   280  		panic("Non-sequential read attempt")
   281  	}
   282  	if self.pos != off {
   283  		glog.V(logger.Debug).Infof("deferred read in POST at position %d, offset %d.",
   284  			self.pos, off)
   285  		wait := make(chan bool)
   286  		self.ahead[off] = wait
   287  		self.lock.Unlock()
   288  		if <-wait {
   289  			// failed read behind
   290  			n = 0
   291  			err = io.ErrUnexpectedEOF
   292  			return
   293  		}
   294  		self.lock.Lock()
   295  	}
   296  	localPos := 0
   297  	for localPos < len(target) {
   298  		n, err = self.reader.Read(target[localPos:])
   299  		localPos += n
   300  		glog.V(logger.Debug).Infof("Read %d bytes into buffer size %d from POST, error %v.",
   301  			n, len(target), err)
   302  		if err != nil {
   303  			glog.V(logger.Debug).Infof("POST stream's reading terminated with %v.", err)
   304  			for i := range self.ahead {
   305  				self.ahead[i] <- true
   306  				delete(self.ahead, i)
   307  			}
   308  			self.lock.Unlock()
   309  			return localPos, err
   310  		}
   311  		self.pos += int64(n)
   312  	}
   313  	wait := self.ahead[self.pos]
   314  	if wait != nil {
   315  		glog.V(logger.Debug).Infof("deferred read in POST at position %d triggered.",
   316  			self.pos)
   317  		delete(self.ahead, self.pos)
   318  		close(wait)
   319  	}
   320  	self.lock.Unlock()
   321  	return localPos, err
   322  }