@@ -47,7 +47,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Extensions.ManagedC | |||||
EndProject | EndProject | ||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.AspNetCore.Tests", "Tests\MQTTnet.AspNetCore.Tests\MQTTnet.AspNetCore.Tests.csproj", "{61B62223-F5D0-48E4-BBD6-2CBA9353CB5E}" | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.AspNetCore.Tests", "Tests\MQTTnet.AspNetCore.Tests\MQTTnet.AspNetCore.Tests.csproj", "{61B62223-F5D0-48E4-BBD6-2CBA9353CB5E}" | ||||
EndProject | EndProject | ||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnetServer", "Source\MQTTnetServer\MQTTnetServer.csproj", "{5699FB8C-838C-4AB0-80A5-9CA809F9B65B}" | |||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Server", "Source\MQTTnet.Server\MQTTnet.Server.csproj", "{5699FB8C-838C-4AB0-80A5-9CA809F9B65B}" | |||||
EndProject | EndProject | ||||
Global | Global | ||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution | GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||||
@@ -7,6 +7,7 @@ | |||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean> | <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean> | ||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean> | <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean> | ||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpRenamePlacementToArrangementMigration/@EntryIndexedValue">True</s:Boolean> | <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpRenamePlacementToArrangementMigration/@EntryIndexedValue">True</s:Boolean> | ||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpUseContinuousIndentInsideBracesMigration/@EntryIndexedValue">True</s:Boolean> | |||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAddAccessorOwnerDeclarationBracesMigration/@EntryIndexedValue">True</s:Boolean> | <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAddAccessorOwnerDeclarationBracesMigration/@EntryIndexedValue">True</s:Boolean> | ||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002ECSharpPlaceAttributeOnSameLineMigration/@EntryIndexedValue">True</s:Boolean> | <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002ECSharpPlaceAttributeOnSameLineMigration/@EntryIndexedValue">True</s:Boolean> | ||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean> | <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean> |
@@ -40,7 +40,6 @@ namespace MQTTnet.AspNetCore | |||||
public void Dispose() | public void Dispose() | ||||
{ | { | ||||
StopAsync().GetAwaiter().GetResult(); | |||||
} | } | ||||
} | } | ||||
} | } |
@@ -0,0 +1,9 @@ | |||||
namespace MQTTnet.Server.Configuration | |||||
{ | |||||
public class EndpointConfiguration | |||||
{ | |||||
public int Port { get; set; } = 1883; | |||||
public int BacklogSize { get; set; } = 10; | |||||
} | |||||
} |
@@ -1,4 +1,4 @@ | |||||
namespace MQTTnetServer.Settings | |||||
namespace MQTTnet.Server.Configuration | |||||
{ | { | ||||
/// <summary> | /// <summary> | ||||
/// Listen Entry Settings Model | /// Listen Entry Settings Model |
@@ -1,4 +1,4 @@ | |||||
namespace MQTTnetServer.Settings | |||||
namespace MQTTnet.Server.Configuration | |||||
{ | { | ||||
/// <summary> | /// <summary> | ||||
/// Listen Protocol Types | /// Listen Protocol Types |
@@ -0,0 +1,7 @@ | |||||
namespace MQTTnet.Server.Configuration | |||||
{ | |||||
public class MqttNetServerConfiguration | |||||
{ | |||||
} | |||||
} |
@@ -1,6 +1,6 @@ | |||||
using System.Collections.Generic; | using System.Collections.Generic; | ||||
namespace MQTTnetServer.Settings | |||||
namespace MQTTnet.Server.Configuration | |||||
{ | { | ||||
/// <summary> | /// <summary> | ||||
/// Main Settings Model | /// Main Settings Model |
@@ -1,10 +1,7 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Threading.Tasks; | |||||
using System.Collections.Generic; | |||||
using Microsoft.AspNetCore.Mvc; | using Microsoft.AspNetCore.Mvc; | ||||
namespace MQTTnetServer.Controllers | |||||
namespace MQTTnet.Server.Controllers | |||||
{ | { | ||||
[Route("api/[controller]")] | [Route("api/[controller]")] | ||||
[ApiController] | [ApiController] |
@@ -0,0 +1,21 @@ | |||||
MIT License | |||||
Copyright (c) 2017 Christian Kratky | |||||
Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
of this software and associated documentation files (the "Software"), to deal | |||||
in the Software without restriction, including without limitation the rights | |||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
copies of the Software, and to permit persons to whom the Software is | |||||
furnished to do so, subject to the following conditions: | |||||
The above copyright notice and this permission notice shall be included in all | |||||
copies or substantial portions of the Software. | |||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||||
SOFTWARE. |
@@ -0,0 +1,43 @@ | |||||
using System; | |||||
using MQTTnet.Diagnostics; | |||||
namespace MQTTnet.Server.Logging | |||||
{ | |||||
public class MqttNetChildLoggerWrapper : IMqttNetChildLogger | |||||
{ | |||||
private readonly MqttNetLoggerWrapper _logger; | |||||
private readonly string _source; | |||||
public MqttNetChildLoggerWrapper(string source, MqttNetLoggerWrapper logger) | |||||
{ | |||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); | |||||
_source = source; | |||||
} | |||||
public IMqttNetChildLogger CreateChildLogger(string source = null) | |||||
{ | |||||
return _logger.CreateChildLogger(source); | |||||
} | |||||
public void Verbose(string message, params object[] parameters) | |||||
{ | |||||
_logger.Publish(MqttNetLogLevel.Verbose, _source, message, parameters, null); | |||||
} | |||||
public void Info(string message, params object[] parameters) | |||||
{ | |||||
_logger.Publish(MqttNetLogLevel.Info, _source, message, parameters, null); | |||||
} | |||||
public void Warning(Exception exception, string message, params object[] parameters) | |||||
{ | |||||
_logger.Publish(MqttNetLogLevel.Warning, _source, message, parameters, exception); | |||||
} | |||||
public void Error(Exception exception, string message, params object[] parameters) | |||||
{ | |||||
_logger.Publish(MqttNetLogLevel.Error, _source, message, parameters, exception); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,48 @@ | |||||
using System; | |||||
using Microsoft.Extensions.Logging; | |||||
using MQTTnet.Diagnostics; | |||||
namespace MQTTnet.Server.Logging | |||||
{ | |||||
public class MqttNetLoggerWrapper : IMqttNetLogger | |||||
{ | |||||
private readonly ILogger<MqttServer> _logger; | |||||
public MqttNetLoggerWrapper(ILogger<MqttServer> logger) | |||||
{ | |||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); | |||||
} | |||||
public event EventHandler<MqttNetLogMessagePublishedEventArgs> LogMessagePublished; | |||||
public IMqttNetChildLogger CreateChildLogger(string source = null) | |||||
{ | |||||
return new MqttNetChildLoggerWrapper(source, this); | |||||
} | |||||
public void Publish(MqttNetLogLevel logLevel, string source, string message, object[] parameters, Exception exception) | |||||
{ | |||||
var convertedLogLevel = ConvertLogLevel(logLevel); | |||||
if (!_logger.IsEnabled(convertedLogLevel)) | |||||
{ | |||||
return; | |||||
} | |||||
_logger.Log(convertedLogLevel, exception, message, parameters); | |||||
} | |||||
private static LogLevel ConvertLogLevel(MqttNetLogLevel logLevel) | |||||
{ | |||||
switch (logLevel) | |||||
{ | |||||
case MqttNetLogLevel.Error: return LogLevel.Error; | |||||
case MqttNetLogLevel.Warning: return LogLevel.Warning; | |||||
case MqttNetLogLevel.Info: return LogLevel.Information; | |||||
case MqttNetLogLevel.Verbose: return LogLevel.Trace; | |||||
} | |||||
return LogLevel.Debug; | |||||
} | |||||
} | |||||
} |
@@ -3,11 +3,11 @@ | |||||
<PropertyGroup> | <PropertyGroup> | ||||
<TargetFramework>netcoreapp2.2</TargetFramework> | <TargetFramework>netcoreapp2.2</TargetFramework> | ||||
<AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel> | <AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel> | ||||
<AssemblyName>MQTTnetServer</AssemblyName> | |||||
<RootNamespace>MQTTnetServer</RootNamespace> | |||||
<AssemblyName>MQTTnet.Server</AssemblyName> | |||||
<RootNamespace>MQTTnet.Server</RootNamespace> | |||||
<GeneratePackageOnBuild>False</GeneratePackageOnBuild> | <GeneratePackageOnBuild>False</GeneratePackageOnBuild> | ||||
<Company /> | <Company /> | ||||
<Product /> | |||||
<Product>MQTTnet</Product> | |||||
<Description /> | <Description /> | ||||
<Authors /> | <Authors /> | ||||
<PackageId /> | <PackageId /> | ||||
@@ -16,8 +16,12 @@ | |||||
<LangVersion>latest</LangVersion> | <LangVersion>latest</LangVersion> | ||||
</PropertyGroup> | </PropertyGroup> | ||||
<PropertyGroup> | |||||
<StartupObject></StartupObject> | |||||
</PropertyGroup> | |||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> | <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> | ||||
<DocumentationFile>MQTTnetServer.xml</DocumentationFile> | |||||
<DocumentationFile></DocumentationFile> | |||||
<WarningsAsErrors>NU1605</WarningsAsErrors> | <WarningsAsErrors>NU1605</WarningsAsErrors> | ||||
<NoWarn>1701;1702</NoWarn> | <NoWarn>1701;1702</NoWarn> | ||||
</PropertyGroup> | </PropertyGroup> | ||||
@@ -29,8 +33,12 @@ | |||||
</ItemGroup> | </ItemGroup> | ||||
<ItemGroup> | <ItemGroup> | ||||
<PackageReference Include="IronPython" Version="2.7.9" /> | |||||
<PackageReference Include="IronPython.StdLib" Version="2.7.9" /> | |||||
<PackageReference Include="Microsoft.AspNetCore.App" /> | <PackageReference Include="Microsoft.AspNetCore.App" /> | ||||
<PackageReference Include="Microsoft.AspNetCore.Razor.Design" Version="2.2.0" PrivateAssets="All" /> | <PackageReference Include="Microsoft.AspNetCore.Razor.Design" Version="2.2.0" PrivateAssets="All" /> | ||||
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="4.0.1" /> | |||||
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="4.0.1" /> | |||||
</ItemGroup> | </ItemGroup> | ||||
<ItemGroup> | <ItemGroup> | ||||
@@ -38,4 +46,16 @@ | |||||
<ProjectReference Include="..\MQTTnet\MQTTnet.csproj" /> | <ProjectReference Include="..\MQTTnet\MQTTnet.csproj" /> | ||||
</ItemGroup> | </ItemGroup> | ||||
<ItemGroup> | |||||
<None Update="LICENSE"> | |||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory> | |||||
</None> | |||||
<None Update="Scripts\readme.md"> | |||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory> | |||||
</None> | |||||
<None Update="Scripts\00_sample.py"> | |||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory> | |||||
</None> | |||||
</ItemGroup> | |||||
</Project> | </Project> |
@@ -0,0 +1,50 @@ | |||||
using System; | |||||
using System.Threading.Tasks; | |||||
using IronPython.Runtime; | |||||
using Microsoft.Extensions.Logging; | |||||
using MQTTnet.Protocol; | |||||
using MQTTnet.Server.Scripting; | |||||
namespace MQTTnet.Server.Mqtt | |||||
{ | |||||
public class MqttApplicationMessageInterceptor : IMqttServerApplicationMessageInterceptor | |||||
{ | |||||
private readonly PythonScriptHostService _pythonScriptHostService; | |||||
private readonly ILogger<MqttApplicationMessageInterceptor> _logger; | |||||
public MqttApplicationMessageInterceptor(PythonScriptHostService pythonScriptHostService, ILogger<MqttApplicationMessageInterceptor> logger) | |||||
{ | |||||
_pythonScriptHostService = pythonScriptHostService ?? throw new ArgumentNullException(nameof(pythonScriptHostService)); | |||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); | |||||
} | |||||
public Task InterceptApplicationMessagePublishAsync(MqttApplicationMessageInterceptorContext context) | |||||
{ | |||||
try | |||||
{ | |||||
var pythonContext = new PythonDictionary | |||||
{ | |||||
{ "accept_publish", context.AcceptPublish }, | |||||
{ "close_connection", context.CloseConnection }, | |||||
{ "client_id", context.ClientId }, | |||||
{ "topic", context.ApplicationMessage.Topic }, | |||||
{ "qos", (int)context.ApplicationMessage.QualityOfServiceLevel }, | |||||
{ "retain", context.ApplicationMessage.Retain } | |||||
}; | |||||
_pythonScriptHostService.InvokeOptionalFunction("on_intercept_application_message", pythonContext); | |||||
context.AcceptPublish = (bool)pythonContext.get("accept_publish", context.AcceptPublish); | |||||
context.CloseConnection = (bool)pythonContext.get("close_connection", context.CloseConnection); | |||||
context.ApplicationMessage.Topic = (string)pythonContext.get("topic", context.ApplicationMessage.Topic); | |||||
context.ApplicationMessage.QualityOfServiceLevel = (MqttQualityOfServiceLevel)(int)pythonContext.get("qos", (int)context.ApplicationMessage.QualityOfServiceLevel); | |||||
} | |||||
catch (Exception exception) | |||||
{ | |||||
_logger.LogError(exception, "Error while intercepting application message."); | |||||
} | |||||
return Task.CompletedTask; | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,46 @@ | |||||
using System; | |||||
using System.Threading.Tasks; | |||||
using IronPython.Runtime; | |||||
using Microsoft.Extensions.Logging; | |||||
using MQTTnet.Protocol; | |||||
using MQTTnet.Server.Scripting; | |||||
namespace MQTTnet.Server.Mqtt | |||||
{ | |||||
public class MqttConnectionValidator : IMqttServerConnectionValidator | |||||
{ | |||||
private readonly PythonScriptHostService _pythonScriptHostService; | |||||
private readonly ILogger<MqttConnectionValidator> _logger; | |||||
public MqttConnectionValidator(PythonScriptHostService pythonScriptHostService, ILogger<MqttConnectionValidator> logger) | |||||
{ | |||||
_pythonScriptHostService = pythonScriptHostService ?? throw new ArgumentNullException(nameof(pythonScriptHostService)); | |||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); | |||||
} | |||||
public Task ValidateConnectionAsync(MqttConnectionValidatorContext context) | |||||
{ | |||||
try | |||||
{ | |||||
var pythonContext = new PythonDictionary | |||||
{ | |||||
{ "client_id", context.ClientId }, | |||||
{ "endpoint", context.Endpoint }, | |||||
{ "username", context.Username }, | |||||
{ "password", context.Password }, | |||||
{ "result", PythonConvert.Pythonfy(context.ReturnCode) } | |||||
}; | |||||
_pythonScriptHostService.InvokeOptionalFunction("on_validate_client_connection", pythonContext); | |||||
context.ReturnCode = PythonConvert.ParseEnum<MqttConnectReturnCode>((string)pythonContext["result"]); | |||||
} | |||||
catch (Exception exception) | |||||
{ | |||||
_logger.LogError(exception, "Error while validating client connection."); | |||||
} | |||||
return Task.CompletedTask; | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,101 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using IronPython.Runtime; | |||||
using Microsoft.Extensions.Logging; | |||||
using MQTTnet.Adapter; | |||||
using MQTTnet.AspNetCore; | |||||
using MQTTnet.Implementations; | |||||
using MQTTnet.Protocol; | |||||
using MQTTnet.Server.Logging; | |||||
using MQTTnet.Server.Scripting; | |||||
namespace MQTTnet.Server.Mqtt | |||||
{ | |||||
public class MqttServerService | |||||
{ | |||||
private readonly ILogger<MqttServerService> _logger; | |||||
private readonly MqttConnectionValidator _mqttConnectionValidator; | |||||
private readonly MqttSubscriptionInterceptor _mqttSubscriptionInterceptor; | |||||
private readonly MqttApplicationMessageInterceptor _mqttApplicationMessageInterceptor; | |||||
private readonly PythonScriptHostService _pythonScriptHostService; | |||||
private readonly IMqttServer _mqttServer; | |||||
public MqttServerService( | |||||
IMqttServerFactory mqttServerFactory, | |||||
MqttWebSocketServerAdapter webSocketServerAdapter, | |||||
MqttNetLoggerWrapper mqttNetLogger, | |||||
MqttConnectionValidator mqttConnectionValidator, | |||||
MqttSubscriptionInterceptor mqttSubscriptionInterceptor, | |||||
MqttApplicationMessageInterceptor mqttApplicationMessageInterceptor, | |||||
PythonScriptHostService pythonScriptHostService, | |||||
ILogger<MqttServerService> logger) | |||||
{ | |||||
_mqttConnectionValidator = mqttConnectionValidator ?? throw new ArgumentNullException(nameof(mqttConnectionValidator)); | |||||
_mqttSubscriptionInterceptor = mqttSubscriptionInterceptor ?? throw new ArgumentNullException(nameof(mqttSubscriptionInterceptor)); | |||||
_mqttApplicationMessageInterceptor = mqttApplicationMessageInterceptor ?? throw new ArgumentNullException(nameof(mqttApplicationMessageInterceptor)); | |||||
_pythonScriptHostService = pythonScriptHostService ?? throw new ArgumentNullException(nameof(pythonScriptHostService)); | |||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); | |||||
var adapters = new List<IMqttServerAdapter> | |||||
{ | |||||
new MqttTcpServerAdapter(new MqttNetChildLoggerWrapper(null, mqttNetLogger)), | |||||
webSocketServerAdapter | |||||
}; | |||||
_mqttServer = mqttServerFactory.CreateMqttServer(adapters); | |||||
} | |||||
public void Configure() | |||||
{ | |||||
_pythonScriptHostService.RegisterProxyObject("publish", new Action<PythonDictionary>(Publish)); | |||||
var options = new MqttServerOptionsBuilder() | |||||
.WithDefaultEndpoint() | |||||
.WithDefaultEndpointPort(1883) | |||||
.WithConnectionValidator(_mqttConnectionValidator) | |||||
.WithApplicationMessageInterceptor(_mqttApplicationMessageInterceptor) | |||||
.WithSubscriptionInterceptor(_mqttSubscriptionInterceptor) | |||||
.Build(); | |||||
_mqttServer.StartAsync(options).GetAwaiter().GetResult(); | |||||
_logger.LogInformation("MQTT server started."); | |||||
} | |||||
private void Publish(PythonDictionary parameters) | |||||
{ | |||||
var applicationMessageBuilder = new MqttApplicationMessageBuilder() | |||||
.WithTopic((string)parameters.get("topic", null)) | |||||
.WithRetainFlag((bool)parameters.get("retain", false)) | |||||
.WithQualityOfServiceLevel((MqttQualityOfServiceLevel)(int)parameters.get("qos", 0)); | |||||
var payload = parameters.get("payload", null); | |||||
var binaryPayload = new byte[0]; | |||||
if (payload is string stringPayload) | |||||
{ | |||||
binaryPayload = Encoding.UTF8.GetBytes(stringPayload); | |||||
} | |||||
else if (payload is ByteArray byteArray) | |||||
{ | |||||
binaryPayload = byteArray.ToArray(); | |||||
} | |||||
else if (payload is IEnumerable<int> intArray) | |||||
{ | |||||
binaryPayload = intArray.Select(Convert.ToByte).ToArray(); | |||||
} | |||||
applicationMessageBuilder = applicationMessageBuilder | |||||
.WithPayload(binaryPayload); | |||||
var applicationMessage = applicationMessageBuilder.Build(); | |||||
_mqttServer.PublishAsync(applicationMessage).GetAwaiter().GetResult(); | |||||
_logger.LogInformation($"Published topic '{applicationMessage.Topic}' from server."); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,47 @@ | |||||
using System; | |||||
using System.Threading.Tasks; | |||||
using IronPython.Runtime; | |||||
using Microsoft.Extensions.Logging; | |||||
using MQTTnet.Server.Scripting; | |||||
namespace MQTTnet.Server.Mqtt | |||||
{ | |||||
public class MqttSubscriptionInterceptor : IMqttServerSubscriptionInterceptor | |||||
{ | |||||
private readonly PythonScriptHostService _pythonScriptHostService; | |||||
private readonly ILogger<MqttSubscriptionInterceptor> _logger; | |||||
public MqttSubscriptionInterceptor(PythonScriptHostService pythonScriptHostService, ILogger<MqttSubscriptionInterceptor> logger) | |||||
{ | |||||
_pythonScriptHostService = pythonScriptHostService ?? throw new ArgumentNullException(nameof(pythonScriptHostService)); | |||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); | |||||
} | |||||
public Task InterceptSubscriptionAsync(MqttSubscriptionInterceptorContext context) | |||||
{ | |||||
try | |||||
{ | |||||
var pythonContext = new PythonDictionary | |||||
{ | |||||
{ "accept_subscription", context.AcceptSubscription }, | |||||
{ "close_connection", context.CloseConnection }, | |||||
{ "client_id", context.ClientId }, | |||||
{ "topic", context.TopicFilter.Topic }, | |||||
{ "qos", (int)context.TopicFilter.QualityOfServiceLevel } | |||||
}; | |||||
_pythonScriptHostService.InvokeOptionalFunction("on_intercept_subscription", pythonContext); | |||||
context.AcceptSubscription = (bool)pythonContext["accept_subscription"]; | |||||
context.CloseConnection = (bool)pythonContext["close_connection"]; | |||||
} | |||||
catch (Exception exception) | |||||
{ | |||||
_logger.LogError(exception, "Error while intercepting subscription."); | |||||
} | |||||
return Task.CompletedTask; | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,143 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using Microsoft.AspNetCore; | |||||
using Microsoft.AspNetCore.Hosting; | |||||
using Microsoft.Extensions.Configuration; | |||||
using MQTTnet.Server.Configuration; | |||||
namespace MQTTnet.Server | |||||
{ | |||||
public static class Program | |||||
{ | |||||
public static int Main(string[] args) | |||||
{ | |||||
try | |||||
{ | |||||
PrintLogo(); | |||||
CreateWebHostBuilder(args).Build().Run(); | |||||
return 0; | |||||
} | |||||
catch (Exception exception) | |||||
{ | |||||
Console.WriteLine(exception); | |||||
return -1; | |||||
} | |||||
} | |||||
private static void PrintLogo() | |||||
{ | |||||
Console.ForegroundColor = ConsoleColor.Red; | |||||
Console.WriteLine(@" | |||||
__ __ ____ _______ _______ _ _____ | |||||
| \/ |/ __ \__ __|__ __| | | / ____| | |||||
| \ / | | | | | | | |_ __ ___| |_ | (___ ___ _ ____ _____ _ __ | |||||
| |\/| | | | | | | | | '_ \ / _ \ __| \___ \ / _ \ '__\ \ / / _ \ '__| | |||||
| | | | |__| | | | | | | | | __/ |_ ____) | __/ | \ V / __/ | | |||||
|_| |_|\___\_\ |_| |_|_| |_|\___|\__| |_____/ \___|_| \_/ \___|_|"); | |||||
Console.ForegroundColor = ConsoleColor.White; | |||||
Console.WriteLine(@" | |||||
-- The official MQTT server implementation of MQTTnet -- | |||||
Copyright (c) 2017-2019 The MQTTnet Team"); | |||||
Console.ForegroundColor = ConsoleColor.Blue; | |||||
Console.WriteLine(@" | |||||
https://github.com/chkr1011/MQTTnet"); | |||||
Console.ForegroundColor = ConsoleColor.White; | |||||
Console.WriteLine(@" | |||||
Version: 1.0.0-alpha1 | |||||
License: MIT (read LICENSE file) | |||||
"); | |||||
Console.BackgroundColor = ConsoleColor.White; | |||||
Console.ForegroundColor = ConsoleColor.Red; | |||||
Console.WriteLine(" ! THIS IS AN ALPHA VERSION! IT IS NOT RECOMMENDED TO USE IT FOR ANY DIFFERENT PURPOSE THAN TESTING OR EVALUATING!"); | |||||
Console.WriteLine(); | |||||
} | |||||
private static IWebHostBuilder CreateWebHostBuilder(string[] args) | |||||
{ | |||||
var webHost = WebHost.CreateDefaultBuilder(args) | |||||
.UseKestrel(kestrelOptions => | |||||
{ | |||||
kestrelOptions.ListenAnyIP(80, listenOptions => | |||||
{ | |||||
listenOptions.NoDelay = true; | |||||
}); | |||||
}); | |||||
//var listen = ReadListenSettings(); | |||||
//webHost | |||||
// .UseKestrel(o => | |||||
// { | |||||
// if (listen?.Length > 0) | |||||
// { | |||||
// foreach (var item in listen) | |||||
// { | |||||
// if (item.Address?.Trim() == "*") | |||||
// { | |||||
// if (item.Protocol == ListenProtocolTypes.MQTT) | |||||
// { | |||||
// o.ListenAnyIP(item.Port, c => c.UseMqtt()); | |||||
// } | |||||
// else | |||||
// { | |||||
// o.ListenAnyIP(item.Port); | |||||
// } | |||||
// } | |||||
// else if (item.Address?.Trim() == "localhost") | |||||
// { | |||||
// if (item.Protocol == ListenProtocolTypes.MQTT) | |||||
// { | |||||
// o.ListenLocalhost(item.Port, c => c.UseMqtt()); | |||||
// } | |||||
// else | |||||
// { | |||||
// o.ListenLocalhost(item.Port); | |||||
// } | |||||
// } | |||||
// else | |||||
// { | |||||
// if (IPAddress.TryParse(item.Address, out var ip)) | |||||
// { | |||||
// if (item.Protocol == ListenProtocolTypes.MQTT) | |||||
// { | |||||
// o.Listen(ip, item.Port, c => c.UseMqtt()); | |||||
// } | |||||
// else | |||||
// { | |||||
// o.Listen(ip, item.Port); | |||||
// } | |||||
// } | |||||
// } | |||||
// } | |||||
// } | |||||
// else | |||||
// { | |||||
// o.ListenAnyIP(1883, l => l.UseMqtt()); | |||||
// o.ListenAnyIP(5000); | |||||
// } | |||||
// }); | |||||
webHost.UseStartup<Startup>(); | |||||
return webHost; | |||||
} | |||||
public static ListenModel[] ReadListenSettings() | |||||
{ | |||||
var builder = new ConfigurationBuilder() | |||||
.AddJsonFile("appsettings.json") | |||||
.AddEnvironmentVariables() | |||||
.Build(); | |||||
var listen = new List<ListenModel>(); | |||||
builder.Bind("MQTTnetServer:Listen", listen); | |||||
return listen.ToArray(); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,47 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using Microsoft.Extensions.Logging; | |||||
namespace MQTTnet.Server.Scripting.DataSharing | |||||
{ | |||||
public class DataSharingService | |||||
{ | |||||
private readonly Dictionary<string, object> _storage = new Dictionary<string, object>(); | |||||
private readonly PythonScriptHostService _pythonScriptHostService; | |||||
private readonly ILogger<DataSharingService> _logger; | |||||
public DataSharingService(PythonScriptHostService pythonScriptHostService, ILogger<DataSharingService> logger) | |||||
{ | |||||
_pythonScriptHostService = pythonScriptHostService ?? throw new ArgumentNullException(nameof(pythonScriptHostService)); | |||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); | |||||
} | |||||
public void Configure() | |||||
{ | |||||
_pythonScriptHostService.RegisterProxyObject("write_shared_data", new Action<string, object>(Write)); | |||||
_pythonScriptHostService.RegisterProxyObject("read_shared_data", new Func<string, object, object>(Read)); | |||||
} | |||||
public void Write(string key, object value) | |||||
{ | |||||
lock (_storage) | |||||
{ | |||||
_storage[key] = value; | |||||
_logger.LogInformation($"Shared data with key '{key}' updated."); | |||||
} | |||||
} | |||||
public object Read(string key, object defaultValue) | |||||
{ | |||||
lock (_storage) | |||||
{ | |||||
if (!_storage.TryGetValue(key, out var value)) | |||||
{ | |||||
return defaultValue; | |||||
} | |||||
return value; | |||||
} | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,88 @@ | |||||
using System; | |||||
using System.Collections; | |||||
using System.Text; | |||||
using IronPython.Runtime; | |||||
namespace MQTTnet.Server.Scripting | |||||
{ | |||||
public static class PythonConvert | |||||
{ | |||||
public static object ToPython(object value) | |||||
{ | |||||
if (value is PythonDictionary) | |||||
{ | |||||
return value; | |||||
} | |||||
if (value is string) | |||||
{ | |||||
return value; | |||||
} | |||||
if (value is int) | |||||
{ | |||||
return value; | |||||
} | |||||
if (value is float) | |||||
{ | |||||
return value; | |||||
} | |||||
if (value is bool) | |||||
{ | |||||
return value; | |||||
} | |||||
if (value is IDictionary dictionary) | |||||
{ | |||||
var pythonDictionary = new PythonDictionary(); | |||||
foreach (DictionaryEntry dictionaryEntry in dictionary) | |||||
{ | |||||
pythonDictionary.Add(dictionaryEntry.Key, ToPython(dictionaryEntry.Value)); | |||||
} | |||||
return pythonDictionary; | |||||
} | |||||
if (value is IEnumerable enumerable) | |||||
{ | |||||
var pythonList = new List(); | |||||
foreach (var item in enumerable) | |||||
{ | |||||
pythonList.Add(ToPython(item)); | |||||
} | |||||
return pythonList; | |||||
} | |||||
return value; | |||||
} | |||||
public static string Pythonfy(Enum value) | |||||
{ | |||||
return Pythonfy(value.ToString()); | |||||
} | |||||
public static string Pythonfy(string value) | |||||
{ | |||||
var result = new StringBuilder(); | |||||
foreach (var @char in value) | |||||
{ | |||||
if (char.IsUpper(@char) && result.Length > 0) | |||||
{ | |||||
result.Append('_'); | |||||
} | |||||
result.Append(char.ToLowerInvariant(@char)); | |||||
} | |||||
return result.ToString(); | |||||
} | |||||
public static TEnum ParseEnum<TEnum>(string value) where TEnum : Enum | |||||
{ | |||||
return (TEnum)Enum.Parse(typeof(TEnum), value.Replace("_", string.Empty), true); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,66 @@ | |||||
using System; | |||||
using System.IO; | |||||
using System.Text; | |||||
using Microsoft.Extensions.Logging; | |||||
namespace MQTTnet.Server.Scripting | |||||
{ | |||||
public class PythonIOStream : Stream | |||||
{ | |||||
private readonly ILogger _logger; | |||||
private readonly Encoding _encoder = Encoding.UTF8; | |||||
public PythonIOStream(ILogger<PythonIOStream> logger) | |||||
{ | |||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); | |||||
} | |||||
public override void Flush() | |||||
{ | |||||
} | |||||
public override int Read(byte[] buffer, int offset, int count) | |||||
{ | |||||
throw new NotSupportedException(); | |||||
} | |||||
public override long Seek(long offset, SeekOrigin origin) | |||||
{ | |||||
throw new NotSupportedException(); | |||||
} | |||||
public override void SetLength(long value) | |||||
{ | |||||
throw new NotSupportedException(); | |||||
} | |||||
public override void Write(byte[] buffer, int offset, int count) | |||||
{ | |||||
if (buffer == null) throw new ArgumentNullException(nameof(buffer)); | |||||
if (count == 0) | |||||
{ | |||||
return; | |||||
} | |||||
var text = _encoder.GetString(buffer, offset, count); | |||||
if (text.Equals(Environment.NewLine)) | |||||
{ | |||||
return; | |||||
} | |||||
_logger.LogDebug(text); | |||||
} | |||||
public override bool CanRead { get; } = false; | |||||
public override bool CanSeek { get; } = false; | |||||
public override bool CanWrite { get; } = true; | |||||
public override long Length { get; } = 0L; | |||||
public override long Position | |||||
{ | |||||
get => throw new NotSupportedException(); | |||||
set => throw new NotSupportedException(); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,130 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Dynamic; | |||||
using System.IO; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using Microsoft.Extensions.Logging; | |||||
using Microsoft.Scripting; | |||||
using Microsoft.Scripting.Hosting; | |||||
namespace MQTTnet.Server.Scripting | |||||
{ | |||||
public class PythonScriptHostService | |||||
{ | |||||
private readonly IDictionary<string, object> _proxyObjects = new ExpandoObject(); | |||||
private readonly List<PythonScriptInstance> _scriptInstances = new List<PythonScriptInstance>(); | |||||
private readonly ILogger<PythonScriptHostService> _logger; | |||||
private readonly ScriptEngine _scriptEngine; | |||||
public PythonScriptHostService(PythonIOStream pythonIOStream, ILogger<PythonScriptHostService> logger) | |||||
{ | |||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); | |||||
_scriptEngine = IronPython.Hosting.Python.CreateEngine(); | |||||
_scriptEngine.Runtime.IO.SetOutput(pythonIOStream, Encoding.UTF8); | |||||
} | |||||
public void Configure() | |||||
{ | |||||
AddSearchPaths(_scriptEngine); | |||||
var scriptsDirectory = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Scripts"); | |||||
foreach (var filename in Directory.GetFiles(scriptsDirectory, "*.py", SearchOption.AllDirectories).OrderBy(file => file)) | |||||
{ | |||||
TryInitializeScript(filename); | |||||
} | |||||
} | |||||
public void RegisterProxyObject(string name, object action) | |||||
{ | |||||
if (name == null) throw new ArgumentNullException(nameof(name)); | |||||
if (action == null) throw new ArgumentNullException(nameof(action)); | |||||
_proxyObjects.Add(name, action); | |||||
} | |||||
public void InvokeOptionalFunction(string name, object parameters) | |||||
{ | |||||
if (name == null) throw new ArgumentNullException(nameof(name)); | |||||
lock (_scriptInstances) | |||||
{ | |||||
foreach (var pythonScriptInstance in _scriptInstances) | |||||
{ | |||||
try | |||||
{ | |||||
pythonScriptInstance.InvokeOptionalFunction(name, parameters); | |||||
} | |||||
catch (Exception exception) | |||||
{ | |||||
_logger.LogError(exception, $"Error while invoking function '{name}' at script '{pythonScriptInstance.Name}'."); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
private void TryInitializeScript(string filename) | |||||
{ | |||||
try | |||||
{ | |||||
var scriptName = new FileInfo(filename).Name; | |||||
_logger.LogTrace($"Initializing Python script '{scriptName}'..."); | |||||
var code = File.ReadAllText(filename); | |||||
var scriptInstance = CreateScriptInstance(scriptName, code); | |||||
scriptInstance.InvokeOptionalFunction("initialize"); | |||||
_scriptInstances.Add(scriptInstance); | |||||
_logger.LogInformation($"Initialized script '{scriptName}'."); | |||||
} | |||||
catch (Exception exception) | |||||
{ | |||||
_logger.LogError(exception, $"Error while initializing script '{new FileInfo(filename).Name}'."); | |||||
} | |||||
} | |||||
private PythonScriptInstance CreateScriptInstance(string name, string code) | |||||
{ | |||||
var scriptScope = _scriptEngine.CreateScope(); | |||||
var source = scriptScope.Engine.CreateScriptSourceFromString(code, SourceCodeKind.File); | |||||
var compiledCode = source.Compile(); | |||||
scriptScope.SetVariable("mqtt_net_server", _proxyObjects); | |||||
compiledCode.Execute(scriptScope); | |||||
return new PythonScriptInstance(name, scriptScope); | |||||
} | |||||
private void AddSearchPaths(ScriptEngine scriptEngine) | |||||
{ | |||||
var paths = new List<string> | |||||
{ | |||||
Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Lib"), | |||||
"/usr/lib/python2.7", | |||||
@"C:\Python27\Lib" | |||||
}; | |||||
AddSearchPaths(scriptEngine, paths); | |||||
} | |||||
private void AddSearchPaths(ScriptEngine scriptEngine, IEnumerable<string> paths) | |||||
{ | |||||
var searchPaths = scriptEngine.GetSearchPaths(); | |||||
foreach (var path in paths) | |||||
{ | |||||
if (Directory.Exists(path)) | |||||
{ | |||||
searchPaths.Add(path); | |||||
_logger.LogInformation($"Added Python lib path: {path}"); | |||||
} | |||||
} | |||||
scriptEngine.SetSearchPaths(searchPaths); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,57 @@ | |||||
using System; | |||||
using IronPython.Runtime; | |||||
using Microsoft.Scripting.Hosting; | |||||
namespace MQTTnet.Server.Scripting | |||||
{ | |||||
public class PythonScriptInstance | |||||
{ | |||||
private readonly ScriptScope _scriptScope; | |||||
public PythonScriptInstance(string name, ScriptScope scriptScope) | |||||
{ | |||||
_scriptScope = scriptScope; | |||||
Name = name; | |||||
} | |||||
public string Name { get; } | |||||
public bool InvokeOptionalFunction(string name, params object[] parameters) | |||||
{ | |||||
return InvokeOptionalFunction(name, parameters, out _); | |||||
} | |||||
public bool InvokeOptionalFunction(string name, object[] parameters, out object result) | |||||
{ | |||||
if (name == null) throw new ArgumentNullException(nameof(name)); | |||||
lock (_scriptScope) | |||||
{ | |||||
if (!_scriptScope.Engine.Operations.TryGetMember(_scriptScope, name, out var member)) | |||||
{ | |||||
result = null; | |||||
return false; | |||||
} | |||||
if (!(member is PythonFunction function)) | |||||
{ | |||||
throw new Exception($"Member '{name}' is no Python function."); | |||||
} | |||||
try | |||||
{ | |||||
result = _scriptScope.Engine.Operations.Invoke(function, parameters); | |||||
return true; | |||||
} | |||||
catch (Exception exception) | |||||
{ | |||||
var details = _scriptScope.Engine.GetService<ExceptionOperations>().FormatException(exception); | |||||
var message = $"Error while invoking function '{name}'. " + Environment.NewLine + details; | |||||
throw new Exception(message, exception); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,74 @@ | |||||
import json | |||||
def initialize(): | |||||
""" | |||||
This function is invoked after the script file has been loaded. | |||||
It will be executed only one time. | |||||
""" | |||||
print("Hello World from Sample script.") | |||||
def on_validate_client_connection(context): | |||||
""" | |||||
This function is invoked whenever a client wants to connect. It can be used to validate the connection. | |||||
""" | |||||
print(context) | |||||
mqtt_net_server.write_shared_data(context["client_id"], {"custom_value_1": 1, "custom_value_2": True}) | |||||
return | |||||
if context["client_id"] != "test_client": | |||||
context["result"] = "connection_refused_not_authorized" | |||||
return | |||||
if context["username"] != "bud spencer": | |||||
context["result"] = "connection_refused_not_authorized" | |||||
return | |||||
if context["password"] != "secret": | |||||
context["result"] = "connection_refused_not_authorized" | |||||
print(context) | |||||
def on_intercept_subscription(context): | |||||
""" | |||||
This function is invoked whenever a client wants to subscribe to a topic. | |||||
""" | |||||
print("Client '{client_id}' want's to subscribe to topic '{topic}'.".format(client_id=context["client_id"], topic=context["topic"])) | |||||
def on_intercept_application_message(context): | |||||
""" | |||||
This function is invoked for every processed application message. It also allows modifying | |||||
the message or cancel processing at all. | |||||
""" | |||||
client_id = context["client_id"] | |||||
if client_id != None: | |||||
shared_data = mqtt_net_server.read_shared_data(context["client_id"], {}) | |||||
print(shared_data) | |||||
if context["topic"] == "topic_with_response": | |||||
json_payload = { | |||||
"hello": "world", | |||||
"x": 1, | |||||
"y": True, | |||||
"z": None | |||||
} | |||||
application_message = { | |||||
"retain": False, | |||||
"topic": "reply", | |||||
"payload": json.dumps(json_payload) | |||||
} | |||||
mqtt_net_server.publish(application_message) | |||||
print("Client '{client_id}' published topic '{topic}'.".format(client_id=context["client_id"], topic=context["topic"])) |
@@ -0,0 +1,19 @@ | |||||
This directory contains scripts which are loaded by the server and can be used to perform the following tasks. | |||||
1. Validation of client connections via credentials, client IDs etc. | |||||
2. Manipulation of every processed message. | |||||
3. Validation of subscription attempts. | |||||
4. Publishing of custom application messages. | |||||
All scripts are loaded and _MQTTnet Server_ will invoke functions according to predefined naming conventions. | |||||
If a function is implemented in multiple scripts the context will be moved throug all instances. This allows overriding of results or passing data to other (following) scripts. | |||||
The Python starndard library ships with _MQTTnet Server_. But it is possible to add custom paths with Python libraries. | |||||
``` | |||||
import sys | |||||
sys.path.append(PATH_TO_LIBRARY) | |||||
``` | |||||
* All scripts must have the file extension _.py_. | |||||
* All scripts are sorted alphabetically (A to Z) before being loaded and parsed. |
@@ -0,0 +1,69 @@ | |||||
using Microsoft.AspNetCore.Builder; | |||||
using Microsoft.AspNetCore.Hosting; | |||||
using Microsoft.AspNetCore.Mvc; | |||||
using Microsoft.Extensions.Configuration; | |||||
using Microsoft.Extensions.DependencyInjection; | |||||
using MQTTnet.AspNetCore; | |||||
using MQTTnet.Server.Logging; | |||||
using MQTTnet.Server.Mqtt; | |||||
using MQTTnet.Server.Scripting; | |||||
using MQTTnet.Server.Scripting.DataSharing; | |||||
namespace MQTTnet.Server | |||||
{ | |||||
public class Startup | |||||
{ | |||||
public Startup(IConfiguration configuration) | |||||
{ | |||||
//var builder = new ConfigurationBuilder() | |||||
// .AddJsonFile("appsettings.json") | |||||
// .AddEnvironmentVariables(); | |||||
//Configuration = builder.Build(); | |||||
} | |||||
public IConfigurationRoot Configuration { get; } | |||||
public void Configure( | |||||
IApplicationBuilder application, | |||||
IHostingEnvironment environment, | |||||
MqttServerService mqttServerService, | |||||
PythonScriptHostService pythonScriptHostService, | |||||
DataSharingService dataSharingService) | |||||
{ | |||||
if (environment.IsDevelopment()) | |||||
{ | |||||
application.UseDeveloperExceptionPage(); | |||||
} | |||||
else | |||||
{ | |||||
application.UseHsts(); | |||||
} | |||||
application.UseHttpsRedirection(); | |||||
application.UseMvc(); | |||||
dataSharingService.Configure(); | |||||
pythonScriptHostService.Configure(); | |||||
mqttServerService.Configure(); | |||||
} | |||||
public void ConfigureServices(IServiceCollection services) | |||||
{ | |||||
services.AddSingleton<MqttNetLoggerWrapper>(); | |||||
services.AddSingleton<PythonIOStream>(); | |||||
services.AddSingleton<PythonScriptHostService>(); | |||||
services.AddSingleton<DataSharingService>(); | |||||
services.AddSingleton<MqttWebSocketServerAdapter>(); | |||||
services.AddSingleton<IMqttServerFactory, MqttFactory>(); | |||||
services.AddSingleton<MqttServerService>(); | |||||
services.AddSingleton<MqttConnectionValidator>(); | |||||
services.AddSingleton<MqttSubscriptionInterceptor>(); | |||||
services.AddSingleton<MqttApplicationMessageInterceptor>(); | |||||
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); | |||||
} | |||||
} | |||||
} |
@@ -10,6 +10,17 @@ namespace MQTTnet | |||||
{ | { | ||||
public class MqttFactory : IMqttClientFactory, IMqttServerFactory | public class MqttFactory : IMqttClientFactory, IMqttServerFactory | ||||
{ | { | ||||
private readonly IMqttNetLogger _logger; | |||||
public MqttFactory() : this(new MqttNetLogger()) | |||||
{ | |||||
} | |||||
public MqttFactory(IMqttNetLogger logger) | |||||
{ | |||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); | |||||
} | |||||
public IMqttClient CreateMqttClient() | public IMqttClient CreateMqttClient() | ||||
{ | { | ||||
return CreateMqttClient(new MqttNetLogger()); | return CreateMqttClient(new MqttNetLogger()); | ||||
@@ -57,5 +68,12 @@ namespace MQTTnet | |||||
return new MqttServer(adapters, logger.CreateChildLogger()); | return new MqttServer(adapters, logger.CreateChildLogger()); | ||||
} | } | ||||
public IMqttServer CreateMqttServer(IEnumerable<IMqttServerAdapter> adapters) | |||||
{ | |||||
if (adapters == null) throw new ArgumentNullException(nameof(adapters)); | |||||
return new MqttServer(adapters, _logger.CreateChildLogger()); | |||||
} | |||||
} | } | ||||
} | } |
@@ -1,116 +0,0 @@ | |||||
<?xml version="1.0"?> | |||||
<doc> | |||||
<assembly> | |||||
<name>MQTTnetServer</name> | |||||
</assembly> | |||||
<members> | |||||
<member name="T:MQTTnetServer.Program"> | |||||
<summary> | |||||
Main Entry point | |||||
</summary> | |||||
</member> | |||||
<member name="M:MQTTnetServer.Program.Main(System.String[])"> | |||||
<summary> | |||||
Main | |||||
</summary> | |||||
<param name="args"></param> | |||||
</member> | |||||
<member name="M:MQTTnetServer.Program.CreateWebHostBuilder(System.String[])"> | |||||
<summary> | |||||
Configure and Start Kestrel | |||||
</summary> | |||||
<param name="args"></param> | |||||
<returns></returns> | |||||
</member> | |||||
<member name="M:MQTTnetServer.Program.ReadListenSettings"> | |||||
<summary> | |||||
Read Application Settings | |||||
</summary> | |||||
<returns></returns> | |||||
</member> | |||||
<member name="T:MQTTnetServer.Settings.ListenModel"> | |||||
<summary> | |||||
Listen Entry Settings Model | |||||
</summary> | |||||
</member> | |||||
<member name="M:MQTTnetServer.Settings.ListenModel.#ctor"> | |||||
<summary> | |||||
Constructor | |||||
</summary> | |||||
</member> | |||||
<member name="P:MQTTnetServer.Settings.ListenModel.Address"> | |||||
<summary> | |||||
Listen Address | |||||
</summary> | |||||
</member> | |||||
<member name="P:MQTTnetServer.Settings.ListenModel.Port"> | |||||
<summary> | |||||
Listen Port | |||||
</summary> | |||||
</member> | |||||
<member name="P:MQTTnetServer.Settings.ListenModel.Protocol"> | |||||
<summary> | |||||
Protocol Type | |||||
</summary> | |||||
</member> | |||||
<member name="T:MQTTnetServer.Settings.ListenProtocolTypes"> | |||||
<summary> | |||||
Listen Protocol Types | |||||
</summary> | |||||
</member> | |||||
<member name="F:MQTTnetServer.Settings.ListenProtocolTypes.HTTP"> | |||||
<summary> | |||||
HTTP | |||||
</summary> | |||||
</member> | |||||
<member name="F:MQTTnetServer.Settings.ListenProtocolTypes.HTTPS"> | |||||
<summary> | |||||
HTTPS | |||||
</summary> | |||||
</member> | |||||
<member name="F:MQTTnetServer.Settings.ListenProtocolTypes.MQTT"> | |||||
<summary> | |||||
MQTT | |||||
</summary> | |||||
</member> | |||||
<member name="T:MQTTnetServer.Settings.SettingsModel"> | |||||
<summary> | |||||
Main Settings Model | |||||
</summary> | |||||
</member> | |||||
<member name="P:MQTTnetServer.Settings.SettingsModel.Listen"> | |||||
<summary> | |||||
Listen Settings | |||||
</summary> | |||||
</member> | |||||
<member name="T:MQTTnetServer.Startup"> | |||||
<summary> | |||||
Web App Startup | |||||
</summary> | |||||
</member> | |||||
<member name="M:MQTTnetServer.Startup.#ctor(Microsoft.Extensions.Configuration.IConfiguration)"> | |||||
<summary> | |||||
Constructor | |||||
</summary> | |||||
<param name="configuration"></param> | |||||
</member> | |||||
<member name="P:MQTTnetServer.Startup.Configuration"> | |||||
<summary> | |||||
Application Settings | |||||
</summary> | |||||
</member> | |||||
<member name="M:MQTTnetServer.Startup.Configure(Microsoft.AspNetCore.Builder.IApplicationBuilder,Microsoft.AspNetCore.Hosting.IHostingEnvironment)"> | |||||
<summary> | |||||
This method gets called by the runtime. Use this method to configure the HTTP request pipeline. | |||||
</summary> | |||||
<param name="app"></param> | |||||
<param name="env"></param> | |||||
</member> | |||||
<member name="M:MQTTnetServer.Startup.ConfigureServices(Microsoft.Extensions.DependencyInjection.IServiceCollection)"> | |||||
<summary> | |||||
This method gets called by the runtime. Use this method to add services to the container. | |||||
</summary> | |||||
<param name="services"></param> | |||||
</member> | |||||
</members> | |||||
</doc> |
@@ -1,118 +0,0 @@ | |||||
using Microsoft.AspNetCore; | |||||
using Microsoft.AspNetCore.Hosting; | |||||
using Microsoft.Extensions.Configuration; | |||||
using MQTTnet.AspNetCore; | |||||
using MQTTnetServer.Settings; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.IO; | |||||
using System.Net; | |||||
namespace MQTTnetServer | |||||
{ | |||||
/// <summary> | |||||
/// Main Entry point | |||||
/// </summary> | |||||
public class Program | |||||
{ | |||||
/// <summary> | |||||
/// Main | |||||
/// </summary> | |||||
/// <param name="args"></param> | |||||
public static void Main(string[] args) | |||||
{ | |||||
try | |||||
{ | |||||
CreateWebHostBuilder(args).Build().Run(); | |||||
} | |||||
catch (FileNotFoundException e) | |||||
{ | |||||
Console.WriteLine("Could not find application settings file in: " + e.FileName); | |||||
return; | |||||
} | |||||
} | |||||
/// <summary> | |||||
/// Configure and Start Kestrel | |||||
/// </summary> | |||||
/// <param name="args"></param> | |||||
/// <returns></returns> | |||||
public static IWebHostBuilder CreateWebHostBuilder(string[] args) | |||||
{ | |||||
var webHost = WebHost.CreateDefaultBuilder(args); | |||||
var listen = ReadListenSettings(); | |||||
webHost | |||||
.UseKestrel(o => | |||||
{ | |||||
if (listen?.Length > 0) | |||||
{ | |||||
foreach (var item in listen) | |||||
{ | |||||
if (item.Address?.Trim() == "*") | |||||
{ | |||||
if (item.Protocol == ListenProtocolTypes.MQTT) | |||||
{ | |||||
o.ListenAnyIP(item.Port, c => c.UseMqtt()); | |||||
} | |||||
else | |||||
{ | |||||
o.ListenAnyIP(item.Port); | |||||
} | |||||
} | |||||
else if (item.Address?.Trim() == "localhost") | |||||
{ | |||||
if (item.Protocol == ListenProtocolTypes.MQTT) | |||||
{ | |||||
o.ListenLocalhost(item.Port, c => c.UseMqtt()); | |||||
} | |||||
else | |||||
{ | |||||
o.ListenLocalhost(item.Port); | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
if (IPAddress.TryParse(item.Address, out var ip)) | |||||
{ | |||||
if (item.Protocol == ListenProtocolTypes.MQTT) | |||||
{ | |||||
o.Listen(ip, item.Port, c => c.UseMqtt()); | |||||
} | |||||
else | |||||
{ | |||||
o.Listen(ip, item.Port); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
o.ListenAnyIP(1883, l => l.UseMqtt()); | |||||
o.ListenAnyIP(5000); | |||||
} | |||||
}); | |||||
webHost.UseStartup<Startup>(); | |||||
return webHost; | |||||
} | |||||
/// <summary> | |||||
/// Read Application Settings | |||||
/// </summary> | |||||
/// <returns></returns> | |||||
public static ListenModel[] ReadListenSettings() | |||||
{ | |||||
var builder = new ConfigurationBuilder() | |||||
.AddJsonFile("appsettings.json") | |||||
.AddEnvironmentVariables() | |||||
.Build(); | |||||
var listen = new List<ListenModel>(); | |||||
builder.Bind("MQTTnetServer:Listen", listen); | |||||
return listen.ToArray(); | |||||
} | |||||
} | |||||
} |
@@ -1,61 +0,0 @@ | |||||
using Microsoft.AspNetCore.Builder; | |||||
using Microsoft.AspNetCore.Hosting; | |||||
using Microsoft.AspNetCore.Mvc; | |||||
using Microsoft.Extensions.Configuration; | |||||
using Microsoft.Extensions.DependencyInjection; | |||||
namespace MQTTnetServer | |||||
{ | |||||
/// <summary> | |||||
/// Web App Startup | |||||
/// </summary> | |||||
public class Startup | |||||
{ | |||||
/// <summary> | |||||
/// Constructor | |||||
/// </summary> | |||||
/// <param name="configuration"></param> | |||||
public Startup(IConfiguration configuration) | |||||
{ | |||||
var builder = new ConfigurationBuilder() | |||||
.AddJsonFile("appsettings.json") | |||||
.AddEnvironmentVariables(); | |||||
Configuration = builder.Build(); | |||||
} | |||||
/// <summary> | |||||
/// Application Settings | |||||
/// </summary> | |||||
public IConfigurationRoot Configuration { get; } | |||||
/// <summary> | |||||
/// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. | |||||
/// </summary> | |||||
/// <param name="app"></param> | |||||
/// <param name="env"></param> | |||||
public void Configure(IApplicationBuilder app, IHostingEnvironment env) | |||||
{ | |||||
if (env.IsDevelopment()) | |||||
{ | |||||
app.UseDeveloperExceptionPage(); | |||||
} | |||||
else | |||||
{ | |||||
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. | |||||
app.UseHsts(); | |||||
} | |||||
app.UseHttpsRedirection(); | |||||
app.UseMvc(); | |||||
} | |||||
/// <summary> | |||||
/// This method gets called by the runtime. Use this method to add services to the container. | |||||
/// </summary> | |||||
/// <param name="services"></param> | |||||
public void ConfigureServices(IServiceCollection services) | |||||
{ | |||||
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); | |||||
} | |||||
} | |||||
} |