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 }