public void Process(IProcessedRedirect processedRedirect)
        {
            // skip processed redirect if it's invalid or contains one or more of skip result types
            if (!processedRedirect.ParsedRedirect.IsValid ||
                processedRedirect.Results
                .Select(r => r.Type)
                .Distinct()
                .Count(r => _skipResultTypes.Contains(r, StringComparer.OrdinalIgnoreCase)) > 0)
            {
                return;
            }

            var checkRedirect    = false;
            var isCyclicRedirect = false;
            var redirectCount    = -1;
            var urlsIndex        = new HashSet <string>();
            var redirectsVisited = new List <IParsedRedirect>();

            string url    = null;
            string newUrl =
                processedRedirect.ParsedRedirect.OldUrl.Formatted;
            string            lastVisitedUrl;
            UrlResponseResult urlResponseResult = null;

            var urlsVisited = new List <string>(new [] { newUrl });

            do
            {
                url    = newUrl;
                newUrl = null;

                var urlFormatted = _urlHelper.FormatUrl(url);

                redirectCount++;
                checkRedirect = false;

                // get response from url
                HttpResponse response;
                if (_responseCache.ContainsKey(urlFormatted))
                {
                    // get response from cache
                    response = _responseCache[urlFormatted];
                }
                else
                {
                    // get response from url
                    response = _httpClient.Get(urlFormatted);

                    // add response to cache
                    _responseCache.Add(urlFormatted, response);
                }

                lastVisitedUrl = url;

                // set has redirect and url to response location,
                // if url returns 301 and has location
                if (response != null)
                {
                    var statusCode = response.StatusCode;

                    string locationUrl;
                    if (response.Headers.ContainsKey("Location"))
                    {
                        var location = response.Headers["Location"] ?? string.Empty;

                        locationUrl = !Regex.IsMatch(
                            location ?? string.Empty, "^https?://", RegexOptions.IgnoreCase | RegexOptions.Compiled)
                            ? _urlHelper.Combine(url, location)
                            : location ?? string.Empty;
                    }
                    else
                    {
                        locationUrl = string.Empty;
                    }

                    urlResponseResult = new UrlResponseResult
                    {
                        Type    = ResultTypes.UrlResponse,
                        Message = string.Format(
                            "Url '{0}' returned response with status code '{1}'",
                            url,
                            statusCode),
                        Url        = url,
                        StatusCode = statusCode,
                        Location   = locationUrl
                    };

                    switch (response.StatusCode)
                    {
                    case 301:
                    case 302:
                        // url returns 301 or 302
                        // update redirect with url from location
                        newUrl = locationUrl;
                        break;

                    case 404:
                        // url returns 404, check if a redirect exists
                        checkRedirect = true;
                        break;

                    default:
                        // urls not returning 301 or 404 are considered a url with a response
                        // stop redirecting
                        url = null;
                        break;
                    }
                }
                else
                {
                    urlResponseResult = null;
                }

                // check redirect for url
                var matchingRedirectResult = GetMatchingRedirect(urlFormatted);
                if (checkRedirect &&
                    matchingRedirectResult.HasMatch)
                {
                    redirectsVisited.Add(
                        matchingRedirectResult.ParsedRedirect);

                    if (matchingRedirectResult.ResultRedirectType == RedirectType.Replace)
                    {
                        // update redirect with new url from replaced redirect
                        newUrl = _redirectHelper.Replace(urlFormatted,
                                                         matchingRedirectResult.ParsedRedirect);
                    }
                    else
                    {
                        // update redirect with new url from existing redirect
                        newUrl = matchingRedirectResult.ParsedRedirect.NewUrl.Formatted;
                    }
                }

                // add new url to urls visited, if not null
                if (!string.IsNullOrWhiteSpace(newUrl))
                {
                    urlsVisited.Add(newUrl);
                }

                // cyclic redirect, if url and new url is not https redirect and url exists in url index
                if (newUrl != null && !_urlHelper.IsHttpsRedirect(
                        url,
                        newUrl) &&
                    urlsIndex.Contains(_urlHelper.FormatUrl(newUrl)))
                {
                    isCyclicRedirect = true;
                    break;
                }

                // add formatted url to urls index, if it doesn't exist
                if (!urlsIndex.Contains(urlFormatted))
                {
                    urlsIndex.Add(urlFormatted);
                }
            } while (!string.IsNullOrWhiteSpace(newUrl) &&
                     redirectCount < 20);

            // add url response result, if it's defined
            if (urlResponseResult != null)
            {
                processedRedirect.Results.Add(
                    urlResponseResult);
                _results.Add(urlResponseResult);
            }

            if (isCyclicRedirect)
            {
                // add cyclic redirect result
                var cyclicResult = new RedirectResult
                {
                    Type    = ResultTypes.CyclicRedirect,
                    Message =
                        string.Format(
                            "Cyclic redirect at url '{0}'",
                            lastVisitedUrl),
                    Url              = lastVisitedUrl,
                    RedirectCount    = redirectCount,
                    UrlsVisited      = urlsVisited,
                    RedirectsVisited = redirectsVisited
                };
                processedRedirect.Results.Add(
                    cyclicResult);
                _results.Add(cyclicResult);
            }
            else if (redirectCount >= _configuration.MaxRedirectCount)
            {
                // add too many redirects result as redirect count is
                // higher then max redirect count
                var tooManyRedirectsResult = new RedirectResult
                {
                    Type    = ResultTypes.TooManyRedirects,
                    Message = string.Format(
                        "Too many redirect at url '{0}' exceeding max redirect count of {1}",
                        lastVisitedUrl,
                        _configuration.MaxRedirectCount),
                    Url              = lastVisitedUrl,
                    RedirectCount    = redirectCount,
                    UrlsVisited      = urlsVisited,
                    RedirectsVisited = redirectsVisited
                };
                processedRedirect.Results.Add(
                    tooManyRedirectsResult);
                _results.Add(
                    tooManyRedirectsResult);
            }
            else if (redirectCount > 1 && redirectCount < _configuration.MaxRedirectCount)
            {
                // add optimized redirect result as redirect count is higher than 1
                // and less than max redirect count
                var optimizedRedirectResult = new RedirectResult
                {
                    Type    = ResultTypes.OptimizedRedirect,
                    Message = string.Format(
                        "Optimized redirect to url '{0}'",
                        lastVisitedUrl),
                    Url              = lastVisitedUrl,
                    RedirectCount    = redirectCount,
                    UrlsVisited      = urlsVisited,
                    RedirectsVisited = redirectsVisited
                };
                processedRedirect.Results.Add(
                    optimizedRedirectResult);
                _results.Add(
                    optimizedRedirectResult);
            }
        }
        public void RedirectProcessorCachesResponse()
        {
            var configuration =
                TestData.TestData.DefaultConfiguration;
            var urlParser      = new UrlParser();
            var urlFormatter   = new UrlFormatter();
            var redirectParser = new RedirectParser(
                configuration,
                urlParser,
                urlFormatter);

            // create redirect processor
            var testHttpClient =
                new TestHttpClient();
            var redirectProcessor = new RedirectProcessor(
                configuration,
                new UrlHelper(
                    configuration,
                    urlParser,
                    urlFormatter),
                testHttpClient,
                urlParser,
                urlFormatter,
                new RedirectHelper(
                    configuration,
                    urlParser,
                    urlFormatter));

            // create and parse redirects
            var redirects = new List <IRedirect>
            {
                new Redirect
                {
                    OldUrl = "/url1",
                    NewUrl = "/url3"
                },
                new Redirect
                {
                    OldUrl = "/url2",
                    NewUrl = "/url3"
                }
            };
            var parsedRedirects = new List <IParsedRedirect>();

            foreach (var redirect in redirects)
            {
                parsedRedirects.Add(
                    redirectParser.ParseRedirect(
                        redirect));
            }

            // preload parsed redirects
            redirectProcessor.PreloadParsedRedirects(
                parsedRedirects);

            // verify controlled http client doesn't have any responses
            Assert.AreEqual(
                0,
                testHttpClient.Responses.Count);

            // process redirects and verify responses are cached by overriding responses
            UrlResponseResult urlResponseResult = null;
            var processedRedirects = new List <IProcessedRedirect>();

            foreach (var parsedRedirect in parsedRedirects)
            {
                var processedRedirect = new ProcessedRedirect
                {
                    ParsedRedirect = parsedRedirect
                };

                redirectProcessor.Process(
                    processedRedirect);

                // get url response result, if url response result is null and
                // controlled http client has a response for old url
                if (urlResponseResult == null &&
                    testHttpClient.Responses.ContainsKey(
                        parsedRedirect.NewUrl.Formatted))
                {
                    urlResponseResult = processedRedirect.Results
                                        .FirstOrDefault(r => r.Type.Equals(
                                                            ResultTypes.UrlResponse)) as UrlResponseResult;
                }
                else
                {
                    // override response with forbidden status code
                    testHttpClient.Responses[
                        parsedRedirect.NewUrl.Formatted] =
                        new HttpResponse
                    {
                        StatusCode = 401
                    };
                }
            }

            // verify url response result for /url3 has status code ok and not forbidden
            Assert.IsNotNull(
                urlResponseResult);
            Assert.AreEqual(
                "http://www.test.local/url3",
                urlResponseResult.Url);
            Assert.AreEqual(
                404,
                urlResponseResult.StatusCode
                );
        }