using System; using System.Collections.Generic; using System.Text; using System.Net; using System.Net.Security; using System.Security.Cryptography.X509Certificates; using COSXML.Log; using System.Reflection; using System.IO; using System.Net.Cache; namespace COSXML.Network { /// /// network request and response. /// /// type1: command request /// type2: upload file /// type3: download file /// /// public sealed class CommandTask { public const string TAG = "CommandTask"; public static int MaxRetries = 3; public static HttpClientConfig config; /// /// init connectionLimit and statueCode = 100 action /// /// public static void Init(HttpClientConfig config) { ServicePointManager.Expect100Continue = false; ServicePointManager.DefaultConnectionLimit = config.ConnectionLimit; CommandTask.MaxRetries = config.MaxRetry; CommandTask.config = config; } /// /// sync excute /// /// /// /// /// public static void Excute(Request request, Response response, HttpClientConfig config) { HttpWebRequest httpWebRequest = null; HttpWebResponse httpWebResponse = null; try { //step1: create HttpWebRequest by request.url httpWebRequest = HttpWebRequest.Create(request.RequestUrlString) as HttpWebRequest; httpWebRequest.AllowWriteStreamBuffering = false; //bind webRequest request.BindHttpWebRequest(httpWebRequest); // handler request HandleHttpWebRequest(httpWebRequest, request, config); //final: get response httpWebResponse = httpWebRequest.GetResponse() as HttpWebResponse; //notify has been got response request.onNotifyGetResponse(); //handle response for [100, 300) HandleHttpWebResponse(httpWebResponse, response); } catch (WebException webEx) { if (webEx.Response != null && webEx.Response is HttpWebResponse) { //notify has been got response request.onNotifyGetResponse(); httpWebResponse = (HttpWebResponse)webEx.Response; //handle response for [400, 500] HandleHttpWebResponse(httpWebResponse, response); } else { //QLog.E(TAG, webEx.Message, webEx); throw; } } catch (Exception) { //QLog.E(TAG, ex.Message, ex); throw; } finally { if (httpWebResponse != null) { // print log PrintResponseInfo(httpWebResponse); httpWebResponse.Close(); //QLog.D("XIAO", "response close"); } if (httpWebRequest != null) { httpWebRequest.Abort(); //QLog.D("XIAO", "request close"); } QLog.Debug(TAG, "close"); } } /// /// handle request /// /// /// /// private static void HandleHttpWebRequest(HttpWebRequest httpWebRequest, Request request, HttpClientConfig config) { HandleHttpWebRequestHeaders(request, httpWebRequest, config); //setp5: send request content: body if (request.Body != null) { httpWebRequest.ContentLength = request.Body.ContentLength; request.Body.OnWrite(httpWebRequest.GetRequestStream()); } //print request start log PrintReqeustInfo(httpWebRequest); } /// /// handle response /// /// /// private static void HandleHttpWebResponse(HttpWebResponse httpWebResponse, Response response) { HandleHttpWebResponseHeaders(response, httpWebResponse); //handle body response.Body.HandleResponseBody(httpWebResponse.GetResponseStream()); response.OnFinish(response.Code >= 200 && response.Code < 300, null); // close //httpWebResponse.Close(); } /// /// async to excute /// /// /// /// public static void Schedue(Request request, Response response, HttpClientConfig config, int retryIndex = 0) { HttpWebRequest httpWebRequest = null; RequestState requestState = new RequestState(); try { requestState.request = request; requestState.response = response; httpWebRequest = WebRequest.Create(request.RequestUrlString) as HttpWebRequest; httpWebRequest.AllowWriteStreamBuffering = false; //bind webRequest request.BindHttpWebRequest(httpWebRequest); //handle request header HandleHttpWebRequestHeaders(request, httpWebRequest, config); requestState.httpWebRequest = httpWebRequest; requestState.retryIndex = retryIndex; //handle request body if (request.Body != null) { httpWebRequest.ContentLength = request.Body.ContentLength; httpWebRequest.BeginGetRequestStream(new AsyncCallback(AsyncRequestCallback), requestState); } else { //wait for response httpWebRequest.BeginGetResponse(new AsyncCallback(AsyncResponseCallback), requestState); } //print log PrintReqeustInfo(httpWebRequest); } catch (WebException webEx) { response.OnFinish(false, webEx); //abort requestState.Clear(); QLog.Debug(TAG, webEx.Message, webEx); } catch (Exception ex) { response.OnFinish(false, ex); //abort requestState.Clear(); QLog.Error(TAG, ex.Message, ex); } } public static void AsyncRequestCallback(IAsyncResult ar) { RequestState requestState = ar.AsyncState as RequestState; Stream requestStream = null; try { HttpWebRequest httpWebRequest = requestState.httpWebRequest; requestStream = httpWebRequest.EndGetRequestStream(ar); ////开始写入数据 //requestState.request.Body.OnWrite(requestStream); ////wait for response //httpWebRequest.BeginGetResponse(AsyncResponseCallback, requestState); requestState.request.Body.StartHandleRequestBody(requestStream, delegate (Exception exception) { if (exception != null) { // handle request body throw exception if (requestState.retryIndex < MaxRetries) { QLog.Error(TAG, exception.Message, exception); Schedue(requestState.request, requestState.response, config, requestState.retryIndex + 1); return; } requestState.response.OnFinish(false, exception); //abort requestState.Clear(); QLog.Error(TAG, exception.Message, exception); } else { //wait for response httpWebRequest.BeginGetResponse(new AsyncCallback(AsyncResponseCallback), requestState); } }); } catch (Exception ex) { if (requestState.retryIndex < MaxRetries) { QLog.Error(TAG, ex.Message, ex); Schedue(requestState.request, requestState.response, config, requestState.retryIndex + 1); return; } requestState.response.OnFinish(false, ex); //abort requestState.Clear(); QLog.Error(TAG, ex.Message, ex); } } public static void AsyncResponseCallback(IAsyncResult ar) { RequestState requestState = ar.AsyncState as RequestState; HttpWebResponse httpWebResponse = null; try { HttpWebRequest httpWebRequest = requestState.httpWebRequest; httpWebResponse = (HttpWebResponse)httpWebRequest.EndGetResponse(ar); //nofity get response requestState.request.onNotifyGetResponse(); requestState.httpWebResponse = httpWebResponse; //handle response headers HandleHttpWebResponseHeaders(requestState.response, httpWebResponse); Stream responseStream = httpWebResponse.GetResponseStream(); requestState.response.Body.StartHandleResponseBody(responseStream, delegate (bool isSuccess, Exception ex) { PrintResponseInfo(httpWebResponse); requestState.response.OnFinish(isSuccess, ex); requestState.Clear(); }); } catch (WebException webEx) { if (requestState.retryIndex < MaxRetries) { Schedue(requestState.request, requestState.response, config, requestState.retryIndex + 1); return; } if (webEx.Response != null && webEx.Response is HttpWebResponse) { //nofity get response requestState.request.onNotifyGetResponse(); //handle response for [400, 500] httpWebResponse = (HttpWebResponse)webEx.Response; requestState.httpWebResponse = httpWebResponse; //handle response headers HandleHttpWebResponseHeaders(requestState.response, httpWebResponse); Stream responseStream = httpWebResponse.GetResponseStream(); requestState.response.Body.StartHandleResponseBody(responseStream, delegate (bool isSuccess, Exception ex) { PrintResponseInfo(httpWebResponse); requestState.response.OnFinish(isSuccess, ex); requestState.Clear(); }); } else { requestState.response.OnFinish(false, webEx); //abort requestState.Clear(); QLog.Error(TAG, webEx.Message, webEx); } } catch (Exception ex) { if (requestState.retryIndex < MaxRetries) { Schedue(requestState.request, requestState.response, config, requestState.retryIndex + 1); return; } requestState.response.OnFinish(false, ex); //abort requestState.Clear(); QLog.Error(TAG, ex.Message, ex); } } /// /// handle request headers /// /// /// /// private static void HandleHttpWebRequestHeaders(Request request, HttpWebRequest httpWebRequest, HttpClientConfig config) { // set connect timeout httpWebRequest.Timeout = config.ConnectionTimeoutMs; //set read write timeout httpWebRequest.ReadWriteTimeout = config.ReadWriteTimeoutMs; // set request method httpWebRequest.Method = request.Method.ToUpperInvariant(); // set user-agent httpWebRequest.UserAgent = request.UserAgent; //set host, net2.0 cannot set; // set allow auto redirect httpWebRequest.AllowAutoRedirect = config.AllowAutoRedirect; // set connection httpWebRequest.KeepAlive = config.KeepAlive; // notice: it is not allowed to set common headers with the WebHeaderCollection.Accept // such as: Connection,Content-Length,Content-Type,Date,Expect. Host,If-Modified-Since,Range, Referer,Transfer-Encoding,User-Agent,Proxy-Connection //step2: set header and connection properity by request.heders foreach (KeyValuePair pair in request.Headers) { HttpHeaderHandle.AddHeader(httpWebRequest.Headers, pair.Key, pair.Value); } //step3: set proxy, default proxy = null, improte performation SetRequestProxy(httpWebRequest, config); //step4: https, default all true for "*.myqcloud.com" if (request.IsHttps) { ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(CheckValidationCertificate); } //初始化长度 httpWebRequest.ContentLength = 0L; } /// /// headle response headers /// /// /// private static void HandleHttpWebResponseHeaders(Response response, HttpWebResponse httpWebResponse) { response.Code = (int)httpWebResponse.StatusCode; response.Message = httpWebResponse.StatusDescription; WebHeaderCollection headers = httpWebResponse.Headers; // Transfer-Encoding: chunked bool isChunked = false; if (headers != null) { Dictionary> result = new Dictionary>(headers.Count); for (int i = 0; i < headers.Count; i++) { List values = null; string key = headers.GetKey(i); if (headers.GetValues(i) != null) { values = new List(); foreach (string value in headers.GetValues(i)) { values.Add(value); } } result.Add(key, values); if ("Transfer-Encoding".EndsWith(key, StringComparison.OrdinalIgnoreCase) && values.Contains("chunked")) { isChunked = true; } } response.Headers = result; } if (!isChunked) { response.ContentLength = httpWebResponse.ContentLength; } response.ContentType = httpWebResponse.ContentType; if (response.Body != null) { if (!isChunked) { response.Body.ContentLength = httpWebResponse.ContentLength; } response.Body.ContentType = httpWebResponse.ContentType; } //handle header response.HandleResponseHeader(); } /// /// set proxy /// /// /// private static void SetRequestProxy(HttpWebRequest httpWebRequest, HttpClientConfig config) { httpWebRequest.Proxy = null; if (!String.IsNullOrEmpty(config.ProxyHost)) { if (config.ProxyPort < 0) { httpWebRequest.Proxy = new WebProxy(config.ProxyHost); } else { httpWebRequest.Proxy = new WebProxy(config.ProxyHost, config.ProxyPort); } if (!String.IsNullOrEmpty(config.ProxyUserName)) { httpWebRequest.Proxy.Credentials = String.IsNullOrEmpty(config.ProxyDomain) ? new NetworkCredential(config.ProxyUserName, config.ProxyUserPassword ?? String.Empty) : new NetworkCredential(config.ProxyUserName, config.ProxyUserPassword ?? String.Empty, config.ProxyDomain); } // 代理验证 // 代理验证 httpWebRequest.PreAuthenticate = true; } } /// /// check certificate /// /// /// /// /// /// private static bool CheckValidationCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors) { return true; } /// /// print request info /// /// private static void PrintReqeustInfo(HttpWebRequest httpWebRequest) { StringBuilder requestLog = new StringBuilder("--->"); requestLog.Append(httpWebRequest.Method).Append(' ').Append(httpWebRequest.Address.AbsoluteUri).Append('\n'); int count = httpWebRequest.Headers.Count; for (int i = 0; i < count; i++) { requestLog.Append(httpWebRequest.Headers.GetKey(i)).Append(":").Append(httpWebRequest.Headers.GetValues(i)[0]).Append('\n'); } requestLog.Append("allow auto redirect: " + httpWebRequest.AllowAutoRedirect).Append('\n'); requestLog.Append("connect timeout: " + httpWebRequest.Timeout).Append('\n'); requestLog.Append("read write timeout: " + httpWebRequest.ReadWriteTimeout).Append('\n'); requestLog.Append("AllowWriteStreamBuffering: " + httpWebRequest.AllowWriteStreamBuffering).Append('\n'); //requestLog.Append("proxy: " + (httpWebRequest.Proxy == null ? "null" : ((WebProxy)httpWebRequest.Proxy).Address.ToString())); requestLog.Append("<---"); QLog.Debug(TAG, requestLog.ToString()); } /// /// print response info /// /// private static void PrintResponseInfo(HttpWebResponse httpWebResponse) { StringBuilder responseLog = new StringBuilder("<---"); responseLog.Append(httpWebResponse.Method).Append(' ').Append(httpWebResponse.ResponseUri.AbsoluteUri).Append('\n'); responseLog.Append((int)httpWebResponse.StatusCode).Append(' ').Append(httpWebResponse.StatusDescription).Append('\n'); int count = httpWebResponse.Headers.Count; for (int i = 0; i < count; i++) { responseLog.Append(httpWebResponse.Headers.GetKey(i)).Append(":").Append(httpWebResponse.Headers.GetValues(i)[0]).Append('\n'); } responseLog.Append("<---"); QLog.Debug(TAG, responseLog.ToString()); } internal static class HttpHeaderHandle { private static MethodInfo addHeaderMethod; private static readonly ICollection monoPlatforms = new List { PlatformID.MacOSX, PlatformID.Unix }; private static bool? isMonoPlatform; internal static void AddHeader(WebHeaderCollection webHeaderCollection, string key, string value) { if (isMonoPlatform == null) { isMonoPlatform = monoPlatforms.Contains(Environment.OSVersion.Platform); } // HTTP headers should be encoded to iso-8859-1, // however it will be encoded automatically by HttpWebRequest in mono. if (false == isMonoPlatform) { // Encode headers for win platforms. } if (addHeaderMethod == null) { // Specify the internal method name for adding headers // mono: AddWithoutValidate // win: AddInternal //var internalMethodName = (isMonoPlatform == false) ? "AddWithoutValidate" : "AddInternal"; var internalMethodName = "AddWithoutValidate"; QLog.Debug(TAG, internalMethodName.ToString()); var method = typeof(WebHeaderCollection).GetMethod( internalMethodName, BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[] { typeof(string), typeof(string) }, null); if (method == null) { internalMethodName = "AddInternal"; QLog.Debug(TAG, internalMethodName.ToString()); method = typeof(WebHeaderCollection).GetMethod( internalMethodName, BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[] { typeof(string), typeof(string) }, null); } addHeaderMethod = method; } addHeaderMethod.Invoke(webHeaderCollection, new Object[] { key, value }); } } internal class RequestState { public HttpWebRequest httpWebRequest; public HttpWebResponse httpWebResponse; public Response response; public Request request; public int retryIndex; public RequestState() { httpWebRequest = null; httpWebResponse = null; response = null; request = null; } public void Clear() { if (httpWebRequest != null) { httpWebRequest.Abort(); } if (httpWebResponse != null) { httpWebResponse.Close(); } QLog.Debug(TAG, "Close"); } } } }