From 004e4b0fc685f9281b3393c338ad8cdd6aeca21f Mon Sep 17 00:00:00 2001
From: SmallChi <564952747@qq.com>
Date: Wed, 21 Nov 2018 20:27:10 +0800
Subject: [PATCH] =?UTF-8?q?=E9=9B=86=E6=88=90=E8=BD=BB=E9=87=8F=E7=BA=A7we?=
 =?UTF-8?q?bapi=E6=8E=A5=E5=8F=A3?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../Configurations/JT808Configuration.cs      |   7 ++
 .../Dtos/JT808DefaultResultDto.cs             |  15 +++
 .../Handlers/JT808WebAPIServerHandler.cs      | 119 ++++++++++++++++++
 src/JT808.DotNetty/JT808.DotNetty.csproj      |   3 +-
 src/JT808.DotNetty/JT808DotnettyExtensions.cs |  16 +--
 src/JT808.DotNetty/JT808ServerHost.cs         |   8 +-
 src/JT808.DotNetty/JT808WebAPIServerHost.cs   |  82 ++++++++++++
 .../JT808WebHostBuilderKestrelExtensions.cs   |  41 ------
 8 files changed, 230 insertions(+), 61 deletions(-)
 create mode 100644 src/JT808.DotNetty/Dtos/JT808DefaultResultDto.cs
 create mode 100644 src/JT808.DotNetty/Handlers/JT808WebAPIServerHandler.cs
 create mode 100644 src/JT808.DotNetty/JT808WebAPIServerHost.cs
 delete mode 100644 src/JT808.DotNetty/JT808WebHostBuilderKestrelExtensions.cs

diff --git a/src/JT808.DotNetty/Configurations/JT808Configuration.cs b/src/JT808.DotNetty/Configurations/JT808Configuration.cs
index adbb104..4c73388 100644
--- a/src/JT808.DotNetty/Configurations/JT808Configuration.cs
+++ b/src/JT808.DotNetty/Configurations/JT808Configuration.cs
@@ -25,6 +25,13 @@ namespace JT808.DotNetty.Configurations
         public int WriterIdleTimeSeconds { get; set; } = 3600;
 
         public int AllIdleTimeSeconds { get; set; } = 3600;
+
+        /// <summary>
+        /// WebAPI服务
+        /// 默认828端口
+        /// </summary>
+        public int WebAPIPort { get; set; } = 828;
+
         /// <summary>
         /// 会话报时
         /// 默认5分钟
diff --git a/src/JT808.DotNetty/Dtos/JT808DefaultResultDto.cs b/src/JT808.DotNetty/Dtos/JT808DefaultResultDto.cs
new file mode 100644
index 0000000..58b18dc
--- /dev/null
+++ b/src/JT808.DotNetty/Dtos/JT808DefaultResultDto.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace JT808.DotNetty.Dtos
+{
+    public class JT808DefaultResultDto: JT808ResultDto<string>
+    {
+        public JT808DefaultResultDto()
+        {
+            Data = "Hello,JT808 WebAPI";
+            Code = 200;
+        }
+    }
+}
diff --git a/src/JT808.DotNetty/Handlers/JT808WebAPIServerHandler.cs b/src/JT808.DotNetty/Handlers/JT808WebAPIServerHandler.cs
new file mode 100644
index 0000000..f30e557
--- /dev/null
+++ b/src/JT808.DotNetty/Handlers/JT808WebAPIServerHandler.cs
@@ -0,0 +1,119 @@
+using DotNetty.Buffers;
+using DotNetty.Codecs.Http;
+using DotNetty.Common;
+using DotNetty.Common.Utilities;
+using DotNetty.Transport.Channels;
+using JT808.DotNetty.Dtos;
+using JT808.DotNetty.Interfaces;
+using Microsoft.Extensions.Logging;
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace JT808.DotNetty.Handlers
+{
+    /// <summary>
+    /// jt808 webapi服务
+    /// 请求量不大,只支持JSON格式
+    /// ref: dotnetty HttpServer
+    /// </summary>
+    internal class JT808WebAPIServerHandler : ChannelHandlerAdapter
+    {
+        private static readonly ThreadLocalCache Cache = new ThreadLocalCache();
+
+        sealed class ThreadLocalCache : FastThreadLocal<AsciiString>
+        {
+            protected override AsciiString GetInitialValue()
+            {
+                DateTime dateTime = DateTime.UtcNow;
+                return AsciiString.Cached($"{dateTime.DayOfWeek}, {dateTime:dd MMM yyyy HH:mm:ss z}");
+            }
+        }
+
+        private static readonly AsciiString TypeJson = AsciiString.Cached("application/json");
+        private static readonly AsciiString ServerName = AsciiString.Cached("JT808WebAPINetty");
+        private static readonly AsciiString ContentTypeEntity = HttpHeaderNames.ContentType;
+        private static readonly AsciiString DateEntity = HttpHeaderNames.Date;
+        private static readonly AsciiString ContentLengthEntity = HttpHeaderNames.ContentLength;
+        private static readonly AsciiString ServerEntity = HttpHeaderNames.Server;
+
+        volatile ICharSequence date = Cache.Value;
+
+        private readonly ILogger<JT808WebAPIServerHandler> logger;
+
+        private readonly IJT808SessionService jT808SessionService;
+
+        private readonly IJT808UnificationSendService jT808UnificationSendService;
+
+        public JT808WebAPIServerHandler(
+            IJT808SessionService jT808SessionService,
+            IJT808UnificationSendService  jT808UnificationSendService,
+            ILoggerFactory loggerFactory)
+        {
+            this.jT808SessionService = jT808SessionService;
+            this.jT808UnificationSendService = jT808UnificationSendService;
+            logger = loggerFactory.CreateLogger<JT808WebAPIServerHandler>();
+        }
+
+        public override void ChannelRead(IChannelHandlerContext ctx, object message)
+        {
+            if (message is IHttpRequest request)
+            {
+                try
+                {
+                    Process(ctx, request);
+                }
+                finally
+                {
+                    ReferenceCountUtil.Release(message);
+                }
+            }
+            else
+            {
+                ctx.FireChannelRead(message);
+            }
+        }
+
+        private void Process(IChannelHandlerContext ctx, IHttpRequest request)
+        {
+            string uri = request.Uri;
+            //switch (uri)
+            //{
+            //    //case "/json":
+            //    //    byte[] json = Encoding.UTF8.GetBytes(NewMessage().ToJsonFormat());
+            //    //    this.WriteResponse(ctx, Unpooled.WrappedBuffer(json), TypeJson, JsonClheaderValue);
+            //    //    break;
+            //    default:
+            //        var response = new DefaultFullHttpResponse(HttpVersion.Http11, HttpResponseStatus.NotFound, Unpooled.Empty, false);
+            //        ctx.WriteAndFlushAsync(response);
+            //        ctx.CloseAsync();
+            //        break;
+            //}
+            byte[] json = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(new JT808DefaultResultDto()));
+            this.WriteResponse(ctx, Unpooled.WrappedBuffer(json), TypeJson, json.Length);
+        }
+
+        private void WriteResponse(IChannelHandlerContext ctx, IByteBuffer buf, ICharSequence contentType, int contentLength)
+        {
+            // Build the response object.
+            var response = new DefaultFullHttpResponse(HttpVersion.Http11, HttpResponseStatus.OK, buf, false);
+            HttpHeaders headers = response.Headers;
+            headers.Set(ContentTypeEntity, contentType);
+            headers.Set(ServerEntity, ServerName);
+            headers.Set(DateEntity, this.date);
+            headers.Set(ContentLengthEntity, contentLength);
+            // Close the non-keep-alive connection after the write operation is done.
+            ctx.WriteAsync(response);
+        }
+
+        public override void ExceptionCaught(IChannelHandlerContext context, Exception exception)
+        {
+            string channelId = context.Channel.Id.AsShortText();
+            logger.LogError(exception, $"{channelId} {exception.Message}");
+            context.CloseAsync();
+        }
+
+        public override void ChannelReadComplete(IChannelHandlerContext context) => context.Flush();
+    }
+}
diff --git a/src/JT808.DotNetty/JT808.DotNetty.csproj b/src/JT808.DotNetty/JT808.DotNetty.csproj
index 608982c..11c8ee7 100644
--- a/src/JT808.DotNetty/JT808.DotNetty.csproj
+++ b/src/JT808.DotNetty/JT808.DotNetty.csproj
@@ -7,11 +7,10 @@
 
   <ItemGroup>
     <PackageReference Include="DotNetty.Codecs" Version="0.6.0" />
+    <PackageReference Include="DotNetty.Codecs.Http" Version="0.6.0" />
     <PackageReference Include="DotNetty.Handlers" Version="0.6.0" />
     <PackageReference Include="DotNetty.Transport.Libuv" Version="0.6.0" />
     <PackageReference Include="JT808" Version="1.1.0" />
-    <PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.1.3" />
-    <PackageReference Include="Microsoft.AspNetCore.Server.Kestrel.Core" Version="2.1.3" />
     <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="2.1.1" />
     <PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="2.1.1" />
     <PackageReference Include="Microsoft.Extensions.Options" Version="2.1.1" />
diff --git a/src/JT808.DotNetty/JT808DotnettyExtensions.cs b/src/JT808.DotNetty/JT808DotnettyExtensions.cs
index bbefa67..4951a61 100644
--- a/src/JT808.DotNetty/JT808DotnettyExtensions.cs
+++ b/src/JT808.DotNetty/JT808DotnettyExtensions.cs
@@ -24,24 +24,12 @@ namespace JT808.DotNetty
                 services.TryAddScoped<JT808ConnectionHandler>();
                 services.TryAddScoped<JT808Decoder>();
                 services.TryAddScoped<JT808ServerHandler>();
+                services.TryAddScoped<JT808WebAPIServerHandler>();
                 services.TryAddSingleton<IJT808SessionService, JT808SessionServiceDefaultImpl>();
                 services.TryAddSingleton<IJT808UnificationSendService, JT808UnificationSendServiceDefaultImpl>();
                 services.AddHostedService<JT808ServerHost>();
+                services.AddHostedService<JT808WebAPIServerHost>();
             });
         }
-
-        public static void UseJT808Host(this IServiceCollection  serviceDescriptors, HostBuilderContext hostContext)
-        {
-            serviceDescriptors.Configure<JT808Configuration>(hostContext.Configuration.GetSection("JT808Configuration"));
-            serviceDescriptors.TryAddSingleton<JT808SessionManager>();
-            serviceDescriptors.TryAddSingleton<JT808MsgIdHandlerBase, JT808MsgIdDefaultHandler>();
-            serviceDescriptors.TryAddSingleton<IJT808SourcePackageDispatcher, JT808SourcePackageDispatcherDefaultImpl>();
-            serviceDescriptors.TryAddScoped<JT808ConnectionHandler>();
-            serviceDescriptors.TryAddScoped<JT808Decoder>();
-            serviceDescriptors.TryAddScoped<JT808ServerHandler>();
-            serviceDescriptors.TryAddSingleton<IJT808SessionService, JT808SessionServiceDefaultImpl>();
-            serviceDescriptors.TryAddSingleton<IJT808UnificationSendService, JT808UnificationSendServiceDefaultImpl>();
-            serviceDescriptors.AddHostedService<JT808ServerHost>();
-        }
     }
 }
\ No newline at end of file
diff --git a/src/JT808.DotNetty/JT808ServerHost.cs b/src/JT808.DotNetty/JT808ServerHost.cs
index 4ae3e94..1674583 100644
--- a/src/JT808.DotNetty/JT808ServerHost.cs
+++ b/src/JT808.DotNetty/JT808ServerHost.cs
@@ -21,9 +21,9 @@ using System.Threading.Tasks;
 
 namespace JT808.DotNetty
 {
-    public class JT808ServerHost : IHostedService
+    internal class JT808ServerHost : IHostedService
     {
-        public IServiceProvider Provider { get; }
+        private readonly IServiceProvider serviceProvider;
         private readonly JT808Configuration configuration;
         private readonly ILogger<JT808ServerHost> logger;
         private DispatcherEventLoopGroup bossGroup;
@@ -35,7 +35,7 @@ namespace JT808.DotNetty
             ILoggerFactory loggerFactory,
             IOptions<JT808Configuration> jT808ConfigurationAccessor)
         {
-            Provider = provider;
+            serviceProvider = provider;
             configuration = jT808ConfigurationAccessor.Value;
             logger=loggerFactory.CreateLogger<JT808ServerHost>();
         }
@@ -59,7 +59,7 @@ namespace JT808.DotNetty
                .ChildHandler(new ActionChannelInitializer<IChannel>(channel =>
                {
                    IChannelPipeline pipeline = channel.Pipeline;
-                   using(var scope= Provider.CreateScope())
+                   using (var scope = serviceProvider.CreateScope())
                    {
                        channel.Pipeline.AddLast("systemIdleState", new IdleStateHandler(
                                                 configuration.ReaderIdleTimeSeconds,
diff --git a/src/JT808.DotNetty/JT808WebAPIServerHost.cs b/src/JT808.DotNetty/JT808WebAPIServerHost.cs
new file mode 100644
index 0000000..6e0c39c
--- /dev/null
+++ b/src/JT808.DotNetty/JT808WebAPIServerHost.cs
@@ -0,0 +1,82 @@
+using DotNetty.Codecs.Http;
+using DotNetty.Transport.Bootstrapping;
+using DotNetty.Transport.Channels;
+using DotNetty.Transport.Libuv;
+using JT808.DotNetty.Configurations;
+using JT808.DotNetty.Handlers;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace JT808.DotNetty
+{
+    /// <summary>
+    /// 集成一个webapi服务
+    /// </summary>
+    internal class JT808WebAPIServerHost : IHostedService
+    {
+        private readonly IServiceProvider serviceProvider;
+        private readonly JT808Configuration configuration;
+        private readonly ILogger<JT808WebAPIServerHost> logger;
+        private DispatcherEventLoopGroup bossGroup;
+        private WorkerEventLoopGroup workerGroup;
+        private IChannel bootstrapChannel;
+
+        public JT808WebAPIServerHost(
+            IServiceProvider provider,
+            ILoggerFactory loggerFactory,
+            IOptions<JT808Configuration> jT808ConfigurationAccessor)
+        {
+            serviceProvider = provider;
+            configuration = jT808ConfigurationAccessor.Value;
+            logger = loggerFactory.CreateLogger<JT808WebAPIServerHost>();
+        }
+
+        public Task StartAsync(CancellationToken cancellationToken)
+        {
+            bossGroup = new DispatcherEventLoopGroup();
+            workerGroup = new WorkerEventLoopGroup(bossGroup, 1);
+            ServerBootstrap bootstrap = new ServerBootstrap();
+            bootstrap.Group(bossGroup, workerGroup);
+            bootstrap.Channel<TcpServerChannel>();
+            if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)
+                || RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+            {
+                bootstrap
+                    .Option(ChannelOption.SoReuseport, true)
+                    .ChildOption(ChannelOption.SoReuseaddr, true);
+            }
+            bootstrap
+                    .Option(ChannelOption.SoBacklog, 8192)
+                    .ChildHandler(new ActionChannelInitializer<IChannel>(channel =>
+                    {
+                        IChannelPipeline pipeline = channel.Pipeline;
+                        using (var scope = serviceProvider.CreateScope())
+                        {
+                            pipeline.AddLast("encoder", new HttpResponseEncoder());
+                            pipeline.AddLast("decoder", new HttpRequestDecoder(4096, 8192, 8192, false));
+                            pipeline.AddLast("jt808webapihandler", scope.ServiceProvider.GetRequiredService<JT808WebAPIServerHandler>());
+                        }
+                    }));
+            logger.LogInformation($"WebAPI Server start at {IPAddress.Any}:{configuration.WebAPIPort}.");
+            return bootstrap.BindAsync(configuration.WebAPIPort).ContinueWith(i => bootstrapChannel = i.Result);
+        }
+
+        public async Task StopAsync(CancellationToken cancellationToken)
+        {
+            await bootstrapChannel.CloseAsync();
+            var quietPeriod = configuration.QuietPeriodTimeSpan;
+            var shutdownTimeout = configuration.ShutdownTimeoutTimeSpan;
+            await workerGroup.ShutdownGracefullyAsync(quietPeriod, shutdownTimeout);
+            await bossGroup.ShutdownGracefullyAsync(quietPeriod, shutdownTimeout);
+        }
+    }
+}
diff --git a/src/JT808.DotNetty/JT808WebHostBuilderKestrelExtensions.cs b/src/JT808.DotNetty/JT808WebHostBuilderKestrelExtensions.cs
deleted file mode 100644
index dbcfc73..0000000
--- a/src/JT808.DotNetty/JT808WebHostBuilderKestrelExtensions.cs
+++ /dev/null
@@ -1,41 +0,0 @@
-using Microsoft.AspNetCore.Hosting;
-using Microsoft.AspNetCore.Server.Kestrel.Core;
-using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
-using Microsoft.Extensions.Hosting;
-using Microsoft.Extensions.DependencyInjection.Abstractions;
-using System;
-using System.Collections.Generic;
-using System.Text;
-using Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets;
-using Microsoft.Extensions.DependencyInjection.Extensions;
-using Microsoft.Extensions.Options;
-using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.AspNetCore.Hosting.Server;
-
-namespace JT808.DotNetty
-{
-    public static class JT808WebHostBuilderKestrelExtensions
-    {
-        /// <summary>
-        /// Specify Kestrel as the server to be used by the web host.
-        /// </summary>
-        /// <param name="hostBuilder">
-        /// The Microsoft.AspNetCore.Hosting.IWebHostBuilder to configure.
-        /// </param>
-        /// <returns>
-        /// The Microsoft.AspNetCore.Hosting.IWebHostBuilder.
-        /// </returns>
-        public static IHostBuilder UseKestrel(this IHostBuilder hostBuilder, Action<KestrelServerOptions> options)
-        {
-            return hostBuilder.ConfigureServices((context,services) =>
-            {
-                services.Configure(options);
-                // Don't override an already-configured transport
-                services.TryAddSingleton<ITransportFactory, SocketTransportFactory>();
-                services.AddTransient<IConfigureOptions<KestrelServerOptions>, KestrelServerOptionsSetup>();
-                services.AddSingleton<IServer, KestrelServer>();
-            });
-        }
-    }
-}