github.com/muhammedhassanm/blockchain@v0.0.0-20200120143007-697261defd4d/go-ethereum-master/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 "archive/tar" 24 "bufio" 25 "bytes" 26 "context" 27 "encoding/json" 28 "errors" 29 "fmt" 30 "io" 31 "io/ioutil" 32 "mime" 33 "mime/multipart" 34 "net/http" 35 "os" 36 "path" 37 "regexp" 38 "strconv" 39 "strings" 40 "time" 41 42 "github.com/ethereum/go-ethereum/common" 43 "github.com/ethereum/go-ethereum/common/hexutil" 44 "github.com/ethereum/go-ethereum/metrics" 45 "github.com/ethereum/go-ethereum/swarm/api" 46 "github.com/ethereum/go-ethereum/swarm/log" 47 "github.com/ethereum/go-ethereum/swarm/storage" 48 "github.com/ethereum/go-ethereum/swarm/storage/mru" 49 "github.com/pborman/uuid" 50 "github.com/rs/cors" 51 ) 52 53 type resourceResponse struct { 54 Manifest storage.Address `json:"manifest"` 55 Resource string `json:"resource"` 56 Update storage.Address `json:"update"` 57 } 58 59 var ( 60 postRawCount = metrics.NewRegisteredCounter("api.http.post.raw.count", nil) 61 postRawFail = metrics.NewRegisteredCounter("api.http.post.raw.fail", nil) 62 postFilesCount = metrics.NewRegisteredCounter("api.http.post.files.count", nil) 63 postFilesFail = metrics.NewRegisteredCounter("api.http.post.files.fail", nil) 64 deleteCount = metrics.NewRegisteredCounter("api.http.delete.count", nil) 65 deleteFail = metrics.NewRegisteredCounter("api.http.delete.fail", nil) 66 getCount = metrics.NewRegisteredCounter("api.http.get.count", nil) 67 getFail = metrics.NewRegisteredCounter("api.http.get.fail", nil) 68 getFileCount = metrics.NewRegisteredCounter("api.http.get.file.count", nil) 69 getFileNotFound = metrics.NewRegisteredCounter("api.http.get.file.notfound", nil) 70 getFileFail = metrics.NewRegisteredCounter("api.http.get.file.fail", nil) 71 getFilesCount = metrics.NewRegisteredCounter("api.http.get.files.count", nil) 72 getFilesFail = metrics.NewRegisteredCounter("api.http.get.files.fail", nil) 73 getListCount = metrics.NewRegisteredCounter("api.http.get.list.count", nil) 74 getListFail = metrics.NewRegisteredCounter("api.http.get.list.fail", nil) 75 ) 76 77 // ServerConfig is the basic configuration needed for the HTTP server and also 78 // includes CORS settings. 79 type ServerConfig struct { 80 Addr string 81 CorsString string 82 } 83 84 // browser API for registering bzz url scheme handlers: 85 // https://developer.mozilla.org/en/docs/Web-based_protocol_handlers 86 // electron (chromium) api for registering bzz url scheme handlers: 87 // https://github.com/atom/electron/blob/master/docs/api/protocol.md 88 89 // starts up http server 90 func StartHTTPServer(api *api.API, config *ServerConfig) { 91 var allowedOrigins []string 92 for _, domain := range strings.Split(config.CorsString, ",") { 93 allowedOrigins = append(allowedOrigins, strings.TrimSpace(domain)) 94 } 95 c := cors.New(cors.Options{ 96 AllowedOrigins: allowedOrigins, 97 AllowedMethods: []string{"POST", "GET", "DELETE", "PATCH", "PUT"}, 98 MaxAge: 600, 99 AllowedHeaders: []string{"*"}, 100 }) 101 hdlr := c.Handler(NewServer(api)) 102 103 go http.ListenAndServe(config.Addr, hdlr) 104 } 105 106 func NewServer(api *api.API) *Server { 107 return &Server{api} 108 } 109 110 type Server struct { 111 api *api.API 112 } 113 114 // Request wraps http.Request and also includes the parsed bzz URI 115 type Request struct { 116 http.Request 117 118 uri *api.URI 119 ruid string // request unique id 120 } 121 122 // HandlePostRaw handles a POST request to a raw bzz-raw:/ URI, stores the request 123 // body in swarm and returns the resulting storage address as a text/plain response 124 func (s *Server) HandlePostRaw(ctx context.Context, w http.ResponseWriter, r *Request) { 125 log.Debug("handle.post.raw", "ruid", r.ruid) 126 127 postRawCount.Inc(1) 128 129 toEncrypt := false 130 if r.uri.Addr == "encrypt" { 131 toEncrypt = true 132 } 133 134 if r.uri.Path != "" { 135 postRawFail.Inc(1) 136 Respond(w, r, "raw POST request cannot contain a path", http.StatusBadRequest) 137 return 138 } 139 140 if r.uri.Addr != "" && r.uri.Addr != "encrypt" { 141 postRawFail.Inc(1) 142 Respond(w, r, "raw POST request addr can only be empty or \"encrypt\"", http.StatusBadRequest) 143 return 144 } 145 146 if r.Header.Get("Content-Length") == "" { 147 postRawFail.Inc(1) 148 Respond(w, r, "missing Content-Length header in request", http.StatusBadRequest) 149 return 150 } 151 addr, _, err := s.api.Store(ctx, r.Body, r.ContentLength, toEncrypt) 152 if err != nil { 153 postRawFail.Inc(1) 154 Respond(w, r, err.Error(), http.StatusInternalServerError) 155 return 156 } 157 158 log.Debug("stored content", "ruid", r.ruid, "key", addr) 159 160 w.Header().Set("Content-Type", "text/plain") 161 w.WriteHeader(http.StatusOK) 162 fmt.Fprint(w, addr) 163 } 164 165 // HandlePostFiles handles a POST request to 166 // bzz:/<hash>/<path> which contains either a single file or multiple files 167 // (either a tar archive or multipart form), adds those files either to an 168 // existing manifest or to a new manifest under <path> and returns the 169 // resulting manifest hash as a text/plain response 170 func (s *Server) HandlePostFiles(ctx context.Context, w http.ResponseWriter, r *Request) { 171 log.Debug("handle.post.files", "ruid", r.ruid) 172 173 postFilesCount.Inc(1) 174 contentType, params, err := mime.ParseMediaType(r.Header.Get("Content-Type")) 175 if err != nil { 176 postFilesFail.Inc(1) 177 Respond(w, r, err.Error(), http.StatusBadRequest) 178 return 179 } 180 181 toEncrypt := false 182 if r.uri.Addr == "encrypt" { 183 toEncrypt = true 184 } 185 186 var addr storage.Address 187 if r.uri.Addr != "" && r.uri.Addr != "encrypt" { 188 addr, err = s.api.Resolve(ctx, r.uri) 189 if err != nil { 190 postFilesFail.Inc(1) 191 Respond(w, r, fmt.Sprintf("cannot resolve %s: %s", r.uri.Addr, err), http.StatusInternalServerError) 192 return 193 } 194 log.Debug("resolved key", "ruid", r.ruid, "key", addr) 195 } else { 196 addr, err = s.api.NewManifest(ctx, toEncrypt) 197 if err != nil { 198 postFilesFail.Inc(1) 199 Respond(w, r, err.Error(), http.StatusInternalServerError) 200 return 201 } 202 log.Debug("new manifest", "ruid", r.ruid, "key", addr) 203 } 204 205 newAddr, err := s.updateManifest(ctx, addr, func(mw *api.ManifestWriter) error { 206 switch contentType { 207 208 case "application/x-tar": 209 return s.handleTarUpload(ctx, r, mw) 210 211 case "multipart/form-data": 212 return s.handleMultipartUpload(ctx, r, params["boundary"], mw) 213 214 default: 215 return s.handleDirectUpload(ctx, r, mw) 216 } 217 }) 218 if err != nil { 219 postFilesFail.Inc(1) 220 Respond(w, r, fmt.Sprintf("cannot create manifest: %s", err), http.StatusInternalServerError) 221 return 222 } 223 224 log.Debug("stored content", "ruid", r.ruid, "key", newAddr) 225 226 w.Header().Set("Content-Type", "text/plain") 227 w.WriteHeader(http.StatusOK) 228 fmt.Fprint(w, newAddr) 229 } 230 231 func (s *Server) handleTarUpload(ctx context.Context, req *Request, mw *api.ManifestWriter) error { 232 log.Debug("handle.tar.upload", "ruid", req.ruid) 233 tr := tar.NewReader(req.Body) 234 for { 235 hdr, err := tr.Next() 236 if err == io.EOF { 237 return nil 238 } else if err != nil { 239 return fmt.Errorf("error reading tar stream: %s", err) 240 } 241 242 // only store regular files 243 if !hdr.FileInfo().Mode().IsRegular() { 244 continue 245 } 246 247 // add the entry under the path from the request 248 path := path.Join(req.uri.Path, hdr.Name) 249 entry := &api.ManifestEntry{ 250 Path: path, 251 ContentType: hdr.Xattrs["user.swarm.content-type"], 252 Mode: hdr.Mode, 253 Size: hdr.Size, 254 ModTime: hdr.ModTime, 255 } 256 log.Debug("adding path to new manifest", "ruid", req.ruid, "bytes", entry.Size, "path", entry.Path) 257 contentKey, err := mw.AddEntry(ctx, tr, entry) 258 if err != nil { 259 return fmt.Errorf("error adding manifest entry from tar stream: %s", err) 260 } 261 log.Debug("stored content", "ruid", req.ruid, "key", contentKey) 262 } 263 } 264 265 func (s *Server) handleMultipartUpload(ctx context.Context, req *Request, boundary string, mw *api.ManifestWriter) error { 266 log.Debug("handle.multipart.upload", "ruid", req.ruid) 267 mr := multipart.NewReader(req.Body, boundary) 268 for { 269 part, err := mr.NextPart() 270 if err == io.EOF { 271 return nil 272 } else if err != nil { 273 return fmt.Errorf("error reading multipart form: %s", err) 274 } 275 276 var size int64 277 var reader io.Reader = part 278 if contentLength := part.Header.Get("Content-Length"); contentLength != "" { 279 size, err = strconv.ParseInt(contentLength, 10, 64) 280 if err != nil { 281 return fmt.Errorf("error parsing multipart content length: %s", err) 282 } 283 reader = part 284 } else { 285 // copy the part to a tmp file to get its size 286 tmp, err := ioutil.TempFile("", "swarm-multipart") 287 if err != nil { 288 return err 289 } 290 defer os.Remove(tmp.Name()) 291 defer tmp.Close() 292 size, err = io.Copy(tmp, part) 293 if err != nil { 294 return fmt.Errorf("error copying multipart content: %s", err) 295 } 296 if _, err := tmp.Seek(0, io.SeekStart); err != nil { 297 return fmt.Errorf("error copying multipart content: %s", err) 298 } 299 reader = tmp 300 } 301 302 // add the entry under the path from the request 303 name := part.FileName() 304 if name == "" { 305 name = part.FormName() 306 } 307 path := path.Join(req.uri.Path, name) 308 entry := &api.ManifestEntry{ 309 Path: path, 310 ContentType: part.Header.Get("Content-Type"), 311 Size: size, 312 ModTime: time.Now(), 313 } 314 log.Debug("adding path to new manifest", "ruid", req.ruid, "bytes", entry.Size, "path", entry.Path) 315 contentKey, err := mw.AddEntry(ctx, reader, entry) 316 if err != nil { 317 return fmt.Errorf("error adding manifest entry from multipart form: %s", err) 318 } 319 log.Debug("stored content", "ruid", req.ruid, "key", contentKey) 320 } 321 } 322 323 func (s *Server) handleDirectUpload(ctx context.Context, req *Request, mw *api.ManifestWriter) error { 324 log.Debug("handle.direct.upload", "ruid", req.ruid) 325 key, err := mw.AddEntry(ctx, req.Body, &api.ManifestEntry{ 326 Path: req.uri.Path, 327 ContentType: req.Header.Get("Content-Type"), 328 Mode: 0644, 329 Size: req.ContentLength, 330 ModTime: time.Now(), 331 }) 332 if err != nil { 333 return err 334 } 335 log.Debug("stored content", "ruid", req.ruid, "key", key) 336 return nil 337 } 338 339 // HandleDelete handles a DELETE request to bzz:/<manifest>/<path>, removes 340 // <path> from <manifest> and returns the resulting manifest hash as a 341 // text/plain response 342 func (s *Server) HandleDelete(ctx context.Context, w http.ResponseWriter, r *Request) { 343 log.Debug("handle.delete", "ruid", r.ruid) 344 345 deleteCount.Inc(1) 346 key, err := s.api.Resolve(ctx, r.uri) 347 if err != nil { 348 deleteFail.Inc(1) 349 Respond(w, r, fmt.Sprintf("cannot resolve %s: %s", r.uri.Addr, err), http.StatusInternalServerError) 350 return 351 } 352 353 newKey, err := s.updateManifest(ctx, key, func(mw *api.ManifestWriter) error { 354 log.Debug(fmt.Sprintf("removing %s from manifest %s", r.uri.Path, key.Log()), "ruid", r.ruid) 355 return mw.RemoveEntry(r.uri.Path) 356 }) 357 if err != nil { 358 deleteFail.Inc(1) 359 Respond(w, r, fmt.Sprintf("cannot update manifest: %s", err), http.StatusInternalServerError) 360 return 361 } 362 363 w.Header().Set("Content-Type", "text/plain") 364 w.WriteHeader(http.StatusOK) 365 fmt.Fprint(w, newKey) 366 } 367 368 // Parses a resource update post url to corresponding action 369 // possible combinations: 370 // / add multihash update to existing hash 371 // /raw add raw update to existing hash 372 // /# create new resource with first update as mulitihash 373 // /raw/# create new resource with first update raw 374 func resourcePostMode(path string) (isRaw bool, frequency uint64, err error) { 375 re, err := regexp.Compile("^(raw)?/?([0-9]+)?$") 376 if err != nil { 377 return isRaw, frequency, err 378 } 379 m := re.FindAllStringSubmatch(path, 2) 380 var freqstr = "0" 381 if len(m) > 0 { 382 if m[0][1] != "" { 383 isRaw = true 384 } 385 if m[0][2] != "" { 386 freqstr = m[0][2] 387 } 388 } else if len(path) > 0 { 389 return isRaw, frequency, fmt.Errorf("invalid path") 390 } 391 frequency, err = strconv.ParseUint(freqstr, 10, 64) 392 return isRaw, frequency, err 393 } 394 395 // Handles creation of new mutable resources and adding updates to existing mutable resources 396 // There are two types of updates available, "raw" and "multihash." 397 // If the latter is used, a subsequent bzz:// GET call to the manifest of the resource will return 398 // the page that the multihash is pointing to, as if it held a normal swarm content manifest 399 // 400 // The resource name will be verbatim what is passed as the address part of the url. 401 // For example, if a POST is made to /bzz-resource:/foo.eth/raw/13 a new resource with frequency 13 402 // and name "foo.eth" will be created 403 func (s *Server) HandlePostResource(ctx context.Context, w http.ResponseWriter, r *Request) { 404 log.Debug("handle.post.resource", "ruid", r.ruid) 405 var err error 406 var addr storage.Address 407 var name string 408 var outdata []byte 409 isRaw, frequency, err := resourcePostMode(r.uri.Path) 410 if err != nil { 411 Respond(w, r, err.Error(), http.StatusBadRequest) 412 return 413 } 414 415 // new mutable resource creation will always have a frequency field larger than 0 416 if frequency > 0 { 417 418 name = r.uri.Addr 419 420 // the key is the content addressed root chunk holding mutable resource metadata information 421 addr, err = s.api.ResourceCreate(r.Context(), name, frequency) 422 if err != nil { 423 code, err2 := s.translateResourceError(w, r, "resource creation fail", err) 424 425 Respond(w, r, err2.Error(), code) 426 return 427 } 428 429 // we create a manifest so we can retrieve the resource with bzz:// later 430 // this manifest has a special "resource type" manifest, and its hash is the key of the mutable resource 431 // root chunk 432 m, err := s.api.NewResourceManifest(ctx, addr.Hex()) 433 if err != nil { 434 Respond(w, r, fmt.Sprintf("failed to create resource manifest: %v", err), http.StatusInternalServerError) 435 return 436 } 437 438 // the key to the manifest will be passed back to the client 439 // the client can access the root chunk key directly through its Hash member 440 // the manifest key should be set as content in the resolver of the ENS name 441 // \TODO update manifest key automatically in ENS 442 outdata, err = json.Marshal(m) 443 if err != nil { 444 Respond(w, r, fmt.Sprintf("failed to create json response: %s", err), http.StatusInternalServerError) 445 return 446 } 447 } else { 448 // to update the resource through http we need to retrieve the key for the mutable resource root chunk 449 // that means that we retrieve the manifest and inspect its Hash member. 450 manifestAddr := r.uri.Address() 451 if manifestAddr == nil { 452 manifestAddr, err = s.api.Resolve(ctx, r.uri) 453 if err != nil { 454 getFail.Inc(1) 455 Respond(w, r, fmt.Sprintf("cannot resolve %s: %s", r.uri.Addr, err), http.StatusNotFound) 456 return 457 } 458 } else { 459 w.Header().Set("Cache-Control", "max-age=2147483648") 460 } 461 462 // get the root chunk key from the manifest 463 addr, err = s.api.ResolveResourceManifest(ctx, manifestAddr) 464 if err != nil { 465 getFail.Inc(1) 466 Respond(w, r, fmt.Sprintf("error resolving resource root chunk for %s: %s", r.uri.Addr, err), http.StatusNotFound) 467 return 468 } 469 470 log.Debug("handle.post.resource: resolved", "ruid", r.ruid, "manifestkey", manifestAddr, "rootchunkkey", addr) 471 472 name, _, err = s.api.ResourceLookup(r.Context(), addr, 0, 0, &mru.LookupParams{}) 473 if err != nil { 474 Respond(w, r, err.Error(), http.StatusNotFound) 475 return 476 } 477 } 478 479 // Creation and update must send data aswell. This data constitutes the update data itself. 480 data, err := ioutil.ReadAll(r.Body) 481 if err != nil { 482 Respond(w, r, err.Error(), http.StatusInternalServerError) 483 return 484 } 485 486 // Multihash will be passed as hex-encoded data, so we need to parse this to bytes 487 if isRaw { 488 _, _, _, err = s.api.ResourceUpdate(r.Context(), name, data) 489 if err != nil { 490 Respond(w, r, err.Error(), http.StatusBadRequest) 491 return 492 } 493 } else { 494 bytesdata, err := hexutil.Decode(string(data)) 495 if err != nil { 496 Respond(w, r, err.Error(), http.StatusBadRequest) 497 return 498 } 499 _, _, _, err = s.api.ResourceUpdateMultihash(r.Context(), name, bytesdata) 500 if err != nil { 501 Respond(w, r, err.Error(), http.StatusBadRequest) 502 return 503 } 504 } 505 506 // If we have data to return, write this now 507 // \TODO there should always be data to return here 508 if len(outdata) > 0 { 509 w.Header().Add("Content-type", "text/plain") 510 w.WriteHeader(http.StatusOK) 511 fmt.Fprint(w, string(outdata)) 512 return 513 } 514 w.WriteHeader(http.StatusOK) 515 } 516 517 // Retrieve mutable resource updates: 518 // bzz-resource://<id> - get latest update 519 // bzz-resource://<id>/<n> - get latest update on period n 520 // bzz-resource://<id>/<n>/<m> - get update version m of period n 521 // <id> = ens name or hash 522 func (s *Server) HandleGetResource(ctx context.Context, w http.ResponseWriter, r *Request) { 523 s.handleGetResource(ctx, w, r) 524 } 525 526 // TODO: Enable pass maxPeriod parameter 527 func (s *Server) handleGetResource(ctx context.Context, w http.ResponseWriter, r *Request) { 528 log.Debug("handle.get.resource", "ruid", r.ruid) 529 var err error 530 531 // resolve the content key. 532 manifestAddr := r.uri.Address() 533 if manifestAddr == nil { 534 manifestAddr, err = s.api.Resolve(ctx, r.uri) 535 if err != nil { 536 getFail.Inc(1) 537 Respond(w, r, fmt.Sprintf("cannot resolve %s: %s", r.uri.Addr, err), http.StatusNotFound) 538 return 539 } 540 } else { 541 w.Header().Set("Cache-Control", "max-age=2147483648") 542 } 543 544 // get the root chunk key from the manifest 545 key, err := s.api.ResolveResourceManifest(ctx, manifestAddr) 546 if err != nil { 547 getFail.Inc(1) 548 Respond(w, r, fmt.Sprintf("error resolving resource root chunk for %s: %s", r.uri.Addr, err), http.StatusNotFound) 549 return 550 } 551 552 log.Debug("handle.get.resource: resolved", "ruid", r.ruid, "manifestkey", manifestAddr, "rootchunk key", key) 553 554 // determine if the query specifies period and version 555 var params []string 556 if len(r.uri.Path) > 0 { 557 params = strings.Split(r.uri.Path, "/") 558 } 559 var name string 560 var period uint64 561 var version uint64 562 var data []byte 563 now := time.Now() 564 565 switch len(params) { 566 case 0: // latest only 567 name, data, err = s.api.ResourceLookup(r.Context(), key, 0, 0, nil) 568 case 2: // specific period and version 569 version, err = strconv.ParseUint(params[1], 10, 32) 570 if err != nil { 571 break 572 } 573 period, err = strconv.ParseUint(params[0], 10, 32) 574 if err != nil { 575 break 576 } 577 name, data, err = s.api.ResourceLookup(r.Context(), key, uint32(period), uint32(version), nil) 578 case 1: // last version of specific period 579 period, err = strconv.ParseUint(params[0], 10, 32) 580 if err != nil { 581 break 582 } 583 name, data, err = s.api.ResourceLookup(r.Context(), key, uint32(period), uint32(version), nil) 584 default: // bogus 585 err = mru.NewError(storage.ErrInvalidValue, "invalid mutable resource request") 586 } 587 588 // any error from the switch statement will end up here 589 if err != nil { 590 code, err2 := s.translateResourceError(w, r, "mutable resource lookup fail", err) 591 Respond(w, r, err2.Error(), code) 592 return 593 } 594 595 // All ok, serve the retrieved update 596 log.Debug("Found update", "name", name, "ruid", r.ruid) 597 w.Header().Set("Content-Type", "application/octet-stream") 598 http.ServeContent(w, &r.Request, "", now, bytes.NewReader(data)) 599 } 600 601 func (s *Server) translateResourceError(w http.ResponseWriter, r *Request, supErr string, err error) (int, error) { 602 code := 0 603 defaultErr := fmt.Errorf("%s: %v", supErr, err) 604 rsrcErr, ok := err.(*mru.Error) 605 if !ok && rsrcErr != nil { 606 code = rsrcErr.Code() 607 } 608 switch code { 609 case storage.ErrInvalidValue: 610 return http.StatusBadRequest, defaultErr 611 case storage.ErrNotFound, storage.ErrNotSynced, storage.ErrNothingToReturn, storage.ErrInit: 612 return http.StatusNotFound, defaultErr 613 case storage.ErrUnauthorized, storage.ErrInvalidSignature: 614 return http.StatusUnauthorized, defaultErr 615 case storage.ErrDataOverflow: 616 return http.StatusRequestEntityTooLarge, defaultErr 617 } 618 619 return http.StatusInternalServerError, defaultErr 620 } 621 622 // HandleGet handles a GET request to 623 // - bzz-raw://<key> and responds with the raw content stored at the 624 // given storage key 625 // - bzz-hash://<key> and responds with the hash of the content stored 626 // at the given storage key as a text/plain response 627 func (s *Server) HandleGet(ctx context.Context, w http.ResponseWriter, r *Request) { 628 log.Debug("handle.get", "ruid", r.ruid, "uri", r.uri) 629 getCount.Inc(1) 630 var err error 631 addr := r.uri.Address() 632 if addr == nil { 633 addr, err = s.api.Resolve(ctx, r.uri) 634 if err != nil { 635 getFail.Inc(1) 636 Respond(w, r, fmt.Sprintf("cannot resolve %s: %s", r.uri.Addr, err), http.StatusNotFound) 637 return 638 } 639 } else { 640 w.Header().Set("Cache-Control", "max-age=2147483648, immutable") // url was of type bzz://<hex key>/path, so we are sure it is immutable. 641 } 642 643 log.Debug("handle.get: resolved", "ruid", r.ruid, "key", addr) 644 645 // if path is set, interpret <key> as a manifest and return the 646 // raw entry at the given path 647 if r.uri.Path != "" { 648 walker, err := s.api.NewManifestWalker(ctx, addr, nil) 649 if err != nil { 650 getFail.Inc(1) 651 Respond(w, r, fmt.Sprintf("%s is not a manifest", addr), http.StatusBadRequest) 652 return 653 } 654 var entry *api.ManifestEntry 655 walker.Walk(func(e *api.ManifestEntry) error { 656 // if the entry matches the path, set entry and stop 657 // the walk 658 if e.Path == r.uri.Path { 659 entry = e 660 // return an error to cancel the walk 661 return errors.New("found") 662 } 663 664 // ignore non-manifest files 665 if e.ContentType != api.ManifestType { 666 return nil 667 } 668 669 // if the manifest's path is a prefix of the 670 // requested path, recurse into it by returning 671 // nil and continuing the walk 672 if strings.HasPrefix(r.uri.Path, e.Path) { 673 return nil 674 } 675 676 return api.ErrSkipManifest 677 }) 678 if entry == nil { 679 getFail.Inc(1) 680 Respond(w, r, fmt.Sprintf("manifest entry could not be loaded"), http.StatusNotFound) 681 return 682 } 683 addr = storage.Address(common.Hex2Bytes(entry.Hash)) 684 } 685 etag := common.Bytes2Hex(addr) 686 noneMatchEtag := r.Header.Get("If-None-Match") 687 w.Header().Set("ETag", fmt.Sprintf("%q", etag)) // set etag to manifest key or raw entry key. 688 if noneMatchEtag != "" { 689 if bytes.Equal(storage.Address(common.Hex2Bytes(noneMatchEtag)), addr) { 690 Respond(w, r, "Not Modified", http.StatusNotModified) 691 return 692 } 693 } 694 695 // check the root chunk exists by retrieving the file's size 696 reader, isEncrypted := s.api.Retrieve(ctx, addr) 697 if _, err := reader.Size(nil); err != nil { 698 getFail.Inc(1) 699 Respond(w, r, fmt.Sprintf("root chunk not found %s: %s", addr, err), http.StatusNotFound) 700 return 701 } 702 703 w.Header().Set("X-Decrypted", fmt.Sprintf("%v", isEncrypted)) 704 705 switch { 706 case r.uri.Raw(): 707 // allow the request to overwrite the content type using a query 708 // parameter 709 contentType := "application/octet-stream" 710 if typ := r.URL.Query().Get("content_type"); typ != "" { 711 contentType = typ 712 } 713 w.Header().Set("Content-Type", contentType) 714 http.ServeContent(w, &r.Request, "", time.Now(), reader) 715 case r.uri.Hash(): 716 w.Header().Set("Content-Type", "text/plain") 717 w.WriteHeader(http.StatusOK) 718 fmt.Fprint(w, addr) 719 } 720 } 721 722 // HandleGetFiles handles a GET request to bzz:/<manifest> with an Accept 723 // header of "application/x-tar" and returns a tar stream of all files 724 // contained in the manifest 725 func (s *Server) HandleGetFiles(ctx context.Context, w http.ResponseWriter, r *Request) { 726 log.Debug("handle.get.files", "ruid", r.ruid, "uri", r.uri) 727 getFilesCount.Inc(1) 728 if r.uri.Path != "" { 729 getFilesFail.Inc(1) 730 Respond(w, r, "files request cannot contain a path", http.StatusBadRequest) 731 return 732 } 733 734 addr, err := s.api.Resolve(ctx, r.uri) 735 if err != nil { 736 getFilesFail.Inc(1) 737 Respond(w, r, fmt.Sprintf("cannot resolve %s: %s", r.uri.Addr, err), http.StatusNotFound) 738 return 739 } 740 log.Debug("handle.get.files: resolved", "ruid", r.ruid, "key", addr) 741 742 walker, err := s.api.NewManifestWalker(ctx, addr, nil) 743 if err != nil { 744 getFilesFail.Inc(1) 745 Respond(w, r, err.Error(), http.StatusInternalServerError) 746 return 747 } 748 749 tw := tar.NewWriter(w) 750 defer tw.Close() 751 w.Header().Set("Content-Type", "application/x-tar") 752 w.WriteHeader(http.StatusOK) 753 754 err = walker.Walk(func(entry *api.ManifestEntry) error { 755 // ignore manifests (walk will recurse into them) 756 if entry.ContentType == api.ManifestType { 757 return nil 758 } 759 760 // retrieve the entry's key and size 761 reader, isEncrypted := s.api.Retrieve(ctx, storage.Address(common.Hex2Bytes(entry.Hash))) 762 size, err := reader.Size(nil) 763 if err != nil { 764 return err 765 } 766 w.Header().Set("X-Decrypted", fmt.Sprintf("%v", isEncrypted)) 767 768 // write a tar header for the entry 769 hdr := &tar.Header{ 770 Name: entry.Path, 771 Mode: entry.Mode, 772 Size: size, 773 ModTime: entry.ModTime, 774 Xattrs: map[string]string{ 775 "user.swarm.content-type": entry.ContentType, 776 }, 777 } 778 if err := tw.WriteHeader(hdr); err != nil { 779 return err 780 } 781 782 // copy the file into the tar stream 783 n, err := io.Copy(tw, io.LimitReader(reader, hdr.Size)) 784 if err != nil { 785 return err 786 } else if n != size { 787 return fmt.Errorf("error writing %s: expected %d bytes but sent %d", entry.Path, size, n) 788 } 789 790 return nil 791 }) 792 if err != nil { 793 getFilesFail.Inc(1) 794 log.Error(fmt.Sprintf("error generating tar stream: %s", err)) 795 } 796 } 797 798 // HandleGetList handles a GET request to bzz-list:/<manifest>/<path> and returns 799 // a list of all files contained in <manifest> under <path> grouped into 800 // common prefixes using "/" as a delimiter 801 func (s *Server) HandleGetList(ctx context.Context, w http.ResponseWriter, r *Request) { 802 log.Debug("handle.get.list", "ruid", r.ruid, "uri", r.uri) 803 getListCount.Inc(1) 804 // ensure the root path has a trailing slash so that relative URLs work 805 if r.uri.Path == "" && !strings.HasSuffix(r.URL.Path, "/") { 806 http.Redirect(w, &r.Request, r.URL.Path+"/", http.StatusMovedPermanently) 807 return 808 } 809 810 addr, err := s.api.Resolve(ctx, r.uri) 811 if err != nil { 812 getListFail.Inc(1) 813 Respond(w, r, fmt.Sprintf("cannot resolve %s: %s", r.uri.Addr, err), http.StatusNotFound) 814 return 815 } 816 log.Debug("handle.get.list: resolved", "ruid", r.ruid, "key", addr) 817 818 list, err := s.getManifestList(ctx, addr, r.uri.Path) 819 820 if err != nil { 821 getListFail.Inc(1) 822 Respond(w, r, err.Error(), http.StatusInternalServerError) 823 return 824 } 825 826 // if the client wants HTML (e.g. a browser) then render the list as a 827 // HTML index with relative URLs 828 if strings.Contains(r.Header.Get("Accept"), "text/html") { 829 w.Header().Set("Content-Type", "text/html") 830 err := htmlListTemplate.Execute(w, &htmlListData{ 831 URI: &api.URI{ 832 Scheme: "bzz", 833 Addr: r.uri.Addr, 834 Path: r.uri.Path, 835 }, 836 List: &list, 837 }) 838 if err != nil { 839 getListFail.Inc(1) 840 log.Error(fmt.Sprintf("error rendering list HTML: %s", err)) 841 } 842 return 843 } 844 845 w.Header().Set("Content-Type", "application/json") 846 json.NewEncoder(w).Encode(&list) 847 } 848 849 func (s *Server) getManifestList(ctx context.Context, addr storage.Address, prefix string) (list api.ManifestList, err error) { 850 walker, err := s.api.NewManifestWalker(ctx, addr, nil) 851 if err != nil { 852 return 853 } 854 855 err = walker.Walk(func(entry *api.ManifestEntry) error { 856 // handle non-manifest files 857 if entry.ContentType != api.ManifestType { 858 // ignore the file if it doesn't have the specified prefix 859 if !strings.HasPrefix(entry.Path, prefix) { 860 return nil 861 } 862 863 // if the path after the prefix contains a slash, add a 864 // common prefix to the list, otherwise add the entry 865 suffix := strings.TrimPrefix(entry.Path, prefix) 866 if index := strings.Index(suffix, "/"); index > -1 { 867 list.CommonPrefixes = append(list.CommonPrefixes, prefix+suffix[:index+1]) 868 return nil 869 } 870 if entry.Path == "" { 871 entry.Path = "/" 872 } 873 list.Entries = append(list.Entries, entry) 874 return nil 875 } 876 877 // if the manifest's path is a prefix of the specified prefix 878 // then just recurse into the manifest by returning nil and 879 // continuing the walk 880 if strings.HasPrefix(prefix, entry.Path) { 881 return nil 882 } 883 884 // if the manifest's path has the specified prefix, then if the 885 // path after the prefix contains a slash, add a common prefix 886 // to the list and skip the manifest, otherwise recurse into 887 // the manifest by returning nil and continuing the walk 888 if strings.HasPrefix(entry.Path, prefix) { 889 suffix := strings.TrimPrefix(entry.Path, prefix) 890 if index := strings.Index(suffix, "/"); index > -1 { 891 list.CommonPrefixes = append(list.CommonPrefixes, prefix+suffix[:index+1]) 892 return api.ErrSkipManifest 893 } 894 return nil 895 } 896 897 // the manifest neither has the prefix or needs recursing in to 898 // so just skip it 899 return api.ErrSkipManifest 900 }) 901 902 return list, nil 903 } 904 905 // HandleGetFile handles a GET request to bzz://<manifest>/<path> and responds 906 // with the content of the file at <path> from the given <manifest> 907 func (s *Server) HandleGetFile(ctx context.Context, w http.ResponseWriter, r *Request) { 908 log.Debug("handle.get.file", "ruid", r.ruid) 909 getFileCount.Inc(1) 910 // ensure the root path has a trailing slash so that relative URLs work 911 if r.uri.Path == "" && !strings.HasSuffix(r.URL.Path, "/") { 912 http.Redirect(w, &r.Request, r.URL.Path+"/", http.StatusMovedPermanently) 913 return 914 } 915 var err error 916 manifestAddr := r.uri.Address() 917 918 if manifestAddr == nil { 919 manifestAddr, err = s.api.Resolve(ctx, r.uri) 920 if err != nil { 921 getFileFail.Inc(1) 922 Respond(w, r, fmt.Sprintf("cannot resolve %s: %s", r.uri.Addr, err), http.StatusNotFound) 923 return 924 } 925 } else { 926 w.Header().Set("Cache-Control", "max-age=2147483648, immutable") // url was of type bzz://<hex key>/path, so we are sure it is immutable. 927 } 928 929 log.Debug("handle.get.file: resolved", "ruid", r.ruid, "key", manifestAddr) 930 931 reader, contentType, status, contentKey, err := s.api.Get(ctx, manifestAddr, r.uri.Path) 932 933 etag := common.Bytes2Hex(contentKey) 934 noneMatchEtag := r.Header.Get("If-None-Match") 935 w.Header().Set("ETag", fmt.Sprintf("%q", etag)) // set etag to actual content key. 936 if noneMatchEtag != "" { 937 if bytes.Equal(storage.Address(common.Hex2Bytes(noneMatchEtag)), contentKey) { 938 Respond(w, r, "Not Modified", http.StatusNotModified) 939 return 940 } 941 } 942 943 if err != nil { 944 switch status { 945 case http.StatusNotFound: 946 getFileNotFound.Inc(1) 947 Respond(w, r, err.Error(), http.StatusNotFound) 948 default: 949 getFileFail.Inc(1) 950 Respond(w, r, err.Error(), http.StatusInternalServerError) 951 } 952 return 953 } 954 955 //the request results in ambiguous files 956 //e.g. /read with readme.md and readinglist.txt available in manifest 957 if status == http.StatusMultipleChoices { 958 list, err := s.getManifestList(ctx, manifestAddr, r.uri.Path) 959 960 if err != nil { 961 getFileFail.Inc(1) 962 Respond(w, r, err.Error(), http.StatusInternalServerError) 963 return 964 } 965 966 log.Debug(fmt.Sprintf("Multiple choices! --> %v", list), "ruid", r.ruid) 967 //show a nice page links to available entries 968 ShowMultipleChoices(w, r, list) 969 return 970 } 971 972 // check the root chunk exists by retrieving the file's size 973 if _, err := reader.Size(nil); err != nil { 974 getFileNotFound.Inc(1) 975 Respond(w, r, fmt.Sprintf("file not found %s: %s", r.uri, err), http.StatusNotFound) 976 return 977 } 978 979 w.Header().Set("Content-Type", contentType) 980 http.ServeContent(w, &r.Request, "", time.Now(), newBufferedReadSeeker(reader, getFileBufferSize)) 981 } 982 983 // The size of buffer used for bufio.Reader on LazyChunkReader passed to 984 // http.ServeContent in HandleGetFile. 985 // Warning: This value influences the number of chunk requests and chunker join goroutines 986 // per file request. 987 // Recommended value is 4 times the io.Copy default buffer value which is 32kB. 988 const getFileBufferSize = 4 * 32 * 1024 989 990 // bufferedReadSeeker wraps bufio.Reader to expose Seek method 991 // from the provied io.ReadSeeker in newBufferedReadSeeker. 992 type bufferedReadSeeker struct { 993 r io.Reader 994 s io.Seeker 995 } 996 997 // newBufferedReadSeeker creates a new instance of bufferedReadSeeker, 998 // out of io.ReadSeeker. Argument `size` is the size of the read buffer. 999 func newBufferedReadSeeker(readSeeker io.ReadSeeker, size int) bufferedReadSeeker { 1000 return bufferedReadSeeker{ 1001 r: bufio.NewReaderSize(readSeeker, size), 1002 s: readSeeker, 1003 } 1004 } 1005 1006 func (b bufferedReadSeeker) Read(p []byte) (n int, err error) { 1007 return b.r.Read(p) 1008 } 1009 1010 func (b bufferedReadSeeker) Seek(offset int64, whence int) (int64, error) { 1011 return b.s.Seek(offset, whence) 1012 } 1013 1014 func (s *Server) ServeHTTP(rw http.ResponseWriter, r *http.Request) { 1015 ctx := context.TODO() 1016 1017 defer metrics.GetOrRegisterResettingTimer(fmt.Sprintf("http.request.%s.time", r.Method), nil).UpdateSince(time.Now()) 1018 req := &Request{Request: *r, ruid: uuid.New()[:8]} 1019 metrics.GetOrRegisterCounter(fmt.Sprintf("http.request.%s", r.Method), nil).Inc(1) 1020 log.Info("serving request", "ruid", req.ruid, "method", r.Method, "url", r.RequestURI) 1021 1022 // wrapping the ResponseWriter, so that we get the response code set by http.ServeContent 1023 w := newLoggingResponseWriter(rw) 1024 1025 if r.RequestURI == "/" && strings.Contains(r.Header.Get("Accept"), "text/html") { 1026 1027 err := landingPageTemplate.Execute(w, nil) 1028 if err != nil { 1029 log.Error(fmt.Sprintf("error rendering landing page: %s", err)) 1030 } 1031 return 1032 } 1033 1034 if r.URL.Path == "/robots.txt" { 1035 w.Header().Set("Last-Modified", time.Now().Format(http.TimeFormat)) 1036 fmt.Fprintf(w, "User-agent: *\nDisallow: /") 1037 return 1038 } 1039 1040 if r.RequestURI == "/" && strings.Contains(r.Header.Get("Accept"), "application/json") { 1041 w.Header().Set("Content-Type", "application/json") 1042 w.WriteHeader(http.StatusOK) 1043 json.NewEncoder(w).Encode("Welcome to Swarm!") 1044 return 1045 } 1046 1047 uri, err := api.Parse(strings.TrimLeft(r.URL.Path, "/")) 1048 if err != nil { 1049 Respond(w, req, fmt.Sprintf("invalid URI %q", r.URL.Path), http.StatusBadRequest) 1050 return 1051 } 1052 1053 req.uri = uri 1054 1055 log.Debug("parsed request path", "ruid", req.ruid, "method", req.Method, "uri.Addr", req.uri.Addr, "uri.Path", req.uri.Path, "uri.Scheme", req.uri.Scheme) 1056 1057 switch r.Method { 1058 case "POST": 1059 if uri.Raw() { 1060 log.Debug("handlePostRaw") 1061 s.HandlePostRaw(ctx, w, req) 1062 } else if uri.Resource() { 1063 log.Debug("handlePostResource") 1064 s.HandlePostResource(ctx, w, req) 1065 } else if uri.Immutable() || uri.List() || uri.Hash() { 1066 log.Debug("POST not allowed on immutable, list or hash") 1067 Respond(w, req, fmt.Sprintf("POST method on scheme %s not allowed", uri.Scheme), http.StatusMethodNotAllowed) 1068 } else { 1069 log.Debug("handlePostFiles") 1070 s.HandlePostFiles(ctx, w, req) 1071 } 1072 1073 case "PUT": 1074 Respond(w, req, fmt.Sprintf("PUT method to %s not allowed", uri), http.StatusBadRequest) 1075 return 1076 1077 case "DELETE": 1078 if uri.Raw() { 1079 Respond(w, req, fmt.Sprintf("DELETE method to %s not allowed", uri), http.StatusBadRequest) 1080 return 1081 } 1082 s.HandleDelete(ctx, w, req) 1083 1084 case "GET": 1085 1086 if uri.Resource() { 1087 s.HandleGetResource(ctx, w, req) 1088 return 1089 } 1090 1091 if uri.Raw() || uri.Hash() { 1092 s.HandleGet(ctx, w, req) 1093 return 1094 } 1095 1096 if uri.List() { 1097 s.HandleGetList(ctx, w, req) 1098 return 1099 } 1100 1101 if r.Header.Get("Accept") == "application/x-tar" { 1102 s.HandleGetFiles(ctx, w, req) 1103 return 1104 } 1105 1106 s.HandleGetFile(ctx, w, req) 1107 1108 default: 1109 Respond(w, req, fmt.Sprintf("%s method is not supported", r.Method), http.StatusMethodNotAllowed) 1110 } 1111 1112 log.Info("served response", "ruid", req.ruid, "code", w.statusCode) 1113 } 1114 1115 func (s *Server) updateManifest(ctx context.Context, addr storage.Address, update func(mw *api.ManifestWriter) error) (storage.Address, error) { 1116 mw, err := s.api.NewManifestWriter(ctx, addr, nil) 1117 if err != nil { 1118 return nil, err 1119 } 1120 1121 if err := update(mw); err != nil { 1122 return nil, err 1123 } 1124 1125 addr, err = mw.Store() 1126 if err != nil { 1127 return nil, err 1128 } 1129 log.Debug(fmt.Sprintf("generated manifest %s", addr)) 1130 return addr, nil 1131 } 1132 1133 type loggingResponseWriter struct { 1134 http.ResponseWriter 1135 statusCode int 1136 } 1137 1138 func newLoggingResponseWriter(w http.ResponseWriter) *loggingResponseWriter { 1139 return &loggingResponseWriter{w, http.StatusOK} 1140 } 1141 1142 func (lrw *loggingResponseWriter) WriteHeader(code int) { 1143 lrw.statusCode = code 1144 lrw.ResponseWriter.WriteHeader(code) 1145 }