diff --git a/Source/MQTTnet.Server/Controllers/ClientsController.cs b/Source/MQTTnet.Server/Controllers/ClientsController.cs new file mode 100644 index 00000000..afc75119 --- /dev/null +++ b/Source/MQTTnet.Server/Controllers/ClientsController.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using System.Web; +using Microsoft.AspNetCore.Mvc; +using MQTTnet.Server.Mqtt; +using MQTTnet.Server.Status; + +namespace MQTTnet.Server.Controllers +{ + [ApiController] + public class ClientsController : ControllerBase + { + private readonly MqttServerService _mqttServerService; + + public ClientsController(MqttServerService mqttServerService) + { + _mqttServerService = mqttServerService ?? throw new ArgumentNullException(nameof(mqttServerService)); + } + + [Route("api/v1/clients")] + [HttpGet] + public async Task>> GetClients() + { + return new ObjectResult(await _mqttServerService.GetClientStatusAsync()); + } + + [Route("api/v1/clients/{clientId}")] + [HttpGet] + public async Task> 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 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); + } + } +} diff --git a/Source/MQTTnet.Server/Controllers/RetainedApplicationMessagesController.cs b/Source/MQTTnet.Server/Controllers/RetainedApplicationMessagesController.cs new file mode 100644 index 00000000..30110b38 --- /dev/null +++ b/Source/MQTTnet.Server/Controllers/RetainedApplicationMessagesController.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using System.Web; +using Microsoft.AspNetCore.Mvc; +using MQTTnet.Server.Mqtt; + +namespace MQTTnet.Server.Controllers +{ + [ApiController] + public class RetainedApplicationMessagesController : ControllerBase + { + private readonly MqttServerService _mqttServerService; + + public RetainedApplicationMessagesController(MqttServerService mqttServerService) + { + _mqttServerService = mqttServerService ?? throw new ArgumentNullException(nameof(mqttServerService)); + } + + [Route("api/v1/retainedApplicationMessages")] + [HttpGet] + public async Task>> GetRetainedApplicationMessages() + { + return new ObjectResult(await _mqttServerService.GetRetainedApplicationMessagesAsync()); + } + + [Route("api/v1/retainedApplicationMessages/{topic}")] + [HttpGet] + public async Task> 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 DeleteRetainedApplicationMessages() + { + await _mqttServerService.ClearRetainedApplicationMessagesAsync(); + return StatusCode((int)HttpStatusCode.NoContent); + } + + [Route("api/v1/retainedApplicationMessages/{topic}")] + [HttpDelete] + public async Task DeleteRetainedApplicationMessage(string topic) + { + topic = HttpUtility.UrlDecode(topic); + + await _mqttServerService.PublishAsync(new MqttApplicationMessageBuilder().WithTopic(topic).WithRetainFlag().Build()); + return StatusCode((int)HttpStatusCode.NoContent); + } + } +} diff --git a/Source/MQTTnet.Server/Controllers/ScriptsController.cs b/Source/MQTTnet.Server/Controllers/ScriptsController.cs new file mode 100644 index 00000000..1b2a7032 --- /dev/null +++ b/Source/MQTTnet.Server/Controllers/ScriptsController.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace MQTTnet.Server.Controllers +{ + public class ScriptsController + { + } +} diff --git a/Source/MQTTnet.Server/Controllers/SessionsController.cs b/Source/MQTTnet.Server/Controllers/SessionsController.cs new file mode 100644 index 00000000..415d4b9c --- /dev/null +++ b/Source/MQTTnet.Server/Controllers/SessionsController.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using System.Web; +using Microsoft.AspNetCore.Mvc; +using MQTTnet.Server.Mqtt; +using MQTTnet.Server.Status; + +namespace MQTTnet.Server.Controllers +{ + [ApiController] + public class SessionsController : ControllerBase + { + private readonly MqttServerService _mqttServerService; + + public SessionsController(MqttServerService mqttServerService) + { + _mqttServerService = mqttServerService ?? throw new ArgumentNullException(nameof(mqttServerService)); + } + + [Route("api/v1/sessions")] + [HttpGet] + public async Task>> GetSessions() + { + return new ObjectResult(await _mqttServerService.GetSessionStatusAsync()); + } + + [Route("api/v1/sessions/{clientId}")] + [HttpGet] + public async Task> 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 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 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); + } + } +} diff --git a/Source/MQTTnet.Server/Controllers/ValuesController.cs b/Source/MQTTnet.Server/Controllers/ValuesController.cs deleted file mode 100644 index bfcc398f..00000000 --- a/Source/MQTTnet.Server/Controllers/ValuesController.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System.Collections.Generic; -using Microsoft.AspNetCore.Mvc; - -namespace MQTTnet.Server.Controllers -{ - [Route("api/[controller]")] - [ApiController] - public class ValuesController : ControllerBase - { - // GET api/values - [HttpGet] - public ActionResult> Get() - { - return new string[] { "value1", "value2" }; - } - - // GET api/values/5 - [HttpGet("{id}")] - public ActionResult Get(int id) - { - return "value"; - } - - // POST api/values - [HttpPost] - public void Post([FromBody] string value) - { - } - - // PUT api/values/5 - [HttpPut("{id}")] - public void Put(int id, [FromBody] string value) - { - } - - // DELETE api/values/5 - [HttpDelete("{id}")] - public void Delete(int id) - { - } - } -} diff --git a/Source/MQTTnet.Server/MQTTnet.Server.csproj b/Source/MQTTnet.Server/MQTTnet.Server.csproj index d8adb495..c322548c 100644 --- a/Source/MQTTnet.Server/MQTTnet.Server.csproj +++ b/Source/MQTTnet.Server/MQTTnet.Server.csproj @@ -40,8 +40,8 @@ - - + + diff --git a/Source/MQTTnet.Server/Mqtt/MqttServerService.cs b/Source/MQTTnet.Server/Mqtt/MqttServerService.cs index 7b796f61..42ec60b8 100644 --- a/Source/MQTTnet.Server/Mqtt/MqttServerService.cs +++ b/Source/MQTTnet.Server/Mqtt/MqttServerService.cs @@ -10,10 +10,12 @@ 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; namespace MQTTnet.Server.Mqtt { @@ -92,6 +94,33 @@ namespace MQTTnet.Server.Mqtt return _webSocketServerAdapter.RunWebSocketConnectionAsync(webSocket, httpContext); } + public Task> GetClientStatusAsync() + { + return _mqttServer.GetClientStatusAsync(); + } + + public Task> GetSessionStatusAsync() + { + return _mqttServer.GetSessionStatusAsync(); + } + + public Task ClearRetainedApplicationMessagesAsync() + { + return _mqttServer.ClearRetainedApplicationMessagesAsync(); + } + + public Task> GetRetainedApplicationMessagesAsync() + { + return _mqttServer.GetRetainedApplicationMessagesAsync(); + } + + public Task PublishAsync(MqttApplicationMessage applicationMessage) + { + if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage)); + + return _mqttServer.PublishAsync(applicationMessage); + } + private void Publish(PythonDictionary parameters) { try diff --git a/Source/MQTTnet.Server/Startup.cs b/Source/MQTTnet.Server/Startup.cs index 8d9c3ee3..7135508e 100644 --- a/Source/MQTTnet.Server/Startup.cs +++ b/Source/MQTTnet.Server/Startup.cs @@ -5,12 +5,14 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore.Internal; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.OpenApi.Models; using Microsoft.Scripting.Utils; using MQTTnet.Server.Configuration; using MQTTnet.Server.Logging; using MQTTnet.Server.Mqtt; using MQTTnet.Server.Scripting; using MQTTnet.Server.Scripting.DataSharing; +using Swashbuckle.AspNetCore.SwaggerUI; namespace MQTTnet.Server { @@ -55,11 +57,28 @@ namespace MQTTnet.Server 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.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); + services.AddMvc() + .SetCompatibilityVersion(CompatibilityVersion.Version_2_2) + .AddJsonOptions(options => + { + options.SerializerSettings.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter()); + }); services.AddSingleton(ReadSettings()); @@ -79,6 +98,28 @@ namespace MQTTnet.Server services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + + services.AddSwaggerGen(c => + { + c.DescribeAllEnumsAsStrings(); + 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") + }, + }); + }); } private SettingsModel ReadSettings()