【用.NET5写Windows服务】四、托管服务和HTTP监听任务,实现监听HTTP请求和对外接口

默认创建的托管服务是一个1秒轮询的任务,而windows服务的很多应用场景需要一直监听的任务,此篇我们就改造一下默认创建的托管服务,使其能够一直监听。
引入 HttpListener 类,实现监听HTTP请求和对外接口,以及提供web端网页。

改造Worker.cs

重写BackgroundService.StartAsyncBackgroundService.StopAsyncBackgroundService.ExecuteAsync方法

public class Worker : BackgroundService
{
    private readonly ILogger<Worker> _logger;
    private readonly IConfiguration _configuration;

    public HostedServiceOptions HostedServiceOptions { get; private set; }

    public Worker(ILogger<Worker> logger, IConfiguration configuration)
    {
        _logger = logger;
        _configuration = configuration; //依赖注入IConfiguration
    }

    public override async Task StartAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("Worker starting at: {time}", DateTimeOffset.Now);

        HostedServiceOptions = _configuration.GetSection(HostedServiceOptions.HostedService).Get<HostedServiceOptions>(); //绑定并返回指定的类型

        await base.StopAsync(cancellationToken);
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        await Task.Run(async () =>
        {
            //++如果服务被停止,那么下面的IsCancellationRequested会返回true,我们就应该结束循环
            while (!stoppingToken.IsCancellationRequested)
            {
                _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
                await Task.Delay(HostedServiceOptions.WorkerInterval, stoppingToken); //设置托管任务轮询时间
            }
        }, stoppingToken);
    }

    public override async Task StopAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("Worker stopping at: {time}", DateTimeOffset.Now);

        await base.StopAsync(cancellationToken);
    }
}

实现监听HTTP请求和对外接口

引入 HTTP 协议侦听器

Worker.cs 中,添加 HTTP 协议侦听器。

        private readonly ILogger<Worker> _logger;
        private readonly IConfiguration _configuration;

        private static HttpListener _httpListener; //++声明一个静态的监听器

        public HostedServiceOptions HostedServiceOptions { get; private set; }

        public Worker(ILogger<Worker> logger, IConfiguration configuration)
        {
            _logger = logger;
            _configuration = configuration; //依赖注入IConfiguration
        }

在配置文件 appsettings.json 中添加 HTTP 协议侦听地址。

{
  ...
  "HostedService": {
    "WorkerInterval": 60000, //托管任务轮询时间
    "HttpListenerPrefixes": [ "http://+:51515/" ] //++ HTTP 协议侦听地址
  } 
}

HostedServiceOptions.cs 文件中添加对应的属性。

    public class HostedServiceOptions
    {
        public const string HostedService = "HostedService";

        public int WorkerInterval { get; set; }

        public List<string> HttpListenerPrefixes { get; set; } //++对应 HTTP 协议侦听地址
    }
监听HTTP请求

监听HTTP的请求,写在 Worker.cs 文件中。首先在 StartAsync 中初始化 HTTP 协议侦听器。

        public override async Task StartAsync(CancellationToken cancellationToken)
        {
            _logger.LogInformation("Worker starting at: {time}", DateTimeOffset.Now);

            HostedServiceOptions = _configuration.GetSection(HostedServiceOptions.HostedService).Get<HostedServiceOptions>(); //绑定并返回指定的类型

            InitializeHttpListener(); //++初始化 HTTP 协议侦听器

            await base.StopAsync(cancellationToken);
        }

        /// <summary>
        /// 初始化 HTTP 协议侦听器
        /// </summary>
        private void InitializeHttpListener()
        {
            try
            {
                if (!HttpListener.IsSupported)
                {
                    _logger.LogWarning("Windows XP SP2 or Server 2003 is required to use the HttpListener class.");
                    return;
                }

                if (HostedServiceOptions.HttpListenerPrefixes == null || HostedServiceOptions.HttpListenerPrefixes.Count == 0)
                {
                    throw new ArgumentException("prefixes");
                }

                if (_httpListener == null)
                {
                    _httpListener = new HttpListener();
                }

                foreach (var prefix in HostedServiceOptions.HttpListenerPrefixes)
                {
                    if (!_httpListener.Prefixes.Contains(prefix))
                    {
                        _httpListener.Prefixes.Add(prefix);
                    }
                }

                foreach (var httpListenerPrefix in _httpListener.Prefixes)
                {
                    if (!HostedServiceOptions.HttpListenerPrefixes.Contains(httpListenerPrefix))
                    {
                        _httpListener.Prefixes.Remove(httpListenerPrefix);
                    }
                }

                if (!_httpListener.IsListening)
                {
                    _httpListener.Start();
                }

                _httpListener.BeginGetContext(GetHttpListenerContext, null);

                _logger.LogDebug("Worker HttpListener Is Listening.");

            }
            catch (Exception exception)
            {
                _logger.LogError(exception, "Worker HttpListener catching.");
            }
            finally
            {
                _logger.LogDebug("Worker HttpListener finally.");
            }
        }

获取 HTTP 协议侦听器上下文。

        /// <summary>
        /// 获取 HTTP 协议侦听器上下文
        /// </summary>
        /// <param name="ar"></param>
        private void GetHttpListenerContext(IAsyncResult ar)
        {
            //继续异步监听
            _httpListener.BeginGetContext(GetHttpListenerContext, null);
            var id = Guid.NewGuid().ToString("N");
            _logger.LogDebug($"HttpListener recieve a request - {id}.");
            //获得context对象
            var context = _httpListener.EndGetContext(ar);
            var request = context.Request;
            var response = context.Response;
            if (request.Url != null)
            {
                _logger.LogDebug($"HttpListener request url - {request.Url.AbsoluteUri}");
            }
            如果是js的ajax请求,还可以设置跨域的ip地址与参数
            //context.Response.AppendHeader("Access-Control-Allow-Origin", "*"); //后台跨域请求,通常设置为配置文件
            //context.Response.AppendHeader("Access-Control-Allow-Headers", "ID,PW"); //后台跨域参数设置,通常设置为配置文件
            //context.Response.AppendHeader("Access-Control-Allow-Method", "post"); //后台跨域请求设置,通常设置为配置文件
            context.Response.ContentType = "text/plain;charset=UTF-8"; //返回纯文本格式,编码为UTF-8
            context.Response.AddHeader("Content-type", "text/plain"); //添加响应头信息
            context.Response.ContentEncoding = Encoding.UTF8;
            if (request.HttpMethod == "GET")
            {
                //处理客户端发送的GET请求并返回处理信息
                HttpListenerGetRequest(request, response);
            }
            else if (request.HttpMethod == "POST")
            {
                //处理客户端发送的POST请求并返回处理信息
                HttpListenerPostRequest(request, response);
            }

            _logger.LogDebug($"HttpListener return a response - {id}.");
        }

处理GET请求和POST请求。

        /// <summary>
        /// GET请求
        /// </summary>
        /// <param name="request"></param>
        /// <param name="response"></param>
        private void HttpListenerGetRequest(HttpListenerRequest request, HttpListenerResponse response)
        {
            try
            {
                if (request.Url != null && !string.IsNullOrWhiteSpace(request.Url.AbsolutePath))
                {
                    //注意地址的匹配全部转换成了小写
                    if (request.Url.AbsolutePath.ToLower().StartsWith("/logs"))
                    {
                        //TODO: output logs
                    }
                    else if (request.Url.AbsolutePath.ToLower().StartsWith("/api"))
                    {
                        switch (request.Url.AbsolutePath.ToLower())
                        {
                            default:
                                response.StatusCode = 404;
                                response.OutputStream.Close();
                                return;
                            case "/api/gethelloworld":
                                response.StatusCode = 200;
                                response.OutputStream.Write(Encoding.UTF8.GetBytes("Hello world!"));
                                response.OutputStream.Close();
                                break;
                        }
                        return;
                    }
                    else
                    {
                        //文件资源请求
                        var filePath = request.Url.AbsolutePath.ToLower();
                        filePath = "dist" + filePath;
                        filePath = !request.Url.AbsolutePath.Contains(".") ? filePath + "/index.html" : filePath;
                        filePath = AppDomain.CurrentDomain.BaseDirectory + filePath.Replace("/", Path.DirectorySeparatorChar.ToString());
                        if (File.Exists(filePath))
                        {
                            response.StatusCode = 200;
                            var extension = Path.GetExtension(filePath);
                            switch (extension)
                            {
                                default:
                                    response.ContentType = "";
                                    break;
                                case ".html":
                                case ".htm":
                                    response.ContentType = "text/html;charset=utf-8";
                                    break;
                                case ".js":
                                    response.ContentType = "application/javascript;charset=utf-8";
                                    break;
                                case ".css":
                                    response.ContentType = "text/css;charset=utf-8";
                                    break;
                                case ".png":
                                    response.ContentType = "image/png";
                                    break;
                                case ".jpg":
                                    response.ContentType = "image/jpeg";
                                    break;
                                case ".gif":
                                    response.ContentType = "image/gif";
                                    break;
                                case ".svg":
                                    response.ContentType = "image/svg+xml";
                                    break;
                                case ".swf":
                                    response.ContentType = "application/x-shockwave-flash";
                                    break;
                            }
                            var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
                            var fileStreamLength = (int)fileStream.Length;
                            var buffer = new byte[fileStreamLength];
                            fileStream.Read(buffer, 0, fileStreamLength);
                            fileStream.Close();
                            fileStream.Dispose();
                            response.ContentLength64 = fileStreamLength;
                            response.OutputStream.Write(buffer, 0, fileStreamLength);
                            response.OutputStream.Close();
                            return;
                        }
                    }
                }
            }
            catch (Exception exception)
            {
                response.StatusCode = 500;
                _logger.LogError(exception, "HttpListener response error.");
                response.OutputStream.Write(Encoding.UTF8.GetBytes("Internal Server Error."));
                response.OutputStream.Close();
                return;
            }
            response.StatusCode = 404;
            response.OutputStream.Close();
        }

        /// <summary>
        /// POST请求
        /// </summary>
        /// <param name="request"></param>
        /// <param name="response"></param>
        private void HttpListenerPostRequest(HttpListenerRequest request, HttpListenerResponse response)
        {
            try
            {
                if (request.Url != null && !string.IsNullOrWhiteSpace(request.Url.AbsolutePath))
                {
                    //注意地址的匹配全部转换成了小写
                    if (request.Url.AbsolutePath.ToLower().StartsWith("/api"))
                    {
                        switch (request.Url.AbsolutePath.ToLower())
                        {
                            default:
                                response.StatusCode = 404;
                                response.OutputStream.Close();
                                return;
                        }
                        return;
                    }
                }
                response.StatusCode = 404;
                response.OutputStream.Close();
            }
            catch (Exception exception)
            {
                response.StatusCode = 500;
                _logger.LogError(exception, "HttpListener response error.");
                response.OutputStream.Write(Encoding.UTF8.GetBytes("Internal Server Error."));
                response.OutputStream.Close();
            }
        }
调用对外接口 /api/gethelloworld

浏览器访问接口 http://localhost:51515/api/gethelloworld
/api/gethelloworld

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

公西雒

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值