thingsboard官网: https://thingsboard.io/

thingsboard GitHub: https://github.com/thingsboard/thingsboard

thingsboard提供的体验地址: http://demo.thingsboard.io/

BY Thingsboard team

以下内容是在原文基础上演绎的译文。除非另行注明,页面上所有内容采用知识共享-署名(CC BY 2.5 AU)协议共享。

原文地址: ThingsBoard API参考:HTTP设备API


HTTP

协议介绍

HTTP是可用于IoT应用程序的通用网络协议。您可以在此处找到有关HTTP的更多信息。HTTP协议是基于TCP的,并使用请求 - 响应模型。当然它的缺点也极为明显,HTTP对于嵌入式设备来说太重了,也不灵活。

协议特点

  1. 支持客户/服务器模式。

  2. 简单快速: 客户向服务器请求服务时,只需传送请求方法和路径。请求方法常用的有GET、PUT、POST。每种方法规定了客户与服务器联系的类型不同。由于HTTP协议简单,使得HTTP服务器的程序规模小,因此通信速度很快。

  3. 灵活: HTTP允许传输任意类型的数据对象。正在传输的类型由Content-Type加以标记。

  4. 无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。

  5. 无状态:HTTP协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。

客户端设置

Thingsboard的HTTP传输协议架构

因为Thingsboard最新release,是基于微服务架构,不利用单独理解代码。

Thingsboard CoAP设备传输协议源代码:https://github.com/thingsboard/thingsboard/tree/release-2.0/transport/http

本文基于上面源代码后,剔除相关的安全验证和处理之后搭建简易的讲解项目:

https://github.com/sanshengshui/IOT-Technical-Guide/tree/master/IOT-Guide-HTTP


Spring Boot框架

Thingsboard的HTTP设备传输协议是基于Spring Boot

Spring Boot 是 Spring 的子项目,正如其名字,提供 Spring 的引导( Boot )的功能。

通过 Spring Boot ,我们开发者可以快速配置 Spring 项目,引入各种 Spring MVC、Spring Transaction、Spring AOP、MyBatis 等等框架,而无需不断重复编写繁重的 Spring 配置,降低了 Spring 的使用成本。

犹记当年,Spring XML 为主的时代,大晚上各种搜索 Spring 的配置,苦不堪言。现在有了 Spring Boot 之后,生活真美好。

Spring Boot 提供了各种 Starter 启动器,提供标准化的默认配置。例如:

并且,Spring Boot 基本已经一统 Java 项目的开发,大量的开源项目都实现了其的 Starter 启动器。例如:

项目解读

项目结构

 ├── java
│ └── com
│ └── sanshengshui
│ └── http
│ ├── controller
│ │ └── DeviceApiController.java // 设备传输API接口
│ ├── HttpApiServer.java //项目启动主类
│ └── quota //API限制类包
│ ├── AbstractQuotaService.java //抽象限制服务类
│ ├── Clock.java //时钟类
│ ├── host
│ │ ├── HostIntervalRegistryCleaner.java //主机API清理器
│ │ ├── HostIntervalRegistryLogger.java  //主机API记录器
│ │ ├── HostRequestIntervalRegistry.java //主机API请求注册表
│ │ ├── HostRequestLimitPolicy.java  //主机API请求限制条件
│ │ └── HostRequestsQuotaService.java  //主机请求限制开关
│ ├── inmemory
│ │ ├── IntervalCount.java  //间歇计数
│ │ ├── IntervalRegistryCleaner.java //时间间隔内注册表清理器
│ │ ├── IntervalRegistryLogger.java  //时间间隔内注册表记录器
│ │ └── KeyBasedIntervalRegistry.java  //基础API请求逻辑
│ ├── QuotaService.java //限制服务类
│ └── RequestLimitPolicy.java //请求限制策略
└── resources
└── application.yml
 ​

项目代码

引入依赖

 <dependencies>
<dependency>
<groupId>com.sanshengshui</groupId>
<artifactId>IOT-Guide-TSL</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
</dependencies>
  • Spring Boot提供的web框架基于Tomcat,可以通过引入spring-boot-starter-web来配置依赖关系。

  • commons-lang3guava用于API请求限制服务。

参数配置

 server:
port: 8080


http:
request_timeout: "${HTTP_REQUEST_TIMEOUT:60000}"


quota:
host:
limit: "${QUOTA_HOST_LIMIT:10}"
intervalMs: "${QUOTA_HOST_INTERVAL_MS:60000}"
ttlMs: "${QUOTA_HOST_TTL_MS:60000}"
cleanPeriodMs: "${QUOTA_HOST_CLEAN_PERIOD_MS:300000}"
enabled: "${QUOTA_HOST_ENABLED:true}"
whitelist: "${QUOTA_HOST_WHITELIST:localhost,127.0.0.1}"
blacklist: "${QUOTA_HOST_BLACKLIST:}"
log:
topSize: 10
intervalMin: 2
  • server.port: 8080: 服务器启动绑定的端口,缺省情况下是:8080。

  • http.request_timeout : 请求超时时间,此处设定为60000。

  • quota.host.limitquota.host.intervalMs: 分别为API请求限额数和单位时间。此处为了验证方便,设定为10次和60s,即60s内API请求限额数为10次。

  • quota.host.cleanPeriodMsquota.host.ttlMs : 分别为清理周期时间和TTL时间。

  • quota.host.enabledquota.host.whitelistquota.host.blacklist分别表示API请求开关、白名单及黑名单。

  • quota.host.log.topSizequota.host.log.intervalMin: 指的是高速缓存中的(近似)最大条目数和间隔时间。

API限制服务类

KeyBasedIntervalRegistry:基础API请求逻辑

 
 package com.sanshengshui.http.quota.inmemory;

import com.google.common.collect.Sets;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
* @author james mu
* @date 2019/8/10 下午4:50
*/
@Slf4j
public class KeyBasedIntervalRegistry {

private final Map<String, IntervalCount> hostCounts = new ConcurrentHashMap<>();
private final long intervalDurationMs;
private final long ttlMs;
private final Set<String> whiteList;
private final Set<String> blackList;

public KeyBasedIntervalRegistry(long intervalDurationMs, long ttlMs, String whiteList, String blackList, String name) {
this.intervalDurationMs = intervalDurationMs;
this.ttlMs = ttlMs;
this.whiteList = Sets.newHashSet(StringUtils.split(whiteList, ','));
this.blackList = Sets.newHashSet(StringUtils.split(blackList, ','));

}

private void validate(String name) {
if (ttlMs < intervalDurationMs) {
log.warn("TTL for {} IntervalRegistry [{}] smaller than interval duration [{}]", name, ttlMs, intervalDurationMs);
}
log.info("Start {} KeyBasedIntervalRegistry with whitelist {}", name, whiteList);
log.info("Start {} KeyBasedIntervalRegistry with blacklist {}", name, blackList);
}

public long tick(String clientHostId) {
IntervalCount intervalCount = hostCounts.computeIfAbsent(clientHostId, s -> new IntervalCount(intervalDurationMs));
long currentCount = intervalCount.resetIfExpiredAndTick();
if (whiteList.contains(clientHostId)) {
return 0;
} else if (blackList.contains(clientHostId)) {
return Long.MAX_VALUE;
}
return currentCount;
}

public void clean() {
hostCounts.entrySet().removeIf(entry -> entry.getValue().silenceDuration() > ttlMs);
}

public Map<String, Long> getContent() {
return hostCounts.entrySet().stream()
.collect(
Collectors.toMap(
Map.Entry:: getKey,
interval -> interval.getValue().getCount()
)
);
}
}
  • validate(string name): 要求ttlMs<intervalDurationMs,并打印出API请求的黑名单和白名单。

  • 第42行通过computeIfAbsent函数对map中不存在key时的处理,在这里通过新建intervalCount(intervalDurationMs)的方式来处理。

  • 第43行通过intervalCount的resetIfExpiredAndTick()对时间间隔内进行计数。

  • 第44-48行通过判断API请求客户端地址是否在黑白名单中,如果在白名单,返回0,如果在黑名单中,返回Long.MAX_VALUE

  • clean()为通过时间间隔内是否大于ttlMs来过滤集合中的元素。

  • getContent()为遍历hostCounts中的客户端地址的IntervalCount。

IntervalCount: 间歇时间内计数

  package com.sanshengshui.http.quota.inmemory;

import com.sanshengshui.http.quota.Clock;

import java.util.concurrent.atomic.LongAdder;

/**
* @author james mu
* @date 19-8-9 下午16:50
*/
public class IntervalCount {

private final LongAdder addr = new LongAdder();
private final long intervalDurationMs;
private volatile long startTime;
private volatile long lastTickTime;

public IntervalCount(long intervalDurationMs) {
this.intervalDurationMs = intervalDurationMs;
startTime = Clock.millis();
}

//计数或时间过期后重置时间
public long resetIfExpiredAndTick(){
if (isExpired()){
reset();
}
tick();
return addr.sum();
}

//计算已过时间
public long silenceDuration() {
return Clock.millis() - lastTickTime;
}

public long getCount() {
return addr.sum();
}

//计数操作,累加一
private void tick() {
addr.add(1);
lastTickTime = Clock.millis();
}

//重置计数时间
private void reset() {
addr.reset();
lastTickTime = Clock.millis();
}

//判断间隔时间是否失效
private boolean isExpired() {
return (Clock.millis() - startTime) > intervalDurationMs;
}

}

剩下的处理类,留给读者去自己研究了!

  1. 主机API清理器: HostIntervalRegistryCleaner注入quota.host.cleanPeriodMs并继承抽象类IntervalRegistryCleaner

  2. 主机API记录器: HostIntervalRegistryLogger注入quota.host.log.topSizequota.host.log.intervalMin并继承IntervalRegistryLogger

  3. 主机API请求注册表: HostRequestIntervalRegistry注入quota.host.intervalMsquota.host.ttlMsquota.host.whitelistquota.host.blacklist并继承KeyBasedIntervalRegistry

  4. 主机API请求限制条件: HostRequestLimitPolicy注入quota.host.limit并继承RequestLimitPolicy

  5. 主机请求限制开关: HostRequestsQuotaService注入quota.host.enabled并继承AbstractQuotaService

属性API和遥测数据上传API

 
 @RestController
@RequestMapping("/api/v1")
@Slf4j
public class DeviceApiController { @Autowired(required = false)
private HostRequestsQuotaService quotaService;//API限制服务类 @RequestMapping(value = "/attributes",method = RequestMethod.POST)
public DeferredResult<ResponseEntity> postDeviceAttributes(
@RequestBody String json, HttpServletRequest request) {
DeferredResult<ResponseEntity> responseWriter = new DeferredResult<ResponseEntity>();
if (quotaExceeded(request, responseWriter)) {
return responseWriter;
}
responseWriter.setResult(new ResponseEntity<>(HttpStatus.ACCEPTED));
Set<AttributeKvEntry> attributeKvEntrySet = JsonConverter.convertToAttributes(new JsonParser().parse(json)).getAttributes();
for (AttributeKvEntry attributeKvEntry : attributeKvEntrySet){
System.out.println("属性名="+attributeKvEntry.getKey()+" 属性值="+attributeKvEntry.getValueAsString());
}
return responseWriter;
} @RequestMapping(value = "/telemetry",method = RequestMethod.POST)
public DeferredResult<ResponseEntity> postTelemetry(@RequestBody String json, HttpServletRequest request){
DeferredResult<ResponseEntity> responseWriter = new DeferredResult<ResponseEntity>();
if (quotaExceeded(request, responseWriter)) {
return responseWriter;
}
responseWriter.setResult(new ResponseEntity(HttpStatus.ACCEPTED));
Map<Long, List<KvEntry>> telemetryMaps = JsonConverter.convertToTelemetry(new JsonParser().parse(json)).getData();
for (Map.Entry<Long,List<KvEntry>> entry : telemetryMaps.entrySet()) {
System.out.println("key= " + entry.getKey());
for (KvEntry kvEntry: entry.getValue()) {
System.out.println("属性名="+kvEntry.getKey()+ " 属性值="+kvEntry.getValueAsString());
}
}
return responseWriter;
}
}

项目演示

遥测上传API

要将遥测数据发布到服务器节点,请将POST请求发送到以下URL:

 http://localhost:8080/api/v1/telemetry

最简单的支持数据格式是:

 {"key1":"value1", "key2":"value2"}

要么

 [{"key1":"value1"}, {"key2":"value2"}]

请注意,在这种情况下,服务器端时间戳将分配给上传的数据!

如果您的设备能够获取客户端时间戳,您可以使用以下格式:

 
{"ts":1451649600512, "values":{"key1":"value1", "key2":"value2"}}

在上面的示例中,我们假设“1451649600512”是具有毫秒精度的unix时间戳。例如,值'1451649600512'对应于'Fri,2016年1月1日12:00:00.512 GMT'

例子:

 curl -v -X POST -d "{"stringKey":"value1", "booleanKey":true, "doubleKey":42.0, "longKey":73}" http://localhost:8080/api/v1/telemetry --header "Content-Type:application/json"
C:\Users\james>curl -v -X POST -d "{"stringKey":"value1", "booleanKey":true, "doubleKey":42.0, "longKey":73}" http://localhost:8080/api/v1/telemetry --header "Content-Type:application/json"
Note: Unnecessary use of -X or --request, POST is already inferred.
* Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> POST /api/v1/telemetry HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.55.1
> Accept: */*
> Content-Type:application/json
> Content-Length: 63
>
* upload completely sent off: 63 out of 63 bytes
< HTTP/1.1 202
< Content-Length: 0
< Date: Sun, 18 Aug 2019 16:16:07 GMT
<
* Connection #0 to host localhost left intact

结果:

 key= 1566144967139
属性名=stringKey 属性值=value1
属性名=booleanKey 属性值=true
属性名=doubleKey 属性值=42.0
属性名=longKey 属性值=73

属性API

属性API允许设备

  • 将客户端设备属性上载到服务器。

  • 从服务器请求客户端和共享设备属性。

将属性更新发布到服务器

要将客户端设备属性发布到ThingsBoard服务器节点,请将POST请求发送到以下URL:

http://localhost:8080/api/v1/attributes
 例子:
 
curl -v -X POST -d "{"stringKey":"value1", "booleanKey":true, "doubleKey":42.0, "longKey":73}" http://localhost:8080/api/v1/attributes --header "Content-Type:application/json"
C:\Users\james>curl -v -X POST -d "{"stringKey":"value1", "booleanKey":true, "doubleKey":42.0, "longKey":73}" http://localhost:8080/api/v1/attributes --header "Content-Type:application/json"
Note: Unnecessary use of -X or --request, POST is already inferred.
* Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> POST /api/v1/attributes HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.55.1
> Accept: */*
> Content-Type:application/json
> Content-Length: 63
>
* upload completely sent off: 63 out of 63 bytes
< HTTP/1.1 202
< Content-Length: 0
< Date: Sun, 18 Aug 2019 16:21:00 GMT
<
* Connection #0 to host localhost left intact

结果:

 属性名=stringKey 属性值=value1
属性名=booleanKey 属性值=true
属性名=doubleKey 属性值=42.0
属性名=longKey 属性值=73

API限额服务

为了演示方便,我们设置60s内最多API请求测试为10次,现在我们使用遥测上传API连续发起接口调用,我们会发现如下的情况出现:

 属性名=longKey 属性值=73
属性名=stringKey 属性值=value1
属性名=booleanKey 属性值=true
属性名=doubleKey 属性值=42.0
属性名=longKey 属性值=73
2019-08-19 00:26:25.696 WARN 16332 --- [nio-8080-exec-1] c.s.http.controller.DeviceApiController : REST Quota exceeded for [0:0:0:0:0:0:0:1] . Disconnect
2019-08-19 00:26:26.402 WARN 16332 --- [nio-8080-exec-2] c.s.http.controller.DeviceApiController : REST Quota exceeded for [0:0:0:0:0:0:0:1] . Disconnect

这说明了我们的API限额服务起了作用,当然你也可以测试黑白名单等功能。

当在真实情况下,通常的API限额会很大,我这里提供了一个gatling自动化测试来提供接口测试。地址为:https://github.com/sanshengshui/IOT-Technical-Guide/tree/master/IOT-Guide-HTTP-Test

关于gatling的其他信息,大家可以参考:

到此,物联网时代,相信大家对IOT架构下的HTTP协议和API相关限制有所了解了,感谢大家的阅读!

物联网时代-跟着Thingsboard学IOT架构-HTTP设备协议及API相关限制的更多相关文章

  1. 物联网时代-跟着Thingsboard学IOT架构-MQTT设备协议

    Thingsboard的MQTT设备协议 thingsboard官网: https://thingsboard.io/ thingsboard GitHub: https://github.com/t ...

  2. 物联网时代 跟着Thingsboard学IOT架构-CoAP设备协议

    thingsboard官网: https://thingsboard.io/ thingsboard GitHub: https://github.com/thingsboard/thingsboar ...

  3. 物联网时代-新基建-ThingsBoard调试环境搭建

    前言 2020开年之际,科比不幸离世.疫情当道.经济受到了严重的损失.人们都不幸的感慨: 2020年真是太不真实的一年,可以重新来过就好了!国家和政府出台了拯救经济和加速建设的利好消息.3月份最热的词 ...

  4. 基于Azure IoT开发.NET物联网应用系列-全新的Azure IoT架构

    物联网技术已经火了很多年了,业界各大厂商都有各自成熟的解决方案.我们公司主要搞新能源汽车充电,充电桩就是物联网技术的最大应用,车联网.物联网.互联网三网合一.2017年的时候重点研究过Azure Io ...

  5. 开源的物联网技术平台(Thingsboard)

    1   总体说明 1.1   产品概述 1.1.1 Thingsboard作用 1.置备并控制设备. 2.采集设备数据并进行数据可视化. 3.分析设备数据,触发告警. 4.将数据传输到另一个系统. 5 ...

  6. [零基础学IoT Pwn] 环境搭建

    [零基础学IoT Pwn] 环境搭建 0x00 前言 这里指的零基础其实是我们在实战中遇到一些基础问题,再相应的去补充学习理论知识,这样起码不会枯燥. 本系列主要是利用网上已知的IoT设备(路由器)漏 ...

  7. 【跟着stackoverflow学Pandas】 - Adding new column to existing DataFrame in Python pandas - Pandas 添加列

    最近做一个系列博客,跟着stackoverflow学Pandas. 以 pandas作为关键词,在stackoverflow中进行搜索,随后安照 votes 数目进行排序: https://stack ...

  8. 【跟着stackoverflow学Pandas】 -Get list from pandas DataFrame column headers - Pandas 获取列名

    最近做一个系列博客,跟着stackoverflow学Pandas. 以 pandas作为关键词,在stackoverflow中进行搜索,随后安照 votes 数目进行排序: https://stack ...

  9. 【跟着stackoverflow学Pandas】add one row in a pandas.DataFrame -DataFrame添加行

    最近做一个系列博客,跟着stackoverflow学Pandas. 以 pandas作为关键词,在stackoverflow中进行搜索,随后安照 votes 数目进行排序: https://stack ...

随机推荐

  1. 扫描线——POJ1151

    平面上有若干个矩形,求矩形相互覆盖的面积.为方便起见,矩形的边均平行于坐标轴. 我们根据容斥原理,矩形相互覆盖的面积即为所有矩形的面积和减去所有矩形所覆盖的面积即可. 而现在问题是如何求得所有矩形所覆 ...

  2. 洛谷P2319 [HNOI2006]超级英雄 题解

    题目链接: https://www.luogu.org/problemnew/show/P2319 分析 每错,这是一道海南不对是河南呀呀呀错了是湖南的省选题. 但是还是可以作为二分图第二题来练手的, ...

  3. 个人永久性免费-Excel催化剂功能第47波-VBA开发者喜爱的加密函数类

    VBA的确是个很不错的编程工具,寄生在OFFICE内,无需安装庞大的开发环境,即开即用,方便灵活,能实现的事情也很多,但毕竟VBA是微软停止更新维护将近20年的一种语言,计算机的世界发展速度有多快大家 ...

  4. SpringBoot学习笔记2

    九:创建父工程 注意:打包方式选择为pom 将创建SpringBoot常用的依赖和插件放在父工程的pom.xml,如下: <project xmlns="http://maven.ap ...

  5. c实现生产者消费者问题。 windows下。

    #include <stdio.h>#include <windows.h> #define P(S) WaitForSingleObject(S,INFINITE)//定义W ...

  6. 《VR入门系列教程》之12---转换矩阵

    转换矩阵     模型网格的三维空间位置都是由它们的顶点坐标决定的,如果每次想要移动一下模型位置都要依次改变每个网格的顶点坐标,这将一件非常头疼的事,要是遇上需要显示动画效果那就更糟了.为了解决这个问 ...

  7. Spring:IOC本质分析探究

    IOC本质分析 分析实现 我们先用我们原来的方式写一段代码 . 先写一个UserDao接口 public interface UserDao { public void getUser(); } 再去 ...

  8. 在eclipse中创建Web项目中没有web.xml的解决方法

      右键点击项目 → “Java EE Tool” → “Generate Deployment descriptor stub” 即可生成web.xml文件

  9. android蓝牙通讯开发(详细)

    新建一个工程之后,我们可以先看到界面左边的项目栏,我们可以看到,除了app目录以外,大多数的文件和目录都是自动生成的,我们也不需要对他们进行修改,而app目录之下的文件才是我们工作的重点.下面,我先对 ...

  10. Python flask构建微信小程序订餐系统

    第1章 <Python Flask构建微信小程序订餐系统>课程简介 本章内容会带领大家通览整体架构,功能模块,及学习建议.让大家在一个清晰的开发思路下,进行后续的学习.同时领着大家登陆ht ...