/// <summary> /// Set the HTTP headers in the response using values from /// the supplied flow data. /// If the supplied headers already have values in the response /// then they will be amended rather than replaced. /// </summary> /// <param name="context"> /// The <see cref="HttpContext"/> to set the response headers in /// </param> /// <param name="flowData"> /// The flow data containing the headers to set. /// </param> public static void SetHeaders(HttpContext context, IFlowData flowData) { if (context == null) { throw new ArgumentNullException(nameof(context)); } if (flowData == null) { throw new ArgumentNullException(nameof(flowData)); } var element = flowData.Pipeline.GetElement <ISetHeadersElement>(); foreach (var header in flowData.GetFromElement(element) .ResponseHeaderDictionary) { if (context.Response.Headers.ContainsKey(header.Key)) { context.Response.Headers.Append(header.Key, header.Value); } else { context.Response.Headers.Add(header.Key, header.Value); } } }
public void ValidateData(IFlowData data, bool validEvidence = true) { var elementData = data.GetFromElement(_engine); var dict = elementData.AsDictionary(); foreach (var property in _engine.Properties .Where(p => p.Available && // The JavascriptImageOptimiser property is deprecated. // It exists in the meta-data but is never populated // so we need to ignore it here. p.Name.Equals("JavascriptImageOptimiser", StringComparison.OrdinalIgnoreCase) == false)) { Assert.IsTrue(dict.ContainsKey(property.Name)); IAspectPropertyValue value = dict[property.Name] as IAspectPropertyValue; if (validEvidence) { Assert.IsTrue(value.HasValue); } else { if (property.Category.Equals("Device Metrics")) { Assert.IsTrue(value.HasValue); } else { Assert.IsFalse(value.HasValue); if (EvidenceContainsUserAgent(data) == false) { Assert.AreEqual("The evidence required to determine" + " this property was not supplied. The most" + " common evidence passed to this engine is" + " 'header.user-agent'.", value.NoValueMessage); } else { Assert.AreEqual("No matching profiles could be " + "found for the supplied evidence. A 'best " + "guess' can be returned by configuring more " + "lenient matching rules. See " + "https://51degrees.com/documentation/_device_detection__features__false_positive_control.html", value.NoValueMessage); } } } } Assert.IsTrue(string.IsNullOrEmpty(elementData.DeviceId.Value) == false); if (validEvidence == false) { Assert.AreEqual("0-0-0-0", elementData.DeviceId.Value); } var validKeys = data.GetEvidence().AsDictionary().Keys.Where( k => _engine.EvidenceKeyFilter.Include(k)).Count(); Assert.AreEqual(validKeys, elementData.UserAgents.Value.Count); }
public void ValidateProfileIds(IFlowData data, string[] profileIds) { var elementData = data.GetFromElement(_engine); var matchedProfiles = elementData.DeviceId.Value.Split('-'); foreach (var profileId in profileIds) { Assert.IsTrue(matchedProfiles.Contains(profileId), $"The profile '{profileId}' was not set in the result."); } }
/// <summary> /// Retrieve the raw JSON response from the /// <see cref="CloudRequestEngine"/> in this pipeline, extract /// the data for this specific engine and populate the /// <code>TData</code> instance accordingly. /// </summary> /// <param name="data"> /// The <see cref="IFlowData"/> to get the raw JSON data from. /// </param> /// <param name="aspectData"> /// The <code>TData</code> instance to populate with values. /// </param> /// <exception cref="ArgumentNullException"> /// Thrown if a required parameter is null. /// </exception> protected override void ProcessEngine(IFlowData data, T aspectData) { if (data == null) { throw new ArgumentNullException(nameof(data)); } CloudRequestData requestData; // Get requestData from CloudRequestEngine. If requestData does not // exist in the element data TypedKeyMap then the engine either // does not exist in the Pipeline or is not run before this engine. try { requestData = data.GetFromElement(RequestEngine.GetInstance()); } catch (KeyNotFoundException ex) { throw new PipelineConfigurationException( $"The '{GetType().Name}' requires a 'CloudRequestEngine' " + $"before it in the Pipeline. This engine will be unable " + $"to produce results until this is corrected.", ex); } // Check the requestData ProcessStarted flag which informs whether // the cloud request engine process method was called. if (requestData?.ProcessStarted == false) { throw new PipelineConfigurationException( $"The '{GetType().Name}' requires a 'CloudRequestEngine' " + $"before it in the Pipeline. This engine will be unable " + $"to produce results until this is corrected."); } var json = requestData?.JsonResponse; // If the JSON is empty or null then do not Process the CloudAspectEngine. // Empty or null JSON indicates that an error has occurred in the // CloudRequestEngine. The error will have been reported by the // CloudRequestEngine so just log a warning that this // CloudAspectEngine did not process. if (string.IsNullOrEmpty(json) == false) { ProcessCloudEngine(data, aspectData, json); } else { Logger.LogInformation($"The '{GetType().Name}' did not process " + $"as the JSON response from the CloudRequestEngine was null " + $"or empty. Please refer to errors generated by the " + $"CloudRequestEngine in the logs as this indicates an error " + $"occurred there."); } }
/// <summary> /// Retrieve the raw JSON response from the /// <see cref="CloudRequestEngine"/> in this pipeline, extract /// the data for this specific engine and populate the /// <code>TData</code> instance accordingly. /// </summary> /// <param name="data"> /// The <see cref="IFlowData"/> to get the raw JSON data from. /// </param> /// <param name="aspectData"> /// The <code>TData</code> instance to populate with values. /// </param> /// <exception cref="ArgumentNullException"> /// Thrown if a required parameter is null. /// </exception> protected override void ProcessEngine(IFlowData data, TData aspectData) { if (data == null) { throw new ArgumentNullException(nameof(data)); } var requestData = data.GetFromElement(RequestEngine.GetInstance()); var json = requestData?.JsonResponse; if (string.IsNullOrEmpty(json)) { throw new PipelineConfigurationException( $"Json response from cloud request engine is null. " + $"This is probably because there is not a " + $"'CloudRequestEngine' before the '{GetType().Name}' " + $"in the Pipeline. This engine will be unable " + $"to produce results until this is corrected."); } else { // Extract data from json to the aspectData instance. var dictionary = JsonConvert.DeserializeObject <Dictionary <string, object> >(json); // Access the data relating to this engine. var propertyKeyed = dictionary[ElementDataKey] as JObject; // Access the 'Profiles' property foreach (var entry in propertyKeyed["profiles"]) { // Iterate through the devices, parsing each one and // adding it to the result. var propertyValues = JsonConvert.DeserializeObject <Dictionary <string, object> >(entry.ToString(), new JsonSerializerSettings() { Converters = JSON_CONVERTERS, }); var device = CreateProfileData(); // Get the meta-data for properties on device instances. var propertyMetaData = Properties .Single(p => p.Name == "Profiles").ItemProperties; var deviceData = CreateAPVDictionary( propertyValues, propertyMetaData); device.PopulateFromDictionary(deviceData); //device.SetNoValueReasons(nullReasons); aspectData.AddProfile(device); } } }
/// <summary> /// Private method that checks if the result is already in the cache /// or not. /// If it is then the result is added to 'data', if not then /// <see cref="ProcessEngine(IFlowData, T)"/> is called to do so. /// </summary> /// <param name="data"> /// The <see cref="IFlowData"/> instance that provides the evidence /// and holds the result. /// </param> private void ProcessWithCache(IFlowData data) { T cacheResult = default(T); // If there is a cache then check if the result // is already in there. if (_cache != null) { try { cacheResult = (T)_cache[data]; } catch (InvalidCastException) { } } // If we don't have a result from the cache then // run through the normal processing. if (cacheResult == null) { // If the flow data already contains an entry for this // element's key then use it. Otherwise, create a new // aspect data instance and add it to the flow data. T aspectData = data.GetOrAdd(ElementDataKeyTyped, CreateElementData); if (aspectData.Engines.Contains(this) == false) { (aspectData as AspectDataBase).AddEngine(this); } // Start the engine processing if (LazyLoadingConfiguration != null) { // If lazy loading is configured then create a task // to do the processing and assign the task to the // aspect data property. var task = Task.Run(() => { ProcessEngine(data, aspectData); }); (aspectData as AspectDataBase).AddProcessTask(task); } else { // If not lazy loading, just start processing. ProcessEngine(data, aspectData); } // If there is a cache then add the result // of processing to the cache. if (_cache != null) { _cache.Put(data, data.GetFromElement(this)); } } else { // We have a result from the cache so add it // into the flow data. data.GetOrAdd(ElementDataKeyTyped, (f) => { return(cacheResult); }); } }
private void ServeContent(IContextAdapter context, IFlowData flowData, ContentType contentType) { if (context == null) { throw new ArgumentNullException(nameof(context)); } if (flowData == null) { throw new ArgumentNullException(nameof(flowData)); } context.Response.Clear(); context.Response.ClearHeaders(); // Get the hash code. var hash = flowData.GenerateKey(_pipeline.EvidenceKeyFilter).GetHashCode(); if (int.TryParse(context.Request.GetHeaderValue("If-None-Match"), out int previousHash) && hash == previousHash) { // The response hasn't changed so respond with a 304. context.Response.StatusCode = 304; } else { // Otherwise, return the requested content to the client. string content = null; switch (contentType) { case ContentType.JavaScript: var jsElement = flowData.Pipeline.GetElement <JavaScriptBuilderElement>(); if (jsElement == null) { throw new PipelineConfigurationException( Messages.ExceptionNoJavaScriptBuilder); } var jsData = flowData.GetFromElement(jsElement); content = jsData?.JavaScript; break; case ContentType.Json: var jsonElement = flowData.Pipeline.GetElement <JsonBuilderElement>(); if (jsonElement == null) { throw new PipelineConfigurationException( Messages.ExceptionNoJsonBuilder); } var jsonData = flowData.GetFromElement(jsonElement); content = jsonData?.Json; break; default: break; } int length = 0; if (content != null && content.Length > 0) { length = Encoding.UTF8.GetBytes(content).Length; } context.Response.StatusCode = 200; SetHeaders(context, hash.ToString(CultureInfo.InvariantCulture), length, contentType == ContentType.JavaScript ? "x-javascript" : "json"); context.Response.Write(content); } }