/// <summary> /// Provide fault /// </summary> public bool ProvideFault(Exception error, RestResponseMessage faultMessage) { var uriMatched = RestOperationContext.Current.IncomingRequest.Url; while (error.InnerException != null) { error = error.InnerException; } var fault = new RestServiceFault(error); var authScheme = RestOperationContext.Current.AppliedPolicies.OfType <BasicAuthorizationAccessBehavior>().Any() ? "Basic" : "Bearer"; var authRealm = RestOperationContext.Current.IncomingRequest.Url.Host; // Formulate appropriate response if (error is DomainStateException) { faultMessage.StatusCode = (int)System.Net.HttpStatusCode.ServiceUnavailable; } else if (error is ObjectLockedException lockException) { faultMessage.StatusCode = 423; fault.Data.Add(lockException.LockedUser); } else if (error is PolicyViolationException) { var pve = error as PolicyViolationException; if (pve.PolicyDecision == PolicyGrantType.Elevate) { // Ask the user to elevate themselves faultMessage.StatusCode = 401; faultMessage.AddAuthenticateHeader(authScheme, authRealm, "insufficient_scope", pve.PolicyId, error.Message); } else { faultMessage.StatusCode = 403; } } else if (error is SecurityException) { faultMessage.StatusCode = (int)HttpStatusCode.Forbidden; } else if (error is SecurityTokenException) { // TODO: Audit this faultMessage.StatusCode = (int)System.Net.HttpStatusCode.Unauthorized; var authHeader = $"Bearer realm=\"{RestOperationContext.Current.IncomingRequest.Url.Host}\" error=\"invalid_token\" error_description=\"{error.Message}\""; faultMessage.AddAuthenticateHeader(authScheme, authRealm, error: "invalid_token", description: error.Message); } else if (error is LimitExceededException) { faultMessage.StatusCode = (int)(HttpStatusCode)429; faultMessage.StatusDescription = "Too Many Requests"; faultMessage.Headers.Add("Retry-After", "1200"); } else if (error is AuthenticationException) { faultMessage.StatusCode = (int)System.Net.HttpStatusCode.Unauthorized; faultMessage.AddAuthenticateHeader(authScheme, authRealm, "invalid_token", description: error.Message); } else if (error is UnauthorizedAccessException) { faultMessage.StatusCode = (int)System.Net.HttpStatusCode.Forbidden; } else if (error is SecuritySessionException ses) { switch (ses.Type) { case SessionExceptionType.Expired: case SessionExceptionType.NotYetValid: case SessionExceptionType.NotEstablished: faultMessage.StatusCode = (int)System.Net.HttpStatusCode.Unauthorized; faultMessage.AddAuthenticateHeader(authScheme, authRealm, error: "unauthorized"); break; default: faultMessage.StatusCode = (int)System.Net.HttpStatusCode.Forbidden; break; } } else if (error is FaultException) { faultMessage.StatusCode = (int)(error as FaultException).StatusCode; } else if (error is Newtonsoft.Json.JsonException || error is System.Xml.XmlException) { faultMessage.StatusCode = (int)System.Net.HttpStatusCode.BadRequest; } else if (error is DuplicateKeyException || error is DuplicateNameException) { faultMessage.StatusCode = (int)System.Net.HttpStatusCode.Conflict; } else if (error is FileNotFoundException || error is KeyNotFoundException) { faultMessage.StatusCode = (int)System.Net.HttpStatusCode.NotFound; } else if (error is DomainStateException) { faultMessage.StatusCode = (int)System.Net.HttpStatusCode.ServiceUnavailable; } else if (error is DetectedIssueException) { faultMessage.StatusCode = (int)(System.Net.HttpStatusCode) 422; } else if (error is NotImplementedException) { faultMessage.StatusCode = (int)HttpStatusCode.NotImplemented; } else if (error is NotSupportedException) { faultMessage.StatusCode = (int)HttpStatusCode.MethodNotAllowed; } else if (error is PatchException) { faultMessage.StatusCode = (int)HttpStatusCode.Conflict; } else { faultMessage.StatusCode = (int)System.Net.HttpStatusCode.InternalServerError; } switch (faultMessage.StatusCode) { case 409: case 429: case 503: this.m_traceSource.TraceInfo("Issue on REST pipeline: {0}", error); break; case 401: case 403: case 501: case 405: this.m_traceSource.TraceWarning("Warning on REST pipeline: {0}", error); break; default: this.m_traceSource.TraceError("Error on REST pipeline: {0}", error); break; } RestMessageDispatchFormatter.CreateFormatter(RestOperationContext.Current.ServiceEndpoint.Description.Contract.Type).SerializeResponse(faultMessage, null, fault); AuditUtil.AuditNetworkRequestFailure(error, uriMatched, RestOperationContext.Current.IncomingRequest.Headers.AllKeys.ToDictionary(o => o, o => RestOperationContext.Current.IncomingRequest.Headers[o]), RestOperationContext.Current.OutgoingResponse.Headers.AllKeys.ToDictionary(o => o, o => RestOperationContext.Current.OutgoingResponse.Headers[o])); return(true); }
/// <summary> /// Invokes the specified method against the url provided /// </summary> /// <param name="method">Method.</param> /// <param name="url">URL.</param> /// <param name="contentType">Content type.</param> /// <param name="body">Body.</param> /// <param name="query">Query.</param> /// <typeparam name="TBody">The 1st type parameter.</typeparam> /// <typeparam name="TResult">The 2nd type parameter.</typeparam> protected override TResult InvokeInternal <TBody, TResult>(string method, string url, string contentType, WebHeaderCollection additionalHeaders, out WebHeaderCollection responseHeaders, TBody body, NameValueCollection query) { if (String.IsNullOrEmpty(method)) { throw new ArgumentNullException(nameof(method)); } //if (String.IsNullOrEmpty(url)) // throw new ArgumentNullException(nameof(url)); // Credentials provided ? HttpWebRequest requestObj = this.CreateHttpRequest(url, query) as HttpWebRequest; if (!String.IsNullOrEmpty(contentType)) { requestObj.ContentType = contentType; } requestObj.Method = method; // Additional headers if (additionalHeaders != null) { foreach (var hdr in additionalHeaders.AllKeys) { if (hdr == "If-Modified-Since") { requestObj.IfModifiedSince = DateTime.Parse(additionalHeaders[hdr]); } else { requestObj.Headers.Add(hdr, additionalHeaders[hdr]); } } } #if PERFMON Stopwatch sw = new Stopwatch(); sw.Start(); #endif // Get request object // Body was provided? try { // Try assigned credentials IBodySerializer serializer = null; if (body != null) { // GET Stream, Stream requestStream = null; try { // Get request object var cancellationTokenSource = new CancellationTokenSource(); cancellationTokenSource.CancelAfter(this.Description.Endpoint[0].Timeout); using (var requestTask = Task.Run(async() => { return(await requestObj.GetRequestStreamAsync()); }, cancellationTokenSource.Token)) { try { requestStream = requestTask.Result; } catch (AggregateException e) { requestObj.Abort(); throw e.InnerExceptions.First(); } } if (contentType == null && typeof(TResult) != typeof(Object)) { throw new ArgumentNullException(nameof(contentType)); } serializer = this.Description.Binding.ContentTypeMapper.GetSerializer(contentType, typeof(TBody)); // Serialize and compress with deflate using (MemoryStream ms = new MemoryStream()) { if (this.Description.Binding.Optimize) { switch ((this.Description.Binding as ServiceClientBinding)?.OptimizationMethod) { case OptimizationMethod.Lzma: requestObj.Headers.Add("Content-Encoding", "lzma"); using (var df = new LZipStream(new NonDisposingStream(requestStream), CompressionMode.Compress)) serializer.Serialize(df, body); break; case OptimizationMethod.Bzip2: requestObj.Headers.Add("Content-Encoding", "bzip2"); using (var df = new BZip2Stream(new NonDisposingStream(requestStream), CompressionMode.Compress, false)) serializer.Serialize(df, body); break; case OptimizationMethod.Gzip: requestObj.Headers.Add("Content-Encoding", "gzip"); using (var df = new GZipStream(new NonDisposingStream(requestStream), CompressionMode.Compress)) serializer.Serialize(df, body); break; case OptimizationMethod.Deflate: requestObj.Headers.Add("Content-Encoding", "deflate"); using (var df = new DeflateStream(new NonDisposingStream(requestStream), CompressionMode.Compress)) serializer.Serialize(df, body); break; case OptimizationMethod.None: default: serializer.Serialize(ms, body); break; } } else { serializer.Serialize(ms, body); } // Trace if (this.Description.Trace) { this.m_tracer.TraceVerbose("HTTP >> {0}", Convert.ToBase64String(ms.ToArray())); } using (var nms = new MemoryStream(ms.ToArray())) nms.CopyTo(requestStream); } } finally { if (requestStream != null) { requestStream.Dispose(); } } } // Response HttpWebResponse response = null; try { var cancellationTokenSource = new CancellationTokenSource(); cancellationTokenSource.CancelAfter(this.Description.Endpoint[0].Timeout); using (var responseTask = Task.Run(async() => { return(await requestObj.GetResponseAsync()); }, cancellationTokenSource.Token)) { try { response = (HttpWebResponse)responseTask.Result; } catch (AggregateException e) { requestObj.Abort(); throw e.InnerExceptions.First(); } } responseHeaders = response.Headers; // No content - does the result want a pointer maybe? if (response.StatusCode == HttpStatusCode.NoContent || response.StatusCode == HttpStatusCode.Continue || response.StatusCode == HttpStatusCode.NotModified) { return(default(TResult)); } else if (response.StatusCode == HttpStatusCode.RedirectKeepVerb) { return(this.InvokeInternal <TBody, TResult>(method, response.Headers[HttpResponseHeader.Location], contentType, additionalHeaders, out responseHeaders, body, query)); } else if (response.StatusCode == HttpStatusCode.RedirectMethod) { return(this.InvokeInternal <TBody, TResult>("GET", response.Headers[HttpResponseHeader.Location], contentType, additionalHeaders, out responseHeaders, default(TBody), query)); } else { // De-serialize var responseContentType = response.ContentType; if (String.IsNullOrEmpty(responseContentType)) { return(default(TResult)); } if (responseContentType.Contains(";")) { responseContentType = responseContentType.Substring(0, responseContentType.IndexOf(";")); } if (response.StatusCode == HttpStatusCode.NotModified) { return(default(TResult)); } serializer = this.Description.Binding.ContentTypeMapper.GetSerializer(responseContentType, typeof(TResult)); TResult retVal = default(TResult); // Compression? using (MemoryStream ms = new MemoryStream()) { if (this.Description.Trace) { this.m_tracer.TraceVerbose("Received response {0} : {1} bytes", response.ContentType, response.ContentLength); } response.GetResponseStream().CopyTo(ms); ms.Seek(0, SeekOrigin.Begin); // Trace if (this.Description.Trace) { this.m_tracer.TraceVerbose("HTTP << {0}", Convert.ToBase64String(ms.ToArray())); } switch (response.Headers[HttpResponseHeader.ContentEncoding]) { case "deflate": using (DeflateStream df = new DeflateStream(new NonDisposingStream(ms), CompressionMode.Decompress)) retVal = (TResult)serializer.DeSerialize(df); break; case "gzip": using (GZipStream df = new GZipStream(new NonDisposingStream(ms), CompressionMode.Decompress)) retVal = (TResult)serializer.DeSerialize(df); break; case "bzip2": using (var bzs = new BZip2Stream(new NonDisposingStream(ms), CompressionMode.Decompress, false)) retVal = (TResult)serializer.DeSerialize(bzs); break; case "lzma": using (var lzmas = new LZipStream(new NonDisposingStream(ms), CompressionMode.Decompress)) retVal = (TResult)serializer.DeSerialize(lzmas); break; default: retVal = (TResult)serializer.DeSerialize(ms); break; } //retVal = (TResult)serializer.DeSerialize(ms); } return(retVal); } } finally { if (response != null) { response.Close(); response.Dispose(); } //responseTask.Dispose(); } } catch (TimeoutException e) { this.m_tracer.TraceError("Request timed out:{0}", e.Message); throw; } catch (WebException e) when(e.Response is HttpWebResponse errorResponse && errorResponse.StatusCode == HttpStatusCode.NotModified) { this.m_tracer.TraceInfo("Server indicates not modified {0} {1} : {2}", method, url, e.Message); responseHeaders = errorResponse?.Headers; return(default(TResult)); } catch (WebException e) when(e.Response is HttpWebResponse errorResponse && e.Status == WebExceptionStatus.ProtocolError) { this.m_tracer.TraceError("Error executing {0} {1} : {2}", method, url, e.Message); // Deserialize object errorResult = null; var responseContentType = errorResponse.ContentType; if (responseContentType.Contains(";")) { responseContentType = responseContentType.Substring(0, responseContentType.IndexOf(";")); } var ms = new MemoryStream(); // copy response to memory errorResponse.GetResponseStream().CopyTo(ms); ms.Seek(0, SeekOrigin.Begin); try { var serializer = this.Description.Binding.ContentTypeMapper.GetSerializer(responseContentType, typeof(TResult)); switch (errorResponse.Headers[HttpResponseHeader.ContentEncoding]) { case "deflate": using (DeflateStream df = new DeflateStream(new NonDisposingStream(ms), CompressionMode.Decompress)) errorResult = serializer.DeSerialize(df); break; case "gzip": using (GZipStream df = new GZipStream(new NonDisposingStream(ms), CompressionMode.Decompress)) errorResult = serializer.DeSerialize(df); break; case "bzip2": using (var bzs = new BZip2Stream(new NonDisposingStream(ms), CompressionMode.Decompress, false)) errorResult = serializer.DeSerialize(bzs); break; case "lzma": using (var lzmas = new LZipStream(new NonDisposingStream(ms), CompressionMode.Decompress)) errorResult = serializer.DeSerialize(lzmas); break; default: errorResult = serializer.DeSerialize(ms); break; } } catch { errorResult = new RestServiceFault(e); } Exception exception = null; if (errorResult is RestServiceFault rse) { exception = new RestClientException <RestServiceFault>(rse, e, e.Status, e.Response); } else if (errorResponse is TResult) { exception = new RestClientException <TResult>((TResult)errorResult, e, e.Status, e.Response); } else { exception = new RestClientException <object>(errorResult, e, e.Status, e.Response); } switch (errorResponse.StatusCode) { case HttpStatusCode.Unauthorized: // Validate the response throw exception; case HttpStatusCode.NotModified: responseHeaders = errorResponse?.Headers; return(default(TResult)); case (HttpStatusCode)422: throw exception; default: throw exception; } } catch (WebException e) when(e.Status == WebExceptionStatus.Timeout) { this.m_tracer.TraceError("Error executing {0} {1} : {2}", method, url, e.Message); throw new TimeoutException($"Timeout executing REST operation {method} {url}", e); } catch (WebException e) when(e.Status == WebExceptionStatus.ConnectFailure) { this.m_tracer.TraceError("Error executing {0} {1} : {2}", method, url, e.Message); if ((e.InnerException as SocketException)?.SocketErrorCode == SocketError.TimedOut) { throw new TimeoutException(); } else { throw; } } catch (WebException e) { this.m_tracer.TraceError("Error executing {0} {1} : {2}", method, url, e.Message); throw; } catch (InvalidOperationException e) { this.m_tracer.TraceError("Invalid Operation: {0}", e.Message); throw; } responseHeaders = new WebHeaderCollection(); return(default(TResult)); }
/// <summary> /// Invokes the specified method against the url provided /// </summary> /// <param name="method">Method.</param> /// <param name="url">URL.</param> /// <param name="contentType">Content type.</param> /// <param name="body">Body.</param> /// <param name="query">Query.</param> /// <typeparam name="TBody">The 1st type parameter.</typeparam> /// <typeparam name="TResult">The 2nd type parameter.</typeparam> protected override TResult InvokeInternal <TBody, TResult>(string method, string url, string contentType, WebHeaderCollection requestHeaders, out WebHeaderCollection responseHeaders, TBody body, NameValueCollection query) { if (String.IsNullOrEmpty(method)) { throw new ArgumentNullException(nameof(method)); } if (String.IsNullOrEmpty(url)) { throw new ArgumentNullException(nameof(url)); } // Three times: // 1. With provided credential // 2. With challenge // 3. With challenge again for (int i = 0; i < 2; i++) { // Credentials provided ? HttpWebRequest requestObj = this.CreateHttpRequest(url, query) as HttpWebRequest; if (!String.IsNullOrEmpty(contentType)) { requestObj.ContentType = contentType; } requestObj.Method = method; // Additional headers if (requestHeaders != null) { foreach (var hdr in requestHeaders.AllKeys) { if (hdr == "If-Modified-Since") { requestObj.IfModifiedSince = DateTime.Parse(requestHeaders[hdr]); } else { requestObj.Headers.Add(hdr, requestHeaders[hdr]); } } } // Body was provided? try { // Try assigned credentials IBodySerializer serializer = null; if (body != null) { // GET Stream, Stream requestStream = null; Exception requestException = null; try { //requestStream = requestObj.GetRequestStream(); var requestTask = requestObj.GetRequestStreamAsync().ContinueWith(r => { if (r.IsFaulted) { requestException = r.Exception.InnerExceptions.First(); } else { requestStream = r.Result; } }, TaskContinuationOptions.LongRunning); if (!requestTask.Wait(this.Description.Endpoint[0].Timeout)) { throw new TimeoutException(); } else if (requestException != null) { throw requestException; } if (contentType == null) { throw new ArgumentNullException(nameof(contentType)); } serializer = this.Description.Binding.ContentTypeMapper.GetSerializer(contentType, typeof(TBody)); // Serialize and compress with deflate if (this.Description.Binding.Optimize) { #if HTTP_LZMA requestObj.Headers.Add("Content-Encoding", "lzma"); using (var df = new LZipStream(requestStream, CompressionMode.Compress, leaveOpen: true)) serializer.Serialize(df, body); #elif HTTP_GZ requestObj.Headers.Add("Content-Encoding", "gzip"); using (var df = new GZipStream(requestStream, CompressionMode.Compress, leaveOpen: true)) serializer.Serialize(df, body); #else requestObj.Headers.Add("Content-Encoding", "deflate"); using (var df = new DeflateStream(new NonDisposingStream(requestStream), CompressionMode.Compress)) serializer.Serialize(df, body); #endif } else { serializer.Serialize(requestStream, body); } } finally { if (requestStream != null) { requestStream.Dispose(); } } } // Response HttpWebResponse response = null; Exception responseError = null; try { var responseTask = requestObj.GetResponseAsync().ContinueWith(r => { if (r.IsFaulted) { responseError = r.Exception.InnerExceptions.First(); } else { response = r.Result as HttpWebResponse; } }, TaskContinuationOptions.LongRunning); if (!responseTask.Wait(this.Description.Endpoint[0].Timeout)) { throw new TimeoutException(); } else { if (responseError != null) { responseHeaders = new WebHeaderCollection(); if (((responseError as WebException)?.Response as HttpWebResponse)?.StatusCode == HttpStatusCode.NotModified) { return(default(TResult)); } else { throw responseError; } } responseHeaders = response.Headers; } var validationResult = this.ValidateResponse(response); if (validationResult != ServiceClientErrorType.Valid) { this.traceSource.TraceEvent(EventLevel.Error, "Response failed validation : {0}", validationResult); throw new WebException("Response failed validation", null, WebExceptionStatus.Success, response); } // De-serialize var responseContentType = response.ContentType; if (responseContentType.Contains(";")) { responseContentType = responseContentType.Substring(0, responseContentType.IndexOf(";")); } if (response.StatusCode == HttpStatusCode.NotModified || response.StatusCode == HttpStatusCode.NoContent || typeof(TResult) == typeof(object)) { return(default(TResult)); } serializer = this.Description.Binding.ContentTypeMapper.GetSerializer(responseContentType, typeof(TResult)); TResult retVal = default(TResult); using (var ms = new MemoryStream()) { response.GetResponseStream().CopyTo(ms); ms.Seek(0, SeekOrigin.Begin); // Compression? switch (response.Headers[HttpResponseHeader.ContentEncoding]) { case "deflate": using (DeflateStream df = new DeflateStream(new NonDisposingStream(ms), CompressionMode.Decompress)) retVal = (TResult)serializer.DeSerialize(df); break; case "gzip": using (GZipStream df = new GZipStream(new NonDisposingStream(ms), CompressionMode.Decompress)) retVal = (TResult)serializer.DeSerialize(df); break; case "bzip2": using (var bzs = new BZip2Stream(new NonDisposingStream(ms), CompressionMode.Decompress, false)) retVal = (TResult)serializer.DeSerialize(bzs); break; case "lzma": using (var lzmas = new LZipStream(new NonDisposingStream(ms), CompressionMode.Decompress)) retVal = (TResult)serializer.DeSerialize(lzmas); break; default: retVal = (TResult)serializer.DeSerialize(ms); break; } } return(retVal); } finally { if (response != null) { response.Dispose(); } } } catch (TimeoutException e) { this.traceSource.TraceEvent(EventLevel.Error, "Request timed out:{0}", e); throw; } catch (WebException e) { this.traceSource.TraceEvent(EventLevel.Error, e.ToString()); // status switch (e.Status) { case WebExceptionStatus.ProtocolError: // Deserialize object errorResult = null; var errorResponse = (e.Response as HttpWebResponse); var responseContentType = errorResponse.ContentType; if (responseContentType.Contains(";")) { responseContentType = responseContentType.Substring(0, responseContentType.IndexOf(";")); } try { var serializer = this.Description.Binding.ContentTypeMapper.GetSerializer(responseContentType, typeof(TResult)); switch (errorResponse.Headers[HttpResponseHeader.ContentEncoding]) { case "deflate": using (DeflateStream df = new DeflateStream(new NonDisposingStream(errorResponse.GetResponseStream()), CompressionMode.Decompress)) errorResult = serializer.DeSerialize(df); break; case "gzip": using (GZipStream df = new GZipStream(new NonDisposingStream(errorResponse.GetResponseStream()), CompressionMode.Decompress)) errorResult = serializer.DeSerialize(df); break; case "bzip2": using (var bzs = new BZip2Stream(new NonDisposingStream(errorResponse.GetResponseStream()), CompressionMode.Decompress, false)) errorResult = serializer.DeSerialize(bzs); break; case "lzma": using (var lzmas = new LZipStream(new NonDisposingStream(errorResponse.GetResponseStream()), CompressionMode.Decompress)) errorResult = serializer.DeSerialize(lzmas); break; default: errorResult = serializer.DeSerialize(errorResponse.GetResponseStream()); break; } } catch { errorResult = new RestServiceFault(e); } if (errorResult is TResult) { throw new RestClientException <TResult>((TResult)errorResult, e, e.Status, e.Response); } else { throw new RestClientException <RestServiceFault>((RestServiceFault)errorResult, e, e.Status, e.Response); } default: throw; } } } responseHeaders = new WebHeaderCollection(); return(default(TResult)); }