Initial commit

This commit is contained in:
David Fowler 2016-09-30 01:44:56 -07:00
commit 03352354dc
22 changed files with 1136 additions and 0 deletions

52
.gitattributes vendored Normal file
View File

@ -0,0 +1,52 @@
*.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
*.sh eol=lf

33
.gitignore vendored Normal file
View File

@ -0,0 +1,33 @@
[Oo]bj/
[Bb]in/
TestResults/
.nuget/
*.sln.ide/
_ReSharper.*/
packages/
artifacts/
PublishProfiles/
.vs/
*.user
*.suo
*.cache
*.docstates
_ReSharper.*
nuget.exe
*net45.csproj
*net451.csproj
*k10.csproj
*.psess
*.vsp
*.pidb
*.userprefs
*DS_Store
*.ncrunchsolution
*.*sdf
*.ipch
project.lock.json
runtimes/
.build/
.testPublish/
launchSettings.json
*.tmp

13
LICENSE.md Normal file
View File

@ -0,0 +1,13 @@
Copyright (c) David Fowler All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License"); you
may not use this file except in compliance with the License. You may
obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied. See the License for the specific language governing permissions
and limitations under the License.

7
NuGet.config Normal file
View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="Channels" value="https://www.myget.org/F/channels/api/v3/index.json" />
<add key="dotnet-corefxlab" value="https://dotnet.myget.org/F/dotnet-corefxlab/api/v3/index.json" />
</packageSources>
</configuration>

32
WebApplication95.sln Normal file
View File

@ -0,0 +1,32 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.25420.1
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{DA69F624-5398-4884-87E4-B816698CDE65}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{83B2C3EB-A3D8-4E6F-9A3C-A380B005EF31}"
ProjectSection(SolutionItems) = preProject
global.json = global.json
EndProjectSection
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "WebApplication95", "src\WebApplication95\WebApplication95.xproj", "{52ED8B3A-2DBB-448A-A708-FAA0783B7917}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{52ED8B3A-2DBB-448A-A708-FAA0783B7917}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{52ED8B3A-2DBB-448A-A708-FAA0783B7917}.Debug|Any CPU.Build.0 = Debug|Any CPU
{52ED8B3A-2DBB-448A-A708-FAA0783B7917}.Release|Any CPU.ActiveCfg = Release|Any CPU
{52ED8B3A-2DBB-448A-A708-FAA0783B7917}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{52ED8B3A-2DBB-448A-A708-FAA0783B7917} = {DA69F624-5398-4884-87E4-B816698CDE65}
EndGlobalSection
EndGlobal

6
global.json Normal file
View File

@ -0,0 +1,6 @@
{
"projects": [ "src", "test" ],
"sdk": {
"version": "1.0.0-preview2-003121"
}
}

View File

@ -0,0 +1,58 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace WebApplication95
{
public class Message
{
public string ContentType { get; set; }
public ArraySegment<byte> Payload { get; set; }
}
public class Bus
{
private readonly ConcurrentDictionary<string, List<IObserver<Message>>> _subscriptions = new ConcurrentDictionary<string, List<IObserver<Message>>>();
public IDisposable Subscribe(string key, IObserver<Message> observer)
{
var connections = _subscriptions.GetOrAdd(key, _ => new List<IObserver<Message>>());
connections.Add(observer);
return new DisposableAction(() =>
{
connections.Remove(observer);
});
}
public void Publish(string key, Message message)
{
List<IObserver<Message>> connections;
if (_subscriptions.TryGetValue(key, out connections))
{
foreach (var c in connections)
{
c.OnNext(message);
}
}
}
private class DisposableAction : IDisposable
{
private Action _action;
public DisposableAction(Action action)
{
_action = action;
}
public void Dispose()
{
Interlocked.Exchange(ref _action, () => { }).Invoke();
}
}
}
}

View File

@ -0,0 +1,52 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Channels;
namespace WebApplication95
{
public enum TransportType
{
LongPolling,
WebSockets,
ServerSentEvents
}
public class Connection : IChannel
{
public TransportType TransportType { get; set; }
public string ConnectionId { get; set; }
IReadableChannel IChannel.Input => Input;
IWritableChannel IChannel.Output => Output;
internal Channel Input { get; set; }
internal Channel Output { get; set; }
public Connection()
{
Stream = new ChannelStream(this);
}
public Stream Stream { get; }
public void Complete()
{
Input.CompleteReader();
Input.CompleteWriter();
Output.CompleteReader();
Output.CompleteWriter();
}
public void Dispose()
{
}
}
}

View File

@ -0,0 +1,102 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Channels;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives;
namespace WebApplication95
{
public class ConnectionManager
{
private ConcurrentDictionary<string, ConnectionState> _connections = new ConcurrentDictionary<string, ConnectionState>();
private readonly ChannelFactory _channelFactory = new ChannelFactory();
private Timer _timer;
public ConnectionManager()
{
_timer = new Timer(Scan, this, 0, 1000);
}
private static void Scan(object state)
{
((ConnectionManager)state).Scan();
}
private void Scan()
{
foreach (var c in _connections)
{
if (!c.Value.Alive && (DateTimeOffset.UtcNow - c.Value.LastSeen).TotalSeconds > 30)
{
ConnectionState s;
if (_connections.TryRemove(c.Key, out s))
{
s.Connection.Complete();
}
else
{
}
}
}
}
public string GetConnectionId(HttpContext context)
{
// REVIEW: Only check the query string for longpolling
var id = context.Request.Query["id"];
if (!StringValues.IsNullOrEmpty(id))
{
return id.ToString();
}
return Guid.NewGuid().ToString();
}
public bool TryGetConnection(string id, out ConnectionState state)
{
return _connections.TryGetValue(id, out state);
}
public bool AddConnection(string id, out ConnectionState state)
{
state = _connections.GetOrAdd(id, connectionId => new ConnectionState());
var isNew = state.Connection == null;
if (isNew)
{
state.Connection = new Connection
{
ConnectionId = id,
Input = _channelFactory.CreateChannel(),
Output = _channelFactory.CreateChannel()
};
}
state.LastSeen = DateTimeOffset.UtcNow;
state.Alive = true;
return isNew;
}
public void MarkConnectionDead(string id)
{
ConnectionState state;
if (_connections.TryGetValue(id, out state))
{
state.Alive = false;
}
}
public void RemoveConnection(string id)
{
ConnectionState state;
if (_connections.TryRemove(id, out state))
{
}
}
}
}

View File

@ -0,0 +1,11 @@
using System;
namespace WebApplication95
{
public class ConnectionState
{
public DateTimeOffset LastSeen { get; set; }
public bool Alive { get; set; } = true;
public Connection Connection { get; set; }
}
}

View File

@ -0,0 +1,143 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Channels;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives;
namespace WebApplication95
{
public class Dispatcher
{
private readonly ConnectionManager _manager = new ConnectionManager();
private readonly EndPoint _endpoint = new EndPoint();
public async Task Execute(HttpContext context)
{
if (context.Request.Path.StartsWithSegments("/send"))
{
var connectionId = context.Request.Query["id"];
if (StringValues.IsNullOrEmpty(connectionId))
{
throw new InvalidOperationException("Missing connection id");
}
ConnectionState state;
if (_manager.TryGetConnection(connectionId, out state))
{
// Write the message length
await context.Request.Body.CopyToAsync(state.Connection.Input);
}
}
else
{
var connectionId = _manager.GetConnectionId(context);
// Outgoing channels
if (context.Request.Path.StartsWithSegments("/sse"))
{
ConnectionState state;
_manager.AddConnection(connectionId, out state);
var sse = new ServerSentEvents(state);
var ignore = _endpoint.OnConnected(state.Connection);
state.Connection.TransportType = TransportType.ServerSentEvents;
await sse.ProcessRequest(context);
state.Connection.Complete();
_manager.RemoveConnection(connectionId);
}
else if (context.Request.Path.StartsWithSegments("/ws"))
{
ConnectionState state;
_manager.AddConnection(connectionId, out state);
var ws = new WebSockets(state);
var ignore = _endpoint.OnConnected(state.Connection);
state.Connection.TransportType = TransportType.WebSockets;
await ws.ProcessRequest(context);
state.Connection.Complete();
_manager.RemoveConnection(connectionId);
}
else if (context.Request.Path.StartsWithSegments("/poll"))
{
ConnectionState state;
bool newConnection = false;
if (_manager.AddConnection(connectionId, out state))
{
newConnection = true;
var ignore = _endpoint.OnConnected(state.Connection);
state.Connection.TransportType = TransportType.LongPolling;
}
var longPolling = new LongPolling(state);
await longPolling.ProcessRequest(newConnection, context);
_manager.MarkConnectionDead(connectionId);
}
}
}
}
public class EndPoint
{
private List<Connection> _connections = new List<Connection>();
public virtual async Task OnConnected(Connection connection)
{
lock (_connections)
{
_connections.Add(connection);
}
// Echo server
while (true)
{
var input = await connection.Input.ReadAsync();
try
{
if (input.IsEmpty && connection.Input.Reading.IsCompleted)
{
break;
}
List<Connection> connections = null;
lock (_connections)
{
connections = _connections;
}
foreach (var c in connections)
{
var output = c.Output.Alloc();
output.Append(ref input);
await output.FlushAsync();
}
}
finally
{
connection.Input.Advance(input.End);
}
}
lock (_connections)
{
_connections.Remove(connection);
}
}
}
}

View File

@ -0,0 +1,9 @@
using System;
namespace WebApplication95
{
public interface IDispatcher
{
void OnIncoming(ArraySegment<byte> data);
}
}

View File

@ -0,0 +1,138 @@
using System;
using System.Text;
using System.Threading.Tasks;
using Channels;
using Microsoft.AspNetCore.Http;
namespace WebApplication95
{
public class LongPolling
{
private Task _lastTask;
private object _lockObj = new object();
private bool _completed;
private TaskCompletionSource<object> _initTcs = new TaskCompletionSource<object>();
private TaskCompletionSource<object> _lifetime = new TaskCompletionSource<object>();
private HttpContext _context;
private readonly ConnectionState _state;
public LongPolling(ConnectionState state)
{
_lastTask = _initTcs.Task;
_state = state;
}
private Task Post(Func<object, Task> work, object state)
{
if (_completed)
{
return _lastTask;
}
lock (_lockObj)
{
_lastTask = _lastTask.ContinueWith((t, s1) => work(s1), state).Unwrap();
}
return _lastTask;
}
public async Task ProcessRequest(bool newConnection, HttpContext context)
{
context.Response.ContentType = "application/json";
// End the connection if the client goes away
context.RequestAborted.Register(state => OnConnectionAborted(state), this);
_context = context;
_initTcs.TrySetResult(null);
if (newConnection)
{
// Flush the connection id to the connection
var ignore = Send(default(ArraySegment<byte>));
}
else
{
// Send queue messages to the connection
var ignore = ProcessMessages(context);
}
await _lifetime.Task;
_completed = true;
}
private async Task ProcessMessages(HttpContext context)
{
var buffer = await _state.Connection.Output.ReadAsync();
foreach (var memory in buffer)
{
ArraySegment<byte> data;
if (memory.TryGetArray(out data))
{
await Send(data);
// Advance the buffer one block of memory
_state.Connection.Output.Advance(buffer.Slice(memory.Length).Start);
break;
}
}
}
private static void OnConnectionAborted(object state)
{
((LongPolling)state).CompleteRequest();
}
private void CompleteRequest()
{
Post(state =>
{
((TaskCompletionSource<object>)state).TrySetResult(null);
return Task.CompletedTask;
},
_lifetime);
}
public async Task Send(ArraySegment<byte> value)
{
await Post(async state =>
{
var data = ((ArraySegment<byte>)state);
// + 100 = laziness
var buffer = new byte[data.Count + _state.Connection.ConnectionId.Length + 100];
var at = 0;
buffer[at++] = (byte)'{';
buffer[at++] = (byte)'"';
buffer[at++] = (byte)'c';
buffer[at++] = (byte)'"';
buffer[at++] = (byte)':';
buffer[at++] = (byte)'"';
int count = Encoding.UTF8.GetBytes(_state.Connection.ConnectionId, 0, _state.Connection.ConnectionId.Length, buffer, at);
at += count;
buffer[at++] = (byte)'"';
if (data.Array != null)
{
buffer[at++] = (byte)',';
buffer[at++] = (byte)'"';
buffer[at++] = (byte)'d';
buffer[at++] = (byte)'"';
buffer[at++] = (byte)':';
//buffer[at++] = (byte)'"';
Buffer.BlockCopy(data.Array, data.Offset, buffer, at, data.Count);
}
at += data.Count;
//buffer[at++] = (byte)'"';
buffer[at++] = (byte)'}';
_context.Response.ContentLength = at;
await _context.Response.Body.WriteAsync(buffer, 0, at);
},
value);
CompleteRequest();
}
}
}

View File

@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
namespace WebApplication95
{
public class Program
{
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseKestrel(options =>
{
options.UseConnectionLogging();
})
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>()
.Build();
host.Run();
}
}
}

View File

@ -0,0 +1,126 @@
using System;
using System.Threading.Tasks;
using Channels;
using Microsoft.AspNetCore.Http;
namespace WebApplication95
{
public class ServerSentEvents
{
private Task _lastTask;
private object _lockObj = new object();
private bool _completed;
private TaskCompletionSource<object> _initTcs = new TaskCompletionSource<object>();
private TaskCompletionSource<object> _lifetime = new TaskCompletionSource<object>();
private HttpContext _context;
private readonly ConnectionState _state;
public ServerSentEvents(ConnectionState state)
{
_state = state;
_lastTask = _initTcs.Task;
var ignore = StartSending();
}
private Task Post(Func<object, Task> work, object state)
{
if (_completed)
{
return _lastTask;
}
lock (_lockObj)
{
_lastTask = _lastTask.ContinueWith((t, s1) => work(s1), state).Unwrap();
}
return _lastTask;
}
public async Task ProcessRequest(HttpContext context)
{
context.Response.ContentType = "text/event-stream";
// End the connection if the client goes away
context.RequestAborted.Register(state => OnConnectionAborted(state), this);
_context = context;
await _context.Response.WriteAsync($"data: {_state.Connection.ConnectionId}\n\n");
// Set the initial TCS when everything is setup
_initTcs.TrySetResult(null);
await _lifetime.Task;
_completed = true;
}
private static void OnConnectionAborted(object state)
{
((ServerSentEvents)state).OnConnectedAborted();
}
private void OnConnectedAborted()
{
Post(state =>
{
((TaskCompletionSource<object>)state).TrySetResult(null);
return Task.CompletedTask;
},
_lifetime);
}
private async Task StartSending()
{
await _initTcs.Task;
while (true)
{
var buffer = await _state.Connection.Output.ReadAsync();
if (buffer.IsEmpty && _state.Connection.Output.Reading.IsCompleted)
{
break;
}
foreach (var memory in buffer)
{
ArraySegment<byte> data;
if (memory.TryGetArray(out data))
{
await Send(data);
}
}
_state.Connection.Output.Advance(buffer.End);
}
_state.Connection.Output.CompleteReader();
}
private Task Send(ArraySegment<byte> value)
{
return Post(async state =>
{
var data = ((ArraySegment<byte>)state);
// TODO: Pooled buffers
// 8 = 6(data: ) + 2 (\n\n)
var buffer = new byte[8 + data.Count];
var at = 0;
buffer[at++] = (byte)'d';
buffer[at++] = (byte)'a';
buffer[at++] = (byte)'t';
buffer[at++] = (byte)'a';
buffer[at++] = (byte)':';
buffer[at++] = (byte)' ';
Buffer.BlockCopy(data.Array, data.Offset, buffer, at, data.Count);
at += data.Count;
buffer[at++] = (byte)'\n';
buffer[at++] = (byte)'\n';
await _context.Response.Body.WriteAsync(buffer, 0, at);
},
value);
}
}
}

View File

@ -0,0 +1,36 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace WebApplication95
{
public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(LogLevel.Debug);
app.UseFileServer();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
var dispatcher = new Dispatcher();
app.Run(async (context) =>
{
await dispatcher.Execute(context);
});
}
}
}

View File

@ -0,0 +1,25 @@
<?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)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>52ed8b3a-2dbb-448a-a708-faa0783b7917</ProjectGuid>
<RootNamespace>WebApplication95</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<ItemGroup>
<DnxInvisibleContent Include="bower.json" />
<DnxInvisibleContent Include=".bowerrc" />
</ItemGroup>
<Import Project="$(VSToolsPath)\DotNet.Web\Microsoft.DotNet.Web.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

View File

@ -0,0 +1,86 @@
using System;
using System.Net.WebSockets;
using System.Threading;
using System.Threading.Tasks;
using Channels;
using Microsoft.AspNetCore.Http;
namespace WebApplication95
{
public class WebSockets
{
private WebSocket _ws;
private ConnectionState _state;
private TaskCompletionSource<object> _tcs = new TaskCompletionSource<object>();
public WebSockets(ConnectionState state)
{
_state = state;
var ignore = StartSending();
}
private async Task StartSending()
{
await _tcs.Task;
while (true)
{
var buffer = await _state.Connection.Output.ReadAsync();
if (buffer.IsEmpty && _state.Connection.Output.Reading.IsCompleted)
{
break;
}
foreach (var memory in buffer)
{
ArraySegment<byte> data;
if (memory.TryGetArray(out data))
{
await _ws.SendAsync(data, WebSocketMessageType.Text, endOfMessage: true, cancellationToken: CancellationToken.None);
}
}
_state.Connection.Output.Advance(buffer.End);
}
_state.Connection.Output.CompleteReader();
}
public async Task ProcessRequest(HttpContext context)
{
if (!context.WebSockets.IsWebSocketRequest)
{
await Task.CompletedTask;
return;
}
var ws = await context.WebSockets.AcceptWebSocketAsync();
_ws = ws;
_tcs.TrySetResult(null);
var buffer = new byte[2048];
while (true)
{
var result = await ws.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
// TODO: Fragments
if (result.MessageType == WebSocketMessageType.Text)
{
await _state.Connection.Input.WriteAsync(new Span<byte>(buffer, 0, result.Count));
}
else if (result.MessageType == WebSocketMessageType.Binary)
{
await _state.Connection.Input.WriteAsync(new Span<byte>(buffer, 0, result.Count));
}
else if (result.MessageType == WebSocketMessageType.Close)
{
await ws.CloseAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None);
break;
}
}
}
}
}

View File

@ -0,0 +1,50 @@
{
"dependencies": {
"Channels": "0.2.0-beta-*",
"Microsoft.NETCore.App": {
"version": "1.0.0",
"type": "platform"
},
"Microsoft.AspNetCore.Diagnostics": "1.0.0",
"Microsoft.AspNetCore.StaticFiles": "1.0.0",
"Microsoft.AspNetCore.Server.IISIntegration": "1.0.0",
"Microsoft.AspNetCore.Server.Kestrel": "1.0.0",
"Microsoft.Extensions.Logging.Console": "1.0.0"
},
"tools": {
"Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final"
},
"frameworks": {
"netcoreapp1.0": {
"imports": [
"dotnet5.6",
"portable-net45+win8"
]
}
},
"buildOptions": {
"emitEntryPoint": true,
"preserveCompilationContext": true
},
"runtimeOptions": {
"configProperties": {
"System.GC.Server": true
}
},
"publishOptions": {
"include": [
"wwwroot",
"web.config"
]
},
"scripts": {
"postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ]
}
}

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<!--
Configure your application settings in appsettings.json. Learn more at http://go.microsoft.com/fwlink/?LinkId=786380
-->
<system.webServer>
<handlers>
<add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModule" resourceType="Unspecified"/>
</handlers>
<aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" forwardWindowsAuthToken="false"/>
</system.webServer>
</configuration>

View File

@ -0,0 +1,55 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
<script>
var connectionId;
function send() {
var body = document.getElementById('data').value;
var xhr = new XMLHttpRequest();
var url = '/send?id=' + connectionId;
xhr.open("POST", url, true);
xhr.setRequestHeader('Content-type', 'application/json');
xhr.onreadystatechange = function () {
if (xhr.readyState == 4 && xhr.status == 200) {
}
}
var data = JSON.stringify(body);
xhr.send(data);
}
var source = new EventSource('/sse');
source.onopen = function () {
console.log('Opened!');
};
source.onerror = function (err) {
console.log('Error: ' + err.type);
};
source.onmessage = function (data) {
if (!connectionId) {
connectionId = data.data;
return;
}
var child = document.createElement('li');
child.innerText = data.data;
document.getElementById('messages').appendChild(child);
};
</script>
</head>
<body>
<h1>Server Sent Events</h1>
<input type="text" id="data" />
<input type="button" value="Send" onclick="send()" />
<ul id="messages">
</ul>
</body>
</html>

View File

@ -0,0 +1,61 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
<script>
var connectionId;
function send() {
var body = document.getElementById('data').value;
var xhr = new XMLHttpRequest();
var url = '/send?id=' + connectionId;
xhr.open("POST", url, true);
xhr.setRequestHeader('Content-type', 'application/json');
xhr.onreadystatechange = function () {
if (xhr.readyState == 4 && xhr.status == 200) {
}
}
var data = JSON.stringify(body);
xhr.send(data);
}
function poll(id) {
var xhr = new XMLHttpRequest();
var url = '/poll' + (id == null ? '' : '?id=' + id);
xhr.open("POST", url, true);
xhr.onreadystatechange = function () {
if (xhr.readyState == 4 && xhr.status == 200) {
var json = JSON.parse(xhr.responseText);
var id = json.c;
var data = json.d;
if (data) {
var child = document.createElement('li');
child.innerText = data;
document.getElementById('messages').appendChild(child);
}
if (!connectionId) {
connectionId = id;
}
poll(id);
}
}
xhr.send(null);
}
poll();
</script>
</head>
<body>
<h1>Long Polling</h1>
<input type="text" id="data" />
<input type="button" value="Send" onclick="send()" />
<ul id="messages"></ul>
</body>
</html>