Skip to content

Commit bfbe7fb

Browse files
committed
WebSocket Support
1 parent 8bc8add commit bfbe7fb

File tree

13 files changed

+734
-13
lines changed

13 files changed

+734
-13
lines changed

PowerShellEditorServices.sln

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PowerShellEditorServices.Te
4141
EndProject
4242
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PowerShellEditorServices.Host.x86", "src\PowerShellEditorServices.Host.x86\PowerShellEditorServices.Host.x86.csproj", "{0E720BF8-84E3-4C56-BCEA-7D6FD34D8948}"
4343
EndProject
44+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PowerShellEditorServices.Channel.WebSocket", "src\PowerShellEditorServices.Channel.WebSocket\PowerShellEditorServices.Channel.WebSocket.csproj", "{A6663F64-3C3D-461F-8A05-0CC4CA7A9945}"
45+
EndProject
46+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PowerShellEditorServices.Channel.WebSocket.Test", "test\PowerShellEditorServices.Channel.WebSocket.Test\PowerShellEditorServices.Channel.WebSocket.Test.csproj", "{9D98120C-9601-4678-AD50-EF2808DABAC9}"
47+
EndProject
4448
Global
4549
GlobalSection(SolutionConfigurationPlatforms) = preSolution
4650
Debug|Any CPU = Debug|Any CPU
@@ -87,6 +91,14 @@ Global
8791
{0E720BF8-84E3-4C56-BCEA-7D6FD34D8948}.Debug|Any CPU.Build.0 = Debug|Any CPU
8892
{0E720BF8-84E3-4C56-BCEA-7D6FD34D8948}.Release|Any CPU.ActiveCfg = Release|Any CPU
8993
{0E720BF8-84E3-4C56-BCEA-7D6FD34D8948}.Release|Any CPU.Build.0 = Release|Any CPU
94+
{A6663F64-3C3D-461F-8A05-0CC4CA7A9945}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
95+
{A6663F64-3C3D-461F-8A05-0CC4CA7A9945}.Debug|Any CPU.Build.0 = Debug|Any CPU
96+
{A6663F64-3C3D-461F-8A05-0CC4CA7A9945}.Release|Any CPU.ActiveCfg = Release|Any CPU
97+
{A6663F64-3C3D-461F-8A05-0CC4CA7A9945}.Release|Any CPU.Build.0 = Release|Any CPU
98+
{9D98120C-9601-4678-AD50-EF2808DABAC9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
99+
{9D98120C-9601-4678-AD50-EF2808DABAC9}.Debug|Any CPU.Build.0 = Debug|Any CPU
100+
{9D98120C-9601-4678-AD50-EF2808DABAC9}.Release|Any CPU.ActiveCfg = Release|Any CPU
101+
{9D98120C-9601-4678-AD50-EF2808DABAC9}.Release|Any CPU.Build.0 = Release|Any CPU
90102
EndGlobalSection
91103
GlobalSection(SolutionProperties) = preSolution
92104
HideSolutionNode = FALSE
@@ -102,5 +114,7 @@ Global
102114
{F8A0946A-5D25-4651-8079-B8D5776916FB} = {F594E7FD-1E72-4E51-A496-B019C2BA3180}
103115
{E3A5CF5D-6E41-44AC-AE0A-4C227E4BACD4} = {422E561A-8118-4BE7-A54F-9309E4F03AAE}
104116
{0E720BF8-84E3-4C56-BCEA-7D6FD34D8948} = {F594E7FD-1E72-4E51-A496-B019C2BA3180}
117+
{A6663F64-3C3D-461F-8A05-0CC4CA7A9945} = {F594E7FD-1E72-4E51-A496-B019C2BA3180}
118+
{9D98120C-9601-4678-AD50-EF2808DABAC9} = {422E561A-8118-4BE7-A54F-9309E4F03AAE}
105119
EndGlobalSection
106120
EndGlobal
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3+
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
4+
<PropertyGroup>
5+
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
6+
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
7+
<ProjectGuid>{A6663F64-3C3D-461F-8A05-0CC4CA7A9945}</ProjectGuid>
8+
<OutputType>Library</OutputType>
9+
<AppDesignerFolder>Properties</AppDesignerFolder>
10+
<RootNamespace>PowerShellEditorServices.Channel.WebSocket</RootNamespace>
11+
<AssemblyName>PowerShellEditorServices.Channel.WebSocket</AssemblyName>
12+
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
13+
<FileAlignment>512</FileAlignment>
14+
</PropertyGroup>
15+
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
16+
<DebugSymbols>true</DebugSymbols>
17+
<DebugType>full</DebugType>
18+
<Optimize>false</Optimize>
19+
<OutputPath>bin\Debug\</OutputPath>
20+
<DefineConstants>DEBUG;TRACE</DefineConstants>
21+
<ErrorReport>prompt</ErrorReport>
22+
<WarningLevel>4</WarningLevel>
23+
</PropertyGroup>
24+
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
25+
<DebugType>pdbonly</DebugType>
26+
<Optimize>true</Optimize>
27+
<OutputPath>bin\Release\</OutputPath>
28+
<DefineConstants>TRACE</DefineConstants>
29+
<ErrorReport>prompt</ErrorReport>
30+
<WarningLevel>4</WarningLevel>
31+
</PropertyGroup>
32+
<ItemGroup>
33+
<Reference Include="Microsoft.Owin, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
34+
<HintPath>..\..\packages\Microsoft.Owin.3.0.0\lib\net45\Microsoft.Owin.dll</HintPath>
35+
<Private>True</Private>
36+
</Reference>
37+
<Reference Include="Microsoft.Practices.ServiceLocation, Version=1.3.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
38+
<HintPath>..\..\packages\CommonServiceLocator.1.3\lib\portable-net4+sl5+netcore45+wpa81+wp8\Microsoft.Practices.ServiceLocation.dll</HintPath>
39+
<Private>True</Private>
40+
</Reference>
41+
<Reference Include="Owin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f0ebd12fd5e55cc5, processorArchitecture=MSIL">
42+
<HintPath>..\..\packages\Owin.1.0\lib\net40\Owin.dll</HintPath>
43+
<Private>True</Private>
44+
</Reference>
45+
<Reference Include="Owin.WebSocket, Version=1.5.0.0, Culture=neutral, processorArchitecture=MSIL">
46+
<HintPath>..\..\packages\Owin.WebSocket.1.5.0\lib\net45\Owin.WebSocket.dll</HintPath>
47+
<Private>True</Private>
48+
</Reference>
49+
<Reference Include="System" />
50+
<Reference Include="System.Core" />
51+
<Reference Include="System.Xml.Linq" />
52+
<Reference Include="System.Data.DataSetExtensions" />
53+
<Reference Include="Microsoft.CSharp" />
54+
<Reference Include="System.Data" />
55+
<Reference Include="System.Net.Http" />
56+
<Reference Include="System.Xml" />
57+
</ItemGroup>
58+
<ItemGroup>
59+
<Compile Include="Properties\AssemblyInfo.cs" />
60+
<Compile Include="WebsocketClientChannel.cs" />
61+
<Compile Include="WebsocketServerChannel.cs" />
62+
</ItemGroup>
63+
<ItemGroup>
64+
<ProjectReference Include="..\PowerShellEditorServices.Protocol\PowerShellEditorServices.Protocol.csproj">
65+
<Project>{f8a0946a-5d25-4651-8079-b8d5776916fb}</Project>
66+
<Name>PowerShellEditorServices.Protocol</Name>
67+
</ProjectReference>
68+
<ProjectReference Include="..\PowerShellEditorServices\PowerShellEditorServices.csproj">
69+
<Project>{81e8cbcd-6319-49e7-9662-0475bd0791f4}</Project>
70+
<Name>PowerShellEditorServices</Name>
71+
</ProjectReference>
72+
</ItemGroup>
73+
<ItemGroup>
74+
<None Include="packages.config" />
75+
</ItemGroup>
76+
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
77+
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
78+
Other similar extension points exist, see Microsoft.Common.targets.
79+
<Target Name="BeforeBuild">
80+
</Target>
81+
<Target Name="AfterBuild">
82+
</Target>
83+
-->
84+
</Project>
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
using System.Reflection;
2+
using System.Runtime.CompilerServices;
3+
using System.Runtime.InteropServices;
4+
5+
// General Information about an assembly is controlled through the following
6+
// set of attributes. Change these attribute values to modify the information
7+
// associated with an assembly.
8+
[assembly: AssemblyTitle("PowerShellEditorServices.Channel.WebSocket")]
9+
[assembly: AssemblyDescription("")]
10+
[assembly: AssemblyConfiguration("")]
11+
[assembly: AssemblyCompany("")]
12+
[assembly: AssemblyProduct("PowerShellEditorServices.Channel.WebSocket")]
13+
[assembly: AssemblyCopyright("Copyright © 2015")]
14+
[assembly: AssemblyTrademark("")]
15+
[assembly: AssemblyCulture("")]
16+
17+
// Setting ComVisible to false makes the types in this assembly not visible
18+
// to COM components. If you need to access a type in this assembly from
19+
// COM, set the ComVisible attribute to true on that type.
20+
[assembly: ComVisible(false)]
21+
22+
// The following GUID is for the ID of the typelib if this project is exposed to COM
23+
[assembly: Guid("a6663f64-3c3d-461f-8a05-0cc4ca7a9945")]
24+
25+
// Version information for an assembly consists of the following four values:
26+
//
27+
// Major Version
28+
// Minor Version
29+
// Build Number
30+
// Revision
31+
//
32+
// You can specify all the values or you can default the Build and Revision Numbers
33+
// by using the '*' as shown below:
34+
// [assembly: AssemblyVersion("1.0.*")]
35+
[assembly: AssemblyVersion("1.0.0.0")]
36+
[assembly: AssemblyFileVersion("1.0.0.0")]
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
using System;
2+
using System.IO;
3+
using System.Linq;
4+
using System.Net.WebSockets;
5+
using System.Threading;
6+
using System.Threading.Tasks;
7+
using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol;
8+
using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol.Channel;
9+
using Microsoft.PowerShell.EditorServices.Utility;
10+
11+
namespace PowerShellEditorServices.Channel.WebSocket
12+
{
13+
/// <summary>
14+
/// Implementation of <see cref="ChannelBase"/> that enables WebSocket communication.
15+
/// </summary>
16+
public class WebsocketClientChannel : ChannelBase
17+
{
18+
private readonly string serverUrl;
19+
private ClientWebSocket socket;
20+
private ClientWebSocketStream inputStream;
21+
private ClientWebSocketStream outputStream;
22+
23+
/// <summary>
24+
/// Gets the process ID of the server process.
25+
/// </summary>
26+
public int ProcessId { get; private set; }
27+
28+
/// <summary>
29+
/// Initializes an instance of the WebsocketClientChannel.
30+
/// </summary>
31+
/// <param name="url">The full path to the server process executable.</param>
32+
public WebsocketClientChannel(string url)
33+
{
34+
this.serverUrl = url;
35+
}
36+
37+
protected override void Initialize(IMessageSerializer messageSerializer)
38+
{
39+
try
40+
{
41+
this.socket = new ClientWebSocket();
42+
this.socket.ConnectAsync(new Uri(serverUrl), CancellationToken.None).Wait();
43+
}
44+
catch (AggregateException ex)
45+
{
46+
var wsException= ex.InnerExceptions.FirstOrDefault() as WebSocketException;
47+
if (wsException != null)
48+
{
49+
Logger.Write(LogLevel.Warning,
50+
string.Format("Failed to connect to WebSocket server. Error was '{0}'", wsException.Message));
51+
52+
}
53+
54+
throw;
55+
}
56+
57+
this.inputStream = new ClientWebSocketStream(socket);
58+
this.outputStream = new ClientWebSocketStream(socket);
59+
60+
// Set up the message reader and writer
61+
this.MessageReader =
62+
new MessageReader(
63+
this.inputStream,
64+
messageSerializer);
65+
66+
this.MessageWriter =
67+
new MessageWriter(
68+
this.outputStream,
69+
messageSerializer);
70+
}
71+
72+
protected override void Shutdown()
73+
{
74+
if (this.MessageReader != null)
75+
{
76+
this.MessageReader = null;
77+
}
78+
79+
if (this.MessageWriter != null)
80+
{
81+
this.MessageWriter = null;
82+
}
83+
84+
if (this.socket != null)
85+
{
86+
socket.Dispose();
87+
}
88+
}
89+
}
90+
91+
/// <summary>
92+
/// Extension of <see cref="MemoryStream"/> that sends data to a WebSocket during FlushAsync
93+
/// and reads during WriteAsync.
94+
/// </summary>
95+
internal class ClientWebSocketStream : MemoryStream
96+
{
97+
private readonly ClientWebSocket socket;
98+
99+
/// <summary>
100+
/// Constructor
101+
/// </summary>
102+
/// <remarks>
103+
/// It is expected that the socket is in an Open state.
104+
/// </remarks>
105+
/// <param name="socket"></param>
106+
public ClientWebSocketStream(ClientWebSocket socket)
107+
{
108+
this.socket = socket;
109+
}
110+
111+
/// <summary>
112+
/// Reads from the WebSocket.
113+
/// </summary>
114+
/// <param name="buffer"></param>
115+
/// <param name="offset"></param>
116+
/// <param name="count"></param>
117+
/// <param name="cancellationToken"></param>
118+
/// <returns></returns>
119+
public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
120+
{
121+
if (socket.State != WebSocketState.Open)
122+
{
123+
return 0;
124+
}
125+
126+
WebSocketReceiveResult result;
127+
do
128+
{
129+
result = await socket.ReceiveAsync(new ArraySegment<byte>(buffer, offset, count), cancellationToken);
130+
} while (!result.EndOfMessage);
131+
132+
if (result.MessageType == WebSocketMessageType.Close)
133+
{
134+
await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", cancellationToken);
135+
return 0;
136+
}
137+
138+
return result.Count;
139+
}
140+
141+
/// <summary>
142+
/// Sends the data in the stream to the buffer and clears the stream.
143+
/// </summary>
144+
/// <param name="cancellationToken"></param>
145+
/// <returns></returns>
146+
public override async Task FlushAsync(CancellationToken cancellationToken)
147+
{
148+
await socket.SendAsync(new ArraySegment<byte>(ToArray()), WebSocketMessageType.Binary, true, cancellationToken);
149+
SetLength(0);
150+
}
151+
}
152+
}

0 commit comments

Comments
 (0)