// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Security.Cryptography;
using System.Threading.Tasks;
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Features;
using Microsoft.Framework.Internal;
using Microsoft.Framework.Logging;
using Microsoft.Framework.OptionsModel;
namespace Microsoft.AspNet.Session
{
///
/// Enables the session state for the application.
///
public class SessionMiddleware
{
private static readonly RandomNumberGenerator CryptoRandom = RandomNumberGenerator.Create();
private const int SessionKeyLength = 36; // "382c74c3-721d-4f34-80e5-57657b6cbc27"
private static readonly Func ReturnTrue = () => true;
private readonly RequestDelegate _next;
private readonly SessionOptions _options;
private readonly ILogger _logger;
private readonly ISessionStore _sessionStore;
///
/// Creates a new .
///
/// The representing the next middleware in the pipeline.
/// The representing the factory that used to create logger instances.
/// The representing the session store.
/// The session configuration options.
public SessionMiddleware(
[NotNull] RequestDelegate next,
[NotNull] ILoggerFactory loggerFactory,
[NotNull] ISessionStore sessionStore,
[NotNull] IOptions options)
{
_next = next;
_logger = loggerFactory.CreateLogger();
_options = options.Options;
_sessionStore = sessionStore;
_sessionStore.Connect();
}
///
/// Invokes the logic of the middleware.
///
/// The .
/// A that completes when the middleware has completed processing.
public async Task Invoke(HttpContext context)
{
var isNewSessionKey = false;
Func tryEstablishSession = ReturnTrue;
string sessionKey = context.Request.Cookies[_options.CookieName];
if (string.IsNullOrWhiteSpace(sessionKey) || sessionKey.Length != SessionKeyLength)
{
// No valid cookie, new session.
var guidBytes = new byte[16];
CryptoRandom.GetBytes(guidBytes);
sessionKey = new Guid(guidBytes).ToString();
var establisher = new SessionEstablisher(context, sessionKey, _options);
tryEstablishSession = establisher.TryEstablishSession;
isNewSessionKey = true;
}
var feature = new SessionFeature();
feature.Session = _sessionStore.Create(sessionKey, _options.IdleTimeout, tryEstablishSession, isNewSessionKey);
context.SetFeature(feature);
try
{
await _next(context);
}
finally
{
context.SetFeature(null);
if (feature.Session != null)
{
try
{
await feature.Session.CommitAsync();
}
catch (Exception ex)
{
_logger.LogError("Error closing the session.", ex);
}
}
}
}
private class SessionEstablisher
{
private readonly HttpContext _context;
private readonly string _sessionKey;
private readonly SessionOptions _options;
private bool _shouldEstablishSession;
public SessionEstablisher(HttpContext context, string sessionKey, SessionOptions options)
{
_context = context;
_sessionKey = sessionKey;
_options = options;
context.Response.OnStarting(OnStartingCallback, state: this);
}
private static Task OnStartingCallback(object state)
{
var establisher = (SessionEstablisher)state;
if (establisher._shouldEstablishSession)
{
establisher.SetCookie();
}
return Task.FromResult(0);
}
private void SetCookie()
{
var cookieOptions = new CookieOptions
{
Domain = _options.CookieDomain,
HttpOnly = _options.CookieHttpOnly,
Path = _options.CookiePath ?? SessionDefaults.CookiePath,
};
_context.Response.Cookies.Append(_options.CookieName, _sessionKey, cookieOptions);
_context.Response.Headers["Cache-Control"] = "no-cache";
_context.Response.Headers["Pragma"] = "no-cache";
_context.Response.Headers["Expires"] = "-1";
}
// Returns true if the session has already been established, or if it still can be because the response has not been sent.
internal bool TryEstablishSession()
{
return (_shouldEstablishSession |= !_context.Response.HasStarted);
}
}
}
}