Mettere un sito in Maintenance mode.

Print Content | More

Mentre pianificavo il deploy di una nuova versione di Dexter, mi è venuto in mente di realizzare un HttpModule che bloccasse l’accesso al sito a tutti clients non provenienti da un determinanto indirizzo IP, reindirizzandoli verso una pagina temporanea che comunicava all’utente che era in corso un aggiornamento.

Fin qui nulla di particolare, un semplice HttpModule che  verifica IPAddress del client e, nel caso questo non sia presente nella “white list”, effettua un semplice redirect.

Ma cosa accade nel caso in cui un crawler di un search engine passi in quel determinato momento?

Una buona soluzione è quella di comunicare al bot che il sito non è momentaneamente disponibile e di rimandare la fase di crawling.

Per far ciò una possibile soluzione consiste nell’effettuare il redirect di ogni pagina verso un’altra, ed impostare uno status code differente, così che il search engine non sostituisca nell’indice il contenuto della pagina richiesta con quello della pagina temporanea.

Ma quale status code è più adatto a questo tipo di operazione?

Leggendo da W3C, un possibile status code sarebbe il “503 Service Unavailable”, ma questo non è customizzabile come spiegato qui:

The 503 error cannot be customized because they are handled in the kernel mode by HTTP.sys. This means that user-mode routines cannot be run.

Una soluzione alternativa è effettuare un “302 Found” ad una pagina html custom, che verrà esclusa dall’indicizzazione tramite l’apposito file Robots.txt.

Il redirect con status code 302 indica al client che il contenuto è stato spostato solo temporaneamente verso il nuovo indirizzo, e che successivamente potrà trovarlo nuovamente al vecchio url.

Questo permette di reindirizzare il client verso una pagina esclusa dall’indicizzazione.

Di seguito potete trovare il codice e l’apposita area di configurazione del HttpModule.

public class MaintenanceModule : IHttpModule
{
    #region IHttpModule Members

    public void Dispose()
    {
    }

    public void Init(HttpApplication context)
    {
        if (MaintenanceConfiguration.Instance.EnableMaintenance)
            context.BeginRequest += context_BeginRequest;
    }

    #endregion

    private void context_BeginRequest(object sender, EventArgs e)
    {
        HttpContext context = HttpContext.Current;

        if (context.Request.Url.AbsolutePath == MaintenanceConfiguration.Instance.AbsolutePagePath)
            return;

        if (MaintenanceConfiguration.Instance.AllowedIP.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries).Contains(context.Request.UserHostAddress))
            return;

        context.Response.StatusCode = 302;
        context.Response.Cache.SetCacheability(HttpCacheability.NoCache);
        context.Response.AddHeader("Location", MaintenanceConfiguration.Instance.AbsolutePagePath);
    }
}

 

/// <summary>
/// Maintenance Configuration class.
/// </summary>
public static class MaintenanceConfiguration
{
    private static readonly MaintenanceConfigurationSection section;

    /// <summary>
    /// Initializes the <see cref="MaintenanceConfiguration"/> class.
    /// </summary>
    static MaintenanceConfiguration()
    {
        section = ConfigurationManager.GetSection("dexter.maintenance.configurationSection") as MaintenanceConfigurationSection;

        if (section == null)
            throw new MaintenanceSettingException("Maintenance section not found in the configuration file.");
    }

    /// <summary>
    /// Gets the instance of MaintenanceConfigurationSection class.
    /// </summary>
    /// <value>The instance.</value>
    public static MaintenanceConfigurationSection Instance
    {
        get { return section; }
    }
}

/// <summary>
/// Log Configuration Section class.
/// </summary>
public sealed class MaintenanceConfigurationSection : ConfigurationSection
{
    [ConfigurationProperty("enableMaintenance", DefaultValue = false, IsRequired = true)]
    public bool EnableMaintenance
    {
        get { return (bool) this["enableMaintenance"]; }
        set { this["enableMaintenance"] = value; }
    }

    [ConfigurationProperty("allowedIP", IsRequired = true)]
    public string AllowedIP
    {
        get { return (string) this["allowedIP"]; }
        set { this["allowedIP"] = value; }
    }

    [ConfigurationProperty("absolutePagePath", IsRequired = true)]
    public string AbsolutePagePath
    {
        get { return (string) this["absolutePagePath"]; }
        set { this["absolutePagePath"] = value; }
    }
}

public class MaintenanceSettingException : Exception
{
    /// <summary>
    /// Initializes a new instance of the <see cref="MaintenanceSettingException"/> class.
    /// </summary>
    /// <param name="message">The message.</param>
    public MaintenanceSettingException(string message) : base(message)
    {
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="MaintenanceSettingException"/> class.
    /// </summary>
    /// <param name="message">The message.</param>
    /// <param name="innerException">The inner exception.</param>
    public MaintenanceSettingException(string message, Exception innerException) : base(message, innerException)
    {
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="MaintenanceSettingException"/> class.
    /// </summary>
    /// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo"/> that holds the serialized object data about the exception being thrown.</param>
    /// <param name="context">The <see cref="T:System.Runtime.Serialization.StreamingContext"/> that contains contextual information about the source or destination.</param>
    /// <exception cref="T:System.ArgumentNullException">
    /// The <paramref name="info"/> parameter is null.
    /// </exception>
    /// <exception cref="T:System.Runtime.Serialization.SerializationException">
    /// The class name is null or <see cref="P:System.Exception.HResult"/> is zero (0).
    /// </exception>
    public MaintenanceSettingException(SerializationInfo info, StreamingContext context)
        : base(info, context)
    {
    }
}
<dexter.maintenance.configurationSection 
            enableMaintenance="true" 
            allowedIP="127.0.0.1" 
            absolutePagePath="/Maintenance.aspx" />

Ciauz


ASP.NET , Configurazione , Deploy , HttpModule , SEO

6 comments

Related Post

  1. #1 da Maurizio Tammacco Tuesday September 2009 alle 12:47

    Complimenti Ugo, ottima soluzione!

  2. #2 da Gianluca Tuesday September 2009 alle 12:47

    Ottima soluzione! Che ne dici di sostituire la riga 24 della classe MaintenanceModule con

    Regex ipRegex = new Regex(MaintenanceConfiguration.Instance.AllowedIP);
    if (ipRegex.Match(context.Request.UserHostAddress).Success)
    return;

    ;) ciao!

  3. #3 da Andrea Balducci Wednesday October 2009 alle 12:08

    così fai maintenance dei contenuti ma non gestisci il deploy. Se tocchi un file di configurazione o una dll l'appdomain va giù con il tuo httpmodule... Per gestire il deploy dovresti necessariamente usare l'app_offline.htm mentre aggiorni i file e poi tirare su (se necessario) la webapp in maintenance mode. Per lo status code dovresti optare per 307 e solo in presenza di chiamate esplicitamente http 1.0 usare il 302.

  4. #4 da Ugo Lattanzi Wednesday October 2009 alle 12:23

    Hi know, infatti non serve per permetterti di effettuare il deploy del sito, del db, ecc ma di poter lavorare sulla skin, css, immagini, markup ecc. Se io cambio il markup di una pagina posso generare un errore senza far cadere l'appdomain ed in quel caso il crawler avrebbe un 500. In questo modo hai la possibilita di lavorare in tranquillità e di vedere le modifiche. L'app_offline non ti permette di farlo in quanto chiude le porte at tutti. Inoltre l'app_offline restituisce un 404 che per la parte SEO è una bel problema (parlo per esperienza). Per il deploy di una nuova versione dell'applicazione (quindi anche database) non si può fare a meno di un down dell'applicativo, ma in quel caso è meglio gestire la cosa da IIS piuttosto che da app_offline. Ciauz e grazie

  5. #5 da Andrea Balducci Wednesday October 2009 alle 12:47

    Infatti credo che l'unica soluzione sia avere "accesso" ad IIS e reindirizzare il sito verso una app ad hoc di maintenance che manda 307 / 302 in attesa che venga rimesso online l'applicativo in manutenzione. Altrimenti il 404 mentre fai l'aggiornamento (magari via ftp) non te lo levi di mezzo...

  6. #6 da Ugo Lattanzi Wednesday October 2009 alle 11:02

    Beh, ribadisco che dipende dal tipo di deploy e/o manutenzione che devi fare. Se css, markup, js, img, ecc va benissimo il module, se è applicativo ovviamente no, essendo il module parte dell'applicativo stesso. L'app_offline IMHO restituendo un 404 rimane un problema grave per la parte SEO. P.S: mi sa che devo sistemare la parte dei commenti che mancano i [br] :S. Ciauz

The comments for this post are closed.

  1. There is no TrackBack for this post.