Bridge.exe runs elevated and controls all Bridge commands
Bridge.exe now has an app manifest that allows it to run as admin (UAC prompting if appropriate). The Bridge.exe command line options have now been improved to match the names in TestProperties and BridgeConfiguration. And it is possible to give Bridge.exe a .json file to initialize multiple Bridge options. The prior certificates used for https tests were replaced with newer versions, and they are installed and uninstalled by the Bridge as needed. All firewall rule and certificate cleanup that used to happen in other scripts is now handled by the Bridge itself. And those prior scripts have been removed. The BridgeController was added and a DELETE request to it will shutdown the Bridge cleanly.
This commit is contained in:
Родитель
9cf85ca8a1
Коммит
cdeccaaa45
|
@ -9,60 +9,48 @@ service(s) on a machine other than the machine running the test
|
|||
(example: cross-platform testing).
|
||||
|
||||
For this we have developed what we call the "Bridge." This Bridge is
|
||||
a WebAPI application meant to run on a Windows OS using the full
|
||||
.NET framework. It offers a REST API that the scenario tests can
|
||||
invoke through normal HTTP requests to launch WCF services on other
|
||||
machines.
|
||||
a self-elevating .exe that hosts a WebAPI application capable of
|
||||
starting WCF services on demand. Currently this exe must be run on a
|
||||
Windows OS using the full .NET Framework.
|
||||
|
||||
The Bridge itself is agnostic of WCF services. Instead, it is aware
|
||||
of types that implement IResource, and it uses reflection to analyze
|
||||
a collection of assemblies to discover them. It then exposes each of these
|
||||
as a "resource" to the Bridge REST API. When a test requests a particular
|
||||
resource, the Bridge invokes the appropriate IResource. In this case, the
|
||||
WCF tests provide IResources that know how to create and host WCF services.
|
||||
When Outerloop tests are run, the Bridge is automatically started at
|
||||
the beginning of the run and closed at the end.
|
||||
|
||||
When the tests start up, they inform the Bridge where to find these
|
||||
resource assemblies. This folder is known as the "Bridge Resource Folder".
|
||||
|
||||
There are several Bridge-specific properties that are meaningful to
|
||||
both the Bridge and the tests. They can be defined either as Environment
|
||||
variables or passed into build.cmd as MSBuild properties. They are:
|
||||
|
||||
- **BuildHost**: the name of the machine on which the Bridge runs (default localhost)
|
||||
- **BridgePort**: the port to use to communicate with the Bridge (default 44283)
|
||||
- **BridgeResourceFolder**: the full path to the folder that contains the Bridge IResource assemblies (default bin\Wcf\Bridge\Resources)
|
||||
- **BridgeAllowRemote**: indicates whether the Bridge will accept requests from machines other than itself (default: false)
|
||||
|
||||
Starting the Bridge Locally
|
||||
Starting the Bridge manually
|
||||
---------------------------
|
||||
By default, the Bridge will self-start locally during OuterLoop tests and accept only
|
||||
requests from the machine running the tests. It will shutdown when all the OuterLoop
|
||||
tests have completed.
|
||||
|
||||
If you want to run the Bridge on a different machine, you must follow the steps below.
|
||||
|
||||
Starting the Bridge on a different machine
|
||||
------------------------------------------
|
||||
On the Bridge where the machine will run, do these things:
|
||||
|
||||
cd to the root folder of a local WCF Git repository and run this command:
|
||||
However, the Bridge can be started manually. This is useful if you want to run it on a different machine or attach a debugger to it before the tests run. Executing this CMD from the repository root will start the Bridge locally:
|
||||
|
||||
```
|
||||
startBridge.cmd {options}
|
||||
startBridge.cmd {options}
|
||||
```
|
||||
|
||||
The options can be any of these:
|
||||
See below for a description of available options.
|
||||
|
||||
-portNumber NNN
|
||||
-allowRemote true/false
|
||||
-remoteAddresses comma-separated-list
|
||||
If you start the Bridge manually this way you must also stop it manually.
|
||||
This manual start sets an Environment variable 'BridgeKeepRunning' to true,
|
||||
and this prevents the Bridge from being shutdown when OuterLoop tests are complete.
|
||||
To shutdown the Bridge manually, type "exit" into the CMD window in which the Bridge is running.
|
||||
As long as 'BridgeKeepRunning' is set to true, the OuterLoop tests will not stop the Bridge when complete, even if the tests caused the Bridge to start.
|
||||
|
||||
The 'portNumber' option allows you to choose a port other than
|
||||
the default 44283.
|
||||
The Bridge.exe program starts minimized. If you want to see its
|
||||
output as it runs, restore its CMD window from the taskbar.
|
||||
|
||||
Security options to consider when starting the Bridge
|
||||
-----------------------------------------------------
|
||||
Because the Bridge offers the ability to exercise arbitrary
|
||||
code, it is important that it not be exposed publically.
|
||||
There are 2 options to 'startBridge' to limit its visibility:
|
||||
|
||||
-allowRemote
|
||||
-remoteAddresses:comma-separated-list
|
||||
|
||||
The 'allowRemote' option tells the Bridge it is allowed to accept
|
||||
requests from other machines. If unspecified, it will accept only
|
||||
from localhost. It must be set to true if the Bridge is running
|
||||
requests from other machines. If unspecified, it will accept only requests
|
||||
from localhost. It must be set explicitly if the Bridge is running
|
||||
on a machine other than where the tests run.
|
||||
|
||||
The 'remoteAddresses' is a comma-separated list of IP addresses,
|
||||
|
@ -70,63 +58,133 @@ a range of IP's, or one of the supported predefined terms supported
|
|||
for the Scope properties page in the Windows Firewall.
|
||||
See https://technet.microsoft.com/en-us/library/dd759059.aspx .
|
||||
|
||||
If left unspecified, but 'allowRemote' is true, the 'remoteAddresses'
|
||||
will default to "LocalSubnet". The value "*" allows remote
|
||||
This option is used only if 'allowRemote' has also been specified.
|
||||
Its default value is "LocalSubnet". This means setting 'allowRemote' will by default allow only requests on the same local subnet. The value "*" allows remote
|
||||
access from all addresses, but for security reasons is not recommended.
|
||||
|
||||
The purpose of these options is to limit the scope applied to the
|
||||
firewall rules the Bridge automatically generates while it is running.
|
||||
It does this to open specific ports needed for the WCF test services.
|
||||
The Bridge deletes these firewall rules when it exits.
|
||||
|
||||
After running 'startBridge.cmd', a PowerShell window will start Bridge.exe in elevated mode.
|
||||
You will see a CMD window indicating what URL the Bridge is using and confirmation
|
||||
whether it is enabled to receive remote requests. It will remain running until you manually close it or type "exit" in that CMD window.
|
||||
|
||||
Running OuterLoop tests when the Bridge is remote
|
||||
------------------------------------------------
|
||||
On the machine where you want the tests to run, follow these steps:
|
||||
|
||||
Start the OuterLoop tests like this:
|
||||
|
||||
* cd to the root folder
|
||||
* run this CMD
|
||||
|
||||
```
|
||||
build.cmd /p:WithCategories=OuterLoop /p:BridgeHost=bridge-host-name /p:BridgeResourceFolder=shared-folder-Bridge-can-access
|
||||
On the machine where you want the Bridge to run, run this
|
||||
command from the repository root:
|
||||
```
|
||||
startBridge.cmd -allowRemote
|
||||
```
|
||||
|
||||
Alternatively, you could have set these as environment variables to skip passing them as MSBuild properties:
|
||||
On the machine where you want the tests to run, follow these steps to execute the OuterLoop tests using the remote Bridge:
|
||||
|
||||
```
|
||||
build.cmd /p:WithCategories=OuterLoop /p:BridgeHost={bridge host name} /p:BridgeResourceFolder={shared folder Bridge can access}
|
||||
```
|
||||
|
||||
Alternatively, you could have set these as environment variables to skip passing them as MSBuild properties:
|
||||
|
||||
set BridgeHost=bridge-host-name
|
||||
set BridgeResourceFolder=shared-folder-Bridge-can-access
|
||||
[optional] set BridgePort=NNN
|
||||
|
||||
Another way to run the Bridge and specify multiple options at the same time is to create a file in json format and specify it using the BridgeConfig optionm like this:
|
||||
|
||||
BridgeHost must match the machine name where the Bridge is running.
|
||||
```
|
||||
build.cmd /p:WithCategories=OuterLoop /p:BridgeConfig={my config file}
|
||||
```
|
||||
|
||||
BridgeHost and BridgePort must match the machine name where the Bridge is running.
|
||||
|
||||
BridgeResourceFolder must be the location of a folder that both the client
|
||||
and Bridge machines can access. The WCF services built for the tests will
|
||||
be written to that folder, and the Bridge will be asked to read from it.
|
||||
|
||||
BridgePort needs to be set only if you started the Bridge on a different port.
|
||||
|
||||
After you started 'build.cmd' you will see a PowerShell window execute elevated,
|
||||
and it will ping the remotely running Bridge to verify it is available. All OuterLoop tests
|
||||
will then run against WCF test services running on the Bridge machine.
|
||||
|
||||
If you watch the CMD window running on the Bridge machine, you will see it
|
||||
write to the console the incoming configuration and resource requests.
|
||||
After all tests have run, the Bridge will continue to run for several minutes.
|
||||
The timeout interval can be specified on the client test machine by setting
|
||||
the BridgeMaxIdleTimeSpan to some valid TimeSpan value (either as an environment
|
||||
variable or on the build.cmd line). Default is "20:00" (twenty minutes). After
|
||||
no activity for this amount of time, the Bridge will close. Alternatively, you
|
||||
can type "exit" in the Bridge CMD window to close it manually.
|
||||
|
||||
You can control the amount of time the Bridge will remain alive when idle by setting the environment variable BridgeMaxIdleTimeSpan to any legal TimeSpan string (example: 'set BridgeMaxIdleTimeSpan=1.02:03:04' will allow it to run 1 day, 2 hours, 3 minutes, and 4 seconds). This value should be set on the machine where the tests run, because they will configure the Bridge machine when they start.
|
||||
How the Bridge works
|
||||
--------------------
|
||||
The Bridge is a WebAPI application that can run on any Windows
|
||||
machine using the full .NET framework. The Bridge can launch
|
||||
WCF services requested by tests running on the same or a different
|
||||
machine.
|
||||
|
||||
This allows the tests to be run in environments (such as NET Native
|
||||
or CoreCLR) or on other operating systems (such as Linux or the Mac)
|
||||
that are not able to host their own WCF services.
|
||||
|
||||
The Bridge is agnostic of WCF but instead supports the notion of
|
||||
named "resources". A resource is any type that implements the IResource
|
||||
interface and resides in the "Bridge Resource Folder" when the Bridge
|
||||
is configured. The Bridge can be reconfigured on-the-fly without being
|
||||
stopped and restarted.
|
||||
|
||||
Each resource has a name, which is just the simple class name of the
|
||||
type implementing IResource. In this way, a remote application can
|
||||
request the Bridge to invoke the Get or Put method of any named resource.
|
||||
For WCF tests, there are a number of IResources that know how to start
|
||||
and host specific WCF services. This allows a test running in a .NET
|
||||
Core enviromnent to say "I need the URL to reach a running instance of
|
||||
the XYZ WCF service" and have the Bridge automatically start that service and return its URL to the client.
|
||||
|
||||
The Bridge offers 3 endpoints:
|
||||
- http://{host}:{port}/**Bridge**
|
||||
- http://{host}:{port}/**Config**
|
||||
- http://{host}:{port}/**Resource**
|
||||
|
||||
The 'Bridge' endpoint supports these Http requests:
|
||||
|
||||
- GET -- returns the current Bridge configuration as a set of name/value pairs
|
||||
- DELETE -- Terminates the Bridge process cleanly
|
||||
|
||||
The 'Config' endpoint supports these Http requests:
|
||||
|
||||
- GET -- returns the current Bridge configuration as a set of name/value pairs
|
||||
- POST -- reconfigures the Bridge configuration with a new set of name/value pairs
|
||||
- DELETE -- releases all resources currently used by the Bridge but remains running
|
||||
|
||||
The 'Resource' endpoint supports these Http requests:
|
||||
|
||||
- GET -- return the result of the IResource.Get for the resource of the given name
|
||||
- POST -- returns the result of the IResource.Put for the resource of the given name
|
||||
|
||||
Bridge.exe
|
||||
-----------
|
||||
The Bridge.exe is a self-elevating executable capable of starting and
|
||||
stopping the Bridge WebAPI application. If started from a non-elevated
|
||||
process, it will ask for elevation confirmation depending on UAC settings.
|
||||
|
||||
Usage is: Bridge.exe [/ping] [/stop] [/stopIfLocal] [/allowRemote] [/remoteAddresses:x,y,z] [/{BridgeProperty}:value]
|
||||
|
||||
- **ping** Pings the Bridge to check if it is running
|
||||
- **stop** Stops the Bridge if it is running
|
||||
- **stopIfLocal** Stops the Bridge if it is running locally
|
||||
- **allowRemote** If starting the Bridge, allows access from other than localHost (default is localhost only)
|
||||
- **remoteAddresses** If starting the Bridge, comma-separated list of addresses firewall rules will accept (default is 'LocalSubnet')
|
||||
- **BridgeConfig:file** Treat file as json name/value pairs to initialize any or all other options
|
||||
- **BridgeResourceFolder** The folder containing the Bridge 'resources'
|
||||
- **BridgeHost** The machine on which the Bridge is running
|
||||
- **BridgePort** The port on which the Bridge is listening
|
||||
- **BridgeHttpPort** The port used for Http tests
|
||||
- **BridgeHttpsPort** The port used for Https tests
|
||||
- **BridgeTcpPort** The port used for TCP tests
|
||||
- **BridgeWebSocketPort** The port used for web socket tests
|
||||
- **BridgeCertificateAuthority** The name of the certificate file to serve as the certificate authorithy
|
||||
- **BridgeHttpsCertificate** The name of the certificate file to import for Https tests
|
||||
- **BridgeMaxIdleTimeSpan** The maximum TimeSpan the Bridge can stay idle before shutting down
|
||||
|
||||
Any of the options starting with 'Bridge' can also be specified by setting an Environment
|
||||
variable with that name. If passed explicitly on the command line, the command line value takes precedence over the Environment variable.
|
||||
|
||||
Any of these options can also be specified by placing them into a json-formatted file and specifying the 'BridgeConfig' property. An example of such a file might be:
|
||||
```
|
||||
{
|
||||
allowRemote : "",
|
||||
BridgePort : "44289",
|
||||
BridgeMaxIdleTimeSpan : "24:00:00"
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Options stated explicitly on the command line take precedence over Environment variables or values read from the BridgeConfig file.
|
||||
|
||||
When the Outerloop tests are run, Bridge.exe is started automatically running on localhost. When the Outerloop tests complete 'Bridge /stopIfLocal' is invoked to close the Bridge.
|
||||
|
||||
While running, the Bridge will add firewall rules to allow access to specific ports.
|
||||
It may also load certificates as they are needed by tests. When the Bridge closes,
|
||||
it removes all those firewall rules or certificates.
|
||||
|
||||
|
||||
|
|
13
build.cmd
13
build.cmd
|
@ -18,11 +18,6 @@ if %errorlevel% equ 0 (
|
|||
set outloop=true
|
||||
)
|
||||
|
||||
if "%outloop%" equ "true" (
|
||||
start /D %setupFilesFolder% /wait BuildWCFTestService.cmd
|
||||
)
|
||||
|
||||
|
||||
if not defined VisualStudioVersion (
|
||||
if defined VS140COMNTOOLS (
|
||||
call "%VS140COMNTOOLS%\VsDevCmd.bat"
|
||||
|
@ -48,7 +43,9 @@ set _buildprefix=echo
|
|||
set _buildpostfix=^> "%_buildlog%"
|
||||
|
||||
if "%outloop%" equ "true" (
|
||||
start /D %setupFilesFolder% /wait RunElevated.vbs SetupWCFTestService.cmd
|
||||
pushd %setupFilesFolder%
|
||||
call SetupWCFTestService.cmd
|
||||
popd
|
||||
)
|
||||
|
||||
call :build %*
|
||||
|
@ -72,7 +69,9 @@ findstr /ir /c:".*Warning(s)" /c:".*Error(s)" /c:"Time Elapsed.*" "%_buildlog%"
|
|||
echo Build Exit Code = %BUILDERRORLEVEL%
|
||||
|
||||
if "%outloop%" equ "true" (
|
||||
start /D %setupFilesFolder% /wait RunElevated.vbs CleanupWCFTestService.cmd
|
||||
pushd %setupFilesFolder%
|
||||
call CleanupWCFTestService.cmd
|
||||
popd
|
||||
)
|
||||
|
||||
exit /b %BUILDERRORLEVEL%
|
|
@ -26,10 +26,6 @@ set _buildlog=%~dp0..\..\..\..\msbuildWCFTestService.log
|
|||
set _buildprefix=echo
|
||||
set _buildpostfix=^> "%_buildlog%"
|
||||
|
||||
:Clean Up Test Service
|
||||
pushd %~dp0
|
||||
start /wait RunElevated.vbs CleanupWCFTestService.cmd
|
||||
popd
|
||||
call :build %*
|
||||
|
||||
:: Build
|
||||
|
@ -52,4 +48,4 @@ echo.
|
|||
findstr /ir /c:".*Warning(s)" /c:".*Error(s)" /c:"Time Elapsed.*" "%_buildlog%"
|
||||
echo Build Exit Code = %BUILDERRORLEVEL%
|
||||
|
||||
exit %BUILDERRORLEVEL%
|
||||
exit /b %BUILDERRORLEVEL%
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
echo off
|
||||
setlocal
|
||||
pushd %~dp0
|
||||
|
||||
echo BridgeKeepRunning=%BridgeKeepRunning%
|
||||
if '%BridgeKeepRunning%' neq 'true' (
|
||||
echo Stopping the Bridge.exe task locally...
|
||||
Taskkill /IM bridge.exe /F
|
||||
echo Stopping the Bridge...
|
||||
pushd %~dp0..\..\..\..\bin\wcf\tools\Bridge
|
||||
call Bridge.exe -stopIfLocal %*
|
||||
popd
|
||||
) else (
|
||||
echo Bridge is left running because BridgeKeepRunning is true
|
||||
echo The Bridge was left running because BridgeKeepRunning is true
|
||||
)
|
||||
|
||||
exit /b
|
||||
|
|
Двоичные данные
src/System.Private.ServiceModel/tools/setupfiles/RootCATest.cer
Двоичные данные
src/System.Private.ServiceModel/tools/setupfiles/RootCATest.cer
Двоичный файл не отображается.
|
@ -1,11 +0,0 @@
|
|||
'Capture command line arguments to forward to program we start
|
||||
strName = WScript.Arguments.Item(0)
|
||||
cmdArgs = ""
|
||||
If WScript.Arguments.Count > 1 Then
|
||||
For i = 1 To WScript.Arguments.Count - 1
|
||||
cmdArgs = cmdArgs & " " & WScript.Arguments.Item(i)
|
||||
Next
|
||||
End If
|
||||
|
||||
Set objShell = CreateObject("Shell.Application")
|
||||
objShell.ShellExecute WScript.Arguments(0), cmdArgs,"", "runas", 1
|
|
@ -2,20 +2,14 @@ echo off
|
|||
setlocal
|
||||
|
||||
pushd %~dp0
|
||||
echo Building the Bridge...
|
||||
call BuildWCFTestService.cmd
|
||||
popd
|
||||
|
||||
if '%BridgeHost%' neq '' (
|
||||
set _bridgeHostArg=-hostName %BridgeHost%
|
||||
)
|
||||
echo Starting the Bridge with parameters %*
|
||||
pushd %~dp0..\..\..\..\bin\wcf\tools\Bridge
|
||||
start /MIN Bridge.exe %*
|
||||
popd
|
||||
|
||||
if '%BridgePort%' neq '' (
|
||||
set _bridgePortArg=-portNumber %BridgePort%
|
||||
)
|
||||
|
||||
if '%BridgeAllowRemote%' neq '' (
|
||||
set _bridgeAllowRemoteArg=-allowRemote %BridgeAllowRemote%
|
||||
)
|
||||
|
||||
echo Executing: start powershell -ExecutionPolicy Bypass -File ..\test\Bridge\bin\ensureBridge.ps1 %_bridgeHostArg% %_bridgePortArg% %_bridgeAllowRemoteArg% %*
|
||||
|
||||
start powershell -ExecutionPolicy Bypass -File ..\test\Bridge\bin\ensureBridge.ps1 %_bridgeHostArg% %_bridgePortArg% %_bridgeAllowRemoteArg% %*
|
||||
exit /b
|
||||
|
||||
|
|
Двоичный файл не отображается.
|
@ -66,7 +66,7 @@ namespace Bridge
|
|||
throw new ArgumentNullException("appDomainName");
|
||||
}
|
||||
|
||||
lock (ConfigController.BridgeLock)
|
||||
lock (ConfigController.ConfigLock)
|
||||
{
|
||||
AppDomain appDomain = null;
|
||||
if (TypeCache.AppDomains.TryGetValue(appDomainName, out appDomain))
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
|
||||
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), wcf.targets))\wcf.targets" Condition=" '$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), wcf.targets))' != '' " />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
|
@ -14,7 +15,7 @@
|
|||
<FileAlignment>512</FileAlignment>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
|
||||
<OutputPath>..\bin\</OutputPath>
|
||||
<OutputPath>$(WcfToolsOutputPath)\Bridge\</OutputPath>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
|
@ -34,6 +35,10 @@
|
|||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup />
|
||||
<PropertyGroup>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.Owin, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>$(PackageOutputDir)\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll</HintPath>
|
||||
|
@ -91,6 +96,8 @@
|
|||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="AppDomainManager.cs" />
|
||||
<Compile Include="BridgeController.cs" />
|
||||
<Compile Include="BridgeState.cs" />
|
||||
<Compile Include="ChangedEventArgs.cs" />
|
||||
<Compile Include="ConfigController.cs" />
|
||||
<Compile Include="IdleTimeoutManager.cs" />
|
||||
|
@ -104,6 +111,7 @@
|
|||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="App.config" />
|
||||
<None Include="app.manifest" />
|
||||
<None Include="ensureBridge.ps1">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
|
@ -117,4 +125,7 @@
|
|||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
|
||||
<Target Name="BeforeBuild">
|
||||
<Message Text="$$$ OutputPath is $(OutputPath)" />
|
||||
</Target>
|
||||
</Project>
|
|
@ -0,0 +1,114 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Web.Http;
|
||||
using WcfTestBridgeCommon;
|
||||
|
||||
namespace Bridge
|
||||
{
|
||||
public class BridgeController : ApiController
|
||||
{
|
||||
private static object BridgeLock { get; set; }
|
||||
public static BridgeState BridgeState { get; private set; }
|
||||
|
||||
static BridgeController()
|
||||
{
|
||||
BridgeLock = new object();
|
||||
BridgeState = BridgeState.Running;
|
||||
}
|
||||
|
||||
public HttpResponseMessage Get(HttpRequestMessage request)
|
||||
{
|
||||
Dictionary<string, string> dictionary = ConfigController.BridgeConfiguration.ToDictionary();
|
||||
|
||||
string configResponse = JsonSerializer.SerializeDictionary(dictionary);
|
||||
|
||||
Trace.WriteLine(String.Format("{0:T} - GET bridge returning raw content:{1}{2}",
|
||||
DateTime.Now, Environment.NewLine, configResponse),
|
||||
typeof(BridgeController).Name);
|
||||
|
||||
// Bridge GET response is the current Bridge configuration
|
||||
HttpResponseMessage response = request.CreateResponse(HttpStatusCode.OK);
|
||||
response.Content = new StringContent(configResponse);
|
||||
response.Content.Headers.ContentType = new MediaTypeHeaderValue(JsonSerializer.JsonMediaType);
|
||||
return response;
|
||||
}
|
||||
|
||||
// The DELETE Http verb means stop the Bridge cleanly
|
||||
public HttpResponseMessage Delete(HttpRequestMessage request)
|
||||
{
|
||||
Trace.WriteLine(String.Format("{0:T} - received DELETE request", DateTime.Now),
|
||||
typeof(BridgeController).Name);
|
||||
|
||||
lock(BridgeLock)
|
||||
{
|
||||
if (BridgeState == BridgeState.Running)
|
||||
{
|
||||
// 'Stopping' is the Bridge's terminal state because
|
||||
// the process itself will terminate during this response.
|
||||
BridgeState = BridgeState.Stopping;
|
||||
try
|
||||
{
|
||||
HttpResponseMessage response = request.CreateResponse(HttpStatusCode.OK);
|
||||
response.Content = new ExitOnDisposeStringContent("\"The Bridge has closed.\"");
|
||||
response.Content.Headers.ContentType = new MediaTypeHeaderValue(JsonSerializer.JsonMediaType);
|
||||
return response;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var exceptionResponse = ex.Message;
|
||||
Trace.WriteLine(String.Format("{0:T} - DELETE config exception:{1}{2}",
|
||||
DateTime.Now, Environment.NewLine, ex),
|
||||
typeof(BridgeController).Name);
|
||||
|
||||
return request.CreateResponse(HttpStatusCode.BadRequest, exceptionResponse);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Multiple concurrent DELETE requests are blocked by the monitor.
|
||||
// But in case the process has not yet terminated from the first request,
|
||||
// send back BADREQUEST for any others.
|
||||
return request.CreateResponse(HttpStatusCode.BadRequest, "Bridge is already stopping.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void ReleaseAllResources()
|
||||
{
|
||||
CertificateManager.UninstallAllCertificates();
|
||||
PortManager.RemoveAllBridgeFirewallRules();
|
||||
}
|
||||
|
||||
public static void StopBridgeProcess(int exitCode)
|
||||
{
|
||||
ReleaseAllResources();
|
||||
Environment.Exit(exitCode);
|
||||
}
|
||||
|
||||
// This class exists to release all Bridge resources
|
||||
// in this class's Dispose(). WebAPI guarantees the
|
||||
// HttpResponseMessage and its content will be disposed
|
||||
// only after the response has been sent, allowing the
|
||||
// Bridge to provide a valid 200 response for the DELETE
|
||||
// and then immediately terminate the process.
|
||||
class ExitOnDisposeStringContent : StringContent
|
||||
{
|
||||
public ExitOnDisposeStringContent(string content) : base(content)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
StopBridgeProcess(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Bridge
|
||||
{
|
||||
public enum BridgeState
|
||||
{
|
||||
Running,
|
||||
Stopping
|
||||
}
|
||||
}
|
|
@ -27,7 +27,7 @@ namespace Bridge
|
|||
|
||||
static ConfigController()
|
||||
{
|
||||
BridgeLock = new object();
|
||||
ConfigLock = new object();
|
||||
|
||||
// Register to manage AppDomains in response to changes to the resource folder
|
||||
ResourceFolderChanged += (object s, ChangedEventArgs<string> args) =>
|
||||
|
@ -39,7 +39,7 @@ namespace Bridge
|
|||
|
||||
// We lock the Bridge when necessary to prevent configuration
|
||||
// changes or resource instantiation concurrent execution.
|
||||
public static object BridgeLock { get; private set; }
|
||||
internal static object ConfigLock { get; private set; }
|
||||
|
||||
public static BridgeConfiguration BridgeConfiguration
|
||||
{
|
||||
|
@ -55,7 +55,7 @@ namespace Bridge
|
|||
public HttpResponseMessage POST(HttpRequestMessage request)
|
||||
{
|
||||
// A configuration change can have wide impact, so we don't allow concurrent use
|
||||
lock(BridgeLock)
|
||||
lock(ConfigLock)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -106,7 +106,7 @@ namespace Bridge
|
|||
typeof(ConfigController).Name);
|
||||
|
||||
// Directly return a json string to avoid use of MediaTypeFormatters
|
||||
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.OK);
|
||||
HttpResponseMessage response = request.CreateResponse(HttpStatusCode.OK);
|
||||
response.Content = new StringContent(configResponse);
|
||||
response.Content.Headers.ContentType = new MediaTypeHeaderValue(JsonSerializer.JsonMediaType);
|
||||
return response;
|
||||
|
@ -118,7 +118,7 @@ namespace Bridge
|
|||
DateTime.Now, Environment.NewLine, ex),
|
||||
typeof(ConfigController).Name);
|
||||
|
||||
return Request.CreateResponse(HttpStatusCode.BadRequest, exceptionResponse);
|
||||
return request.CreateResponse(HttpStatusCode.BadRequest, exceptionResponse);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -134,7 +134,7 @@ namespace Bridge
|
|||
typeof(ConfigController).Name);
|
||||
|
||||
// Directly return a json string to avoid use of MediaTypeFormatters
|
||||
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.OK);
|
||||
HttpResponseMessage response = request.CreateResponse(HttpStatusCode.OK);
|
||||
response.Content = new StringContent(configResponse);
|
||||
response.Content.Headers.ContentType = new MediaTypeHeaderValue(JsonSerializer.JsonMediaType);
|
||||
return response;
|
||||
|
@ -148,7 +148,7 @@ namespace Bridge
|
|||
typeof(ConfigController).Name);
|
||||
|
||||
// A configuration change can have wide impact, so we don't allow concurrent use
|
||||
lock (ConfigController.BridgeLock)
|
||||
lock (ConfigController.ConfigLock)
|
||||
{
|
||||
try {
|
||||
if (!String.IsNullOrEmpty(CurrentAppDomainName))
|
||||
|
@ -161,7 +161,7 @@ namespace Bridge
|
|||
ResourceFolderChanged(this, new ChangedEventArgs<string>(oldResourceFolder, null));
|
||||
}
|
||||
}
|
||||
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.OK);
|
||||
HttpResponseMessage response = request.CreateResponse(HttpStatusCode.OK);
|
||||
response.Content = new StringContent("\"Bridge configuration has been cleared.\"");
|
||||
response.Content.Headers.ContentType = new MediaTypeHeaderValue(JsonSerializer.JsonMediaType);
|
||||
return response;
|
||||
|
@ -173,7 +173,7 @@ namespace Bridge
|
|||
DateTime.Now, Environment.NewLine, ex),
|
||||
typeof(ConfigController).Name);
|
||||
|
||||
return Request.CreateResponse(HttpStatusCode.BadRequest, exceptionResponse);
|
||||
return request.CreateResponse(HttpStatusCode.BadRequest, exceptionResponse);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ namespace Bridge
|
|||
|
||||
internal static Dictionary<string, string> DeserializeDictionary(string data)
|
||||
{
|
||||
Dictionary<string, string> dictionary = new Dictionary<string, string>();
|
||||
Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
data = data.Replace("{", String.Empty)
|
||||
.Replace("}", String.Empty)
|
||||
.Trim();
|
||||
|
|
|
@ -2,8 +2,11 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using WcfTestBridgeCommon;
|
||||
|
||||
namespace Bridge
|
||||
|
@ -18,10 +21,186 @@ namespace Bridge
|
|||
{
|
||||
CommandLineArguments commandLineArgs = new CommandLineArguments(args);
|
||||
|
||||
Console.WriteLine("Specified BridgeConfiguration is:{0}{1}",
|
||||
Environment.NewLine, commandLineArgs.BridgeConfiguration.ToString());
|
||||
|
||||
// If asked to ping (not the default), just ping and return an exit code indicating its state
|
||||
if (commandLineArgs.Ping)
|
||||
{
|
||||
string errorMessage = null;
|
||||
if (PingBridge(commandLineArgs.BridgeConfiguration.BridgeHost,
|
||||
commandLineArgs.BridgeConfiguration.BridgePort,
|
||||
out errorMessage))
|
||||
{
|
||||
Console.WriteLine("The Bridge is running.");
|
||||
Environment.Exit(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("The Bridge is not running: {0}", errorMessage);
|
||||
Environment.Exit(1);
|
||||
}
|
||||
}
|
||||
else if (commandLineArgs.StopIfLocal)
|
||||
{
|
||||
StopBridgeIfLocal(commandLineArgs);
|
||||
}
|
||||
else if (commandLineArgs.Stop)
|
||||
{
|
||||
StopBridge(commandLineArgs);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Default action is starting the Bridge
|
||||
StartBridge(commandLineArgs);
|
||||
}
|
||||
}
|
||||
|
||||
// Issues a GET request to the Bridge to determine whether it is alive.
|
||||
// A return of 'true' means the Bridge is healthy. A return of 'false'
|
||||
// indicates the Bridge is not healthy, and 'errorMessage' describes the problem.
|
||||
private static bool PingBridge(string host, int port, out string errorMessage)
|
||||
{
|
||||
errorMessage = null;
|
||||
|
||||
string bridgeUrl = String.Format("http://{0}:{1}/Bridge", host, port);
|
||||
|
||||
using (HttpClient httpClient = new HttpClient())
|
||||
{
|
||||
Console.WriteLine("Testing Bridge at {0}", bridgeUrl);
|
||||
try
|
||||
{
|
||||
var response = httpClient.GetAsync(bridgeUrl).GetAwaiter().GetResult();
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
errorMessage = String.Format("{0}Bridge returned unexpected status code='{1}', reason='{2}'",
|
||||
Environment.NewLine, response.StatusCode, response.ReasonPhrase);
|
||||
if (response.Content != null)
|
||||
{
|
||||
string contentAsString = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
|
||||
if (contentAsString.Length > 1000)
|
||||
{
|
||||
contentAsString = contentAsString.Substring(0, 999) + "...";
|
||||
}
|
||||
errorMessage = String.Format("{0}, content:{1}{2}",
|
||||
errorMessage, Environment.NewLine, contentAsString);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
errorMessage = ex.Message;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void StopBridgeIfLocal(CommandLineArguments commandLineArgs)
|
||||
{
|
||||
if (IsBridgeHostLocal(commandLineArgs.BridgeConfiguration))
|
||||
{
|
||||
StopBridge(commandLineArgs);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("The Bridge on host {0} is not running locally and will not be stopped.",
|
||||
commandLineArgs.BridgeConfiguration.BridgeHost);
|
||||
Console.WriteLine("Use 'Bridge.exe /stop' to stop a Bridge on another machine.");
|
||||
}
|
||||
}
|
||||
|
||||
private static void StopBridge(CommandLineArguments commandLineArgs)
|
||||
{
|
||||
string errorMessage = null;
|
||||
|
||||
if (!PingBridge(commandLineArgs.BridgeConfiguration.BridgeHost,
|
||||
commandLineArgs.BridgeConfiguration.BridgePort,
|
||||
out errorMessage))
|
||||
{
|
||||
Console.WriteLine("The Bridge is not running: {0}", errorMessage);
|
||||
Environment.Exit(0);
|
||||
}
|
||||
|
||||
string bridgeUrl = String.Format("http://{0}:{1}/Bridge", commandLineArgs.BridgeConfiguration.BridgeHost, commandLineArgs.BridgeConfiguration.BridgePort);
|
||||
string problem = null;
|
||||
|
||||
// We stop the Bridge using a DELETE request.
|
||||
// If the Bridge is running on localhost, it will be running
|
||||
// in a different process on this machine.
|
||||
using (HttpClient httpClient = new HttpClient())
|
||||
{
|
||||
Console.WriteLine("Stopping Bridge at {0}", bridgeUrl);
|
||||
try
|
||||
{
|
||||
var response = httpClient.DeleteAsync(bridgeUrl).GetAwaiter().GetResult();
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
problem = String.Format("{0}Bridge returned unexpected status code='{1}', reason='{2}'",
|
||||
Environment.NewLine, response.StatusCode, response.ReasonPhrase);
|
||||
if (response.Content != null)
|
||||
{
|
||||
string contentAsString = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
|
||||
if (contentAsString.Length > 1000)
|
||||
{
|
||||
contentAsString = contentAsString.Substring(0, 999) + "...";
|
||||
}
|
||||
problem = String.Format("{0}, content:{1}{2}",
|
||||
problem, Environment.NewLine, contentAsString);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
problem = ex.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
if (problem != null)
|
||||
{
|
||||
Console.WriteLine("A problem was encountered stopping the Bridge:{0}{1}",
|
||||
Environment.NewLine, problem);
|
||||
Console.WriteLine("Forcing local resource cleanup...");
|
||||
BridgeController.StopBridgeProcess(1);
|
||||
}
|
||||
|
||||
// A successfull DELETE will have cleaned up all firewall rules,
|
||||
// certificates, etc. So when using localhost, this cleanup will
|
||||
// be redundant and harmless. When the Bridge is running remotely,
|
||||
// this cleanup will remove all firewall rules and certificates we
|
||||
// installed on the current machine to talk with that Bridge.
|
||||
BridgeController.StopBridgeProcess(0);
|
||||
}
|
||||
|
||||
// Starts the Bridge locally if it is not already running.
|
||||
private static void StartBridge(CommandLineArguments commandLineArgs)
|
||||
{
|
||||
string errorMessage = null;
|
||||
|
||||
if (PingBridge(commandLineArgs.BridgeConfiguration.BridgeHost,
|
||||
commandLineArgs.BridgeConfiguration.BridgePort,
|
||||
out errorMessage))
|
||||
{
|
||||
Console.WriteLine("The Bridge is already running.");
|
||||
Environment.Exit(0);
|
||||
}
|
||||
|
||||
// The host is not local so we cannot start the Bridge
|
||||
if (!IsBridgeHostLocal(commandLineArgs.BridgeConfiguration))
|
||||
{
|
||||
Console.WriteLine("The Bridge cannot be started from this machine on {0}",
|
||||
commandLineArgs.BridgeConfiguration.BridgeHost);
|
||||
Environment.Exit(1);
|
||||
}
|
||||
|
||||
int port = commandLineArgs.BridgeConfiguration.BridgePort;
|
||||
|
||||
string hostFormatString = "http://{0}:{1}";
|
||||
string owinAddress = String.Format(hostFormatString, commandLineArgs.AllowRemote ? "+" : "localhost", commandLineArgs.Port);
|
||||
string owinAddress = String.Format(hostFormatString, commandLineArgs.AllowRemote ? "+" : "localhost", port);
|
||||
string visibleHost = (commandLineArgs.AllowRemote) ? Environment.MachineName : "localhost";
|
||||
string visibleAddress = String.Format(hostFormatString, visibleHost, commandLineArgs.Port);
|
||||
string visibleAddress = String.Format(hostFormatString, visibleHost, port);
|
||||
|
||||
// Configure the remote addresses the firewall rules will accept.
|
||||
// If remote access is not allowed, specifically disallow remote addresses
|
||||
|
@ -29,25 +208,31 @@ namespace Bridge
|
|||
|
||||
// Initialize the BridgeConfiguration from command line.
|
||||
// The first POST to the ConfigController will supply the rest.
|
||||
ConfigController.BridgeConfiguration = commandLineArgs.BridgeConfiguration;
|
||||
ConfigController.BridgeConfiguration.BridgeHost = visibleHost;
|
||||
ConfigController.BridgeConfiguration.BridgePort = commandLineArgs.Port;
|
||||
|
||||
// Remove any pre-existing firewall rules the Bridge may have added
|
||||
// in past runs. We normally cleanup on exit but could have been
|
||||
// aborted.
|
||||
PortManager.RemoveAllBridgeFirewallRules();
|
||||
// Remove any pre-existing firewall rules or certificates the Bridge
|
||||
// may have added in past runs. We normally clean them up on exit but
|
||||
// it is possible a prior Bridge process was terminated prematurely.
|
||||
BridgeController.ReleaseAllResources();
|
||||
|
||||
// Open the port used to communicate with the Bridge itself
|
||||
PortManager.OpenPortInFirewall(commandLineArgs.Port);
|
||||
PortManager.OpenPortInFirewall(port);
|
||||
|
||||
Console.WriteLine("Starting the Bridge at {0}", visibleAddress);
|
||||
OwinSelfhostStartup.Startup(owinAddress);
|
||||
|
||||
Test(visibleHost, commandLineArgs.Port);
|
||||
// Now test whether the Bridge is running. Failure cleans up
|
||||
// all resources and terminates the process.
|
||||
if (!PingBridge(visibleHost, port, out errorMessage))
|
||||
{
|
||||
Console.WriteLine("The Bridge failed to start or is not responding: {0}", errorMessage);
|
||||
BridgeController.StopBridgeProcess(1);
|
||||
}
|
||||
|
||||
while (true)
|
||||
{
|
||||
Console.WriteLine("The Bridge is listening at {0}", visibleAddress);
|
||||
Console.WriteLine("The Bridge is running and listening at {0}", visibleAddress);
|
||||
if (commandLineArgs.AllowRemote)
|
||||
{
|
||||
Console.WriteLine("Remote access is allowed from '{0}'", commandLineArgs.RemoteAddresses);
|
||||
|
@ -57,8 +242,6 @@ namespace Bridge
|
|||
Console.WriteLine("Remote access is disabled.");
|
||||
}
|
||||
|
||||
Console.WriteLine("Current configuration is:{0}{1}", Environment.NewLine, ConfigController.BridgeConfiguration.ToString());
|
||||
|
||||
Console.WriteLine("Type \"exit\" to stop the Bridge.");
|
||||
string answer = Console.ReadLine();
|
||||
if (String.Equals(answer, "exit", StringComparison.OrdinalIgnoreCase))
|
||||
|
@ -67,36 +250,33 @@ namespace Bridge
|
|||
}
|
||||
}
|
||||
|
||||
Environment.Exit(0);
|
||||
BridgeController.StopBridgeProcess(0);
|
||||
}
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
private static void Test(string hostName, int portNumber)
|
||||
// Returns 'true' if the BridgeConfiguration describes a Bridge that
|
||||
// would run locally.
|
||||
private static bool IsBridgeHostLocal(BridgeConfiguration configuration)
|
||||
{
|
||||
Console.WriteLine("Self-testing the Bridge on http://{0}:{1} ...", hostName, portNumber);
|
||||
string executionFolder = Path.GetDirectoryName(typeof(Program).Assembly.Location);
|
||||
string ensureBridgePath = Path.Combine(executionFolder, "ensureBridge.ps1");
|
||||
string commandLine = String.Format("-ExecutionPolicy Bypass -File {0} -portNumber {1} -hostName {2}",
|
||||
ensureBridgePath, portNumber, hostName);
|
||||
ProcessStartInfo procStartInfo = new ProcessStartInfo("powershell.exe", commandLine);
|
||||
procStartInfo.RedirectStandardOutput = true;
|
||||
procStartInfo.UseShellExecute = false;
|
||||
procStartInfo.CreateNoWindow = true;
|
||||
var proc = new Process();
|
||||
proc.StartInfo = procStartInfo;
|
||||
proc.Start();
|
||||
proc.WaitForExit();
|
||||
string result = proc.StandardOutput.ReadToEnd();
|
||||
Console.WriteLine("Result from Test: " + result);
|
||||
if (String.Equals("localhost", configuration.BridgeHost, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (String.Equals(Environment.MachineName, configuration.BridgeHost, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
class CommandLineArguments
|
||||
{
|
||||
public CommandLineArguments(string[] args)
|
||||
{
|
||||
Port = DefaultPortNumber;
|
||||
AllowRemote = DefaultAllowRemote;
|
||||
RemoteAddresses = DefaultRemoteAddresses;
|
||||
Ping = false;
|
||||
|
||||
bool success = Parse(args);
|
||||
if (!success)
|
||||
|
@ -106,52 +286,138 @@ namespace Bridge
|
|||
}
|
||||
}
|
||||
|
||||
public int Port { get; private set; }
|
||||
public BridgeConfiguration BridgeConfiguration { get; private set; }
|
||||
public bool AllowRemote { get; private set; }
|
||||
public string RemoteAddresses { get; private set; }
|
||||
public bool Ping { get; private set; }
|
||||
public bool Stop { get; private set; }
|
||||
public bool StopIfLocal { get; private set; }
|
||||
|
||||
private bool Parse(string[] args)
|
||||
{
|
||||
// Build a dictionary of all command line arguments.
|
||||
// This allows us to initialize BridgeConfiguration from it.
|
||||
// Precedence of values in the BridgeConfiguration is this:
|
||||
// - Lowest precedence is the BridgeConfiguration ctor defaults
|
||||
// - Next precedence is any value found in a specified configuration file
|
||||
// - Next precedence is environment variables
|
||||
// - Highest precedence is a BridgeConfiguration value explicitly set on the command line
|
||||
|
||||
Dictionary<string, string> argumentDictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (string arg in args)
|
||||
{
|
||||
if (!arg.StartsWith("/") && !arg.StartsWith("-"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
string[] argAndValue = arg.Substring(1).Split(':');
|
||||
if (argAndValue.Length == 0)
|
||||
|
||||
// Cannot use split because some argument values could contain colons
|
||||
int index = arg.IndexOf(':');
|
||||
string argName = (index < 0) ? arg.Substring(1) : arg.Substring(1, index - 1);
|
||||
string argValue = (index < 0) ? String.Empty : arg.Substring(index+1);
|
||||
|
||||
if (String.Equals(argName, "?", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (String.Equals(argAndValue[0], "port", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (argAndValue.Length < 2)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
int port = 0;
|
||||
if (!int.TryParse(argAndValue[1], out port))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
Port = port;
|
||||
}
|
||||
else if (String.Equals(argAndValue[0], "allowRemote", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
AllowRemote = true;
|
||||
}
|
||||
else if (String.Equals(argAndValue[0], "remoteAddresses", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (argAndValue.Length < 2)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
RemoteAddresses = argAndValue[1];
|
||||
}
|
||||
else
|
||||
|
||||
argumentDictionary[argName] = argValue;
|
||||
}
|
||||
|
||||
BridgeConfiguration = new BridgeConfiguration();
|
||||
BridgeConfiguration.BridgePort = DefaultPortNumber;
|
||||
BridgeConfiguration.BridgeHost = "localhost";
|
||||
BridgeConfiguration.BridgeMaxIdleTimeSpan = IdleTimeoutHandler.Default_MaxIdleTimeSpan;
|
||||
|
||||
// If the user specified a configuration file, deserialize it as json
|
||||
// and treat each name-value pair as if it had been on the command line.
|
||||
// But options explicitly on the command line take precedence over these file options.
|
||||
string argumentValue;
|
||||
if (argumentDictionary.TryGetValue("bridgeConfig", out argumentValue))
|
||||
{
|
||||
if (!File.Exists(argumentValue))
|
||||
{
|
||||
Console.WriteLine("The configuration file '{0}' does not exist.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read the configuration file as json and deserialize it
|
||||
string configurationAsJson = File.ReadAllText(argumentValue);
|
||||
Dictionary<string, string> deserializedConfig = null;
|
||||
|
||||
try
|
||||
{
|
||||
deserializedConfig = JsonSerializer.DeserializeDictionary(configurationAsJson);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Catch all exceptions because any will cause
|
||||
// this application to terminate.
|
||||
Console.WriteLine("Error deserializing {0} : {1}",
|
||||
argumentValue, ex.Message);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Every name/value pair in the config file not explicitly set on the command line
|
||||
// is treated as if it had been on the command line.
|
||||
foreach (var pair in deserializedConfig)
|
||||
{
|
||||
if (!argumentDictionary.ContainsKey(pair.Key))
|
||||
{
|
||||
argumentDictionary[pair.Key] = pair.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// For every property in the BridgeConfiguration that has not been explicitly
|
||||
// specified on the command line or via the config file, check if there is an
|
||||
// Environment variable set for it. If so, use it as if it had been on the command line.
|
||||
foreach (string key in BridgeConfiguration.ToDictionary().Keys)
|
||||
{
|
||||
// If the property is explicitly on the command line, it has highest precedence
|
||||
if (!argumentDictionary.ContainsKey(key))
|
||||
{
|
||||
// But if it is not explicitly on the command line but
|
||||
// an environment variable exists for it, it has higher precedence
|
||||
// than defaults or the config file.
|
||||
string environmentVariable = Environment.GetEnvironmentVariable(key);
|
||||
if (!String.IsNullOrWhiteSpace(environmentVariable))
|
||||
{
|
||||
argumentDictionary[key] = environmentVariable;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, apply all our command line arguments to the BridgeConfiguration,
|
||||
// overwriting any values that were the default or came from the optional config file
|
||||
BridgeConfiguration = new BridgeConfiguration(BridgeConfiguration, argumentDictionary);
|
||||
|
||||
// Finish parsing the command line arguments that are not part of BridgeConfiguration
|
||||
if (argumentDictionary.ContainsKey("allowRemote"))
|
||||
{
|
||||
AllowRemote = true;
|
||||
}
|
||||
|
||||
if (argumentDictionary.ContainsKey("ping"))
|
||||
{
|
||||
Ping = true;
|
||||
}
|
||||
|
||||
if (argumentDictionary.ContainsKey("stop"))
|
||||
{
|
||||
Stop = true;
|
||||
}
|
||||
|
||||
if (argumentDictionary.ContainsKey("stopiflocal"))
|
||||
{
|
||||
StopIfLocal = true;
|
||||
}
|
||||
|
||||
string remoteAddresses;
|
||||
if (argumentDictionary.TryGetValue("remoteAddresses", out remoteAddresses))
|
||||
{
|
||||
RemoteAddresses = remoteAddresses;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -159,11 +425,18 @@ namespace Bridge
|
|||
|
||||
private void ShowUsage()
|
||||
{
|
||||
Console.WriteLine("Starts a new instance of the Bridge. Usage is:");
|
||||
Console.WriteLine("Bridge.exe [/port:nnn] [/allowRemote] [/remoteAddresses:x,y,z");
|
||||
Console.WriteLine(" /port:nnn Listening port for the bridge (default is {0}", DefaultPortNumber);
|
||||
Console.WriteLine(" /allowRemote If specified, allows access from other than localHost (default is localhost only)");
|
||||
Console.WriteLine(" /remoteAddresses Comma-separated list of addresses firewall rules will accept (default is 'LocalSubnet')");
|
||||
Console.WriteLine("Usage is: Bridge.exe [/ping] [/stop] [/stopIfLocal] [/allowRemote] [/remoteAddresses:x,y,z] [/{BridgeProperty}:value");
|
||||
Console.WriteLine(" /ping Pings the Bridge to check if it is running");
|
||||
Console.WriteLine(" /stop Stops the Bridge if it is running");
|
||||
Console.WriteLine(" /stopIfLocal Stops the Bridge if it is running locally");
|
||||
Console.WriteLine(" /allowRemote If starting the Bridge, allows access from other than localHost (default is localhost only)");
|
||||
Console.WriteLine(" /remoteAddresses If starting the Bridge, comma-separated list of addresses firewall rules will accept (default is 'LocalSubnet')");
|
||||
Console.WriteLine(" /BridgeConfig:file Treat file as json name/value pairs to initialize any or all other options");
|
||||
|
||||
string bridgePropertyList = String.Join(Environment.NewLine + " /", new BridgeConfiguration().ToDictionary().Keys);
|
||||
Console.WriteLine(" /{0}", bridgePropertyList);
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("If no other action is specified, the Bridge will be started unless it is already running.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -67,7 +67,7 @@ namespace Bridge
|
|||
Trace.WriteLine(String.Format("{0:T} - Exception executing PUT for resource {1}{2}:{3}",
|
||||
DateTime.Now, resourceName, Environment.NewLine, exception.ToString()),
|
||||
this.GetType().Name);
|
||||
return Request.CreateResponse(HttpStatusCode.InternalServerError, new resourceResponse
|
||||
return request.CreateResponse(HttpStatusCode.InternalServerError, new resourceResponse
|
||||
{
|
||||
id = correlationId,
|
||||
details = exception.Message
|
||||
|
|
|
@ -17,7 +17,7 @@ namespace Bridge
|
|||
}
|
||||
|
||||
// Disallow concurrent resource instantation or configuration changes
|
||||
lock (ConfigController.BridgeLock)
|
||||
lock (ConfigController.ConfigLock)
|
||||
{
|
||||
AppDomain appDomain;
|
||||
if (String.IsNullOrWhiteSpace(ConfigController.CurrentAppDomainName))
|
||||
|
@ -52,7 +52,7 @@ namespace Bridge
|
|||
}
|
||||
|
||||
// Disallow concurrent resource instantation or configuration changes
|
||||
lock (ConfigController.BridgeLock)
|
||||
lock (ConfigController.ConfigLock)
|
||||
{
|
||||
AppDomain appDomain;
|
||||
if (!TypeCache.AppDomains.TryGetValue(ConfigController.CurrentAppDomainName, out appDomain))
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<asmv1:assembly manifestVersion="1.0"
|
||||
xmlns="urn:schemas-microsoft-com:asm.v1"
|
||||
xmlns:asmv1="urn:schemas-microsoft-com:asm.v1"
|
||||
xmlns:asmv2="urn:schemas-microsoft-com:asm.v2"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<assemblyIdentity version="1.0.0.0" name="Bridge.app"/>
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
|
||||
<security>
|
||||
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<requestedExecutionLevel
|
||||
level="requireAdministrator" uiAccess="false" />
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
</asmv1:assembly>
|
||||
|
|
@ -9,7 +9,7 @@ using System.Threading.Tasks;
|
|||
|
||||
namespace WcfTestBridgeCommon
|
||||
{
|
||||
public class CertificateManager
|
||||
public static class CertificateManager
|
||||
{
|
||||
private static object s_certificateLock = new object();
|
||||
private static bool s_registeredForProcessExit = false;
|
||||
|
|
|
@ -5,6 +5,7 @@ using NetFwTypeLib;
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
|
@ -12,7 +13,7 @@ namespace WcfTestBridgeCommon
|
|||
{
|
||||
// This class exists to create and delete firewall rules to
|
||||
// manage which ports are open on behalf of the Bridge.
|
||||
public class PortManager
|
||||
public static class PortManager
|
||||
{
|
||||
// This prefix is used both to name rules and to discover existing
|
||||
// rules created by this class, so it must be unique
|
||||
|
@ -157,7 +158,18 @@ namespace WcfTestBridgeCommon
|
|||
|
||||
foreach (string ruleName in ruleSet)
|
||||
{
|
||||
NetFwPolicy2.Rules.Remove(ruleName);
|
||||
try {
|
||||
NetFwPolicy2.Rules.Remove(ruleName);
|
||||
Console.WriteLine("Removed firewall rule '{0}'", ruleName);
|
||||
}
|
||||
catch (FileNotFoundException fnfe)
|
||||
{
|
||||
// This exception can happen when multiple processes
|
||||
// are cleaning up the rules, and the rule has already
|
||||
// been removed.
|
||||
Console.WriteLine("Unable to remove rule '{0}' : {1}",
|
||||
ruleName, fnfe.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
@echo off
|
||||
|
||||
pushd %~dp0src\System.Private.ServiceModel\tools\setupfiles
|
||||
|
||||
echo Building the Bridge...
|
||||
call start /wait BuildWCFTestService.cmd
|
||||
|
||||
echo Launching the Bridge (elevated) ...
|
||||
start /wait RunElevated.vbs SetupWCFTestService.cmd %*
|
||||
|
||||
set BridgeKeepRunning=true
|
||||
|
||||
pushd %~dp0src\System.Private.ServiceModel\tools\setupfiles
|
||||
call SetupWCFTestService.cmd %*
|
||||
popd
|
||||
|
||||
echo Because you started the Bridge manually, it will remain running until you close it manually.
|
||||
echo Set the BridgeKeepRunning environment variable to 'false' to allow it to be closed by OuterLoop tests.
|
||||
|
||||
:done
|
||||
popd
|
||||
popd
|
||||
|
|
Загрузка…
Ссылка в новой задаче