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