├── .gitignore
├── LICENSE
├── README.md
└── Src
├── ServiceBusRelayUtil.sln
├── ServiceBusRelayUtil
├── App.config
├── App.ico
├── DispatcherService.cs
├── Program.cs
├── Properties
│ └── AssemblyInfo.cs
├── RawContentTypeMapper.cs
├── ServiceBusRelayUtil.csproj
├── ServiceBusRelayUtilConfig.cs
├── azureservicebusrelaylogo_150.png
└── packages.config
└── ServiceBusRelayUtilNetCore
├── App.ico
├── DispatcherService.cs
├── Extensions
└── StringEx.cs
├── Program.cs
├── ServiceBusRelayUtilNetCore.csproj
└── appsettings.json
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.suo
8 | *.user
9 | *.userosscache
10 | *.sln.docstates
11 |
12 | # User-specific files (MonoDevelop/Xamarin Studio)
13 | *.userprefs
14 |
15 | # Build results
16 | [Dd]ebug/
17 | [Dd]ebugPublic/
18 | [Rr]elease/
19 | [Rr]eleases/
20 | x64/
21 | x86/
22 | bld/
23 | [Bb]in/
24 | [Oo]bj/
25 | [Ll]og/
26 |
27 | # Visual Studio 2015 cache/options directory
28 | .vs/
29 | # Uncomment if you have tasks that create the project's static files in wwwroot
30 | #wwwroot/
31 |
32 | # MSTest test Results
33 | [Tt]est[Rr]esult*/
34 | [Bb]uild[Ll]og.*
35 |
36 | # NUNIT
37 | *.VisualState.xml
38 | TestResult.xml
39 |
40 | # Build Results of an ATL Project
41 | [Dd]ebugPS/
42 | [Rr]eleasePS/
43 | dlldata.c
44 |
45 | # .NET Core
46 | project.lock.json
47 | project.fragment.lock.json
48 | artifacts/
49 | **/Properties/launchSettings.json
50 |
51 | *_i.c
52 | *_p.c
53 | *_i.h
54 | *.ilk
55 | *.meta
56 | *.obj
57 | *.pch
58 | *.pdb
59 | *.pgc
60 | *.pgd
61 | *.rsp
62 | *.sbr
63 | *.tlb
64 | *.tli
65 | *.tlh
66 | *.tmp
67 | *.tmp_proj
68 | *.log
69 | *.vspscc
70 | *.vssscc
71 | .builds
72 | *.pidb
73 | *.svclog
74 | *.scc
75 |
76 | # Chutzpah Test files
77 | _Chutzpah*
78 |
79 | # Visual C++ cache files
80 | ipch/
81 | *.aps
82 | *.ncb
83 | *.opendb
84 | *.opensdf
85 | *.sdf
86 | *.cachefile
87 | *.VC.db
88 | *.VC.VC.opendb
89 |
90 | # Visual Studio profiler
91 | *.psess
92 | *.vsp
93 | *.vspx
94 | *.sap
95 |
96 | # TFS 2012 Local Workspace
97 | $tf/
98 |
99 | # Guidance Automation Toolkit
100 | *.gpState
101 |
102 | # ReSharper is a .NET coding add-in
103 | _ReSharper*/
104 | *.[Rr]e[Ss]harper
105 | *.DotSettings.user
106 |
107 | # JustCode is a .NET coding add-in
108 | .JustCode
109 |
110 | # TeamCity is a build add-in
111 | _TeamCity*
112 |
113 | # DotCover is a Code Coverage Tool
114 | *.dotCover
115 |
116 | # Visual Studio code coverage results
117 | *.coverage
118 | *.coveragexml
119 |
120 | # NCrunch
121 | _NCrunch_*
122 | .*crunch*.local.xml
123 | nCrunchTemp_*
124 |
125 | # MightyMoose
126 | *.mm.*
127 | AutoTest.Net/
128 |
129 | # Web workbench (sass)
130 | .sass-cache/
131 |
132 | # Installshield output folder
133 | [Ee]xpress/
134 |
135 | # DocProject is a documentation generator add-in
136 | DocProject/buildhelp/
137 | DocProject/Help/*.HxT
138 | DocProject/Help/*.HxC
139 | DocProject/Help/*.hhc
140 | DocProject/Help/*.hhk
141 | DocProject/Help/*.hhp
142 | DocProject/Help/Html2
143 | DocProject/Help/html
144 |
145 | # Click-Once directory
146 | publish/
147 |
148 | # Publish Web Output
149 | *.[Pp]ublish.xml
150 | *.azurePubxml
151 | # TODO: Comment the next line if you want to checkin your web deploy settings
152 | # but database connection strings (with potential passwords) will be unencrypted
153 | *.pubxml
154 | *.publishproj
155 |
156 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
157 | # checkin your Azure Web App publish settings, but sensitive information contained
158 | # in these scripts will be unencrypted
159 | PublishScripts/
160 |
161 | # NuGet Packages
162 | *.nupkg
163 | # The packages folder can be ignored because of Package Restore
164 | **/packages/*
165 | # except build/, which is used as an MSBuild target.
166 | !**/packages/build/
167 | # Uncomment if necessary however generally it will be regenerated when needed
168 | #!**/packages/repositories.config
169 | # NuGet v3's project.json files produces more ignorable files
170 | *.nuget.props
171 | *.nuget.targets
172 |
173 | # Microsoft Azure Build Output
174 | csx/
175 | *.build.csdef
176 |
177 | # Microsoft Azure Emulator
178 | ecf/
179 | rcf/
180 |
181 | # Windows Store app package directories and files
182 | AppPackages/
183 | BundleArtifacts/
184 | Package.StoreAssociation.xml
185 | _pkginfo.txt
186 |
187 | # Visual Studio cache files
188 | # files ending in .cache can be ignored
189 | *.[Cc]ache
190 | # but keep track of directories ending in .cache
191 | !*.[Cc]ache/
192 |
193 | # Others
194 | ClientBin/
195 | ~$*
196 | *~
197 | *.dbmdl
198 | *.dbproj.schemaview
199 | *.jfm
200 | *.pfx
201 | *.publishsettings
202 | orleans.codegen.cs
203 |
204 | # Since there are multiple workflows, uncomment next line to ignore bower_components
205 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
206 | #bower_components/
207 |
208 | # RIA/Silverlight projects
209 | Generated_Code/
210 |
211 | # Backup & report files from converting an old project file
212 | # to a newer Visual Studio version. Backup files are not needed,
213 | # because we have git ;-)
214 | _UpgradeReport_Files/
215 | Backup*/
216 | UpgradeLog*.XML
217 | UpgradeLog*.htm
218 |
219 | # SQL Server files
220 | *.mdf
221 | *.ldf
222 | *.ndf
223 |
224 | # Business Intelligence projects
225 | *.rdl.data
226 | *.bim.layout
227 | *.bim_*.settings
228 |
229 | # Microsoft Fakes
230 | FakesAssemblies/
231 |
232 | # GhostDoc plugin setting file
233 | *.GhostDoc.xml
234 |
235 | # Node.js Tools for Visual Studio
236 | .ntvs_analysis.dat
237 | node_modules/
238 |
239 | # Typescript v1 declaration files
240 | typings/
241 |
242 | # Visual Studio 6 build log
243 | *.plg
244 |
245 | # Visual Studio 6 workspace options file
246 | *.opt
247 |
248 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
249 | *.vbw
250 |
251 | # Visual Studio LightSwitch build output
252 | **/*.HTMLClient/GeneratedArtifacts
253 | **/*.DesktopClient/GeneratedArtifacts
254 | **/*.DesktopClient/ModelManifest.xml
255 | **/*.Server/GeneratedArtifacts
256 | **/*.Server/ModelManifest.xml
257 | _Pvt_Extensions
258 |
259 | # Paket dependency manager
260 | .paket/paket.exe
261 | paket-files/
262 |
263 | # FAKE - F# Make
264 | .fake/
265 |
266 | # JetBrains Rider
267 | .idea/
268 | *.sln.iml
269 |
270 | # CodeRush
271 | .cr/
272 |
273 | # Python Tools for Visual Studio (PTVS)
274 | __pycache__/
275 | *.pyc
276 |
277 | # Cake - Uncomment if you are using it
278 | # tools/**
279 | # !tools/packages.config
280 |
281 | # Telerik's JustMock configuration file
282 | *.jmconfig
283 |
284 | # BizTalk build output
285 | *.btp.cs
286 | *.btm.cs
287 | *.odx.cs
288 | *.xsd.cs
289 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Gabo Gilabert
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Overview
2 | A relay utility for bots based on Azure Service Bus.
3 |
4 | This utility allows you to forward a message sent to a bot hosted on any channel to your local machine.
5 |
6 | It is useful for debug scenarios or for more complex situations where the BotEmulator is not enough (i.e.: you use the WebChat control hosted on a site and you need to receive ChannelData in your requests).
7 |
8 | ## Acknowledgments
9 | Part of this code is based on the work that [Pedro Felix](https://github.com/pmhsfelix) did in his project [here](https://github.com/pmhsfelix/WebApi.Explorations.ServiceBusRelayHost).
10 |
11 | # How to configure and run the utility
12 | ### Building with .Net Framework
13 |
14 | 1. Once the solution has been cloned to your machine, open the solution in Visual Studio.
15 |
16 | 2. In Solution Explorer, expand the **ServiceBusRelayUtil** folder.
17 |
18 | 3. Open the **App.config** file and replace the following values with those from your service bus (not the hybrid connection).
19 |
20 | a. "RelayNamespace" is the name of your service bus created earlier. Enter the value in place of **[Your Namespace]**.
21 |
22 | b. "RelayName" is the name of the shared access policy created in steps 9 through 11 during the service bus set up process. Enter the value in place of **[Your Relay Name]**.
23 |
24 | c. "PolicyName" is the value to the shared access policy created in steps 9 through 11 during the service bus set up process. Enter the value in place of **[Your Shared Access Policy Name]**.
25 |
26 | d. "PolicyKey" is the WCF relay to be used. Remember, this relay is programmatically created and only exists on your machine. Create a new, unused name and enter the value in place of **[Your Policy's Key]**.
27 |
28 | e. "TargetServiceAddress" sets the port to be used for localhost. The address and port number should match the address and port used by your bot. Enter a value in place of the "TODO" string part. For example, "http://localhost:[PORT]".
29 |
30 | 4. Before testing the relay, your Azure Web Bot's messaging endpoint must be updated to match the relay.
31 |
32 | a. Login to the Azure portal and open your Web App Bot.
33 |
34 | b. Select **Settings** under Bot management to open the settings blade.
35 |
36 | c. In the **Messaging endpoint** field, enter the service bus namespace and relay. The relay should match the relay name entered in the **App.config** file and should not exist in Azure.
37 |
38 | d. Append **"/api/messages"** to the end to create the full endpoint to be used. For example, “https://example-service-bus.servicebus.windows.net/wcf-example-relay/api/messages".
39 |
40 | e. Click **Save** when completed.
41 |
42 | 5. In Visual Studio, press **F5** to run the project.
43 |
44 | 6. Open and run your locally hosted bot.
45 |
46 | 7. Test your bot on a channel (Test in Web Chat, Skype, Teams, etc.). User data is captured and logged as activity occurs.
47 |
48 | - When using the Bot Framework Emulator: The endpoint entered in Emulator must be the service bus endpoint saved in your Azure Web Bot **Settings** blade, under **Messaging Endpoint**.
49 |
50 | 8. Once testing is completed, you can compile the project into an executable.
51 |
52 | a. Right click the project folder in Visual Studio and select **Build**.
53 |
54 | b. The .exe will output to the **/bin/debug** folder, along with other necessary files, located in the project’s directory folder. All the files are necessary to run and should be included when moving the .exe to a new folder/location.
55 | - The **app.config** is in the same folder and can be edited as credentials change without needing to recompile the project.
56 |
57 | ### Building with .Net Core
58 |
59 | 1. Once the solution has been cloned to your machine, open the solution in Visual Studio.
60 |
61 | 2. In Solution Explorer, expand the **ServiceBusRelayUtilNetCore** folder.
62 |
63 | 3. Open the **appsettings.json** file and replace the following values with those from your service bus hybrid connection.
64 |
65 | a. "RelayNamespace" is the name of your service bus created earlier. Enter the value in place of **[Your Namespace]**.
66 |
67 | b. "RelayName" is the name of the hybrid connection created in step 12. Enter the value in place of **[Your Relay Name]**.
68 |
69 | c. "PolicyName" is the name of the shared access policy created in steps 9 through 11 during the service bus set up process. Enter the value in place of **[Your Shared Access Policy Name]**.
70 |
71 | d. "PolicyKey" is the value to the shared access policy created in steps 9 through 11 during the service bus set up process. Enter the value in place of **[Your Policy's Key]**.
72 |
73 | e. "TargetServiceAddress" sets the port to be used for localhost. The address and port number should match the address and port used by your bot. Enter a value in place of the **"http://localhost:[PORT]"**. For example, "http://localhost:3978".
74 |
75 | 4. Before testing the relay, your Azure Web App Bot's messaging endpoint must be updated to match the relay.
76 |
77 | a. Login to the Azure portal and open your Web App Bot.
78 |
79 | b. Select **Settings** under Bot management to open the settings blade.
80 |
81 | c. In the **Messaging endpoint** field, enter the service bus namespace and relay.
82 |
83 | d. Append **"/api/messages"** to the end to create the full endpoint to be used. For example, “https://example-service-bus.servicebus.windows.net/hc1/api/messages".
84 |
85 | e. Click **Save** when completed.
86 |
87 | 5. In Visual Studio, press **F5** to run the project.
88 |
89 | 6. Open and run your locally hosted bot.
90 |
91 | 7. Test your bot on a channel (Test in Web Chat, Skype, Teams, etc.). User data is captured and logged as activity occurs.
92 |
93 | - When using the Bot Framework Emulator: The endpoint entered in Emulator must be the service bus endpoint saved in your Azure Web Bot **Settings** blade, under **Messaging Endpoint**.
94 |
95 | 8. Once testing is completed, you can compile the project into an executable.
96 |
97 | a. Right click the project folder in Visual Studio and select **Publish**.
98 |
99 | b. For **Pick a publish Target**, select **Folder**.
100 |
101 | c. For **Folder or File Share**, choose an output location or keep the default.
102 |
103 | d. Click **Create Profile** to create a publish profile.
104 |
105 | e. Click **Configure...** to change the build configuration and change the following:
106 |
107 | - **Configuration** to "Debug | Any CPU"
108 | - **Deployment Mode** to "Self-contained"
109 | - **Target Runtime** to "win-x64"
110 |
111 | f. Click **Save** and then **Publish**
112 |
113 | g. The .exe will output to the **/bin/debug** folder, along with other necessary files, located in the project’s directory folder. All the files are necessary to run and should be included when moving the .exe to a new folder/location.
114 | - The **appsettings.json** is in the same folder and can be edited as credentials change without needing to recompile the project.
115 |
--------------------------------------------------------------------------------
/Src/ServiceBusRelayUtil.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.27004.2005
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceBusRelayUtil", "ServiceBusRelayUtil\ServiceBusRelayUtil.csproj", "{B9DA41E3-4E0A-41D6-B7D1-64AB017D4FED}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceBusRelayUtilNetCore", "ServiceBusRelayUtilNetCore\ServiceBusRelayUtilNetCore.csproj", "{9AECFF0E-26C7-4D96-A00A-8A09198711EC}"
9 | EndProject
10 | Global
11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
12 | Debug|Any CPU = Debug|Any CPU
13 | Release|Any CPU = Release|Any CPU
14 | EndGlobalSection
15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
16 | {B9DA41E3-4E0A-41D6-B7D1-64AB017D4FED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
17 | {B9DA41E3-4E0A-41D6-B7D1-64AB017D4FED}.Debug|Any CPU.Build.0 = Debug|Any CPU
18 | {B9DA41E3-4E0A-41D6-B7D1-64AB017D4FED}.Release|Any CPU.ActiveCfg = Release|Any CPU
19 | {B9DA41E3-4E0A-41D6-B7D1-64AB017D4FED}.Release|Any CPU.Build.0 = Release|Any CPU
20 | {9AECFF0E-26C7-4D96-A00A-8A09198711EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21 | {9AECFF0E-26C7-4D96-A00A-8A09198711EC}.Debug|Any CPU.Build.0 = Debug|Any CPU
22 | {9AECFF0E-26C7-4D96-A00A-8A09198711EC}.Release|Any CPU.ActiveCfg = Release|Any CPU
23 | {9AECFF0E-26C7-4D96-A00A-8A09198711EC}.Release|Any CPU.Build.0 = Release|Any CPU
24 | EndGlobalSection
25 | GlobalSection(SolutionProperties) = preSolution
26 | HideSolutionNode = FALSE
27 | EndGlobalSection
28 | GlobalSection(ExtensibilityGlobals) = postSolution
29 | SolutionGuid = {2C96A03F-F825-402A-B109-0F1FC60BA3CE}
30 | EndGlobalSection
31 | EndGlobal
32 |
--------------------------------------------------------------------------------
/Src/ServiceBusRelayUtil/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/Src/ServiceBusRelayUtil/App.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gabog/AzureServiceBusBotRelay/6151ecf933029cd51e86f9969d25566e896c4093/Src/ServiceBusRelayUtil/App.ico
--------------------------------------------------------------------------------
/Src/ServiceBusRelayUtil/DispatcherService.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Net.Http;
6 | using System.ServiceModel;
7 | using System.ServiceModel.Channels;
8 | using System.ServiceModel.Web;
9 | using System.Text;
10 | using System.Threading;
11 | using System.Threading.Tasks;
12 | using System.Xml;
13 | using Microsoft.ServiceBus.Web;
14 | using Newtonsoft.Json;
15 | using Newtonsoft.Json.Linq;
16 | using Formatting = Newtonsoft.Json.Formatting;
17 |
18 | namespace GaboG.ServiceBusRelayUtil
19 | {
20 | [ServiceContract(Namespace = "http://samples.microsoft.com/ServiceModel/Relay/")]
21 | [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple)]
22 | internal class DispatcherService
23 | {
24 | private static readonly HashSet _httpContentHeaders = new HashSet
25 | {
26 | "Allow",
27 | "Content-Encoding",
28 | "Content-Language",
29 | "Content-Length",
30 | "Content-Location",
31 | "Content-MD5",
32 | "Content-Range",
33 | "Content-Type",
34 | "Expires",
35 | "Last-Modified"
36 | };
37 |
38 | private readonly ServiceBusRelayUtilConfig _config;
39 |
40 | public DispatcherService(ServiceBusRelayUtilConfig config)
41 | {
42 | _config = config;
43 | }
44 |
45 | [WebGet(UriTemplate = "*")]
46 | [OperationContract(AsyncPattern = true)]
47 | public async Task GetAsync()
48 | {
49 | try
50 | {
51 | var ti0 = DateTime.Now;
52 | Console.WriteLine("In GetAsync:");
53 | var context = WebOperationContext.Current;
54 | var request = BuildForwardedRequest(context, null);
55 | Console.WriteLine("...calling {0}...", request.RequestUri);
56 | HttpResponseMessage response;
57 | using (var client = new HttpClient())
58 | {
59 | response = await client.SendAsync(request, CancellationToken.None);
60 | }
61 |
62 | Console.WriteLine("...and back {0:N0} ms...", DateTime.Now.Subtract(ti0).TotalMilliseconds);
63 | Console.WriteLine("");
64 |
65 | Console.WriteLine("...reading and creating response...");
66 | CopyHttpResponseMessageToOutgoingResponse(response, context.OutgoingResponse);
67 | var stream = response.Content != null ? await response.Content.ReadAsStreamAsync() : null;
68 | var message = StreamMessageHelper.CreateMessage(MessageVersion.None, "GETRESPONSE", stream ?? new MemoryStream());
69 | Console.WriteLine("...and done (total time: {0:N0} ms).", DateTime.Now.Subtract(ti0).TotalMilliseconds);
70 | Console.WriteLine("");
71 | return message;
72 | }
73 | catch (Exception ex)
74 | {
75 | WriteException(ex);
76 | throw;
77 | }
78 | }
79 |
80 | [WebInvoke(UriTemplate = "*", Method = "*")]
81 | [OperationContract(AsyncPattern = true)]
82 | public async Task InvokeAsync(Message msg)
83 | {
84 | try
85 | {
86 | var ti0 = DateTime.Now;
87 | WriteFlowerLine();
88 | Console.WriteLine("In InvokeAsync:");
89 | var context = WebOperationContext.Current;
90 | var request = BuildForwardedRequest(context, msg);
91 | Console.WriteLine("...calling {0}", request.RequestUri);
92 | HttpResponseMessage response;
93 | using (var client = new HttpClient())
94 | {
95 | response = await client.SendAsync(request, CancellationToken.None);
96 | }
97 |
98 | Console.WriteLine("...and done {0:N0} ms...", DateTime.Now.Subtract(ti0).TotalMilliseconds);
99 |
100 | Console.WriteLine("...reading and creating response...");
101 | CopyHttpResponseMessageToOutgoingResponse(response, context.OutgoingResponse);
102 | var stream = response.Content != null ? await response.Content.ReadAsStreamAsync() : null;
103 | var message = StreamMessageHelper.CreateMessage(MessageVersion.None, "GETRESPONSE", stream ?? new MemoryStream());
104 | Console.WriteLine("...and done (total time: {0:N0} ms).", DateTime.Now.Subtract(ti0).TotalMilliseconds);
105 | return message;
106 | }
107 | catch (Exception ex)
108 | {
109 | WriteException(ex);
110 | throw;
111 | }
112 | }
113 |
114 | private HttpRequestMessage BuildForwardedRequest(WebOperationContext context, Message msg)
115 | {
116 | var incomingRequest = context.IncomingRequest;
117 |
118 | var mappedUri = new Uri(incomingRequest.UriTemplateMatch.RequestUri.ToString().Replace(_config.RelayAddress.ToString(), _config.TargetAddress.ToString()));
119 | var newRequest = new HttpRequestMessage(new HttpMethod(incomingRequest.Method), mappedUri);
120 |
121 | // Copy headers
122 | var hostHeader = _config.TargetAddress.Host + (_config.TargetAddress.Port != 80 || _config.TargetAddress.Port != 443 ? ":" + _config.TargetAddress.Port : "");
123 | foreach (var name in incomingRequest.Headers.AllKeys.Where(name => !_httpContentHeaders.Contains(name)))
124 | {
125 | newRequest.Headers.TryAddWithoutValidation(name, name == "Host" ? hostHeader : incomingRequest.Headers.Get(name));
126 | }
127 |
128 | if (msg != null)
129 | {
130 | Stream messageStream = null;
131 | if (msg.Properties.TryGetValue("WebBodyFormatMessageProperty", out var value))
132 | {
133 | if (value is WebBodyFormatMessageProperty prop && (prop.Format == WebContentFormat.Json || prop.Format == WebContentFormat.Raw))
134 | {
135 | messageStream = StreamMessageHelper.GetStream(msg);
136 | }
137 | }
138 | else
139 | {
140 | var ms = new MemoryStream();
141 | using (var xw = XmlDictionaryWriter.CreateTextWriter(ms, Encoding.UTF8, false))
142 | {
143 | msg.WriteBodyContents(xw);
144 | }
145 |
146 | ms.Seek(0, SeekOrigin.Begin);
147 | messageStream = ms;
148 | }
149 |
150 | if (messageStream != null)
151 | {
152 | if (_config.BufferRequestContent)
153 | {
154 | var ms1 = new MemoryStream();
155 | messageStream.CopyTo(ms1);
156 | ms1.Seek(0, SeekOrigin.Begin);
157 | newRequest.Content = new StreamContent(ms1);
158 | }
159 | else
160 | {
161 | var ms1 = new MemoryStream();
162 | messageStream.CopyTo(ms1);
163 | ms1.Seek(0, SeekOrigin.Begin);
164 |
165 | var debugMs = new MemoryStream();
166 | ms1.CopyTo(debugMs);
167 | debugMs.Seek(0, SeekOrigin.Begin);
168 |
169 | var result = Encoding.UTF8.GetString(debugMs.ToArray());
170 | WriteJsonObject(result);
171 |
172 | ms1.Seek(0, SeekOrigin.Begin);
173 | newRequest.Content = new StreamContent(ms1);
174 | }
175 |
176 | foreach (var name in incomingRequest.Headers.AllKeys.Where(name => _httpContentHeaders.Contains(name)))
177 | {
178 | newRequest.Content.Headers.TryAddWithoutValidation(name, incomingRequest.Headers.Get(name));
179 | }
180 | }
181 | }
182 |
183 | return newRequest;
184 | }
185 |
186 | private static void WriteException(Exception ex)
187 | {
188 | Console.ForegroundColor = ConsoleColor.Red;
189 | Console.WriteLine(ex);
190 | Console.WriteLine("");
191 | Console.ResetColor();
192 | }
193 |
194 | private static void WriteJsonObject(string result)
195 | {
196 | Console.ForegroundColor = ConsoleColor.Yellow;
197 |
198 | var formatted = result;
199 | if (IsValidJson(result))
200 | {
201 | var s = new JsonSerializerSettings
202 | {
203 | Formatting = Formatting.Indented
204 | };
205 |
206 | dynamic o = JsonConvert.DeserializeObject(result);
207 | formatted = JsonConvert.SerializeObject(o, s);
208 | }
209 |
210 | Console.WriteLine(formatted);
211 | Console.ResetColor();
212 | }
213 |
214 | private static void WriteFlowerLine()
215 | {
216 | Console.ForegroundColor = ConsoleColor.Green;
217 | Console.WriteLine("\r\n=> {0:MM/dd/yyyy hh:mm:ss.fff tt} {1}", DateTime.Now, new string('*', 80));
218 | Console.ResetColor();
219 | }
220 |
221 | private static void CopyHttpResponseMessageToOutgoingResponse(HttpResponseMessage response, OutgoingWebResponseContext outgoingResponse)
222 | {
223 | outgoingResponse.StatusCode = response.StatusCode;
224 | outgoingResponse.StatusDescription = response.ReasonPhrase;
225 | if (response.Content == null)
226 | {
227 | outgoingResponse.SuppressEntityBody = true;
228 | }
229 |
230 | foreach (var kvp in response.Headers)
231 | {
232 | foreach (var value in kvp.Value)
233 | {
234 | outgoingResponse.Headers.Add(kvp.Key, value);
235 | }
236 | }
237 |
238 | if (response.Content != null)
239 | {
240 | foreach (var kvp in response.Content.Headers)
241 | {
242 | foreach (var value in kvp.Value)
243 | {
244 | outgoingResponse.Headers.Add(kvp.Key, value);
245 | }
246 | }
247 | }
248 | }
249 |
250 | private static bool IsValidJson(string strInput)
251 | {
252 | strInput = strInput.Trim();
253 | if ((!strInput.StartsWith("{") || !strInput.EndsWith("}")) && (!strInput.StartsWith("[") || !strInput.EndsWith("]")))
254 | {
255 | return false;
256 | }
257 |
258 | try
259 | {
260 | JToken.Parse(strInput);
261 | return true;
262 | }
263 | catch //some other exception
264 | {
265 | return false;
266 | }
267 | }
268 | }
269 | }
--------------------------------------------------------------------------------
/Src/ServiceBusRelayUtil/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Configuration;
3 | using System.ServiceModel.Channels;
4 | using System.ServiceModel.Web;
5 | using Microsoft.ServiceBus;
6 |
7 | namespace GaboG.ServiceBusRelayUtil
8 | {
9 | internal class Program
10 | {
11 | // https://github.com/pmhsfelix/WebApi.Explorations.ServiceBusRelayHost
12 | // https://docs.microsoft.com/en-us/azure/service-bus-relay/service-bus-relay-rest-tutorial
13 | private static void Main()
14 | {
15 | var relayNamespace = ConfigurationManager.AppSettings["RelayNamespace"];
16 | var relayAddress = ServiceBusEnvironment.CreateServiceUri("https", relayNamespace, ConfigurationManager.AppSettings["RelayName"]);
17 |
18 | var config = new ServiceBusRelayUtilConfig
19 | {
20 | RelayAddress = relayAddress,
21 | RelayPolicyName = ConfigurationManager.AppSettings["PolicyName"],
22 | RelayPolicyKey = ConfigurationManager.AppSettings["PolicyKey"],
23 | MaxReceivedMessageSize = long.Parse(ConfigurationManager.AppSettings["MaxReceivedMessageSize"]),
24 | TargetAddress = new Uri(ConfigurationManager.AppSettings["TargetServiceAddress"])
25 | };
26 |
27 | var host = CreateWebServiceHost(config, relayAddress);
28 | host.Open();
29 |
30 | Console.WriteLine("Azure Service Bus is listening at \n\r\t{0}\n\rrouting requests to \n\r\t{1}\n\r\n\r", relayAddress, config.TargetAddress);
31 | Console.WriteLine();
32 | Console.WriteLine("Press [Enter] to exit");
33 | Console.ReadLine();
34 |
35 | host.Close();
36 | }
37 |
38 | private static WebServiceHost CreateWebServiceHost(ServiceBusRelayUtilConfig config, Uri address)
39 | {
40 | var host = new WebServiceHost(new DispatcherService(config));
41 | var binding = GetBinding(config.MaxReceivedMessageSize);
42 | var endpoint = host.AddServiceEndpoint(typeof(DispatcherService), binding, address);
43 | var behavior = GetTransportBehavior(config.RelayPolicyName, config.RelayPolicyKey);
44 | endpoint.Behaviors.Add(behavior);
45 | return host;
46 | }
47 |
48 | private static Binding GetBinding(long maxReceivedMessageSize)
49 | {
50 | var webHttpRelayBinding = new WebHttpRelayBinding(EndToEndWebHttpSecurityMode.None, RelayClientAuthenticationType.None)
51 | {
52 | MaxReceivedMessageSize = maxReceivedMessageSize
53 | };
54 | var bindingElements = webHttpRelayBinding.CreateBindingElements();
55 | var webMessageEncodingBindingElement = bindingElements.Find();
56 | webMessageEncodingBindingElement.ContentTypeMapper = new RawContentTypeMapper();
57 | return new CustomBinding(bindingElements);
58 | }
59 |
60 | private static TransportClientEndpointBehavior GetTransportBehavior(string keyName, string sharedAccessKey)
61 | {
62 | return new TransportClientEndpointBehavior
63 | {
64 | TokenProvider = TokenProvider.CreateSharedAccessSignatureTokenProvider(keyName, sharedAccessKey)
65 | };
66 | }
67 | }
68 | }
--------------------------------------------------------------------------------
/Src/ServiceBusRelayUtil/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
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("ServiceBusRelayUtil")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("ServiceBusRelayUtil")]
13 | [assembly: AssemblyCopyright("Copyright © 2017")]
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("b9da41e3-4e0a-41d6-b7d1-64ab017d4fed")]
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")]
37 |
--------------------------------------------------------------------------------
/Src/ServiceBusRelayUtil/RawContentTypeMapper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.ServiceModel.Channels;
3 |
4 | namespace GaboG.ServiceBusRelayUtil
5 | {
6 | internal class RawContentTypeMapper : WebContentTypeMapper
7 | {
8 | public override WebContentFormat GetMessageFormatForContentType(string contentType)
9 | {
10 | return WebContentFormat.Raw;
11 | }
12 | }
13 | }
--------------------------------------------------------------------------------
/Src/ServiceBusRelayUtil/ServiceBusRelayUtil.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {B9DA41E3-4E0A-41D6-B7D1-64AB017D4FED}
8 | Exe
9 | GaboG.ServiceBusRelayUtil
10 | ServiceBusRelayUtil
11 | v4.7
12 | 512
13 | true
14 |
15 |
16 |
17 |
18 | AnyCPU
19 | true
20 | full
21 | false
22 | bin\Debug\
23 | DEBUG;TRACE
24 | prompt
25 | 4
26 |
27 |
28 | AnyCPU
29 | pdbonly
30 | true
31 | bin\Release\
32 | TRACE
33 | prompt
34 | 4
35 |
36 |
37 | App.ico
38 |
39 |
40 |
41 | ..\packages\Microsoft.Azure.Amqp.2.3.7\lib\net45\Microsoft.Azure.Amqp.dll
42 |
43 |
44 | ..\packages\Microsoft.Azure.ServiceBus.3.3.0\lib\net461\Microsoft.Azure.ServiceBus.dll
45 |
46 |
47 | ..\packages\Microsoft.Azure.Services.AppAuthentication.1.0.3\lib\net452\Microsoft.Azure.Services.AppAuthentication.dll
48 |
49 |
50 | ..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.19.8\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll
51 |
52 |
53 | ..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.19.8\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll
54 |
55 |
56 | ..\packages\Microsoft.IdentityModel.Logging.5.2.2\lib\net451\Microsoft.IdentityModel.Logging.dll
57 |
58 |
59 | ..\packages\Microsoft.IdentityModel.Tokens.5.2.2\lib\net451\Microsoft.IdentityModel.Tokens.dll
60 |
61 |
62 | ..\packages\WindowsAzure.ServiceBus.5.1.0\lib\net46\Microsoft.ServiceBus.dll
63 |
64 |
65 | ..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll
66 |
67 |
68 |
69 |
70 |
71 |
72 | ..\packages\System.Diagnostics.DiagnosticSource.4.4.1\lib\net46\System.Diagnostics.DiagnosticSource.dll
73 |
74 |
75 | ..\packages\System.IdentityModel.Tokens.Jwt.5.2.2\lib\net451\System.IdentityModel.Tokens.Jwt.dll
76 |
77 |
78 | ..\packages\System.IO.4.3.0\lib\net462\System.IO.dll
79 |
80 |
81 | ..\packages\System.Net.WebSockets.4.3.0\lib\net46\System.Net.WebSockets.dll
82 |
83 |
84 | ..\packages\System.Net.WebSockets.Client.4.3.2\lib\net46\System.Net.WebSockets.Client.dll
85 |
86 |
87 | ..\packages\System.Runtime.4.3.0\lib\net462\System.Runtime.dll
88 |
89 |
90 |
91 | ..\packages\System.Runtime.Serialization.Primitives.4.3.0\lib\net46\System.Runtime.Serialization.Primitives.dll
92 |
93 |
94 | ..\packages\System.Runtime.Serialization.Xml.4.3.0\lib\net46\System.Runtime.Serialization.Xml.dll
95 |
96 |
97 | ..\packages\System.Security.Cryptography.Algorithms.4.3.1\lib\net463\System.Security.Cryptography.Algorithms.dll
98 |
99 |
100 | ..\packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll
101 |
102 |
103 | ..\packages\System.Security.Cryptography.Primitives.4.3.0\lib\net46\System.Security.Cryptography.Primitives.dll
104 |
105 |
106 | ..\packages\System.Security.Cryptography.X509Certificates.4.3.2\lib\net461\System.Security.Cryptography.X509Certificates.dll
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 | Designer
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
140 |
141 |
142 |
143 |
--------------------------------------------------------------------------------
/Src/ServiceBusRelayUtil/ServiceBusRelayUtilConfig.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace GaboG.ServiceBusRelayUtil
4 | {
5 | public class ServiceBusRelayUtilConfig
6 | {
7 | public string RelayPolicyName { get; set; }
8 | public string RelayPolicyKey { get; set; }
9 | public Uri RelayAddress { get; set; }
10 |
11 | public bool BufferRequestContent { get; set; }
12 | public long MaxReceivedMessageSize { get; set; }
13 | public Uri TargetAddress { get; set; }
14 | }
15 | }
--------------------------------------------------------------------------------
/Src/ServiceBusRelayUtil/azureservicebusrelaylogo_150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gabog/AzureServiceBusBotRelay/6151ecf933029cd51e86f9969d25566e896c4093/Src/ServiceBusRelayUtil/azureservicebusrelaylogo_150.png
--------------------------------------------------------------------------------
/Src/ServiceBusRelayUtil/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/Src/ServiceBusRelayUtilNetCore/App.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gabog/AzureServiceBusBotRelay/6151ecf933029cd51e86f9969d25566e896c4093/Src/ServiceBusRelayUtilNetCore/App.ico
--------------------------------------------------------------------------------
/Src/ServiceBusRelayUtilNetCore/DispatcherService.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net;
3 | using System.Net.Http;
4 | using System.Net.Http.Headers;
5 | using System.Threading;
6 | using System.Threading.Tasks;
7 | using GaboG.ServiceBusRelayUtilNetCore.Extensions;
8 | using Microsoft.Azure.Relay;
9 | using Newtonsoft.Json;
10 | using Newtonsoft.Json.Linq;
11 |
12 | namespace GaboG.ServiceBusRelayUtilNetCore
13 |
14 | {
15 | internal class DispatcherService
16 | {
17 | private readonly HttpClient _httpClient;
18 | private readonly string _hybridConnectionSubPath;
19 | private readonly HybridConnectionListener _listener;
20 | private readonly Uri _targetServiceAddress;
21 |
22 | public DispatcherService(string relayNamespace, string connectionName, string keyName, string key, Uri targetServiceAddress)
23 | {
24 | _targetServiceAddress = targetServiceAddress;
25 |
26 | var tokenProvider = TokenProvider.CreateSharedAccessSignatureTokenProvider(keyName, key);
27 | _listener = new HybridConnectionListener(new Uri($"sb://{relayNamespace}/{connectionName}"), tokenProvider);
28 |
29 | _httpClient = new HttpClient
30 | {
31 | BaseAddress = targetServiceAddress
32 | };
33 | _httpClient.DefaultRequestHeaders.ExpectContinue = false;
34 |
35 | _hybridConnectionSubPath = _listener.Address.AbsolutePath.EnsureEndsWith("/");
36 | }
37 |
38 | public async Task OpenAsync(CancellationToken cancelToken)
39 | {
40 | _listener.RequestHandler = ListenerRequestHandler;
41 | await _listener.OpenAsync(cancelToken);
42 | Console.WriteLine("Azure Service Bus is listening on \n\r\t{0}\n\rand routing requests to \n\r\t{1}\n\r\n\r", _listener.Address, _httpClient.BaseAddress);
43 | Console.WriteLine("Press [Enter] to exit");
44 | }
45 |
46 | public Task CloseAsync(CancellationToken cancelToken)
47 | {
48 | _httpClient.Dispose();
49 | return _listener.CloseAsync(cancelToken);
50 | }
51 |
52 | private async void ListenerRequestHandler(RelayedHttpListenerContext context)
53 | {
54 | var startTimeUtc = DateTime.UtcNow;
55 | try
56 | {
57 | Console.WriteLine("Calling {0}...", _targetServiceAddress);
58 | var requestMessage = CreateHttpRequestMessage(context);
59 | var responseMessage = await _httpClient.SendAsync(requestMessage);
60 | await SendResponseAsync(context, responseMessage);
61 | await context.Response.CloseAsync();
62 | }
63 |
64 | catch (Exception ex)
65 | {
66 | LogException(ex);
67 | SendErrorResponse(ex, context);
68 | }
69 | finally
70 | {
71 | LogRequest(startTimeUtc);
72 | }
73 | }
74 |
75 | private async Task SendResponseAsync(RelayedHttpListenerContext context, HttpResponseMessage responseMessage)
76 | {
77 | context.Response.StatusCode = responseMessage.StatusCode;
78 | context.Response.StatusDescription = responseMessage.ReasonPhrase;
79 | foreach (var header in responseMessage.Headers)
80 | {
81 | if (string.Equals(header.Key, "Transfer-Encoding"))
82 | {
83 | continue;
84 | }
85 |
86 | context.Response.Headers.Add(header.Key, string.Join(",", header.Value));
87 | }
88 |
89 | var responseStream = await responseMessage.Content.ReadAsStreamAsync();
90 | await responseStream.CopyToAsync(context.Response.OutputStream);
91 | }
92 |
93 | private void SendErrorResponse(Exception ex, RelayedHttpListenerContext context)
94 | {
95 | context.Response.StatusCode = HttpStatusCode.InternalServerError;
96 | context.Response.StatusDescription = $"Internal Server Error: {ex.GetType().FullName}: {ex.Message}";
97 | context.Response.Close();
98 | }
99 |
100 | private HttpRequestMessage CreateHttpRequestMessage(RelayedHttpListenerContext context)
101 | {
102 | var requestMessage = new HttpRequestMessage();
103 | if (context.Request.HasEntityBody)
104 | {
105 | requestMessage.Content = new StreamContent(context.Request.InputStream);
106 | // Experiment to see if I can capture the return message instead of having the bot responding directly (so far it doesn't work).
107 | //var contentStream = new MemoryStream();
108 | //var writer = new StreamWriter(contentStream);
109 | //var newActivity = requestMessage.Content.ReadAsStringAsync().Result.Replace("https://directline.botframework.com/", "https://localhost:44372/");
110 | //writer.Write(newActivity);
111 | //writer.Flush();
112 | //contentStream.Position = 0;
113 | //requestMessage.Content = new StreamContent(contentStream);
114 | var contentType = context.Request.Headers[HttpRequestHeader.ContentType];
115 | if (!string.IsNullOrEmpty(contentType))
116 | {
117 | requestMessage.Content.Headers.ContentType = MediaTypeHeaderValue.Parse(contentType);
118 | }
119 | }
120 |
121 | var relativePath = context.Request.Url.GetComponents(UriComponents.PathAndQuery, UriFormat.Unescaped);
122 | relativePath = relativePath.Replace(_hybridConnectionSubPath, string.Empty, StringComparison.OrdinalIgnoreCase);
123 | requestMessage.RequestUri = new Uri(relativePath, UriKind.RelativeOrAbsolute);
124 | requestMessage.Method = new HttpMethod(context.Request.HttpMethod);
125 |
126 | foreach (var headerName in context.Request.Headers.AllKeys)
127 | {
128 | if (string.Equals(headerName, "Host", StringComparison.OrdinalIgnoreCase) ||
129 | string.Equals(headerName, "Content-Type", StringComparison.OrdinalIgnoreCase))
130 | {
131 | // Don't flow these headers here
132 | continue;
133 | }
134 |
135 | requestMessage.Headers.Add(headerName, context.Request.Headers[headerName]);
136 | }
137 |
138 | LogRequestActivity(requestMessage);
139 |
140 | return requestMessage;
141 | }
142 |
143 | private void LogRequest(DateTime startTimeUtc)
144 | {
145 | var stopTimeUtc = DateTime.UtcNow;
146 | //var buffer = new StringBuilder();
147 | //buffer.Append($"{startTimeUtc.ToString("s", CultureInfo.InvariantCulture)}, ");
148 | //buffer.Append($"\"{context.Request.HttpMethod} {context.Request.Url.GetComponents(UriComponents.PathAndQuery, UriFormat.Unescaped)}\", ");
149 | //buffer.Append($"{(int)context.Response.StatusCode}, ");
150 | //buffer.Append($"{(int)stopTimeUtc.Subtract(startTimeUtc).TotalMilliseconds}");
151 | //Console.WriteLine(buffer);
152 |
153 | Console.WriteLine("...and back {0:N0} ms...", stopTimeUtc.Subtract(startTimeUtc).TotalMilliseconds);
154 | Console.WriteLine("");
155 | }
156 |
157 | private void LogRequestActivity(HttpRequestMessage requestMessage)
158 | {
159 | var content = requestMessage.Content.ReadAsStringAsync().Result;
160 | Console.ForegroundColor = ConsoleColor.Yellow;
161 |
162 | var formatted = content;
163 | if (IsValidJson(formatted))
164 | {
165 | var s = new JsonSerializerSettings
166 | {
167 | Formatting = Formatting.Indented
168 | };
169 |
170 | dynamic o = JsonConvert.DeserializeObject(content);
171 | formatted = JsonConvert.SerializeObject(o, s);
172 | }
173 |
174 | Console.WriteLine(formatted);
175 | Console.ResetColor();
176 | }
177 |
178 | private static void LogException(Exception ex)
179 | {
180 | Console.ForegroundColor = ConsoleColor.Red;
181 | Console.WriteLine(ex);
182 | Console.WriteLine("");
183 | Console.ResetColor();
184 | }
185 |
186 | private static bool IsValidJson(string strInput)
187 | {
188 | strInput = strInput.Trim();
189 | if ((!strInput.StartsWith("{") || !strInput.EndsWith("}")) && (!strInput.StartsWith("[") || !strInput.EndsWith("]")))
190 | {
191 | return false;
192 | }
193 |
194 | try
195 | {
196 | JToken.Parse(strInput);
197 | return true;
198 | }
199 | catch
200 | {
201 | return false;
202 | }
203 | }
204 | }
205 | }
--------------------------------------------------------------------------------
/Src/ServiceBusRelayUtilNetCore/Extensions/StringEx.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using System;
5 |
6 | namespace GaboG.ServiceBusRelayUtilNetCore.Extensions
7 | {
8 | public static class StringEx
9 | {
10 | ///
11 | /// Ensures the given string ends with the requested pattern. If it does no allocations are performed.
12 | ///
13 | public static string EnsureEndsWith(this string s, string value, StringComparison comparisonType = StringComparison.Ordinal)
14 | {
15 | if (!string.IsNullOrEmpty(s) && s.EndsWith(value, comparisonType))
16 | {
17 | return s;
18 | }
19 |
20 | return s + value;
21 | }
22 | }
23 | }
--------------------------------------------------------------------------------
/Src/ServiceBusRelayUtilNetCore/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 | using Microsoft.Extensions.Configuration;
6 |
7 | // https://docs.microsoft.com/en-us/azure/service-bus-relay/service-bus-relay-rest-tutorial
8 | // https://github.com/Azure/azure-relay-dotnet
9 | // https://docs.microsoft.com/en-us/azure/service-bus-relay/relay-hybrid-connections-http-requests-dotnet-get-started
10 |
11 | // This is what I think I need
12 | // https://github.com/Azure/azure-relay/blob/master/samples/hybrid-connections/dotnet/hcreverseproxy/README.md
13 | // https://github.com/Azure/azure-relay/tree/master/samples/hybrid-connections/dotnet/hcreverseproxy
14 |
15 | // Publish
16 | // https://stackoverflow.com/questions/44074121/build-net-core-console-application-to-output-an-exe
17 | // https://docs.microsoft.com/en-us/dotnet/core/rid-catalog
18 |
19 | namespace GaboG.ServiceBusRelayUtilNetCore
20 | {
21 | public class Program
22 | {
23 | public static IConfiguration Configuration { get; set; }
24 |
25 | public static void Main(string[] args)
26 | {
27 | var builder = new ConfigurationBuilder()
28 | .SetBasePath(AppDomain.CurrentDomain.BaseDirectory)
29 | .AddJsonFile("appsettings.json", true, true)
30 | .AddEnvironmentVariables();
31 | Configuration = builder.Build();
32 |
33 | RunAsync().GetAwaiter().GetResult();
34 | }
35 |
36 | static async Task RunAsync()
37 | {
38 | var relayNamespace = Configuration["RelayNamespace"];
39 | var connectionName = Configuration["RelayName"];
40 | var keyName = Configuration["PolicyName"];
41 | var key = Configuration["PolicyKey"];
42 | var targetServiceAddress = new Uri(Configuration["TargetServiceAddress"]);
43 |
44 | var hybridProxy = new DispatcherService(relayNamespace, connectionName, keyName, key, targetServiceAddress);
45 |
46 | await hybridProxy.OpenAsync(CancellationToken.None);
47 |
48 | Console.ReadLine();
49 |
50 | await hybridProxy.CloseAsync(CancellationToken.None);
51 | }
52 | }
53 | }
--------------------------------------------------------------------------------
/Src/ServiceBusRelayUtilNetCore/ServiceBusRelayUtilNetCore.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | netcoreapp2.1
6 | App.ico
7 | GaboG.ServiceBusRelayUtilNetCore
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | Always
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/Src/ServiceBusRelayUtilNetCore/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "RelayNamespace": "[Your Namespace].servicebus.windows.net",
3 | "RelayName": "[Your Relay Name]",
4 | "PolicyName": "[Your Shared Access Policy Name]",
5 | "PolicyKey": "[Your Policy's Key]",
6 | "TargetServiceAddress": "http://localhost:[PORT]"
7 | }
8 |
--------------------------------------------------------------------------------