1+ namespace SqlStreamStore . HAL . DevServer
2+ {
3+ using System ;
4+ using System . Collections . Generic ;
5+ using System . Linq ;
6+ using System . Net . Http ;
7+ using System . Net . Http . Headers ;
8+ using System . Net . WebSockets ;
9+ using System . Threading ;
10+ using System . Threading . Tasks ;
11+ using Microsoft . AspNetCore . Builder ;
12+ using Microsoft . AspNetCore . Http ;
13+ using Microsoft . Extensions . Primitives ;
14+ using MidFunc = System . Func <
15+ Microsoft . AspNetCore . Http . HttpContext ,
16+ System . Func < System . Threading . Tasks . Task > ,
17+ System . Threading . Tasks . Task
18+ > ;
19+
20+ internal static class SqlStreamStoreBrowserExtensions
21+ {
22+ public static IApplicationBuilder UseSqlStreamStoreBrowser (
23+ this IApplicationBuilder builder )
24+ {
25+ var httpClient = new HttpClient ( ) ;
26+
27+ return builder
28+ . UseWebSockets ( )
29+ . Use ( ForwardWebsockets ( httpClient ) )
30+ . Use ( ForwardAcceptHtml ( httpClient ) )
31+ . Use ( ForwardStaticFiles ( httpClient ) ) ;
32+ }
33+
34+ private static MidFunc ForwardWebsockets ( HttpClient httpClient ) => ( context , next )
35+ => context . WebSockets . IsWebSocketRequest
36+ ? ForwardWebsocketToClientDevServer ( context )
37+ : ( context . Request . Path . StartsWithSegments ( new PathString ( "/sockjs-node" ) )
38+ ? ForwardToClientDevServer ( httpClient , context )
39+ : next ( ) ) ;
40+
41+
42+ private static MidFunc ForwardStaticFiles ( HttpClient httpClient ) => ( context , next )
43+ => context . Request . Path . StartsWithSegments ( new PathString ( "/static" ) )
44+ || context . Request . Path . StartsWithSegments ( "/__webpack_dev_server__" )
45+ ? ForwardToClientDevServer (
46+ httpClient ,
47+ context )
48+ : next ( ) ;
49+
50+ private static MidFunc ForwardAcceptHtml ( HttpClient httpClient ) => ( context , next )
51+ => GetAcceptHeaders ( context . Request )
52+ . Any ( header => header == "text/html" )
53+ ? ForwardToClientDevServer (
54+ httpClient ,
55+ context ,
56+ context . Request . PathBase . ToUriComponent ( ) )
57+ : next ( ) ;
58+
59+ private static Task ForwardToClientDevServer (
60+ HttpClient httpClient ,
61+ HttpContext context )
62+ => ForwardToClientDevServer ( httpClient , context , context . Request . Path ) ;
63+
64+ private static async Task ForwardToClientDevServer (
65+ HttpClient httpClient ,
66+ HttpContext context ,
67+ PathString path )
68+ {
69+ using ( var request = BuildRequest ( context , path ) )
70+ using ( var response = await httpClient . SendAsync ( request ) )
71+ using ( var stream = await response . Content . ReadAsStreamAsync ( ) )
72+ {
73+ context . Response . StatusCode = ( int ) response . StatusCode ;
74+
75+ var headers = from header in response . Headers . Concat (
76+ response . Content ? . Headers
77+ ?? Enumerable . Empty < KeyValuePair < string , IEnumerable < string > > > ( ) )
78+ where ! "transfer-encoding" . Equals ( header . Key , StringComparison . InvariantCultureIgnoreCase )
79+ select header ;
80+
81+ foreach ( var header in headers )
82+ {
83+ context . Response . Headers . Add ( header . Key , new StringValues ( header . Value . ToArray ( ) ) ) ;
84+ }
85+
86+ await stream . CopyToAsync ( context . Response . Body , 8196 , context . RequestAborted ) ;
87+
88+ await context . Response . Body . FlushAsync ( context . RequestAborted ) ;
89+ }
90+ }
91+
92+ private static HttpRequestMessage BuildRequest ( HttpContext context , PathString path )
93+ {
94+ var request = new HttpRequestMessage (
95+ new HttpMethod ( context . Request . Method ) ,
96+ new UriBuilder
97+ {
98+ Port = 3000 ,
99+ Path = path . ToUriComponent ( ) ,
100+ Query = context . Request . QueryString . ToUriComponent ( )
101+ } . Uri ) ;
102+
103+ foreach ( var header in context . Request . Headers )
104+ {
105+ var values = header . Value . ToArray ( ) ;
106+
107+ request . Headers . TryAddWithoutValidation ( header . Key , values ) ;
108+ request . Content ? . Headers . TryAddWithoutValidation ( header . Key , values ) ;
109+ }
110+
111+ return request ;
112+ }
113+
114+ private static string [ ] GetAcceptHeaders ( HttpRequest contextRequest )
115+ => Array . ConvertAll (
116+ contextRequest . Headers . GetCommaSeparatedValues ( "Accept" ) ,
117+ value => MediaTypeWithQualityHeaderValue . TryParse ( value , out var header )
118+ ? header . MediaType
119+ : null ) ;
120+
121+ static async Task ForwardWebsocketToClientDevServer ( HttpContext context )
122+ {
123+ var socket = await context . WebSockets . AcceptWebSocketAsync ( ) ;
124+
125+ using ( var forwarder = new WebsocketForwarder ( socket ) )
126+ {
127+ await forwarder . SendAndReceive ( context . RequestAborted ) ;
128+ }
129+ }
130+
131+ private class WebsocketForwarder : IDisposable
132+ {
133+ private readonly WebSocket _socket ;
134+ private readonly ClientWebSocket _client ;
135+
136+ public WebsocketForwarder ( WebSocket socket )
137+ {
138+ _socket = socket ;
139+ _client = new ClientWebSocket ( ) ;
140+ }
141+
142+ public async Task SendAndReceive ( CancellationToken ct )
143+ {
144+ await _client . ConnectAsync ( new UriBuilder
145+ {
146+ Port = 3000
147+ } . Uri ,
148+ ct ) ;
149+
150+ await Task . WhenAll ( Send ( ct ) , Receive ( ct ) ) ;
151+ }
152+
153+ private async Task Send ( CancellationToken ct )
154+ {
155+ var receiveBuffer = new byte [ 4096 ] ;
156+
157+ while ( _socket . State == WebSocketState . Open )
158+ {
159+ var buffer = new ArraySegment < byte > ( receiveBuffer ) ;
160+
161+ var result = await _socket . ReceiveAsync (
162+ buffer ,
163+ ct ) ;
164+
165+ if ( result . MessageType == WebSocketMessageType . Close )
166+ {
167+ await _client . CloseAsync (
168+ result . CloseStatus ?? WebSocketCloseStatus . Empty ,
169+ result . CloseStatusDescription ,
170+ ct ) ;
171+
172+ return ;
173+ }
174+
175+ await _client . SendAsync ( buffer , result . MessageType , result . EndOfMessage , ct ) ;
176+ }
177+ }
178+
179+ private async Task Receive ( CancellationToken ct )
180+ {
181+ var sendBuffer = new byte [ 4096 ] ;
182+
183+ while ( _socket . State == WebSocketState . Open )
184+ {
185+ var buffer = new ArraySegment < byte > ( sendBuffer ) ;
186+
187+ var result = await _client . ReceiveAsync (
188+ buffer ,
189+ ct ) ;
190+
191+ if ( result . MessageType == WebSocketMessageType . Close )
192+ {
193+ await _socket . CloseAsync (
194+ result . CloseStatus ?? WebSocketCloseStatus . Empty ,
195+ result . CloseStatusDescription ,
196+ ct ) ;
197+
198+ return ;
199+ }
200+
201+ await _socket . SendAsync ( buffer , result . MessageType , result . EndOfMessage , ct ) ;
202+ }
203+ }
204+
205+ public void Dispose ( ) => _client ? . Dispose ( ) ;
206+ }
207+ }
208+ }
0 commit comments