OSDN Git Service

fe3000ec85f38420fa26db45f2f8d6df58bf0f43
[opentween/open-tween.git] / OpenTween / Connection / HttpConnection.cs
1 // OpenTween - Client of Twitter
2 // Copyright (c) 2007-2011 kiri_feather (@kiri_feather) <kiri.feather@gmail.com>
3 //           (c) 2008-2011 Moz (@syo68k)
4 //           (c) 2008-2011 takeshik (@takeshik) <http://www.takeshik.org/>
5 //           (c) 2010-2011 anis774 (@anis774) <http://d.hatena.ne.jp/anis774/>
6 //           (c) 2010-2011 fantasticswallow (@f_swallow) <http://twitter.com/f_swallow>
7 //           (c) 2011      kim_upsilon (@kim_upsilon) <https://upsilo.net/~upsilon/>
8 // All rights reserved.
9 // 
10 // This file is part of OpenTween.
11 // 
12 // This program is free software; you can redistribute it and/or modify it
13 // under the terms of the GNU General Public License as published by the Free
14 // Software Foundation; either version 3 of the License, or (at your option)
15 // any later version.
16 // 
17 // This program is distributed in the hope that it will be useful, but
18 // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
19 // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
20 // for more details. 
21 // 
22 // You should have received a copy of the GNU General Public License along
23 // with this program. If not, see <http://www.gnu.org/licenses/>, or write to
24 // the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
25 // Boston, MA 02110-1301, USA.
26
27 using System.Collections.Specialized;
28 using System.IO;
29 using System.Net;
30 using System.Net.Http;
31 using System.Text;
32 using System.Threading;
33 using System;
34 using System.Collections.Generic;
35 using System.IO.Compression;
36 using System.Drawing;
37
38 ///<summary>
39 ///HttpWebRequest,HttpWebResponseを使用した基本的な通信機能を提供する
40 ///</summary>
41 ///<remarks>
42 ///プロキシ情報などを設定するため、使用前に静的メソッドInitializeConnectionを呼び出すこと。
43 ///通信方式によって必要になるHTTPヘッダの付加などは、派生クラスで行う。
44 ///</remarks>
45 namespace OpenTween
46 {
47     public class HttpConnection
48     {
49         ///<summary>
50         ///プロキシ
51         ///</summary>
52         private static IWebProxy proxy = null;
53
54         ///<summary>
55         ///ユーザーが選択したプロキシの方式
56         ///</summary>
57         private static ProxyType proxyKind = ProxyType.IE;
58
59         ///<summary>
60         ///初期化済みフラグ
61         ///</summary>
62         private static bool isInitialize = false;
63
64         public enum ProxyType
65         {
66             None,
67             IE,
68             Specified,
69         }
70
71         /// <summary>
72         /// リクエスト間で Cookie を保持するか否か
73         /// </summary>
74         public bool UseCookie { get; set; }
75
76         /// <summary>
77         /// クッキー保存用コンテナ
78         /// </summary>
79         private CookieContainer cookieContainer = new CookieContainer();
80
81         protected const string PostMethod = "POST";
82         protected const string GetMethod = "GET";
83         protected const string HeadMethod = "HEAD";
84
85         ///<summary>
86         ///HttpWebRequestオブジェクトを取得する。パラメータはGET/HEAD/DELETEではクエリに、POST/PUTではエンティティボディに変換される。
87         ///</summary>
88         ///<remarks>
89         ///追加で必要となるHTTPヘッダや通信オプションは呼び出し元で付加すること
90         ///(Timeout,AutomaticDecompression,AllowAutoRedirect,UserAgent,ContentType,Accept,HttpRequestHeader.Authorization,カスタムヘッダ)
91         ///POST/PUTでクエリが必要な場合は、requestUriに含めること。
92         ///</remarks>
93         ///<param name="method">HTTP通信メソッド(GET/HEAD/POST/PUT/DELETE)</param>
94         ///<param name="requestUri">通信先URI</param>
95         ///<param name="param">GET時のクエリ、またはPOST時のエンティティボディ</param>
96         ///<returns>引数で指定された内容を反映したHttpWebRequestオブジェクト</returns>
97         protected HttpWebRequest CreateRequest(string method,
98                                                Uri requestUri,
99                                                Dictionary<string, string> param)
100         {
101             if (!isInitialize) throw new Exception("Sequence error.(not initialized)");
102
103             //GETメソッドの場合はクエリとurlを結合
104             UriBuilder ub = new UriBuilder(requestUri.AbsoluteUri);
105             if (param != null && (method == "GET" || method == "DELETE" || method == "HEAD"))
106             {
107                 ub.Query = MyCommon.BuildQueryString(param);
108             }
109
110             HttpWebRequest webReq = (HttpWebRequest)WebRequest.Create(ub.Uri);
111
112             webReq.ReadWriteTimeout = 90 * 1000; //Streamの読み込みは90秒でタイムアウト(デフォルト5分)
113
114             //プロキシ設定
115             if (proxyKind != ProxyType.IE) webReq.Proxy = proxy;
116
117             webReq.Method = method;
118             if (method == "POST" || method == "PUT")
119             {
120                 webReq.ContentType = "application/x-www-form-urlencoded";
121                 //POST/PUTメソッドの場合は、ボディデータとしてクエリ構成して書き込み
122                 using (StreamWriter writer = new StreamWriter(webReq.GetRequestStream()))
123                 {
124                     writer.Write(MyCommon.BuildQueryString(param));
125                 }
126             }
127             //cookie設定
128             if (this.UseCookie) webReq.CookieContainer = this.cookieContainer;
129             //タイムアウト設定
130             webReq.Timeout = this.InstanceTimeout ?? HttpConnection.DefaultTimeout;
131
132             webReq.UserAgent = MyCommon.GetUserAgentString();
133
134             return webReq;
135         }
136
137         ///<summary>
138         ///HttpWebRequestオブジェクトを取得する。multipartでのバイナリアップロード用。
139         ///</summary>
140         ///<remarks>
141         ///methodにはPOST/PUTのみ指定可能
142         ///</remarks>
143         ///<param name="method">HTTP通信メソッド(POST/PUT)</param>
144         ///<param name="requestUri">通信先URI</param>
145         ///<param name="param">form-dataで指定する名前と文字列のディクショナリ</param>
146         ///<param name="binaryFileInfo">form-dataで指定する名前とバイナリファイル情報のリスト</param>
147         ///<returns>引数で指定された内容を反映したHttpWebRequestオブジェクト</returns>
148         protected HttpWebRequest CreateRequest(string method,
149                                                Uri requestUri,
150                                                Dictionary<string, string> param,
151                                                List<KeyValuePair<String, FileInfo>> binaryFileInfo)
152         {
153             if (!isInitialize) throw new Exception("Sequence error.(not initialized)");
154
155             //methodはPOST,PUTのみ許可
156             UriBuilder ub = new UriBuilder(requestUri.AbsoluteUri);
157             if (method == "GET" || method == "DELETE" || method == "HEAD")
158                 throw new ArgumentException("Method must be POST or PUT");
159             if ((param == null || param.Count == 0) && (binaryFileInfo == null || binaryFileInfo.Count == 0))
160                 throw new ArgumentException("Data is empty");
161
162             HttpWebRequest webReq = (HttpWebRequest)WebRequest.Create(ub.Uri);
163
164             //プロキシ設定
165             if (proxyKind != ProxyType.IE) webReq.Proxy = proxy;
166
167             webReq.Method = method;
168             if (method == "POST" || method == "PUT")
169             {
170                 string boundary = System.Environment.TickCount.ToString();
171                 webReq.ContentType = "multipart/form-data; boundary=" + boundary;
172                 using (Stream reqStream = webReq.GetRequestStream())
173                 {
174                     //POST送信する文字データを作成
175                     if (param != null)
176                     {
177                         string postData = "";
178                         foreach (KeyValuePair<string, string> kvp in param)
179                         {
180                             postData += "--" + boundary + "\r\n" +
181                                     "Content-Disposition: form-data; name=\"" + kvp.Key + "\"" +
182                                     "\r\n\r\n" + kvp.Value + "\r\n";
183                         }
184                         byte[] postBytes = Encoding.UTF8.GetBytes(postData);
185                         reqStream.Write(postBytes, 0, postBytes.Length);
186                     }
187                     //POST送信するバイナリデータを作成
188                     if (binaryFileInfo != null)
189                     {
190                         foreach (KeyValuePair<string, FileInfo> kvp in binaryFileInfo)
191                         {
192                             string postData = "";
193                             byte[] crlfByte = Encoding.UTF8.GetBytes("\r\n");
194                             //コンテンツタイプの指定
195                             string mime = "";
196                             switch (kvp.Value.Extension.ToLower())
197                             {
198                                 case ".jpg":
199                                 case ".jpeg":
200                                 case ".jpe":
201                                     mime = "image/jpeg";
202                                     break;
203                                 case ".gif":
204                                     mime = "image/gif";
205                                     break;
206                                 case ".png":
207                                     mime = "image/png";
208                                     break;
209                                 case ".tiff":
210                                 case ".tif":
211                                     mime = "image/tiff";
212                                     break;
213                                 case ".bmp":
214                                     mime = "image/x-bmp";
215                                     break;
216                                 case ".avi":
217                                     mime = "video/avi";
218                                     break;
219                                 case ".wmv":
220                                     mime = "video/x-ms-wmv";
221                                     break;
222                                 case ".flv":
223                                     mime = "video/x-flv";
224                                     break;
225                                 case ".m4v":
226                                     mime = "video/x-m4v";
227                                     break;
228                                 case ".mov":
229                                     mime = "video/quicktime";
230                                     break;
231                                 case ".mp4":
232                                     mime = "video/3gpp";
233                                     break;
234                                 case ".rm":
235                                     mime = "application/vnd.rn-realmedia";
236                                     break;
237                                 case ".mpeg":
238                                 case ".mpg":
239                                     mime = "video/mpeg";
240                                     break;
241                                 case ".3gp":
242                                     mime = "movie/3gp";
243                                     break;
244                                 case ".3g2":
245                                     mime = "video/3gpp2";
246                                     break;
247                                 default:
248                                     mime = "application/octet-stream\r\nContent-Transfer-Encoding: binary";
249                                     break;
250                             }
251                             postData = "--" + boundary + "\r\n" +
252                                 "Content-Disposition: form-data; name=\"" + kvp.Key + "\"; filename=\"" +
253                                 kvp.Value.Name + "\"\r\n" +
254                                 "Content-Type: " + mime + "\r\n\r\n";
255                             byte[] postBytes = Encoding.UTF8.GetBytes(postData);
256                             reqStream.Write(postBytes, 0, postBytes.Length);
257                             //ファイルを読み出してHTTPのストリームに書き込み
258                             using (FileStream fs = new FileStream(kvp.Value.FullName, FileMode.Open, FileAccess.Read))
259                             {
260                                 int readSize = 0;
261                                 byte[] readBytes = new byte[0x1000];
262                                 while (true)
263                                 {
264                                     readSize = fs.Read(readBytes, 0, readBytes.Length);
265                                     if (readSize == 0) break;
266                                     reqStream.Write(readBytes, 0, readSize);
267                                 }
268                             }
269                             reqStream.Write(crlfByte, 0, crlfByte.Length);
270                         }
271                     }
272                     //終端
273                     byte[] endBytes = Encoding.UTF8.GetBytes("--" + boundary + "--\r\n");
274                     reqStream.Write(endBytes, 0, endBytes.Length);
275                 }
276             }
277             //cookie設定
278             if (this.UseCookie) webReq.CookieContainer = this.cookieContainer;
279             //タイムアウト設定
280             webReq.Timeout = this.InstanceTimeout ?? HttpConnection.DefaultTimeout;
281
282             return webReq;
283         }
284
285         ///<summary>
286         ///HTTPの応答を処理し、引数で指定されたストリームに書き込み
287         ///</summary>
288         ///<remarks>
289         ///リダイレクト応答の場合(AllowAutoRedirect=Falseの場合のみ)は、headerInfoインスタンスがあればLocationを追加してリダイレクト先を返却
290         ///WebExceptionはハンドルしていないので、呼び出し元でキャッチすること
291         ///gzipファイルのダウンロードを想定しているため、他形式の場合は伸張時に問題が発生する可能性があります。
292         ///</remarks>
293         ///<param name="webRequest">HTTP通信リクエストオブジェクト</param>
294         ///<param name="contentStream">[OUT]HTTP応答のボディストリームのコピー先</param>
295         ///<param name="headerInfo">[IN/OUT]HTTP応答のヘッダ情報。ヘッダ名をキーにして空データのコレクションを渡すことで、該当ヘッダの値をデータに設定して戻す</param>
296         ///<returns>HTTP応答のステータスコード</returns>
297         protected HttpStatusCode GetResponse(HttpWebRequest webRequest,
298                                              Stream contentStream,
299                                              Dictionary<string, string> headerInfo)
300         {
301             try
302             {
303                 using (HttpWebResponse webRes = (HttpWebResponse)webRequest.GetResponse())
304                 {
305                     HttpStatusCode statusCode = webRes.StatusCode;
306                     //cookie保持
307                     if (this.UseCookie) this.FixCookies(webRes.Cookies);
308                     //リダイレクト応答の場合は、リダイレクト先を設定
309                     GetHeaderInfo(webRes, headerInfo);
310                     //応答のストリームをコピーして戻す
311                     if (webRes.ContentLength > 0)
312                     {
313                         //gzipなら応答ストリームの内容は伸張済み。それ以外なら伸張する。
314                         if (webRes.ContentEncoding == "gzip" || webRes.ContentEncoding == "deflate")
315                         {
316                             using (Stream stream = webRes.GetResponseStream())
317                             {
318                                 if (stream != null) stream.CopyTo(contentStream);
319                             }
320                         }
321                         else
322                         {
323                             using (Stream stream = new GZipStream(webRes.GetResponseStream(), CompressionMode.Decompress))
324                             {
325                                 if (stream != null) stream.CopyTo(contentStream);
326                             }
327                         }
328                     }
329                     return statusCode;
330                 }
331             }
332             catch (WebException ex)
333             {
334                 if (ex.Status == WebExceptionStatus.ProtocolError)
335                 {
336                     HttpWebResponse res = (HttpWebResponse)ex.Response;
337                     GetHeaderInfo(res, headerInfo);
338                     return res.StatusCode;
339                 }
340                 throw;
341             }
342         }
343
344         ///<summary>
345         ///HTTPの応答を処理し、応答ボディデータをテキストとして返却する
346         ///</summary>
347         ///<remarks>
348         ///リダイレクト応答の場合(AllowAutoRedirect=Falseの場合のみ)は、headerInfoインスタンスがあればLocationを追加してリダイレクト先を返却
349         ///WebExceptionはハンドルしていないので、呼び出し元でキャッチすること
350         ///テキストの文字コードはUTF-8を前提として、エンコードはしていません
351         ///</remarks>
352         ///<param name="webRequest">HTTP通信リクエストオブジェクト</param>
353         ///<param name="contentText">[OUT]HTTP応答のボディデータ</param>
354         ///<param name="headerInfo">[IN/OUT]HTTP応答のヘッダ情報。ヘッダ名をキーにして空データのコレクションを渡すことで、該当ヘッダの値をデータに設定して戻す</param>
355         ///<returns>HTTP応答のステータスコード</returns>
356         protected HttpStatusCode GetResponse(HttpWebRequest webRequest,
357                                              out string contentText,
358                                              Dictionary<string, string> headerInfo)
359         {
360             try
361             {
362                 using (HttpWebResponse webRes = (HttpWebResponse)webRequest.GetResponse())
363                 {
364                     HttpStatusCode statusCode = webRes.StatusCode;
365                     //cookie保持
366                     if (this.UseCookie) this.FixCookies(webRes.Cookies);
367                     //リダイレクト応答の場合は、リダイレクト先を設定
368                     GetHeaderInfo(webRes, headerInfo);
369                     //応答のストリームをテキストに書き出し
370                     using (StreamReader sr = new StreamReader(webRes.GetResponseStream()))
371                     {
372                         contentText = sr.ReadToEnd();
373                     }
374                     return statusCode;
375                 }
376             }
377             catch (WebException ex)
378             {
379                 if (ex.Status == WebExceptionStatus.ProtocolError)
380                 {
381                     HttpWebResponse res = (HttpWebResponse)ex.Response;
382                     GetHeaderInfo(res, headerInfo);
383                     using (StreamReader sr = new StreamReader(res.GetResponseStream()))
384                     {
385                         contentText = sr.ReadToEnd();
386                     }
387                     return res.StatusCode;
388                 }
389                 throw;
390             }
391         }
392
393         ///<summary>
394         ///HTTPの応答を処理します。応答ボディデータが不要な用途向け。
395         ///</summary>
396         ///<remarks>
397         ///リダイレクト応答の場合(AllowAutoRedirect=Falseの場合のみ)は、headerInfoインスタンスがあればLocationを追加してリダイレクト先を返却
398         ///WebExceptionはハンドルしていないので、呼び出し元でキャッチすること
399         ///</remarks>
400         ///<param name="webRequest">HTTP通信リクエストオブジェクト</param>
401         ///<param name="headerInfo">[IN/OUT]HTTP応答のヘッダ情報。ヘッダ名をキーにして空データのコレクションを渡すことで、該当ヘッダの値をデータに設定して戻す</param>
402         ///<returns>HTTP応答のステータスコード</returns>
403         protected HttpStatusCode GetResponse(HttpWebRequest webRequest,
404                                              Dictionary<string, string> headerInfo)
405         {
406             try
407             {
408                 using (HttpWebResponse webRes = (HttpWebResponse)webRequest.GetResponse())
409                 {
410                     HttpStatusCode statusCode = webRes.StatusCode;
411                     //cookie保持
412                     if (this.UseCookie) this.FixCookies(webRes.Cookies);
413                     //リダイレクト応答の場合は、リダイレクト先を設定
414                     GetHeaderInfo(webRes, headerInfo);
415                     return statusCode;
416                 }
417             }
418             catch (WebException ex)
419             {
420                 if (ex.Status == WebExceptionStatus.ProtocolError)
421                 {
422                     HttpWebResponse res = (HttpWebResponse)ex.Response;
423                     GetHeaderInfo(res, headerInfo);
424                     return res.StatusCode;
425                 }
426                 throw;
427             }
428         }
429
430         ///<summary>
431         ///HTTPの応答を処理し、応答ボディデータをBitmapとして返却します
432         ///</summary>
433         ///<remarks>
434         ///リダイレクト応答の場合(AllowAutoRedirect=Falseの場合のみ)は、headerInfoインスタンスがあればLocationを追加してリダイレクト先を返却
435         ///WebExceptionはハンドルしていないので、呼び出し元でキャッチすること
436         ///</remarks>
437         ///<param name="webRequest">HTTP通信リクエストオブジェクト</param>
438         ///<param name="contentBitmap">[OUT]HTTP応答のボディデータを書き込むBitmap</param>
439         ///<param name="headerInfo">[IN/OUT]HTTP応答のヘッダ情報。ヘッダ名をキーにして空データのコレクションを渡すことで、該当ヘッダの値をデータに設定して戻す</param>
440         ///<returns>HTTP応答のステータスコード</returns>
441         protected HttpStatusCode GetResponse(HttpWebRequest webRequest,
442                                              out Bitmap contentBitmap,
443                                              Dictionary<string, string> headerInfo)
444         {
445             try
446             {
447                 using (HttpWebResponse webRes = (HttpWebResponse)webRequest.GetResponse())
448                 {
449                     HttpStatusCode statusCode = webRes.StatusCode;
450                     //cookie保持
451                     if (this.UseCookie) this.FixCookies(webRes.Cookies);
452                     //リダイレクト応答の場合は、リダイレクト先を設定
453                     GetHeaderInfo(webRes, headerInfo);
454                     //応答のストリームをBitmapにして戻す
455                     //if (webRes.ContentLength > 0) contentBitmap = new Bitmap(webRes.GetResponseStream());
456                     contentBitmap = new Bitmap(webRes.GetResponseStream());
457                     return statusCode;
458                 }
459             }
460             catch (WebException ex)
461             {
462                 if (ex.Status == WebExceptionStatus.ProtocolError)
463                 {
464                     HttpWebResponse res = (HttpWebResponse)ex.Response;
465                     GetHeaderInfo(res, headerInfo);
466                     contentBitmap = null;
467                     return res.StatusCode;
468                 }
469                 throw;
470             }
471         }
472
473         /// <summary>
474         /// ホスト名なしのドメインはドメイン名から先頭のドットを除去しないと再利用されないため修正して追加する
475         /// </summary>
476         private void FixCookies(CookieCollection cookieCollection)
477         {
478             foreach (Cookie ck in cookieCollection)
479             {
480                 if (ck.Domain.StartsWith("."))
481                 {
482                     ck.Domain = ck.Domain.Substring(1);
483                     cookieContainer.Add(ck);
484                 }
485             }
486         }
487
488         ///<summary>
489         ///headerInfoのキー情報で指定されたHTTPヘッダ情報を取得・格納する。redirect応答時はLocationヘッダの内容を追記する
490         ///</summary>
491         ///<param name="webResponse">HTTP応答</param>
492         ///<param name="headerInfo">[IN/OUT]キーにヘッダ名を指定したデータ空のコレクション。取得した値をデータにセットして戻す</param>
493         private void GetHeaderInfo(HttpWebResponse webResponse,
494                                    Dictionary<string, string> headerInfo)
495         {
496             if (headerInfo == null) return;
497
498             if (headerInfo.Count > 0)
499             {
500                 var headers = webResponse.Headers;
501                 var dictKeys = new string[headerInfo.Count];
502                 headerInfo.Keys.CopyTo(dictKeys, 0);
503
504                 foreach (var key in dictKeys)
505                 {
506                     var value = headers[key];
507                     headerInfo[key] = value ?? "";
508                 }
509             }
510
511             HttpStatusCode statusCode = webResponse.StatusCode;
512             if (statusCode == HttpStatusCode.MovedPermanently ||
513                 statusCode == HttpStatusCode.Found ||
514                 statusCode == HttpStatusCode.SeeOther ||
515                 statusCode == HttpStatusCode.TemporaryRedirect)
516             {
517                 if (webResponse.Headers["Location"] != null)
518                 {
519                     headerInfo["Location"] = webResponse.Headers["Location"];
520                 }
521             }
522         }
523
524         ///<summary>
525         ///クエリ形式(key1=value1&key2=value2&...)の文字列をkey-valueコレクションに詰め直し
526         ///</summary>
527         ///<param name="queryString">クエリ文字列</param>
528         ///<returns>key-valueのコレクション</returns>
529         protected NameValueCollection ParseQueryString(string queryString)
530         {
531             NameValueCollection query = new NameValueCollection();
532             string[] parts = queryString.Split('&');
533             foreach (string part in parts)
534             {
535                 int index = part.IndexOf('=');
536                 if (index == -1)
537                     query.Add(Uri.UnescapeDataString(part), "");
538                 else
539                     query.Add(Uri.UnescapeDataString(part.Substring(0, index)), Uri.UnescapeDataString(part.Substring(index + 1)));
540             }
541             return query;
542         }
543
544         ///<summary>
545         ///2バイト文字も考慮したUrlエンコード
546         ///</summary>
547         ///<param name="stringToEncode">エンコードする文字列</param>
548         ///<returns>エンコード結果文字列</returns>
549         protected string UrlEncode(string stringToEncode)
550         {
551             const string UnreservedChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~";
552             StringBuilder sb = new StringBuilder();
553             byte[] bytes = Encoding.UTF8.GetBytes(stringToEncode);
554
555             foreach (byte b in bytes)
556             {
557                 if (UnreservedChars.IndexOf((char)b) != -1)
558                     sb.Append((char)b);
559                 else
560                     sb.AppendFormat("%{0:X2}", b);
561             }
562             return sb.ToString();
563         }
564
565         #region "InstanceTimeout"
566         ///<summary>
567         ///通信タイムアウト時間(ms)
568         ///</summary>
569         private int? _timeout = null;
570
571         ///<summary>
572         ///通信タイムアウト時間(ms)。10~120秒の範囲で指定。範囲外は20秒とする
573         ///</summary>
574         protected int? InstanceTimeout
575         {
576             get { return _timeout; }
577             set
578             {
579                 const int TimeoutMinValue = 10000;
580                 const int TimeoutMaxValue = 120000;
581                 if (value < TimeoutMinValue || value > TimeoutMaxValue)
582                     throw new ArgumentOutOfRangeException("Set " + TimeoutMinValue + "-" + TimeoutMaxValue + ": Value=" + value);
583                 else
584                     _timeout = value;
585             }
586         }
587         #endregion
588
589         #region "DefaultTimeout"
590         ///<summary>
591         ///通信タイムアウト時間(ms)
592         ///</summary>
593         private static int timeout = 20000;
594
595         ///<summary>
596         ///通信タイムアウト時間(ms)。10~120秒の範囲で指定。範囲外は20秒とする
597         ///</summary>
598         protected static int DefaultTimeout
599         {
600             get { return timeout; }
601             set
602             {
603                 const int TimeoutMinValue = 10000;
604                 const int TimeoutMaxValue = 120000;
605                 const int TimeoutDefaultValue = 20000;
606                 if (value < TimeoutMinValue || value > TimeoutMaxValue)
607                     // 範囲外ならデフォルト値設定
608                     timeout = TimeoutDefaultValue;
609                 else
610                     timeout = value;
611             }
612         }
613         #endregion
614
615         /// <summary>
616         /// OpenTween 内で共通して使用する HttpClient インスタンス
617         /// </summary>
618         public static HttpClient GlobalHttpClient
619         {
620             get { return globalHttpClient; }
621         }
622
623         /// <summary>
624         /// Webプロキシの設定が変更された場合に発生します
625         /// </summary>
626         public static event EventHandler WebProxyChanged;
627
628         private static HttpClient globalHttpClient = CreateHttpClient(new HttpClientHandler());
629
630         ///<summary>
631         ///通信クラスの初期化処理。タイムアウト値とプロキシを設定する
632         ///</summary>
633         ///<remarks>
634         ///通信開始前に最低一度呼び出すこと
635         ///</remarks>
636         ///<param name="timeout">タイムアウト値(秒)</param>
637         ///<param name="proxyType">なし・指定・IEデフォルト</param>
638         ///<param name="proxyAddress">プロキシのホスト名orIPアドレス</param>
639         ///<param name="proxyPort">プロキシのポート番号</param>
640         ///<param name="proxyUser">プロキシ認証が必要な場合のユーザ名。不要なら空文字</param>
641         ///<param name="proxyPassword">プロキシ認証が必要な場合のパスワード。不要なら空文字</param>
642         public static void InitializeConnection(int timeout,
643             ProxyType proxyType, string proxyAddress, int proxyPort,
644             string proxyUser, string proxyPassword)
645         {
646             HttpConnection.isInitialize = true;
647             HttpConnection.DefaultTimeout = timeout * 1000; // s -> ms
648
649             ServicePointManager.Expect100Continue = false;
650
651             SetWebProxy(proxyType, proxyAddress, proxyPort, proxyUser, proxyPassword);
652         }
653
654         public static void SetWebProxy(ProxyType proxyType, string proxyAddress, int proxyPort,
655             string proxyUser, string proxyPassword)
656         {
657             IWebProxy proxy;
658             switch (proxyType)
659             {
660                 case ProxyType.None:
661                     proxy = null;
662                     break;
663                 case ProxyType.Specified:
664                     proxy = new WebProxy(proxyAddress, proxyPort);
665                     if (!string.IsNullOrEmpty(proxyUser) || !string.IsNullOrEmpty(proxyPassword))
666                         proxy.Credentials = new NetworkCredential(proxyUser, proxyPassword);
667                     break;
668                 case ProxyType.IE:
669                 default:
670                     proxy = WebRequest.GetSystemWebProxy();
671                     break;
672             }
673
674             HttpConnection.proxyKind = proxyType;
675             HttpConnection.proxy = proxy;
676
677             Win32Api.SetProxy(proxyType, proxyAddress, proxyPort, proxyUser, proxyPassword);
678
679             OnWebProxyChanged(EventArgs.Empty);
680         }
681
682         /// <summary>
683         /// プロキシ等の設定を施した HttpClient インスタンスを生成します
684         /// </summary>
685         /// <remarks>
686         /// 通常は HttpConnection.GlobalHttpClient を使用すべきです。
687         /// このメソッドを使用する場合は、WebProxyChanged イベントが発生する度に HttpClient を生成し直すように実装してください。
688         /// </remarks>
689         public static HttpClient CreateHttpClient(HttpClientHandler handler)
690         {
691             if (HttpConnection.proxy != null)
692             {
693                 handler.UseProxy = true;
694                 handler.Proxy = HttpConnection.proxy;
695             }
696             else
697             {
698                 handler.UseProxy = false;
699             }
700
701             var client = new HttpClient(handler);
702             client.Timeout = TimeSpan.FromMilliseconds(HttpConnection.DefaultTimeout);
703             client.DefaultRequestHeaders.Add("User-Agent", MyCommon.GetUserAgentString());
704
705             return client;
706         }
707
708         private static void OnWebProxyChanged(EventArgs e)
709         {
710             var newClient = HttpConnection.CreateHttpClient(new HttpClientHandler());
711             var oldClient = Interlocked.Exchange(ref globalHttpClient, newClient);
712             oldClient.Dispose();
713
714             if (WebProxyChanged != null)
715                 WebProxyChanged(null, e);
716         }
717     }
718 }