@@ -12,14 +12,7 @@ | |||
<requireLicenseAcceptance>true</requireLicenseAcceptance> | |||
<description>MQTTnet is a high performance .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker) and supports v3.1.0, v3.1.1 and v5.0.0 of the MQTT protocol.</description> | |||
<releaseNotes> | |||
* [Core] Fixed _DISCONNECT_ packet reading for protocol version 3.1.0.DisconnectPacket | |||
* [Client] Added support for deferred message approval (#1075, thanks to @tkurucsai). | |||
* [Client] Exposed missing APIs for .NET 5.0 build (thanks to @yyjdelete). | |||
* [Client] Improved disconnect handling (#1134, thanks to @yyjdelete). | |||
* [Client] Fix TLS parameter options builder (#1104). | |||
* [Server] Add new overload for options builder. | |||
* [Server] Exposed endpoint in _MqttServerClientDisconnectedEventArgs_. | |||
* [Server] Remove client disconnected handler from wrong implementation location (BREAKING CHANGE). | |||
* [MQTTnet.Server] Moved server project to a dedicated GitHub repository. | |||
Git commit: $gitCommit | |||
</releaseNotes> | |||
@@ -95,50 +95,4 @@ Move-Item MQTTnet.Extensions.Rpc.nuspec.old -Destination MQTTnet.Extensions.Rpc. | |||
Move-Item MQTTnet.Extensions.ManagedClient.nuspec.old -Destination MQTTnet.Extensions.ManagedClient.nuspec -Force | |||
Move-Item MQTTnet.Extensions.WebSocket4Net.nuspec.old -Destination MQTTnet.Extensions.WebSocket4Net.nuspec -Force | |||
Remove-Item "nuget.exe" -Force -Recurse -ErrorAction SilentlyContinue | |||
#################################################################### | |||
# Build MQTTnet.Server Portable | |||
&dotnet publish ..\Source\MQTTnet.Server\MQTTnet.Server.csproj --configuration Release /p:FileVersion=$assemblyVersion /p:Version=$nugetVersion --framework net5.0 | |||
$source = (Convert-Path .) + "\..\Source\MQTTnet.Server\bin\Release\net5.0\publish" | |||
$destination = (Convert-Path .) + "\..\Source\MQTTnet.Server\bin\MQTTnet.Server-Portable-v$nugetVersion.zip" | |||
If(Test-path $destination) {Remove-item $destination} | |||
Add-Type -assembly "system.io.compression.filesystem" | |||
[io.compression.zipfile]::CreateFromDirectory($source, $destination) | |||
#################################################################### | |||
# Build MQTTnet.Server Linux-x64 | |||
&dotnet publish ..\Source\MQTTnet.Server\MQTTnet.Server.csproj --configuration Release /p:FileVersion=$assemblyVersion /p:Version=$nugetVersion --self-contained --runtime linux-x64 --framework net5.0 | |||
$source = (Convert-Path .) + "\..\Source\MQTTnet.Server\bin\Release\net5.0\linux-x64\publish" | |||
$destination = (Convert-Path .) + "\..\Source\MQTTnet.Server\bin\MQTTnet.Server-Linux-x64-v$nugetVersion.zip" | |||
If(Test-path $destination) {Remove-item $destination} | |||
Add-Type -assembly "system.io.compression.filesystem" | |||
[io.compression.zipfile]::CreateFromDirectory($source, $destination) | |||
#################################################################### | |||
# Build MQTTnet.Server Linux-ARM | |||
&dotnet publish ..\Source\MQTTnet.Server\MQTTnet.Server.csproj --configuration Release /p:FileVersion=$assemblyVersion /p:Version=$nugetVersion --self-contained --runtime linux-arm --framework net5.0 | |||
$source = (Convert-Path .) + "\..\Source\MQTTnet.Server\bin\Release\net5.0\linux-ARM\publish" | |||
$destination = (Convert-Path .) + "\..\Source\MQTTnet.Server\bin\MQTTnet.Server-Linux-ARM-v$nugetVersion.zip" | |||
If(Test-path $destination) {Remove-item $destination} | |||
Add-Type -assembly "system.io.compression.filesystem" | |||
[io.compression.zipfile]::CreateFromDirectory($source, $destination) | |||
#################################################################### | |||
# Build MQTTnet.Server Windows-x64 | |||
&dotnet publish ..\Source\MQTTnet.Server\MQTTnet.Server.csproj --configuration Release /p:FileVersion=$assemblyVersion /p:Version=$nugetVersion --self-contained --runtime win-x64 --framework net5.0 | |||
$source = (Convert-Path .) + "\..\Source\MQTTnet.Server\bin\Release\net5.0\win-x64\publish" | |||
$destination = (Convert-Path .) + "\..\Source\MQTTnet.Server\bin\MQTTnet.Server-Windows-x64-v$nugetVersion.zip" | |||
If(Test-path $destination) {Remove-item $destination} | |||
Add-Type -assembly "system.io.compression.filesystem" | |||
[io.compression.zipfile]::CreateFromDirectory($source, $destination) | |||
#################################################################### | |||
Remove-Item "nuget.exe" -Force -Recurse -ErrorAction SilentlyContinue |
@@ -47,12 +47,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Extensions.ManagedC | |||
EndProject | |||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.AspNetCore.Tests", "Tests\MQTTnet.AspNetCore.Tests\MQTTnet.AspNetCore.Tests.csproj", "{61B62223-F5D0-48E4-BBD6-2CBA9353CB5E}" | |||
EndProject | |||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Server", "Source\MQTTnet.Server\MQTTnet.Server.csproj", "{5699FB8C-838C-4AB0-80A5-9CA809F9B65B}" | |||
EndProject | |||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Extensions.WebSocket4Net", "Source\MQTTnet.Extensions.WebSocket4Net\MQTTnet.Extensions.WebSocket4Net.csproj", "{2BD01D53-4CA5-4142-BE8D-313876395E3E}" | |||
EndProject | |||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Apps", "Apps", "{A56E3128-1639-4F31-873A-325E14BB6295}" | |||
EndProject | |||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Blazor", "Blazor", "{61B165A0-5AA8-4E04-A53D-A22A84AA6EB7}" | |||
EndProject | |||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Test.BlazorApp.Server", "Tests\MQTTnet.Test.BlazorApp\Server\MQTTnet.Test.BlazorApp.Server.csproj", "{A9662AF3-3520-4BF8-9DFF-C55C0C33D08F}" | |||
@@ -217,22 +213,6 @@ Global | |||
{61B62223-F5D0-48E4-BBD6-2CBA9353CB5E}.Release|x64.Build.0 = Release|Any CPU | |||
{61B62223-F5D0-48E4-BBD6-2CBA9353CB5E}.Release|x86.ActiveCfg = Release|Any CPU | |||
{61B62223-F5D0-48E4-BBD6-2CBA9353CB5E}.Release|x86.Build.0 = Release|Any CPU | |||
{5699FB8C-838C-4AB0-80A5-9CA809F9B65B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||
{5699FB8C-838C-4AB0-80A5-9CA809F9B65B}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||
{5699FB8C-838C-4AB0-80A5-9CA809F9B65B}.Debug|ARM.ActiveCfg = Debug|Any CPU | |||
{5699FB8C-838C-4AB0-80A5-9CA809F9B65B}.Debug|ARM.Build.0 = Debug|Any CPU | |||
{5699FB8C-838C-4AB0-80A5-9CA809F9B65B}.Debug|x64.ActiveCfg = Debug|Any CPU | |||
{5699FB8C-838C-4AB0-80A5-9CA809F9B65B}.Debug|x64.Build.0 = Debug|Any CPU | |||
{5699FB8C-838C-4AB0-80A5-9CA809F9B65B}.Debug|x86.ActiveCfg = Debug|Any CPU | |||
{5699FB8C-838C-4AB0-80A5-9CA809F9B65B}.Debug|x86.Build.0 = Debug|Any CPU | |||
{5699FB8C-838C-4AB0-80A5-9CA809F9B65B}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||
{5699FB8C-838C-4AB0-80A5-9CA809F9B65B}.Release|Any CPU.Build.0 = Release|Any CPU | |||
{5699FB8C-838C-4AB0-80A5-9CA809F9B65B}.Release|ARM.ActiveCfg = Release|Any CPU | |||
{5699FB8C-838C-4AB0-80A5-9CA809F9B65B}.Release|ARM.Build.0 = Release|Any CPU | |||
{5699FB8C-838C-4AB0-80A5-9CA809F9B65B}.Release|x64.ActiveCfg = Release|Any CPU | |||
{5699FB8C-838C-4AB0-80A5-9CA809F9B65B}.Release|x64.Build.0 = Release|Any CPU | |||
{5699FB8C-838C-4AB0-80A5-9CA809F9B65B}.Release|x86.ActiveCfg = Release|Any CPU | |||
{5699FB8C-838C-4AB0-80A5-9CA809F9B65B}.Release|x86.Build.0 = Release|Any CPU | |||
{2BD01D53-4CA5-4142-BE8D-313876395E3E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||
{2BD01D53-4CA5-4142-BE8D-313876395E3E}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||
{2BD01D53-4CA5-4142-BE8D-313876395E3E}.Debug|ARM.ActiveCfg = Debug|Any CPU | |||
@@ -311,7 +291,6 @@ Global | |||
{998D04DD-7CB0-45F5-A393-E2495C16399E} = {9248C2E1-B9D6-40BF-81EC-86004D7765B4} | |||
{C400533A-8EBA-4F0B-BF4D-295C3708604B} = {12816BCC-AF9E-44A9-9AE5-C246AF2A0587} | |||
{61B62223-F5D0-48E4-BBD6-2CBA9353CB5E} = {9248C2E1-B9D6-40BF-81EC-86004D7765B4} | |||
{5699FB8C-838C-4AB0-80A5-9CA809F9B65B} = {A56E3128-1639-4F31-873A-325E14BB6295} | |||
{2BD01D53-4CA5-4142-BE8D-313876395E3E} = {12816BCC-AF9E-44A9-9AE5-C246AF2A0587} | |||
{61B165A0-5AA8-4E04-A53D-A22A84AA6EB7} = {9248C2E1-B9D6-40BF-81EC-86004D7765B4} | |||
{A9662AF3-3520-4BF8-9DFF-C55C0C33D08F} = {61B165A0-5AA8-4E04-A53D-A22A84AA6EB7} | |||
@@ -1,12 +0,0 @@ | |||
{ | |||
"version": 1, | |||
"isRoot": true, | |||
"tools": { | |||
"dotnet-ef": { | |||
"version": "3.1.4", | |||
"commands": [ | |||
"dotnet-ef" | |||
] | |||
} | |||
} | |||
} |
@@ -1,35 +0,0 @@ | |||
using System.IO; | |||
namespace MQTTnet.Server.Configuration | |||
{ | |||
public class CertificateSettingsModel | |||
{ | |||
/// <summary> | |||
/// Path to certificate. | |||
/// </summary> | |||
public string Path { get; set; } | |||
/// <summary> | |||
/// Password of certificate. | |||
/// </summary> | |||
public string Password { get; set; } | |||
/// <summary> | |||
/// Read certificate file. | |||
/// </summary> | |||
public byte[] ReadCertificate() | |||
{ | |||
if (string.IsNullOrEmpty(Path) || string.IsNullOrWhiteSpace(Path)) | |||
{ | |||
throw new FileNotFoundException("No path set"); | |||
} | |||
if (!File.Exists(Path)) | |||
{ | |||
throw new FileNotFoundException($"Could not find Certificate in path: {Path}"); | |||
} | |||
return File.ReadAllBytes(Path); | |||
} | |||
} | |||
} |
@@ -1,53 +0,0 @@ | |||
namespace MQTTnet.Server.Configuration | |||
{ | |||
/// <summary> | |||
/// MQTT settings Model | |||
/// </summary> | |||
public class MqttSettingsModel | |||
{ | |||
/// <summary> | |||
/// Set default connection timeout in seconds | |||
/// </summary> | |||
public int CommunicationTimeout { get; set; } = 15; | |||
/// <summary> | |||
/// Set 0 to disable connection backlogging | |||
/// </summary> | |||
public int ConnectionBacklog { get; set; } | |||
/// <summary> | |||
/// Enable support for persistent sessions | |||
/// </summary> | |||
public bool EnablePersistentSessions { get; set; } = false; | |||
/// <summary> | |||
/// Listen Settings | |||
/// </summary> | |||
public TcpEndPointModel TcpEndPoint { get; set; } = new TcpEndPointModel(); | |||
/// <summary> | |||
/// Encryption Listen Settings | |||
/// </summary> | |||
public TcpEndPointModel EncryptedTcpEndPoint { get; set; } = new TcpEndPointModel(); | |||
/// <summary> | |||
/// Settings for the Web Socket endpoint. | |||
/// </summary> | |||
public WebSocketEndPointModel WebSocketEndPoint { get; set; } = new WebSocketEndPointModel(); | |||
/// <summary> | |||
/// Set limit for max pending messages per client | |||
/// </summary> | |||
public int MaxPendingMessagesPerClient { get; set; } = 250; | |||
/// <summary> | |||
/// The settings for retained messages. | |||
/// </summary> | |||
public RetainedApplicationMessagesModel RetainedApplicationMessages { get; set; } = new RetainedApplicationMessagesModel(); | |||
/// <summary> | |||
/// Enables or disables the MQTTnet internal logging. | |||
/// </summary> | |||
public bool EnableDebugLogging { get; set; } = false; | |||
} | |||
} |
@@ -1,24 +0,0 @@ | |||
using System; | |||
using System.IO; | |||
namespace MQTTnet.Server.Configuration | |||
{ | |||
public static class PathHelper | |||
{ | |||
public static string ExpandPath(string path) | |||
{ | |||
if (path == null) | |||
{ | |||
return null; | |||
} | |||
var uri = new Uri(path, UriKind.RelativeOrAbsolute); | |||
if (!uri.IsAbsoluteUri) | |||
{ | |||
return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, path); | |||
} | |||
return path; | |||
} | |||
} | |||
} |
@@ -1,11 +0,0 @@ | |||
namespace MQTTnet.Server.Configuration | |||
{ | |||
public class RetainedApplicationMessagesModel | |||
{ | |||
public bool Persist { get; set; } = false; | |||
public int WriteInterval { get; set; } = 10; | |||
public string Path { get; set; } | |||
} | |||
} |
@@ -1,11 +0,0 @@ | |||
using System.Collections.Generic; | |||
namespace MQTTnet.Server.Configuration | |||
{ | |||
public class ScriptingSettingsModel | |||
{ | |||
public string ScriptsPath { get; set; } | |||
public List<string> IncludePaths { get; set; } | |||
} | |||
} |
@@ -1,102 +0,0 @@ | |||
using System.IO; | |||
using System.Net; | |||
namespace MQTTnet.Server.Configuration | |||
{ | |||
/// <summary> | |||
/// Listen Entry Settings Model | |||
/// </summary> | |||
public class TcpEndPointModel | |||
{ | |||
/// <summary> | |||
/// Certificate settings. | |||
/// </summary> | |||
public CertificateSettingsModel Certificate { get; set; } | |||
/// <summary> | |||
/// Enabled / Disable | |||
/// </summary> | |||
public bool Enabled { get; set; } = true; | |||
/// <summary> | |||
/// Listen Address | |||
/// </summary> | |||
public string IPv4 { get; set; } | |||
/// <summary> | |||
/// Listen Address | |||
/// </summary> | |||
public string IPv6 { get; set; } | |||
/// <summary> | |||
/// Listen Port | |||
/// </summary> | |||
public int Port { get; set; } = 1883; | |||
/// <summary> | |||
/// Read IPv4 | |||
/// </summary> | |||
/// <returns></returns> | |||
public bool TryReadIPv4(out IPAddress address) | |||
{ | |||
if (IPv4 == "*") | |||
{ | |||
address = IPAddress.Any; | |||
return true; | |||
} | |||
if (IPv4 == "localhost") | |||
{ | |||
address = IPAddress.Loopback; | |||
return true; | |||
} | |||
if (IPv4 == "disable") | |||
{ | |||
address = IPAddress.None; | |||
return true; | |||
} | |||
if (IPAddress.TryParse(IPv4, out var ip)) | |||
{ | |||
address = ip; | |||
return true; | |||
} | |||
throw new System.Exception($"Could not parse IPv4 address: {IPv4}"); | |||
} | |||
/// <summary> | |||
/// Read IPv6 | |||
/// </summary> | |||
/// <returns></returns> | |||
public bool TryReadIPv6(out IPAddress address) | |||
{ | |||
if (IPv6 == "*") | |||
{ | |||
address = IPAddress.IPv6Any; | |||
return true; | |||
} | |||
if (IPv6 == "localhost") | |||
{ | |||
address = IPAddress.IPv6Loopback; | |||
return true; | |||
} | |||
if (IPv6 == "disable") | |||
{ | |||
address = IPAddress.None; | |||
return true; | |||
} | |||
if (IPAddress.TryParse(IPv6, out var ip)) | |||
{ | |||
address = ip; | |||
return true; | |||
} | |||
throw new System.Exception($"Could not parse IPv6 address: {IPv6}"); | |||
} | |||
} | |||
} |
@@ -1,18 +0,0 @@ | |||
| |||
using System.Collections.Generic; | |||
namespace MQTTnet.Server.Configuration | |||
{ | |||
public class WebSocketEndPointModel | |||
{ | |||
public bool Enabled { get; set; } = true; | |||
public string Path { get; set; } = "/mqtt"; | |||
public int ReceiveBufferSize { get; set; } = 4096; | |||
public int KeepAliveInterval { get; set; } = 120; | |||
public List<string> AllowedOrigins { get; set; } | |||
} | |||
} |
@@ -1,79 +0,0 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Net; | |||
using System.Threading.Tasks; | |||
using System.Web; | |||
using Microsoft.AspNetCore.Authorization; | |||
using Microsoft.AspNetCore.Mvc; | |||
using MQTTnet.Server.Mqtt; | |||
using MQTTnet.Server.Status; | |||
namespace MQTTnet.Server.Controllers | |||
{ | |||
[Authorize] | |||
[ApiController] | |||
public class ClientsController : Controller | |||
{ | |||
private readonly MqttServerService _mqttServerService; | |||
public ClientsController(MqttServerService mqttServerService) | |||
{ | |||
_mqttServerService = mqttServerService ?? throw new ArgumentNullException(nameof(mqttServerService)); | |||
} | |||
[Route("api/v1/clients")] | |||
[HttpGet] | |||
public async Task<ActionResult<IList<IMqttClientStatus>>> GetClients() | |||
{ | |||
return new ObjectResult(await _mqttServerService.GetClientStatusAsync()); | |||
} | |||
[Route("api/v1/clients/{clientId}")] | |||
[HttpGet] | |||
public async Task<ActionResult<IMqttClientStatus>> GetClient(string clientId) | |||
{ | |||
clientId = HttpUtility.UrlDecode(clientId); | |||
var client = (await _mqttServerService.GetClientStatusAsync()).FirstOrDefault(c => c.ClientId == clientId); | |||
if (client == null) | |||
{ | |||
return new StatusCodeResult((int)HttpStatusCode.NotFound); | |||
} | |||
return new ObjectResult(client); | |||
} | |||
[Route("api/v1/clients/{clientId}")] | |||
[HttpDelete] | |||
public async Task<ActionResult> DeleteClient(string clientId) | |||
{ | |||
clientId = HttpUtility.UrlDecode(clientId); | |||
var client = (await _mqttServerService.GetClientStatusAsync()).FirstOrDefault(c => c.ClientId == clientId); | |||
if (client == null) | |||
{ | |||
return new StatusCodeResult((int)HttpStatusCode.NotFound); | |||
} | |||
await client.DisconnectAsync(); | |||
return StatusCode((int)HttpStatusCode.NoContent); | |||
} | |||
[Route("api/v1/clients/{clientId}/statistics")] | |||
[HttpDelete] | |||
public async Task<ActionResult> DeleteClientStatistics(string clientId) | |||
{ | |||
clientId = HttpUtility.UrlDecode(clientId); | |||
var client = (await _mqttServerService.GetClientStatusAsync()).FirstOrDefault(c => c.ClientId == clientId); | |||
if (client == null) | |||
{ | |||
return new StatusCodeResult((int)HttpStatusCode.NotFound); | |||
} | |||
client.ResetStatistics(); | |||
return StatusCode((int)HttpStatusCode.NoContent); | |||
} | |||
} | |||
} |
@@ -1,51 +0,0 @@ | |||
using System; | |||
using System.IO; | |||
using System.Threading.Tasks; | |||
using Microsoft.AspNetCore.Authorization; | |||
using Microsoft.AspNetCore.Mvc; | |||
using MQTTnet.Protocol; | |||
using MQTTnet.Server.Mqtt; | |||
namespace MQTTnet.Server.Controllers | |||
{ | |||
[Authorize] | |||
[ApiController] | |||
public class MessagesController : Controller | |||
{ | |||
private readonly MqttServerService _mqttServerService; | |||
public MessagesController(MqttServerService mqttServerService) | |||
{ | |||
_mqttServerService = mqttServerService ?? throw new ArgumentNullException(nameof(mqttServerService)); | |||
} | |||
[Route("api/v1/messages")] | |||
[HttpPost] | |||
public async Task<ActionResult> PostMessage(MqttApplicationMessage message) | |||
{ | |||
await _mqttServerService.PublishAsync(message); | |||
return Ok(); | |||
} | |||
[Route("api/v1/messages/{*topic}")] | |||
[HttpPost] | |||
public async Task<ActionResult> PostMessage(string topic, int qosLevel = 0) | |||
{ | |||
byte[] payload; | |||
using (var memoryStream = new MemoryStream()) | |||
{ | |||
await HttpContext.Request.Body.CopyToAsync(memoryStream); | |||
payload = memoryStream.ToArray(); | |||
} | |||
var message = new MqttApplicationMessageBuilder() | |||
.WithTopic(topic) | |||
.WithPayload(payload) | |||
.WithQualityOfServiceLevel((MqttQualityOfServiceLevel)qosLevel) | |||
.Build(); | |||
return await PostMessage(message); | |||
} | |||
} | |||
} |
@@ -1,64 +0,0 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Net; | |||
using System.Threading.Tasks; | |||
using System.Web; | |||
using Microsoft.AspNetCore.Authorization; | |||
using Microsoft.AspNetCore.Mvc; | |||
using MQTTnet.Server.Mqtt; | |||
namespace MQTTnet.Server.Controllers | |||
{ | |||
[Authorize] | |||
[ApiController] | |||
public class RetainedApplicationMessagesController : Controller | |||
{ | |||
private readonly MqttServerService _mqttServerService; | |||
public RetainedApplicationMessagesController(MqttServerService mqttServerService) | |||
{ | |||
_mqttServerService = mqttServerService ?? throw new ArgumentNullException(nameof(mqttServerService)); | |||
} | |||
[Route("api/v1/retainedApplicationMessages")] | |||
[HttpGet] | |||
public async Task<ActionResult<IList<MqttApplicationMessage>>> GetRetainedApplicationMessages() | |||
{ | |||
return new ObjectResult(await _mqttServerService.GetRetainedApplicationMessagesAsync()); | |||
} | |||
[Route("api/v1/retainedApplicationMessages/{topic}")] | |||
[HttpGet] | |||
public async Task<ActionResult<MqttApplicationMessage>> GetRetainedApplicationMessage(string topic) | |||
{ | |||
topic = HttpUtility.UrlDecode(topic); | |||
var applicationMessage = (await _mqttServerService.GetRetainedApplicationMessagesAsync()).FirstOrDefault(c => c.Topic == topic); | |||
if (applicationMessage == null) | |||
{ | |||
return new StatusCodeResult((int)HttpStatusCode.NotFound); | |||
} | |||
return new ObjectResult(applicationMessage); | |||
} | |||
[Route("api/v1/retainedApplicationMessages")] | |||
[HttpDelete] | |||
public async Task<ActionResult> DeleteRetainedApplicationMessages() | |||
{ | |||
await _mqttServerService.ClearRetainedApplicationMessagesAsync(); | |||
return StatusCode((int)HttpStatusCode.NoContent); | |||
} | |||
[Route("api/v1/retainedApplicationMessages/{topic}")] | |||
[HttpDelete] | |||
public async Task<ActionResult> DeleteRetainedApplicationMessage(string topic) | |||
{ | |||
topic = HttpUtility.UrlDecode(topic); | |||
await _mqttServerService.PublishAsync(new MqttApplicationMessageBuilder().WithTopic(topic).WithRetainFlag().Build()); | |||
return StatusCode((int)HttpStatusCode.NoContent); | |||
} | |||
} | |||
} |
@@ -1,65 +0,0 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Threading; | |||
using System.Threading.Tasks; | |||
using Microsoft.AspNetCore.Authorization; | |||
using Microsoft.AspNetCore.Mvc; | |||
using MQTTnet.Server.Scripting; | |||
using MQTTnet.Server.Web; | |||
namespace MQTTnet.Server.Controllers | |||
{ | |||
[Authorize] | |||
[ApiController] | |||
public class ScriptsController : Controller | |||
{ | |||
private readonly PythonScriptHostService _pythonScriptHostService; | |||
public ScriptsController(PythonScriptHostService pythonScriptHostService) | |||
{ | |||
_pythonScriptHostService = pythonScriptHostService ?? throw new ArgumentNullException(nameof(pythonScriptHostService)); | |||
} | |||
[Route("api/v1/scripts")] | |||
[HttpGet] | |||
public ActionResult<List<string>> GetScriptUids() | |||
{ | |||
return _pythonScriptHostService.GetScriptUids(); | |||
} | |||
[Route("api/v1/scripts/uid")] | |||
[HttpGet] | |||
public async Task<ActionResult<string>> GetScript(string uid) | |||
{ | |||
var script = await _pythonScriptHostService.ReadScriptAsync(uid, HttpContext.RequestAborted); | |||
if (script == null) | |||
{ | |||
return NotFound(); | |||
} | |||
return Content(script); | |||
} | |||
[Route("api/v1/scripts/uid")] | |||
[HttpPost] | |||
public Task PostScript(string uid) | |||
{ | |||
var code = HttpContext.Request.ReadBodyAsString(); | |||
return _pythonScriptHostService.WriteScriptAsync(uid, code, CancellationToken.None); | |||
} | |||
[Route("api/v1/scripts/uid")] | |||
[HttpDelete] | |||
public Task DeleteScript(string uid) | |||
{ | |||
return _pythonScriptHostService.DeleteScriptAsync(uid); | |||
} | |||
[Route("api/v1/scripts/initialize")] | |||
[HttpPost] | |||
public Task PostInitializeScripts() | |||
{ | |||
return _pythonScriptHostService.TryInitializeScriptsAsync(); | |||
} | |||
} | |||
} |
@@ -1,20 +0,0 @@ | |||
using Microsoft.AspNetCore.Authorization; | |||
using Microsoft.AspNetCore.Mvc; | |||
using System.Diagnostics; | |||
using System.Reflection; | |||
namespace MQTTnet.Server.Controllers | |||
{ | |||
[Authorize] | |||
[ApiController] | |||
public class ServerController : Controller | |||
{ | |||
[Route("api/v1/server/version")] | |||
[HttpGet] | |||
public ActionResult<string> GetVersion() | |||
{ | |||
var fileVersion = FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location); | |||
return fileVersion.ProductVersion.ToString(); | |||
} | |||
} | |||
} |
@@ -1,79 +0,0 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Net; | |||
using System.Threading.Tasks; | |||
using System.Web; | |||
using Microsoft.AspNetCore.Authorization; | |||
using Microsoft.AspNetCore.Mvc; | |||
using MQTTnet.Server.Mqtt; | |||
using MQTTnet.Server.Status; | |||
namespace MQTTnet.Server.Controllers | |||
{ | |||
[Authorize] | |||
[ApiController] | |||
public class SessionsController : Controller | |||
{ | |||
private readonly MqttServerService _mqttServerService; | |||
public SessionsController(MqttServerService mqttServerService) | |||
{ | |||
_mqttServerService = mqttServerService ?? throw new ArgumentNullException(nameof(mqttServerService)); | |||
} | |||
[Route("api/v1/sessions")] | |||
[HttpGet] | |||
public async Task<ActionResult<IList<IMqttSessionStatus>>> GetSessions() | |||
{ | |||
return new ObjectResult(await _mqttServerService.GetSessionStatusAsync()); | |||
} | |||
[Route("api/v1/sessions/{clientId}")] | |||
[HttpGet] | |||
public async Task<ActionResult<IMqttClientStatus>> GetSession(string clientId) | |||
{ | |||
clientId = HttpUtility.UrlDecode(clientId); | |||
var session = (await _mqttServerService.GetSessionStatusAsync()).FirstOrDefault(c => c.ClientId == clientId); | |||
if (session == null) | |||
{ | |||
return new StatusCodeResult((int)HttpStatusCode.NotFound); | |||
} | |||
return new ObjectResult(session); | |||
} | |||
[Route("api/v1/sessions/{clientId}")] | |||
[HttpDelete] | |||
public async Task<ActionResult> DeleteSession(string clientId) | |||
{ | |||
clientId = HttpUtility.UrlDecode(clientId); | |||
var session = (await _mqttServerService.GetSessionStatusAsync()).FirstOrDefault(c => c.ClientId == clientId); | |||
if (session == null) | |||
{ | |||
return new StatusCodeResult((int)HttpStatusCode.NotFound); | |||
} | |||
await session.DeleteAsync(); | |||
return StatusCode((int)HttpStatusCode.NoContent); | |||
} | |||
[Route("api/v1/sessions/{clientId}/pendingApplicationMessages")] | |||
[HttpDelete] | |||
public async Task<ActionResult> DeletePendingApplicationMessages(string clientId) | |||
{ | |||
clientId = HttpUtility.UrlDecode(clientId); | |||
var session = (await _mqttServerService.GetSessionStatusAsync()).FirstOrDefault(c => c.ClientId == clientId); | |||
if (session == null) | |||
{ | |||
return new StatusCodeResult((int)HttpStatusCode.NotFound); | |||
} | |||
await session.ClearPendingApplicationMessagesAsync(); | |||
return StatusCode((int)HttpStatusCode.NoContent); | |||
} | |||
} | |||
} |
@@ -1,97 +0,0 @@ | |||
# MQTTnet | |||
MIT License | |||
Copyright (c) 2019 MQTTnet Team | |||
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. | |||
# Swashbuckle | |||
Copyright (c) 2013, Richard Morris | |||
All rights reserved. | |||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: | |||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. | |||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. | |||
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. | |||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE | |||
# IronPython | |||
Copyright (c) .NET Foundation and Contributors | |||
All Rights Reserved | |||
Apache License | |||
Version 2.0, January 2004 | |||
http://www.apache.org/licenses/ | |||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION | |||
1. Definitions. | |||
"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. | |||
"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. | |||
"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. | |||
"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. | |||
"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. | |||
"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. | |||
"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). | |||
"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. | |||
"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." | |||
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. | |||
2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. | |||
3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. | |||
4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: | |||
You must give any other recipients of the Work or Derivative Works a copy of this License; and | |||
You must cause any modified files to carry prominent notices stating that You changed the files; and | |||
You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and | |||
If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. | |||
You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. | |||
5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. | |||
6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. | |||
7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. | |||
8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. | |||
9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. | |||
@@ -1,43 +0,0 @@ | |||
//using MQTTnet.Diagnostics; | |||
//using System; | |||
//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 IMqttNetLogger 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); | |||
// } | |||
// } | |||
//} |
@@ -1,64 +0,0 @@ | |||
using Microsoft.Extensions.Logging; | |||
using MQTTnet.Diagnostics; | |||
using System; | |||
using System.Threading; | |||
namespace MQTTnet.Server.Logging | |||
{ | |||
public sealed class MqttNetLoggerWrapper : IMqttNetLogger | |||
{ | |||
readonly ILogger<MqttServer> _logger; | |||
public MqttNetLoggerWrapper(ILogger<MqttServer> logger) | |||
{ | |||
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); | |||
} | |||
public event EventHandler<MqttNetLogMessagePublishedEventArgs> LogMessagePublished; | |||
public IMqttNetScopedLogger CreateScopedLogger(string source) | |||
{ | |||
return new MqttNetScopedLogger(this, source); | |||
} | |||
public void Publish(MqttNetLogLevel level, string source, string message, object[] parameters, Exception exception) | |||
{ | |||
var convertedLogLevel = ConvertLogLevel(level); | |||
_logger.Log(convertedLogLevel, exception, message, parameters); | |||
var logMessagePublishedEvent = LogMessagePublished; | |||
if (logMessagePublishedEvent != null) | |||
{ | |||
var logMessage = new MqttNetLogMessage | |||
{ | |||
Timestamp = DateTime.UtcNow, | |||
ThreadId = Thread.CurrentThread.ManagedThreadId, | |||
Source = source, | |||
Level = level, | |||
Message = message, | |||
Exception = exception | |||
}; | |||
logMessagePublishedEvent.Invoke(this, new MqttNetLogMessagePublishedEventArgs(logMessage)); | |||
} | |||
} | |||
public void Publish(MqttNetLogLevel logLevel, string message, object[] parameters, Exception exception) | |||
{ | |||
Publish(logLevel, null, message, parameters, exception); | |||
} | |||
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; | |||
} | |||
} | |||
} |
@@ -1,93 +0,0 @@ | |||
<Project Sdk="Microsoft.NET.Sdk.Web"> | |||
<PropertyGroup> | |||
<TargetFrameworks>net5.0</TargetFrameworks> | |||
<AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel> | |||
<AssemblyName>MQTTnet.Server</AssemblyName> | |||
<RootNamespace>MQTTnet.Server</RootNamespace> | |||
<GeneratePackageOnBuild>False</GeneratePackageOnBuild> | |||
<Company /> | |||
<Product>MQTTnet</Product> | |||
<Description>Standalone MQTT server with Python scripting backend.</Description> | |||
<Authors>The authors of MQTTnet.Server.</Authors> | |||
<PackageId /> | |||
<SignAssembly>false</SignAssembly> | |||
<DelaySign>false</DelaySign> | |||
<LangVersion>latest</LangVersion> | |||
<Version>3.0.11</Version> | |||
<TypeScriptToolsVersion>3.3</TypeScriptToolsVersion> | |||
</PropertyGroup> | |||
<PropertyGroup> | |||
<StartupObject>MQTTnet.Server.Program</StartupObject> | |||
<UserSecretsId>c564f0de-28b4-45bf-b726-4d665d705653</UserSecretsId> | |||
<Copyright>(c) Christian Kratky 2016-2020</Copyright> | |||
<PackageProjectUrl>https://github.com/chkr1011/MQTTnet</PackageProjectUrl> | |||
<RepositoryUrl>https://github.com/chkr1011/MQTTnet</RepositoryUrl> | |||
</PropertyGroup> | |||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> | |||
<DocumentationFile></DocumentationFile> | |||
<WarningsAsErrors>NU1605</WarningsAsErrors> | |||
<NoWarn>1701;1702</NoWarn> | |||
</PropertyGroup> | |||
<ItemGroup> | |||
<Content Update="appsettings.json"> | |||
<CopyToOutputDirectory>Always</CopyToOutputDirectory> | |||
</Content> | |||
</ItemGroup> | |||
<ItemGroup> | |||
<PackageReference Include="IronPython" Version="2.7.11" /> | |||
<PackageReference Include="IronPython.StdLib" Version="2.7.11" /> | |||
<PackageReference Include="MSTest.TestAdapter" Version="2.1.2" /> | |||
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="5.6.3" /> | |||
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="5.6.3" /> | |||
</ItemGroup> | |||
<ItemGroup Condition="'$(TargetFramework)' != 'net5.0'"> | |||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.5" /> | |||
</ItemGroup> | |||
<ItemGroup Condition="'$(TargetFramework)' == 'net5.0'"> | |||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="5.0.0" /> | |||
</ItemGroup> | |||
<ItemGroup> | |||
<ProjectReference Include="..\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj" /> | |||
<ProjectReference Include="..\MQTTnet\MQTTnet.csproj" /> | |||
</ItemGroup> | |||
<ItemGroup> | |||
<None Update="LICENSE"> | |||
<CopyToOutputDirectory>Always</CopyToOutputDirectory> | |||
</None> | |||
<None Update="README.md"> | |||
<CopyToOutputDirectory>Always</CopyToOutputDirectory> | |||
</None> | |||
<None Update="Scripts\README.md"> | |||
<CopyToOutputDirectory>Always</CopyToOutputDirectory> | |||
</None> | |||
<None Update="Scripts\00_sample.py"> | |||
<CopyToOutputDirectory>Always</CopyToOutputDirectory> | |||
</None> | |||
<None Update="Web\authorization_handler.py"> | |||
<CopyToOutputDirectory>Always</CopyToOutputDirectory> | |||
</None> | |||
<None Update="Web\wwwroot\Hello.txt"> | |||
<CopyToOutputDirectory>Always</CopyToOutputDirectory> | |||
</None> | |||
<None Update="Web\wwwroot\mqtt_Test.html"> | |||
<CopyToOutputDirectory>Always</CopyToOutputDirectory> | |||
</None> | |||
<None Update="Web\wwwroot\pahoJS_Test.html"> | |||
<CopyToOutputDirectory>Always</CopyToOutputDirectory> | |||
</None> | |||
</ItemGroup> | |||
<ItemGroup> | |||
<Folder Include="Properties\PublishProfiles\" /> | |||
</ItemGroup> | |||
</Project> |
@@ -1,48 +0,0 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using Microsoft.Extensions.Logging; | |||
using MQTTnet.Adapter; | |||
using MQTTnet.Diagnostics; | |||
using MQTTnet.Server.Configuration; | |||
using MQTTnet.Server.Logging; | |||
namespace MQTTnet.Server.Mqtt | |||
{ | |||
public class CustomMqttFactory | |||
{ | |||
private readonly MqttFactory _mqttFactory; | |||
public CustomMqttFactory(MqttSettingsModel settings, ILogger<MqttServer> logger) | |||
{ | |||
if (settings == null) throw new ArgumentNullException(nameof(settings)); | |||
if (logger == null) throw new ArgumentNullException(nameof(logger)); | |||
// It is important to avoid injecting the logger wrapper to ensure that no | |||
// unused log messages are generated by the MQTTnet library. Debug logging | |||
// has a huge performance impact. | |||
if (settings.EnableDebugLogging) | |||
{ | |||
var mqttNetLogger = new MqttNetLoggerWrapper(logger); | |||
_mqttFactory = new MqttFactory(mqttNetLogger); | |||
logger.LogWarning("Debug logging is enabled. Performance of MQTTnet Server is decreased!"); | |||
} | |||
else | |||
{ | |||
_mqttFactory = new MqttFactory(); | |||
} | |||
Logger = _mqttFactory.DefaultLogger; | |||
} | |||
public IMqttNetLogger Logger { get; } | |||
public IMqttServer CreateMqttServer(List<IMqttServerAdapter> adapters) | |||
{ | |||
if (adapters == null) throw new ArgumentNullException(nameof(adapters)); | |||
return _mqttFactory.CreateMqttServer(adapters); | |||
} | |||
} | |||
} |
@@ -1,54 +0,0 @@ | |||
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 _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 | |||
{ | |||
// This might be not set when a message was published by the server instead of a client. | |||
context.SessionItems.TryGetValue(MqttServerConnectionValidator.WrappedSessionItemsKey, out var sessionItems); | |||
var pythonContext = new PythonDictionary | |||
{ | |||
{ "client_id", context.ClientId }, | |||
{ "session_items", sessionItems }, | |||
{ "retain", context.ApplicationMessage.Retain }, | |||
{ "accept_publish", context.AcceptPublish }, | |||
{ "close_connection", context.CloseConnection }, | |||
{ "topic", context.ApplicationMessage.Topic }, | |||
{ "qos", (int)context.ApplicationMessage.QualityOfServiceLevel } | |||
}; | |||
_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; | |||
} | |||
} | |||
} |
@@ -1,39 +0,0 @@ | |||
using System; | |||
using System.Threading.Tasks; | |||
using IronPython.Runtime; | |||
using Microsoft.Extensions.Logging; | |||
using MQTTnet.Server.Scripting; | |||
namespace MQTTnet.Server.Mqtt | |||
{ | |||
public class MqttClientConnectedHandler : IMqttServerClientConnectedHandler | |||
{ | |||
private readonly PythonScriptHostService _pythonScriptHostService; | |||
private readonly ILogger _logger; | |||
public MqttClientConnectedHandler(PythonScriptHostService pythonScriptHostService, ILogger<MqttClientConnectedHandler> logger) | |||
{ | |||
_pythonScriptHostService = pythonScriptHostService ?? throw new ArgumentNullException(nameof(pythonScriptHostService)); | |||
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); | |||
} | |||
public Task HandleClientConnectedAsync(MqttServerClientConnectedEventArgs eventArgs) | |||
{ | |||
try | |||
{ | |||
var pythonEventArgs = new PythonDictionary | |||
{ | |||
{ "client_id", eventArgs.ClientId } | |||
}; | |||
_pythonScriptHostService.InvokeOptionalFunction("on_client_connected", pythonEventArgs); | |||
} | |||
catch (Exception exception) | |||
{ | |||
_logger.LogError(exception, "Error while handling client connected event."); | |||
} | |||
return Task.CompletedTask; | |||
} | |||
} | |||
} |
@@ -1,40 +0,0 @@ | |||
using System; | |||
using System.Threading.Tasks; | |||
using IronPython.Runtime; | |||
using Microsoft.Extensions.Logging; | |||
using MQTTnet.Server.Scripting; | |||
namespace MQTTnet.Server.Mqtt | |||
{ | |||
public class MqttClientDisconnectedHandler : IMqttServerClientDisconnectedHandler | |||
{ | |||
private readonly PythonScriptHostService _pythonScriptHostService; | |||
private readonly ILogger _logger; | |||
public MqttClientDisconnectedHandler(PythonScriptHostService pythonScriptHostService, ILogger<MqttClientDisconnectedHandler> logger) | |||
{ | |||
_pythonScriptHostService = pythonScriptHostService ?? throw new ArgumentNullException(nameof(pythonScriptHostService)); | |||
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); | |||
} | |||
public Task HandleClientDisconnectedAsync(MqttServerClientDisconnectedEventArgs eventArgs) | |||
{ | |||
try | |||
{ | |||
var pythonEventArgs = new PythonDictionary | |||
{ | |||
{ "client_id", eventArgs.ClientId }, | |||
{ "type", PythonConvert.Pythonfy(eventArgs.DisconnectType) } | |||
}; | |||
_pythonScriptHostService.InvokeOptionalFunction("on_client_disconnected", pythonEventArgs); | |||
} | |||
catch (Exception exception) | |||
{ | |||
_logger.LogError(exception, "Error while handling client disconnected event."); | |||
} | |||
return Task.CompletedTask; | |||
} | |||
} | |||
} |
@@ -1,41 +0,0 @@ | |||
using System; | |||
using System.Threading.Tasks; | |||
using IronPython.Runtime; | |||
using Microsoft.Extensions.Logging; | |||
using MQTTnet.Server.Scripting; | |||
namespace MQTTnet.Server.Mqtt | |||
{ | |||
public class MqttClientSubscribedTopicHandler : IMqttServerClientSubscribedTopicHandler | |||
{ | |||
private readonly PythonScriptHostService _pythonScriptHostService; | |||
private readonly ILogger _logger; | |||
public MqttClientSubscribedTopicHandler(PythonScriptHostService pythonScriptHostService, ILogger<MqttClientSubscribedTopicHandler> logger) | |||
{ | |||
_pythonScriptHostService = pythonScriptHostService ?? throw new ArgumentNullException(nameof(pythonScriptHostService)); | |||
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); | |||
} | |||
public Task HandleClientSubscribedTopicAsync(MqttServerClientSubscribedTopicEventArgs eventArgs) | |||
{ | |||
try | |||
{ | |||
var pythonEventArgs = new PythonDictionary | |||
{ | |||
{ "client_id", eventArgs.ClientId }, | |||
{ "topic", eventArgs.TopicFilter.Topic }, | |||
{ "qos", (int)eventArgs.TopicFilter.QualityOfServiceLevel } | |||
}; | |||
_pythonScriptHostService.InvokeOptionalFunction("on_client_subscribed_topic", pythonEventArgs); | |||
} | |||
catch (Exception exception) | |||
{ | |||
_logger.LogError(exception, "Error while handling client subscribed topic event."); | |||
} | |||
return Task.CompletedTask; | |||
} | |||
} | |||
} |
@@ -1,40 +0,0 @@ | |||
using System; | |||
using System.Threading.Tasks; | |||
using IronPython.Runtime; | |||
using Microsoft.Extensions.Logging; | |||
using MQTTnet.Server.Scripting; | |||
namespace MQTTnet.Server.Mqtt | |||
{ | |||
public class MqttClientUnsubscribedTopicHandler : IMqttServerClientUnsubscribedTopicHandler | |||
{ | |||
private readonly PythonScriptHostService _pythonScriptHostService; | |||
private readonly ILogger _logger; | |||
public MqttClientUnsubscribedTopicHandler(PythonScriptHostService pythonScriptHostService, ILogger<MqttClientUnsubscribedTopicHandler> logger) | |||
{ | |||
_pythonScriptHostService = pythonScriptHostService ?? throw new ArgumentNullException(nameof(pythonScriptHostService)); | |||
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); | |||
} | |||
public Task HandleClientUnsubscribedTopicAsync(MqttServerClientUnsubscribedTopicEventArgs eventArgs) | |||
{ | |||
try | |||
{ | |||
var pythonEventArgs = new PythonDictionary | |||
{ | |||
{ "client_id", eventArgs.ClientId }, | |||
{ "topic", eventArgs.TopicFilter } | |||
}; | |||
_pythonScriptHostService.InvokeOptionalFunction("on_client_unsubscribed_topic", pythonEventArgs); | |||
} | |||
catch (Exception exception) | |||
{ | |||
_logger.LogError(exception, "Error while handling client unsubscribed topic event."); | |||
} | |||
return Task.CompletedTask; | |||
} | |||
} | |||
} |
@@ -1,61 +0,0 @@ | |||
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 MqttServerConnectionValidator : IMqttServerConnectionValidator | |||
{ | |||
public const string WrappedSessionItemsKey = "WRAPPED_ITEMS"; | |||
private readonly PythonScriptHostService _pythonScriptHostService; | |||
private readonly ILogger _logger; | |||
public MqttServerConnectionValidator(PythonScriptHostService pythonScriptHostService, ILogger<MqttServerConnectionValidator> logger) | |||
{ | |||
_pythonScriptHostService = pythonScriptHostService ?? throw new ArgumentNullException(nameof(pythonScriptHostService)); | |||
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); | |||
} | |||
public Task ValidateConnectionAsync(MqttConnectionValidatorContext context) | |||
{ | |||
try | |||
{ | |||
var sessionItems = new PythonDictionary(); | |||
var pythonContext = new PythonDictionary | |||
{ | |||
{ "endpoint", context.Endpoint }, | |||
{ "is_secure_connection", context.IsSecureConnection }, | |||
{ "client_id", context.ClientId }, | |||
{ "username", context.Username }, | |||
{ "password", context.Password }, | |||
{ "raw_password", new Bytes(context.RawPassword ?? new byte[0]) }, | |||
{ "clean_session", context.CleanSession}, | |||
{ "authentication_method", context.AuthenticationMethod}, | |||
{ "authentication_data", new Bytes(context.AuthenticationData ?? new byte[0]) }, | |||
{ "session_items", sessionItems }, | |||
{ "result", PythonConvert.Pythonfy(context.ReasonCode) } | |||
}; | |||
_pythonScriptHostService.InvokeOptionalFunction("on_validate_client_connection", pythonContext); | |||
context.ReasonCode = PythonConvert.ParseEnum<MqttConnectReasonCode>((string)pythonContext["result"]); | |||
context.SessionItems[WrappedSessionItemsKey] = sessionItems; | |||
} | |||
catch (Exception exception) | |||
{ | |||
_logger.LogError(exception, "Error while validating client connection."); | |||
context.ReasonCode = MqttConnectReasonCode.UnspecifiedError; | |||
} | |||
return Task.CompletedTask; | |||
} | |||
} | |||
} |
@@ -1,273 +0,0 @@ | |||
using IronPython.Runtime; | |||
using Microsoft.AspNetCore.Http; | |||
using Microsoft.Extensions.Logging; | |||
using MQTTnet.Adapter; | |||
using MQTTnet.AspNetCore; | |||
using MQTTnet.Client.Publishing; | |||
using MQTTnet.Implementations; | |||
using MQTTnet.Protocol; | |||
using MQTTnet.Server.Configuration; | |||
using MQTTnet.Server.Scripting; | |||
using MQTTnet.Server.Status; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Net.WebSockets; | |||
using System.Security.Authentication; | |||
using System.Text; | |||
using System.Threading.Tasks; | |||
namespace MQTTnet.Server.Mqtt | |||
{ | |||
public class MqttServerService | |||
{ | |||
readonly ILogger<MqttServerService> _logger; | |||
readonly MqttSettingsModel _settings; | |||
readonly MqttApplicationMessageInterceptor _mqttApplicationMessageInterceptor; | |||
readonly MqttServerStorage _mqttServerStorage; | |||
readonly MqttClientConnectedHandler _mqttClientConnectedHandler; | |||
readonly MqttClientDisconnectedHandler _mqttClientDisconnectedHandler; | |||
readonly MqttClientSubscribedTopicHandler _mqttClientSubscribedTopicHandler; | |||
readonly MqttClientUnsubscribedTopicHandler _mqttClientUnsubscribedTopicHandler; | |||
readonly MqttServerConnectionValidator _mqttConnectionValidator; | |||
readonly IMqttServer _mqttServer; | |||
readonly MqttSubscriptionInterceptor _mqttSubscriptionInterceptor; | |||
readonly MqttUnsubscriptionInterceptor _mqttUnsubscriptionInterceptor; | |||
readonly PythonScriptHostService _pythonScriptHostService; | |||
readonly MqttWebSocketServerAdapter _webSocketServerAdapter; | |||
public MqttServerService( | |||
MqttSettingsModel mqttSettings, | |||
CustomMqttFactory mqttFactory, | |||
MqttClientConnectedHandler mqttClientConnectedHandler, | |||
MqttClientDisconnectedHandler mqttClientDisconnectedHandler, | |||
MqttClientSubscribedTopicHandler mqttClientSubscribedTopicHandler, | |||
MqttClientUnsubscribedTopicHandler mqttClientUnsubscribedTopicHandler, | |||
MqttServerConnectionValidator mqttConnectionValidator, | |||
MqttSubscriptionInterceptor mqttSubscriptionInterceptor, | |||
MqttUnsubscriptionInterceptor mqttUnsubscriptionInterceptor, | |||
MqttApplicationMessageInterceptor mqttApplicationMessageInterceptor, | |||
MqttServerStorage mqttServerStorage, | |||
PythonScriptHostService pythonScriptHostService, | |||
ILogger<MqttServerService> logger) | |||
{ | |||
_settings = mqttSettings ?? throw new ArgumentNullException(nameof(mqttSettings)); | |||
_mqttClientConnectedHandler = mqttClientConnectedHandler ?? throw new ArgumentNullException(nameof(mqttClientConnectedHandler)); | |||
_mqttClientDisconnectedHandler = mqttClientDisconnectedHandler ?? throw new ArgumentNullException(nameof(mqttClientDisconnectedHandler)); | |||
_mqttClientSubscribedTopicHandler = mqttClientSubscribedTopicHandler ?? throw new ArgumentNullException(nameof(mqttClientSubscribedTopicHandler)); | |||
_mqttClientUnsubscribedTopicHandler = mqttClientUnsubscribedTopicHandler ?? throw new ArgumentNullException(nameof(mqttClientUnsubscribedTopicHandler)); | |||
_mqttConnectionValidator = mqttConnectionValidator ?? throw new ArgumentNullException(nameof(mqttConnectionValidator)); | |||
_mqttSubscriptionInterceptor = mqttSubscriptionInterceptor ?? throw new ArgumentNullException(nameof(mqttSubscriptionInterceptor)); | |||
_mqttUnsubscriptionInterceptor = mqttUnsubscriptionInterceptor ?? throw new ArgumentNullException(nameof(mqttUnsubscriptionInterceptor)); | |||
_mqttApplicationMessageInterceptor = mqttApplicationMessageInterceptor ?? throw new ArgumentNullException(nameof(mqttApplicationMessageInterceptor)); | |||
_mqttServerStorage = mqttServerStorage ?? throw new ArgumentNullException(nameof(mqttServerStorage)); | |||
_pythonScriptHostService = pythonScriptHostService ?? throw new ArgumentNullException(nameof(pythonScriptHostService)); | |||
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); | |||
_webSocketServerAdapter = new MqttWebSocketServerAdapter(mqttFactory.Logger); | |||
var adapters = new List<IMqttServerAdapter> | |||
{ | |||
new MqttTcpServerAdapter(mqttFactory.Logger) | |||
{ | |||
TreatSocketOpeningErrorAsWarning = true // Opening other ports than for HTTP is not allows in Azure App Services. | |||
}, | |||
_webSocketServerAdapter | |||
}; | |||
_mqttServer = mqttFactory.CreateMqttServer(adapters); | |||
} | |||
public void Configure() | |||
{ | |||
_pythonScriptHostService.RegisterProxyObject("publish", new Action<PythonDictionary>(Publish)); | |||
_mqttServerStorage.Configure(); | |||
_mqttServer.ClientConnectedHandler = _mqttClientConnectedHandler; | |||
_mqttServer.ClientDisconnectedHandler = _mqttClientDisconnectedHandler; | |||
_mqttServer.ClientSubscribedTopicHandler = _mqttClientSubscribedTopicHandler; | |||
_mqttServer.ClientUnsubscribedTopicHandler = _mqttClientUnsubscribedTopicHandler; | |||
_mqttServer.StartAsync(CreateMqttServerOptions()).GetAwaiter().GetResult(); | |||
_logger.LogInformation("MQTT server started."); | |||
} | |||
public Task RunWebSocketConnectionAsync(WebSocket webSocket, HttpContext httpContext) | |||
{ | |||
return _webSocketServerAdapter.RunWebSocketConnectionAsync(webSocket, httpContext); | |||
} | |||
public Task<IList<IMqttClientStatus>> GetClientStatusAsync() | |||
{ | |||
return _mqttServer.GetClientStatusAsync(); | |||
} | |||
public Task<IList<IMqttSessionStatus>> GetSessionStatusAsync() | |||
{ | |||
return _mqttServer.GetSessionStatusAsync(); | |||
} | |||
public Task ClearRetainedApplicationMessagesAsync() | |||
{ | |||
return _mqttServer.ClearRetainedApplicationMessagesAsync(); | |||
} | |||
public Task<IList<MqttApplicationMessage>> GetRetainedApplicationMessagesAsync() | |||
{ | |||
return _mqttServer.GetRetainedApplicationMessagesAsync(); | |||
} | |||
public Task<MqttClientPublishResult> PublishAsync(MqttApplicationMessage applicationMessage) | |||
{ | |||
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage)); | |||
return _mqttServer.PublishAsync(applicationMessage); | |||
} | |||
void Publish(PythonDictionary parameters) | |||
{ | |||
try | |||
{ | |||
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); | |||
byte[] binaryPayload; | |||
if (payload == null) | |||
{ | |||
binaryPayload = new byte[0]; | |||
} | |||
else 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(); | |||
} | |||
else | |||
{ | |||
throw new NotSupportedException("Payload type not supported."); | |||
} | |||
applicationMessageBuilder = applicationMessageBuilder | |||
.WithPayload(binaryPayload); | |||
var applicationMessage = applicationMessageBuilder.Build(); | |||
_mqttServer.PublishAsync(applicationMessage).GetAwaiter().GetResult(); | |||
} | |||
catch (Exception exception) | |||
{ | |||
_logger.LogError(exception, "Error while publishing application message from server."); | |||
} | |||
} | |||
IMqttServerOptions CreateMqttServerOptions() | |||
{ | |||
var options = new MqttServerOptionsBuilder() | |||
.WithMaxPendingMessagesPerClient(_settings.MaxPendingMessagesPerClient) | |||
.WithDefaultCommunicationTimeout(TimeSpan.FromSeconds(_settings.CommunicationTimeout)) | |||
.WithConnectionValidator(_mqttConnectionValidator) | |||
.WithApplicationMessageInterceptor(_mqttApplicationMessageInterceptor) | |||
.WithSubscriptionInterceptor(_mqttSubscriptionInterceptor) | |||
.WithUnsubscriptionInterceptor(_mqttUnsubscriptionInterceptor) | |||
.WithStorage(_mqttServerStorage); | |||
// Configure unencrypted connections | |||
if (_settings.TcpEndPoint.Enabled) | |||
{ | |||
options.WithDefaultEndpoint(); | |||
if (_settings.TcpEndPoint.TryReadIPv4(out var address4)) | |||
{ | |||
options.WithDefaultEndpointBoundIPAddress(address4); | |||
} | |||
if (_settings.TcpEndPoint.TryReadIPv6(out var address6)) | |||
{ | |||
options.WithDefaultEndpointBoundIPV6Address(address6); | |||
} | |||
if (_settings.TcpEndPoint.Port > 0) | |||
{ | |||
options.WithDefaultEndpointPort(_settings.TcpEndPoint.Port); | |||
} | |||
} | |||
else | |||
{ | |||
options.WithoutDefaultEndpoint(); | |||
} | |||
// Configure encrypted connections | |||
if (_settings.EncryptedTcpEndPoint.Enabled) | |||
{ | |||
#if NETCOREAPP3_1 || NET5_0 | |||
options | |||
.WithEncryptedEndpoint() | |||
.WithEncryptionSslProtocol(SslProtocols.Tls13); | |||
#else | |||
options | |||
.WithEncryptedEndpoint() | |||
.WithEncryptionSslProtocol(SslProtocols.Tls12); | |||
#endif | |||
if (!string.IsNullOrEmpty(_settings.EncryptedTcpEndPoint?.Certificate?.Path)) | |||
{ | |||
IMqttServerCertificateCredentials certificateCredentials = null; | |||
if (!string.IsNullOrEmpty(_settings.EncryptedTcpEndPoint?.Certificate?.Password)) | |||
{ | |||
certificateCredentials = new MqttServerCertificateCredentials | |||
{ | |||
Password = _settings.EncryptedTcpEndPoint.Certificate.Password | |||
}; | |||
} | |||
options.WithEncryptionCertificate(_settings.EncryptedTcpEndPoint.Certificate.ReadCertificate(), certificateCredentials); | |||
} | |||
if (_settings.EncryptedTcpEndPoint.TryReadIPv4(out var address4)) | |||
{ | |||
options.WithEncryptedEndpointBoundIPAddress(address4); | |||
} | |||
if (_settings.EncryptedTcpEndPoint.TryReadIPv6(out var address6)) | |||
{ | |||
options.WithEncryptedEndpointBoundIPV6Address(address6); | |||
} | |||
if (_settings.EncryptedTcpEndPoint.Port > 0) | |||
{ | |||
options.WithEncryptedEndpointPort(_settings.EncryptedTcpEndPoint.Port); | |||
} | |||
} | |||
else | |||
{ | |||
options.WithoutEncryptedEndpoint(); | |||
} | |||
if (_settings.ConnectionBacklog > 0) | |||
{ | |||
options.WithConnectionBacklog(_settings.ConnectionBacklog); | |||
} | |||
if (_settings.EnablePersistentSessions) | |||
{ | |||
options.WithPersistentSessions(); | |||
} | |||
return options.Build(); | |||
} | |||
} | |||
} |
@@ -1,119 +0,0 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.IO; | |||
using System.Text; | |||
using System.Threading; | |||
using System.Threading.Tasks; | |||
using Microsoft.Extensions.Logging; | |||
using MQTTnet.Server.Configuration; | |||
using Newtonsoft.Json; | |||
namespace MQTTnet.Server.Mqtt | |||
{ | |||
public class MqttServerStorage : IMqttServerStorage | |||
{ | |||
private readonly List<MqttApplicationMessage> _messages = new List<MqttApplicationMessage>(); | |||
private readonly MqttSettingsModel _mqttSettings; | |||
private readonly ILogger<MqttServerStorage> _logger; | |||
private string _path; | |||
private bool _messagesHaveChanged; | |||
public MqttServerStorage(MqttSettingsModel mqttSettings, ILogger<MqttServerStorage> logger) | |||
{ | |||
_mqttSettings = mqttSettings ?? throw new ArgumentNullException(nameof(mqttSettings)); | |||
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); | |||
} | |||
public void Configure() | |||
{ | |||
if (_mqttSettings.RetainedApplicationMessages?.Persist != true || | |||
string.IsNullOrEmpty(_mqttSettings.RetainedApplicationMessages.Path)) | |||
{ | |||
_logger.LogInformation("Persisting of retained application messages is disabled."); | |||
return; | |||
} | |||
_path = PathHelper.ExpandPath(_mqttSettings.RetainedApplicationMessages.Path); | |||
// The retained application messages are stored in a separate thread. | |||
// This is mandatory because writing them to a slow storage (like RaspberryPi SD card) | |||
// will slow down the whole message processing speed. | |||
Task.Run(SaveRetainedMessagesInternalAsync, CancellationToken.None); | |||
} | |||
public Task SaveRetainedMessagesAsync(IList<MqttApplicationMessage> messages) | |||
{ | |||
lock (_messages) | |||
{ | |||
_messages.Clear(); | |||
_messages.AddRange(messages); | |||
_messagesHaveChanged = true; | |||
} | |||
return Task.CompletedTask; | |||
} | |||
private async Task SaveRetainedMessagesInternalAsync() | |||
{ | |||
while (true) | |||
{ | |||
try | |||
{ | |||
await Task.Delay(TimeSpan.FromSeconds(_mqttSettings.RetainedApplicationMessages.WriteInterval)).ConfigureAwait(false); | |||
List<MqttApplicationMessage> messages; | |||
lock (_messages) | |||
{ | |||
if (!_messagesHaveChanged) | |||
{ | |||
continue; | |||
} | |||
messages = new List<MqttApplicationMessage>(_messages); | |||
_messagesHaveChanged = false; | |||
} | |||
var json = JsonConvert.SerializeObject(messages); | |||
await File.WriteAllTextAsync(_path, json, Encoding.UTF8).ConfigureAwait(false); | |||
_logger.LogInformation($"{messages.Count} retained MQTT messages written."); | |||
} | |||
catch (Exception exception) | |||
{ | |||
_logger.LogError(exception, "Error while writing retained MQTT messages."); | |||
} | |||
} | |||
} | |||
public async Task<IList<MqttApplicationMessage>> LoadRetainedMessagesAsync() | |||
{ | |||
if (_mqttSettings.RetainedApplicationMessages?.Persist != true) | |||
{ | |||
return null; | |||
} | |||
if (!File.Exists(_path)) | |||
{ | |||
return null; | |||
} | |||
try | |||
{ | |||
var json = await File.ReadAllTextAsync(_path).ConfigureAwait(false); | |||
var applicationMessages = JsonConvert.DeserializeObject<List<MqttApplicationMessage>>(json); | |||
_logger.LogInformation($"{applicationMessages.Count} retained MQTT messages loaded."); | |||
return applicationMessages; | |||
} | |||
catch (Exception exception) | |||
{ | |||
_logger.LogWarning(exception, "Error while loading persisted retained application messages."); | |||
return null; | |||
} | |||
} | |||
} | |||
} |
@@ -1,49 +0,0 @@ | |||
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 _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 sessionItems = (PythonDictionary)context.SessionItems[MqttServerConnectionValidator.WrappedSessionItemsKey]; | |||
var pythonContext = new PythonDictionary | |||
{ | |||
{ "client_id", context.ClientId }, | |||
{ "session_items", sessionItems }, | |||
{ "topic", context.TopicFilter.Topic }, | |||
{ "qos", (int)context.TopicFilter.QualityOfServiceLevel }, | |||
{ "accept_subscription", context.AcceptSubscription }, | |||
{ "close_connection", context.CloseConnection } | |||
}; | |||
_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; | |||
} | |||
} | |||
} |
@@ -1,48 +0,0 @@ | |||
using System; | |||
using System.Threading.Tasks; | |||
using IronPython.Runtime; | |||
using Microsoft.Extensions.Logging; | |||
using MQTTnet.Server.Scripting; | |||
namespace MQTTnet.Server.Mqtt | |||
{ | |||
public class MqttUnsubscriptionInterceptor : IMqttServerUnsubscriptionInterceptor | |||
{ | |||
private readonly PythonScriptHostService _pythonScriptHostService; | |||
private readonly ILogger _logger; | |||
public MqttUnsubscriptionInterceptor(PythonScriptHostService pythonScriptHostService, ILogger<MqttUnsubscriptionInterceptor> logger) | |||
{ | |||
_pythonScriptHostService = pythonScriptHostService ?? throw new ArgumentNullException(nameof(pythonScriptHostService)); | |||
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); | |||
} | |||
public Task InterceptUnsubscriptionAsync(MqttUnsubscriptionInterceptorContext context) | |||
{ | |||
try | |||
{ | |||
var sessionItems = (PythonDictionary)context.SessionItems[MqttServerConnectionValidator.WrappedSessionItemsKey]; | |||
var pythonContext = new PythonDictionary | |||
{ | |||
{ "client_id", context.ClientId }, | |||
{ "session_items", sessionItems }, | |||
{ "topic", context.Topic }, | |||
{ "accept_unsubscription", context.AcceptUnsubscription }, | |||
{ "close_connection", context.CloseConnection } | |||
}; | |||
_pythonScriptHostService.InvokeOptionalFunction("on_intercept_unsubscription", pythonContext); | |||
context.AcceptUnsubscription = (bool)pythonContext["accept_unsubscription"]; | |||
context.CloseConnection = (bool)pythonContext["close_connection"]; | |||
} | |||
catch (Exception exception) | |||
{ | |||
_logger.LogError(exception, "Error while intercepting unsubscription."); | |||
} | |||
return Task.CompletedTask; | |||
} | |||
} | |||
} |
@@ -1,80 +0,0 @@ | |||
using Microsoft.AspNetCore.Hosting; | |||
using Microsoft.Extensions.Hosting; | |||
using MQTTnet.Server.Web; | |||
using System; | |||
using System.Diagnostics; | |||
using System.IO; | |||
using System.Reflection; | |||
namespace MQTTnet.Server | |||
{ | |||
public static class Program | |||
{ | |||
public static int Main(string[] args) | |||
{ | |||
try | |||
{ | |||
PrintLogo(); | |||
Host.CreateDefaultBuilder(args) | |||
.ConfigureWebHostDefaults(webBuilder => | |||
{ | |||
webBuilder.ConfigureKestrel(serverOptions => | |||
{ | |||
}) | |||
.UseWebRoot(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Web", "wwwroot")) | |||
.UseStartup<Startup>(); | |||
}).Build().Run(); | |||
return 0; | |||
} | |||
catch (Exception exception) | |||
{ | |||
Console.WriteLine(exception); | |||
return -1; | |||
} | |||
} | |||
static void PrintLogo() | |||
{ | |||
Console.ResetColor(); | |||
Console.ForegroundColor = ConsoleColor.Red; | |||
const string LogoText = | |||
@" | |||
███╗ ███╗ ██████╗ ████████╗████████╗███╗ ██╗███████╗████████╗ ███████╗███████╗██████╗ ██╗ ██╗███████╗██████╗ | |||
████╗ ████║██╔═══██╗╚══██╔══╝╚══██╔══╝████╗ ██║██╔════╝╚══██╔══╝ ██╔════╝██╔════╝██╔══██╗██║ ██║██╔════╝██╔══██╗ | |||
██╔████╔██║██║ ██║ ██║ ██║ ██╔██╗ ██║█████╗ ██║ ███████╗█████╗ ██████╔╝██║ ██║█████╗ ██████╔╝ | |||
██║╚██╔╝██║██║▄▄ ██║ ██║ ██║ ██║╚██╗██║██╔══╝ ██║ ╚════██║██╔══╝ ██╔══██╗╚██╗ ██╔╝██╔══╝ ██╔══██╗ | |||
██║ ╚═╝ ██║╚██████╔╝ ██║ ██║ ██║ ╚████║███████╗ ██║ ███████║███████╗██║ ██║ ╚████╔╝ ███████╗██║ ██║ | |||
╚═╝ ╚═╝ ╚══▀▀═╝ ╚═╝ ╚═╝ ╚═╝ ╚═══╝╚══════╝ ╚═╝ ╚══════╝╚══════╝╚═╝ ╚═╝ ╚═══╝ ╚══════╝╚═╝ ╚═╝ | |||
"; | |||
Console.WriteLine(LogoText); | |||
Console.ResetColor(); | |||
Console.WriteLine(); | |||
Console.ForegroundColor = ConsoleColor.White; | |||
Console.WriteLine("The official MQTT server implementation of MQTTnet"); | |||
Console.WriteLine("Copyright (c) 2017-2020 The MQTTnet Team"); | |||
Console.WriteLine(@"https://github.com/chkr1011/MQTTnet"); | |||
Console.ForegroundColor = ConsoleColor.White; | |||
var fileVersion = FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location); | |||
Console.WriteLine($@" | |||
Version: {fileVersion.ProductVersion} | |||
License: MIT (read LICENSE file) | |||
Sponsoring: https://opencollective.com/mqttnet | |||
Support: https://github.com/chkr1011/MQTTnet/issues | |||
Docs: https://github.com/chkr1011/MQTTnet/wiki/MQTTnetServer | |||
"); | |||
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.ResetColor(); | |||
Console.WriteLine(); | |||
} | |||
} | |||
} |
@@ -1,10 +0,0 @@ | |||
# Starting portable version | |||
The portable version requires a local installation of the .net core runtime. With that runtime installed the server can be started via the following comand. | |||
dotnet .\MQTTnet.Server.dll | |||
# Starting self contained versions | |||
The self contained versions are fully portable versions including the .net core runtime. The server can be started using the contained executable files. | |||
Windows: MQTTnet.Server.exe | |||
Linux: MQTTnet.Server (must be set to _executable_ first) |
@@ -1,47 +0,0 @@ | |||
using Microsoft.Extensions.Logging; | |||
using System; | |||
using System.Collections.Generic; | |||
namespace MQTTnet.Server.Scripting.DataSharing | |||
{ | |||
public class DataSharingService | |||
{ | |||
readonly Dictionary<string, object> _storage = new Dictionary<string, object>(); | |||
readonly PythonScriptHostService _pythonScriptHostService; | |||
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; | |||
} | |||
} | |||
} | |||
} |
@@ -1,88 +0,0 @@ | |||
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); | |||
} | |||
} | |||
} |
@@ -1,66 +0,0 @@ | |||
using Microsoft.Extensions.Logging; | |||
using System; | |||
using System.IO; | |||
using System.Text; | |||
namespace MQTTnet.Server.Scripting | |||
{ | |||
public class PythonIOStream : Stream | |||
{ | |||
readonly ILogger _logger; | |||
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(); | |||
} | |||
} | |||
} |
@@ -1,203 +0,0 @@ | |||
using Microsoft.Extensions.Logging; | |||
using Microsoft.Scripting; | |||
using Microsoft.Scripting.Hosting; | |||
using MQTTnet.Server.Configuration; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Dynamic; | |||
using System.IO; | |||
using System.Linq; | |||
using System.Text; | |||
using System.Threading; | |||
using System.Threading.Tasks; | |||
namespace MQTTnet.Server.Scripting | |||
{ | |||
public class PythonScriptHostService | |||
{ | |||
readonly IDictionary<string, object> _proxyObjects = new ExpandoObject(); | |||
readonly List<PythonScriptInstance> _scriptInstances = new List<PythonScriptInstance>(); | |||
readonly string _scriptsPath; | |||
readonly ScriptingSettingsModel _scriptingSettings; | |||
readonly ILogger<PythonScriptHostService> _logger; | |||
readonly ScriptEngine _scriptEngine; | |||
public PythonScriptHostService(ScriptingSettingsModel scriptingSettings, PythonIOStream pythonIOStream, ILogger<PythonScriptHostService> logger) | |||
{ | |||
_scriptingSettings = scriptingSettings ?? throw new ArgumentNullException(nameof(scriptingSettings)); | |||
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); | |||
_scriptEngine = IronPython.Hosting.Python.CreateEngine(); | |||
_scriptEngine.Runtime.IO.SetOutput(pythonIOStream, Encoding.UTF8); | |||
_scriptsPath = PathHelper.ExpandPath(scriptingSettings.ScriptsPath); | |||
} | |||
public void Configure() | |||
{ | |||
AddSearchPaths(_scriptEngine); | |||
TryInitializeScriptsAsync().GetAwaiter().GetResult(); | |||
} | |||
public void RegisterProxyObject(string name, object @object) | |||
{ | |||
if (name == null) throw new ArgumentNullException(nameof(name)); | |||
if (@object == null) throw new ArgumentNullException(nameof(@object)); | |||
_proxyObjects.Add(name, @object); | |||
} | |||
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.Uid}'."); | |||
} | |||
} | |||
} | |||
} | |||
public List<string> GetScriptUids() | |||
{ | |||
lock (_scriptInstances) | |||
{ | |||
return _scriptInstances.Select(si => si.Uid).ToList(); | |||
} | |||
} | |||
public Task<string> ReadScriptAsync(string uid, CancellationToken cancellationToken) | |||
{ | |||
if (uid == null) throw new ArgumentNullException(nameof(uid)); | |||
string path; | |||
lock (_scriptInstances) | |||
{ | |||
path = _scriptInstances.FirstOrDefault(si => si.Uid == uid)?.Path; | |||
} | |||
if (path == null || !File.Exists(path)) | |||
{ | |||
return null; | |||
} | |||
return File.ReadAllTextAsync(path, Encoding.UTF8, cancellationToken); | |||
} | |||
public async Task WriteScriptAsync(string uid, string code, CancellationToken cancellationToken) | |||
{ | |||
var path = Path.Combine(_scriptsPath, uid + ".py"); | |||
await File.WriteAllTextAsync(path, code, Encoding.UTF8, cancellationToken).ConfigureAwait(false); | |||
await TryInitializeScriptsAsync().ConfigureAwait(false); | |||
} | |||
public async Task DeleteScriptAsync(string uid) | |||
{ | |||
var path = Path.Combine(_scriptsPath, uid + ".py"); | |||
if (File.Exists(path)) | |||
{ | |||
File.Delete(path); | |||
await TryInitializeScriptsAsync().ConfigureAwait(false); | |||
} | |||
} | |||
public async Task TryInitializeScriptsAsync() | |||
{ | |||
lock (_scriptInstances) | |||
{ | |||
foreach (var scriptInstance in _scriptInstances) | |||
{ | |||
try | |||
{ | |||
scriptInstance.InvokeOptionalFunction("destroy"); | |||
} | |||
catch (Exception exception) | |||
{ | |||
_logger.LogWarning(exception, $"Error while unloading script '{scriptInstance.Uid}'."); | |||
} | |||
} | |||
_scriptInstances.Clear(); | |||
} | |||
foreach (var path in Directory.GetFiles(_scriptsPath, "*.py", SearchOption.AllDirectories).OrderBy(file => file)) | |||
{ | |||
await TryInitializeScriptAsync(path).ConfigureAwait(false); | |||
} | |||
} | |||
async Task TryInitializeScriptAsync(string path) | |||
{ | |||
var uid = new FileInfo(path).Name.Replace(".py", string.Empty, StringComparison.OrdinalIgnoreCase); | |||
try | |||
{ | |||
_logger.LogTrace($"Initializing Python script '{uid}'..."); | |||
var code = await File.ReadAllTextAsync(path).ConfigureAwait(false); | |||
var scriptInstance = CreateScriptInstance(uid, path, code); | |||
scriptInstance.InvokeOptionalFunction("initialize"); | |||
lock (_scriptInstances) | |||
{ | |||
_scriptInstances.Add(scriptInstance); | |||
} | |||
_logger.LogInformation($"Initialized script '{uid}'."); | |||
} | |||
catch (Exception exception) | |||
{ | |||
_logger.LogError(exception, $"Error while initializing script '{uid}'."); | |||
} | |||
} | |||
PythonScriptInstance CreateScriptInstance(string uid, string path, 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(uid, path, scriptScope); | |||
} | |||
void AddSearchPaths(ScriptEngine scriptEngine) | |||
{ | |||
if (_scriptingSettings.IncludePaths?.Any() != true) | |||
{ | |||
return; | |||
} | |||
var searchPaths = scriptEngine.GetSearchPaths(); | |||
foreach (var path in _scriptingSettings.IncludePaths) | |||
{ | |||
var effectivePath = PathHelper.ExpandPath(path); | |||
if (Directory.Exists(effectivePath)) | |||
{ | |||
searchPaths.Add(effectivePath); | |||
_logger.LogInformation($"Added Python lib path: {effectivePath}"); | |||
} | |||
} | |||
scriptEngine.SetSearchPaths(searchPaths); | |||
} | |||
} | |||
} |
@@ -1,60 +0,0 @@ | |||
using IronPython.Runtime; | |||
using Microsoft.Scripting.Hosting; | |||
using System; | |||
namespace MQTTnet.Server.Scripting | |||
{ | |||
public class PythonScriptInstance | |||
{ | |||
readonly ScriptScope _scriptScope; | |||
public PythonScriptInstance(string uid, string path, ScriptScope scriptScope) | |||
{ | |||
Uid = uid; | |||
Path = path; | |||
_scriptScope = scriptScope; | |||
} | |||
public string Uid { get; } | |||
public string Path { 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); | |||
} | |||
} | |||
} | |||
} | |||
} |
@@ -1,140 +0,0 @@ | |||
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 destroy(): | |||
""" | |||
This function is invoked when the script is unloaded due to a script file update etc. | |||
""" | |||
print("Bye 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) | |||
# Access some custom data here which was inserted upon connect and may use it for validation. | |||
mqtt_net_server.write_shared_data(context["client_id"], {"custom_value_1": 1, "custom_value_2": True}) | |||
return | |||
# Supported results: | |||
# * success | |||
# * unspecified_error | |||
# * malformed_packet | |||
# * protocol_error | |||
# * implementation_specific_error | |||
# * unsupported_protocol_version | |||
# * client_identifier_not_valid | |||
# * bad_user_name_or_password | |||
# * not_authorized | |||
# * server_unavailable | |||
# * server_busy | |||
# * banned | |||
# * bad_authentication_method | |||
# * topic_name_invalid | |||
# * packet_too_large | |||
# * quota_exceeded | |||
# * payload_format_invalid | |||
# * retain_not_supported | |||
# * qos_not_supported | |||
# * use_another_server | |||
# * server_moved | |||
# * connection_rate_exceeded | |||
if context["client_id"] != "test_client": | |||
context["result"] = "bad_user_name_or_password" | |||
return | |||
if context["username"] != "bud spencer": | |||
context["result"] = "bad_user_name_or_password" | |||
return | |||
if context["password_string"] != "secret": | |||
context["result"] = "bad_user_name_or_password" | |||
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"])) | |||
def on_client_connected(event_args): | |||
""" | |||
This function is called whenever a client has passed the validation is connected. | |||
""" | |||
print("Client '{client_id}' is now connected.".format(client_id=event_args["client_id"])) | |||
def on_client_disconnected(event_args): | |||
""" | |||
This function is called whenever a client has disconnected. | |||
""" | |||
print("Client '{client_id}' is now disconnected (type = {type}).".format(client_id=event_args["client_id"], type=event_args["type"])) | |||
def on_client_subscribed_topic(event_args): | |||
""" | |||
This function is called whenever a client has subscribed to a topic (when allowed). | |||
""" | |||
print("Client '{client_id}' has subscribed to '{topic}'.".format(client_id=event_args["client_id"], topic=event_args["topic"])) | |||
def on_client_unsubscribed_topic(event_args): | |||
""" | |||
This function is called whenever a client has unsubscribed from a topic. | |||
""" | |||
print("Client '{client_id}' has unsubscribed from '{topic}'.".format(client_id=event_args["client_id"], topic=event_args["topic"])) |
@@ -1,21 +0,0 @@ | |||
# MQTT Scripts | |||
This directory contains scripts which are loaded by the server and can be used to perform the following tasks. | |||
* Validation of client connections via credentials, client IDs etc. | |||
* Manipulation of every processed message. | |||
* Validation of subscription attempts. | |||
* 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. |
@@ -1,118 +0,0 @@ | |||
using System; | |||
using System.IO; | |||
using System.Net.Http.Headers; | |||
using System.Security.Claims; | |||
using System.Text; | |||
using System.Text.Encodings.Web; | |||
using System.Threading.Tasks; | |||
using IronPython.Runtime; | |||
using Microsoft.AspNetCore.Authentication; | |||
using Microsoft.Extensions.Logging; | |||
using Microsoft.Extensions.Options; | |||
using Microsoft.Net.Http.Headers; | |||
using Microsoft.Scripting; | |||
namespace MQTTnet.Server.Web | |||
{ | |||
public class AuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions> | |||
{ | |||
private readonly ILogger<AuthenticationHandler> _logger; | |||
public AuthenticationHandler( | |||
IOptionsMonitor<AuthenticationSchemeOptions> options, | |||
ILoggerFactory loggerFactory, | |||
UrlEncoder encoder, | |||
ISystemClock clock, | |||
ILogger<AuthenticationHandler> logger) | |||
: base(options, loggerFactory, encoder, clock) | |||
{ | |||
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); | |||
} | |||
protected override async Task<AuthenticateResult> HandleAuthenticateAsync() | |||
{ | |||
if (!Request.Headers.ContainsKey(HeaderNames.Authorization)) | |||
{ | |||
return AuthenticateResult.NoResult(); | |||
} | |||
try | |||
{ | |||
var headerValue = Request.Headers[HeaderNames.Authorization]; | |||
var parsedHeaderValue = AuthenticationHeaderValue.Parse(Request.Headers[HeaderNames.Authorization]); | |||
var scheme = parsedHeaderValue.Scheme; | |||
var parameter = parsedHeaderValue.Parameter; | |||
string username = null; | |||
string password = null; | |||
if (scheme.Equals("Basic", StringComparison.OrdinalIgnoreCase)) | |||
{ | |||
var credentialBytes = Convert.FromBase64String(parsedHeaderValue.Parameter); | |||
var credentials = Encoding.UTF8.GetString(credentialBytes).Split(':'); | |||
username = credentials[0]; | |||
password = credentials[1]; | |||
} | |||
var context = new PythonDictionary | |||
{ | |||
["header_value"] = headerValue, | |||
["scheme"] = scheme, | |||
["parameter"] = parameter, | |||
["username"] = username, | |||
["password"] = password, | |||
["is_authenticated"] = false | |||
}; | |||
if (!ValidateUser(context)) | |||
{ | |||
return AuthenticateResult.Fail("Invalid credentials."); | |||
} | |||
var claims = new[] | |||
{ | |||
new Claim(ClaimTypes.NameIdentifier, context.get("username") as string ?? string.Empty) | |||
}; | |||
var identity = new ClaimsIdentity(claims, Scheme.Name); | |||
var principal = new ClaimsPrincipal(identity); | |||
var ticket = new AuthenticationTicket(principal, Scheme.Name); | |||
return AuthenticateResult.Success(ticket); | |||
} | |||
catch (Exception exception) | |||
{ | |||
_logger.LogWarning("Error while authenticating user.", exception); | |||
return AuthenticateResult.Fail(exception); | |||
} | |||
finally | |||
{ | |||
await Task.CompletedTask; | |||
} | |||
} | |||
private bool ValidateUser(PythonDictionary context) | |||
{ | |||
var handlerPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Web", "authorization_handler.py"); | |||
if (!File.Exists(handlerPath)) | |||
{ | |||
return false; | |||
} | |||
var code = File.ReadAllText(handlerPath); | |||
var scriptEngine = IronPython.Hosting.Python.CreateEngine(); | |||
//scriptEngine.Runtime.IO.SetOutput(new PythonIOStream(_logger.), Encoding.UTF8); | |||
var scriptScope = scriptEngine.CreateScope(); | |||
var scriptSource = scriptScope.Engine.CreateScriptSourceFromString(code, SourceCodeKind.File); | |||
var compiledCode = scriptSource.Compile(); | |||
compiledCode.Execute(scriptScope); | |||
var function = scriptScope.Engine.Operations.GetMember<PythonFunction>(scriptScope, "handle_authenticate"); | |||
scriptScope.Engine.Operations.Invoke(function, context); | |||
return context.get("is_authenticated", false) as bool? == true; | |||
} | |||
} | |||
} |
@@ -1,20 +0,0 @@ | |||
using System; | |||
using System.IO; | |||
using System.Text; | |||
using Microsoft.AspNetCore.Http; | |||
namespace MQTTnet.Server.Web | |||
{ | |||
public static class Extensions | |||
{ | |||
public static string ReadBodyAsString(this HttpRequest request) | |||
{ | |||
if (request == null) throw new ArgumentNullException(nameof(request)); | |||
using (var reader = new StreamReader(request.Body, Encoding.UTF8)) | |||
{ | |||
return reader.ReadToEnd(); | |||
} | |||
} | |||
} | |||
} |
@@ -1,228 +0,0 @@ | |||
using Microsoft.AspNetCore.Authentication; | |||
using Microsoft.AspNetCore.Builder; | |||
using Microsoft.AspNetCore.Mvc; | |||
using Microsoft.Extensions.Configuration; | |||
using Microsoft.Extensions.DependencyInjection; | |||
using Microsoft.Net.Http.Headers; | |||
using Microsoft.OpenApi.Models; | |||
using Microsoft.Scripting.Utils; | |||
using MQTTnet.AspNetCore; | |||
using MQTTnet.Server.Configuration; | |||
using MQTTnet.Server.Logging; | |||
using MQTTnet.Server.Mqtt; | |||
using MQTTnet.Server.Scripting; | |||
using MQTTnet.Server.Scripting.DataSharing; | |||
using Newtonsoft.Json.Converters; | |||
using Swashbuckle.AspNetCore.SwaggerUI; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Net; | |||
namespace MQTTnet.Server.Web | |||
{ | |||
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, | |||
MqttServerService mqttServerService, | |||
PythonScriptHostService pythonScriptHostService, | |||
DataSharingService dataSharingService, | |||
MqttSettingsModel mqttSettings) | |||
{ | |||
application.UseDefaultFiles(); | |||
application.UseStaticFiles(); | |||
application.UseHsts(); | |||
application.UseRouting(); | |||
application.UseCors(x => x | |||
.AllowAnyOrigin() | |||
.AllowAnyMethod() | |||
.AllowAnyHeader()); | |||
application.UseAuthentication(); | |||
application.UseAuthorization(); | |||
application.UseEndpoints(endpoints => | |||
{ | |||
endpoints.MapControllers(); | |||
}); | |||
ConfigureWebSocketEndpoint(application, mqttServerService, mqttSettings); | |||
dataSharingService.Configure(); | |||
pythonScriptHostService.Configure(); | |||
mqttServerService.Configure(); | |||
application.UseSwagger(o => o.RouteTemplate = "/api/{documentName}/swagger.json"); | |||
application.UseSwaggerUI(o => | |||
{ | |||
o.RoutePrefix = "api"; | |||
o.DocumentTitle = "MQTTnet.Server API"; | |||
o.SwaggerEndpoint("/api/v1/swagger.json", "MQTTnet.Server API v1"); | |||
o.DisplayRequestDuration(); | |||
o.DocExpansion(DocExpansion.List); | |||
o.DefaultModelRendering(ModelRendering.Model); | |||
}); | |||
} | |||
public void ConfigureServices(IServiceCollection services) | |||
{ | |||
services.AddCors(); | |||
services.AddControllers(); | |||
services.AddMvc() | |||
.SetCompatibilityVersion(CompatibilityVersion.Version_3_0) | |||
.AddNewtonsoftJson(o => | |||
{ | |||
o.SerializerSettings.Converters.Add(new StringEnumConverter()); | |||
}); | |||
ReadMqttSettings(services); | |||
services.AddSingleton<PythonIOStream>(); | |||
services.AddSingleton<PythonScriptHostService>(); | |||
services.AddSingleton<DataSharingService>(); | |||
services.AddSingleton<MqttNetLoggerWrapper>(); | |||
services.AddSingleton<CustomMqttFactory>(); | |||
services.AddSingleton<MqttServerService>(); | |||
services.AddSingleton<MqttServerStorage>(); | |||
services.AddSingleton<MqttClientConnectedHandler>(); | |||
services.AddSingleton<MqttClientDisconnectedHandler>(); | |||
services.AddSingleton<MqttClientSubscribedTopicHandler>(); | |||
services.AddSingleton<MqttClientUnsubscribedTopicHandler>(); | |||
services.AddSingleton<MqttServerConnectionValidator>(); | |||
services.AddSingleton<MqttSubscriptionInterceptor>(); | |||
services.AddSingleton<MqttUnsubscriptionInterceptor>(); | |||
services.AddSingleton<MqttApplicationMessageInterceptor>(); | |||
services.AddSwaggerGen(c => | |||
{ | |||
var securityScheme = new OpenApiSecurityScheme | |||
{ | |||
Scheme = "basic", | |||
Name = HeaderNames.Authorization, | |||
Type = SecuritySchemeType.Http, | |||
In = ParameterLocation.Header, | |||
Reference = new OpenApiReference | |||
{ | |||
Type = ReferenceType.SecurityScheme, | |||
Id = "Swagger" | |||
} | |||
}; | |||
c.AddSecurityDefinition("Swagger", securityScheme); | |||
c.AddSecurityRequirement(new OpenApiSecurityRequirement | |||
{ | |||
[securityScheme] = new List<string>() | |||
}); | |||
c.SwaggerDoc("v1", new OpenApiInfo | |||
{ | |||
Title = "MQTTnet.Server API", | |||
Version = "v1", | |||
Description = "The public API for the MQTT broker MQTTnet.Server.", | |||
License = new OpenApiLicense | |||
{ | |||
Name = "MIT", | |||
Url = new Uri("https://github.com/chkr1011/MQTTnet/blob/master/README.md") | |||
}, | |||
Contact = new OpenApiContact | |||
{ | |||
Name = "MQTTnet.Server", | |||
Email = string.Empty, | |||
Url = new Uri("https://github.com/chkr1011/MQTTnet") | |||
}, | |||
}); | |||
}); | |||
services.AddAuthentication("Basic") | |||
.AddScheme<AuthenticationSchemeOptions, AuthenticationHandler>("Basic", null) | |||
.AddCookie(); | |||
} | |||
void ReadMqttSettings(IServiceCollection services) | |||
{ | |||
var mqttSettings = new MqttSettingsModel(); | |||
Configuration.Bind("MQTT", mqttSettings); | |||
services.AddSingleton(mqttSettings); | |||
var scriptingSettings = new ScriptingSettingsModel(); | |||
Configuration.Bind("Scripting", scriptingSettings); | |||
services.AddSingleton(scriptingSettings); | |||
} | |||
static void ConfigureWebSocketEndpoint( | |||
IApplicationBuilder application, | |||
MqttServerService mqttServerService, | |||
MqttSettingsModel mqttSettings) | |||
{ | |||
if (mqttSettings?.WebSocketEndPoint?.Enabled != true) | |||
{ | |||
return; | |||
} | |||
if (string.IsNullOrEmpty(mqttSettings.WebSocketEndPoint.Path)) | |||
{ | |||
return; | |||
} | |||
var webSocketOptions = new WebSocketOptions | |||
{ | |||
KeepAliveInterval = TimeSpan.FromSeconds(mqttSettings.WebSocketEndPoint.KeepAliveInterval) | |||
}; | |||
if (mqttSettings.WebSocketEndPoint.AllowedOrigins?.Any() == true) | |||
{ | |||
webSocketOptions.AllowedOrigins.AddRange(mqttSettings.WebSocketEndPoint.AllowedOrigins); | |||
} | |||
application.UseWebSockets(webSocketOptions); | |||
application.Use(async (context, next) => | |||
{ | |||
if (context.Request.Path == mqttSettings.WebSocketEndPoint.Path) | |||
{ | |||
if (context.WebSockets.IsWebSocketRequest) | |||
{ | |||
string subProtocol = null; | |||
if (context.Request.Headers.TryGetValue("Sec-WebSocket-Protocol", out var requestedSubProtocolValues)) | |||
{ | |||
subProtocol = MqttSubProtocolSelector.SelectSubProtocol(requestedSubProtocolValues); | |||
} | |||
using (var webSocket = await context.WebSockets.AcceptWebSocketAsync(subProtocol).ConfigureAwait(false)) | |||
{ | |||
await mqttServerService.RunWebSocketConnectionAsync(webSocket, context).ConfigureAwait(false); | |||
} | |||
} | |||
else | |||
{ | |||
context.Response.StatusCode = (int)HttpStatusCode.BadRequest; | |||
} | |||
} | |||
else | |||
{ | |||
await next().ConfigureAwait(false); | |||
} | |||
}); | |||
} | |||
} | |||
} |
@@ -1,20 +0,0 @@ | |||
def handle_authenticate(context): | |||
""" | |||
This function is invoked whenever a user tries to access protected HTTP resources. | |||
This function must exist and return a proper value. Otherwise the request is denied. | |||
All authentication data must be sent via using the "Authorization" header. This header | |||
will be parsed and all values will be exposed to the context. | |||
""" | |||
header_value = context["header_value"] # The untouched header value. | |||
scheme = context["scheme"] | |||
parameter = context["parameter"] | |||
username = context["username"] # Only set if proper "Basic" authorization is used. | |||
password = context["password"] # Only set if proper "Basic" authorization is used. | |||
context["is_authenticated"] = True # Change this to _False_ in case of invalid credentials. | |||
# Example for an API key with proper format. | |||
if header_value == "APIKey 123456": | |||
context["is_authenticated"] = True |
@@ -1 +0,0 @@ | |||
World |
@@ -1,33 +0,0 @@ | |||
<html> | |||
<head> | |||
<title>MQTT JS Example</title> | |||
<script src="https://cdn.jsdelivr.net/npm/mqtt@4.0.1/dist/mqtt.min.js" type="text/javascript"></script> | |||
<script type="text/javascript"> | |||
var client = mqtt.connect('ws://localhost:80/mqtt') | |||
client.on('connect', function () { | |||
client.subscribe('mqtt_subscription', function (err) { | |||
console.log("Subscribed"); | |||
}) | |||
}) | |||
client.on('message', function (topic, message) { | |||
console.log(topic); | |||
console.log(message.toString()) | |||
}) | |||
function buttonClick() { | |||
client.publish("MQTT_JS_TOPIC", "payload"); | |||
} | |||
</script> | |||
</head> | |||
<body> | |||
<button onclick="buttonClick()">Publish</button> | |||
</body> | |||
</html> |
@@ -1,41 +0,0 @@ | |||
<html> | |||
<head> | |||
<title>paho JS Example</title> | |||
<script src="https://cdnjs.cloudflare.com/ajax/libs/paho-mqtt/1.0.1/mqttws31.js" type="text/javascript"></script> | |||
<script type="text/javascript"> | |||
var mqttClient = new Paho.MQTT.Client("localhost", 80, "/mqtt", "pahoJS_client"); | |||
mqttClient.onConnectionLost = onConnectionLost; | |||
mqttClient.onMessageArrived = onMessageArrived; | |||
mqttClient.connect({ onSuccess: onConnect }); | |||
function onConnect() { | |||
console.log("MQTT client connected"); | |||
mqttClient.subscribe("pahoJS_subscription"); | |||
} | |||
function onConnectionLost(responseObject) { | |||
if (responseObject.errorCode !== 0) { | |||
console.log("onConnectionLost:" + responseObject.errorMessage); | |||
} | |||
} | |||
function onMessageArrived(message) { | |||
console.log("onMessageArrived:" + message.payloadString); | |||
} | |||
function buttonClick() { | |||
message = new Paho.MQTT.Message("Hello"); | |||
message.destinationName = "pahoJS_topic"; | |||
mqttClient.send(message); | |||
} | |||
</script> | |||
</head> | |||
<body> | |||
<button onclick="buttonClick()">Publish</button> | |||
</body> | |||
</html> |
@@ -1,9 +0,0 @@ | |||
{ | |||
"Logging": { | |||
"LogLevel": { | |||
"Default": "Debug", | |||
"System": "Information", | |||
"Microsoft": "Information" | |||
} | |||
} | |||
} |
@@ -1,65 +0,0 @@ | |||
{ | |||
"Kestrel": { | |||
"EndPoints": { | |||
"Http": { | |||
"Url": "http://*:80" | |||
}, | |||
"Https": { | |||
"Url": "http://*:443" | |||
} | |||
} | |||
}, | |||
"MQTT": { | |||
/* | |||
Wildcard Addresses: | |||
* - All local IP addresses | |||
localhost - Localhost only | |||
disable - Skip address assignment | |||
*/ | |||
"TcpEndPoint": { | |||
"Enabled": true, | |||
"IPv4": "*", | |||
"IPv6": "*", | |||
"Port": 1883 | |||
}, | |||
"EncryptedTcpEndPoint": { | |||
"Enabled": false, | |||
"IPv4": "*", | |||
"IPv6": "*", | |||
"Port": 8883, | |||
"Certificate": { | |||
"Path": "/absolute/path/to/pfx", | |||
"Password": "" | |||
} | |||
}, | |||
"WebSocketEndPoint": { | |||
"Enabled": true, | |||
"Path": "/mqtt", | |||
"KeepAliveInterval": 120, // In seconds. | |||
"ReceiveBufferSize": 4096, | |||
"AllowedOrigins": [] // List of strings with URLs. | |||
}, | |||
"CommunicationTimeout": 15, // In seconds. | |||
"ConnectionBacklog": 10, // Set 0 to disable | |||
"EnablePersistentSessions": true, | |||
"MaxPendingMessagesPerClient": 250, | |||
"RetainedApplicationMessages": { | |||
"Persist": true, | |||
"Path": "RetainedApplicationMessages.json", | |||
"WriteInterval": 10 // In seconds. | |||
}, | |||
"EnableDebugLogging": false | |||
}, | |||
"Scripting": { | |||
"ScriptsPath": "Scripts", | |||
"IncludePaths": [] | |||
}, | |||
"Logging": { | |||
"LogLevel": { | |||
"Default": "Debug", | |||
"System": "Information", | |||
"Microsoft": "Information" | |||
} | |||
}, | |||
"AllowedHosts": "*" | |||
} |
@@ -9,7 +9,7 @@ namespace MQTTnet.Benchmarks | |||
[MemoryDiagnoser] | |||
public class LoggerBenchmark | |||
{ | |||
IMqttNetLogger _logger; | |||
MqttNetLogger _logger; | |||
IMqttNetScopedLogger _childLogger; | |||
bool _useHandler; | |||
@@ -19,10 +19,10 @@ namespace MQTTnet.Benchmarks | |||
_logger = new MqttNetLogger(); | |||
_childLogger = _logger.CreateScopedLogger("child"); | |||
MqttNetGlobalLogger.LogMessagePublished += OnLogMessagePublished; | |||
_logger.LogMessagePublished += OnLogMessagePublished; | |||
} | |||
private void OnLogMessagePublished(object sender, MqttNetLogMessagePublishedEventArgs eventArgs) | |||
void OnLogMessagePublished(object sender, MqttNetLogMessagePublishedEventArgs eventArgs) | |||
{ | |||
if (_useHandler) | |||
{ | |||
@@ -2,6 +2,7 @@ | |||
using System.Threading.Tasks; | |||
using MQTTnet.Client; | |||
using MQTTnet.Client.Options; | |||
using MQTTnet.Diagnostics; | |||
namespace MQTTnet.TestApp.NetCore | |||
{ | |||
@@ -9,10 +10,13 @@ namespace MQTTnet.TestApp.NetCore | |||
{ | |||
public static async Task RunAsync() | |||
{ | |||
MqttNetConsoleLogger.ForwardToConsole(); | |||
try | |||
{ | |||
var factory = new MqttFactory(); | |||
var logger = new MqttNetLogger(); | |||
MqttNetConsoleLogger.ForwardToConsole(logger); | |||
var factory = new MqttFactory(logger); | |||
var client = factory.CreateMqttClient(); | |||
var options = new MqttClientOptionsBuilder() | |||
@@ -7,6 +7,7 @@ using MQTTnet.Protocol; | |||
using System; | |||
using System.Text; | |||
using System.Threading.Tasks; | |||
using MQTTnet.Diagnostics; | |||
namespace MQTTnet.TestApp.NetCore | |||
{ | |||
@@ -16,9 +17,10 @@ namespace MQTTnet.TestApp.NetCore | |||
{ | |||
try | |||
{ | |||
MqttNetConsoleLogger.ForwardToConsole(); | |||
var logger = new MqttNetLogger(); | |||
MqttNetConsoleLogger.ForwardToConsole(logger); | |||
var factory = new MqttFactory(); | |||
var factory = new MqttFactory(logger); | |||
var client = factory.CreateMqttClient(); | |||
var clientOptions = new MqttClientOptions | |||
{ | |||
@@ -8,10 +8,12 @@ namespace MQTTnet.TestApp.NetCore | |||
{ | |||
static readonly object _lock = new object(); | |||
public static void ForwardToConsole() | |||
public static void ForwardToConsole(MqttNetLogger logger) | |||
{ | |||
MqttNetGlobalLogger.LogMessagePublished -= PrintToConsole; | |||
MqttNetGlobalLogger.LogMessagePublished += PrintToConsole; | |||
if (logger == null) throw new ArgumentNullException(nameof(logger)); | |||
logger.LogMessagePublished -= PrintToConsole; | |||
logger.LogMessagePublished += PrintToConsole; | |||
} | |||
public static void PrintToConsole(string message, ConsoleColor color) | |||
@@ -2,6 +2,7 @@ | |||
using System.Threading.Tasks; | |||
using MQTTnet.Client; | |||
using MQTTnet.Client.Options; | |||
using MQTTnet.Diagnostics; | |||
using MQTTnet.Server; | |||
namespace MQTTnet.TestApp.NetCore | |||
@@ -10,9 +11,10 @@ namespace MQTTnet.TestApp.NetCore | |||
{ | |||
public static async Task RunAsync() | |||
{ | |||
MqttNetConsoleLogger.ForwardToConsole(); | |||
var logger = new MqttNetLogger(); | |||
MqttNetConsoleLogger.ForwardToConsole(logger); | |||
var factory = new MqttFactory(); | |||
var factory = new MqttFactory(logger); | |||
var server = factory.CreateMqttServer(); | |||
var client = factory.CreateMqttClient(); | |||