Skip to content

Commit 0d79b52

Browse files
jodavisjodavis
authored andcommitted
Move BrowserLink.Runtime into its own .NET Core package.
1 parent 025a729 commit 0d79b52

45 files changed

Lines changed: 6648 additions & 2 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

BrowserLink.sln

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

22
Microsoft Visual Studio Solution File, Format Version 12.00
33
# Visual Studio 14
4-
VisualStudioVersion = 14.0.23107.0
4+
VisualStudioVersion = 14.0.25420.1
55
MinimumVisualStudioVersion = 10.0.40219.1
66
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{24D02C4B-DF8B-4D12-9F28-566302D585C8}"
77
ProjectSection(SolutionItems) = preProject
@@ -12,6 +12,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{7AC709A9-E84
1212
EndProject
1313
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.VisualStudio.Web.BrowserLink.Loader", "src\Microsoft.VisualStudio.Web.BrowserLink.Loader\Microsoft.VisualStudio.Web.BrowserLink.Loader.xproj", "{C31202E6-FC58-4354-B2D2-ACD1B3118C05}"
1414
EndProject
15+
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.VisualStudio.Web.BrowserLink", "src\Microsoft.VisualStudio.Web.BrowserLink\Microsoft.VisualStudio.Web.BrowserLink.xproj", "{616F445C-7A41-4E61-98E3-1B70D70F9F09}"
16+
EndProject
17+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{7F6F47EA-CAF5-46E2-B29C-8B20291185B7}"
18+
EndProject
19+
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.VisualStudio.Web.BrowserLink.Test", "test\Microsoft.VisualStudio.Web.BrowserLink.Test\Microsoft.VisualStudio.Web.BrowserLink.Test.xproj", "{96146399-2C42-4CAA-ABCD-CF1E0F000CEC}"
20+
EndProject
1521
Global
1622
GlobalSection(SolutionConfigurationPlatforms) = preSolution
1723
Debug|Any CPU = Debug|Any CPU
@@ -22,11 +28,21 @@ Global
2228
{C31202E6-FC58-4354-B2D2-ACD1B3118C05}.Debug|Any CPU.Build.0 = Debug|Any CPU
2329
{C31202E6-FC58-4354-B2D2-ACD1B3118C05}.Release|Any CPU.ActiveCfg = Release|Any CPU
2430
{C31202E6-FC58-4354-B2D2-ACD1B3118C05}.Release|Any CPU.Build.0 = Release|Any CPU
31+
{616F445C-7A41-4E61-98E3-1B70D70F9F09}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
32+
{616F445C-7A41-4E61-98E3-1B70D70F9F09}.Debug|Any CPU.Build.0 = Debug|Any CPU
33+
{616F445C-7A41-4E61-98E3-1B70D70F9F09}.Release|Any CPU.ActiveCfg = Release|Any CPU
34+
{616F445C-7A41-4E61-98E3-1B70D70F9F09}.Release|Any CPU.Build.0 = Release|Any CPU
35+
{96146399-2C42-4CAA-ABCD-CF1E0F000CEC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
36+
{96146399-2C42-4CAA-ABCD-CF1E0F000CEC}.Debug|Any CPU.Build.0 = Debug|Any CPU
37+
{96146399-2C42-4CAA-ABCD-CF1E0F000CEC}.Release|Any CPU.ActiveCfg = Release|Any CPU
38+
{96146399-2C42-4CAA-ABCD-CF1E0F000CEC}.Release|Any CPU.Build.0 = Release|Any CPU
2539
EndGlobalSection
2640
GlobalSection(SolutionProperties) = preSolution
2741
HideSolutionNode = FALSE
2842
EndGlobalSection
2943
GlobalSection(NestedProjects) = preSolution
3044
{C31202E6-FC58-4354-B2D2-ACD1B3118C05} = {7AC709A9-E84C-4C8F-8185-8A3D01DD7D7C}
45+
{616F445C-7A41-4E61-98E3-1B70D70F9F09} = {7AC709A9-E84C-4C8F-8185-8A3D01DD7D7C}
46+
{96146399-2C42-4CAA-ABCD-CF1E0F000CEC} = {7F6F47EA-CAF5-46E2-B29C-8B20291185B7}
3147
EndGlobalSection
3248
EndGlobal

global.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
"projects": [ "src" ]
2+
"projects": [ "src", "test" ]
33
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System.Reflection;
5+
using System.Resources;
6+
using System.Runtime.CompilerServices;
7+
8+
[assembly: AssemblyMetadata("Serviceable", "True")]
9+
[assembly: NeutralResourcesLanguage("en-us")]
10+
[assembly: AssemblyCompany("Microsoft Corporation.")]
11+
[assembly: AssemblyCopyright("© Microsoft Corporation. All rights reserved.")]
12+
[assembly: AssemblyProduct("Microsoft Visual Studio")]
13+
14+
[assembly: InternalsVisibleTo("Microsoft.VisualStudio.Web.BrowserLink.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
15+
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using Microsoft.AspNetCore.Hosting;
6+
using Microsoft.VisualStudio.Web.BrowserLink;
7+
8+
namespace Microsoft.AspNetCore.Builder
9+
{
10+
/// <summary>
11+
/// Implementation of extension methods for configuring Browser Link
12+
/// in an ASP.NET Core application.
13+
/// </summary>
14+
public static class BrowserLinkExtensions
15+
{
16+
/// <summary>
17+
/// This method is called to enable Browser Link in an application. It
18+
/// registers a factory method that creates BrowserLinkMiddleware for
19+
/// each request.
20+
/// </summary>
21+
public static IApplicationBuilder UseBrowserLink(this IApplicationBuilder app)
22+
{
23+
if (!IsMicrosoftRuntime)
24+
{
25+
return app;
26+
}
27+
28+
try
29+
{
30+
string applicationBasePath;
31+
32+
if (GetApplicationBasePath(app, out applicationBasePath))
33+
{
34+
applicationBasePath = PathUtil.NormalizeDirectoryPath(applicationBasePath);
35+
36+
HostConnectionUtil.SignalHostForStartup(applicationBasePath, blockUntilStarted: false);
37+
38+
BrowserLinkMiddlewareFactory factory = new BrowserLinkMiddlewareFactory(applicationBasePath);
39+
40+
return app.Use(factory.CreateBrowserLinkMiddleware);
41+
}
42+
else
43+
{
44+
// Browser Link doesn't work if we don't have an application path
45+
return app;
46+
}
47+
}
48+
catch
49+
{
50+
// Something went wrong initializing the runtime. Browser Link won't work.
51+
return app;
52+
}
53+
}
54+
55+
private static bool IsMicrosoftRuntime
56+
{
57+
get { return Type.GetType("Mono.Runtime") == null; }
58+
}
59+
60+
private static bool GetApplicationBasePath(IApplicationBuilder app, out string applicationBasePath)
61+
{
62+
IHostingEnvironment hostingEnvironment = app.ApplicationServices.GetService(typeof(IHostingEnvironment)) as IHostingEnvironment;
63+
64+
if (hostingEnvironment != null)
65+
{
66+
applicationBasePath = hostingEnvironment.ContentRootPath;
67+
68+
return applicationBasePath != null;
69+
}
70+
71+
applicationBasePath = null;
72+
return false;
73+
}
74+
}
75+
}
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Threading.Tasks;
6+
using Microsoft.AspNetCore.Http;
7+
8+
namespace Microsoft.VisualStudio.Web.BrowserLink
9+
{
10+
/// <summary>
11+
/// This class is created once, and invoked for each request. It puts a filter on
12+
/// the Response.Body, which will inject Browser Link's script links into the
13+
/// content (if the content is HTML).
14+
/// </summary>
15+
internal class BrowserLinkMiddleware
16+
{
17+
// Number of timeouts allowed before we stop trying to connect to the host
18+
private const int FilterRequestTimeoutLimit = 2;
19+
20+
// Number of timeouts that occurred while attempting to connect to the host
21+
private static int _filterRequestTimeouts = 0;
22+
23+
private RequestDelegate _next;
24+
private string _applicationPath;
25+
26+
internal BrowserLinkMiddleware(string applicationPath, RequestDelegate next)
27+
{
28+
_applicationPath = applicationPath;
29+
_next = next;
30+
}
31+
32+
/// <summary>
33+
/// This method is called to process the response.
34+
/// </summary>
35+
internal Task Invoke(HttpContext context)
36+
{
37+
string requestId = Guid.NewGuid().ToString("N");
38+
39+
IHttpSocketAdapter injectScriptSocket = GetSocketConnectionToHost(_applicationPath, requestId, "injectScriptLink", context.Request.IsHttps);
40+
41+
if (injectScriptSocket != null)
42+
{
43+
return ExecuteWithFilter(injectScriptSocket, requestId, context);
44+
}
45+
else
46+
{
47+
return ExecuteWithoutFilter(context);
48+
}
49+
}
50+
51+
private PageExecutionListenerFeature AddPageExecutionListenerFeatureTo(HttpContext context, string requestId)
52+
{
53+
IHttpSocketAdapter mappingDataSocket = GetSocketConnectionToHost(_applicationPath, requestId, "sendMappingData", context.Request.IsHttps);
54+
55+
if (mappingDataSocket != null)
56+
{
57+
PageExecutionListenerFeature listener = new PageExecutionListenerFeature(mappingDataSocket);
58+
59+
context.Features.Set(listener);
60+
61+
return listener;
62+
}
63+
64+
return null;
65+
}
66+
67+
private async Task ExecuteWithFilter(IHttpSocketAdapter injectScriptSocket, string requestId, HttpContext httpContext)
68+
{
69+
ScriptInjectionFilterContext filterContext = new ScriptInjectionFilterContext(httpContext);
70+
71+
using (ScriptInjectionFilterStream filter = new ScriptInjectionFilterStream(injectScriptSocket, filterContext))
72+
{
73+
httpContext.Response.Body = filter;
74+
httpContext.Response.OnStarting(delegate ()
75+
{
76+
httpContext.Response.ContentLength = null;
77+
78+
return StaticTaskResult.True;
79+
});
80+
81+
using (AddPageExecutionListenerFeatureTo(httpContext, requestId))
82+
{
83+
await _next(httpContext);
84+
85+
await filter.WaitForFilterComplete();
86+
87+
if (filter.ScriptInjectionTimedOut)
88+
{
89+
_filterRequestTimeouts++;
90+
}
91+
else
92+
{
93+
_filterRequestTimeouts = 0;
94+
}
95+
}
96+
}
97+
}
98+
99+
private Task ExecuteWithoutFilter(HttpContext context)
100+
{
101+
return _next(context);
102+
}
103+
104+
private static IHttpSocketAdapter GetSocketConnectionToHost(string applicationPath, string requestId, string rpcMethod, bool isHttps)
105+
{
106+
// The host should send an initial response immediately after
107+
// the connection is established. If it fails to do so multiple times,
108+
// stop trying. Each timeout is delaying a response to the browser.
109+
//
110+
// This will only reset when the server process is restarted.
111+
if (_filterRequestTimeouts >= FilterRequestTimeoutLimit)
112+
{
113+
return null;
114+
}
115+
116+
if (FindAndSignalHostConnection(applicationPath))
117+
{
118+
return new DelayConnectingHttpSocketAdapter(async delegate()
119+
{
120+
Uri connectionString;
121+
122+
if (GetHostConnectionString(applicationPath, out connectionString))
123+
{
124+
IHttpSocketAdapter httpSocket = await HttpSocketAdapter.OpenHttpSocketAsync("GET", new Uri(connectionString, rpcMethod));
125+
126+
AddRequestHeaders(httpSocket, requestId, isHttps);
127+
128+
return httpSocket;
129+
}
130+
131+
return null;
132+
});
133+
}
134+
135+
return null;
136+
}
137+
138+
private static void AddRequestHeaders(IHttpSocketAdapter httpSocket, string requestId, bool isHttps)
139+
{
140+
httpSocket.AddRequestHeader(BrowserLinkConstants.RequestIdHeaderName, requestId);
141+
142+
if (isHttps)
143+
{
144+
httpSocket.AddRequestHeader(BrowserLinkConstants.RequestScheme, "https");
145+
}
146+
else
147+
{
148+
httpSocket.AddRequestHeader(BrowserLinkConstants.RequestScheme, "http");
149+
}
150+
}
151+
152+
private static bool FindAndSignalHostConnection(string applicationPath)
153+
{
154+
HostConnectionData connectionData;
155+
156+
if (HostConnectionUtil.FindHostConnection(applicationPath, out connectionData))
157+
{
158+
return HostConnectionUtil.SignalHostForStartup(connectionData, blockUntilStarted: false);
159+
}
160+
161+
return false;
162+
}
163+
164+
private static bool GetHostConnectionString(string applicationPath, out Uri connectionString)
165+
{
166+
HostConnectionData connectionData;
167+
168+
if (GetHostConnectionData(applicationPath, out connectionData))
169+
{
170+
return Uri.TryCreate(connectionData.ConnectionString, UriKind.Absolute, out connectionString);
171+
}
172+
173+
connectionString = null;
174+
return false;
175+
}
176+
177+
private static bool GetHostConnectionData(string applicationPath, out HostConnectionData connectionData)
178+
{
179+
if (HostConnectionUtil.FindHostConnection(applicationPath, out connectionData))
180+
{
181+
return EnsureHostServerStarted(applicationPath, ref connectionData);
182+
}
183+
184+
connectionData = null;
185+
return false;
186+
}
187+
188+
private static bool EnsureHostServerStarted(string applicationPath, ref HostConnectionData connectionData)
189+
{
190+
if (String.IsNullOrEmpty(connectionData.ConnectionString))
191+
{
192+
if (!HostConnectionUtil.SignalHostForStartup(connectionData))
193+
{
194+
return false;
195+
}
196+
197+
if (!HostConnectionUtil.FindHostConnection(applicationPath, out connectionData))
198+
{
199+
return false;
200+
}
201+
202+
if (String.IsNullOrEmpty(connectionData.ConnectionString))
203+
{
204+
return false;
205+
}
206+
}
207+
208+
return true;
209+
}
210+
}
211+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using Microsoft.AspNetCore.Http;
5+
6+
namespace Microsoft.VisualStudio.Web.BrowserLink
7+
{
8+
/// <summary>
9+
/// An instance of this class is created when Browser Link is registered
10+
/// in an application. It's job is to remember the base path of the
11+
/// application, so it can be passed on to the BrowserLinkMiddleware.
12+
/// </summary>
13+
internal class BrowserLinkMiddlewareFactory
14+
{
15+
private string _applicationBasePath;
16+
17+
internal BrowserLinkMiddlewareFactory(string applicationBasePath)
18+
{
19+
_applicationBasePath = applicationBasePath;
20+
}
21+
22+
public RequestDelegate CreateBrowserLinkMiddleware(RequestDelegate next)
23+
{
24+
BrowserLinkMiddleware middleware = new BrowserLinkMiddleware(_applicationBasePath, next);
25+
26+
return middleware.Invoke;
27+
}
28+
}
29+
}

0 commit comments

Comments
 (0)