2012-03-08 12 views
11

Zauważyłem na skrzypcach, że [Wymagaj. Https] robi kod 302 przekierowań zamiast 301. Nie jestem pewien jak to ma sens ...Czy [RequireHttps] w MVC nie powinno wykonywać stałego przekierowania 301? Dlaczego robi 302 (zła dla SEO?)

Jeśli jesteś mówiąc, że kontroler [RequireHttps], to nigdy nie chcesz, aby ludzie odwiedzali wersję Http tej strony. Dlaczego więc nie jest to stałe przekierowanie ... mówiące wyszukiwarkom "proszę zaktualizuj swoje linki na stałe do wersji https tej strony".

Jeśli to ma sens, i mam rację, czy istnieje sposób, aby zmienić go na przekierowanie 301?

+1

http://webmasters.stackexchange.com/questions/22268/when-redirecting-from-http-to-https-in-a-shop-site-which-status-code-should-iu – Hupperware

Odpowiedz

7

Wygląda na to, że wybór opcji 302 powyżej 301 był nieco arbitralny. Jednak niekoniecznie wynika to z tego, że każdy URL będzie "musiał" wykorzystywać schemat HTTPS. Bardzo dobrze może być strona, która pozwala na dostęp zarówno z HTTP, jak i HTTPS, nawet jeśli może to zachęcić. Implementacja, w której może wystąpić taka sytuacja, może zawierać pewien kod w celu określenia, czy używać HTTPS na podstawie pewnych specjalnych kryteriów.

Jako scenariusz przyjrzyj się Gmailowi. W ramach ustawień można zezwolić lub zabronić protokołu HTTPS w dużych częściach aplikacji. Który kod powinien zostać zwrócony? 301 nie byłby dokładny, ponieważ nie jest "trwały" ... tylko zmiana na żądanie użytkownika. Niestety, 302 nie jest całkiem dokładny, albo dlatego, że błąd 302 implikuje, że istnieje zamiar zmiany łącza w pewnym momencie w przyszłości (odnośny odnośnik http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html).

Gmail jest bardzo trudnym przykładem, ponieważ niektóre części witryny, które umożliwiają tę opcję, zazwyczaj nie są indeksowane przez wyszukiwarkę, ale istnieje taka możliwość.

Aby odpowiedzieć na ostatnie pytanie, jeśli chcesz mieć inny kod statusu w ASP.NET MVC (który zakładam, że używasz z niewielkiego przykładu składni), można to zmienić za pomocą prostego, niestandardowego atrybutu:

public class MyRequireHttpsAttribute : RequireHttpsAttribute 
{ 
    public override void OnAuthorization(AuthorizationContext filterContext) 
    { 
     base.OnAuthorization(filterContext); 

     if (!filterContext.HttpContext.Request.IsSecureConnection) 
      filterContext.HttpContext.Response.StatusCode = (int)HttpStatusCode.MovedPermanently; 
    } 
} 

Teraz wszystkie działania, które implementują atrybut, powinny zwracać kod stanu 301 po uzyskaniu dostępu za pośrednictwem protokołu HTTP.

+3

Atrybut RequireHttps nie jest atrybutem, który pozostawia "wybór" w tej sprawie. Zasadniczo mówi się, że * MUSI BYĆ HTTPS - HTTP IS WRONG *, ponieważ nie można uzyskać dostępu do witryny przez HTTP. Dlatego uważam, że na początku powinno być 301 ... ale twój kod jest na miejscu i doceniam tę dyskusję. Zrobię trochę więcej badań, zanim zdecyduję się zamienić go na 301 ... jestem trochę nowy w SEO. Dzięki! –

+1

Dobrze, i zgadzam się z tym punktem w atrybucie RequireHttps specjalnie. Bardziej starałem się uzyskać szerszy obraz tego, jak nie jest to jednoznacznie konieczne w konkretnym zastosowaniu poza kontekstem konkretnej struktury. Cieszę się, że kod pomógł! – Dulan

+4

Niestety to nie zadziałało ... nadal robi 302. Próbowałem z nim rozmawiać w inny sposób ... ale wciąż nie mogłem przesłonić RequireHttps i zrobić to 301. Zamiast tego właśnie stworzyłem własne [RequireHttpsPerm] i robię to przekierowanie. –

4

Rozwiązanie Dulana postawiło mnie na właściwej ścieżce, ale próbka kodu nie zatrzymała przekierowania 302 z podstawowej implementacji RequireHttpsAttribute. Tak więc sprawdziłem kod RequireHttpsAttribute i zhakowałem go. Oto co wymyśliłem: odpowiedź

using System.Net; 
using System.Web.Mvc; 
using System; 
using System.Diagnostics.CodeAnalysis; 

[SuppressMessage("Microsoft.Performance", "CA1813:AvoidUnsealedAttributes", Justification = "Unsealed because type contains virtual extensibility points.")] 
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)] 
public class RequireHttps301Attribute : FilterAttribute, IAuthorizationFilter 
{ 

    public virtual void OnAuthorization(AuthorizationContext filterContext) 
    { 
     if (filterContext == null) { 
      throw new ArgumentNullException("filterContext"); 
     } 

     if (!filterContext.HttpContext.Request.IsSecureConnection) { 
      HandleNonHttpsRequest(filterContext); 
     } 
    } 

    protected virtual void HandleNonHttpsRequest(AuthorizationContext filterContext) 
    { 
     // only redirect for GET requests, otherwise the browser might not propagate the verb and request 
     // body correctly. 

     if (!String.Equals(filterContext.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase)) { 
      throw new InvalidOperationException("Only redirect for GET requests, otherwise the browser might not propagate the verb and request body correctly."); 
     } 

     // redirect to HTTPS version of page 
     string url = "https://" + filterContext.HttpContext.Request.Url.Host + filterContext.HttpContext.Request.RawUrl; 
     //what mvc did to redirect as a 302 
     //filterContext.Result = new RedirectResult(url); 

     //what I did to redirect as a 301 
     filterContext.HttpContext.Response.StatusCode = (int)HttpStatusCode.MovedPermanently; 
     filterContext.HttpContext.Response.RedirectLocation = url; 
    } 
} 
+1

Wierzę, że z MVC 4+ można użyć konstruktora 'RedirectResult', który pobiera 2 argumenty i wymusza 301:' filterContext.Result = new RedirectResult (url, true); ' –

8

DuLan jest blisko, ale to nie działa, przynajmniej z naszego rozwiązania MVC 4+. Ale po kilku próbach i błędach sprawiliśmy, że pracujemy z 301 zamiast z 302. Oto nowa klasa:

public class CustomRequireHttpsAttribute : RequireHttpsAttribute 
{ 
    public override void OnAuthorization(AuthorizationContext filterContext) 
    { 
     #if !DEBUG 
     base.OnAuthorization(filterContext); 

     if (!filterContext.HttpContext.Request.IsSecureConnection) 
     { 
      string url = "https://" + filterContext.HttpContext.Request.Url.Host + filterContext.HttpContext.Request.RawUrl; 
      filterContext.Result = new RedirectResult(url, true); 
     } 
     #endif 
    } 
} 

Powodem odpowiedź DuLan za nie działa wydaje się być fakt, że własność filterContext.Result Permanent jest tylko do odczytu i można ustawić tylko wtedy, gdy RedirectResult() nazywa, a problem jest ten RedirectResult() jest wywoływany w metodzie base.OnAuthorization(). Po prostu wywołaj metodę bazową, a następnie zmień wartość parametru filterContext.Result za pomocą drugiego parametru parametru true, aby wynik był stały. Po wykonaniu tej czynności zaczęliśmy wyświetlać 301 kodów w Fiddler2.

1

Po prostu krótka uwaga, że ​​RequireHttpsAttribute powoduje również wyjątek InvalidOperationException, jeśli żądanie to nie jest tylko żądaniem GET. Najlepszym rozwiązaniem jest zwrócenie metody 405, która nie jest dozwolona, ​​co jest znacznie bardziej odpowiednim błędem.

W mojej implementacji poniżej podaję również użytkownikowi atrybutu opcję, czy chce on przekierować na stałe (301), czy tymczasowo (302). Jak powiedział @Dulan, powinieneś wykonać stałe przekierowanie 301, jeśli strona jest dostępna tylko przez HTTPS i 302 tymczasowe przekierowanie, jeśli strona jest dostępna przez HTTP lub HTTPS.

/// <summary> 
/// Represents an attribute that forces an unsecured HTTP request to be re-sent over HTTPS. 
/// <see cref="System.Web.Mvc.RequireHttpsAttribute"/> performs a 302 Temporary redirect from a HTTP URL to a HTTPS URL. 
/// This filter gives you the option to perform a 301 Permanent redirect or a 302 temporary redirect. 
/// You should perform a 301 permanent redirect if the page can only ever be accessed by HTTPS and a 302 temporary redirect if 
/// the page can be accessed over HTTP or HTTPS. 
/// </summary> 
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = false)] 
public class RedirectToHttpsAttribute : FilterAttribute, IAuthorizationFilter 
{ 
    private readonly bool permanent; 

    /// <summary> 
    /// Initializes a new instance of the <see cref="RedirectToHttpsAttribute"/> class. 
    /// </summary> 
    /// <param name="permanent">if set to <c>true</c> the redirection should be permanent; otherwise, <c>false</c>.</param> 
    public RedirectToHttpsAttribute(bool permanent) 
    { 
     this.permanent = permanent; 
    } 

    /// <summary> 
    /// Gets a value that indicates whether the redirection should be permanent. 
    /// </summary> 
    /// <value> 
    /// <c>true</c> if the redirection should be permanent; otherwise, <c>false</c>. 
    /// </value> 
    public bool Permanent 
    { 
     get { return this.permanent; } 
    } 

    /// <summary> 
    /// Determines whether a request is secured (HTTPS) and, if it is not, calls the <see cref="HandleNonHttpsRequest"/> method. 
    /// </summary> 
    /// <param name="filterContext">An object that encapsulates information that is required in order to use the <see cref="System.Web.Mvc.RequireHttpsAttribute"/> attribute.</param> 
    /// <exception cref="System.ArgumentNullException">The filterContext parameter is null.</exception> 
    public virtual void OnAuthorization(AuthorizationContext filterContext) 
    { 
     if (filterContext == null) 
     { 
      throw new ArgumentNullException("filterContext"); 
     } 

     if (!filterContext.HttpContext.Request.IsSecureConnection) 
     { 
      this.HandleNonHttpsRequest(filterContext); 
     } 
    } 

    /// <summary> 
    /// Handles unsecured HTTP requests that are sent to the action method. 
    /// </summary> 
    /// <param name="filterContext">An object that encapsulates information that is required in order to use the <see cref="System.Web.Mvc.RequireHttpsAttribute"/> attribute.</param> 
    /// <exception cref="System.InvalidOperationException">The HTTP request contains an invalid transfer method override. All GET requests are considered invalid.</exception> 
    protected virtual void HandleNonHttpsRequest(AuthorizationContext filterContext) 
    { 
     // Only redirect for GET requests, otherwise the browser might not propagate the verb and request body correctly. 
     if (!string.Equals(filterContext.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase)) 
     { 
      // The RequireHttpsAttribute throws an InvalidOperationException. Some bots and spiders make HEAD requests (to reduce bandwidth) 
      // and we don’t want them to see a 500-Internal Server Error. A 405 Method Not Allowed would be more appropriate. 
      throw new HttpException((int)HttpStatusCode.MethodNotAllowed, "Method Not Allowed"); 
     } 

     string url = "https://" + filterContext.HttpContext.Request.Url.Host + filterContext.HttpContext.Request.RawUrl; 
     filterContext.Result = new RedirectResult(url, this.permanent); 
    } 
} 
Powiązane problemy