2007 年 6 月
簡介
開發與網路服務互動的應用程式時,會遇到一連串獨特的問題。如果不知道傳送至伺服器的確切訊息,或收到的回應為何,就很容易感到沮喪。最難追蹤的錯誤,通常是我們認為要傳送至伺服器的內容,與實際透過線路傳送的內容不一致所致。
本文將介紹幾項工具,協助您更清楚瞭解網路上的資料,並加以運用。這類工具通常稱為「封包監聽器」,可擷取在網路介面中移動的所有網路封包。檢查這些封包的內容,以及傳送和接收封包的順序,是實用的偵錯技術。
範例:擷取公開動態消息
我正在為慈善騎乘活動組建自行車隊,並建立日曆來記錄資訊說明會、團隊募款和訓練騎乘等活動。我已將這個日曆設為公開,因此團隊成員和其他車友可以查看日曆並參與活動。我也想發送即將舉辦活動的電子報,因此我可以使用 Google 日曆資料 API 查詢這個日曆並擷取活動,不必從 Google 日曆網站複製資訊。
Google Calendar API 說明文件提供相關資訊,說明如何使用 RESTful Google Data API,以程式輔助方式與我的日曆互動。(編輯者附註:自第 3 版起,Google Calendar API 不再使用 Google 資料格式)。首先,請按一下日曆設定頁面上的 按鈕,取得日曆的活動動態消息網址:
http://www.google.com/calendar/feeds/24vj3m5pl125bh2ijbbneh953s%40group.calendar.google.com/public/basic
以 Google 日曆說明文件為參考資料,我可以擷取及顯示日曆活動,如下所示,其中 PUBLIC_FEED_URL
包含活動動態消息網址。
CalendarService myService = new CalendarService("exampleCo-fiddlerExample-1"); final String PUBLIC_FEED_URL = "http://www.google.com/calendar/feeds/24vj3m5pl125bh2ijbbneh953s%40group.calendar.google.com/public/basic"; URL feedUrl = new URL(PUBLIC_FEED_URL); CalendarEventFeed resultFeed = myService.getFeed(feedUrl, CalendarEventFeed.class); System.out.println("All events on your calendar:"); for (int i = 0; i < resultFeed.getEntries().size(); i++) { CalendarEventEntry entry = resultFeed.getEntries().get(i); System.out.println("\t" + entry.getTitle().getPlainText()); } System.out.println();
這會產生日曆上活動的基本清單:
All events on your calendar: MS150 Training ride Meeting with Nicole MS150 Information session
上述程式碼片段會顯示日曆活動的標題,但我們從伺服器收到的其他資料呢?Java 用戶端程式庫無法輕鬆將動態饋給或項目輸出為 XML,即使可以,XML 也不是完整的故事。要求隨附的 HTTP 標頭呢?查詢是否經過 Proxy 或重新導向?如果作業較為複雜,這些問題就變得越來越重要,尤其是在發生錯誤時。封包側錄軟體可以顯示網路流量,回答這些問題。
tcpdump
tcpdump 是一種指令列工具,適用於類似 Unix 的平台,但也有稱為 WinDump 的 Windows 連接埠。與大多數封包監聽器一樣,tcpdump 會將網路卡設為任意模式,這需要超級使用者權限。如要使用 tcpdump,只要指定要監聽的網路介面,網路流量就會傳送至 stdout:
sudo tcpdump -i eth0
如果執行這項指令,您會看到各種網路流量,其中有些您甚至不認識。您可以將輸出內容轉送至檔案,然後 grep 該檔案,但這可能會導致檔案非常龐大。大多數封包擷取軟體都內建篩選機制,因此您只會擷取所需的內容。
tcpdump 支援根據網路流量的各種特徵進行篩選。舉例來說,您可以在下列運算式中插入伺服器的主機名稱,讓 tcpdump 只擷取通訊埠 80 (HTTP 訊息) 上往來伺服器的流量:
dst or src host <hostname> and port 80
對於符合篩選器運算式的每個封包,tcpdump 會顯示時間戳記、封包的來源和目的地,以及數個 TCP 標記。這項資訊很有價值,因為它會顯示封包的傳送和接收順序。
查看封包內容通常也很有用。「-A」旗標會指示 tcpdump 以 ASCII 格式列印每個封包,顯示 HTTP 標頭和訊息主體。「-s」標記用於指定要顯示的位元組數 (其中「-s 0」表示完全不要截斷郵件內文)。
將所有內容整合在一起,即可得到下列指令:
sudo tcpdump -A -s 0 -i eth0 dst or src host <hostname> and port 80
執行這項指令,然後執行上述簡短的 .Java 範例,您會看到這項作業涉及的所有網路通訊。在您看到的流量中,會出現 HTTP GET
要求:
22:22:30.870771 IP dellalicious.mshome.net.4520 > po-in-f99.google.com.80: P 1:360(359) ack 1 win 65535 E.....@....\...eH..c...P.=.....zP......GET /calendar/feeds/24vj3m5pl125bh2ijbbneh953s%40group.calendar.google.com/public/basic HTTP/1.1 User-Agent: exampleCo-fiddlerExample-1 GCalendar-Java/1.0.6 GData-Java/1.0.10(gzip) Accept-Encoding: gzip Cache-Control: no-cache Pragma: no-cache Host: www.google.com Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2 Connection: keep-alive
您也會看到包含 Google 產品資料動態饋給的 200 OK
回覆訊息。請注意,動態饋給會分成四個封包:
22:22:31.148789 IP po-in-f99.google.com.80 > dellalicious.mshome.net.4520: . 1:1431(1430) ack 360 win 6432 E...1 ..2.I.H..c...e.P.....z.=.:P..M...HTTP/1.1 200 OK Content-Type: application/atom+xml; charset=UTF-8 Cache-Control: max-age=0, must-revalidate, private Last-Modified: Mon, 11 Jun 2007 15:11:40 GMT Transfer-Encoding: chunked Date: Sun, 24 Jun 2007 02:22:10 GMT Server: GFE/1.3 13da <?xml version='1.0' encoding='UTF-8'?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:gCal='http://sc hemas.google.com/gCal/2005' xmlns:gd='http://schemas.google.com/g/2005'><id>http ://www.google.com/calendar/feeds/24vj3m5pl125bh2ijbbneh953s%40group.calendar.goo gle.com/public/basic</id><updated>2007-06-11T15:11:40.000Z</updated><category sc heme='http://schemas.google.com/g/2005#kind' term='http://schemas.google.com/g/2 005#event'></category><title type='text'>MS150 Training Schedule</title><subtitl e type='text'>This calendar is public</subtitle><link rel='http://schemas.google .com/g/2005#feed' type='application/atom+xml' href='http://www.google.com/calend ar/feeds/24vj3m5pl125bh2ijbbneh953s%40group.calendar.google.com/public/basic'></ link><link rel='self' type='application/atom+xml' href='http://www.google.com/ca lendar/feeds/24vj3m5pl125bh2ijbbneh953s%40group.calendar.google.com/public/basic ?max-results=25'></link><author><name>Lane LiaBraaten</name><email>api.lliabraa@ gmail.com</email></author><generator version='1.0' uri='http://www.google.com/ca lendar'>Google Calendar</generator><openSearch:totalRe 22:22:31.151501 IP po-in-f99.google.com.80 > dellalicious.mshome.net.4520: . 1431:2861(1430) ack 360 win 6432 E...1!..2.I.H..c...e.P.......=.:P.. 2...sults>3</openSearch:totalResults><openSe arch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch :itemsPerPage><gd:where valueString=''></gd:where><gCal:timezone value='America/ Los_Angeles'></gCal:timezone><entry><id>http://www.google.com/calendar/feeds/24v j3m5pl125bh2ijbbneh953s%40group.calendar.google.com/public/basic/dgt40022cui2k3j 740hnj46744</id><published>2007-06-11T15:11:05.000Z</published><updated>2007-06- 11T15:11:05.000Z</updated><category scheme='http://schemas.google.com/g/2005#kin d' term='http://schemas.google.com/g/2005#event'></category><title type='text'>M S150 Training ride</title><summary type='html'>When: Sat Jun 9, 2007 7am to 10am &nbsp; PDT<br> <br>Event Status: confirmed</summary><conte nt type='text'>When: Sat Jun 9, 2007 7am to 10am&nbsp; PDT<br> <b r>Event Status: confirmed</content><link rel='alternate' type='text/html' href='http://www.google.com/calendar/event?eid=ZGd0NDAwMjJjdWkyazNqNzQwaG5qNDY3 NDQgMjR2ajNtNXBsMTI1YmgyaWpiYm5laDk1M3NAZw' title='alternate'></link><link rel=' self' type='application/atom+xml' href='http://www.google.com/calendar/feeds/24v j3m5pl125bh2ijbbneh953s%40group.calendar.google.com/public/basic/dgt40022cui2k3j 740hnj46744'></link><author><name>MS150 Training Schedule</name></author><gCal:s endEventNotifications value='false'></gCal:sendEventNotifications></entry><entry ><id>http://www.google.com/cal 22:22:31.153097 IP po-in-f99.google.com.80 > dellalicious.mshome.net.4520: . 2861:4291(1430) ack 360 win 6432 E...1#..2.I.H..c...e.P.......=.:P.. ....endar/feeds/24vj3m5pl125bh2ijbbneh953s%4 0group.calendar.google.com/public/basic/51d8kh4s3bplqnbf1lp6p0kjp8</id><publishe d>2007-06-11T15:08:23.000Z</published><updated>2007-06-11T15:10:39.000Z</updated ><category scheme='http://schemas.google.com/g/2005#kind' term='http://schemas.g oogle.com/g/2005#event'></category><title type='text'>Meeting with Nicole</title ><summary type='html'>When: Mon Jun 4, 2007 10am to 11am&nbsp; PDT<br> <br>Where: Conference Room B <br>Event Status: confirmed</summ ary><content type='text'>When: Mon Jun 4, 2007 10am to 11am&nbsp; PDT<br& gt; <br>Where: Conference Room B <br>Event Status: confirmed <br>Event Description: Discuss building cycling team for MS150</content><l ink rel='alternate' type='text/html' href='http://www.google.com/calendar/event? eid=NTFkOGtoNHMzYnBscW5iZjFscDZwMGtqcDggMjR2ajNtNXBsMTI1YmgyaWpiYm5laDk1M3NAZw' title='alternate'></link><link rel='self' type='application/atom+xml' href='http ://www.google.com/calendar/feeds/24vj3m5pl125bh2ijbbneh953s%40group.calendar.goo gle.com/public/basic/51d8kh4s3bplqnbf1lp6p0kjp8'></link><author><name>MS150 Trai ning Schedule</name></author><gCal:sendEventNotifications value='false'></gCal:s endEventNotifications></entry><entry><id>http://www.google.com/calendar/feeds/24 vj3m5pl125bh2ijbbneh953s%40group.calendar.google.com/public/basic/va41amq3r08dhh kpm3lc1abs2o</id><published>20 22:22:31.190244 IP po-in-f99.google.com.80 > dellalicious.mshome.net.4520: P 4291:5346(1055) ack 360 win 6432 E..G1$..2.K.H..c...e.P.....<.=.:P.. ....07-06-11T15:10:08.000Z</published><updat ed>2007-06-11T15:10:08.000Z</updated><category scheme='http://schemas.google.com /g/2005#kind' term='http://schemas.google.com/g/2005#event'></category><title ty pe='text'>MS150 Information session</title><summary type='html'>When: Wed Jun 6, 2007 4pm to Wed Jun 6, 2007 5pm&nbsp; PDT<br> <br>Event Statu s: confirmed</summary><content type='text'>When: Wed Jun 6, 2007 4pm to Wed Jun 6, 2007 5pm&nbsp; PDT<br> <br>Event Status: confirmed< /content><link rel='alternate' type='text/html' href='http://www.google.com/cale ndar/event?eid=dmE0MWFtcTNyMDhkaGhrcG0zbGMxYWJzMm8gMjR2ajNtNXBsMTI1YmgyaWpiYm5la Dk1M3NAZw' title='alternate'></link><link rel='self' type='application/atom+xml' href='http://www.google.com/calendar/feeds/24vj3m5pl125bh2ijbbneh953s%40group.c alendar.google.com/public/basic/va41amq3r08dhhkpm3lc1abs2o'></link><author><name >MS150 Training Schedule</name></author><gCal:sendEventNotifications value='fals e'></gCal:sendEventNotifications></entry></feed>
這項輸出內容包含所有 HTTP 標頭和內容,以及多個隱晦的 TCP 旗標。這裡會顯示所有資料,但可能難以閱讀和理解。您可以使用多種圖形工具,輕鬆查看這項資料。
WireShark (原稱 Ethereal)

WireShark 會以多種方式顯示網路流量。
WireShark 是以 libpcap 建構的圖形化工具,與 tcpdump 使用的程式庫相同,適用於 Linux、Mac OS X 和 Windows。WireShark 的 GUI 提供多種解讀封包擷取資料和與之互動的新方式。舉例來說,從網路介面擷取封包時,系統會根據封包使用的通訊協定,以不同顏色顯示封包。您也可以依時間戳記、來源、目的地和通訊協定排序流量。
如果您在封包清單中選取資料列,Wireshark 會在封包標頭中以易讀樹狀結構顯示 IP、TCP 和其他通訊協定專屬資訊。畫面底部也會以十六進位和 ASCII 格式顯示資料。
雖然 WireShark 的視覺化特性有助於瞭解網路流量,但您在大多數情況下仍會想要篩選網路流量。WireShark 具有強大的篩選功能,支援數百種通訊協定。
提示:如要查看可用的通訊協定及建立複雜的篩選器,請按一下 WireShark 視窗頂端的 按鈕。
如要重新建立上述 tcpdump 範例中使用的篩選器,請將下列運算式插入 WireShark 篩選器方塊:
ip.addr==<your IP address> && tcp.port==80
或者,運用 WireShark 的 HTTP 知識:
ip.addr==<your IP address> && http
這樣一來,擷取結果就會經過篩選,只顯示與 Google 日曆伺服器互動時涉及的封包。您可以點選每個封包查看內容,並拼湊出交易。
提示:你可以對其中一個封包按一下滑鼠右鍵,然後選擇「Follow TCP Stream」,在單一視窗中依序顯示要求和回應。
WireShark 提供多種儲存擷取資訊的方式。您可以儲存一個、部分或所有封包。如果您正在查看 TCP 串流,只要按一下「另存為」按鈕,即可只儲存相關封包。您也可以匯入 tcpdump 擷取的輸出內容,並在 WireShark 中查看。
問題:SSL 和加密
封包擷取工具的常見缺點是無法查看透過 SSL 連線加密的資料。上述範例存取的是公開動態饋給,因此不需要 SSL。不過,如果範例存取的是私人動態消息,用戶端就必須向 Google 驗證服務進行驗證,這時就需要 SSL 連線。
下列程式碼片段與上一個範例類似,但這裡的 CalendarService
會要求使用者的日曆中繼動態消息,這是需要驗證的私人動態消息。如要進行驗證,只要呼叫 setUserCredentials
方法即可。這個方法會觸發對 ClientLogin 服務的 HTTPS 要求,並從回應中擷取驗證權杖。之後,CalendarService
物件就會在所有後續要求中加入驗證權杖。
CalendarService myService = new CalendarService("exampleCo-fiddlerSslExample-1"); myService.setUserCredentials(username, userPassword); final String METAFEED_URL = "http://www.google.com/calendar/feeds/default"; URL feedUrl = new URL(METAFEED_URL); CalendarFeed resultFeed = myService.getFeed(feedUrl, CalendarFeed.class); System.out.println("Your calendars:"); for (int i = 0; i < resultFeed.getEntries().size(); i++) { CalendarEntry entry = resultFeed.getEntries().get(i); System.out.println("\t" + entry.getTitle().getPlainText()); } System.out.println();
請考量驗證及存取私人 Google Data API 資訊動態饋給所需的網路流量:
- 將使用者憑證提交至 ClientLogin 服務
- 將 HTTP
POST
傳送至 https://www.google.com/accounts/ClientLogin,並在訊息主體中加入下列參數:- 電子郵件:使用者的電子郵件地址。
- Passwd - 使用者的密碼。
- source - 用於識別用戶端應用程式。格式應為 companyName-applicationName-versionID。範例使用 ExampleCo-FiddlerSSLExample-1 這個名稱。
- 服務 - Google 日曆的服務名稱為「cl」。
- 將 HTTP
- 接收授權權杖
- 如果驗證要求失敗,您會收到 HTTP 403 Forbidden 狀態碼。
- 如果成功,服務的回應會是 HTTP 200 OK 狀態碼,加上回應主體中的三個長英數字元代碼:
SID
、LSID
和Auth
。Auth
值是授權權杖。
- 要求私密日曆的 Metafeed
- 將 HTTP
GET
傳送至 http://www.google.com/calendar/feeds/default,並加上下列標頭:
Authorization: GoogleLogin auth=<yourAuthToken>
- 將 HTTP
請試著執行這段程式碼片段,並在 WireShark 中查看網路流量 (使用「http || ssl」做為篩選條件)。您會看到交易中涉及的 SSL 和 TLS 封包,但 ClientLogin 要求和回應封包會加密在「應用程式資料」封包中。別擔心,接下來我們會介紹一個工具,這個工具可以實際揭露這項加密資訊。
Fiddler
Fiddler 是另一項圖形封包監聽工具,但運作方式與目前介紹的工具大不相同。Fiddler 會充當應用程式與您互動的遠端服務之間的 Proxy,有效成為中間人。Fiddler 會與應用程式和遠端網路服務建立 SSL 連線,解密來自某個端點的流量、擷取純文字,然後重新加密流量,再傳送出去。很抱歉,Fiddler 僅適用於 Windows,Mac 和 Linux 使用者無法使用。
注意:如要支援 SSL,必須使用 Fiddler 第 2 版和 .NET Framework 2.0 版。
在 Fiddler 中查看網路流量時,主要會使用「Session Inspector」分頁。如要偵錯 Google Data API 的問題,最實用的子分頁包括:
- 標頭:以可收合的樹狀結構格式顯示 HTTP 標頭。
- Auth - 顯示驗證標頭。
- 原始:以 ASCII 文字顯示網路封包內容
提示:按一下 Fiddler 視窗左下角的 圖示,即可開啟及關閉擷取功能。
Fiddler 會使用 .NET Framework 設定網路連線,將 Fiddler 設為 Proxy。也就是說,您透過 Internet Explorer 或 .NET 程式碼建立的任何連線,預設都會顯示在 Fiddler 中。不過,由於 Java 設定 HTTP 代理伺服器的方式不同,因此上述 Java 範例的流量不會顯示。
在 Java 中,您可以使用系統屬性設定 HTTP Proxy。Fiddler 會在通訊埠 8888 上執行,因此如果是本機安裝,您可以新增下列程式碼行,讓 Java 程式碼將 Fiddler 做為 HTTP 和 HTTPS 的 Proxy:
System.setProperty("http.proxyHost", "localhost"); System.setProperty("http.proxyPort", "8888"); System.setProperty("https.proxyHost", "localhost"); System.setProperty("https.proxyPort", "8888");
如果您使用這些程式碼行執行範例,實際上會從 Java 安全性套件取得難看的堆疊追蹤記錄:
[java] Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

Fiddler 可以解密及顯示 SSL 流量。
如果無法驗證 SSL 連線中伺服器傳回的憑證,就會發生這項錯誤。在這個情況下,不良憑證來自 Fiddler,而 Fiddler 充當中間人。Fiddler 會即時產生憑證,但由於 Fiddler 並非信任的簽發者,這些憑證會導致 Java 無法設定 SSL 連線。
注意:執行 Fiddler 時,您在 Internet Explorer 中建立的任何 SSL 連線都會觸發「安全性快訊」,詢問您是否要繼續操作,即使憑證有問題也一樣。您可以按一下「查看憑證」,查看 Fiddler 產生的憑證。
那麼該如何規避這項安全性例外狀況?基本上,您需要重新設定 Java 的安全架構,以信任所有憑證。幸好您不必重新發明輪子,請查看 Francis Labrie 的解決方案,並將 SSLUtilities.trustAllHttpsCertificates()
方法加到上述範例中。
將 Java 設為使用 Fiddler 做為 Proxy,並停用預設憑證驗證後,您就可以執行範例,並以純文字查看所有透過網路傳送的流量。別偷我的密碼!
請注意,這項驗證交易只是安全資料傳輸層 (SSL) 流量的一小部分範例。部分網路應用程式只使用 SSL 連線,因此如果無法解密資料,就無法偵錯 HTTP 流量。
結論
tcpdump 適用於 Linux、Mac OS X 和 Windows,如果您知道要尋找的內容,且只需要快速擷取,這就是絕佳工具。不過,有些圖形工具會以更容易理解的格式呈現網路流量。tcpdump 的選項和篩選功能比本文介紹的更多。如要完整瞭解 tcpdump 的功能,請輸入「man tcpdump」或在線上查看 tcpdump 手冊頁面。
WireShark 也適用於 Linux、Mac OS X 和 Windows。Wireshark 內建支援數百種通訊協定,因此不僅適用於 HTTP 偵錯,也可用於許多應用程式。這份簡介僅粗略介紹了 WireShark 的眾多功能。如需更多資訊,請輸入「man wireshark」或造訪 WireShark 網站。
Fiddler 也有許多實用功能,但最特別的是解密 SSL 流量的功能。詳情請造訪 Fiddler2 網站。
這些封包監聽應用程式是工具箱中不可或缺的工具,細心的讀者會發現這些應用程式都是免費的!下次使用 Google API 時,如果發現任何可疑之處,請拿出其中一個網路分析器,仔細檢查傳輸線上的內容。如果找不到問題,隨時可以在討論群組中提問。附上相關的網路訊息,有助於其他人瞭解及診斷您的特定問題。
祝你一切順利,盡情享受嗅聞的樂趣!