github.com/alanchchen/go-ethereum@v1.6.6-0.20170601190819-6171d01b1195/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/swarm/api" 41 "github.com/ethereum/go-ethereum/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, os.SEEK_SET); 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 http.NotFound(w, &r.Request) 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.logDebug("key not found %s: %s", key, err) 345 http.NotFound(w, &r.Request) 346 return 347 } 348 349 // allow the request to overwrite the content type using a query 350 // parameter 351 contentType := "application/octet-stream" 352 if typ := r.URL.Query().Get("content_type"); typ != "" { 353 contentType = typ 354 } 355 w.Header().Set("Content-Type", contentType) 356 357 http.ServeContent(w, &r.Request, "", time.Now(), reader) 358 } 359 360 // HandleGetFiles handles a GET request to bzz:/<manifest> with an Accept 361 // header of "application/x-tar" and returns a tar stream of all files 362 // contained in the manifest 363 func (s *Server) HandleGetFiles(w http.ResponseWriter, r *Request) { 364 if r.uri.Path != "" { 365 s.BadRequest(w, r, "files request cannot contain a path") 366 return 367 } 368 369 key, err := s.api.Resolve(r.uri) 370 if err != nil { 371 s.Error(w, r, fmt.Errorf("error resolving %s: %s", r.uri.Addr, err)) 372 return 373 } 374 375 walker, err := s.api.NewManifestWalker(key, nil) 376 if err != nil { 377 s.Error(w, r, err) 378 return 379 } 380 381 tw := tar.NewWriter(w) 382 defer tw.Close() 383 w.Header().Set("Content-Type", "application/x-tar") 384 w.WriteHeader(http.StatusOK) 385 386 err = walker.Walk(func(entry *api.ManifestEntry) error { 387 // ignore manifests (walk will recurse into them) 388 if entry.ContentType == api.ManifestType { 389 return nil 390 } 391 392 // retrieve the entry's key and size 393 reader := s.api.Retrieve(storage.Key(common.Hex2Bytes(entry.Hash))) 394 size, err := reader.Size(nil) 395 if err != nil { 396 return err 397 } 398 399 // write a tar header for the entry 400 hdr := &tar.Header{ 401 Name: entry.Path, 402 Mode: entry.Mode, 403 Size: size, 404 ModTime: entry.ModTime, 405 Xattrs: map[string]string{ 406 "user.swarm.content-type": entry.ContentType, 407 }, 408 } 409 if err := tw.WriteHeader(hdr); err != nil { 410 return err 411 } 412 413 // copy the file into the tar stream 414 n, err := io.Copy(tw, io.LimitReader(reader, hdr.Size)) 415 if err != nil { 416 return err 417 } else if n != size { 418 return fmt.Errorf("error writing %s: expected %d bytes but sent %d", entry.Path, size, n) 419 } 420 421 return nil 422 }) 423 if err != nil { 424 s.logError("error generating tar stream: %s", err) 425 } 426 } 427 428 // HandleGetList handles a GET request to bzz:/<manifest>/<path> which has 429 // the "list" query parameter set to "true" and returns a list of all files 430 // contained in <manifest> under <path> grouped into common prefixes using 431 // "/" as a delimiter 432 func (s *Server) HandleGetList(w http.ResponseWriter, r *Request) { 433 // ensure the root path has a trailing slash so that relative URLs work 434 if r.uri.Path == "" && !strings.HasSuffix(r.URL.Path, "/") { 435 http.Redirect(w, &r.Request, r.URL.Path+"/?list=true", http.StatusMovedPermanently) 436 return 437 } 438 439 key, err := s.api.Resolve(r.uri) 440 if err != nil { 441 s.Error(w, r, fmt.Errorf("error resolving %s: %s", r.uri.Addr, err)) 442 return 443 } 444 445 walker, err := s.api.NewManifestWalker(key, nil) 446 if err != nil { 447 s.Error(w, r, err) 448 return 449 } 450 451 var list api.ManifestList 452 prefix := r.uri.Path 453 err = walker.Walk(func(entry *api.ManifestEntry) error { 454 // handle non-manifest files 455 if entry.ContentType != api.ManifestType { 456 // ignore the file if it doesn't have the specified prefix 457 if !strings.HasPrefix(entry.Path, prefix) { 458 return nil 459 } 460 461 // if the path after the prefix contains a slash, add a 462 // common prefix to the list, otherwise add the entry 463 suffix := strings.TrimPrefix(entry.Path, prefix) 464 if index := strings.Index(suffix, "/"); index > -1 { 465 list.CommonPrefixes = append(list.CommonPrefixes, prefix+suffix[:index+1]) 466 return nil 467 } 468 if entry.Path == "" { 469 entry.Path = "/" 470 } 471 list.Entries = append(list.Entries, entry) 472 return nil 473 } 474 475 // if the manifest's path is a prefix of the specified prefix 476 // then just recurse into the manifest by returning nil and 477 // continuing the walk 478 if strings.HasPrefix(prefix, entry.Path) { 479 return nil 480 } 481 482 // if the manifest's path has the specified prefix, then if the 483 // path after the prefix contains a slash, add a common prefix 484 // to the list and skip the manifest, otherwise recurse into 485 // the manifest by returning nil and continuing the walk 486 if strings.HasPrefix(entry.Path, prefix) { 487 suffix := strings.TrimPrefix(entry.Path, prefix) 488 if index := strings.Index(suffix, "/"); index > -1 { 489 list.CommonPrefixes = append(list.CommonPrefixes, prefix+suffix[:index+1]) 490 return api.SkipManifest 491 } 492 return nil 493 } 494 495 // the manifest neither has the prefix or needs recursing in to 496 // so just skip it 497 return api.SkipManifest 498 }) 499 if err != nil { 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: r.uri, 510 List: &list, 511 }) 512 if err != nil { 513 s.logError("error rendering list HTML: %s", err) 514 } 515 return 516 } 517 518 w.Header().Set("Content-Type", "application/json") 519 json.NewEncoder(w).Encode(&list) 520 } 521 522 // HandleGetFile handles a GET request to bzz://<manifest>/<path> and responds 523 // with the content of the file at <path> from the given <manifest> 524 func (s *Server) HandleGetFile(w http.ResponseWriter, r *Request) { 525 key, err := s.api.Resolve(r.uri) 526 if err != nil { 527 s.Error(w, r, fmt.Errorf("error resolving %s: %s", r.uri.Addr, err)) 528 return 529 } 530 531 reader, contentType, _, err := s.api.Get(key, r.uri.Path) 532 if err != nil { 533 s.Error(w, r, err) 534 return 535 } 536 537 // check the root chunk exists by retrieving the file's size 538 if _, err := reader.Size(nil); err != nil { 539 s.logDebug("file not found %s: %s", r.uri, err) 540 http.NotFound(w, &r.Request) 541 return 542 } 543 544 w.Header().Set("Content-Type", contentType) 545 546 http.ServeContent(w, &r.Request, "", time.Now(), reader) 547 } 548 549 func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { 550 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")) 551 552 uri, err := api.Parse(strings.TrimLeft(r.URL.Path, "/")) 553 if err != nil { 554 s.logError("Invalid URI %q: %s", r.URL.Path, err) 555 http.Error(w, fmt.Sprintf("Invalid bzz URI: %s", err), http.StatusBadRequest) 556 return 557 } 558 s.logDebug("%s request received for %s", r.Method, uri) 559 560 req := &Request{Request: *r, uri: uri} 561 switch r.Method { 562 case "POST": 563 if uri.Raw() { 564 s.HandlePostRaw(w, req) 565 } else { 566 s.HandlePostFiles(w, req) 567 } 568 569 case "PUT": 570 // DEPRECATED: 571 // clients should send a POST request (the request creates a 572 // new manifest leaving the existing one intact, so it isn't 573 // strictly a traditional PUT request which replaces content 574 // at a URI, and POST is more ubiquitous) 575 if uri.Raw() { 576 http.Error(w, fmt.Sprintf("No PUT to %s allowed.", uri), http.StatusBadRequest) 577 return 578 } else { 579 s.HandlePostFiles(w, req) 580 } 581 582 case "DELETE": 583 if uri.Raw() { 584 http.Error(w, fmt.Sprintf("No DELETE to %s allowed.", uri), http.StatusBadRequest) 585 return 586 } 587 s.HandleDelete(w, req) 588 589 case "GET": 590 if uri.Raw() { 591 s.HandleGetRaw(w, req) 592 return 593 } 594 595 if r.Header.Get("Accept") == "application/x-tar" { 596 s.HandleGetFiles(w, req) 597 return 598 } 599 600 if r.URL.Query().Get("list") == "true" { 601 s.HandleGetList(w, req) 602 return 603 } 604 605 s.HandleGetFile(w, req) 606 607 default: 608 http.Error(w, "Method "+r.Method+" is not supported.", http.StatusMethodNotAllowed) 609 610 } 611 } 612 613 func (s *Server) updateManifest(key storage.Key, update func(mw *api.ManifestWriter) error) (storage.Key, error) { 614 mw, err := s.api.NewManifestWriter(key, nil) 615 if err != nil { 616 return nil, err 617 } 618 619 if err := update(mw); err != nil { 620 return nil, err 621 } 622 623 key, err = mw.Store() 624 if err != nil { 625 return nil, err 626 } 627 s.logDebug("generated manifest %s", key) 628 return key, nil 629 } 630 631 func (s *Server) logDebug(format string, v ...interface{}) { 632 log.Debug(fmt.Sprintf("[BZZ] HTTP: "+format, v...)) 633 } 634 635 func (s *Server) logError(format string, v ...interface{}) { 636 log.Error(fmt.Sprintf("[BZZ] HTTP: "+format, v...)) 637 } 638 639 func (s *Server) BadRequest(w http.ResponseWriter, r *Request, reason string) { 640 s.logDebug("bad request %s %s: %s", r.Method, r.uri, reason) 641 http.Error(w, reason, http.StatusBadRequest) 642 } 643 644 func (s *Server) Error(w http.ResponseWriter, r *Request, err error) { 645 s.logError("error serving %s %s: %s", r.Method, r.uri, err) 646 http.Error(w, err.Error(), http.StatusInternalServerError) 647 }