github.com/linapex/ethereum-dpos-chinese@v0.0.0-20190316121959-b78b3a4a1ece/swarm/api/client/client.go (about) 1 2 //<developer> 3 // <name>linapex 曹一峰</name> 4 // <email>linapex@163.com</email> 5 // <wx>superexc</wx> 6 // <qqgroup>128148617</qqgroup> 7 // <url>https://jsq.ink</url> 8 // <role>pku engineer</role> 9 // <date>2019-03-16 12:09:46</date> 10 //</624342668566073344> 11 12 13 package client 14 15 import ( 16 "archive/tar" 17 "bytes" 18 "encoding/json" 19 "errors" 20 "fmt" 21 "io" 22 "io/ioutil" 23 "mime" 24 "mime/multipart" 25 "net/http" 26 "net/textproto" 27 "os" 28 "path/filepath" 29 "regexp" 30 "strconv" 31 "strings" 32 33 "github.com/ethereum/go-ethereum/swarm/api" 34 "github.com/ethereum/go-ethereum/swarm/storage/mru" 35 ) 36 37 var ( 38 DefaultGateway = "http://本地主机:8500 39 DefaultClient = NewClient(DefaultGateway) 40 ) 41 42 var ( 43 ErrUnauthorized = errors.New("unauthorized") 44 ) 45 46 func NewClient(gateway string) *Client { 47 return &Client{ 48 Gateway: gateway, 49 } 50 } 51 52 //客户端将与Swarm HTTP网关的交互进行包装。 53 type Client struct { 54 Gateway string 55 } 56 57 //uploadraw将原始数据上载到swarm并返回结果哈希。如果加密是真的 58 //上载加密数据 59 func (c *Client) UploadRaw(r io.Reader, size int64, toEncrypt bool) (string, error) { 60 if size <= 0 { 61 return "", errors.New("data size must be greater than zero") 62 } 63 addr := "" 64 if toEncrypt { 65 addr = "encrypt" 66 } 67 req, err := http.NewRequest("POST", c.Gateway+"/bzz-raw:/"+addr, r) 68 if err != nil { 69 return "", err 70 } 71 req.ContentLength = size 72 res, err := http.DefaultClient.Do(req) 73 if err != nil { 74 return "", err 75 } 76 defer res.Body.Close() 77 if res.StatusCode != http.StatusOK { 78 return "", fmt.Errorf("unexpected HTTP status: %s", res.Status) 79 } 80 data, err := ioutil.ReadAll(res.Body) 81 if err != nil { 82 return "", err 83 } 84 return string(data), nil 85 } 86 87 //downloadraw从swarm下载原始数据,它返回readcloser和bool 88 //内容已加密 89 func (c *Client) DownloadRaw(hash string) (io.ReadCloser, bool, error) { 90 uri := c.Gateway + "/bzz-raw:/" + hash 91 res, err := http.DefaultClient.Get(uri) 92 if err != nil { 93 return nil, false, err 94 } 95 if res.StatusCode != http.StatusOK { 96 res.Body.Close() 97 return nil, false, fmt.Errorf("unexpected HTTP status: %s", res.Status) 98 } 99 isEncrypted := (res.Header.Get("X-Decrypted") == "true") 100 return res.Body, isEncrypted, nil 101 } 102 103 //文件表示群清单中的文件,用于上传和 104 //从Swarm下载内容 105 type File struct { 106 io.ReadCloser 107 api.ManifestEntry 108 } 109 110 //打开打开一个本地文件,然后可以将其传递到客户端。上载以上载 111 //它蜂拥而至 112 func Open(path string) (*File, error) { 113 f, err := os.Open(path) 114 if err != nil { 115 return nil, err 116 } 117 stat, err := f.Stat() 118 if err != nil { 119 f.Close() 120 return nil, err 121 } 122 return &File{ 123 ReadCloser: f, 124 ManifestEntry: api.ManifestEntry{ 125 ContentType: mime.TypeByExtension(filepath.Ext(path)), 126 Mode: int64(stat.Mode()), 127 Size: stat.Size(), 128 ModTime: stat.ModTime(), 129 }, 130 }, nil 131 } 132 133 //上载将文件上载到Swarm,并将其添加到现有清单中 134 //(如果manifest参数非空)或创建包含 135 //文件,返回生成的清单哈希(然后该文件将 136 //可在bzz:/<hash>/<path>)获取 137 func (c *Client) Upload(file *File, manifest string, toEncrypt bool) (string, error) { 138 if file.Size <= 0 { 139 return "", errors.New("file size must be greater than zero") 140 } 141 return c.TarUpload(manifest, &FileUploader{file}, "", toEncrypt) 142 } 143 144 //下载从swarm manifest下载具有给定路径的文件 145 //给定的哈希(即它得到bzz:/<hash>/<path>) 146 func (c *Client) Download(hash, path string) (*File, error) { 147 uri := c.Gateway + "/bzz:/" + hash + "/" + path 148 res, err := http.DefaultClient.Get(uri) 149 if err != nil { 150 return nil, err 151 } 152 if res.StatusCode != http.StatusOK { 153 res.Body.Close() 154 return nil, fmt.Errorf("unexpected HTTP status: %s", res.Status) 155 } 156 return &File{ 157 ReadCloser: res.Body, 158 ManifestEntry: api.ManifestEntry{ 159 ContentType: res.Header.Get("Content-Type"), 160 Size: res.ContentLength, 161 }, 162 }, nil 163 } 164 165 //uploadDirectory将目录树上载到swarm并添加文件 166 //到现有清单(如果清单参数非空)或创建 167 //新清单,返回生成的清单哈希(来自 168 //目录将在bzz:/<hash>/path/to/file处可用,其中 169 //默认路径中指定的文件正在上载到清单的根目录 170 //(即bzz/<hash>/) 171 func (c *Client) UploadDirectory(dir, defaultPath, manifest string, toEncrypt bool) (string, error) { 172 stat, err := os.Stat(dir) 173 if err != nil { 174 return "", err 175 } else if !stat.IsDir() { 176 return "", fmt.Errorf("not a directory: %s", dir) 177 } 178 if defaultPath != "" { 179 if _, err := os.Stat(filepath.Join(dir, defaultPath)); err != nil { 180 if os.IsNotExist(err) { 181 return "", fmt.Errorf("the default path %q was not found in the upload directory %q", defaultPath, dir) 182 } 183 return "", fmt.Errorf("default path: %v", err) 184 } 185 } 186 return c.TarUpload(manifest, &DirectoryUploader{dir}, defaultPath, toEncrypt) 187 } 188 189 //下载目录下载群清单中包含的文件 190 //到本地目录的给定路径(现有文件将被覆盖) 191 func (c *Client) DownloadDirectory(hash, path, destDir, credentials string) error { 192 stat, err := os.Stat(destDir) 193 if err != nil { 194 return err 195 } else if !stat.IsDir() { 196 return fmt.Errorf("not a directory: %s", destDir) 197 } 198 199 uri := c.Gateway + "/bzz:/" + hash + "/" + path 200 req, err := http.NewRequest("GET", uri, nil) 201 if err != nil { 202 return err 203 } 204 if credentials != "" { 205 req.SetBasicAuth("", credentials) 206 } 207 req.Header.Set("Accept", "application/x-tar") 208 res, err := http.DefaultClient.Do(req) 209 if err != nil { 210 return err 211 } 212 defer res.Body.Close() 213 switch res.StatusCode { 214 case http.StatusOK: 215 case http.StatusUnauthorized: 216 return ErrUnauthorized 217 default: 218 return fmt.Errorf("unexpected HTTP status: %s", res.Status) 219 } 220 tr := tar.NewReader(res.Body) 221 for { 222 hdr, err := tr.Next() 223 if err == io.EOF { 224 return nil 225 } else if err != nil { 226 return err 227 } 228 //忽略默认路径文件 229 if hdr.Name == "" { 230 continue 231 } 232 233 dstPath := filepath.Join(destDir, filepath.Clean(strings.TrimPrefix(hdr.Name, path))) 234 if err := os.MkdirAll(filepath.Dir(dstPath), 0755); err != nil { 235 return err 236 } 237 var mode os.FileMode = 0644 238 if hdr.Mode > 0 { 239 mode = os.FileMode(hdr.Mode) 240 } 241 dst, err := os.OpenFile(dstPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, mode) 242 if err != nil { 243 return err 244 } 245 n, err := io.Copy(dst, tr) 246 dst.Close() 247 if err != nil { 248 return err 249 } else if n != hdr.Size { 250 return fmt.Errorf("expected %s to be %d bytes but got %d", hdr.Name, hdr.Size, n) 251 } 252 } 253 } 254 255 //下载文件将单个文件下载到目标目录中 256 //如果清单项未指定文件名-它将回退 257 //以文件名的形式传递到文件的哈希 258 func (c *Client) DownloadFile(hash, path, dest, credentials string) error { 259 hasDestinationFilename := false 260 if stat, err := os.Stat(dest); err == nil { 261 hasDestinationFilename = !stat.IsDir() 262 } else { 263 if os.IsNotExist(err) { 264 //不存在-应创建 265 hasDestinationFilename = true 266 } else { 267 return fmt.Errorf("could not stat path: %v", err) 268 } 269 } 270 271 manifestList, err := c.List(hash, path, credentials) 272 if err != nil { 273 return err 274 } 275 276 switch len(manifestList.Entries) { 277 case 0: 278 return fmt.Errorf("could not find path requested at manifest address. make sure the path you've specified is correct") 279 case 1: 280 //持续 281 default: 282 return fmt.Errorf("got too many matches for this path") 283 } 284 285 uri := c.Gateway + "/bzz:/" + hash + "/" + path 286 req, err := http.NewRequest("GET", uri, nil) 287 if err != nil { 288 return err 289 } 290 if credentials != "" { 291 req.SetBasicAuth("", credentials) 292 } 293 res, err := http.DefaultClient.Do(req) 294 if err != nil { 295 return err 296 } 297 defer res.Body.Close() 298 switch res.StatusCode { 299 case http.StatusOK: 300 case http.StatusUnauthorized: 301 return ErrUnauthorized 302 default: 303 return fmt.Errorf("unexpected HTTP status: expected 200 OK, got %d", res.StatusCode) 304 } 305 filename := "" 306 if hasDestinationFilename { 307 filename = dest 308 } else { 309 //尝试断言 310 re := regexp.MustCompile("[^/]+$") //最后一个斜线后的所有内容 311 312 if results := re.FindAllString(path, -1); len(results) > 0 { 313 filename = results[len(results)-1] 314 } else { 315 if entry := manifestList.Entries[0]; entry.Path != "" && entry.Path != "/" { 316 filename = entry.Path 317 } else { 318 //如果命令行中没有任何内容,则假定hash为名称 319 filename = hash 320 } 321 } 322 filename = filepath.Join(dest, filename) 323 } 324 filePath, err := filepath.Abs(filename) 325 if err != nil { 326 return err 327 } 328 329 if err := os.MkdirAll(filepath.Dir(filePath), 0777); err != nil { 330 return err 331 } 332 333 dst, err := os.Create(filename) 334 if err != nil { 335 return err 336 } 337 defer dst.Close() 338 339 _, err = io.Copy(dst, res.Body) 340 return err 341 } 342 343 //上载清单将给定清单上载到Swarm 344 func (c *Client) UploadManifest(m *api.Manifest, toEncrypt bool) (string, error) { 345 data, err := json.Marshal(m) 346 if err != nil { 347 return "", err 348 } 349 return c.UploadRaw(bytes.NewReader(data), int64(len(data)), toEncrypt) 350 } 351 352 //下载清单下载群清单 353 func (c *Client) DownloadManifest(hash string) (*api.Manifest, bool, error) { 354 res, isEncrypted, err := c.DownloadRaw(hash) 355 if err != nil { 356 return nil, isEncrypted, err 357 } 358 defer res.Close() 359 var manifest api.Manifest 360 if err := json.NewDecoder(res).Decode(&manifest); err != nil { 361 return nil, isEncrypted, err 362 } 363 return &manifest, isEncrypted, nil 364 } 365 366 //列出具有给定前缀、分组的群清单中的列表文件 367 //使用“/”作为分隔符的常见前缀。 368 // 369 //例如,如果清单表示以下目录结构: 370 // 371 //文件1.TXT 372 //文件2.TXT 373 //DRI1/FIL3.TXT 374 //dir1/dir2/file4.txt文件 375 // 376 //然后: 377 // 378 //-前缀“”将返回[dir1/,file1.txt,file2.txt] 379 //-前缀“file”将返回[file1.txt,file2.txt] 380 //-前缀“dir1/”将返回[dir1/dir2/,dir1/file3.txt] 381 // 382 //其中以“/”结尾的条目是常见的前缀。 383 func (c *Client) List(hash, prefix, credentials string) (*api.ManifestList, error) { 384 req, err := http.NewRequest(http.MethodGet, c.Gateway+"/bzz-list:/"+hash+"/"+prefix, nil) 385 if err != nil { 386 return nil, err 387 } 388 if credentials != "" { 389 req.SetBasicAuth("", credentials) 390 } 391 res, err := http.DefaultClient.Do(req) 392 if err != nil { 393 return nil, err 394 } 395 defer res.Body.Close() 396 switch res.StatusCode { 397 case http.StatusOK: 398 case http.StatusUnauthorized: 399 return nil, ErrUnauthorized 400 default: 401 return nil, fmt.Errorf("unexpected HTTP status: %s", res.Status) 402 } 403 var list api.ManifestList 404 if err := json.NewDecoder(res.Body).Decode(&list); err != nil { 405 return nil, err 406 } 407 return &list, nil 408 } 409 410 //上载程序使用提供的上载将文件上载到Swarm fn 411 type Uploader interface { 412 Upload(UploadFn) error 413 } 414 415 type UploaderFunc func(UploadFn) error 416 417 func (u UploaderFunc) Upload(upload UploadFn) error { 418 return u(upload) 419 } 420 421 //DirectoryUploader上载目录中的所有文件,可以选择上载 422 //默认路径的文件 423 type DirectoryUploader struct { 424 Dir string 425 } 426 427 //上载执行目录和默认路径的上载 428 func (d *DirectoryUploader) Upload(upload UploadFn) error { 429 return filepath.Walk(d.Dir, func(path string, f os.FileInfo, err error) error { 430 if err != nil { 431 return err 432 } 433 if f.IsDir() { 434 return nil 435 } 436 file, err := Open(path) 437 if err != nil { 438 return err 439 } 440 relPath, err := filepath.Rel(d.Dir, path) 441 if err != nil { 442 return err 443 } 444 file.Path = filepath.ToSlash(relPath) 445 return upload(file) 446 }) 447 } 448 449 //文件上载程序上载单个文件 450 type FileUploader struct { 451 File *File 452 } 453 454 //上载执行文件上载 455 func (f *FileUploader) Upload(upload UploadFn) error { 456 return upload(f.File) 457 } 458 459 //uploadfn是传递给上载程序以执行上载的函数类型。 460 //对于单个文件(例如,目录上载程序将调用 461 //目录树中每个文件的uploadfn) 462 type UploadFn func(file *File) error 463 464 //tar upload使用给定的上传器将文件作为tar流上传到swarm, 465 //返回结果清单哈希 466 func (c *Client) TarUpload(hash string, uploader Uploader, defaultPath string, toEncrypt bool) (string, error) { 467 reqR, reqW := io.Pipe() 468 defer reqR.Close() 469 addr := hash 470 471 //如果已经存在哈希(清单),那么该清单将确定上载是否 472 //是否加密。如果没有清单,则toEncrypt参数决定 473 //是否加密。 474 if hash == "" && toEncrypt { 475 //这是加密上载端点的内置地址 476 addr = "encrypt" 477 } 478 req, err := http.NewRequest("POST", c.Gateway+"/bzz:/"+addr, reqR) 479 if err != nil { 480 return "", err 481 } 482 req.Header.Set("Content-Type", "application/x-tar") 483 if defaultPath != "" { 484 q := req.URL.Query() 485 q.Set("defaultpath", defaultPath) 486 req.URL.RawQuery = q.Encode() 487 } 488 489 //使用“expect:100 continue”,以便在以下情况下不发送请求正文: 490 //服务器拒绝请求 491 req.Header.Set("Expect", "100-continue") 492 493 tw := tar.NewWriter(reqW) 494 495 //定义将文件添加到tar流的uploadfn 496 uploadFn := func(file *File) error { 497 hdr := &tar.Header{ 498 Name: file.Path, 499 Mode: file.Mode, 500 Size: file.Size, 501 ModTime: file.ModTime, 502 Xattrs: map[string]string{ 503 "user.swarm.content-type": file.ContentType, 504 }, 505 } 506 if err := tw.WriteHeader(hdr); err != nil { 507 return err 508 } 509 _, err = io.Copy(tw, file) 510 return err 511 } 512 513 //在Goroutine中运行上载,以便我们可以发送请求头和 514 //在发送tar流之前,等待“100 continue”响应 515 go func() { 516 err := uploader.Upload(uploadFn) 517 if err == nil { 518 err = tw.Close() 519 } 520 reqW.CloseWithError(err) 521 }() 522 523 res, err := http.DefaultClient.Do(req) 524 if err != nil { 525 return "", err 526 } 527 defer res.Body.Close() 528 if res.StatusCode != http.StatusOK { 529 return "", fmt.Errorf("unexpected HTTP status: %s", res.Status) 530 } 531 data, err := ioutil.ReadAll(res.Body) 532 if err != nil { 533 return "", err 534 } 535 return string(data), nil 536 } 537 538 //multipartupload使用给定的上载程序将文件作为 539 //多部分表单,返回结果清单哈希 540 func (c *Client) MultipartUpload(hash string, uploader Uploader) (string, error) { 541 reqR, reqW := io.Pipe() 542 defer reqR.Close() 543 req, err := http.NewRequest("POST", c.Gateway+"/bzz:/"+hash, reqR) 544 if err != nil { 545 return "", err 546 } 547 548 //使用“expect:100 continue”,以便在以下情况下不发送请求正文: 549 //服务器拒绝请求 550 req.Header.Set("Expect", "100-continue") 551 552 mw := multipart.NewWriter(reqW) 553 req.Header.Set("Content-Type", fmt.Sprintf("multipart/form-data; boundary=%q", mw.Boundary())) 554 555 //定义将文件添加到多部分表单的uploadfn 556 uploadFn := func(file *File) error { 557 hdr := make(textproto.MIMEHeader) 558 hdr.Set("Content-Disposition", fmt.Sprintf("form-data; name=%q", file.Path)) 559 hdr.Set("Content-Type", file.ContentType) 560 hdr.Set("Content-Length", strconv.FormatInt(file.Size, 10)) 561 w, err := mw.CreatePart(hdr) 562 if err != nil { 563 return err 564 } 565 _, err = io.Copy(w, file) 566 return err 567 } 568 569 //在Goroutine中运行上载,以便我们可以发送请求头和 570 //在发送多部分表单之前,请等待“100继续”响应 571 go func() { 572 err := uploader.Upload(uploadFn) 573 if err == nil { 574 err = mw.Close() 575 } 576 reqW.CloseWithError(err) 577 }() 578 579 res, err := http.DefaultClient.Do(req) 580 if err != nil { 581 return "", err 582 } 583 defer res.Body.Close() 584 if res.StatusCode != http.StatusOK { 585 return "", fmt.Errorf("unexpected HTTP status: %s", res.Status) 586 } 587 data, err := ioutil.ReadAll(res.Body) 588 if err != nil { 589 return "", err 590 } 591 return string(data), nil 592 } 593 594 //createResource创建具有给定名称和频率的可变资源,并使用提供的 595 //数据。数据是否解释为多哈希,取决于多哈希参数。 596 //starttime=0表示“现在” 597 //返回生成的可变资源清单地址,可用于包含在ENS解析程序(setcontent)中。 598 //或引用将来的更新(client.updateResource) 599 func (c *Client) CreateResource(request *mru.Request) (string, error) { 600 responseStream, err := c.updateResource(request) 601 if err != nil { 602 return "", err 603 } 604 defer responseStream.Close() 605 606 body, err := ioutil.ReadAll(responseStream) 607 if err != nil { 608 return "", err 609 } 610 611 var manifestAddress string 612 if err = json.Unmarshal(body, &manifestAddress); err != nil { 613 return "", err 614 } 615 return manifestAddress, nil 616 } 617 618 //UpdateResource允许您设置内容的新版本 619 func (c *Client) UpdateResource(request *mru.Request) error { 620 _, err := c.updateResource(request) 621 return err 622 } 623 624 func (c *Client) updateResource(request *mru.Request) (io.ReadCloser, error) { 625 body, err := request.MarshalJSON() 626 if err != nil { 627 return nil, err 628 } 629 630 req, err := http.NewRequest("POST", c.Gateway+"/bzz-resource:/", bytes.NewBuffer(body)) 631 if err != nil { 632 return nil, err 633 } 634 635 res, err := http.DefaultClient.Do(req) 636 if err != nil { 637 return nil, err 638 } 639 640 return res.Body, nil 641 642 } 643 644 //GetResource返回包含资源原始内容的字节流 645 //ManifestAddressOrDomain是您在CreateResource或其解析程序的ENS域中获得的地址。 646 //指向那个地址 647 func (c *Client) GetResource(manifestAddressOrDomain string) (io.ReadCloser, error) { 648 649 res, err := http.Get(c.Gateway + "/bzz-resource:/" + manifestAddressOrDomain) 650 if err != nil { 651 return nil, err 652 } 653 return res.Body, nil 654 655 } 656 657 //GetResourceMetadata返回一个描述可变资源的结构 658 //ManifestAddressOrDomain是您在CreateResource或其解析程序的ENS域中获得的地址。 659 //指向那个地址 660 func (c *Client) GetResourceMetadata(manifestAddressOrDomain string) (*mru.Request, error) { 661 662 responseStream, err := c.GetResource(manifestAddressOrDomain + "/meta") 663 if err != nil { 664 return nil, err 665 } 666 defer responseStream.Close() 667 668 body, err := ioutil.ReadAll(responseStream) 669 if err != nil { 670 return nil, err 671 } 672 673 var metadata mru.Request 674 if err := metadata.UnmarshalJSON(body); err != nil { 675 return nil, err 676 } 677 return &metadata, nil 678 } 679