OSDN Git Service

191d5e5149113c1fb2db30bec53e2664ca9aeb66
[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 using OpenTween.Connection;
38
39 ///<summary>
40 ///HttpWebRequest,HttpWebResponseを使用した基本的な通信機能を提供する
41 ///</summary>
42 ///<remarks>
43 ///プロキシ情報などを設定するため、使用前に静的メソッドInitializeConnectionを呼び出すこと。
44 ///通信方式によって必要になるHTTPヘッダの付加などは、派生クラスで行う。
45 ///</remarks>
46 namespace OpenTween
47 {
48     public class HttpConnection
49     {
50         /// <summary>
51         /// リクエスト間で Cookie を保持するか否か
52         /// </summary>
53         public bool UseCookie { get; set; }
54
55         /// <summary>
56         /// クッキー保存用コンテナ
57         /// </summary>
58         private CookieContainer cookieContainer = new CookieContainer();
59
60         protected const string PostMethod = "POST";
61         protected const string GetMethod = "GET";
62         protected const string HeadMethod = "HEAD";
63
64         ///<summary>
65         ///HttpWebRequestオブジェクトを取得する。パラメータはGET/HEAD/DELETEではクエリに、POST/PUTではエンティティボディに変換される。
66         ///</summary>
67         ///<remarks>
68         ///追加で必要となるHTTPヘッダや通信オプションは呼び出し元で付加すること
69         ///(Timeout,AutomaticDecompression,AllowAutoRedirect,UserAgent,ContentType,Accept,HttpRequestHeader.Authorization,カスタムヘッダ)
70         ///POST/PUTでクエリが必要な場合は、requestUriに含めること。
71         ///</remarks>
72         ///<param name="method">HTTP通信メソッド(GET/HEAD/POST/PUT/DELETE)</param>
73         ///<param name="requestUri">通信先URI</param>
74         ///<param name="param">GET時のクエリ、またはPOST時のエンティティボディ</param>
75         ///<param name="gzip">Accept-Encodingヘッダにgzipを付加するかどうかを表す真偽値</param>
76         ///<returns>引数で指定された内容を反映したHttpWebRequestオブジェクト</returns>
77         protected HttpWebRequest CreateRequest(string method,
78                                                Uri requestUri,
79                                                Dictionary<string, string> param,
80                                                bool gzip = false)
81         {
82             Networking.CheckInitialized();
83
84             //GETメソッドの場合はクエリとurlを結合
85             UriBuilder ub = new UriBuilder(requestUri.AbsoluteUri);
86             if (param != null && (method == "GET" || method == "DELETE" || method == "HEAD"))
87             {
88                 ub.Query = MyCommon.BuildQueryString(param);
89             }
90
91             HttpWebRequest webReq = (HttpWebRequest)WebRequest.Create(ub.Uri);
92
93             webReq.ReadWriteTimeout = 90 * 1000; //Streamの読み込みは90秒でタイムアウト(デフォルト5分)
94
95             //プロキシ設定
96             if (Networking.ProxyType != ProxyType.IE) webReq.Proxy = Networking.Proxy;
97
98             if (gzip)
99             {
100                 // Accept-Encodingヘッダを付加
101                 webReq.AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip;
102             }
103
104             webReq.Method = method;
105             if (method == "POST" || method == "PUT")
106             {
107                 webReq.ContentType = "application/x-www-form-urlencoded";
108                 //POST/PUTメソッドの場合は、ボディデータとしてクエリ構成して書き込み
109                 using (StreamWriter writer = new StreamWriter(webReq.GetRequestStream()))
110                 {
111                     writer.Write(MyCommon.BuildQueryString(param));
112                 }
113             }
114             //cookie設定
115             if (this.UseCookie) webReq.CookieContainer = this.cookieContainer;
116             //タイムアウト設定
117             webReq.Timeout = this.InstanceTimeout ?? (int)Networking.DefaultTimeout.TotalMilliseconds;
118
119             webReq.UserAgent = Networking.GetUserAgentString();
120
121             // KeepAlive無効なサーバー(Twitter等)に使用すると、タイムアウト後にWebExceptionが発生する場合あり
122             webReq.KeepAlive = false;
123
124             return webReq;
125         }
126
127         ///<summary>
128         ///HttpWebRequestオブジェクトを取得する。multipartでのバイナリアップロード用。
129         ///</summary>
130         ///<remarks>
131         ///methodにはPOST/PUTのみ指定可能
132         ///</remarks>
133         ///<param name="method">HTTP通信メソッド(POST/PUT)</param>
134         ///<param name="requestUri">通信先URI</param>
135         ///<param name="param">form-dataで指定する名前と文字列のディクショナリ</param>
136         ///<param name="binaryFileInfo">form-dataで指定する名前とバイナリファイル情報のリスト</param>
137         ///<returns>引数で指定された内容を反映したHttpWebRequestオブジェクト</returns>
138         protected HttpWebRequest CreateRequest(string method,
139                                                Uri requestUri,
140                                                Dictionary<string, string> param,
141                                                List<KeyValuePair<String, FileInfo>> binaryFileInfo)
142         {
143             Networking.CheckInitialized();
144
145             //methodはPOST,PUTのみ許可
146             UriBuilder ub = new UriBuilder(requestUri.AbsoluteUri);
147             if (method == "GET" || method == "DELETE" || method == "HEAD")
148                 throw new ArgumentException("Method must be POST or PUT");
149             if ((param == null || param.Count == 0) && (binaryFileInfo == null || binaryFileInfo.Count == 0))
150                 throw new ArgumentException("Data is empty");
151
152             HttpWebRequest webReq = (HttpWebRequest)WebRequest.Create(ub.Uri);
153
154             //プロキシ設定
155             if (Networking.ProxyType != ProxyType.IE) webReq.Proxy = Networking.Proxy;
156
157             webReq.Method = method;
158             if (method == "POST" || method == "PUT")
159             {
160                 string boundary = System.Environment.TickCount.ToString();
161                 webReq.ContentType = "multipart/form-data; boundary=" + boundary;
162                 using (Stream reqStream = webReq.GetRequestStream())
163                 {
164                     //POST送信する文字データを作成
165                     if (param != null)
166                     {
167                         string postData = "";
168                         foreach (KeyValuePair<string, string> kvp in param)
169                         {
170                             postData += "--" + boundary + "\r\n" +
171                                     "Content-Disposition: form-data; name=\"" + kvp.Key + "\"" +
172                                     "\r\n\r\n" + kvp.Value + "\r\n";
173                         }
174                         byte[] postBytes = Encoding.UTF8.GetBytes(postData);
175                         reqStream.Write(postBytes, 0, postBytes.Length);
176                     }
177                     //POST送信するバイナリデータを作成
178                     if (binaryFileInfo != null)
179                     {
180                         foreach (KeyValuePair<string, FileInfo> kvp in binaryFileInfo)
181                         {
182                             string postData = "";
183                             byte[] crlfByte = Encoding.UTF8.GetBytes("\r\n");
184                             //コンテンツタイプの指定
185                             string mime = "";
186                             switch (kvp.Value.Extension.ToLower())
187                             {
188                                 case ".jpg":
189                                 case ".jpeg":
190                                 case ".jpe":
191                                     mime = "image/jpeg";
192                                     break;
193                                 case ".gif":
194                                     mime = "image/gif";
195                                     break;
196                                 case ".png":
197                                     mime = "image/png";
198                                     break;
199                                 case ".tiff":
200                                 case ".tif":
201                                     mime = "image/tiff";
202                                     break;
203                                 case ".bmp":
204                                     mime = "image/x-bmp";
205                                     break;
206                                 case ".avi":
207                                     mime = "video/avi";
208                                     break;
209                                 case ".wmv":
210                                     mime = "video/x-ms-wmv";
211                                     break;
212                                 case ".flv":
213                                     mime = "video/x-flv";
214                                     break;
215                                 case ".m4v":
216                                     mime = "video/x-m4v";
217                                     break;
218                                 case ".mov":
219                                     mime = "video/quicktime";
220                                     break;
221                                 case ".mp4":
222                                     mime = "video/3gpp";
223                                     break;
224                                 case ".rm":
225                                     mime = "application/vnd.rn-realmedia";
226                                     break;
227                                 case ".mpeg":
228                                 case ".mpg":
229                                     mime = "video/mpeg";
230                                     break;
231                                 case ".3gp":
232                                     mime = "movie/3gp";
233                                     break;
234                                 case ".3g2":
235                                     mime = "video/3gpp2";
236                                     break;
237                                 default:
238                                     mime = "application/octet-stream\r\nContent-Transfer-Encoding: binary";
239                                     break;
240                             }
241                             postData = "--" + boundary + "\r\n" +
242                                 "Content-Disposition: form-data; name=\"" + kvp.Key + "\"; filename=\"" +
243                                 kvp.Value.Name + "\"\r\n" +
244                                 "Content-Type: " + mime + "\r\n\r\n";
245                             byte[] postBytes = Encoding.UTF8.GetBytes(postData);
246                             reqStream.Write(postBytes, 0, postBytes.Length);
247                             //ファイルを読み出してHTTPのストリームに書き込み
248                             using (FileStream fs = new FileStream(kvp.Value.FullName, FileMode.Open, FileAccess.Read))
249                             {
250                                 int readSize = 0;
251                                 byte[] readBytes = new byte[0x1000];
252                                 while (true)
253                                 {
254                                     readSize = fs.Read(readBytes, 0, readBytes.Length);
255                                     if (readSize == 0) break;
256                                     reqStream.Write(readBytes, 0, readSize);
257                                 }
258                             }
259                             reqStream.Write(crlfByte, 0, crlfByte.Length);
260                         }
261                     }
262                     //終端
263                     byte[] endBytes = Encoding.UTF8.GetBytes("--" + boundary + "--\r\n");
264                     reqStream.Write(endBytes, 0, endBytes.Length);
265                 }
266             }
267             //cookie設定
268             if (this.UseCookie) webReq.CookieContainer = this.cookieContainer;
269             //タイムアウト設定
270             webReq.Timeout = this.InstanceTimeout ?? (int)Networking.DefaultTimeout.TotalMilliseconds;
271
272             // KeepAlive無効なサーバー(Twitter等)に使用すると、タイムアウト後にWebExceptionが発生する場合あり
273             webReq.KeepAlive = false;
274
275             return webReq;
276         }
277
278         ///<summary>
279         ///HTTPの応答を処理し、引数で指定されたストリームに書き込み
280         ///</summary>
281         ///<remarks>
282         ///リダイレクト応答の場合(AllowAutoRedirect=Falseの場合のみ)は、headerInfoインスタンスがあればLocationを追加してリダイレクト先を返却
283         ///WebExceptionはハンドルしていないので、呼び出し元でキャッチすること
284         ///gzipファイルのダウンロードを想定しているため、他形式の場合は伸張時に問題が発生する可能性があります。
285         ///</remarks>
286         ///<param name="webRequest">HTTP通信リクエストオブジェクト</param>
287         ///<param name="contentStream">[OUT]HTTP応答のボディストリームのコピー先</param>
288         ///<param name="headerInfo">[IN/OUT]HTTP応答のヘッダ情報。ヘッダ名をキーにして空データのコレクションを渡すことで、該当ヘッダの値をデータに設定して戻す</param>
289         ///<returns>HTTP応答のステータスコード</returns>
290         protected HttpStatusCode GetResponse(HttpWebRequest webRequest,
291                                              Stream contentStream,
292                                              Dictionary<string, string> headerInfo)
293         {
294             try
295             {
296                 using (HttpWebResponse webRes = (HttpWebResponse)webRequest.GetResponse())
297                 {
298                     HttpStatusCode statusCode = webRes.StatusCode;
299                     //cookie保持
300                     if (this.UseCookie) this.FixCookies(webRes.Cookies);
301                     //リダイレクト応答の場合は、リダイレクト先を設定
302                     GetHeaderInfo(webRes, headerInfo);
303                     //応答のストリームをコピーして戻す
304                     if (webRes.ContentLength > 0)
305                     {
306                         //gzipなら応答ストリームの内容は伸張済み。それ以外なら伸張する。
307                         if (webRes.ContentEncoding == "gzip" || webRes.ContentEncoding == "deflate")
308                         {
309                             using (Stream stream = webRes.GetResponseStream())
310                             {
311                                 if (stream != null) stream.CopyTo(contentStream);
312                             }
313                         }
314                         else
315                         {
316                             using (Stream stream = new GZipStream(webRes.GetResponseStream(), CompressionMode.Decompress))
317                             {
318                                 if (stream != null) stream.CopyTo(contentStream);
319                             }
320                         }
321                     }
322                     return statusCode;
323                 }
324             }
325             catch (WebException ex)
326             {
327                 if (ex.Status == WebExceptionStatus.ProtocolError)
328                 {
329                     HttpWebResponse res = (HttpWebResponse)ex.Response;
330                     GetHeaderInfo(res, headerInfo);
331                     return res.StatusCode;
332                 }
333                 throw;
334             }
335         }
336
337         ///<summary>
338         ///HTTPの応答を処理し、応答ボディデータをテキストとして返却する
339         ///</summary>
340         ///<remarks>
341         ///リダイレクト応答の場合(AllowAutoRedirect=Falseの場合のみ)は、headerInfoインスタンスがあればLocationを追加してリダイレクト先を返却
342         ///WebExceptionはハンドルしていないので、呼び出し元でキャッチすること
343         ///テキストの文字コードはUTF-8を前提として、エンコードはしていません
344         ///</remarks>
345         ///<param name="webRequest">HTTP通信リクエストオブジェクト</param>
346         ///<param name="contentText">[OUT]HTTP応答のボディデータ</param>
347         ///<param name="headerInfo">[IN/OUT]HTTP応答のヘッダ情報。ヘッダ名をキーにして空データのコレクションを渡すことで、該当ヘッダの値をデータに設定して戻す</param>
348         ///<returns>HTTP応答のステータスコード</returns>
349         protected HttpStatusCode GetResponse(HttpWebRequest webRequest,
350                                              out string contentText,
351                                              Dictionary<string, string> headerInfo)
352         {
353             try
354             {
355                 using (HttpWebResponse webRes = (HttpWebResponse)webRequest.GetResponse())
356                 {
357                     HttpStatusCode statusCode = webRes.StatusCode;
358                     //cookie保持
359                     if (this.UseCookie) this.FixCookies(webRes.Cookies);
360                     //リダイレクト応答の場合は、リダイレクト先を設定
361                     GetHeaderInfo(webRes, headerInfo);
362                     //応答のストリームをテキストに書き出し
363                     using (StreamReader sr = new StreamReader(webRes.GetResponseStream()))
364                     {
365                         contentText = sr.ReadToEnd();
366                     }
367                     return statusCode;
368                 }
369             }
370             catch (WebException ex)
371             {
372                 if (ex.Status == WebExceptionStatus.ProtocolError)
373                 {
374                     HttpWebResponse res = (HttpWebResponse)ex.Response;
375                     GetHeaderInfo(res, headerInfo);
376                     using (StreamReader sr = new StreamReader(res.GetResponseStream()))
377                     {
378                         contentText = sr.ReadToEnd();
379                     }
380                     return res.StatusCode;
381                 }
382                 throw;
383             }
384         }
385
386         ///<summary>
387         ///HTTPの応答を処理します。応答ボディデータが不要な用途向け。
388         ///</summary>
389         ///<remarks>
390         ///リダイレクト応答の場合(AllowAutoRedirect=Falseの場合のみ)は、headerInfoインスタンスがあればLocationを追加してリダイレクト先を返却
391         ///WebExceptionはハンドルしていないので、呼び出し元でキャッチすること
392         ///</remarks>
393         ///<param name="webRequest">HTTP通信リクエストオブジェクト</param>
394         ///<param name="headerInfo">[IN/OUT]HTTP応答のヘッダ情報。ヘッダ名をキーにして空データのコレクションを渡すことで、該当ヘッダの値をデータに設定して戻す</param>
395         ///<returns>HTTP応答のステータスコード</returns>
396         protected HttpStatusCode GetResponse(HttpWebRequest webRequest,
397                                              Dictionary<string, string> headerInfo)
398         {
399             try
400             {
401                 using (HttpWebResponse webRes = (HttpWebResponse)webRequest.GetResponse())
402                 {
403                     HttpStatusCode statusCode = webRes.StatusCode;
404                     //cookie保持
405                     if (this.UseCookie) this.FixCookies(webRes.Cookies);
406                     //リダイレクト応答の場合は、リダイレクト先を設定
407                     GetHeaderInfo(webRes, headerInfo);
408                     return statusCode;
409                 }
410             }
411             catch (WebException ex)
412             {
413                 if (ex.Status == WebExceptionStatus.ProtocolError)
414                 {
415                     HttpWebResponse res = (HttpWebResponse)ex.Response;
416                     GetHeaderInfo(res, headerInfo);
417                     return res.StatusCode;
418                 }
419                 throw;
420             }
421         }
422
423         ///<summary>
424         ///HTTPの応答を処理し、応答ボディデータをBitmapとして返却します
425         ///</summary>
426         ///<remarks>
427         ///リダイレクト応答の場合(AllowAutoRedirect=Falseの場合のみ)は、headerInfoインスタンスがあればLocationを追加してリダイレクト先を返却
428         ///WebExceptionはハンドルしていないので、呼び出し元でキャッチすること
429         ///</remarks>
430         ///<param name="webRequest">HTTP通信リクエストオブジェクト</param>
431         ///<param name="contentBitmap">[OUT]HTTP応答のボディデータを書き込むBitmap</param>
432         ///<param name="headerInfo">[IN/OUT]HTTP応答のヘッダ情報。ヘッダ名をキーにして空データのコレクションを渡すことで、該当ヘッダの値をデータに設定して戻す</param>
433         ///<returns>HTTP応答のステータスコード</returns>
434         protected HttpStatusCode GetResponse(HttpWebRequest webRequest,
435                                              out Bitmap contentBitmap,
436                                              Dictionary<string, string> headerInfo)
437         {
438             try
439             {
440                 using (HttpWebResponse webRes = (HttpWebResponse)webRequest.GetResponse())
441                 {
442                     HttpStatusCode statusCode = webRes.StatusCode;
443                     //cookie保持
444                     if (this.UseCookie) this.FixCookies(webRes.Cookies);
445                     //リダイレクト応答の場合は、リダイレクト先を設定
446                     GetHeaderInfo(webRes, headerInfo);
447                     //応答のストリームをBitmapにして戻す
448                     //if (webRes.ContentLength > 0) contentBitmap = new Bitmap(webRes.GetResponseStream());
449                     contentBitmap = new Bitmap(webRes.GetResponseStream());
450                     return statusCode;
451                 }
452             }
453             catch (WebException ex)
454             {
455                 if (ex.Status == WebExceptionStatus.ProtocolError)
456                 {
457                     HttpWebResponse res = (HttpWebResponse)ex.Response;
458                     GetHeaderInfo(res, headerInfo);
459                     contentBitmap = null;
460                     return res.StatusCode;
461                 }
462                 throw;
463             }
464         }
465
466         /// <summary>
467         /// ホスト名なしのドメインはドメイン名から先頭のドットを除去しないと再利用されないため修正して追加する
468         /// </summary>
469         private void FixCookies(CookieCollection cookieCollection)
470         {
471             foreach (Cookie ck in cookieCollection)
472             {
473                 if (ck.Domain.StartsWith("."))
474                 {
475                     ck.Domain = ck.Domain.Substring(1);
476                     cookieContainer.Add(ck);
477                 }
478             }
479         }
480
481         ///<summary>
482         ///headerInfoのキー情報で指定されたHTTPヘッダ情報を取得・格納する。redirect応答時はLocationヘッダの内容を追記する
483         ///</summary>
484         ///<param name="webResponse">HTTP応答</param>
485         ///<param name="headerInfo">[IN/OUT]キーにヘッダ名を指定したデータ空のコレクション。取得した値をデータにセットして戻す</param>
486         private void GetHeaderInfo(HttpWebResponse webResponse,
487                                    Dictionary<string, string> headerInfo)
488         {
489             if (headerInfo == null) return;
490
491             if (headerInfo.Count > 0)
492             {
493                 var headers = webResponse.Headers;
494                 var dictKeys = new string[headerInfo.Count];
495                 headerInfo.Keys.CopyTo(dictKeys, 0);
496
497                 foreach (var key in dictKeys)
498                 {
499                     var value = headers[key];
500                     headerInfo[key] = value ?? "";
501                 }
502             }
503
504             HttpStatusCode statusCode = webResponse.StatusCode;
505             if (statusCode == HttpStatusCode.MovedPermanently ||
506                 statusCode == HttpStatusCode.Found ||
507                 statusCode == HttpStatusCode.SeeOther ||
508                 statusCode == HttpStatusCode.TemporaryRedirect)
509             {
510                 if (webResponse.Headers["Location"] != null)
511                 {
512                     headerInfo["Location"] = webResponse.Headers["Location"];
513                 }
514             }
515         }
516
517         ///<summary>
518         ///クエリ形式(key1=value1&key2=value2&...)の文字列をkey-valueコレクションに詰め直し
519         ///</summary>
520         ///<param name="queryString">クエリ文字列</param>
521         ///<returns>key-valueのコレクション</returns>
522         protected NameValueCollection ParseQueryString(string queryString)
523         {
524             NameValueCollection query = new NameValueCollection();
525             string[] parts = queryString.Split('&');
526             foreach (string part in parts)
527             {
528                 int index = part.IndexOf('=');
529                 if (index == -1)
530                     query.Add(Uri.UnescapeDataString(part), "");
531                 else
532                     query.Add(Uri.UnescapeDataString(part.Substring(0, index)), Uri.UnescapeDataString(part.Substring(index + 1)));
533             }
534             return query;
535         }
536
537         ///<summary>
538         ///2バイト文字も考慮したUrlエンコード
539         ///</summary>
540         ///<param name="stringToEncode">エンコードする文字列</param>
541         ///<returns>エンコード結果文字列</returns>
542         protected string UrlEncode(string stringToEncode)
543         {
544             const string UnreservedChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~";
545             StringBuilder sb = new StringBuilder();
546             byte[] bytes = Encoding.UTF8.GetBytes(stringToEncode);
547
548             foreach (byte b in bytes)
549             {
550                 if (UnreservedChars.IndexOf((char)b) != -1)
551                     sb.Append((char)b);
552                 else
553                     sb.AppendFormat("%{0:X2}", b);
554             }
555             return sb.ToString();
556         }
557
558         #region "InstanceTimeout"
559         ///<summary>
560         ///通信タイムアウト時間(ms)
561         ///</summary>
562         private int? _timeout = null;
563
564         ///<summary>
565         ///通信タイムアウト時間(ms)。10~120秒の範囲で指定。範囲外は20秒とする
566         ///</summary>
567         protected int? InstanceTimeout
568         {
569             get { return _timeout; }
570             set
571             {
572                 const int TimeoutMinValue = 10000;
573                 const int TimeoutMaxValue = 120000;
574                 if (value < TimeoutMinValue || value > TimeoutMaxValue)
575                     throw new ArgumentOutOfRangeException("Set " + TimeoutMinValue + "-" + TimeoutMaxValue + ": Value=" + value);
576                 else
577                     _timeout = value;
578             }
579         }
580         #endregion
581     }
582 }