/* * Copyright (C) Alibaba Cloud Computing * All rights reserved. * * 版权所有 (C)阿里云计算有限公司 */ using System; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Net; using System.Reflection; using System.Security.Cryptography.X509Certificates; using System.Net.Security; using System.Linq; using Aliyun.OSS.Util; namespace Aliyun.OSS.Common.Communication { /// <summary> /// The default implementation of <see cref="ServiceClient"/> that /// used to communicate with Aliyun OSS via HTTP protocol. /// </summary> internal class ServiceClientImpl : ServiceClient { #region Embeded Classes /// <summary> /// Represents the async operation of requests in <see cref="ServiceClientImpl"/>. /// </summary> public class HttpAsyncResult : AsyncResult<ServiceResponse> { public HttpWebRequest WebRequest { get; set; } public ExecutionContext Context { get; set; } public HttpAsyncResult(AsyncCallback callback, object state) : base(callback, state) { } } /// <summary> /// Represents the response data of <see cref="ServiceClientImpl"/> requests. /// </summary> private class ResponseImpl : ServiceResponse { private bool _disposed; private HttpWebResponse _response; private readonly Exception _failure; private IDictionary<string, string> _headers; public override HttpStatusCode StatusCode { get { return _response.StatusCode; } } public override Exception Failure { get { return _failure; } } public override IDictionary<string, string> Headers { get { ThrowIfObjectDisposed(); return _headers ?? (_headers = GetResponseHeaders(_response)); } } public override Stream Content { get { ThrowIfObjectDisposed(); try { return (_response != null) ? _response.GetResponseStream() : null; } catch (ProtocolViolationException ex) { throw new InvalidOperationException(ex.Message, ex); } } } public ResponseImpl(HttpWebResponse httpWebResponse) { _response = httpWebResponse; } public ResponseImpl(WebException failure) { var httpWebResponse = failure.Response as HttpWebResponse; _failure = failure; _response = httpWebResponse; } private static IDictionary<string, string> GetResponseHeaders(HttpWebResponse response) { var headers = response.Headers; var result = new Dictionary<string, string>(headers.Count); for (var i = 0; i < headers.Count; i++) { var key = headers.Keys[i]; var value = headers.Get(key); result.Add(key, HttpUtils.Reencode(value, HttpUtils.Iso88591Charset, HttpUtils.Utf8Charset)); } return result; } protected override void Dispose(bool disposing) { base.Dispose(disposing); if (_disposed) return; if (disposing) { if (_response != null) { _response.Close(); _response = null; } _disposed = true; } } private void ThrowIfObjectDisposed() { if (_disposed) throw new ObjectDisposedException(GetType().Name); } } #endregion #region Constructors public ServiceClientImpl(ClientConfiguration configuration) : base(configuration) { } #endregion #region Implementations protected override ServiceResponse SendCore(ServiceRequest serviceRequest, ExecutionContext context) { var request = HttpFactory.CreateWebRequest(serviceRequest, Configuration); SetRequestContent(request, serviceRequest, false, null); try { var response = request.GetResponse() as HttpWebResponse; if (response.Server.ToLower() == "aliyunoss" || response.Headers.AllKeys.Any(s => s.ToLower().Contains("x-oss-"))) { return new ResponseImpl(response); } else { return HandleException(new WebException("文件不存在.")); } } catch (WebException ex) { return HandleException(ex); } } protected override IAsyncResult BeginSendCore(ServiceRequest serviceRequest, ExecutionContext context, AsyncCallback callback, object state) { var request = HttpFactory.CreateWebRequest(serviceRequest, Configuration); var asyncResult = new HttpAsyncResult(callback, state) { WebRequest = request, Context = context }; SetRequestContent(request, serviceRequest, true, () => request.BeginGetResponse(OnGetResponseCompleted, asyncResult)); return asyncResult; } [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Catch the exception and set it to async result.")] private void OnGetResponseCompleted(IAsyncResult ar) { var asyncResult = ar.AsyncState as HttpAsyncResult; Debug.Assert(asyncResult != null && asyncResult.WebRequest != null); try { var response = asyncResult.WebRequest.EndGetResponse(ar) as HttpWebResponse; ServiceResponse res = new ResponseImpl(response); HandleResponse(res, asyncResult.Context.ResponseHandlers); asyncResult.Complete(res); } catch (WebException we) { try { var res = HandleException(we); HandleResponse(res, asyncResult.Context.ResponseHandlers); asyncResult.WebRequest.Abort(); asyncResult.Complete(res); } catch (Exception ie) { asyncResult.WebRequest.Abort(); asyncResult.Complete(ie); } } catch (Exception oe) { asyncResult.WebRequest.Abort(); asyncResult.Complete(oe); } } /// <summary> /// 为了兼容.NET2.0,定义了OssAction,功能等价于.NET4.0中的System.Action /// </summary> private delegate void OssAction(); private static void SetRequestContent(HttpWebRequest webRequest, ServiceRequest serviceRequest, bool async, OssAction asyncCallback) { var data = serviceRequest.BuildRequestContent(); if (data == null || (serviceRequest.Method != HttpMethod.Put && serviceRequest.Method != HttpMethod.Post)) { // Skip setting content body in this case. if (async) asyncCallback(); return; } // Write data to the request stream. long userSetContentLength = -1; if (serviceRequest.Headers.ContainsKey(HttpHeaders.ContentLength)) userSetContentLength = long.Parse(serviceRequest.Headers[HttpHeaders.ContentLength]); long streamLength = data.Length - data.Position; webRequest.ContentLength = (userSetContentLength >= 0 && userSetContentLength <= streamLength) ? userSetContentLength : streamLength; if (async) { webRequest.BeginGetRequestStream( (ar) => { using (var requestStream = webRequest.EndGetRequestStream(ar)) { IoUtils.WriteTo(data, requestStream, webRequest.ContentLength); } asyncCallback(); }, null); } else { using (var requestStream = webRequest.GetRequestStream()) { IoUtils.WriteTo(data, requestStream, webRequest.ContentLength); } } } private static ServiceResponse HandleException(WebException ex) { var response = ex.Response as HttpWebResponse; if (response == null) throw ex; else return new ResponseImpl(ex); } #endregion } internal static class HttpFactory { public static bool CheckValidationResult(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors) { return true; } internal static HttpWebRequest CreateWebRequest(ServiceRequest serviceRequest, ClientConfiguration configuration) { var webRequest = WebRequest.Create(serviceRequest.BuildRequestUri()) as HttpWebRequest; SetRequestHeaders(webRequest, serviceRequest, configuration); SetRequestProxy(webRequest, configuration); if (webRequest.RequestUri.Scheme == "https") { ServicePointManager.ServerCertificateValidationCallback = new System.Net.Security.RemoteCertificateValidationCallback(CheckValidationResult); } return webRequest; } // Set request headers private static void SetRequestHeaders(HttpWebRequest webRequest, ServiceRequest serviceRequest, ClientConfiguration configuration) { webRequest.Timeout = configuration.ConnectionTimeout; webRequest.Method = serviceRequest.Method.ToString().ToUpperInvariant(); // Because it is not allowed to set common headers // with the WebHeaderCollection.Add method, // we have to call an internal method to skip validation. foreach (var h in serviceRequest.Headers) HttpExtensions.AddInternal(webRequest.Headers, h.Key, h.Value); // Set user-agent if (!string.IsNullOrEmpty(configuration.UserAgent)) webRequest.UserAgent = configuration.UserAgent; } // Set proxy private static void SetRequestProxy(HttpWebRequest webRequest, ClientConfiguration configuration) { // Perf Improvement: // If HttpWebRequest.Proxy is not set to null explicitly, // it will try to load the IE proxy settings including auto proxy detection, // which is quite time consuming. webRequest.Proxy = null; // Set proxy if proxy settings are specified. if (!string.IsNullOrEmpty(configuration.ProxyHost)) { if (configuration.ProxyPort < 0) webRequest.Proxy = new WebProxy(configuration.ProxyHost); else webRequest.Proxy = new WebProxy(configuration.ProxyHost, configuration.ProxyPort); if (!string.IsNullOrEmpty(configuration.ProxyUserName)) { webRequest.Proxy.Credentials = String.IsNullOrEmpty(configuration.ProxyDomain) ? new NetworkCredential(configuration.ProxyUserName, configuration.ProxyPassword ?? string.Empty) : new NetworkCredential(configuration.ProxyUserName, configuration.ProxyPassword ?? string.Empty, configuration.ProxyDomain); } } } } internal static class HttpExtensions { private static MethodInfo _addInternalMethod; private static readonly ICollection<PlatformID> MonoPlatforms = new List<PlatformID> { PlatformID.MacOSX, PlatformID.Unix }; private static bool? _isMonoPlatform; internal static void AddInternal(WebHeaderCollection headers, 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 (_isMonoPlatform == false) // Encode headers for win platforms. value = HttpUtils.Reencode(value, HttpUtils.Utf8Charset, HttpUtils.Iso88591Charset); if (_addInternalMethod == null) { // Specify the internal method name for adding headers // mono: AddWithoutValidate // win: AddInternal var internalMethodName = (_isMonoPlatform == true) ? "AddWithoutValidate" : "AddInternal"; var mi = typeof(WebHeaderCollection).GetMethod( internalMethodName, BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[] { typeof(string), typeof(string) }, null); _addInternalMethod = mi; } _addInternalMethod.Invoke(headers, new object[] { key, value }); } } }