Go 實現對 HTTP 對象的尋找

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。想象一下,在 `HTTP` 伺服器上有一個巨大的 `ZIP` 檔案,你想知道裡面的內容。你不知道壓縮包內是否有你需要的東西,而且你不想下載整個檔案。是否可以像執行 `unzip -l https://example.com/giant.zip` 的操作來查看壓縮包的內容呢?這並不是一個為了用 `Go` 展示某些知識的理論問題。實際上,我也不想寫一篇文章,除了我想通過那些壓縮檔瞭解如何從 [美國專利和商標局(USPTO)](https://bulkdata.uspto.gov/data/patent/officialgazette/2017/) 下載大量專利。或者,我認為,能夠從這些 `tar` 檔案中擷取 [1790 年發布的一些專利映像](https://bulkdata.uspto.gov/data/patent/grant/multipagepdf/1790_1999/) 有多酷?去看看。那裡有數百個巨大的 `ZIP` 和 `tarfiles` 值得探索!在 `ZIP` 檔案中最後的位置,有一個目錄。因此在本地磁碟上,`“unzip -l”` 就像“尋求最終結果,找到 `TOC`,解析並列印它”一樣簡單。事實上,我們可以知道 `Go` 是如何處理的,因為在 [`zip.NewReader` 函數](https://godoc.org/archive/zip#NewReader) 需要傳入一個檔案路徑。至於 `TAR` 檔案,它們被設計用於磁帶串流和記憶體稀少的時候,因此它們的目錄在檔案本身之間交錯排列。但我們不在本地,要從 `URL` 中讀取內容對我們來說很有挑戰。該怎麼辦?從哪裡開始?我們有幾件事需要考慮,然後我們可以規劃接下來的方向。尋找和讀取 `HTTP` 檔案也就是要找到和讀取 `Range` 標題。那麼,`USPTO` 伺服器是否支援 `Range` 頭呢?這很容易檢查,使用 `curl` 和 `HTTP HEAD` 請求:```shell$ curl -I https://bulkdata.uspto.gov/data/patent/officialgazette/2017/e-OG20170103_1434-1.zipHTTP/1.1 200 OKDate: Mon, 11 Dec 2017 21:10:26 GMTServer: ApacheLast-Modified: Tue, 03 Jan 2017 11:58:45 GMTETag: "afb8ac8-5452f63e0a82f"Accept-Ranges: bytesContent-Length: 184257224X-Frame-Options: DENYContent-Type: application/zip```請注意那裡的 `“Accept-Ranges”` 標題,它表示我們可以向它發送位元組範圍。`Range` 頭允許您像隨機訪問讀取作業系統的一樣操作 `HTTP`。(例如 [io.ReaderAt](https://godoc.org/io#ReaderAt) 介面)因此理論上可以選擇從 `Web` 伺服器下載其中包含中繼資料(目錄)的檔案部分來決定下載哪些位元組。現在我們需要寫一個處理 `ZIP` 檔案格式的方法,它可以讓我們使用具有 `Range` 頭部的 `HTTP` 的 `GET` 請求,唯讀取中繼資料的方式,實現替換“讀取下一個目錄標頭檔”的某個部分。這就是 `Go` 的 [`archive/zip`](https://golang.org/pkg/archive/zip) 和 [`archive/tar`](https://godoc.org/archive/tar) 包的實現!正如我們前面所說,[zip.NewReader](https://godoc.org/archive/zip#NewReader) 正在琢磨什麼位置開始尋找。然而,當我們看看 `TAR` 時,我們發現了一個問題。`tar.NewReader` 方法需要一個 `io.Reader`。`io.Reader` 的問題在於,它不會讓我們隨機訪問資源,就像`io.ReaderAt` 一樣。它是這樣實現的,因為它使 `tar` 包更具適應性。特別是,您可以將 `Go tar` 包直接掛接到 `compress/gzip` 包並讀取 `tar.gz` 檔案 - 只要您按順序讀取它們,而不是像我們希望的那樣跳過。那麼該怎麼辦?使用源碼。環顧四周,找找[下一個方法](https://github.com/golang/go/blob/c007ce824d9a4fccb148f9204e04c23ed2984b71/src/archive/tar/reader.go#L88)。這就是我們期望它能夠找到下一個中繼資料的地方。在幾行代碼內,對於 [`skipUnread`](https://github.com/golang/go/blob/c007ce824d9a4fccb148f9204e04c23ed2984b71/src/archive/tar/reader.go#L407) 函數, 我們發現一個有趣的調用。在那裡,我們發現一些非常有趣的東西:```go// skipUnread skips any unread bytes in the existing file entry, as well as any alignment padding.func (tr *Reader) skipUnread() { nr := tr.numBytes() + tr.pad // number of bytes to skip tr.curr, tr.pad = nil, 0 if sr, ok := tr.r.(io.Seeker); ok { if _, err := sr.Seek(nr, os.SEEK_CUR); err == nil { return } } _, tr.err = io.CopyN(ioutil.Discard, tr.r, nr)}// Note: This is from Go 1.4, which had a simpler skipUnread than go 1.9 does.```這裡表示:”如果 `io.Reader` 實際上也能夠搜尋,那麼我們不是直接讀取和丟棄,而是直接找到正確的地方。“找到了!我們只需要將 `tar` 檔案傳給 `io.Reader`。`NewReader` 也滿足 [`io.Seeker`](https://golang.org/pkg/io/#Seeker)的功能(因此,它是一個[`io.ReadSeeker`](https://golang.org/pkg/io/#ReadSeeker))。所以,現在請查看包 [`github.com/jeffallen/seekinghttp`](https://godoc.org/github.com/jeffallen/seekinghttp),就像它的名字所暗示的那樣,它是一個用於在 `HTTP` 對象([`Github` 上的原始碼](https://github.com/jeffallen/seekinghttp) 中尋找的軟體包。這個軟體包不僅[實現](https://github.com/jeffallen/seekinghttp/blob/master/seekinghttp.go#L26)了 `io.ReadSeeker`,還實現了 `io.ReaderAt`。為什嗎?因為,正如我上面提到的,讀取 `ZIP` 檔案需要一個 `io.ReaderAt`。它還需要傳遞給它的檔案的長度,以便它可以查看目錄檔案的末尾。`HTTP HEAD` 方法可以很好地擷取 `HTTP` 對象的 `Content-Length`,而不需要下載整個檔案。用於遠程擷取 `tar` 和 `zip` 檔案目錄的命令列工具位於 `remote-archive-ls` 中。開啟 `“-debug”` 選項用來查看日誌。**將 `Go` 的標準庫作為 `TAR` 或 `ZIP` 閱讀器“回調”到我們的代碼中,並在這裡請求幾個位元組,這裡有幾個位元組是很有趣的。** 在我第一次運行這個程式後不久,我發現了一個嚴重的缺陷。這是一個樣本運行結果:``` shell$ ./remote-archive-ls -debug 'https://bulkdata.uspto.gov/data/patent/grant/multipagepdf/1790_1999/grant_pdf_17900731_18641101.tar'2017/12/12 00:07:38 got read len 5122017/12/12 00:07:38 ReadAt len 512 off 02017/12/12 00:07:38 Start HTTP GET with Range: bytes=0-5112017/12/12 00:07:39 HTTP ok.File: 00000001-X009741H/2017/12/12 00:07:39 got read len 5122017/12/12 00:07:39 ReadAt len 512 off 5122017/12/12 00:07:39 Start HTTP GET with Range: bytes=512-10232017/12/12 00:07:39 HTTP ok.File: 00000001-X009741H/00/2017/12/12 00:07:39 got read len 5122017/12/12 00:07:39 ReadAt len 512 off 10242017/12/12 00:07:39 Start HTTP GET with Range: bytes=1024-15352017/12/12 00:07:39 HTTP ok.File: 00000001-X009741H/00/000/2017/12/12 00:07:39 got read len 5122017/12/12 00:07:39 ReadAt len 512 off 15362017/12/12 00:07:39 Start HTTP GET with Range: bytes=1536-20472017/12/12 00:07:39 HTTP ok.File: 00000001-X009741H/00/000/001/2017/12/12 00:07:39 got read len 5122017/12/12 00:07:39 ReadAt len 512 off 20482017/12/12 00:07:39 Start HTTP GET with Range: bytes=2048-25592017/12/12 00:07:39 HTTP ok.File: 00000001-X009741H/00/000/001/us-patent-image.xml2017/12/12 00:07:39 got seek 0 12017/12/12 00:07:39 got seek 982 12017/12/12 00:07:39 got read len 422017/12/12 00:07:39 ReadAt len 42 off 35422017/12/12 00:07:39 Start HTTP GET with Range: bytes=3542-35832017/12/12 00:07:39 HTTP ok.2017/12/12 00:07:39 got read len 5122017/12/12 00:07:39 ReadAt len 512 off 35842017/12/12 00:07:39 Start HTTP GET with Range: bytes=3584-40952017/12/12 00:07:39 HTTP ok.File: 00000001-X009741H/00/000/001/00000001.pdf2017/12/12 00:07:39 got seek 0 12017/12/12 00:07:39 got seek 320840 12017/12/12 00:07:39 got read len 1842017/12/12 00:07:39 ReadAt len 184 off 324936...etc...```你能看到問題嗎?這是很多 `HTTP` 事務! `TAR reader` 正在一次一點點地完成 `TAR` 流,發出一小串 `bit`。所有這些短的 `HTTP` 事務在伺服器上都很難實現,並且對於輸送量來說很糟糕,因為每個 `HTTP` 事務都需要多次往返伺服器。當然,解決方案是緩衝。**讀取TAR讀取器要求的前 512 個位元組,而不是讀取其中的 10 倍,以便接下來的幾個讀取將直接從緩衝中擷取。**如果讀取超出了緩衝的範圍,我們假設其他讀取也將進入該地區,並刪除整個當前緩衝,以便用當前位移量的 10 倍填充它。`TAR` 閱讀器發送**大量小讀數**的事實指出了有關緩衝的一些非常重要的事情。將 [`os.Open`](https://godoc.org/os#Open) 的結果直接發送給 `tar`。`NewReader` 不是很聰明,尤其是如果你打算跳過檔案尋找中繼資料。儘管 `* os.File` 實現了 `io.ReadSeeker`,我們現在知道 `TAR` 將會向核心發出大量的**小系統調用**。該解決方案與上面的解決方案非常相似,可能是使用 [`bufio`](https://godoc.org/bufio) 包來緩衝 `* os.File`,以便 `TAR` 發出的小資料將從 `RAM` 中取出,而不是轉到作業系統。但請注意:它真的是解決方案嗎?`bufio.Reader` 是否真的實現了 `io`?`ReadSeeker` 和 `io.ReadAt` 就像我們需要的一樣? **(破壞者:它沒有;也許你們有讀者想告訴我們如何使用下一個的替代品 `bufio` 加速 `Go` 的 `tar`?**我希望你喜歡通過標準庫和 `HTTP`,看看如何與標準庫一起工作,以協助它實現更多的功能,以便它可以協助你完成你的工作這個小小的旅程。當你實現 `io.Reader` 和朋友時,你有機會走到你所調用的庫的幕後,並從他們的作者從未期望的地方給他們提供資料!

via: https://blog.gopheracademy.com/advent-2017/seekable-http/

作者:Jeff R. Allen 譯者:deadvia 校對:Unknwon

本文由 GCTT 原創編譯,Go語言中文網 榮譽推出

本文由 GCTT 原創翻譯,Go語言中文網 首發。也想加入譯者行列,為開源做一些自己的貢獻嗎?歡迎加入 GCTT!
翻譯工作和譯文發表僅用於學習和交流目的,翻譯工作遵照 CC-BY-NC-SA 協議規定,如果我們的工作有侵犯到您的權益,請及時聯絡我們。
歡迎遵照 CC-BY-NC-SA 協議規定 轉載,敬請在本文中標註並保留原文/譯文連結和作者/譯者等資訊。
文章僅代表作者的知識和看法,如有不同觀點,請樓下排隊吐槽

404 次點擊  ∙  1 贊  

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.