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