OSDN Git Service

TwitterApiConnection内で発生したHttpRequestExceptionをキャッチする
[opentween/open-tween.git] / OpenTween / Connection / TwitterApiConnection.cs
1 // OpenTween - Client of Twitter
2 // Copyright (c) 2016 kim_upsilon (@kim_upsilon) <https://upsilo.net/~upsilon/>
3 // All rights reserved.
4 //
5 // This file is part of OpenTween.
6 //
7 // This program is free software; you can redistribute it and/or modify it
8 // under the terms of the GNU General Public License as published by the Free
9 // Software Foundation; either version 3 of the License, or (at your option)
10 // any later version.
11 //
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
14 // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
15 // for more details.
16 //
17 // You should have received a copy of the GNU General Public License along
18 // with this program. If not, see <http://www.gnu.org/licenses/>, or write to
19 // the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
20 // Boston, MA 02110-1301, USA.
21
22 using System;
23 using System.Collections.Generic;
24 using System.IO;
25 using System.Linq;
26 using System.Net;
27 using System.Net.Cache;
28 using System.Net.Http;
29 using System.Runtime.Serialization;
30 using System.Text;
31 using System.Threading.Tasks;
32 using System.Web;
33 using OpenTween.Api;
34 using OpenTween.Api.DataModel;
35
36 namespace OpenTween.Connection
37 {
38     public class TwitterApiConnection : IApiConnection, IDisposable
39     {
40         public static Uri RestApiBase { get; set; } = new Uri("https://api.twitter.com/1.1/");
41
42         public bool IsDisposed { get; private set; } = false;
43
44         public string AccessToken { get; }
45         public string AccessSecret { get; }
46
47         internal HttpClient http;
48
49         public TwitterApiConnection(string accessToken, string accessSecret)
50         {
51             this.AccessToken = accessToken;
52             this.AccessSecret = accessSecret;
53
54             this.http = InitializeHttpClient(accessToken, accessSecret);
55             Networking.WebProxyChanged += this.Networking_WebProxyChanged;
56         }
57
58         public Task<T> GetAsync<T>(Uri uri, IDictionary<string, string> param)
59             => this.GetAsync<T>(uri, param, null);
60
61         public async Task<T> GetAsync<T>(Uri uri, IDictionary<string, string> param, string endpointName)
62         {
63             var requestUri = new Uri(RestApiBase, uri);
64
65             if (param != null)
66                 requestUri = new Uri(requestUri, "?" + MyCommon.BuildQueryString(param));
67
68             var request = new HttpRequestMessage(HttpMethod.Get, requestUri);
69
70             try
71             {
72                 using (var response = await this.http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead)
73                     .ConfigureAwait(false))
74                 {
75                     await this.CheckStatusCode(response)
76                         .ConfigureAwait(false);
77
78                     if (endpointName != null)
79                         MyCommon.TwitterApiInfo.UpdateFromHeader(response.Headers, endpointName);
80
81                     using (var content = response.Content)
82                     {
83                         var responseText = await content.ReadAsStringAsync()
84                             .ConfigureAwait(false);
85
86                         try
87                         {
88                             return MyCommon.CreateDataFromJson<T>(responseText);
89                         }
90                         catch (SerializationException ex)
91                         {
92                             throw TwitterApiException.CreateFromException(ex, responseText);
93                         }
94                     }
95                 }
96             }
97             catch (HttpRequestException ex)
98             {
99                 throw TwitterApiException.CreateFromException(ex);
100             }
101             catch (OperationCanceledException ex)
102             {
103                 throw TwitterApiException.CreateFromException(ex);
104             }
105         }
106
107         public async Task<Stream> GetStreamAsync(Uri uri, IDictionary<string, string> param)
108         {
109             var requestUri = new Uri(RestApiBase, uri);
110
111             if (param != null)
112                 requestUri = new Uri(requestUri, "?" + MyCommon.BuildQueryString(param));
113
114             try
115             {
116                 return await this.http.GetStreamAsync(requestUri)
117                     .ConfigureAwait(false);
118             }
119             catch (HttpRequestException ex)
120             {
121                 throw TwitterApiException.CreateFromException(ex);
122             }
123             catch (OperationCanceledException ex)
124             {
125                 throw TwitterApiException.CreateFromException(ex);
126             }
127         }
128
129         public async Task<LazyJson<T>> PostLazyAsync<T>(Uri uri, IDictionary<string, string> param)
130         {
131             var requestUri = new Uri(RestApiBase, uri);
132             var request = new HttpRequestMessage(HttpMethod.Post, requestUri);
133
134             using (var postContent = new FormUrlEncodedContent(param))
135             {
136                 request.Content = postContent;
137
138                 HttpResponseMessage response = null;
139                 try
140                 {
141                     response = await this.http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead)
142                         .ConfigureAwait(false);
143
144                     await this.CheckStatusCode(response)
145                         .ConfigureAwait(false);
146
147                     var result = new LazyJson<T>(response);
148                     response = null;
149
150                     return result;
151                 }
152                 catch (HttpRequestException ex)
153                 {
154                     throw TwitterApiException.CreateFromException(ex);
155                 }
156                 catch (OperationCanceledException ex)
157                 {
158                     throw TwitterApiException.CreateFromException(ex);
159                 }
160                 finally
161                 {
162                     response?.Dispose();
163                 }
164             }
165         }
166
167         public async Task<LazyJson<T>> PostLazyAsync<T>(Uri uri, IDictionary<string, string> param, IDictionary<string, IMediaItem> media)
168         {
169             var requestUri = new Uri(RestApiBase, uri);
170             var request = new HttpRequestMessage(HttpMethod.Post, requestUri);
171
172             using (var postContent = new MultipartFormDataContent())
173             {
174                 foreach (var kv in param)
175                     postContent.Add(new StringContent(kv.Value), kv.Key);
176
177                 foreach (var kv in media)
178                     postContent.Add(new StreamContent(kv.Value.OpenRead()), kv.Key, kv.Value.Name);
179
180                 request.Content = postContent;
181
182                 HttpResponseMessage response = null;
183                 try
184                 {
185                     response = await this.http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead)
186                         .ConfigureAwait(false);
187
188                     await this.CheckStatusCode(response)
189                         .ConfigureAwait(false);
190
191                     var result = new LazyJson<T>(response);
192                     response = null;
193
194                     return result;
195                 }
196                 catch (HttpRequestException ex)
197                 {
198                     throw TwitterApiException.CreateFromException(ex);
199                 }
200                 catch (OperationCanceledException ex)
201                 {
202                     throw TwitterApiException.CreateFromException(ex);
203                 }
204                 finally
205                 {
206                     response?.Dispose();
207                 }
208             }
209         }
210
211         protected async Task CheckStatusCode(HttpResponseMessage response)
212         {
213             var statusCode = response.StatusCode;
214             if (statusCode == HttpStatusCode.OK)
215             {
216                 Twitter.AccountState = MyCommon.ACCOUNT_STATE.Valid;
217                 return;
218             }
219
220             string responseText;
221             using (var content = response.Content)
222             {
223                 responseText = await content.ReadAsStringAsync()
224                     .ConfigureAwait(false);
225             }
226
227             if (string.IsNullOrWhiteSpace(responseText))
228             {
229                 if (statusCode == HttpStatusCode.Unauthorized)
230                     Twitter.AccountState = MyCommon.ACCOUNT_STATE.Invalid;
231
232                 throw new TwitterApiException(statusCode, responseText);
233             }
234
235             try
236             {
237                 var error = TwitterError.ParseJson(responseText);
238
239                 if (error?.Errors == null || error.Errors.Length == 0)
240                     throw new TwitterApiException(statusCode, responseText);
241
242                 var errorCodes = error.Errors.Select(x => x.Code);
243                 if (errorCodes.Any(x => x == TwitterErrorCode.InternalError || x == TwitterErrorCode.SuspendedAccount))
244                 {
245                     Twitter.AccountState = MyCommon.ACCOUNT_STATE.Invalid;
246                 }
247
248                 throw new TwitterApiException(error, responseText);
249             }
250             catch (SerializationException)
251             {
252                 throw new TwitterApiException(statusCode, responseText);
253             }
254         }
255
256         public OAuthEchoHandler CreateOAuthEchoHandler(Uri authServiceProvider, Uri realm = null)
257         {
258             var uri = new Uri(RestApiBase, authServiceProvider);
259
260             return OAuthEchoHandler.CreateHandler(Networking.CreateHttpClientHandler(), uri,
261                 ApplicationSettings.TwitterConsumerKey, ApplicationSettings.TwitterConsumerSecret,
262                 this.AccessToken, this.AccessSecret, realm);
263         }
264
265         public void Dispose()
266         {
267             this.Dispose(true);
268             GC.SuppressFinalize(this);
269         }
270
271         protected virtual void Dispose(bool disposing)
272         {
273             if (this.IsDisposed)
274                 return;
275
276             this.IsDisposed = true;
277
278             if (disposing)
279             {
280                 Networking.WebProxyChanged -= this.Networking_WebProxyChanged;
281                 this.http.Dispose();
282             }
283         }
284
285         ~TwitterApiConnection()
286         {
287             this.Dispose(false);
288         }
289
290         private void Networking_WebProxyChanged(object sender, EventArgs e)
291         {
292             this.http = InitializeHttpClient(this.AccessToken, this.AccessSecret);
293         }
294
295         public static async Task<Tuple<string, string>> GetRequestTokenAsync()
296         {
297             var param = new Dictionary<string, string>
298             {
299                 ["oauth_callback"] = "oob",
300             };
301             var response = await GetOAuthTokenAsync(new Uri("https://api.twitter.com/oauth/request_token"), param, oauthToken: null)
302                 .ConfigureAwait(false);
303
304             return Tuple.Create(response["oauth_token"], response["oauth_token_secret"]);
305         }
306
307         public static Uri GetAuthorizeUri(Tuple<string, string> requestToken, string screenName = null)
308         {
309             var param = new Dictionary<string, string>
310             {
311                 ["oauth_token"] = requestToken.Item1,
312             };
313
314             if (screenName != null)
315                 param["screen_name"] = screenName;
316
317             return new Uri("https://api.twitter.com/oauth/authorize?" + MyCommon.BuildQueryString(param));
318         }
319
320         public static async Task<IDictionary<string, string>> GetAccessTokenAsync(Tuple<string, string> requestToken, string verifier)
321         {
322             var param = new Dictionary<string, string>
323             {
324                 ["oauth_verifier"] = verifier,
325             };
326             var response = await GetOAuthTokenAsync(new Uri("https://api.twitter.com/oauth/access_token"), param, requestToken)
327                 .ConfigureAwait(false);
328
329             return response;
330         }
331
332         private static async Task<IDictionary<string, string>> GetOAuthTokenAsync(Uri uri, IDictionary<string, string> param,
333             Tuple<string, string> oauthToken)
334         {
335             HttpClient authorizeClient;
336             if (oauthToken != null)
337                 authorizeClient = InitializeHttpClient(oauthToken.Item1, oauthToken.Item2);
338             else
339                 authorizeClient = InitializeHttpClient("", "");
340
341             var requestUri = new Uri(uri, "?" + MyCommon.BuildQueryString(param));
342
343             try
344             {
345                 using (var request = new HttpRequestMessage(HttpMethod.Post, requestUri))
346                 using (var response = await authorizeClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead)
347                     .ConfigureAwait(false))
348                 using (var content = response.Content)
349                 {
350                     var responseText = await content.ReadAsStringAsync()
351                         .ConfigureAwait(false);
352
353                     if (!response.IsSuccessStatusCode)
354                         throw new TwitterApiException(response.StatusCode, responseText);
355
356                     var responseParams = HttpUtility.ParseQueryString(responseText);
357
358                     return responseParams.Cast<string>()
359                         .ToDictionary(x => x, x => responseParams[x]);
360                 }
361             }
362             catch (HttpRequestException ex)
363             {
364                 throw TwitterApiException.CreateFromException(ex);
365             }
366             catch (OperationCanceledException ex)
367             {
368                 throw TwitterApiException.CreateFromException(ex);
369             }
370         }
371
372         private static HttpClient InitializeHttpClient(string accessToken, string accessSecret)
373         {
374             var innerHandler = Networking.CreateHttpClientHandler();
375             innerHandler.CachePolicy = new RequestCachePolicy(RequestCacheLevel.BypassCache);
376
377             var handler = new OAuthHandler(innerHandler,
378                 ApplicationSettings.TwitterConsumerKey, ApplicationSettings.TwitterConsumerSecret,
379                 accessToken, accessSecret);
380
381             return Networking.CreateHttpClient(handler);
382         }
383     }
384 }