// Invoked once per request. public async Task Invoke(IOwinContext context) { using (var trace = new Trace(context.Request.Uri.PathAndQuery.ToString())) { string json, templateName, templateData; if (context.Request.Uri.PathAndQuery == "/favicon.ico") { context.Response.StatusCode = 404; await context.Response.WriteAsync(new byte[] { }); return; } try { // pass the same method through for all requests HttpMethod method = new HttpMethod(context.Request.Method); var uri = (HandlebarsProxyConfiguration.Instance.Scheme + "://" + HandlebarsProxyConfiguration.Instance.Domain + ":" + HandlebarsProxyConfiguration.Instance.DomainPort + GetProxyUri(context.Request.Uri)).ToLower(); HttpRequestMessage request = new HttpRequestMessage(method, new Uri(uri)); // keep the same user agent request.Headers.TryAddWithoutValidation("User-Agent", context.Request.Headers["User-Agent"]); // hack the cookies we do and don't want if (context.Request.Headers["Cookie"] != null && !string.IsNullOrEmpty(context.Request.Headers["Cookie"])) { request.Headers.TryAddWithoutValidation("Cookie", context.Request.Headers["Cookie"]); } // check if there is a response for this URI HttpResponseMessage cachedResponse = _store[uri] as HttpResponseMessage; if (cachedResponse != null && context.Request.Method == "GET") { // add an "IfNoneMatch" to the new request to validate this one. if (cachedResponse.Headers.ETag != null) { request.Headers.TryAddWithoutValidation("If-None-Match", cachedResponse.Headers.ETag.ToString()); } } byte[] data; HttpResponseMessage response; switch (context.Request.Method) { case "PUT": case "POST": using (var ms = new MemoryStream()) { context.Request.Body.CopyTo(ms); var originalFormContent = Encoding.UTF8.GetString(ms.ToArray()); var newFormContent = new FormUrlEncodedContent(originalFormContent.Split('&') .Select(_ => { var parts = _.Split('='); var kvp = new KeyValuePair <string, string>(Uri.UnescapeDataString(parts[0]), Uri.UnescapeDataString(parts[1])); return(kvp); })); request.Content = newFormContent; // request.Content.Headers.ContentType = new MediaTypeHeaderValue(context.Request.Headers["Content-Type"]); response = await _client.SendAsync(request); data = await response.Content.ReadAsByteArrayAsync(); if (response.Headers.Contains("Set-Cookie")) { var setCookie = response.Headers.GetValues("Set-Cookie").FirstOrDefault(); if (!string.IsNullOrEmpty(setCookie)) { var cookie = setCookie.ToString(); cookie = cookie.Replace("." + HandlebarsProxyConfiguration.Instance.Domain.Replace("www", "").Trim('.'), HandlebarsProxyConfiguration.Instance.Hostname); Console.WriteLine("Set Cookie: " + cookie); context.Response.Headers["Set-Cookie"] = cookie; } } } break; default: response = await _client.SendAsync(request); data = await response.Content.ReadAsByteArrayAsync(); break; } // use the cached version if (cachedResponse != null && response.StatusCode == HttpStatusCode.NotModified) { response = cachedResponse; data = await cachedResponse.Content.ReadAsByteArrayAsync(); await Console.Out.WriteLineAsync("Using Cached Response"); } // save the response in the cache else if (response.Headers.ETag != null && cachedResponse == null) { CacheItemPolicy policy = new CacheItemPolicy(); policy.AbsoluteExpiration = DateTimeOffset.Now.AddDays(1); _store.Set(request.RequestUri.ToString(), response, policy); } // set headers we need for compatibility in weird situations context.Response.Headers["Access-Control-Allow-Origin"] = "*"; context.Response.Headers["Access-Control-Allow-Methods"] = "GET,PUT,POST,DELETE"; context.Response.Headers["Access-Control-Allow-Headers"] = "Content-Type"; // if this is a redirect, pass it through var location = response.Headers.Location; if (location != null) { var locationUri = new Uri(location.ToString()); // locationUri.Host = HandlebarsProxyConfiguration.Instance.Hostname; // locationUri.Port = HandlebarsProxyConfiguration.Instance.Port; context.Response.Headers["Location"] = locationUri.PathAndQuery; } // if this is an API request, then pass it staight to the client, no template logic if (context.Request.Uri.PathAndQuery.StartsWith("/api") || !response.Content.Headers.ContentType.MediaType.Contains("application/json") || response.StatusCode == HttpStatusCode.Found || response.StatusCode == HttpStatusCode.Redirect || response.StatusCode == HttpStatusCode.TemporaryRedirect) { context.Response.StatusCode = (int)response.StatusCode; context.Response.ContentType = response.Content.Headers.ContentType.MediaType; await context.Response.WriteAsync(data); return; } // ok, now we apply some smarts, first things first, get the data as a JSON object json = Encoding.UTF8.GetString(data); var o = JObject.Parse(json); o["debug"] = true; // here we need to ensure that we are replacing the hostname with the configured local version // this means any logic around hostnames uses this o["_config"]["api"] = "http://" + HandlebarsProxyConfiguration.Instance.Hostname + ":" + HandlebarsProxyConfiguration.Instance.Port; o["_config"]["cdn"] = HandlebarsProxyConfiguration.Instance.ContentDeliveryNetwork; // "//cdn.archfashion.dev"; o["_request"]["protocol"] = "http"; o["_request"]["gzip"] = false; o["_request"]["fqdn"] = "http://" + HandlebarsProxyConfiguration.Instance.Hostname + ":" + HandlebarsProxyConfiguration.Instance.Port; o["_request"]["hostname"] = HandlebarsProxyConfiguration.Instance.Hostname + ":" + HandlebarsProxyConfiguration.Instance.Port; // Add an experiment with the variation as instructed by the request var qs = new UrlEncodingParser(context.Request.QueryString.ToUriComponent()); if (!string.IsNullOrEmpty(qs["experiment"]) && !string.IsNullOrEmpty(qs["alias"])) { JObject experiment = o["_experiment"] as JObject; if (experiment == null) { experiment = new JObject(); o["_experiment"] = experiment; } experiment.RemoveAll(); experiment.Add(qs["experiment"].Urlify(), JObject.Parse(@"{""id"":""rAnDoMlEtTeRs"",""variation"":0,""variation_alias"":""" + qs["alias"].Urlify() + @"""}")); } json = o.ToString(Formatting.None); // send the modified response back to the client (obviously the developer is // trying to work out what is being combined with the template) if (qs["x-format"] == "json") { context.Response.StatusCode = 200; context.Response.ContentType = response.Content.Headers.ContentType.MediaType; await context.Response.WriteAsync(json); return; } // get the header which has the handlebars template in it var xTemplate = response.Headers.GetValues("x-template").FirstOrDefault(); if (string.IsNullOrEmpty(xTemplate)) { await Console.Error.WriteLineAsync("No x-template header for URL: " + request.RequestUri.ToString()); await context.Response.WriteAsync("No x-template header for URL: " + request.RequestUri.ToString()); return; } // get the local template templateName = xTemplate.ToString(); var templatePath = Path.Combine(HandlebarsProxyConfiguration.Instance.Directory + "\\template", templateName.Replace("/", "\\") + ".handlebars"); // try different file extension if (!File.Exists(templatePath)) { templatePath = Path.Combine(HandlebarsProxyConfiguration.Instance.Directory + "\\template", templateName.Replace("/", "\\") + ".hbs"); } // get the data of the template templateData = File.ReadAllText(templatePath); // if this is file not found send a friendly message using (var render = new Trace("render")) { var r = _template.Render(templateName, json); var html = FillSectionData(r, json); var donuts = new List <string>(); var templates = new Dictionary <string, string>(); // detect the donuts int index = html.IndexOf("####donut:", 0, false); int length = 4; while (index != -1) { length = html.IndexOf("####", index + 10, false) - index - 10; donuts.Add(html.ToString(index + 10, length)); if (index + length > html.Length) { break; } index = html.IndexOf("####donut:", index + length, false); } // execute any donuts var sync = new object(); var tasks = new List <Task <KeyValuePair <string, string> > >(donuts.Count); if (donuts.Count > 0) { using (var handler = new WebRequestHandler() { UseCookies = false, AllowAutoRedirect = false }) using (HttpClient client = new HttpClient(handler, false) { BaseAddress = new Uri(HandlebarsProxyConfiguration.Instance.Scheme + "://" + HandlebarsProxyConfiguration.Instance.Domain + ":" + HandlebarsProxyConfiguration.Instance.DomainPort) }) { foreach (var donut in donuts) { var duri = new Uri((HandlebarsProxyConfiguration.Instance.Scheme + "://" + HandlebarsProxyConfiguration.Instance.Domain + ":" + HandlebarsProxyConfiguration.Instance.DomainPort + GetProxyUri(donut)).ToLower()); await Console.Out.WriteLineAsync("Getting Donut: " + duri.ToString()); HttpRequestMessage drequest = new HttpRequestMessage(HttpMethod.Get, duri); drequest.Headers.Authorization = _authHeader; drequest.Headers.TryAddWithoutValidation("User-Agent", context.Request.Headers["User-Agent"]); drequest.Headers.TryAddWithoutValidation("Cookie", context.Request.Headers["Cookie"]); // the donut response's don't have etags so instead compare the cookies // as changes to the cookie state will be what impacts the cache, eg: login and out // or experiment changes HttpResponseMessage dcachedResponse = _store[duri.ToString()] as HttpResponseMessage; if (dcachedResponse != null && (dcachedResponse.Headers.Contains("Cookie") && string.Join(",", dcachedResponse.RequestMessage.Headers.GetValues("Cookie").ToArray()) == string.Join(",", drequest.Headers.GetValues("Cookie").ToArray())) && (dcachedResponse.Headers.Contains("User-Agent") && string.Join(",", dcachedResponse.RequestMessage.Headers.GetValues("User-Agent").ToArray()) == string.Join(",", drequest.Headers.GetValues("User-Agent").ToArray())) ) { // OK, from here we can do the continue task without actually "dong anything" await Console.Out.WriteLineAsync("Cached Response: " + duri.ToString()); var dDataTask = dcachedResponse.Content.ReadAsByteArrayAsync(); Task.WaitAll(dDataTask); var doo = JObject.Parse(Encoding.UTF8.GetString(dDataTask.Result)); // within the handlebars environment replace the doo["_config"]["api"] = "http://" + HandlebarsProxyConfiguration.Instance.Hostname + ":" + HandlebarsProxyConfiguration.Instance.Port; doo["_config"]["cdn"] = HandlebarsProxyConfiguration.Instance.ContentDeliveryNetwork; doo["_request"]["protocol"] = "http"; doo["_request"]["gzip"] = false; doo["_request"]["fqdn"] = "http://" + HandlebarsProxyConfiguration.Instance.Hostname + ":" + HandlebarsProxyConfiguration.Instance.Port; doo["_request"]["hostname"] = HandlebarsProxyConfiguration.Instance.Hostname + ":" + HandlebarsProxyConfiguration.Instance.Port; try { doo.Remove("_experiment"); doo.Add("_experiment", o["_experiment"].ToString(Formatting.Indented)); } catch (Exception exp) { Console.Out.WriteLineAsync("Cached Donut Error: " + duri.ToString() + " : " + exp.Message); } var dxTemplate = donut; if (dcachedResponse.Headers.Contains("x-template")) { dxTemplate = dcachedResponse.Headers.GetValues("x-template").FirstOrDefault(); } var t = Task.FromResult(new KeyValuePair <string, string>(donut, _template.Render(dxTemplate, doo.ToString(Formatting.None)))); lock (sync) { tasks.Add(t); } } else { var t = client.SendAsync(drequest, CancellationToken.None) .ContinueWith(_ => { if (_.IsFaulted || !_.Result.IsSuccessStatusCode) { Console.Out.WriteLine("Live Donut Error: " + duri.ToString()); } else { CacheItemPolicy policy = new CacheItemPolicy(); policy.AbsoluteExpiration = DateTimeOffset.Now.AddHours(1); _store.Set(_.Result.RequestMessage.RequestUri.ToString(), _.Result, policy); } var tcontent = _.Result.Content.ReadAsStringAsync(); Task.WaitAll(tcontent); KeyValuePair <string, string> kvp; var dxTemplate = donut; if (_.Result.Headers.Contains("x-template")) { dxTemplate = _.Result.Headers.GetValues("x-template").FirstOrDefault(); } try { var doo = JObject.Parse(tcontent.Result); // within the handlebars environment replace the doo["_config"]["api"] = "http://" + HandlebarsProxyConfiguration.Instance.Hostname + ":" + HandlebarsProxyConfiguration.Instance.Port; doo["_config"]["cdn"] = HandlebarsProxyConfiguration.Instance.ContentDeliveryNetwork; doo["_request"]["protocol"] = "http"; doo["_request"]["gzip"] = false; doo["_request"]["fqdn"] = "http://" + HandlebarsProxyConfiguration.Instance.Hostname + ":" + HandlebarsProxyConfiguration.Instance.Port; doo["_request"]["hostname"] = HandlebarsProxyConfiguration.Instance.Hostname + ":" + HandlebarsProxyConfiguration.Instance.Port; try { doo.Remove("_experiment"); doo.Add("_experiment", o["_experiment"].ToString(Formatting.Indented)); } catch (Exception exp) { Console.Out.WriteLine("Live Donut Error: " + duri.ToString()); Console.WriteLine(exp.Message); Console.WriteLine(exp.StackTrace); } kvp = new KeyValuePair <string, string>(dxTemplate, _template.Render(donut, doo.ToString(Formatting.None))); } catch (JsonReaderException) { kvp = new KeyValuePair <string, string>(dxTemplate, tcontent.Result); } return(kvp); }); if (!t.IsFaulted) { lock (sync) { tasks.Add(t); } } } } try { Task.WaitAll(tasks.ToArray()); } catch (Exception exp) { Console.WriteLine("Awaiting Donuts: " + exp.Message); } } } // go through the sucessful tasks foreach (var task in tasks) { html.Replace("####donut:" + task.Result.Key + "####", task.Result.Value); } context.Response.ContentType = "text/html"; // make sure the temp partials are cleared _handlebars.Clear(); await context.Response.WriteAsync(html.ToString()); Console.WriteLine("Request Complete: " + context.Request.Uri.ToString()); return; } } catch (Exception exp) { // output any errors Console.Error.WriteLine(context.Request.Uri.ToString() + "\n" + exp.Message + "\n" + exp.StackTrace); // send them to the client as well Task.WaitAll(context.Response.WriteAsync(context.Request.Uri.ToString() + "\n" + exp.Message + "\n" + exp.StackTrace)); return; } } }
// Invoked once per request. public async Task Invoke(IOwinContext context) { using (var trace = new Trace(context.Request.Uri.PathAndQuery.ToString())) { string json, templateName, templateData; if (context.Request.Uri.PathAndQuery == "/favicon.ico") { context.Response.StatusCode = 404; await context.Response.WriteAsync(new byte[] { }); return; } try { // pass the same method through for all requests HttpMethod method = new HttpMethod(context.Request.Method); var uri = (HandlebarsProxyConfiguration.Instance.Scheme + "://" + HandlebarsProxyConfiguration.Instance.Domain + ":" + HandlebarsProxyConfiguration.Instance.DomainPort + GetProxyUri(context.Request.Uri)).ToLower(); HttpRequestMessage request = new HttpRequestMessage(method, new Uri(uri)); // keep the same user agent request.Headers.TryAddWithoutValidation("User-Agent", context.Request.Headers["User-Agent"]); // hack the cookies we do and don't want if (context.Request.Headers["Cookie"] != null && !string.IsNullOrEmpty(context.Request.Headers["Cookie"])) { request.Headers.TryAddWithoutValidation("Cookie", context.Request.Headers["Cookie"]); } // check if there is a response for this URI HttpResponseMessage cachedResponse = _store[uri] as HttpResponseMessage; if (cachedResponse != null && context.Request.Method == "GET") { // add an "IfNoneMatch" to the new request to validate this one. if (cachedResponse.Headers.ETag != null) request.Headers.TryAddWithoutValidation("If-None-Match", cachedResponse.Headers.ETag.ToString()); } byte[] data; HttpResponseMessage response; switch (context.Request.Method) { case "PUT": case "POST": using (var ms = new MemoryStream()) { context.Request.Body.CopyTo(ms); var originalFormContent = Encoding.UTF8.GetString(ms.ToArray()); var newFormContent = new FormUrlEncodedContent(originalFormContent.Split('&') .Select(_ => { var parts = _.Split('='); var kvp = new KeyValuePair<string, string>(Uri.UnescapeDataString(parts[0]), Uri.UnescapeDataString(parts[1])); return kvp; })); request.Content = newFormContent; // request.Content.Headers.ContentType = new MediaTypeHeaderValue(context.Request.Headers["Content-Type"]); response = await _client.SendAsync(request); data = await response.Content.ReadAsByteArrayAsync(); if (response.Headers.Contains("Set-Cookie")) { var setCookie = response.Headers.GetValues("Set-Cookie").FirstOrDefault(); if (!string.IsNullOrEmpty(setCookie)) { var cookie = setCookie.ToString(); cookie = cookie.Replace("." + HandlebarsProxyConfiguration.Instance.Domain .Replace("www.", "") .Replace("m.staging", "") .Replace("staging.", "") .Replace("m.archfashion.", "archfasion.") .Trim('.'), "." + HandlebarsProxyConfiguration.Instance.Hostname.Replace("www.", "")); cookie = cookie.Replace("; secure; HttpOnly", ""); //cookie = cookie.Replace("HttpOnly", ""); Console.WriteLine("Set Cookie: " + cookie); context.Response.Headers["Set-Cookie"] = cookie; } } } break; default: response = await _client.SendAsync(request); data = await response.Content.ReadAsByteArrayAsync(); break; } // use the cached version if (cachedResponse != null && response.StatusCode == HttpStatusCode.NotModified) { response = cachedResponse; data = await cachedResponse.Content.ReadAsByteArrayAsync(); await Console.Out.WriteLineAsync("Using Cached Response"); } // save the response in the cache else if (response.Headers.ETag != null && cachedResponse == null) { CacheItemPolicy policy = new CacheItemPolicy(); policy.AbsoluteExpiration = DateTimeOffset.Now.AddDays(1); _store.Set(request.RequestUri.ToString(), response, policy); } // set headers we need for compatibility in weird situations context.Response.Headers["Access-Control-Allow-Credentials"] = "true"; var origin = "http://" + HandlebarsProxyConfiguration.Instance.Hostname; if (context.Request.Headers.ContainsKey("origin")) origin = context.Request.Headers["origin"]; context.Response.Headers["Access-Control-Allow-Origin"] = origin; context.Response.Headers["Access-Control-Allow-Methods"] = "GET, POST, PUT, DELETE, OPTIONS"; context.Response.Headers["Access-Control-Allow-Headers"] = "X-Requested-With, Content-Type, withCredentials, Set-Cookie"; // if this is a redirect, pass it through var location = response.Headers.Location; if (location != null) { var locationUri = new Uri(location.ToString()); // locationUri.Host = HandlebarsProxyConfiguration.Instance.Hostname; // locationUri.Port = HandlebarsProxyConfiguration.Instance.Port; context.Response.Headers["Location"] = locationUri.PathAndQuery; } // if this is an API request, then pass it staight to the client, no template logic if (context.Request.Uri.PathAndQuery.StartsWith("/api") || !response.Content.Headers.ContentType.MediaType.Contains("application/json") || response.StatusCode == HttpStatusCode.Found || response.StatusCode == HttpStatusCode.Redirect || response.StatusCode == HttpStatusCode.TemporaryRedirect) { context.Response.StatusCode = (int)response.StatusCode; context.Response.ContentType = response.Content.Headers.ContentType.MediaType; await context.Response.WriteAsync(data); return; } // ok, now we apply some smarts, first things first, get the data as a JSON object json = Encoding.UTF8.GetString(data); var o = JObject.Parse(json); o["debug"] = true; // here we need to ensure that we are replacing the hostname with the configured local version // this means any logic around hostnames uses this o["_config"]["api"] = "http://" + HandlebarsProxyConfiguration.Instance.Hostname + ":" + HandlebarsProxyConfiguration.Instance.Port; o["_config"]["cdn"] = HandlebarsProxyConfiguration.Instance.ContentDeliveryNetwork; // "//cdn.archfashion.dev"; o["_request"]["protocol"] = "http"; o["_request"]["gzip"] = false; o["_request"]["fqdn"] = "http://" + HandlebarsProxyConfiguration.Instance.Hostname + ":" + HandlebarsProxyConfiguration.Instance.Port; o["_request"]["hostname"] = HandlebarsProxyConfiguration.Instance.Hostname + ":" + HandlebarsProxyConfiguration.Instance.Port; // Add an experiment with the variation as instructed by the request var qs = new UrlEncodingParser(context.Request.QueryString.ToUriComponent()); if (!string.IsNullOrEmpty(qs["experiment"]) && !string.IsNullOrEmpty(qs["alias"])) { JObject experiment = o["_experiment"] as JObject; if (experiment == null) { experiment = new JObject(); o["_experiment"] = experiment; } experiment.RemoveAll(); experiment.Add(qs["experiment"].Urlify(), JObject.Parse(@"{""id"":""rAnDoMlEtTeRs"",""variation"":0,""variation_alias"":""" + qs["alias"].Urlify() + @"""}")); } json = o.ToString(Formatting.None); // send the modified response back to the client (obviously the developer is // trying to work out what is being combined with the template) if (qs["x-format"] == "json") { context.Response.StatusCode = 200; context.Response.ContentType = response.Content.Headers.ContentType.MediaType; await context.Response.WriteAsync(json); return; } // get the header which has the handlebars template in it var xTemplate = response.Headers.GetValues("x-template").FirstOrDefault(); if (string.IsNullOrEmpty(xTemplate)) { await Console.Error.WriteLineAsync("No x-template header for URL: " + request.RequestUri.ToString()); await context.Response.WriteAsync("No x-template header for URL: " + request.RequestUri.ToString()); return; } // get the local template templateName = xTemplate.ToString(); var templatePath = Path.Combine(HandlebarsProxyConfiguration.Instance.Directory + "\\template", templateName.Replace("/", "\\") + ".handlebars"); // try different file extension if (!File.Exists(templatePath)) templatePath = Path.Combine(HandlebarsProxyConfiguration.Instance.Directory + "\\template", templateName.Replace("/", "\\") + ".hbs"); // get the data of the template templateData = File.ReadAllText(templatePath); // if this is file not found send a friendly message using (var render = new Trace("render")) { var r = _template.Render(templateName, json); var html = FillSectionData(r, json); var donuts = new List<string>(); var templates = new Dictionary<string, string>(); // detect the donuts int index = html.IndexOf("####donut:", 0, false); int length = 4; while (index != -1) { length = html.IndexOf("####", index + 10, false) - index - 10; donuts.Add(html.ToString(index + 10, length)); if (index + length > html.Length) break; index = html.IndexOf("####donut:", index + length, false); } // execute any donuts var sync = new object(); var tasks = new List<Task<KeyValuePair<string, string>>>(donuts.Count); if (donuts.Count > 0) { using (var handler = new WebRequestHandler() { UseCookies = false, AllowAutoRedirect = false }) using (HttpClient client = new HttpClient(handler, false) { BaseAddress = new Uri(HandlebarsProxyConfiguration.Instance.Scheme + "://" + HandlebarsProxyConfiguration.Instance.Domain + ":" + HandlebarsProxyConfiguration.Instance.DomainPort) }) { foreach (var donut in donuts) { var duri = new Uri((HandlebarsProxyConfiguration.Instance.Scheme + "://" + HandlebarsProxyConfiguration.Instance.Domain + ":" + HandlebarsProxyConfiguration.Instance.DomainPort + GetProxyUri(donut)).ToLower()); await Console.Out.WriteLineAsync("Getting Donut: " + duri.ToString()); HttpRequestMessage drequest = new HttpRequestMessage(HttpMethod.Get, duri); drequest.Headers.Authorization = _authHeader; drequest.Headers.TryAddWithoutValidation("User-Agent", context.Request.Headers["User-Agent"]); drequest.Headers.TryAddWithoutValidation("Cookie", context.Request.Headers["Cookie"]); // the donut response's don't have etags so instead compare the cookies // as changes to the cookie state will be what impacts the cache, eg: login and out // or experiment changes HttpResponseMessage dcachedResponse = _store[duri.ToString()] as HttpResponseMessage; if (dcachedResponse != null && (dcachedResponse.Headers.Contains("Cookie") && string.Join(",", dcachedResponse.RequestMessage.Headers.GetValues("Cookie").ToArray()) == string.Join(",", drequest.Headers.GetValues("Cookie").ToArray())) && (dcachedResponse.Headers.Contains("User-Agent") && string.Join(",", dcachedResponse.RequestMessage.Headers.GetValues("User-Agent").ToArray()) == string.Join(",", drequest.Headers.GetValues("User-Agent").ToArray())) ) { // OK, from here we can do the continue task without actually "dong anything" await Console.Out.WriteLineAsync("Cached Response: " + duri.ToString()); var dDataTask = dcachedResponse.Content.ReadAsByteArrayAsync(); Task.WaitAll(dDataTask); var doo = JObject.Parse(Encoding.UTF8.GetString(dDataTask.Result)); // within the handlebars environment replace the doo["_config"]["api"] = "http://" + HandlebarsProxyConfiguration.Instance.Hostname + ":" + HandlebarsProxyConfiguration.Instance.Port; doo["_config"]["cdn"] = HandlebarsProxyConfiguration.Instance.ContentDeliveryNetwork; doo["_request"]["protocol"] = "http"; doo["_request"]["gzip"] = false; doo["_request"]["fqdn"] = "http://" + HandlebarsProxyConfiguration.Instance.Hostname + ":" + HandlebarsProxyConfiguration.Instance.Port; doo["_request"]["hostname"] = HandlebarsProxyConfiguration.Instance.Hostname + ":" + HandlebarsProxyConfiguration.Instance.Port; try { doo.Remove("_experiment"); doo.Add("_experiment", o["_experiment"].ToString(Formatting.Indented)); } catch (Exception exp) { Console.Out.WriteLineAsync("Cached Donut Error: " + duri.ToString() + " : " + exp.Message); } var dxTemplate = donut; if (dcachedResponse.Headers.Contains("x-template")) dxTemplate = dcachedResponse.Headers.GetValues("x-template").FirstOrDefault(); var t = Task.FromResult(new KeyValuePair<string, string>(donut, _template.Render(dxTemplate, doo.ToString(Formatting.None)))); lock (sync) { tasks.Add(t); } } else { var t = client.SendAsync(drequest, CancellationToken.None) .ContinueWith(_ => { if (_.IsFaulted || !_.Result.IsSuccessStatusCode) { Console.Out.WriteLine("Live Donut Error: " + duri.ToString()); } else { CacheItemPolicy policy = new CacheItemPolicy(); policy.AbsoluteExpiration = DateTimeOffset.Now.AddHours(1); _store.Set(_.Result.RequestMessage.RequestUri.ToString(), _.Result, policy); } var tcontent = _.Result.Content.ReadAsStringAsync(); Task.WaitAll(tcontent); KeyValuePair<string, string> kvp; var dxTemplate = donut; if (_.Result.Headers.Contains("x-template")) dxTemplate = _.Result.Headers.GetValues("x-template").FirstOrDefault(); try { var doo = JObject.Parse(tcontent.Result); // within the handlebars environment replace the doo["_config"]["api"] = "http://" + HandlebarsProxyConfiguration.Instance.Hostname + ":" + HandlebarsProxyConfiguration.Instance.Port; doo["_config"]["cdn"] = HandlebarsProxyConfiguration.Instance.ContentDeliveryNetwork; doo["_request"]["protocol"] = "http"; doo["_request"]["gzip"] = false; doo["_request"]["fqdn"] = "http://" + HandlebarsProxyConfiguration.Instance.Hostname + ":" + HandlebarsProxyConfiguration.Instance.Port; doo["_request"]["hostname"] = HandlebarsProxyConfiguration.Instance.Hostname + ":" + HandlebarsProxyConfiguration.Instance.Port; try { doo.Remove("_experiment"); doo.Add("_experiment", o["_experiment"].ToString(Formatting.Indented)); } catch (Exception exp) { Console.Out.WriteLine("Live Donut Error: " + duri.ToString()); Console.WriteLine(exp.Message); Console.WriteLine(exp.StackTrace); } kvp = new KeyValuePair<string, string>(dxTemplate, _template.Render(donut, doo.ToString(Formatting.None))); } catch (JsonReaderException) { kvp = new KeyValuePair<string, string>(dxTemplate, tcontent.Result); } return kvp; }); if (!t.IsFaulted) { lock (sync) { tasks.Add(t); } } } } try { Task.WaitAll(tasks.ToArray()); } catch (Exception exp) { Console.WriteLine("Awaiting Donuts: " + exp.Message); } } } // go through the sucessful tasks foreach (var task in tasks) { await Console.Out.WriteLineAsync("Replacing Donut: " + task.Result.Key); html.Replace("####donut:" + task.Result.Key + "####", task.Result.Value); } context.Response.ContentType = "text/html"; // make sure the temp partials are cleared _handlebars.Clear(); await context.Response.WriteAsync(html.ToString()); Console.WriteLine("Request Complete: " + context.Request.Uri.ToString()); return; } } catch (Exception exp) { // output any errors Console.Error.WriteLine(context.Request.Uri.ToString() + "\n" + exp.Message + "\n" + exp.StackTrace); // send them to the client as well Task.WaitAll(context.Response.WriteAsync(context.Request.Uri.ToString() + "\n" + exp.Message + "\n" + exp.StackTrace)); return; } } }