Porting Session from Microsoft.Framework.Cache
This commit is contained in:
parent
5289d793f9
commit
7df56f6d2d
|
|
@ -0,0 +1,50 @@
|
|||
*.doc diff=astextplain
|
||||
*.DOC diff=astextplain
|
||||
*.docx diff=astextplain
|
||||
*.DOCX diff=astextplain
|
||||
*.dot diff=astextplain
|
||||
*.DOT diff=astextplain
|
||||
*.pdf diff=astextplain
|
||||
*.PDF diff=astextplain
|
||||
*.rtf diff=astextplain
|
||||
*.RTF diff=astextplain
|
||||
|
||||
*.jpg binary
|
||||
*.png binary
|
||||
*.gif binary
|
||||
|
||||
*.cs text=auto diff=csharp
|
||||
*.vb text=auto
|
||||
*.resx text=auto
|
||||
*.c text=auto
|
||||
*.cpp text=auto
|
||||
*.cxx text=auto
|
||||
*.h text=auto
|
||||
*.hxx text=auto
|
||||
*.py text=auto
|
||||
*.rb text=auto
|
||||
*.java text=auto
|
||||
*.html text=auto
|
||||
*.htm text=auto
|
||||
*.css text=auto
|
||||
*.scss text=auto
|
||||
*.sass text=auto
|
||||
*.less text=auto
|
||||
*.js text=auto
|
||||
*.lisp text=auto
|
||||
*.clj text=auto
|
||||
*.sql text=auto
|
||||
*.php text=auto
|
||||
*.lua text=auto
|
||||
*.m text=auto
|
||||
*.asm text=auto
|
||||
*.erl text=auto
|
||||
*.fs text=auto
|
||||
*.fsx text=auto
|
||||
*.hs text=auto
|
||||
|
||||
*.csproj text=auto
|
||||
*.vbproj text=auto
|
||||
*.fsproj text=auto
|
||||
*.dbproj text=auto
|
||||
*.sln text=auto eol=crlf
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
[Oo]bj/
|
||||
[Bb]in/
|
||||
TestResults/
|
||||
.nuget/
|
||||
*.sln.ide/
|
||||
_ReSharper.*/
|
||||
packages/
|
||||
artifacts/
|
||||
PublishProfiles/
|
||||
*.user
|
||||
*.suo
|
||||
*.cache
|
||||
*.docstates
|
||||
_ReSharper.*
|
||||
nuget.exe
|
||||
*net45.csproj
|
||||
*net451.csproj
|
||||
*k10.csproj
|
||||
*.psess
|
||||
*.vsp
|
||||
*.pidb
|
||||
*.userprefs
|
||||
*DS_Store
|
||||
*.ncrunchsolution
|
||||
*.*sdf
|
||||
*.ipch
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
Contributing
|
||||
======
|
||||
|
||||
Information on contributing to this repo is in the [Contributing Guide](https://github.com/aspnet/Home/blob/master/CONTRIBUTING.md) in the Home repo.
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 14
|
||||
VisualStudioVersion = 14.0.22604.0
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{E9D63F97-6078-42AD-BFD3-F956BF921BB5}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{A189F10C-3A9C-4F81-83D0-32E5FE50DAD8}"
|
||||
EndProject
|
||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Session", "src\Microsoft.AspNet.Session\Microsoft.AspNet.Session.kproj", "{71802736-F640-4733-9671-02D267EDD76A}"
|
||||
EndProject
|
||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Session.Tests", "test\Microsoft.AspNet.Session.Tests\Microsoft.AspNet.Session.Tests.kproj", "{8C131A0A-BC1A-4CF3-8B77-8813FBFE5639}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{94E80ED2-9F27-40AC-A9EF-C707BDFAA3BE}"
|
||||
EndProject
|
||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "SessionSample", "samples\SessionSample\SessionSample.kproj", "{FE0B9969-3BDE-4A7D-BE1B-47EAE8DBF365}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{71802736-F640-4733-9671-02D267EDD76A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{71802736-F640-4733-9671-02D267EDD76A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{71802736-F640-4733-9671-02D267EDD76A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{71802736-F640-4733-9671-02D267EDD76A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{8C131A0A-BC1A-4CF3-8B77-8813FBFE5639}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{8C131A0A-BC1A-4CF3-8B77-8813FBFE5639}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8C131A0A-BC1A-4CF3-8B77-8813FBFE5639}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{8C131A0A-BC1A-4CF3-8B77-8813FBFE5639}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{FE0B9969-3BDE-4A7D-BE1B-47EAE8DBF365}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{FE0B9969-3BDE-4A7D-BE1B-47EAE8DBF365}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{FE0B9969-3BDE-4A7D-BE1B-47EAE8DBF365}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{FE0B9969-3BDE-4A7D-BE1B-47EAE8DBF365}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{71802736-F640-4733-9671-02D267EDD76A} = {A189F10C-3A9C-4F81-83D0-32E5FE50DAD8}
|
||||
{8C131A0A-BC1A-4CF3-8B77-8813FBFE5639} = {E9D63F97-6078-42AD-BFD3-F956BF921BB5}
|
||||
{FE0B9969-3BDE-4A7D-BE1B-47EAE8DBF365} = {94E80ED2-9F27-40AC-A9EF-C707BDFAA3BE}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<packageSources>
|
||||
<add key="AspNetVNext" value="https://www.myget.org/F/aspnetvnext/api/v2" />
|
||||
<add key="NuGet.org" value="https://nuget.org/api/v2/" />
|
||||
</packageSources>
|
||||
</configuration>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
Caching
|
||||
================
|
||||
|
||||
Contains libraries for caching for ASP.NET 5.
|
||||
|
||||
This project is part of ASP.NET 5. You can find samples, documentation and getting started instructions for ASP.NET 5 at the [Home](https://github.com/aspnet/home) repo.
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
@echo off
|
||||
cd %~dp0
|
||||
|
||||
SETLOCAL
|
||||
SET CACHED_NUGET=%LocalAppData%\NuGet\NuGet.exe
|
||||
|
||||
IF EXIST %CACHED_NUGET% goto copynuget
|
||||
echo Downloading latest version of NuGet.exe...
|
||||
IF NOT EXIST %LocalAppData%\NuGet md %LocalAppData%\NuGet
|
||||
@powershell -NoProfile -ExecutionPolicy unrestricted -Command "$ProgressPreference = 'SilentlyContinue'; Invoke-WebRequest 'https://www.nuget.org/nuget.exe' -OutFile '%CACHED_NUGET%'"
|
||||
|
||||
:copynuget
|
||||
IF EXIST .nuget\nuget.exe goto restore
|
||||
md .nuget
|
||||
copy %CACHED_NUGET% .nuget\nuget.exe > nul
|
||||
|
||||
:restore
|
||||
IF EXIST packages\KoreBuild goto run
|
||||
.nuget\NuGet.exe install KoreBuild -ExcludeVersion -o packages -nocache -pre
|
||||
.nuget\NuGet.exe install Sake -version 0.2 -o packages -ExcludeVersion
|
||||
|
||||
IF "%SKIP_KRE_INSTALL%"=="1" goto run
|
||||
CALL packages\KoreBuild\build\kvm upgrade -runtime CLR -x86
|
||||
CALL packages\KoreBuild\build\kvm install default -runtime CoreCLR -x86
|
||||
|
||||
:run
|
||||
CALL packages\KoreBuild\build\kvm use default -runtime CLR -x86
|
||||
packages\Sake\tools\Sake.exe -I packages\KoreBuild\build -f makefile.shade %*
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
#!/bin/bash
|
||||
|
||||
if test `uname` = Darwin; then
|
||||
cachedir=~/Library/Caches/KBuild
|
||||
else
|
||||
if [ -z $XDG_DATA_HOME ]; then
|
||||
cachedir=$HOME/.local/share
|
||||
else
|
||||
cachedir=$XDG_DATA_HOME;
|
||||
fi
|
||||
fi
|
||||
mkdir -p $cachedir
|
||||
|
||||
url=https://www.nuget.org/nuget.exe
|
||||
|
||||
if test ! -f $cachedir/nuget.exe; then
|
||||
wget -O $cachedir/nuget.exe $url 2>/dev/null || curl -o $cachedir/nuget.exe --location $url /dev/null
|
||||
fi
|
||||
|
||||
if test ! -e .nuget; then
|
||||
mkdir .nuget
|
||||
cp $cachedir/nuget.exe .nuget/nuget.exe
|
||||
fi
|
||||
|
||||
if test ! -d packages/KoreBuild; then
|
||||
mono .nuget/nuget.exe install KoreBuild -ExcludeVersion -o packages -nocache -pre
|
||||
mono .nuget/nuget.exe install Sake -version 0.2 -o packages -ExcludeVersion
|
||||
fi
|
||||
|
||||
if ! type k > /dev/null 2>&1; then
|
||||
source packages/KoreBuild/build/kvm.sh
|
||||
fi
|
||||
|
||||
if ! type k > /dev/null 2>&1; then
|
||||
kvm upgrade
|
||||
fi
|
||||
|
||||
mono packages/Sake/tools/Sake.exe -I packages/KoreBuild/build -f makefile.shade "$@"
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"sources": ["src"]
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
|
||||
var VERSION='0.1'
|
||||
var FULL_VERSION='0.1'
|
||||
var AUTHORS='Microsoft Open Technologies, Inc.'
|
||||
|
||||
use-standard-lifecycle
|
||||
k-standard-goals
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
|
||||
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.Props" Condition="'$(VSToolsPath)' != ''" />
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>fe0b9969-3bde-4a7d-be1b-47eae8dbf365</ProjectGuid>
|
||||
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
|
||||
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
<DevelopmentServerPort>29562</DevelopmentServerPort>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
using System;
|
||||
using Microsoft.AspNet.Builder;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.Framework.DependencyInjection;
|
||||
using Microsoft.Framework.Logging;
|
||||
using Microsoft.Framework.Logging.Console;
|
||||
|
||||
namespace SessionSample
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
public Startup(ILoggerFactory loggerFactory)
|
||||
{
|
||||
loggerFactory.AddConsole(LogLevel.Verbose);
|
||||
}
|
||||
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddCachingServices();
|
||||
services.AddSessionServices();
|
||||
}
|
||||
|
||||
public void Configure(IApplicationBuilder app)
|
||||
{
|
||||
app.UseSession(o => {
|
||||
o.IdleTimeout = TimeSpan.FromSeconds(30); });
|
||||
// app.UseInMemorySession();
|
||||
// app.UseDistributedSession(new RedisCache(new RedisCacheOptions() { Configuration = "localhost" }));
|
||||
|
||||
app.Map("/session", subApp =>
|
||||
{
|
||||
subApp.Run(async context =>
|
||||
{
|
||||
int visits = 0;
|
||||
visits = context.Session.GetInt("visits") ?? 0;
|
||||
context.Session.SetInt("visits", ++visits);
|
||||
await context.Response.WriteAsync("Counting: You have visited our page this many times: " + visits);
|
||||
});
|
||||
});
|
||||
|
||||
app.Run(async context =>
|
||||
{
|
||||
int visits = 0;
|
||||
visits = context.Session.GetInt("visits") ?? 0;
|
||||
await context.Response.WriteAsync("<html><body>");
|
||||
if (visits == 0)
|
||||
{
|
||||
await context.Response.WriteAsync("Your session has not been established.<br>");
|
||||
await context.Response.WriteAsync(DateTime.Now + "<br>");
|
||||
await context.Response.WriteAsync("<a href=\"/session\">Establish session</a>.<br>");
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Session.SetInt("visits", ++visits);
|
||||
await context.Response.WriteAsync("Your session was located, you've visited the site this many times: " + visits);
|
||||
}
|
||||
await context.Response.WriteAsync("</body></html>");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"webroot": "wwwroot",
|
||||
"exclude": "wwwroot/**/*.*",
|
||||
"dependencies": {
|
||||
"Microsoft.AspNet.Http.Extensions": "1.0.0-*",
|
||||
"Microsoft.AspNet.Server.IIS": "1.0.0-*",
|
||||
"Microsoft.AspNet.Server.WebListener" : "1.0.0-*",
|
||||
"Microsoft.AspNet.Session": "1.0.0-*",
|
||||
"Microsoft.Framework.Cache.Redis": "1.0.0-*",
|
||||
"Microsoft.Framework.Logging.Console": "1.0.0-*"
|
||||
},
|
||||
"commands": { "web": "Microsoft.AspNet.Hosting server=Microsoft.AspNet.Server.WebListener server.urls=http://localhost:5001" },
|
||||
"frameworks" : {
|
||||
"aspnet50" : { }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNet.Session;
|
||||
|
||||
namespace Microsoft.Framework.DependencyInjection
|
||||
{
|
||||
public static class CachingServicesExtensions
|
||||
{
|
||||
public static IServiceCollection AddSessionServices(this IServiceCollection collection)
|
||||
{
|
||||
return collection.AddTransient<ISessionStore, DistributedSessionStore>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,293 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. 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.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.AspNet.Http.Interfaces;
|
||||
using Microsoft.Framework.Cache.Distributed;
|
||||
using Microsoft.Framework.Logging;
|
||||
|
||||
namespace Microsoft.AspNet.Session
|
||||
{
|
||||
public class DistributedSession : ISession
|
||||
{
|
||||
private const byte SerializationRevision = 1;
|
||||
private const int KeyLengthLimit = ushort.MaxValue;
|
||||
|
||||
private readonly IDistributedCache _cache;
|
||||
private readonly string _sessionId;
|
||||
private readonly TimeSpan _idleTimeout;
|
||||
private readonly Func<bool> _tryEstablishSession;
|
||||
private readonly IDictionary<EncodedKey, byte[]> _store;
|
||||
private readonly ILogger _logger;
|
||||
private bool _isModified;
|
||||
private bool _loaded;
|
||||
private bool _isNewSessionKey;
|
||||
|
||||
public DistributedSession([NotNull] IDistributedCache cache, [NotNull] string sessionId, TimeSpan idleTimeout,
|
||||
[NotNull] Func<bool> tryEstablishSession, [NotNull] ILoggerFactory loggerFactory, bool isNewSessionKey)
|
||||
{
|
||||
_cache = cache;
|
||||
_sessionId = sessionId;
|
||||
_idleTimeout = idleTimeout;
|
||||
_tryEstablishSession = tryEstablishSession;
|
||||
_store = new Dictionary<EncodedKey, byte[]>();
|
||||
_logger = loggerFactory.Create<DistributedSession>();
|
||||
_isNewSessionKey = isNewSessionKey;
|
||||
}
|
||||
|
||||
public IEnumerable<string> Keys
|
||||
{
|
||||
get
|
||||
{
|
||||
Load(); // TODO: Silent failure
|
||||
return _store.Keys.Select(key => key.KeyString);
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryGetValue(string key, out byte[] value)
|
||||
{
|
||||
Load(); // TODO: Silent failure
|
||||
return _store.TryGetValue(new EncodedKey(key), out value);
|
||||
}
|
||||
|
||||
public void Set(string key, ArraySegment<byte> value)
|
||||
{
|
||||
var encodedKey = new EncodedKey(key);
|
||||
if (encodedKey.KeyBytes.Length > KeyLengthLimit)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("key", key,
|
||||
string.Format("The key cannot be longer than '{0}' when encoded with UTF-8.", KeyLengthLimit));
|
||||
}
|
||||
if (value.Array == null)
|
||||
{
|
||||
throw new ArgumentException("The ArraySegment<byte>.Array cannot be null.", "value");
|
||||
}
|
||||
|
||||
Load();
|
||||
if (!_tryEstablishSession())
|
||||
{
|
||||
throw new InvalidOperationException("The session cannot be established after the response has started.");
|
||||
}
|
||||
_isModified = true;
|
||||
byte[] copy = new byte[value.Count];
|
||||
Buffer.BlockCopy(value.Array, value.Offset, copy, 0, value.Count);
|
||||
_store[encodedKey] = copy;
|
||||
}
|
||||
|
||||
public void Remove(string key)
|
||||
{
|
||||
Load();
|
||||
_isModified |= _store.Remove(new EncodedKey(key));
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
Load();
|
||||
_isModified |= _store.Count > 0;
|
||||
_store.Clear();
|
||||
}
|
||||
|
||||
// TODO: This should throw if called directly, but most other places it should fail silently (e.g. TryGetValue should just return null).
|
||||
public void Load()
|
||||
{
|
||||
if (!_loaded)
|
||||
{
|
||||
Stream data;
|
||||
if (_cache.TryGetValue(_sessionId, out data))
|
||||
{
|
||||
Deserialize(data);
|
||||
}
|
||||
else if (!_isNewSessionKey)
|
||||
{
|
||||
_logger.WriteWarning("Accessing expired session {0}", _sessionId);
|
||||
}
|
||||
_loaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Commit()
|
||||
{
|
||||
if (_isModified)
|
||||
{
|
||||
Stream data;
|
||||
if (_logger.IsEnabled(LogLevel.Information) && !_cache.TryGetValue(_sessionId, out data))
|
||||
{
|
||||
_logger.WriteInformation("Session {0} started", _sessionId);
|
||||
}
|
||||
_isModified = false;
|
||||
_cache.Set(_sessionId, context => {
|
||||
context.SetSlidingExpiration(_idleTimeout);
|
||||
Serialize(context.Data);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Format:
|
||||
// Serialization revision: 1 byte, range 0-255
|
||||
// Entry count: 3 bytes, range 0-16,777,215
|
||||
// foreach entry:
|
||||
// key name byte length: 2 bytes, range 0-65,535
|
||||
// UTF-8 encoded key name byte[]
|
||||
// data byte length: 4 bytes, range 0-2,147,483,647
|
||||
// data byte[]
|
||||
private void Serialize(Stream output)
|
||||
{
|
||||
output.WriteByte(SerializationRevision);
|
||||
SerializeNumAs3Bytes(output, _store.Count);
|
||||
|
||||
foreach (var entry in _store)
|
||||
{
|
||||
var keyBytes = entry.Key.KeyBytes;
|
||||
SerializeNumAs2Bytes(output, keyBytes.Length);
|
||||
output.Write(keyBytes, 0, keyBytes.Length);
|
||||
SerializeNumAs4Bytes(output, entry.Value.Length);
|
||||
output.Write(entry.Value, 0, entry.Value.Length);
|
||||
}
|
||||
}
|
||||
|
||||
private void Deserialize(Stream content)
|
||||
{
|
||||
if (content == null || content.ReadByte() != SerializationRevision)
|
||||
{
|
||||
// TODO: Throw?
|
||||
// Replace the un-readable format.
|
||||
_isModified = true;
|
||||
return;
|
||||
}
|
||||
|
||||
int expectedEntries = DeserializeNumFrom3Bytes(content);
|
||||
for (int i = 0; i < expectedEntries; i++)
|
||||
{
|
||||
int keyLength = DeserializeNumFrom2Bytes(content);
|
||||
var key = new EncodedKey(content.ReadBytes(keyLength));
|
||||
int dataLength = DeserializeNumFrom4Bytes(content);
|
||||
_store[key] = content.ReadBytes(dataLength);
|
||||
}
|
||||
}
|
||||
|
||||
private void SerializeNumAs2Bytes(Stream output, int num)
|
||||
{
|
||||
if (num < 0 || ushort.MaxValue < num)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("num", num, "The value cannot be serialized in two bytes.");
|
||||
}
|
||||
output.WriteByte((byte)(num >> 8));
|
||||
output.WriteByte((byte)(0xFF & num));
|
||||
}
|
||||
|
||||
private int DeserializeNumFrom2Bytes(Stream content)
|
||||
{
|
||||
return content.ReadByte() << 8 | content.ReadByte();
|
||||
}
|
||||
|
||||
private void SerializeNumAs3Bytes(Stream output, int num)
|
||||
{
|
||||
if (num < 0 || 0xFFFFFF < num)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("num", num, "The value cannot be serialized in three bytes.");
|
||||
}
|
||||
output.WriteByte((byte)(num >> 16));
|
||||
output.WriteByte((byte)(0xFF & (num >> 8)));
|
||||
output.WriteByte((byte)(0xFF & num));
|
||||
}
|
||||
|
||||
private int DeserializeNumFrom3Bytes(Stream content)
|
||||
{
|
||||
return content.ReadByte() << 16 | content.ReadByte() << 8 | content.ReadByte();
|
||||
}
|
||||
|
||||
private void SerializeNumAs4Bytes(Stream output, int num)
|
||||
{
|
||||
if (num < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("num", num, "The value cannot be negative.");
|
||||
}
|
||||
output.WriteByte((byte)(num >> 24));
|
||||
output.WriteByte((byte)(0xFF & (num >> 16)));
|
||||
output.WriteByte((byte)(0xFF & (num >> 8)));
|
||||
output.WriteByte((byte)(0xFF & num));
|
||||
}
|
||||
|
||||
private int DeserializeNumFrom4Bytes(Stream content)
|
||||
{
|
||||
return content.ReadByte() << 24 | content.ReadByte() << 16 | content.ReadByte() << 8 | content.ReadByte();
|
||||
}
|
||||
|
||||
// Keys are stored in their utf-8 encoded state.
|
||||
// This saves us from de-serializing and re-serializing every key on every request.
|
||||
private class EncodedKey
|
||||
{
|
||||
private string _keyString;
|
||||
private int? _hashCode;
|
||||
|
||||
internal EncodedKey(string key)
|
||||
{
|
||||
_keyString = key;
|
||||
KeyBytes = Encoding.UTF8.GetBytes(key);
|
||||
}
|
||||
|
||||
public EncodedKey(byte[] key)
|
||||
{
|
||||
KeyBytes = key;
|
||||
}
|
||||
|
||||
internal string KeyString
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_keyString == null)
|
||||
{
|
||||
_keyString = Encoding.UTF8.GetString(KeyBytes, 0, KeyBytes.Length);
|
||||
}
|
||||
return _keyString;
|
||||
}
|
||||
}
|
||||
|
||||
internal byte[] KeyBytes { get; private set; }
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
var otherKey = obj as EncodedKey;
|
||||
if (otherKey == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (KeyBytes.Length != otherKey.KeyBytes.Length)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (_hashCode.HasValue && otherKey._hashCode.HasValue
|
||||
&& _hashCode.Value != otherKey._hashCode.Value)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < KeyBytes.Length; i++)
|
||||
{
|
||||
if (KeyBytes[i] != otherKey.KeyBytes[i])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
if (!_hashCode.HasValue)
|
||||
{
|
||||
_hashCode = SipHash.GetHashCode(KeyBytes);
|
||||
}
|
||||
return _hashCode.Value;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return KeyString;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNet.Http.Interfaces;
|
||||
using Microsoft.Framework.Cache.Distributed;
|
||||
using Microsoft.Framework.Logging;
|
||||
|
||||
namespace Microsoft.AspNet.Session
|
||||
{
|
||||
public class DistributedSessionStore : ISessionStore
|
||||
{
|
||||
private readonly IDistributedCache _cache;
|
||||
private readonly ILoggerFactory _loggerFactory;
|
||||
|
||||
public DistributedSessionStore([NotNull] IDistributedCache cache, [NotNull] ILoggerFactory loggerFactory)
|
||||
{
|
||||
_cache = cache;
|
||||
_loggerFactory = loggerFactory;
|
||||
}
|
||||
|
||||
public bool IsAvailable
|
||||
{
|
||||
get
|
||||
{
|
||||
return true; // TODO:
|
||||
}
|
||||
}
|
||||
|
||||
public void Connect()
|
||||
{
|
||||
_cache.Connect();
|
||||
}
|
||||
|
||||
public ISession Create([NotNull] string sessionId, TimeSpan idleTimeout, [NotNull] Func<bool> tryEstablishSession, bool isNewSessionKey)
|
||||
{
|
||||
return new DistributedSession(_cache, sessionId, idleTimeout, tryEstablishSession, _loggerFactory, isNewSessionKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNet.Http.Interfaces;
|
||||
|
||||
namespace Microsoft.AspNet.Session
|
||||
{
|
||||
public interface ISessionStore
|
||||
{
|
||||
bool IsAvailable { get; }
|
||||
void Connect();
|
||||
ISession Create(string sessionId, TimeSpan idleTimeout, Func<bool> tryEstablishSession, bool isNewSessionKey);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
|
||||
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.Props" Condition="'$(VSToolsPath)' != ''" />
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>71802736-f640-4733-9671-02d267edd76a</ProjectGuid>
|
||||
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
|
||||
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNet.Session
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)]
|
||||
internal sealed class NotNullAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNet.Session
|
||||
{
|
||||
public static class SessionDefaults
|
||||
{
|
||||
public static string CookieName = ".AspNet.Session";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNet.Http.Interfaces;
|
||||
|
||||
namespace Microsoft.AspNet.Session
|
||||
{
|
||||
public class SessionFactory : ISessionFactory
|
||||
{
|
||||
private readonly string _sessionKey;
|
||||
private readonly ISessionStore _store;
|
||||
private readonly TimeSpan _idleTimeout;
|
||||
private readonly Func<bool> _tryEstablishSession;
|
||||
private readonly bool _isNewSessionKey;
|
||||
|
||||
public SessionFactory([NotNull] string sessionKey, [NotNull] ISessionStore store, TimeSpan idleTimeout, [NotNull] Func<bool> tryEstablishSession, bool isNewSessionKey)
|
||||
{
|
||||
_sessionKey = sessionKey;
|
||||
_store = store;
|
||||
_idleTimeout = idleTimeout;
|
||||
_tryEstablishSession = tryEstablishSession;
|
||||
_isNewSessionKey = isNewSessionKey;
|
||||
}
|
||||
|
||||
public bool IsAvailable
|
||||
{
|
||||
get { return _store.IsAvailable; }
|
||||
}
|
||||
|
||||
public ISession Create()
|
||||
{
|
||||
return _store.Create(_sessionKey, _idleTimeout, _tryEstablishSession, _isNewSessionKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNet.Http.Interfaces;
|
||||
|
||||
namespace Microsoft.AspNet.Session
|
||||
{
|
||||
public class SessionFeature : ISessionFeature
|
||||
{
|
||||
public ISessionFactory Factory { get; set; }
|
||||
|
||||
public ISession Session { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,155 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. 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.Interfaces;
|
||||
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;
|
||||
|
||||
public SessionMiddleware(
|
||||
[NotNull] RequestDelegate next,
|
||||
[NotNull] ILoggerFactory loggerFactory,
|
||||
[NotNull] IEnumerable<ISessionStore> sessionStore,
|
||||
[NotNull] IOptions<SessionOptions> options,
|
||||
[NotNull] ConfigureOptions<SessionOptions> configureOptions)
|
||||
{
|
||||
_next = next;
|
||||
_logger = loggerFactory.Create<SessionMiddleware>();
|
||||
if (configureOptions != null)
|
||||
{
|
||||
_options = options.GetNamedOptions(configureOptions.Name);
|
||||
configureOptions.Configure(_options);
|
||||
}
|
||||
else
|
||||
{
|
||||
_options = options.Options;
|
||||
}
|
||||
|
||||
if (_options.Store == null)
|
||||
{
|
||||
_options.Store = sessionStore.FirstOrDefault();
|
||||
if (_options.Store == null)
|
||||
{
|
||||
throw new ArgumentException("ISessionStore must be specified.");
|
||||
}
|
||||
}
|
||||
|
||||
_options.Store.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.Factory = new SessionFactory(sessionKey, _options.Store, _options.IdleTimeout, tryEstablishSession, isNewSessionKey);
|
||||
feature.Session = feature.Factory.Create();
|
||||
context.SetFeature<ISessionFeature>(feature);
|
||||
|
||||
try
|
||||
{
|
||||
await _next(context);
|
||||
}
|
||||
finally
|
||||
{
|
||||
context.SetFeature<ISessionFeature>(null);
|
||||
|
||||
if (feature.Session != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
feature.Session.Commit();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.WriteError("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.OnSendingHeaders(OnSendingHeadersCallback, state: this);
|
||||
}
|
||||
|
||||
private static void OnSendingHeadersCallback(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 ?? "/",
|
||||
};
|
||||
|
||||
_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.HeadersSent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNet.Session;
|
||||
using Microsoft.Framework.Cache.Distributed;
|
||||
using Microsoft.Framework.Cache.Memory;
|
||||
using Microsoft.Framework.DependencyInjection;
|
||||
using Microsoft.Framework.Logging;
|
||||
using Microsoft.Framework.OptionsModel;
|
||||
|
||||
namespace Microsoft.AspNet.Builder
|
||||
{
|
||||
public static class SessionMiddlewareExtensions
|
||||
{
|
||||
public static IServiceCollection ConfigureSession([NotNull] this IServiceCollection services, [NotNull] Action<SessionOptions> configure)
|
||||
{
|
||||
return services.ConfigureOptions(configure);
|
||||
}
|
||||
|
||||
public static IApplicationBuilder UseInMemorySession([NotNull] this IApplicationBuilder app, IMemoryCache cache = null, Action<SessionOptions> configure = null)
|
||||
{
|
||||
return app.UseDistributedSession(new LocalCache(cache ?? new MemoryCache(new MemoryCacheOptions())), configure);
|
||||
}
|
||||
|
||||
public static IApplicationBuilder UseDistributedSession([NotNull] this IApplicationBuilder app,
|
||||
IDistributedCache cache, Action<SessionOptions> configure = null)
|
||||
{
|
||||
var loggerFactory = app.ApplicationServices.GetRequiredService<ILoggerFactory>();
|
||||
return app.UseSession(options =>
|
||||
{
|
||||
options.Store = new DistributedSessionStore(cache, loggerFactory);
|
||||
if (configure != null)
|
||||
{
|
||||
configure(options);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static IApplicationBuilder UseSession([NotNull] this IApplicationBuilder app, Action<SessionOptions> configure = null)
|
||||
{
|
||||
return app.UseMiddleware<SessionMiddleware>(
|
||||
new ConfigureOptions<SessionOptions>(configure ?? (o => { }))
|
||||
{
|
||||
Name = string.Empty
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNet.Session
|
||||
{
|
||||
public class SessionOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines the cookie name used to persist the session ID. The default value is ".AspNet.Session".
|
||||
/// </summary>
|
||||
public string CookieName { get; set; } = SessionDefaults.CookieName;
|
||||
|
||||
/// <summary>
|
||||
/// Determines the domain used to create the cookie. Is not provided by default.
|
||||
/// </summary>
|
||||
public string CookieDomain { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines the path used to create the cookie. The default value is "/" for highest browser compatibility.
|
||||
/// </summary>
|
||||
public string CookiePath { get; set; } = "/";
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the browser should allow the cookie to be accessed by client-side JavaScript. The
|
||||
/// default is true, which means the cookie will only be passed to HTTP requests and is not made available
|
||||
/// to script on the page.
|
||||
/// </summary>
|
||||
public bool CookieHttpOnly { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// The IdleTimeout indicates how long the session can be idle before its contents are abandoned. Each session access
|
||||
/// resets the timeout. Note this only applies to the content of the session, not the cookie.
|
||||
/// </summary>
|
||||
public TimeSpan IdleTimeout { get; set; } = TimeSpan.FromMinutes(20);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the session storage manager. This overrides any session store passed into the middleware constructor.
|
||||
/// </summary>
|
||||
public ISessionStore Store { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,204 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNet.Session
|
||||
{
|
||||
// A byte[] equality comparer based on the SipHash-2-4 algorithm. Key differences:
|
||||
// (a) we output 32-bit hashes instead of 64-bit hashes, and
|
||||
// (b) we don't care about endianness since hashes are used only in hash tables
|
||||
// and aren't returned to user code.
|
||||
//
|
||||
// Derived from the implementation in SignalR and modified to address byte[] instead of string. This derived version is not for cryptographic use, just hash codes.
|
||||
// https://github.com/aspnet/SignalR-Server/blob/75f74169c81a51780f195d06b798302b2d76dbde/src/Microsoft.AspNet.SignalR.Server/Infrastructure/SipHashBasedStringEqualityComparer.cs
|
||||
// Derivative work of https://github.com/tanglebones/ch-siphash.
|
||||
internal static class SipHash
|
||||
{
|
||||
internal static int GetHashCode(byte[] bytes)
|
||||
{
|
||||
// Assume SipHash-2-4 is a strong PRF, therefore truncation to 32 bits is acceptable.
|
||||
return (int)SipHash_2_4_UlongCast_ForcedInline(bytes);
|
||||
}
|
||||
|
||||
private static ulong SipHash_2_4_UlongCast_ForcedInline(byte[] bytes)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
ulong v0 = 0x736f6d6570736575;
|
||||
ulong v1 = 0x646f72616e646f6d;
|
||||
ulong v2 = 0x6c7967656e657261;
|
||||
ulong v3 = 0x7465646279746573;
|
||||
|
||||
uint inlen = (uint)bytes.Length;
|
||||
fixed (byte* finb = bytes)
|
||||
{
|
||||
var b = ((ulong)inlen) << 56;
|
||||
|
||||
if (inlen > 0)
|
||||
{
|
||||
var inb = finb;
|
||||
var left = inlen & 7;
|
||||
var end = inb + inlen - left;
|
||||
var linb = (ulong*)finb;
|
||||
var lend = (ulong*)end;
|
||||
for (; linb < lend; ++linb)
|
||||
{
|
||||
v3 ^= *linb;
|
||||
|
||||
v0 += v1;
|
||||
v1 = (v1 << 13) | (v1 >> (64 - 13));
|
||||
v1 ^= v0;
|
||||
v0 = (v0 << 32) | (v0 >> (64 - 32));
|
||||
|
||||
v2 += v3;
|
||||
v3 = (v3 << 16) | (v3 >> (64 - 16));
|
||||
v3 ^= v2;
|
||||
|
||||
v0 += v3;
|
||||
v3 = (v3 << 21) | (v3 >> (64 - 21));
|
||||
v3 ^= v0;
|
||||
|
||||
v2 += v1;
|
||||
v1 = (v1 << 17) | (v1 >> (64 - 17));
|
||||
v1 ^= v2;
|
||||
v2 = (v2 << 32) | (v2 >> (64 - 32));
|
||||
v0 += v1;
|
||||
v1 = (v1 << 13) | (v1 >> (64 - 13));
|
||||
v1 ^= v0;
|
||||
v0 = (v0 << 32) | (v0 >> (64 - 32));
|
||||
|
||||
v2 += v3;
|
||||
v3 = (v3 << 16) | (v3 >> (64 - 16));
|
||||
v3 ^= v2;
|
||||
|
||||
v0 += v3;
|
||||
v3 = (v3 << 21) | (v3 >> (64 - 21));
|
||||
v3 ^= v0;
|
||||
|
||||
v2 += v1;
|
||||
v1 = (v1 << 17) | (v1 >> (64 - 17));
|
||||
v1 ^= v2;
|
||||
v2 = (v2 << 32) | (v2 >> (64 - 32));
|
||||
|
||||
v0 ^= *linb;
|
||||
}
|
||||
for (var i = 0; i < left; ++i)
|
||||
{
|
||||
b |= ((ulong)end[i]) << (8 * i);
|
||||
}
|
||||
}
|
||||
|
||||
v3 ^= b;
|
||||
v0 += v1;
|
||||
v1 = (v1 << 13) | (v1 >> (64 - 13));
|
||||
v1 ^= v0;
|
||||
v0 = (v0 << 32) | (v0 >> (64 - 32));
|
||||
|
||||
v2 += v3;
|
||||
v3 = (v3 << 16) | (v3 >> (64 - 16));
|
||||
v3 ^= v2;
|
||||
|
||||
v0 += v3;
|
||||
v3 = (v3 << 21) | (v3 >> (64 - 21));
|
||||
v3 ^= v0;
|
||||
|
||||
v2 += v1;
|
||||
v1 = (v1 << 17) | (v1 >> (64 - 17));
|
||||
v1 ^= v2;
|
||||
v2 = (v2 << 32) | (v2 >> (64 - 32));
|
||||
v0 += v1;
|
||||
v1 = (v1 << 13) | (v1 >> (64 - 13));
|
||||
v1 ^= v0;
|
||||
v0 = (v0 << 32) | (v0 >> (64 - 32));
|
||||
|
||||
v2 += v3;
|
||||
v3 = (v3 << 16) | (v3 >> (64 - 16));
|
||||
v3 ^= v2;
|
||||
|
||||
v0 += v3;
|
||||
v3 = (v3 << 21) | (v3 >> (64 - 21));
|
||||
v3 ^= v0;
|
||||
|
||||
v2 += v1;
|
||||
v1 = (v1 << 17) | (v1 >> (64 - 17));
|
||||
v1 ^= v2;
|
||||
v2 = (v2 << 32) | (v2 >> (64 - 32));
|
||||
v0 ^= b;
|
||||
v2 ^= 0xff;
|
||||
|
||||
v0 += v1;
|
||||
v1 = (v1 << 13) | (v1 >> (64 - 13));
|
||||
v1 ^= v0;
|
||||
v0 = (v0 << 32) | (v0 >> (64 - 32));
|
||||
|
||||
v2 += v3;
|
||||
v3 = (v3 << 16) | (v3 >> (64 - 16));
|
||||
v3 ^= v2;
|
||||
|
||||
v0 += v3;
|
||||
v3 = (v3 << 21) | (v3 >> (64 - 21));
|
||||
v3 ^= v0;
|
||||
|
||||
v2 += v1;
|
||||
v1 = (v1 << 17) | (v1 >> (64 - 17));
|
||||
v1 ^= v2;
|
||||
v2 = (v2 << 32) | (v2 >> (64 - 32));
|
||||
v0 += v1;
|
||||
v1 = (v1 << 13) | (v1 >> (64 - 13));
|
||||
v1 ^= v0;
|
||||
v0 = (v0 << 32) | (v0 >> (64 - 32));
|
||||
|
||||
v2 += v3;
|
||||
v3 = (v3 << 16) | (v3 >> (64 - 16));
|
||||
v3 ^= v2;
|
||||
|
||||
v0 += v3;
|
||||
v3 = (v3 << 21) | (v3 >> (64 - 21));
|
||||
v3 ^= v0;
|
||||
|
||||
v2 += v1;
|
||||
v1 = (v1 << 17) | (v1 >> (64 - 17));
|
||||
v1 ^= v2;
|
||||
v2 = (v2 << 32) | (v2 >> (64 - 32));
|
||||
v0 += v1;
|
||||
v1 = (v1 << 13) | (v1 >> (64 - 13));
|
||||
v1 ^= v0;
|
||||
v0 = (v0 << 32) | (v0 >> (64 - 32));
|
||||
|
||||
v2 += v3;
|
||||
v3 = (v3 << 16) | (v3 >> (64 - 16));
|
||||
v3 ^= v2;
|
||||
|
||||
v0 += v3;
|
||||
v3 = (v3 << 21) | (v3 >> (64 - 21));
|
||||
v3 ^= v0;
|
||||
|
||||
v2 += v1;
|
||||
v1 = (v1 << 17) | (v1 >> (64 - 17));
|
||||
v1 ^= v2;
|
||||
v2 = (v2 << 32) | (v2 >> (64 - 32));
|
||||
v0 += v1;
|
||||
v1 = (v1 << 13) | (v1 >> (64 - 13));
|
||||
v1 ^= v0;
|
||||
v0 = (v0 << 32) | (v0 >> (64 - 32));
|
||||
|
||||
v2 += v3;
|
||||
v3 = (v3 << 16) | (v3 >> (64 - 16));
|
||||
v3 ^= v2;
|
||||
|
||||
v0 += v3;
|
||||
v3 = (v3 << 21) | (v3 >> (64 - 21));
|
||||
v3 ^= v0;
|
||||
|
||||
v2 += v1;
|
||||
v1 = (v1 << 17) | (v1 >> (64 - 17));
|
||||
v1 ^= v2;
|
||||
v2 = (v2 << 32) | (v2 >> (64 - 32));
|
||||
}
|
||||
|
||||
return v0 ^ v1 ^ v2 ^ v3;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"version": "1.0.0-*",
|
||||
"description": "ASP.NET 5 session state middleware.",
|
||||
"dependencies": {
|
||||
"Microsoft.AspNet.Http.Extensions": "1.0.0-*",
|
||||
"Microsoft.AspNet.Http.Interfaces": "1.0.0-*",
|
||||
"Microsoft.Framework.Cache.Distributed": "1.0.0-*",
|
||||
"Microsoft.Framework.Logging": "1.0.0-*"
|
||||
},
|
||||
"compilationOptions": {
|
||||
"allowUnsafe": true
|
||||
},
|
||||
"frameworks": {
|
||||
"aspnet50": { },
|
||||
"aspnetcore50": {
|
||||
"dependencies": {
|
||||
"System.Security.Cryptography.RandomNumberGenerator": "4.0.0-beta-*"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
|
||||
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.Props" Condition="'$(VSToolsPath)' != ''" />
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>8c131a0a-bc1a-4cf3-8b77-8813fbfe5639</ProjectGuid>
|
||||
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
|
||||
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,266 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. 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.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Builder;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.TestHost;
|
||||
using Microsoft.Framework.DependencyInjection;
|
||||
using Microsoft.Framework.Logging;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Session
|
||||
{
|
||||
public class SessionTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task ReadingEmptySessionDoesNotCreateCookie()
|
||||
{
|
||||
using (var server = TestServer.Create(app =>
|
||||
{
|
||||
app.UseServices(services => services.AddOptions());
|
||||
app.UseInMemorySession();
|
||||
app.Run(context =>
|
||||
{
|
||||
Assert.Null(context.Session.GetString("NotFound"));
|
||||
return Task.FromResult(0);
|
||||
});
|
||||
}))
|
||||
{
|
||||
var client = server.CreateClient();
|
||||
var response = await client.GetAsync("/");
|
||||
response.EnsureSuccessStatusCode();
|
||||
IEnumerable<string> values;
|
||||
Assert.False(response.Headers.TryGetValues("Set-Cookie", out values));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SettingAValueCausesTheCookieToBeCreated()
|
||||
{
|
||||
using (var server = TestServer.Create(app =>
|
||||
{
|
||||
app.UseServices(services => services.AddOptions());
|
||||
app.UseInMemorySession();
|
||||
app.Run(context =>
|
||||
{
|
||||
Assert.Null(context.Session.GetString("Key"));
|
||||
context.Session.SetString("Key", "Value");
|
||||
Assert.Equal("Value", context.Session.GetString("Key"));
|
||||
return Task.FromResult(0);
|
||||
});
|
||||
}))
|
||||
{
|
||||
var client = server.CreateClient();
|
||||
var response = await client.GetAsync("/");
|
||||
response.EnsureSuccessStatusCode();
|
||||
IEnumerable<string> values;
|
||||
Assert.True(response.Headers.TryGetValues("Set-Cookie", out values));
|
||||
Assert.Equal(1, values.Count());
|
||||
Assert.True(!string.IsNullOrWhiteSpace(values.First()));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SessionCanBeAccessedOnTheNextRequest()
|
||||
{
|
||||
using (var server = TestServer.Create(app =>
|
||||
{
|
||||
app.UseServices(services => services.AddOptions());
|
||||
app.UseInMemorySession();
|
||||
app.Run(context =>
|
||||
{
|
||||
int? value = context.Session.GetInt("Key");
|
||||
if (context.Request.Path == new PathString("/first"))
|
||||
{
|
||||
Assert.False(value.HasValue);
|
||||
value = 0;
|
||||
}
|
||||
Assert.True(value.HasValue);
|
||||
context.Session.SetInt("Key", value.Value + 1);
|
||||
return context.Response.WriteAsync(value.Value.ToString());
|
||||
});
|
||||
}))
|
||||
{
|
||||
var client = server.CreateClient();
|
||||
var response = await client.GetAsync("/first");
|
||||
response.EnsureSuccessStatusCode();
|
||||
Assert.Equal("0", await response.Content.ReadAsStringAsync());
|
||||
|
||||
client = server.CreateClient();
|
||||
client.DefaultRequestHeaders.Add("Cookie", response.Headers.GetValues("Set-Cookie"));
|
||||
Assert.Equal("1", await client.GetStringAsync("/"));
|
||||
Assert.Equal("2", await client.GetStringAsync("/"));
|
||||
Assert.Equal("3", await client.GetStringAsync("/"));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RemovedItemCannotBeAccessedAgain()
|
||||
{
|
||||
using (var server = TestServer.Create(app =>
|
||||
{
|
||||
app.UseServices(services => services.AddOptions());
|
||||
app.UseInMemorySession();
|
||||
app.Run(context =>
|
||||
{
|
||||
int? value = context.Session.GetInt("Key");
|
||||
if (context.Request.Path == new PathString("/first"))
|
||||
{
|
||||
Assert.False(value.HasValue);
|
||||
value = 0;
|
||||
context.Session.SetInt("Key", 1);
|
||||
}
|
||||
else if (context.Request.Path == new PathString("/second"))
|
||||
{
|
||||
Assert.True(value.HasValue);
|
||||
Assert.Equal(1, value);
|
||||
context.Session.Remove("Key");
|
||||
}
|
||||
else if (context.Request.Path == new PathString("/third"))
|
||||
{
|
||||
Assert.False(value.HasValue);
|
||||
value = 2;
|
||||
}
|
||||
return context.Response.WriteAsync(value.Value.ToString());
|
||||
});
|
||||
}))
|
||||
{
|
||||
var client = server.CreateClient();
|
||||
var response = await client.GetAsync("/first");
|
||||
response.EnsureSuccessStatusCode();
|
||||
Assert.Equal("0", await response.Content.ReadAsStringAsync());
|
||||
|
||||
client = server.CreateClient();
|
||||
client.DefaultRequestHeaders.Add("Cookie", response.Headers.GetValues("Set-Cookie"));
|
||||
Assert.Equal("1", await client.GetStringAsync("/second"));
|
||||
Assert.Equal("2", await client.GetStringAsync("/third"));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ClearedItemsCannotBeAccessedAgain()
|
||||
{
|
||||
using (var server = TestServer.Create(app =>
|
||||
{
|
||||
app.UseServices(services => services.AddOptions());
|
||||
app.UseInMemorySession();
|
||||
app.Run(context =>
|
||||
{
|
||||
int? value = context.Session.GetInt("Key");
|
||||
if (context.Request.Path == new PathString("/first"))
|
||||
{
|
||||
Assert.False(value.HasValue);
|
||||
value = 0;
|
||||
context.Session.SetInt("Key", 1);
|
||||
}
|
||||
else if (context.Request.Path == new PathString("/second"))
|
||||
{
|
||||
Assert.True(value.HasValue);
|
||||
Assert.Equal(1, value);
|
||||
context.Session.Clear();
|
||||
}
|
||||
else if (context.Request.Path == new PathString("/third"))
|
||||
{
|
||||
Assert.False(value.HasValue);
|
||||
value = 2;
|
||||
}
|
||||
return context.Response.WriteAsync(value.Value.ToString());
|
||||
});
|
||||
}))
|
||||
{
|
||||
var client = server.CreateClient();
|
||||
var response = await client.GetAsync("/first");
|
||||
response.EnsureSuccessStatusCode();
|
||||
Assert.Equal("0", await response.Content.ReadAsStringAsync());
|
||||
|
||||
client = server.CreateClient();
|
||||
client.DefaultRequestHeaders.Add("Cookie", response.Headers.GetValues("Set-Cookie"));
|
||||
Assert.Equal("1", await client.GetStringAsync("/second"));
|
||||
Assert.Equal("2", await client.GetStringAsync("/third"));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SessionStart_LogsInformation()
|
||||
{
|
||||
var sink = new TestSink();
|
||||
var loggerFactory = new TestLoggerFactory(sink, enabled: true);
|
||||
using (var server = TestServer.Create(app =>
|
||||
{
|
||||
app.UseServices(services =>
|
||||
{
|
||||
services.AddOptions();
|
||||
services.AddInstance(typeof(ILoggerFactory), loggerFactory);
|
||||
});
|
||||
app.UseInMemorySession();
|
||||
app.Run(context =>
|
||||
{
|
||||
context.Session.SetString("Key", "Value");
|
||||
return Task.FromResult(0);
|
||||
});
|
||||
}))
|
||||
{
|
||||
var client = server.CreateClient();
|
||||
var response = await client.GetAsync("/");
|
||||
response.EnsureSuccessStatusCode();
|
||||
Assert.Single(sink.Writes);
|
||||
Assert.True(((ILoggerStructure)sink.Writes[0].State).Format().Contains("started"));
|
||||
Assert.Equal(LogLevel.Information, sink.Writes[0].LogLevel);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExpiredSession_LogsWarning()
|
||||
{
|
||||
var sink = new TestSink();
|
||||
var loggerFactory = new TestLoggerFactory(sink, enabled: true);
|
||||
using (var server = TestServer.Create(app =>
|
||||
{
|
||||
app.UseServices(services =>
|
||||
{
|
||||
services.AddOptions();
|
||||
services.AddInstance(typeof(ILoggerFactory), loggerFactory);
|
||||
});
|
||||
app.UseInMemorySession(configure: o => {
|
||||
o.IdleTimeout = TimeSpan.FromMilliseconds(30);
|
||||
});
|
||||
app.Run(context =>
|
||||
{
|
||||
int? value = context.Session.GetInt("Key");
|
||||
if (context.Request.Path == new PathString("/first"))
|
||||
{
|
||||
Assert.False(value.HasValue);
|
||||
value = 1;
|
||||
context.Session.SetInt("Key", 1);
|
||||
}
|
||||
else if (context.Request.Path == new PathString("/second"))
|
||||
{
|
||||
Assert.False(value.HasValue);
|
||||
value = 2;
|
||||
}
|
||||
return context.Response.WriteAsync(value.Value.ToString());
|
||||
});
|
||||
}))
|
||||
{
|
||||
var client = server.CreateClient();
|
||||
var response = await client.GetAsync("/first");
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
client = server.CreateClient();
|
||||
client.DefaultRequestHeaders.Add("Cookie", response.Headers.GetValues("Set-Cookie"));
|
||||
Thread.Sleep(50);
|
||||
Assert.Equal("2", await client.GetStringAsync("/second"));
|
||||
Assert.Equal(2, sink.Writes.Count);
|
||||
Assert.True(((ILoggerStructure)sink.Writes[0].State).Format().Contains("started"));
|
||||
Assert.True(((ILoggerStructure)sink.Writes[1].State).Format().Contains("expired"));
|
||||
Assert.Equal(LogLevel.Information, sink.Writes[0].LogLevel);
|
||||
Assert.Equal(LogLevel.Warning, sink.Writes[1].LogLevel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"Microsoft.AspNet.Session": "1.0.0-*",
|
||||
"Microsoft.AspNet.RequestContainer": "1.0.0-*",
|
||||
"Microsoft.AspNet.TestHost": "1.0.0-*",
|
||||
"xunit.runner.kre": "1.0.0-*"
|
||||
},
|
||||
"commands": {
|
||||
"test": "xunit.runner.kre"
|
||||
},
|
||||
"frameworks": {
|
||||
"aspnet50": {}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue