基於surging 的木舟平臺如何透過HTTP網路元件接入裝置

fanly11發表於2024-11-05

一、概述

上篇文章介紹了木舟如何上傳模組熱部署,那麼此篇文章將介紹如何利用HTTP網路元件接入裝置,那麼有些人會問木舟又是什麼,是什麼架構為基礎,能做什麼呢?

木舟 (Kayak) 是什麼?

木舟(Kayak)是基於.NET6.0軟體環境下的surging微服務引擎進行開發的, 平臺包含了微服務和物聯網平臺。支援非同步和響應式程式設計開發,功能包含了物模型,裝置,產品,網路元件的統一管理和微服務平臺下的註冊中心,服務路由,模組,中間服務等管理。還有多協議適配(TCP,MQTT,UDP,CoAP,HTTP,Grpc,websocket,rtmp,httpflv,webservice,等),透過靈活多樣的配置適配能夠接入不同廠家不同協議等裝置。並且透過裝置告警,訊息通知,資料視覺化等功能。能夠讓你能快速建立起微服務物聯網平臺系統。

那麼下面就為大家介紹如何從建立元件、協議、裝置閘道器,裝置到裝置閘道器接入,再到裝置資料上報,把整個流程透過此篇文章進行闡述。

二、網路元件

1.編輯建立HTTP協議的網路元件,可以選擇共享配置和獨立配置(獨立配置是叢集模式),然後可以選擇開啟swagger和webservice.

開啟成功後,可以看看swagger 是否可以訪問

又或者是訪問一下中間服務,以上篇文章上傳的Testapi 模組為例:

三、自定義協議

  • 如何建立自定義協議模組

如果是網路程式設計開發,必然會涉及到協議報文的編碼解碼處理,那麼對於平臺也是做到了靈活處理,首先是協議模組建立,透過以下程式碼看出協議模組可以新增協議說明md文件, 身份鑑權處理,HTTP路由,訊息編解碼,後設資料配置。下面一一介紹如何進行編寫

  public class Demo5ProtocolSupportProvider : ProtocolSupportProvider
    {
        public override IObservable<ProtocolSupport> Create(ProtocolContext context)
        {
   var support = new ComplexProtocolSupport();
support.Id = "demo5";
support.Name = "演示協議5";
support.Description = "演示協議5"; support.AddDocument(MessageTransport.Http,
"Document/document-http.md");     support.AddAuthenticator(MessageTransport.Http, new Demo5Authenticator());      support.AddRoutes(MessageTransport.Http, new List<BasicMessageCodec>() {    BasicMessageCodec.DeviceOnline,    BasicMessageCodec.ReportProperty,   BasicMessageCodec.WriteProperty,   BasicMessageCodec.ReadProperty, BasicMessageCodec.Event }.Select(p => HttpDescriptor.Instance(p.Pattern) .GroupName(p.Route.GroupName()) .HttpMethod(p.Route.HttpMethod()) .Path(p.Pattern) .ContentType(MediaType.ToString(MediaType.ApplicationJson)) .Description(p.Route.Description()) .Example(p.Route.Example()) ).ToList()); support.AddMessageCodecSupport(MessageTransport.Http, () => Observable.Return(new HttpDeviceMessageCodec())); support.AddConfigMetadata(MessageTransport.Http, _httpConfig); return Observable.Return(support); } }

1. 新增協議說明文件如程式碼: support.AddDocument(MessageTransport.Http, "Document/document-http.md");,文件僅支援 markdown檔案,如下所示

### 使用HTTP推送裝置資料

上報屬性例子: 

POST /{productId}/{deviceId}/properties/report
Authorization:{產品或者裝置中配置的Token}
Content-Type: application/json

{
 "properties":{
   "temp":11.5
 }
}

上報事件例子:

POST /{productId}/{deviceId}/event/{eventId}
Authorization:{產品或者裝置中配置的Token}
Content-Type: application/json

{
 "data":{
   "createtime": ""
 }
}

2. 新增身份鑑權如程式碼: support.AddAuthenticator(MessageTransport.Http, new Demo5Authenticator()) ,自定義身份鑑權Demo5Authenticator 程式碼如下:

       public class Demo5Authenticator : IAuthenticator
       {
           public IObservable<AuthenticationResult> Authenticate(IAuthenticationRequest request, IDeviceOperator deviceOperator)
           {
               var result = Observable.Return<AuthenticationResult>(default);
               if (request is DefaultAuthRequest)
               {
                   var authRequest = request as DefaultAuthRequest;
                   deviceOperator.GetConfig(authRequest.GetTransport()==MessageTransport.Http?"token": "key").Subscribe(  config =>
                   {
                       var password = config.Convert<string>();
                       if (authRequest.Password.Equals(password))
                       {
                           result= result.Publish(AuthenticationResult.Success(authRequest.DeviceId));
                       }
                       else
                       {
                           result= result.Publish(AuthenticationResult.Failure(StatusCode.CUSTOM_ERROR, "驗證失敗,密碼錯誤"));
                       }
                   });
               }
               else
               result = Observable.Return<AuthenticationResult>(AuthenticationResult.Failure(StatusCode.CUSTOM_ERROR, "不支援請求引數型別"));
               return result;
           }

           public IObservable<AuthenticationResult> Authenticate(IAuthenticationRequest request, IDeviceRegistry registry)
           {
               var result = Observable.Return<AuthenticationResult>(default);
               var authRequest = request as DefaultAuthRequest;
               registry
                 .GetDevice(authRequest.DeviceId)
                 .Subscribe(async p => {

                    var config=  await p.GetConfig(authRequest.GetTransport() == MessageTransport.Http ? "token" : "key");
                     var password= config.Convert<string>();
                    if(authRequest.Password.Equals(password))
                     {
                         result= result.Publish(AuthenticationResult.Success(authRequest.DeviceId));
                     }
                     else
                     {
                         result= result.Publish(AuthenticationResult.Failure(StatusCode.CUSTOM_ERROR, "驗證失敗,密碼錯誤"));
                     }
                 });
               return result;
           }
       }

3. 新增Http路由程式碼support.AddRoutes,那麼如何配置呢,程式碼如下:

    public static BasicMessageCodec ReportProperty =>
 new BasicMessageCodec("/*/properties/report", typeof(ReadPropertyMessage), route => route.GroupName("屬性上報")
                     .HttpMethod("Post")
                     .Description("上報物模型屬性資料")
                     .Example("{\"properties\":{\"屬性ID\":\"屬性值\"}}"));

4.新增訊息編解碼程式碼 support.AddMessageCodecSupport(MessageTransport.Http, () => Observable.Return(new HttpDeviceMessageCodec())), 可以自定義編解碼,HttpDeviceMessageCodec程式碼如下:

  public class HttpDeviceMessageCodec : DeviceMessageCodec
  {
      private readonly MessageTransport _transport;

      public HttpDeviceMessageCodec() : this(MessageTransport.Http)
      {
      }

      private static DefaultHttpResponseMessage Unauthorized(String msg)
      {
          return new DefaultHttpResponseMessage()
                  .ContentType(MediaType.ApplicationJson)
                  .Body("{\"success\":false,\"code\":\"unauthorized\",\"message\":\"" + msg + "\"}")
                  .Status(HttpStatus.AuthorizationFailed);
      }

      private static DefaultHttpResponseMessage BadRequest()
      {
          return new DefaultHttpResponseMessage()
                  .ContentType(MediaType.ApplicationJson)
                  .Body("{\"success\":false,\"code\":\"bad_request\"}")
                  .Status(HttpStatus.RequestError);
      }

      public HttpDeviceMessageCodec(MessageTransport transport)
      {
          _transport = transport;
      }
      public override IObservable<IDeviceMessage> Decode(MessageDecodeContext context)
      {
          if (context.GetMessage() is HttpRequestMessage)
          {
              return DecodeHttpRequestMessage(context);
          }
          return Observable.Return<IDeviceMessage>(default);
      }


      public override  IObservable<IEncodedMessage> Encode(MessageEncodeContext context)
      {
          return Observable.Return<IEncodedMessage>(default);
      }



      private IObservable<IDeviceMessage> DecodeHttpRequestMessage(MessageDecodeContext context)
      {
          var result = Observable.Return<IDeviceMessage>(default);
          var message = (HttpExchangeMessage)context.GetMessage();

          Header? header = message.Request.GetHeader("Authorization");
          if (header == null || header.Value == null || header.Value.Length == 0)
          {
              message
                   .Response(Unauthorized("Authorization header is required")).ToObservable()
                   .Subscribe(p => result = result.Publish(default));

              return result;
          }
          var httpToken = header.Value[0];

          var paths = message.Path.Split("/");
          if (paths.Length == 0)
          {
              message.Response(BadRequest()).ToObservable()
                 .Subscribe(p => result = result.Publish(default));
              return result;
          }
          String deviceId = paths[1];
          context.GetDevice(deviceId).Subscribe(async deviceOperator =>
          {
              var config = deviceOperator==null?null: await deviceOperator.GetConfig("token");
              var token = config?.Convert<string>();
              if (token == null || !httpToken.Equals(token))
              {
                  await message
                       .Response(Unauthorized("Device not registered or authentication failed"));
              }
              else
              {
                  var deviceMessage = await DecodeBody(message, deviceId);
                  if (deviceMessage != null)
                  {
                      await message.Success("{\"success\":true,\"code\":\"success\"}");
                      result = result.Publish(deviceMessage);
                  }
                  else
                  {
                      await message.Response(BadRequest());
                  }
              }
          });
          return result;
      }

      private async Task<IDeviceMessage> DecodeBody(HttpExchangeMessage message,string deviceId)
      {

          byte[] body = new byte[message.Payload.ReadableBytes];
          message.Payload.ReadBytes(body);
          var deviceMessage = await TopicMessageCodec.Dodecode(message.Path, body);
          deviceMessage.DeviceId = deviceId;
          return deviceMessage;
      }
  }

5.新增後設資料配置程式碼 support.AddConfigMetadata(MessageTransport.Http, _httpConfig); _httpConfig程式碼如下

        private readonly DefaultConfigMetadata _httpConfig = new DefaultConfigMetadata(
        "Http認證配置"
        , "token為http認證令牌")
        .Add("token", "token", "http令牌", StringType.Instance);
  • 如何載入協議模組,協議模組包含了協議模組支援新增引用載入和上傳熱部署載入。

引用載入模組

上傳熱部署協議模組

四、裝置閘道器

建立裝置閘道器

五、產品管理

以下是新增產品。

裝置接入

六、裝置管理

新增裝置

HTTP認證配置

建立告警閾值

七、測試

利用Postman 進行測試,以呼叫http://127.0.0.1:168/{productid}/{deviceid}/properties/report 為例,Authorization設定:123456

1.正常資料測試

2. 如果是選用Get方式呼叫,會因為找不到ServiceRoute而返回錯誤。

3. 把Authorization改成1111,會返回錯誤Device not registered or authentication failed,從而上報資料失敗

以上上傳的資料可以在裝置資訊-》執行狀態中檢視

告警資訊可以在超臨界資料中檢視

七、總結

以上是基於HTTP網路元件裝置接入,現有平臺網路元件可以支援TCP,MQTT,UDP,CoAP,HTTP,Grpc,websocket,rtmp,httpflv,webservice,tcpclient, 而裝置接入支援TCP,UDP,HTTP網路元件,後面會陸續新增支援所有網路元件接入,後面我也會陸續介紹其它網路元件裝置接入 , 然後定於11月20日釋出1.0測試版平臺。也請大家到時候關注捧場。

相關文章