HTTP 仍是行動應用程式與後端服務溝通的主流協定。Windows 8 提供 System.Net.Http.HttpClient 作為現代化、非同步優先的 HTTP 用戶端 API,支援 GET/POST/PUT/DELETE、串流處理、取消機制與標頭控制。本文並示範如何整合 OAuth 2.0 流程,呼叫新浪微博開放平台 API 取得使用者好友動態。
簡單的 ASP.NET Web Form 根據 action 參數回應不同情境:
getString:回傳純文字getStream:回傳長字串模擬串流postString:回傳表單參數與 ReferrerpostStream:回傳請求本體內容並刻意 Thread.Sleep(3000) 模擬網路延遲,方便測試取消功能。
| 類別 | 關鍵成員 | 用途 |
|---|---|---|
| HttpClient | BaseAddress, DefaultRequestHeaders, Timeout, CancelPendingRequests(), GetAsync, PostAsync... | 發送請求、接收回應的主要進入點 |
| HttpRequestMessage | Method, RequestUri, Headers, Content | 封裝單一 HTTP 請求 |
| HttpResponseMessage | StatusCode, ReasonPhrase, Headers, Content, EnsureSuccessStatusCode() | 封裝 HTTP 回應 |
| HttpContent 衍生類別 | StringContent, ByteArrayContent, StreamContent, FormUrlEncodedContent | 請求/回應本體的不同表示形式 |
private async void btnPost_Click_1(object sender, RoutedEventArgs e) {
_httpClient = new HttpClient();
try {
string url = "https://localhost:39629/HttpDemo.aspx?action=postString";
var request = new HttpRequestMessage(HttpMethod.Post, url);
request.Content = new FormUrlEncodedContent(new[] {
new KeyValuePair("param1", "web"),
new KeyValuePair("param2", "abcd")
});
request.Headers.Referrer = new Uri("https://webabcd.cnblogs.com");
HttpResponseMessage response = await _httpClient.SendAsync(request);
lblMsg.Text += (int)response.StatusCode + " " + response.ReasonPhrase + Environment.NewLine;
lblMsg.Text += await response.Content.ReadAsStringAsync() + Environment.NewLine;
} catch (TaskCanceledException) { /* 取消處理 */ }
catch (Exception ex) { /* 錯誤處理 */ }
}
HttpResponseMessage response = await _httpClient.GetAsync(new Uri("...action=getString"));
lblMsg.Text += await response.Content.ReadAsStringAsync();
關鍵在於 HttpCompletionOption.ResponseHeadersRead,讓 GetAsync 在收到標頭即返回,隨後以 ReadAsStreamAsync() 逐塊讀取:
HttpResponseMessage response = await _httpClient.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead);
using (Stream responseStream = await response.Content.ReadAsStreamAsync()) {
byte[] buffer = new byte[32];
int read;
while ((read = await responseStream.ReadAsync(buffer, 0, buffer.Length)) > 0) {
// 處理 buffer,例如顯示十六進位
}
}
var postData = new FormUrlEncodedContent(new[] {
new KeyValuePair("param1", "web"),
new KeyValuePair("param2", "abcd")
});
HttpResponseMessage response = await _httpClient.PostAsync(uri, postData);
Stream stream = GenerateSampleStream(128); // 產生 128 bytes 測試資料
StreamContent streamContent = new StreamContent(stream);
HttpResponseMessage response = await _httpClient.PostAsync(uri, streamContent);
+--------+ +---------------+ | |--(A)- Authorization Request ->| Resource | | | | Owner | | |<-(B)-- Authorization Grant ---| | | | +---------------+ | | | | +---------------+ | |--(C)-- Authorization Grant ->| Authorization | | Client | | Server | | |<-(D)----- Access Token -------| | | | +---------------+ | | | | +---------------+ | |--(E)----- Access Token ------>| Resource | | | | Server | | |<-(F)--- Protected Resource ---| | +--------+ +---------------+
WebAuthenticationBroker.AuthenticateAsync 彈出系統級登入介面,無縫整合第三方 OAuth 頁面。
var requestUrl = string.Format("https://api.weibo.com/oauth2/authorize?client_id={0}&response_type=code&redirect_uri={1}", appKey, callbackUrl);
WebAuthenticationResult result = await WebAuthenticationBroker.AuthenticateAsync(
WebAuthenticationOptions.None, new Uri(requestUrl), new Uri(callbackUrl));
var authorizationCode = QueryString(result.ResponseData, "code");
var tokenUrl = string.Format("https://api.weibo.com/oauth2/access_token?client_id={0}&client_secret={1}&grant_type=authorization_code&redirect_uri={2}&code={3}", ...);
HttpClient client = new HttpClient();
var response = await client.PostAsync(new Uri(tokenUrl), null);
var json = await response.Content.ReadAsStringAsync();
var accessToken = JsonObject.Parse(json)["access_token"].GetString();
var apiUrl = string.Format("https://api.weibo.com/2/statuses/friends_timeline.json?access_token={0}", accessToken);
var timeline = await client.GetStringAsync(new Uri(apiUrl));
private string QueryString(string queryString, string key) {
return Regex.Match(queryString, string.Format(@"(?<=(\&|^|\?)({0})\=).*?(?=\&|$)", key), RegexOptions.IgnoreCase).Value;
}