Mettere un sito in Maintenance mode.

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


Comments