//----------------------------------------------------------------------------- // // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"). // You may not use this file except in compliance with the License. // A copy of the License is located at // // http://aws.amazon.com/apache2.0 // // or in the "license" file accompanying this file. This file is distributed // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either // express or implied. See the License for the specific language governing // permissions and limitations under the License. // //----------------------------------------------------------------------------- using System; using System.Collections.Generic; using System.Net; using System.Net.Http; using Amazon.Runtime.Internal.Util; using Amazon.XRay.Recorder.Core; using Amazon.XRay.Recorder.Core.Exceptions; using Amazon.XRay.Recorder.Core.Internal.Entities; namespace Amazon.XRay.Recorder.AutoInstrumentation.Utils { /// /// Utils for process http outgoing request and response. /// public static class HttpRequestUtil { private static readonly Logger _logger = Logger.GetLogger(typeof(HttpRequestUtil)); /// /// Collects information from and adds a tracing header to the request. /// internal static void ProcessRequest(WebRequest request) { ProcessRequest(request.RequestUri, request.Method, header => request.Headers.Add(TraceHeader.HeaderKey, header)); } /// /// Collects information from and adds a tracing header to the request. /// internal static void ProcessRequest(HttpRequestMessage request) { ProcessRequest(request.RequestUri, request.Method.Method, AddOrReplaceHeader); void AddOrReplaceHeader(string header) { request.Headers.Remove(TraceHeader.HeaderKey); request.Headers.Add(TraceHeader.HeaderKey, header); } } /// /// Collects information from the response and adds to instance. /// internal static void ProcessResponse(HttpWebResponse response) { if (response != null) { ProcessResponse(response.StatusCode, response.ContentLength); } } /// /// Collects information from the response and adds to instance. /// internal static void ProcessResponse(HttpResponseMessage response) { if (response != null) { ProcessResponse(response.StatusCode, response.Content.Headers.ContentLength); } } /// /// Process Request /// private static void ProcessRequest(Uri uri, string method, Action addHeaderAction) { if (AWSXRayRecorder.Instance.IsTracingDisabled()) { _logger.DebugFormat("Tracing is disabled. Not starting a subsegment on HTTP request."); return; } var recorder = AWSXRayRecorder.Instance; recorder.BeginSubsegment(uri.Host); recorder.SetNamespace("remote"); var requestInformation = new Dictionary { ["url"] = uri.AbsoluteUri, ["method"] = method }; recorder.AddHttpInformation("request", requestInformation); try { if (TraceHeader.TryParse(recorder.GetEntity(), out var header)) { addHeaderAction(header.ToString()); } } catch (EntityNotAvailableException e) { recorder.TraceContext.HandleEntityMissing(recorder, e, "Failed to get entity since it is not available in trace context while processing http request."); } } /// /// Process response /// private static void ProcessResponse(HttpStatusCode httpStatusCode, long? contentLength) { if (AWSXRayRecorder.Instance.IsTracingDisabled()) { _logger.DebugFormat("Tracing is disabled. Not ending a subsegment on HTTP response."); return; } var statusCode = (int)httpStatusCode; var responseInformation = new Dictionary { ["status"] = statusCode }; AgentUtil.MarkEntityFromStatus(statusCode); responseInformation["content_length"] = contentLength; AWSXRayRecorder.Instance.AddHttpInformation("response", responseInformation); } /// /// Process exception /// internal static void ProcessException(Exception exception) { AWSXRayRecorder.Instance.AddException(exception); } /// /// Process response /// internal static void ProcessResponse(HttpStatusCode httpStatusCode, long? contentLength, Subsegment subsegment) { AWSXRayRecorder.Instance.SetEntity(subsegment); ProcessResponse(httpStatusCode, contentLength); } /// /// Handles status code when an exception occurs /// internal static void HandleStatus(HttpStatusCode httpStatusCode, Subsegment subsegment) { ProcessResponse(httpStatusCode, null, subsegment); } /// /// End subsegment. /// internal static void EndSubsegment() { AWSXRayRecorder.Instance.EndSubsegment(); } /// /// End subsegment /// internal static void EndSubsegment(Subsegment subsegment) { AWSXRayRecorder.Instance.SetEntity(subsegment); AWSXRayRecorder.Instance.EndSubsegment(); } /// /// Check if Http out going request should be traced. /// Http out going request that is GetSamplingRules or GetSamplingTargets call will not be traced. /// Http out going request that is sent by AWS SDK will no be traced. /// internal static bool IsTraceable(HttpRequestMessage request) { if (request == null || request.RequestUri == null) { return false; } var url = request.RequestUri.ToString(); return !IsSamplingCall(url) && !IsAWSSDKRequest(); } internal static bool IsTraceable(HttpWebRequest request) { if (request == null || request.RequestUri == null) { return false; } var url = request.RequestUri.ToString(); return !IsSamplingCall(url) && !IsAWSSDKRequest(); } /// /// Check if it's a call for get sampling rules or sampling targets. /// private static bool IsSamplingCall(string url) { return url.Contains("GetSamplingRules") || url.Contains("SamplingTargets"); } /// /// Check if request is sent via AWS SDK handler. /// private static bool IsAWSSDKRequest() { try { var subsegment = AWSXRayRecorder.Instance.GetEntity() as Subsegment; if (subsegment == null || subsegment.Namespace == null) { return false; } return subsegment.Namespace.Equals("aws") && subsegment.IsInProgress; } catch (EntityNotAvailableException e) { AWSXRayRecorder.Instance.TraceContext.HandleEntityMissing(AWSXRayRecorder.Instance, e, "Failed to get entity since it is not available in trace context."); } return false; } } }