github.com/sberex/go-sberex@v1.8.2-0.20181113200658-ed96ac38f7d7/swarm/api/http/server.go (about) 1 // This file is part of the go-sberex library. The go-sberex library is 2 // free software: you can redistribute it and/or modify it under the terms 3 // of the GNU Lesser General Public License as published by the Free 4 // Software Foundation, either version 3 of the License, or (at your option) 5 // any later version. 6 // 7 // The go-sberex library is distributed in the hope that it will be useful, 8 // but WITHOUT ANY WARRANTY; without even the implied warranty of 9 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 10 // General Public License <http://www.gnu.org/licenses/> for more details. 11 12 /* 13 A simple http server interface to Swarm 14 */ 15 package http 16 17 import ( 18 "archive/tar" 19 "encoding/json" 20 "errors" 21 "fmt" 22 "io" 23 "io/ioutil" 24 "mime" 25 "mime/multipart" 26 "net/http" 27 "os" 28 "path" 29 "strconv" 30 "strings" 31 "time" 32 33 "github.com/Sberex/go-sberex/common" 34 "github.com/Sberex/go-sberex/log" 35 "github.com/Sberex/go-sberex/metrics" 36 "github.com/Sberex/go-sberex/swarm/api" 37 "github.com/Sberex/go-sberex/swarm/storage" 38 "github.com/rs/cors" 39 ) 40 41 //setup metrics 42 var ( 43 postRawCount = metrics.NewRegisteredCounter("api.http.post.raw.count", nil) 44 postRawFail = metrics.NewRegisteredCounter("api.http.post.raw.fail", nil) 45 postFilesCount = metrics.NewRegisteredCounter("api.http.post.files.count", nil) 46 postFilesFail = metrics.NewRegisteredCounter("api.http.post.files.fail", nil) 47 deleteCount = metrics.NewRegisteredCounter("api.http.delete.count", nil) 48 deleteFail = metrics.NewRegisteredCounter("api.http.delete.fail", nil) 49 getCount = metrics.NewRegisteredCounter("api.http.get.count", nil) 50 getFail = metrics.NewRegisteredCounter("api.http.get.fail", nil) 51 getFileCount = metrics.NewRegisteredCounter("api.http.get.file.count", nil) 52 getFileNotFound = metrics.NewRegisteredCounter("api.http.get.file.notfound", nil) 53 getFileFail = metrics.NewRegisteredCounter("api.http.get.file.fail", nil) 54 getFilesCount = metrics.NewRegisteredCounter("api.http.get.files.count", nil) 55 getFilesFail = metrics.NewRegisteredCounter("api.http.get.files.fail", nil) 56 getListCount = metrics.NewRegisteredCounter("api.http.get.list.count", nil) 57 getListFail = metrics.NewRegisteredCounter("api.http.get.list.fail", nil) 58 requestCount = metrics.NewRegisteredCounter("http.request.count", nil) 59 htmlRequestCount = metrics.NewRegisteredCounter("http.request.html.count", nil) 60 jsonRequestCount = metrics.NewRegisteredCounter("http.request.json.count", nil) 61 requestTimer = metrics.NewRegisteredResettingTimer("http.request.time", nil) 62 ) 63 64 // ServerConfig is the basic configuration needed for the HTTP server and also 65 // includes CORS settings. 66 type ServerConfig struct { 67 Addr string 68 CorsString string 69 } 70 71 // browser API for registering bzz url scheme handlers: 72 // https://developer.mozilla.org/en/docs/Web-based_protocol_handlers 73 // electron (chromium) api for registering bzz url scheme handlers: 74 // https://github.com/atom/electron/blob/master/docs/api/protocol.md 75 76 // starts up http server 77 func StartHttpServer(api *api.Api, config *ServerConfig) { 78 var allowedOrigins []string 79 for _, domain := range strings.Split(config.CorsString, ",") { 80 allowedOrigins = append(allowedOrigins, strings.TrimSpace(domain)) 81 } 82 c := cors.New(cors.Options{ 83 AllowedOrigins: allowedOrigins, 84 AllowedMethods: []string{"POST", "GET", "DELETE", "PATCH", "PUT"}, 85 MaxAge: 600, 86 AllowedHeaders: []string{"*"}, 87 }) 88 hdlr := c.Handler(NewServer(api)) 89 90 go http.ListenAndServe(config.Addr, hdlr) 91 } 92 93 func NewServer(api *api.Api) *Server { 94 return &Server{api} 95 } 96 97 type Server struct { 98 api *api.Api 99 } 100 101 // Request wraps http.Request and also includes the parsed bzz URI 102 type Request struct { 103 http.Request 104 105 uri *api.URI 106 } 107 108 // HandlePostRaw handles a POST request to a raw bzz-raw:/ URI, stores the request 109 // body in swarm and returns the resulting storage key as a text/plain response 110 func (s *Server) HandlePostRaw(w http.ResponseWriter, r *Request) { 111 postRawCount.Inc(1) 112 if r.uri.Path != "" { 113 postRawFail.Inc(1) 114 s.BadRequest(w, r, "raw POST request cannot contain a path") 115 return 116 } 117 118 if r.Header.Get("Content-Length") == "" { 119 postRawFail.Inc(1) 120 s.BadRequest(w, r, "missing Content-Length header in request") 121 return 122 } 123 124 key, err := s.api.Store(r.Body, r.ContentLength, nil) 125 if err != nil { 126 postRawFail.Inc(1) 127 s.Error(w, r, err) 128 return 129 } 130 s.logDebug("content for %s stored", key.Log()) 131 132 w.Header().Set("Content-Type", "text/plain") 133 w.WriteHeader(http.StatusOK) 134 fmt.Fprint(w, key) 135 } 136 137 // HandlePostFiles handles a POST request (or deprecated PUT request) to 138 // bzz:/<hash>/<path> which contains either a single file or multiple files 139 // (either a tar archive or multipart form), adds those files either to an 140 // existing manifest or to a new manifest under <path> and returns the 141 // resulting manifest hash as a text/plain response 142 func (s *Server) HandlePostFiles(w http.ResponseWriter, r *Request) { 143 postFilesCount.Inc(1) 144 contentType, params, err := mime.ParseMediaType(r.Header.Get("Content-Type")) 145 if err != nil { 146 postFilesFail.Inc(1) 147 s.BadRequest(w, r, err.Error()) 148 return 149 } 150 151 var key storage.Key 152 if r.uri.Addr != "" { 153 key, err = s.api.Resolve(r.uri) 154 if err != nil { 155 postFilesFail.Inc(1) 156 s.Error(w, r, fmt.Errorf("error resolving %s: %s", r.uri.Addr, err)) 157 return 158 } 159 } else { 160 key, err = s.api.NewManifest() 161 if err != nil { 162 postFilesFail.Inc(1) 163 s.Error(w, r, err) 164 return 165 } 166 } 167 168 newKey, err := s.updateManifest(key, func(mw *api.ManifestWriter) error { 169 switch contentType { 170 171 case "application/x-tar": 172 return s.handleTarUpload(r, mw) 173 174 case "multipart/form-data": 175 return s.handleMultipartUpload(r, params["boundary"], mw) 176 177 default: 178 return s.handleDirectUpload(r, mw) 179 } 180 }) 181 if err != nil { 182 postFilesFail.Inc(1) 183 s.Error(w, r, fmt.Errorf("error creating manifest: %s", err)) 184 return 185 } 186 187 w.Header().Set("Content-Type", "text/plain") 188 w.WriteHeader(http.StatusOK) 189 fmt.Fprint(w, newKey) 190 } 191 192 func (s *Server) handleTarUpload(req *Request, mw *api.ManifestWriter) error { 193 tr := tar.NewReader(req.Body) 194 for { 195 hdr, err := tr.Next() 196 if err == io.EOF { 197 return nil 198 } else if err != nil { 199 return fmt.Errorf("error reading tar stream: %s", err) 200 } 201 202 // only store regular files 203 if !hdr.FileInfo().Mode().IsRegular() { 204 continue 205 } 206 207 // add the entry under the path from the request 208 path := path.Join(req.uri.Path, hdr.Name) 209 entry := &api.ManifestEntry{ 210 Path: path, 211 ContentType: hdr.Xattrs["user.swarm.content-type"], 212 Mode: hdr.Mode, 213 Size: hdr.Size, 214 ModTime: hdr.ModTime, 215 } 216 s.logDebug("adding %s (%d bytes) to new manifest", entry.Path, entry.Size) 217 contentKey, err := mw.AddEntry(tr, entry) 218 if err != nil { 219 return fmt.Errorf("error adding manifest entry from tar stream: %s", err) 220 } 221 s.logDebug("content for %s stored", contentKey.Log()) 222 } 223 } 224 225 func (s *Server) handleMultipartUpload(req *Request, boundary string, mw *api.ManifestWriter) error { 226 mr := multipart.NewReader(req.Body, boundary) 227 for { 228 part, err := mr.NextPart() 229 if err == io.EOF { 230 return nil 231 } else if err != nil { 232 return fmt.Errorf("error reading multipart form: %s", err) 233 } 234 235 var size int64 236 var reader io.Reader = part 237 if contentLength := part.Header.Get("Content-Length"); contentLength != "" { 238 size, err = strconv.ParseInt(contentLength, 10, 64) 239 if err != nil { 240 return fmt.Errorf("error parsing multipart content length: %s", err) 241 } 242 reader = part 243 } else { 244 // copy the part to a tmp file to get its size 245 tmp, err := ioutil.TempFile("", "swarm-multipart") 246 if err != nil { 247 return err 248 } 249 defer os.Remove(tmp.Name()) 250 defer tmp.Close() 251 size, err = io.Copy(tmp, part) 252 if err != nil { 253 return fmt.Errorf("error copying multipart content: %s", err) 254 } 255 if _, err := tmp.Seek(0, io.SeekStart); err != nil { 256 return fmt.Errorf("error copying multipart content: %s", err) 257 } 258 reader = tmp 259 } 260 261 // add the entry under the path from the request 262 name := part.FileName() 263 if name == "" { 264 name = part.FormName() 265 } 266 path := path.Join(req.uri.Path, name) 267 entry := &api.ManifestEntry{ 268 Path: path, 269 ContentType: part.Header.Get("Content-Type"), 270 Size: size, 271 ModTime: time.Now(), 272 } 273 s.logDebug("adding %s (%d bytes) to new manifest", entry.Path, entry.Size) 274 contentKey, err := mw.AddEntry(reader, entry) 275 if err != nil { 276 return fmt.Errorf("error adding manifest entry from multipart form: %s", err) 277 } 278 s.logDebug("content for %s stored", contentKey.Log()) 279 } 280 } 281 282 func (s *Server) handleDirectUpload(req *Request, mw *api.ManifestWriter) error { 283 key, err := mw.AddEntry(req.Body, &api.ManifestEntry{ 284 Path: req.uri.Path, 285 ContentType: req.Header.Get("Content-Type"), 286 Mode: 0644, 287 Size: req.ContentLength, 288 ModTime: time.Now(), 289 }) 290 if err != nil { 291 return err 292 } 293 s.logDebug("content for %s stored", key.Log()) 294 return nil 295 } 296 297 // HandleDelete handles a DELETE request to bzz:/<manifest>/<path>, removes 298 // <path> from <manifest> and returns the resulting manifest hash as a 299 // text/plain response 300 func (s *Server) HandleDelete(w http.ResponseWriter, r *Request) { 301 deleteCount.Inc(1) 302 key, err := s.api.Resolve(r.uri) 303 if err != nil { 304 deleteFail.Inc(1) 305 s.Error(w, r, fmt.Errorf("error resolving %s: %s", r.uri.Addr, err)) 306 return 307 } 308 309 newKey, err := s.updateManifest(key, func(mw *api.ManifestWriter) error { 310 s.logDebug("removing %s from manifest %s", r.uri.Path, key.Log()) 311 return mw.RemoveEntry(r.uri.Path) 312 }) 313 if err != nil { 314 deleteFail.Inc(1) 315 s.Error(w, r, fmt.Errorf("error updating manifest: %s", err)) 316 return 317 } 318 319 w.Header().Set("Content-Type", "text/plain") 320 w.WriteHeader(http.StatusOK) 321 fmt.Fprint(w, newKey) 322 } 323 324 // HandleGet handles a GET request to 325 // - bzz-raw://<key> and responds with the raw content stored at the 326 // given storage key 327 // - bzz-hash://<key> and responds with the hash of the content stored 328 // at the given storage key as a text/plain response 329 func (s *Server) HandleGet(w http.ResponseWriter, r *Request) { 330 getCount.Inc(1) 331 key, err := s.api.Resolve(r.uri) 332 if err != nil { 333 getFail.Inc(1) 334 s.Error(w, r, fmt.Errorf("error resolving %s: %s", r.uri.Addr, err)) 335 return 336 } 337 338 // if path is set, interpret <key> as a manifest and return the 339 // raw entry at the given path 340 if r.uri.Path != "" { 341 walker, err := s.api.NewManifestWalker(key, nil) 342 if err != nil { 343 getFail.Inc(1) 344 s.BadRequest(w, r, fmt.Sprintf("%s is not a manifest", key)) 345 return 346 } 347 var entry *api.ManifestEntry 348 walker.Walk(func(e *api.ManifestEntry) error { 349 // if the entry matches the path, set entry and stop 350 // the walk 351 if e.Path == r.uri.Path { 352 entry = e 353 // return an error to cancel the walk 354 return errors.New("found") 355 } 356 357 // ignore non-manifest files 358 if e.ContentType != api.ManifestType { 359 return nil 360 } 361 362 // if the manifest's path is a prefix of the 363 // requested path, recurse into it by returning 364 // nil and continuing the walk 365 if strings.HasPrefix(r.uri.Path, e.Path) { 366 return nil 367 } 368 369 return api.SkipManifest 370 }) 371 if entry == nil { 372 getFail.Inc(1) 373 s.NotFound(w, r, fmt.Errorf("Manifest entry could not be loaded")) 374 return 375 } 376 key = storage.Key(common.Hex2Bytes(entry.Hash)) 377 } 378 379 // check the root chunk exists by retrieving the file's size 380 reader := s.api.Retrieve(key) 381 if _, err := reader.Size(nil); err != nil { 382 getFail.Inc(1) 383 s.NotFound(w, r, fmt.Errorf("Root chunk not found %s: %s", key, err)) 384 return 385 } 386 387 switch { 388 case r.uri.Raw() || r.uri.DeprecatedRaw(): 389 // allow the request to overwrite the content type using a query 390 // parameter 391 contentType := "application/octet-stream" 392 if typ := r.URL.Query().Get("content_type"); typ != "" { 393 contentType = typ 394 } 395 w.Header().Set("Content-Type", contentType) 396 397 http.ServeContent(w, &r.Request, "", time.Now(), reader) 398 case r.uri.Hash(): 399 w.Header().Set("Content-Type", "text/plain") 400 w.WriteHeader(http.StatusOK) 401 fmt.Fprint(w, key) 402 } 403 } 404 405 // HandleGetFiles handles a GET request to bzz:/<manifest> with an Accept 406 // header of "application/x-tar" and returns a tar stream of all files 407 // contained in the manifest 408 func (s *Server) HandleGetFiles(w http.ResponseWriter, r *Request) { 409 getFilesCount.Inc(1) 410 if r.uri.Path != "" { 411 getFilesFail.Inc(1) 412 s.BadRequest(w, r, "files request cannot contain a path") 413 return 414 } 415 416 key, err := s.api.Resolve(r.uri) 417 if err != nil { 418 getFilesFail.Inc(1) 419 s.Error(w, r, fmt.Errorf("error resolving %s: %s", r.uri.Addr, err)) 420 return 421 } 422 423 walker, err := s.api.NewManifestWalker(key, nil) 424 if err != nil { 425 getFilesFail.Inc(1) 426 s.Error(w, r, err) 427 return 428 } 429 430 tw := tar.NewWriter(w) 431 defer tw.Close() 432 w.Header().Set("Content-Type", "application/x-tar") 433 w.WriteHeader(http.StatusOK) 434 435 err = walker.Walk(func(entry *api.ManifestEntry) error { 436 // ignore manifests (walk will recurse into them) 437 if entry.ContentType == api.ManifestType { 438 return nil 439 } 440 441 // retrieve the entry's key and size 442 reader := s.api.Retrieve(storage.Key(common.Hex2Bytes(entry.Hash))) 443 size, err := reader.Size(nil) 444 if err != nil { 445 return err 446 } 447 448 // write a tar header for the entry 449 hdr := &tar.Header{ 450 Name: entry.Path, 451 Mode: entry.Mode, 452 Size: size, 453 ModTime: entry.ModTime, 454 Xattrs: map[string]string{ 455 "user.swarm.content-type": entry.ContentType, 456 }, 457 } 458 if err := tw.WriteHeader(hdr); err != nil { 459 return err 460 } 461 462 // copy the file into the tar stream 463 n, err := io.Copy(tw, io.LimitReader(reader, hdr.Size)) 464 if err != nil { 465 return err 466 } else if n != size { 467 return fmt.Errorf("error writing %s: expected %d bytes but sent %d", entry.Path, size, n) 468 } 469 470 return nil 471 }) 472 if err != nil { 473 getFilesFail.Inc(1) 474 s.logError("error generating tar stream: %s", err) 475 } 476 } 477 478 // HandleGetList handles a GET request to bzz-list:/<manifest>/<path> and returns 479 // a list of all files contained in <manifest> under <path> grouped into 480 // common prefixes using "/" as a delimiter 481 func (s *Server) HandleGetList(w http.ResponseWriter, r *Request) { 482 getListCount.Inc(1) 483 // ensure the root path has a trailing slash so that relative URLs work 484 if r.uri.Path == "" && !strings.HasSuffix(r.URL.Path, "/") { 485 http.Redirect(w, &r.Request, r.URL.Path+"/", http.StatusMovedPermanently) 486 return 487 } 488 489 key, err := s.api.Resolve(r.uri) 490 if err != nil { 491 getListFail.Inc(1) 492 s.Error(w, r, fmt.Errorf("error resolving %s: %s", r.uri.Addr, err)) 493 return 494 } 495 496 list, err := s.getManifestList(key, r.uri.Path) 497 498 if err != nil { 499 getListFail.Inc(1) 500 s.Error(w, r, err) 501 return 502 } 503 504 // if the client wants HTML (e.g. a browser) then render the list as a 505 // HTML index with relative URLs 506 if strings.Contains(r.Header.Get("Accept"), "text/html") { 507 w.Header().Set("Content-Type", "text/html") 508 err := htmlListTemplate.Execute(w, &htmlListData{ 509 URI: &api.URI{ 510 Scheme: "bzz", 511 Addr: r.uri.Addr, 512 Path: r.uri.Path, 513 }, 514 List: &list, 515 }) 516 if err != nil { 517 getListFail.Inc(1) 518 s.logError("error rendering list HTML: %s", err) 519 } 520 return 521 } 522 523 w.Header().Set("Content-Type", "application/json") 524 json.NewEncoder(w).Encode(&list) 525 } 526 527 func (s *Server) getManifestList(key storage.Key, prefix string) (list api.ManifestList, err error) { 528 walker, err := s.api.NewManifestWalker(key, nil) 529 if err != nil { 530 return 531 } 532 533 err = walker.Walk(func(entry *api.ManifestEntry) error { 534 // handle non-manifest files 535 if entry.ContentType != api.ManifestType { 536 // ignore the file if it doesn't have the specified prefix 537 if !strings.HasPrefix(entry.Path, prefix) { 538 return nil 539 } 540 541 // if the path after the prefix contains a slash, add a 542 // common prefix to the list, otherwise add the entry 543 suffix := strings.TrimPrefix(entry.Path, prefix) 544 if index := strings.Index(suffix, "/"); index > -1 { 545 list.CommonPrefixes = append(list.CommonPrefixes, prefix+suffix[:index+1]) 546 return nil 547 } 548 if entry.Path == "" { 549 entry.Path = "/" 550 } 551 list.Entries = append(list.Entries, entry) 552 return nil 553 } 554 555 // if the manifest's path is a prefix of the specified prefix 556 // then just recurse into the manifest by returning nil and 557 // continuing the walk 558 if strings.HasPrefix(prefix, entry.Path) { 559 return nil 560 } 561 562 // if the manifest's path has the specified prefix, then if the 563 // path after the prefix contains a slash, add a common prefix 564 // to the list and skip the manifest, otherwise recurse into 565 // the manifest by returning nil and continuing the walk 566 if strings.HasPrefix(entry.Path, prefix) { 567 suffix := strings.TrimPrefix(entry.Path, prefix) 568 if index := strings.Index(suffix, "/"); index > -1 { 569 list.CommonPrefixes = append(list.CommonPrefixes, prefix+suffix[:index+1]) 570 return api.SkipManifest 571 } 572 return nil 573 } 574 575 // the manifest neither has the prefix or needs recursing in to 576 // so just skip it 577 return api.SkipManifest 578 }) 579 580 return list, nil 581 } 582 583 // HandleGetFile handles a GET request to bzz://<manifest>/<path> and responds 584 // with the content of the file at <path> from the given <manifest> 585 func (s *Server) HandleGetFile(w http.ResponseWriter, r *Request) { 586 getFileCount.Inc(1) 587 // ensure the root path has a trailing slash so that relative URLs work 588 if r.uri.Path == "" && !strings.HasSuffix(r.URL.Path, "/") { 589 http.Redirect(w, &r.Request, r.URL.Path+"/", http.StatusMovedPermanently) 590 return 591 } 592 593 key, err := s.api.Resolve(r.uri) 594 if err != nil { 595 getFileFail.Inc(1) 596 s.Error(w, r, fmt.Errorf("error resolving %s: %s", r.uri.Addr, err)) 597 return 598 } 599 600 reader, contentType, status, err := s.api.Get(key, r.uri.Path) 601 if err != nil { 602 switch status { 603 case http.StatusNotFound: 604 getFileNotFound.Inc(1) 605 s.NotFound(w, r, err) 606 default: 607 getFileFail.Inc(1) 608 s.Error(w, r, err) 609 } 610 return 611 } 612 613 //the request results in ambiguous files 614 //e.g. /read with readme.md and readinglist.txt available in manifest 615 if status == http.StatusMultipleChoices { 616 list, err := s.getManifestList(key, r.uri.Path) 617 618 if err != nil { 619 getFileFail.Inc(1) 620 s.Error(w, r, err) 621 return 622 } 623 624 s.logDebug(fmt.Sprintf("Multiple choices! --> %v", list)) 625 //show a nice page links to available entries 626 ShowMultipleChoices(w, &r.Request, list) 627 return 628 } 629 630 // check the root chunk exists by retrieving the file's size 631 if _, err := reader.Size(nil); err != nil { 632 getFileNotFound.Inc(1) 633 s.NotFound(w, r, fmt.Errorf("File not found %s: %s", r.uri, err)) 634 return 635 } 636 637 w.Header().Set("Content-Type", contentType) 638 639 http.ServeContent(w, &r.Request, "", time.Now(), reader) 640 } 641 642 func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { 643 if metrics.Enabled { 644 //The increment for request count and request timer themselves have a flag check 645 //for metrics.Enabled. Nevertheless, we introduce the if here because we 646 //are looking into the header just to see what request type it is (json/html). 647 //So let's take advantage and add all metrics related stuff here 648 requestCount.Inc(1) 649 defer requestTimer.UpdateSince(time.Now()) 650 if r.Header.Get("Accept") == "application/json" { 651 jsonRequestCount.Inc(1) 652 } else { 653 htmlRequestCount.Inc(1) 654 } 655 } 656 s.logDebug("HTTP %s request URL: '%s', Host: '%s', Path: '%s', Referer: '%s', Accept: '%s'", r.Method, r.RequestURI, r.URL.Host, r.URL.Path, r.Referer(), r.Header.Get("Accept")) 657 658 if r.RequestURI == "/" && strings.Contains(r.Header.Get("Accept"), "text/html") { 659 660 err := landingPageTemplate.Execute(w, nil) 661 if err != nil { 662 s.logError("error rendering landing page: %s", err) 663 } 664 return 665 } 666 667 uri, err := api.Parse(strings.TrimLeft(r.URL.Path, "/")) 668 req := &Request{Request: *r, uri: uri} 669 if err != nil { 670 s.logError("Invalid URI %q: %s", r.URL.Path, err) 671 s.BadRequest(w, req, fmt.Sprintf("Invalid URI %q: %s", r.URL.Path, err)) 672 return 673 } 674 s.logDebug("%s request received for %s", r.Method, uri) 675 676 switch r.Method { 677 case "POST": 678 if uri.Raw() || uri.DeprecatedRaw() { 679 s.HandlePostRaw(w, req) 680 } else { 681 s.HandlePostFiles(w, req) 682 } 683 684 case "PUT": 685 // DEPRECATED: 686 // clients should send a POST request (the request creates a 687 // new manifest leaving the existing one intact, so it isn't 688 // strictly a traditional PUT request which replaces content 689 // at a URI, and POST is more ubiquitous) 690 if uri.Raw() || uri.DeprecatedRaw() { 691 ShowError(w, r, fmt.Sprintf("No PUT to %s allowed.", uri), http.StatusBadRequest) 692 return 693 } else { 694 s.HandlePostFiles(w, req) 695 } 696 697 case "DELETE": 698 if uri.Raw() || uri.DeprecatedRaw() { 699 ShowError(w, r, fmt.Sprintf("No DELETE to %s allowed.", uri), http.StatusBadRequest) 700 return 701 } 702 s.HandleDelete(w, req) 703 704 case "GET": 705 if uri.Raw() || uri.Hash() || uri.DeprecatedRaw() { 706 s.HandleGet(w, req) 707 return 708 } 709 710 if uri.List() { 711 s.HandleGetList(w, req) 712 return 713 } 714 715 if r.Header.Get("Accept") == "application/x-tar" { 716 s.HandleGetFiles(w, req) 717 return 718 } 719 720 s.HandleGetFile(w, req) 721 722 default: 723 ShowError(w, r, fmt.Sprintf("Method "+r.Method+" is not supported.", uri), http.StatusMethodNotAllowed) 724 725 } 726 } 727 728 func (s *Server) updateManifest(key storage.Key, update func(mw *api.ManifestWriter) error) (storage.Key, error) { 729 mw, err := s.api.NewManifestWriter(key, nil) 730 if err != nil { 731 return nil, err 732 } 733 734 if err := update(mw); err != nil { 735 return nil, err 736 } 737 738 key, err = mw.Store() 739 if err != nil { 740 return nil, err 741 } 742 s.logDebug("generated manifest %s", key) 743 return key, nil 744 } 745 746 func (s *Server) logDebug(format string, v ...interface{}) { 747 log.Debug(fmt.Sprintf("[BZZ] HTTP: "+format, v...)) 748 } 749 750 func (s *Server) logError(format string, v ...interface{}) { 751 log.Error(fmt.Sprintf("[BZZ] HTTP: "+format, v...)) 752 } 753 754 func (s *Server) BadRequest(w http.ResponseWriter, r *Request, reason string) { 755 ShowError(w, &r.Request, fmt.Sprintf("Bad request %s %s: %s", r.Method, r.uri, reason), http.StatusBadRequest) 756 } 757 758 func (s *Server) Error(w http.ResponseWriter, r *Request, err error) { 759 ShowError(w, &r.Request, fmt.Sprintf("Error serving %s %s: %s", r.Method, r.uri, err), http.StatusInternalServerError) 760 } 761 762 func (s *Server) NotFound(w http.ResponseWriter, r *Request, err error) { 763 ShowError(w, &r.Request, fmt.Sprintf("NOT FOUND error serving %s %s: %s", r.Method, r.uri, err), http.StatusNotFound) 764 }