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