/// <summary> /// Sends the response to client. /// </summary> /// <param name="context">The context.</param> /// <param name="response">The response.</param> private void SendResponseToClient(HttpContext context, WebResponse response) { context.Response.ClearHeaders(); context.Response.ClearContent(); /* You cannot set any cache headers through the API, because it will prevent the use of the cache headers * added through the AppendHeader method. */ // add all the headers from the other proxied session to this request for (int i = 0; i < response.Headers.Count; i++) { string name = response.Headers.GetKey(i); // don't add any of these headers // don't check for restricted response headers because HttpContext doesn't seem to care if (name == "Server" || name == "X-Powered-By" || name == "Date" || name == "Host") { continue; } string[] values = response.Headers.GetValues(i); if (values.Length == 0) { continue; } if (name == "Location") { try { string location = values[0]; // make sure location is not empty before creating the URL if (!String.IsNullOrEmpty(location)) { // reminder: If location is an absolute URL, the Uri instance is created using only location. var requestLocationUrl = new Uri(RequestUrl, location); var responseLocationUrl = new UriBuilder(requestLocationUrl); // if the requested location for the host and port is the same as the requested URL we need to update them to the response if (Uri.Compare(requestLocationUrl, RequestUrl, UriComponents.SchemeAndServer, UriFormat.SafeUnescaped, StringComparison.OrdinalIgnoreCase) == 0) { responseLocationUrl.Port = ResponseUrl.Port; responseLocationUrl.Host = ResponseUrl.Host; responseLocationUrl.Scheme = ResponseUrl.Scheme; string path = responseLocationUrl.Path; int pathIndex = path.IndexOf(RequestUrl.AbsolutePath, StringComparison.OrdinalIgnoreCase); // since this is not redirecting to a different server try to replease the path if (pathIndex > -1) { path = path.Remove(pathIndex, RequestUrl.AbsolutePath.Length); path = path.Insert(pathIndex, ResponseUrl.AbsolutePath); responseLocationUrl.Path = path; } } context.Response.AppendHeader(name, responseLocationUrl.Uri.OriginalString); } } catch { /* do nothing on purpose */ } // nothing else can occure just continue processing from next header continue; } // if this is a chuncked response then we should send it correctly if (name == "Transfer-Encoding") { /* http://www.go-mono.com/docs/index.aspx?link=P%3aSystem.Web.HttpResponse.Buffer * * This controls whether HttpResponse should buffer the output before it is delivered to a * client. The default is true. * * The buffering can be changed during the execution back and forth if needed. Notice that * changing the buffering state will not flush the current contents held in the output buffer, * the contents will only be flushed out on the next write operation or by manually calling * System.Web.HttpResponse.Flush */ context.Response.BufferOutput = false; continue; } if (name == "Content-Type") { /* http://www.w3.org/Protocols/rfc1341/7_2_Multipart.html * http://en.wikipedia.org/wiki/Motion_JPEG#M-JPEG_over_HTTP * * The multipart/x-mixed-replace content-type should be treated as a streaming content and shouldn't be buffered. */ if (values[0].StartsWith("multipart/x-mixed-replace")) { context.Response.BufferOutput = false; } } // it is nessisary to get the values for headers that are allowed to specifiy // multiple values in an instance (i.e. Set-Cookie) foreach (string value in values) { context.Response.AppendHeader(name, value); } } Manager.Log(String.Format("Response is {0}being buffered", (context.Response.BufferOutput ? "" : "not ")), "Proxy"); // add the vanity url to the header Manager.TryToAddVanityHeader(new HttpContextWrapper(context)); // set all HTTP specific protocol stuff if (response is HttpWebResponse) { var httpResponse = response as HttpWebResponse; context.Response.StatusCode = (int)httpResponse.StatusCode; context.Response.StatusDescription = httpResponse.StatusDescription; Manager.Log(String.Format("Responding '{0} {1}'", ((int)httpResponse.StatusCode), httpResponse.StatusDescription), "Proxy"); } OnResponseToClient(context, response); int bufferSize = Manager.Configuration.Rewriter.Proxy.ResponseSize; // push the content out to through the stream using (var responseStream = response.GetResponseStream()) using (var bufferStream = new BufferedStream(responseStream, Manager.Configuration.Rewriter.Proxy.BufferSize)) { byte[] buffer = new byte[bufferSize]; try { while (true) { // make sure that the stream can be read from if (!bufferStream.CanRead) { break; } int bytesReturned = bufferStream.Read(buffer, 0, bufferSize); // if not bytes were returned the end of the stream has been reached // and the loop should exit if (bytesReturned == 0) { break; } // write bytes to the response context.Response.OutputStream.Write(buffer, 0, bytesReturned); } } catch (Exception exc) { Manager.Log("Error on response: " + exc.Message, "Proxy"); } } }
/// <summary> /// Sends the response to client. /// </summary> /// <param name="context">The context.</param> /// <param name="response">The response.</param> private async Task SendResponseToClient(HttpContext context, HttpResponseMessage response) { var hostHeader = context.Request.Headers.Get("Host"); context.Response.ClearHeaders(); context.Response.ClearContent(); /* You cannot set any cache headers through the API, because it will prevent the use of the cache headers * added through the AppendHeader method. */ // add all the headers from the other proxied session to this request foreach (var header in response.Headers) { var name = header.Key; // don't add any of these headers // don't check for restricted response headers because HttpContext doesn't seem to care if (name == "Server" || name == "X-Powered-By" || name == "Date" || name == "Host") { continue; } string[] values = header.Value.ToArray(); if (values.Length == 0) { continue; } if (name == "Location") { try { string location = values[0]; // make sure location is not empty before creating the URL if (!String.IsNullOrEmpty(location)) { // reminder: If location is an absolute URL, the Uri instance is created using only location. var requestLocationUrl = new Uri(RequestUrl, location); var responseLocationUrl = new UriBuilder(requestLocationUrl); // if the requested location for the host and port is the same as the requested URL we need to update them to the response if (Uri.Compare(requestLocationUrl, RequestUrl, UriComponents.SchemeAndServer, UriFormat.SafeUnescaped, StringComparison.OrdinalIgnoreCase) == 0) { responseLocationUrl.Port = ResponseUrl.Port; responseLocationUrl.Host = ResponseUrl.Host; responseLocationUrl.Scheme = ResponseUrl.Scheme; string path = responseLocationUrl.Path; int pathIndex = path.IndexOf(RequestUrl.AbsolutePath, StringComparison.OrdinalIgnoreCase); // since this is not redirecting to a different server try to replease the path if (pathIndex > -1) { path = path.Remove(pathIndex, RequestUrl.AbsolutePath.Length); path = path.Insert(pathIndex, ResponseUrl.AbsolutePath); responseLocationUrl.Path = path; } } context.Response.AppendHeader(name, responseLocationUrl.Uri.OriginalString); } } catch { /* do nothing on purpose */ } // nothing else can occure just continue processing from next header continue; } // if this is a chuncked response then we should send it correctly if (name == "Transfer-Encoding") { /* http://www.go-mono.com/docs/index.aspx?link=P%3aSystem.Web.HttpResponse.Buffer * * This controls whether HttpResponse should buffer the output before it is delivered to a * client. The default is true. * * The buffering can be changed during the execution back and forth if needed. Notice that * changing the buffering state will not flush the current contents held in the output buffer, * the contents will only be flushed out on the next write operation or by manually calling * System.Web.HttpResponse.Flush */ context.Response.BufferOutput = false; continue; } if (name == "Content-Type") { /* http://www.w3.org/Protocols/rfc1341/7_2_Multipart.html * http://en.wikipedia.org/wiki/Motion_JPEG#M-JPEG_over_HTTP * * The multipart/x-mixed-replace content-type should be treated as a streaming content and shouldn't be buffered. */ if (values[0].StartsWith("multipart/x-mixed-replace")) { context.Response.BufferOutput = false; } } // it is nessisary to get the values for headers that are allowed to specifiy // multiple values in an instance (i.e. Set-Cookie) foreach (string value in values) { context.Response.AppendHeader(name, value); } } context.Response.AppendHeader("Host", hostHeader); Manager.Log(String.Format("Response is {0}being buffered", (context.Response.BufferOutput ? "" : "not ")), "Proxy"); // add the vanity url to the header Manager.TryToAddVanityHeader(new HttpContextWrapper(context)); // set all HTTP specific protocol stuff context.Response.StatusCode = (int)response.StatusCode; // for not-modified responses the content type will not be returned so don't try and set it. if (response.Content.Headers.ContentType != null) { context.Response.ContentType = response.Content.Headers.ContentType.MediaType; } Manager.Log(String.Format("Responding '{0}'", ((int)response.StatusCode)), "Proxy"); await OnResponseToClient(context, response); await response.Content.CopyToAsync(context.Response.OutputStream); }
/// <summary> /// Runs the rules. /// </summary> /// <param name="httpContext">The HTTP context.</param> /// <param name="url">The URL.</param> /// <returns> /// Returns a rewritten <see cref="System.Uri"/>, or a value of <see langword="null"/> if no rewriting was done to <paramref name="url"/>. /// </returns> public Uri RunRules(HttpContextBase httpContext, Uri url) { var context = new RuleSetContext(this, url, httpContext); var currentUrl = url; if (!EngineEnabled) { Manager.LogIf(!EngineEnabled && LogLevel >= 9, "Rewrite Engine Is DISABLED", "Rewrite"); } if (_rules.Count > 0 && EngineEnabled) { Manager.LogIf(LogLevel >= 1, "**********************************************************************************"); Manager.LogIf(LogLevel >= 1, "Input: " + currentUrl, "Rewrite"); // check if max number of internal transfers have been exceeded if (InternalTransferCount(httpContext) > MaxInternalTransfers) { string message = "Exceeded the max number of internal transfers."; Manager.LogIf(LogLevel >= 1, message, "Error"); throw new HttpException(500, message); } var temporyFlags = (IRuleFlagProcessor)null; var skipNextChain = false; var initialUrl = currentUrl; if (!String.IsNullOrEmpty(VirtualBase) && VirtualBase != "/") { currentUrl = RemoveBase(VirtualBase, currentUrl); } // process rules according to their settings for (int i = 0; i < _rules.Count; i++) { var ruleContext = new RuleContext(i, context, currentUrl, _rules[i]); temporyFlags = _rules[i].Flags; // continue if this rule shouldn't be processed because it doesn't allow internal transfer requests if (RuleFlagsProcessor.HasNotForInternalSubRequests(temporyFlags) && IsInternalTransfer(httpContext)) { continue; } bool containsChain = RuleFlagsProcessor.HasChain(_rules[i].Flags); bool previousContainsChain = RuleFlagsProcessor.HasChain(_rules[Math.Max(0, i - 1)].Flags); // if the previous rule doesn't contain a chain flag then set the initial URL // this will be used to reset a chain if one of the chain rules fail if (!previousContainsChain) { initialUrl = currentUrl; } // skip if the current rule or the last rule has a chain flag // and if the skip next chain is set if (skipNextChain && (previousContainsChain || containsChain)) { continue; } else { skipNextChain = false; } if (_rules[i].TryExecute(ruleContext)) { var flagResponse = temporyFlags.Apply(ruleContext); currentUrl = ruleContext.SubstitutedUrl; i = ruleContext.RuleIndex ?? -1; bool breakLoop = false; // apply the flags to the rules, and only do special processing // for the flag responses listed in the switch statement below switch (flagResponse) { case RuleFlagProcessorResponse.ExitRuleSet: return(null); case RuleFlagProcessorResponse.LastRule: breakLoop = true; break; } // break the loop because we have reached the last rule as indicated by a flag if (breakLoop) { break; } } else if (containsChain) { skipNextChain = true; // reset the current URL back to the initial URL from the start of the chain currentUrl = initialUrl; } else if (previousContainsChain) { // reset the current URL back to the initial URL from the start of the chain currentUrl = initialUrl; } } // if the scheme, host, and ports do not match on the request vs the rewrite a redirect needs to be performed instead of a rewrite if (Uri.Compare(currentUrl, context.RequestedUrl, UriComponents.SchemeAndServer, UriFormat.SafeUnescaped, StringComparison.OrdinalIgnoreCase) != 0) { Manager.LogIf(LogLevel >= 1, "Output: 302 Redirect to " + currentUrl, "Rewrite"); Manager.Redirect(httpContext, "found", currentUrl); } if (!String.IsNullOrEmpty(VirtualBase) && VirtualBase != "/") { currentUrl = AddBase(VirtualBase, currentUrl); } Manager.LogIf(LogLevel >= 1, "Output: " + currentUrl, "Rewrite"); Manager.LogIf(LogLevel >= 1, "**********************************************************************************"); Manager.TryToAddXRewriteUrlHeader(httpContext); Manager.TryToAddVanityHeader(httpContext); } // if the http request url matches for both the request and the rewrite no work was done so the url should be null if (Uri.Compare(currentUrl, url, UriComponents.HttpRequestUrl, UriFormat.SafeUnescaped, StringComparison.OrdinalIgnoreCase) == 0) { currentUrl = null; } return(currentUrl); }