public void SetupViewModel(FrontendContext frontendContext, IContent node, Frontends.Engines.Jit.ViewModels.NodeViewModel viewModel)
 {
     if (node.ContentItem.ContentType == "AssociativyTagNode")
     {
         viewModel.name = node.As<IAssociativyNodeLabelAspect>().Label;
     }
     else viewModel.name = node.As<ITitleAspect>().Title;
 }
Ejemplo n.º 2
0
        public void RaiseRawObjectEvents(object obj)
        {
            int? priority = int.MinValue;
            bool handled  = false;

            foreach (var frontend in Frontends.OrderByDescending(k => k.MiddlewareConfig?.Priority))
            {
                int?p = frontend.MiddlewareConfig?.Priority;
                if (p < priority && handled)
                {
                    break;
                }

                priority = frontend.MiddlewareConfig?.Priority;
                handled  = frontend.RawObject_Received(obj);
            }
        }
Ejemplo n.º 3
0
        /// <summary>
        /// <b>INTERNAL USE ONLY:</b> Returns the frontend corresponding to a cache warming target URI.
        /// </summary>
        /// <param name="target">The warming target.</param>
        /// <returns>The corresponding <see cref="TrafficHttpFrontend"/>"/> or <c>null</c> if there's no match.</returns>
        public TrafficHttpFrontend GetFrontendForWarmTarget(TrafficWarmTarget target)
        {
            // We'll match frontends on scheme, hostname, port, and longest path prefix.
            //
            // Select the candidate frontends that match on scheme, hostname, and port:

            var uri        = new Uri(target.Uri);
            var tls        = uri.Scheme.Equals("https", StringComparison.InvariantCultureIgnoreCase);
            var candidates = Frontends.Where(fe => fe.Tls == tls && fe.Host.Equals(uri.Host, StringComparison.InvariantCultureIgnoreCase) && fe.ProxyPort == uri.Port).ToList();

            // We're done if there's no or only one candidate.

            if (candidates.Count == 0)
            {
                return(null);
            }
            else if (candidates.Count == 1)
            {
                return(candidates.Single());
            }

            // There's more than one candidate, so we'll try to match the frontend based on the
            // the path prefix, longest prefixes first.

            foreach (var frontend in candidates.OrderByDescending(fe => (fe.PathPrefix ?? string.Empty).Length))
            {
                var frontendPrefix = frontend.PathPrefix ?? string.Empty;

                if (uri.AbsolutePath.StartsWith(frontendPrefix))
                {
                    return(frontend);
                }
            }

            // None of the prefixes matched.

            return(null);
        }
Ejemplo n.º 4
0
        /// <summary>
        /// Validates the rule.
        /// </summary>
        /// <param name="context">The validation context.</param>
        public override void Validate(TrafficValidationContext context)
        {
            base.Validate(context);

            Frontends = Frontends ?? new List <TrafficHttpFrontend>();
            Backends  = Backends ?? new List <TrafficHttpBackend>();

            if (Frontends.Count == 0)
            {
                context.Error($"Rule [{Name}] has does not define a frontend.");
            }

            if (!string.IsNullOrEmpty(CheckUri))
            {
                if (!Uri.TryCreate(CheckUri, UriKind.Relative, out var uri))
                {
                    context.Error($"Rule [{Name}] has invalid [{nameof(CheckUri)}={CheckUri}].");
                }
            }

            if (string.IsNullOrEmpty(CheckMethod) || CheckMethod.IndexOfAny(new char[] { ' ', '\r', '\n', '\t' }) != -1)
            {
                context.Error($"Rule [{Name}] has invalid [{nameof(CheckMethod)}={CheckMethod}].");
            }

            if (string.IsNullOrEmpty(CheckVersion))
            {
                CheckVersion = "1.0";
            }

            var regex = new Regex(@"^\d+\.\d+$");

            if (!regex.Match(CheckVersion).Success)
            {
                context.Error($"Rule [{Name}] has invalid [{nameof(CheckVersion)}={CheckVersion}].");
            }

            if (!string.IsNullOrEmpty(CheckHost) && !HiveDefinition.DnsHostRegex.Match(CheckHost).Success)
            {
                context.Error($"Rule [{Name}] has invalid [{nameof(CheckHost)}={CheckHost}].");
            }

            if (!string.IsNullOrEmpty(CheckExpect))
            {
                var error = $"Rule [{Name}] has invalid [{nameof(CheckExpect)}={CheckExpect}].";
                var value = CheckExpect.Trim();

                if (value.StartsWith("! "))
                {
                    value = value.Substring(2).Trim();
                }

                var pos = value.IndexOf(' ');

                if (pos == -1)
                {
                    context.Error(error + "  Expected: <match> <pattern>");
                }
                else
                {
                    var match   = value.Substring(0, pos);
                    var pattern = value.Substring(pos).Trim();

                    if (pattern.Replace("\\ ", string.Empty).IndexOf(' ') != -1)
                    {
                        context.Error(error + $"  Pattern [{pattern}] includes unescaped spaces.");
                    }

                    switch (match)
                    {
                    case "status":
                    case "string":

                        break;

                    case "rstatus":
                    case "rstring":

                        try
                        {
                            new Regex(pattern);
                        }
                        catch (Exception e)
                        {
                            context.Error(error + $"  Pattern regex [{pattern}] parsing error: {e.Message}.");
                        }
                        break;

                    default:

                        context.Error(error + "  Invalid [match], expected one of: status, rstatus, string, rstring");
                        break;
                    }
                }
            }

            foreach (var frontend in Frontends)
            {
                frontend.Validate(context, this);
            }

            foreach (var backend in Backends)
            {
                backend.Validate(context, this);
            }

            // Verify that the port/host combinations are unique for each frontend.

            var frontendMap = new HashSet <string>(StringComparer.OrdinalIgnoreCase);

            foreach (var frontend in Frontends)
            {
                if (string.IsNullOrEmpty(frontend.PathPrefix))
                {
                    var key = $"{frontend.Host}:{frontend.ProxyPort}";

                    if (frontendMap.Contains(key))
                    {
                        context.Error($"HTTP rule [{Name}] includes two or more frontends that map to [{key}].");
                    }

                    frontendMap.Add(key);
                }
            }

            foreach (var frontend in Frontends)
            {
                if (!string.IsNullOrEmpty(frontend.PathPrefix))
                {
                    var key = $"{frontend.Host}:{frontend.ProxyPort}{frontend.PathPrefix}";

                    if (frontendMap.Contains($"{frontend.Host}:{frontend.ProxyPort}") ||    // Ensure there's no *all* path frontend
                        frontendMap.Contains(key))
                    {
                        context.Error($"HTTP rule [{Name}] includes two or more frontends that map to [{key}].");
                    }

                    frontendMap.Add(key);
                }
            }

            if (Cache != null && Cache.Enabled)
            {
                Cache.Validate(context, this);

                // The Varnish open source release doesn't support TLS backends.  This requires
                // Varnish Plus (of course) which is very expensive.

                foreach (TrafficHttpBackend backend in Backends)
                {
                    if (backend.Tls)
                    {
                        context.Error($"HTTP rule [{Name}] cannot support caching because one or more backends required TLS.");
                        break;
                    }
                }

                // Varnish doesn't support comparing health probe status codes with a regex
                // like HAProxy does.  We're going to enforce having CheckExpect set to
                // something like "status 200".

                var statusFields = CheckExpect.Split(' ');

                if (statusFields.Length != 2 || statusFields[0] != "status" ||
                    !int.TryParse(statusFields[1], out var statusCode) ||
                    statusCode < 100 || 600 <= statusCode)
                {
                    context.Error($"HTTP rule [{Name}] cannot support caching because [{nameof(CheckExpect)}={CheckExpect}] doesn't specify a fixed status code like [status 200].  Varnish-Cache does not support verifying health probe status codes as regular expressions like HAProxy can.");
                }

                // $todo(jeff.lill):
                //
                // We need to enforce some restrictions due to Varnish limitations
                // described here:
                //
                //      https://github.com/jefflill/NeonForge/issues/379
                //
                // It would be nice to revisit this in the future.

                // Ensure that:
                //
                //      * If one backend has a hostname then it must be the only backend.
                //      * IP address and hostname backends cannot be mixed.

                if (Backends.Count > 1)
                {
                    var hasHostname  = false;
                    var hasIPAddress = false;

                    foreach (var backend in Backends)
                    {
                        if (IPAddress.TryParse(backend.Server, out var address))
                        {
                            hasIPAddress = true;
                        }
                        else
                        {
                            hasHostname = true;
                        }
                    }

                    if (hasIPAddress)
                    {
                        context.Error($"HTTP rule [{Name}] has multiple backends reachable via hostname which is not supported.  You may define only a single backend that requires a DNS lookup.");
                    }
                    else if (hasIPAddress && hasHostname)
                    {
                        context.Error($"HTTP rule [{Name}] has backends reachable via IP address and hostname which is not supported.  You cannot mix backends with IP address and hostnames in the same rule.");
                    }
                }

                // Ensure that all cache warming targets have schemes, hostnames, ports that
                // match a rule frontend, and that HTTP rules don't map to reserved HTTPS ports
                // and HTTPS rules don't map to reserved HTTP ports.

                foreach (var frontend in Frontends)
                {
                    if (frontend.Tls)
                    {
                        if (frontend.ProxyPort == HiveHostPorts.ProxyPublicHttp || frontend.ProxyPort == HiveHostPorts.ProxyPrivateHttp)
                        {
                            context.Error($"Rule [{Name}] has an HTTPS frontend with [{nameof(frontend.ProxyPort)}={frontend.ProxyPort}] that is incorrectly mapped to a reserved HTTP port.");
                        }
                    }
                    else
                    {
                        if (frontend.ProxyPort == HiveHostPorts.ProxyPublicHttps || frontend.ProxyPort == HiveHostPorts.ProxyPrivateHttps)
                        {
                            context.Error($"Rule [{Name}] has an HTTP frontend with [{nameof(frontend.ProxyPort)}={frontend.ProxyPort}] that is incorrectly mapped to a reserved HTTPS port.");
                        }
                    }
                }

                // Ensure that all cache warming targets have schemes, hostnames, and ports that
                // match a rule frontend.

                foreach (var warmTarget in Cache.WarmTargets)
                {
                    var uri = new Uri(warmTarget.Uri);
                    var tls = uri.Scheme.Equals("https", StringComparison.InvariantCultureIgnoreCase);

                    if (Frontends.IsEmpty(fe => fe.Tls == tls && fe.Host.Equals(uri.Host, StringComparison.InvariantCultureIgnoreCase) && fe.ProxyPort == uri.Port))
                    {
                        context.Error($"Cache warm target [{uri}] does not match one of the [{Name}] traffic manager frontends.");
                    }
                }
            }
        }