11namespace SqlStreamStore . HAL . DevServer
22{
33 using System ;
4+ using System . Linq ;
5+ using System . Net . Http ;
6+ using System . Net . Http . Headers ;
47 using System . Threading . Tasks ;
58 using Microsoft . AspNetCore . Builder ;
69 using Microsoft . AspNetCore . Hosting ;
710 using Microsoft . AspNetCore . Http ;
811 using Microsoft . Extensions . DependencyInjection ;
12+ using Microsoft . Extensions . Primitives ;
913 using MidFunc = System . Func <
1014 Microsoft . AspNetCore . Http . HttpContext ,
1115 System . Func < System . Threading . Tasks . Task > ,
1519 internal class DevServerStartup : IStartup
1620 {
1721 private readonly IStreamStore _streamStore ;
22+ private readonly HttpClient _httpClient ;
1823
1924 public DevServerStartup ( IStreamStore streamStore )
2025 {
2126 _streamStore = streamStore ;
27+ _httpClient = new HttpClient ( ) ;
2228 }
2329
2430 public IServiceProvider ConfigureServices ( IServiceCollection services ) => services
@@ -27,8 +33,10 @@ public IServiceProvider ConfigureServices(IServiceCollection services) => servic
2733
2834 public void Configure ( IApplicationBuilder app ) => app
2935 . UseResponseCompression ( )
36+ . Use ( VaryAccept )
3037 . Use ( CatchAndDisplayErrors )
31- . Use ( AllowAllOrigins )
38+ . Use ( SqlStreamStreamBrowserJavascript )
39+ . Use ( SqlStreamStreamBrowserHtml )
3240 . UseSqlStreamStoreHal ( _streamStore ) ;
3341
3442 private static MidFunc CatchAndDisplayErrors => async ( context , next ) =>
@@ -43,19 +51,81 @@ public void Configure(IApplicationBuilder app) => app
4351 }
4452 } ;
4553
46- // don't actually do this in production
47- private static MidFunc AllowAllOrigins => ( context , next ) =>
54+ private static MidFunc VaryAccept => ( context , next ) =>
4855 {
49- context . Response . OnStarting ( _ =>
50- {
51- var response = ( HttpResponse ) _ ;
52- response . Headers [ "Access-Control-Allow-Origin" ] = "*" ;
56+ Task Vary ( object state )
57+ {
58+ var response = ( HttpResponse ) state ;
59+
60+ response . Headers . AppendCommaSeparatedValues ( "Vary" , "Accept" ) ;
61+
62+ return Task . CompletedTask ;
63+ }
64+
65+ context . Response . OnStarting ( Vary , context . Response ) ;
66+
67+ return next ( ) ;
68+ } ;
5369
54- return Task . CompletedTask ;
55- } ,
56- context . Response ) ;
70+ private MidFunc SqlStreamStreamBrowserJavascript => ( context , next ) =>
71+ {
72+ if ( context . Request . Path . Value ? . EndsWith ( ".js" ) ?? false )
73+ {
74+ var segments = context . Request . Path . ToUriComponent ( ) . Split ( '/' ) ;
75+ if ( segments . Length > 2 )
76+ {
77+ return RedirectToPathBase ( context , $ "/{ segments . Last ( ) } ") ;
78+ }
5779
80+ return ForwardToClientDevServer (
81+ context ,
82+ context . Request . PathBase + context . Request . Path ) ;
83+ }
5884 return next ( ) ;
5985 } ;
86+
87+ private MidFunc SqlStreamStreamBrowserHtml => ( context , next )
88+ => GetAcceptHeaders ( context . Request )
89+ . Any ( header => header == "text/html" )
90+ ? ForwardToClientDevServer ( context , context . Request . PathBase . ToUriComponent ( ) )
91+ : next ( ) ;
92+
93+ private static string [ ] GetAcceptHeaders ( HttpRequest contextRequest )
94+ => Array . ConvertAll (
95+ contextRequest . Headers . GetCommaSeparatedValues ( "Accept" ) ,
96+ value => MediaTypeWithQualityHeaderValue . TryParse ( value , out var header )
97+ ? header . MediaType
98+ : null ) ;
99+
100+ private Task RedirectToPathBase ( HttpContext context , PathString path )
101+ {
102+ context . Response . Redirect ( context . Request . PathBase + path ) ;
103+
104+ return Task . CompletedTask ;
105+ }
106+
107+ private async Task ForwardToClientDevServer ( HttpContext context , PathString path )
108+ {
109+ using ( var request = new HttpRequestMessage (
110+ new HttpMethod ( context . Request . Method ) ,
111+ new UriBuilder
112+ {
113+ Port = 3000 ,
114+ Host = "localhost" ,
115+ Path = path . ToUriComponent ( ) ,
116+ Query = context . Request . QueryString . ToUriComponent ( )
117+ } . Uri ) )
118+ using ( var response = await _httpClient . SendAsync ( request ) )
119+ using ( var stream = await response . Content . ReadAsStreamAsync ( ) )
120+ {
121+ context . Response . StatusCode = ( int ) response . StatusCode ;
122+ foreach ( var header in response . Headers . Concat ( response . Content . Headers ) )
123+ {
124+ context . Response . Headers . Add ( header . Key , new StringValues ( header . Value . ToArray ( ) ) ) ;
125+ }
126+
127+ await stream . CopyToAsync ( context . Response . Body , 8192 , context . RequestAborted ) ;
128+ }
129+ }
60130 }
61131}
0 commit comments