使用 AppHarbor 处理 URL,无需修改我的所有控制器

发布于 2024-11-30 22:05:17 字数 1032 浏览 0 评论 0 原文

我正在尝试在 AppHarbor 上托管 MVC 3 应用程序 (FunnelWeb)。由于我仍然不清楚的原因,当我的路线只是控制器+操作时(例如,mysite/admin 是 Admin+Index,mysite/login 是 Admin+login),一切正常,但如果我的路线中有其他内容(例如像{*page}这样的变量)我的URL将是mysite:12345/mypage(其中12345是AppHarbor分配的端口号,mypage是我请求的页面的名称)。这使得请求失败,因为端口 12345 没有公开暴露。

AppHarbor 使用负载均衡在之间分配请求多个 IIS。这是他们做事的方式,这就是为什么在内部请求被路由到一些非标准端口。我对此没有任何问题,但我对尝试将我路由到该内部 URL 的 MVC 有问题。

我不是在这里指手画脚;这不是任何人的错:)所以让我们转向这个问题:

  1. 为什么仅使用 Controller+Action 请求路由与使用 {*page} 等变量请求路由之间存在差异?请讲技术:)
  2. 这里是一个如何在 AppHarbor 中处理请求的示例,但是,似乎它要求我修改我的所有控制器(OMG)。有没有办法在不修改我的控制器的情况下实现这一点?
  3. 欢迎任何其他建议:)

提前致谢。

更新:巧合的是,我观察到的行为与我得出的结论相符。但是,该问题与 ASP.Net MVC 路由无关。简而言之,FunnelWeb 强制使用小写 URL,因此,每当它收到对资源的​​请求时,它都会将其转换为小写(如果需要),并发出 301 响应。问题是,在为 301 响应创建 URL 时,请求 URL(绝对 URL)现在是从负载均衡器向 IIS 发出请求时使用的 URL,而不是从客户端发出的 URL;因此,请求失败。

I'm trying to host an MVC 3 application (FunnelWeb) on AppHarbor. For a reason that's still not clear to me, when my route is only a Controller+Action (e.g. mysite/admin is Admin+Index and mysite/login is Admin+login) everything works fine, but if I have anything else in the route (e.g. a variable like {*page}) my URL will be mysite:12345/mypage (where 12345 is a port number assigned by AppHarbor and mypage is the name of the page I'm requesting). This makes the request fail as the port 12345 is not publicly exposed.

AppHarbor uses load balancing to distribute the request between multiple IIS's. This is their way of doing stuff and this is why internally the requests are routed to some non-standard ports. I don't have a problem with that, but I have problem with MVC that tries to route me to that internal URL.

I'm not pointing fingers here; it's nobody's fault :) so let's move to the question:

  1. Why there is a difference between requesting a route with Controller+Action only and requesting a route with a variable like {*page}? Be technical please :)
  2. Here is an example of how to handle requests in AppHarbor, however, it seems that it requires me to modify all my controllers (OMG). Is there any way to implement this without modifying my controllers?
  3. Any other suggestions are welcomed :)

Thanks in advance.

UPDATE: Coincidentally, the behaviour that I observed matches the conclusion that I reached. However, the issue has nothing to do with ASP.Net MVC routing. The short story is, FunnelWeb forces lowercase URL's, so, whenever it receives a request to a resource it convert it to lowercase, if needed, and issue a 301 response. The problem is, when creating the URL for the 301 response, the request URL (absolute URL) is now the URL used when the request made from the load balancer to IIS and not the one made from the client; hence, the request fails.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

新一帅帅 2024-12-07 22:05:17

这是 AppHarbor 上 FunnelWeb url 生成的已知问题。当使用标准 MVC 方法生成相对 URL 时,这不是问题。 AppHarbor 有一个简短指南和示例关于如何在知识库中生成公共 URL

This is known issue with FunnelWeb url generation on AppHarbor. When using standard MVC methods to generate relative URLs, this is not a problem. AppHarbor has a short guide and sample on how the generate public URLs in the knowledge base.

最终幸福 2024-12-07 22:05:17

现在您可能需要以下内容:

<appSettings>
  <!-- AppHarbor Setting to stop AppHb load balancer internal port numbers from showing up in URLs-->
  <add key="aspnet:UseHostHeaderForRequestUrl" value="true" />
</appSettings>

这是 AppHarbor 支持页面上的更新,网址为 http://support.appharbor.com/kb/getting-started/workaround-for-generate-absolute-urls-without-port-number

MSDN 表示以下 关于 UseHostHeaderForRequestUrl

aspnet:UseHostHeaderForRequestUrl - 如果此值属性为 false [默认],则根据 Web 服务器提供的主机、端口和路径动态构建 Url 属性。如果此值属性为 true,则使用传入“Host”标头提供的主机和端口以及 Web 服务器提供的路径动态构建 Url 属性。

It's possible that the following is now all you need:

<appSettings>
  <!-- AppHarbor Setting to stop AppHb load balancer internal port numbers from showing up in URLs-->
  <add key="aspnet:UseHostHeaderForRequestUrl" value="true" />
</appSettings>

This is noted as an update on AppHarbor's support page at http://support.appharbor.com/kb/getting-started/workaround-for-generating-absolute-urls-without-port-number

MSDN says the following about UseHostHeaderForRequestUrl:

aspnet:UseHostHeaderForRequestUrl - If this value attribute is false [default], the Url property is dynamically built from the host, port, and path provided by the web server. If this value attribute is true, the Url property is dynamically built by using the host and port provided by the incoming "Host" header and the path provided by the web server.

鹿! 2024-12-07 22:05:17

有一种方法,但需要几个课程。

当 ASP.NET MVC 注册路由时,它定义了一个路由处理程序。该路由处理程序返回一个处理请求的 HTTP 处理程序。如果您使用返回自定义 HTTP 处理程序的自定义路由处理程序,则可以使用几个装饰器类重写 HTTP 上下文。

首先创建一个 HttpContextProxyHttpRequestProxy,它们派生自基类,并将所有方法和属性包装到内部实例。我已经提供了艰苦的工作

接下来创建装饰器,首先是 HTTP 上下文装饰器:

using System.Web;

public class HttpContextDecorator : HttpContextProxy
{
    public HttpContextDecorator(HttpContextBase innerHttpContext)
        : base(innerHttpContext)
    {
    }

    public override HttpRequestBase Request
    {
        get
        {
            return new HttpRequestDecorator(base.Request);
        }
    }
}

HTTP 请求装饰器:

using System;
using System.Web;

public class HttpRequestDecorator : HttpRequestProxy
{
    public HttpRequestDecorator(HttpRequestBase innerHttpRequest)
        : base(innerHttpRequest)
    {
    }

    public override bool IsSecureConnection
    {
        get
        {
            return string.Equals(Headers["X-Forwarded-Proto"], "https", StringComparison.OrdinalIgnoreCase);
        }
    }

    public override Uri Url
    {
        get
        {
            var url = base.Url;
            var urlBuilder = new UriBuilder(url);

            if (IsSecureConnection)
            {
                urlBuilder.Port = 443;
                urlBuilder.Scheme = "https";
            }
            else
            {
                urlBuilder.Port = 80;
            }

            return urlBuilder.Uri;
        }
    }

    public override string UserHostAddress
    {
        get
        {
            const string forwardedForHeader = "HTTP_X_FORWARDED_FOR";
            var forwardedFor = ServerVariables[forwardedForHeader];
            if (forwardedFor != null)
            {
                return forwardedFor;
            }

            return base.UserHostAddress;
        }
    }
}

如上所述,您还需要重写 MVC 类 - 这里是 HTTP 处理程序:

using System;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;

public class CustomMvcHandler : MvcHandler
{
    public CustomMvcHandler(RequestContext requestContext)
        : base(requestContext)
    {
        requestContext.HttpContext = new HttpContextDecorator(requestContext.HttpContext);
    }

    protected override IAsyncResult BeginProcessRequest(HttpContextBase httpContext, AsyncCallback callback, object state)
    {
        httpContext = new HttpContextDecorator(httpContext);
        return base.BeginProcessRequest(httpContext, callback, state);
    }

    protected override void ProcessRequest(HttpContextBase httpContext)
    {
        httpContext = new HttpContextDecorator(httpContext);
        base.ProcessRequest(httpContext);
    }
}

然后是路由处理程序:

using System.Web;
using System.Web.Mvc;
using System.Web.Routing;

public class CustomMvcRouteHandler : MvcRouteHandler
{
    protected override IHttpHandler GetHttpHandler(RequestContext requestContext)
    {
        return new CustomMvcHandler(requestContext);
    }
}

最后,您需要替换关联的处理程序所有已注册的路线(或从一开始就正确映射它们):

var routes = RouteTable.Routes.OfType<Route>().Where(x => x.RouteHandler is MvcRouteHandler);
foreach (var route in routes)
{
    route.RouteHandler = new CustomMvcRouteHandler();
}

There is a way, but it requires a couple of classes.

When ASP.NET MVC registers a route, it defines a route handler. This route handler returns a HTTP handler that handles the request. If you use a custom route handler that returns a custom HTTP handler, you can rewrite the HTTP context by using a couple decorator classes.

Start by creating a HttpContextProxy and HttpRequestProxy that derives from the base classes and wraps all methods and properties to an inner instance. I've made the hard work available.

Next create the decorators, first the HTTP context decorator:

using System.Web;

public class HttpContextDecorator : HttpContextProxy
{
    public HttpContextDecorator(HttpContextBase innerHttpContext)
        : base(innerHttpContext)
    {
    }

    public override HttpRequestBase Request
    {
        get
        {
            return new HttpRequestDecorator(base.Request);
        }
    }
}

The HTTP request decorator:

using System;
using System.Web;

public class HttpRequestDecorator : HttpRequestProxy
{
    public HttpRequestDecorator(HttpRequestBase innerHttpRequest)
        : base(innerHttpRequest)
    {
    }

    public override bool IsSecureConnection
    {
        get
        {
            return string.Equals(Headers["X-Forwarded-Proto"], "https", StringComparison.OrdinalIgnoreCase);
        }
    }

    public override Uri Url
    {
        get
        {
            var url = base.Url;
            var urlBuilder = new UriBuilder(url);

            if (IsSecureConnection)
            {
                urlBuilder.Port = 443;
                urlBuilder.Scheme = "https";
            }
            else
            {
                urlBuilder.Port = 80;
            }

            return urlBuilder.Uri;
        }
    }

    public override string UserHostAddress
    {
        get
        {
            const string forwardedForHeader = "HTTP_X_FORWARDED_FOR";
            var forwardedFor = ServerVariables[forwardedForHeader];
            if (forwardedFor != null)
            {
                return forwardedFor;
            }

            return base.UserHostAddress;
        }
    }
}

As mentioned, you also need to override the MVC classes - here the HTTP handler:

using System;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;

public class CustomMvcHandler : MvcHandler
{
    public CustomMvcHandler(RequestContext requestContext)
        : base(requestContext)
    {
        requestContext.HttpContext = new HttpContextDecorator(requestContext.HttpContext);
    }

    protected override IAsyncResult BeginProcessRequest(HttpContextBase httpContext, AsyncCallback callback, object state)
    {
        httpContext = new HttpContextDecorator(httpContext);
        return base.BeginProcessRequest(httpContext, callback, state);
    }

    protected override void ProcessRequest(HttpContextBase httpContext)
    {
        httpContext = new HttpContextDecorator(httpContext);
        base.ProcessRequest(httpContext);
    }
}

Then the route handler:

using System.Web;
using System.Web.Mvc;
using System.Web.Routing;

public class CustomMvcRouteHandler : MvcRouteHandler
{
    protected override IHttpHandler GetHttpHandler(RequestContext requestContext)
    {
        return new CustomMvcHandler(requestContext);
    }
}

Finally, you'll need to replace the associated handler for all registered routes (or map them properly from the beginning):

var routes = RouteTable.Routes.OfType<Route>().Where(x => x.RouteHandler is MvcRouteHandler);
foreach (var route in routes)
{
    route.RouteHandler = new CustomMvcRouteHandler();
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文