aspnetcore/src/Microsoft.AspNet.Session/SessionMiddleware.cs

138 lines
5.0 KiB
C#

// 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.Collections.Generic;
using System.Linq;
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
{
public class SessionMiddleware
{
private static readonly RandomNumberGenerator CryptoRandom = RandomNumberGenerator.Create();
private const int SessionKeyLength = 36; // "382c74c3-721d-4f34-80e5-57657b6cbc27"
private static readonly Func<bool> ReturnTrue = () => true;
private readonly RequestDelegate _next;
private readonly SessionOptions _options;
private readonly ILogger _logger;
private readonly ISessionStore _sessionStore;
public SessionMiddleware(
[NotNull] RequestDelegate next,
[NotNull] ILoggerFactory loggerFactory,
[NotNull] ISessionStore sessionStore,
[NotNull] IOptions<SessionOptions> options)
{
_next = next;
_logger = loggerFactory.CreateLogger<SessionMiddleware>();
_options = options.Options;
_sessionStore = sessionStore;
_sessionStore.Connect();
}
public async Task Invoke(HttpContext context)
{
var isNewSessionKey = false;
Func<bool> tryEstablishSession = ReturnTrue;
var sessionKey = context.Request.Cookies.Get(_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<ISessionFeature>(feature);
try
{
await _next(context);
}
finally
{
context.SetFeature<ISessionFeature>(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.OnResponseStarting(OnResponseStartingCallback, state: this);
}
private static void OnResponseStartingCallback(object state)
{
var establisher = (SessionEstablisher)state;
if (establisher._shouldEstablishSession)
{
establisher.SetCookie();
}
}
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.Set(
"Cache-Control",
"no-cache");
_context.Response.Headers.Set(
"Pragma",
"no-cache");
_context.Response.Headers.Set(
"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);
}
}
}
}