You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iotdb.apache.org by ro...@apache.org on 2021/11/11 11:49:59 UTC
[iotdb] branch master updated: [IOTDB-1859] IoTDB REST Data
Service: Framework (#4280)
This is an automated email from the ASF dual-hosted git repository.
rong pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iotdb.git
The following commit(s) were added to refs/heads/master by this push:
new c4b9c2b [IOTDB-1859] IoTDB REST Data Service: Framework (#4280)
c4b9c2b is described below
commit c4b9c2b087478a56061feb56ab09f6cbe054f053
Author: CloudWise-Lukemiao <76...@users.noreply.github.com>
AuthorDate: Thu Nov 11 19:49:32 2021 +0800
[IOTDB-1859] IoTDB REST Data Service: Framework (#4280)
Co-authored-by: Steve Yurong Su <ro...@apache.org>
---
.../Communication-Service-Protocol/RestService.md | 268 ++++++++++++++++++++
openapi/pom.xml | 124 +++++++++
openapi/src/main/openapi3/iotdb-rest.yaml | 167 ++++++++++++
pom.xml | 7 +-
server/pom.xml | 7 +
.../resources/conf/iotdb-engine.properties | 2 +-
.../assembly/resources/conf/iotdb-rest.properties | 55 ++++
.../iotdb/db/conf/rest/IoTDBRestServiceCheck.java | 65 +++++
.../iotdb/db/conf/rest/IoTDBRestServiceConfig.java | 143 +++++++++++
.../db/conf/rest/IoTDBRestServiceDescriptor.java | 162 ++++++++++++
.../java/org/apache/iotdb/db/rest/RestService.java | 156 ++++++++++++
.../iotdb/db/rest/filter/ApiOriginFilter.java | 45 ++++
.../iotdb/db/rest/filter/AuthorizationFilter.java | 125 +++++++++
.../iotdb/db/rest/filter/BasicSecurityContext.java | 56 +++++
.../java/org/apache/iotdb/db/rest/filter/User.java | 38 +++
.../org/apache/iotdb/db/rest/filter/UserCache.java | 56 +++++
.../db/rest/handler/AuthorizationHandler.java | 52 ++++
.../iotdb/db/rest/handler/ExceptionHandler.java | 69 +++++
.../handler/PhysicalPlanConstructionHandler.java | 156 ++++++++++++
.../iotdb/db/rest/handler/QueryDataSetHandler.java | 95 +++++++
.../db/rest/handler/RequestValidationHandler.java | 39 +++
.../iotdb/db/rest/impl/PingApiServiceImpl.java | 37 +++
.../iotdb/db/rest/impl/RestApiServiceImpl.java | 144 +++++++++++
.../java/org/apache/iotdb/db/service/IoTDB.java | 8 +-
.../org/apache/iotdb/db/service/ServiceType.java | 3 +-
.../apache/iotdb/db/rest/IoTDBRestServiceIT.java | 280 +++++++++++++++++++++
server/src/test/resources/iotdb-rest.properties | 55 ++++
site/src/main/.vuepress/config.js | 1 +
28 files changed, 2410 insertions(+), 5 deletions(-)
diff --git a/docs/zh/UserGuide/Communication-Service-Protocol/RestService.md b/docs/zh/UserGuide/Communication-Service-Protocol/RestService.md
new file mode 100644
index 0000000..37dbdd7
--- /dev/null
+++ b/docs/zh/UserGuide/Communication-Service-Protocol/RestService.md
@@ -0,0 +1,268 @@
+<!--
+
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+
+-->
+
+## RESTful 服务
+IoTDB 的 RESTful 服务可用于查询、写入和管理操作,它使用 OpenAPI 标准来定义接口并生成框架。
+
+
+
+### 鉴权
+RESTful 服务使用了基础(basic)鉴权,每次 URL 请求都需要在 header 中携带 `'Authorization': 'Basic ' + base64.encode(username + ':' + password)`。
+
+
+
+### 接口
+
+#### ping
+
+请求方式:`GET`
+
+请求路径:http://ip:port/ping
+
+示例中使用的用户名为:root,密码为:root
+
+请求示例:
+
+```shell
+$ curl -H "Authorization:Basic cm9vdDpyb2901" http://127.0.0.1:18080/ping
+```
+响应参数:
+
+|参数名称 |参数类型 |参数描述|
+| ------------ | ------------ | ------------|
+| code | integer | 状态码 |
+| message | string | 信息提示 |
+
+响应示例:
+```json
+{
+ "code": 200,
+ "message": "SUCCESS_STATUS"
+}
+```
+用户名密码认证失败示例:
+```json
+{
+ "code": 600,
+ "message": "WRONG_LOGIN_PASSWORD_ERROR"
+}
+```
+
+
+
+#### query
+
+请求方式:`POST`
+
+请求头:`application/json`
+
+请求路径:http://ip:port/rest/v1/query
+
+参数说明:
+
+|参数名称 |参数类型 |是否必填|参数描述|
+| ------------ | ------------ | ------------ |------------ |
+| sql | string | 是 | |
+
+请求示例:
+```shell
+curl -H "Content-Type:application/json" -H "Authorization:Basic cm9vdDpyb290" -X POST --data '{"sql":"select s3, s4, s3 + 1 from root.sg27 limit 2"}' http://127.0.0.1:18080/rest/v1/query
+```
+
+响应参数:
+
+|参数名称 |参数类型 |参数描述|
+| ------------ | ------------ | ------------|
+| expressions | array | 结果集列名的数组 |
+| timestamps | array | 时间戳列 |
+|values|array|值列数组,列数与结果集列名数组的长度相同|
+
+响应示例:
+
+```json
+{
+ "expressions": [
+ "root.sg27.s3",
+ "root.sg27.s4",
+ "root.sg27.s3 + 1"
+ ],
+ "timestamps": [1,2],
+ "values": [[11,12],[false,true],[12.0,23.0]]
+}
+```
+
+
+
+#### nonQuery
+
+请求方式:`POST`
+
+请求头:`application/json`
+
+请求路径:http://ip:port/rest/v1/nonQuery
+
+参数说明:
+
+|参数名称 |参数类型 |是否必填|参数描述|
+| ------------ | ------------ | ------------ |------------ |
+| sql | string | 是 | |
+
+请求示例:
+```shell
+curl -H "Content-Type:application/json" -H "Authorization:Basic cm9vdDpyb290" -X POST --data '{"sql":"set storage group to root.ln"}' http://127.0.0.1:18080/rest/v1/nonQuery
+```
+
+响应参数:
+
+|参数名称 |参数类型 |参数描述|
+| ------------ | ------------ | ------------|
+| code | integer | 状态码 |
+| message | string | 信息提示 |
+
+响应示例:
+```json
+{
+ "code": 200,
+ "message": "SUCCESS_STATUS"
+}
+```
+
+
+
+#### insertTablet
+
+请求方式:`POST`
+
+请求头:`application/json`
+
+请求路径:http://ip:port/rest/v1/insertTablet
+
+参数说明:
+
+|参数名称 |参数类型 |是否必填|参数描述|
+| ------------ | ------------ | ------------ |------------ |
+| timestamps | array | 是 | 时间列 |
+| measurements | array | 是 | 测点名称 |
+| dataTypes | array | 是 | 数据类型 |
+| values | array | 是 | 值列,每一列中的值可以为 `null` |
+| isAligned | boolean | 是 | 是否是对齐时间序列 |
+| deviceId | boolean | 是 | 设备名称 |
+
+请求示例:
+```shell
+curl -H "Content-Type:application/json" -H "Authorization:Basic cm9vdDpyb290" -X POST --data '{"timestamps":[1635232143960,1635232153960],"measurements":["s3","s4"],"dataTypes":["INT32","BOOLEAN"],"values":[[11,null],[false,true]],"isAligned":false,"deviceId":"root.sg27"}' http://127.0.0.1:18080/rest/v1/insertTablet
+```
+
+响应参数:
+
+|参数名称 |参数类型 |参数描述|
+| ------------ | ------------ | ------------|
+| code | integer | 状态码 |
+| message | string | 信息提示 |
+
+响应示例:
+```json
+{
+ "code": 200,
+ "message": "SUCCESS_STATUS"
+}
+```
+
+
+
+### 配置
+
+配置位于 `iotdb-rest.properties` 中。
+
+
+
+* 将 `enable_rest_service` 设置为 `true` 以启用该模块,而将 `false` 设置为禁用该模块。默认情况下,该值为 `false`。
+
+```properties
+enable_rest_service=true
+```
+
+* 仅在 `enable_rest_service=true` 时生效。将 `rest_service_port `设置为数字(1025~65535),以自定义REST服务套接字端口。默认情况下,值为 `18080`。
+
+```properties
+rest_service_port=18080
+```
+
+* REST Service 是否开启 SSL 配置,将 `enable_https` 设置为 `true` 以启用该模块,而将 `false` 设置为禁用该模块。默认情况下,该值为 `false`。
+
+```properties
+enable_https=false
+```
+
+* keyStore 所在路径(非必填)
+
+```properties
+key_store_path=
+```
+
+
+* keyStore 密码(非必填)
+
+```properties
+key_store_pwd=
+```
+
+
+* trustStore 所在路径(非必填)
+
+```properties
+trust_store_path=
+```
+
+* trustStore 密码(非必填)
+
+```properties
+trust_store_pwd=
+```
+
+
+* SSL 超时时间,单位为秒
+
+```properties
+idle_timeout=5000
+```
+
+
+* 缓存客户登录信息的过期时间(用于加速用户鉴权的速度,单位为秒,默认是8个小时)
+
+```properties
+cache_expire=28800
+```
+
+
+* 缓存中存储的最大用户数量(默认是100)
+
+```properties
+cache_max_num=100
+```
+
+* 缓存初始容量(默认是10)
+
+```properties
+cache_init_num=10
+```
+
+
diff --git a/openapi/pom.xml b/openapi/pom.xml
new file mode 100644
index 0000000..e44e7da
--- /dev/null
+++ b/openapi/pom.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>iotdb-parent</artifactId>
+ <groupId>org.apache.iotdb</groupId>
+ <version>0.13.0-SNAPSHOT</version>
+ <relativePath>../pom.xml</relativePath>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+ <artifactId>openapi</artifactId>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>build-helper-maven-plugin</artifactId>
+ <version>1.9.1</version>
+ <executions>
+ <execution>
+ <id>add-source</id>
+ <phase>generate-sources</phase>
+ <goals>
+ <goal>add-source</goal>
+ </goals>
+ <configuration>
+ <sources>
+ <source>${project.basedir}/target/generated-sources/java/src/gen/java</source>
+ </sources>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ <dependencies>
+ <dependency>
+ <groupId>io.swagger</groupId>
+ <artifactId>swagger-jersey2-jaxrs</artifactId>
+ <scope>compile</scope>
+ <version>${swagger.core.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>servlet-api</artifactId>
+ <version>${servlet.api.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.datatype</groupId>
+ <artifactId>jackson-datatype-jsr310</artifactId>
+ <version>${jackson.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.jaxrs</groupId>
+ <artifactId>jackson-jaxrs-json-provider</artifactId>
+ <version>${jackson.version}</version>
+ </dependency>
+ </dependencies>
+ <profiles>
+ <profile>
+ <id>openapi-generation</id>
+ <activation>
+ <file>
+ <exists>src/main/openapi3</exists>
+ </file>
+ </activation>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.openapitools</groupId>
+ <artifactId>openapi-generator-maven-plugin</artifactId>
+ <version>${openapi.generator.version}</version>
+ <executions>
+ <execution>
+ <id>generate-java-rest-codes</id>
+ <goals>
+ <goal>generate</goal>
+ </goals>
+ <configuration>
+ <inputSpec>${project.basedir}/src/main/openapi3/iotdb-rest.yaml</inputSpec>
+ <output>${project.build.directory}/generated-sources/java</output>
+ <apiPackage>org.apache.iotdb.db.rest</apiPackage>
+ <modelPackage>org.apache.iotdb.db.rest.model</modelPackage>
+ <invokerPackage>org.apache.iotdb.db.rest.invoker</invokerPackage>
+ <generatorName>jaxrs-jersey</generatorName>
+ <groupId>org.apache.iotdb</groupId>
+ <artifactId>iotdb-rest-service</artifactId>
+ <artifactVersion>${project.version}</artifactVersion>
+ <addCompileSourceRoot>true</addCompileSourceRoot>
+ <configOptions>
+ <licenseName>Apache License 2.0</licenseName>
+ <groupId>org.apache.iotdb</groupId>
+ <artifactId>iotdb-rest-service</artifactId>
+ <artifactVersion>${project.version}</artifactVersion>
+ <dateLibrary>java8</dateLibrary>
+ <useGzipFeature>true</useGzipFeature>
+ </configOptions>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+</project>
diff --git a/openapi/src/main/openapi3/iotdb-rest.yaml b/openapi/src/main/openapi3/iotdb-rest.yaml
new file mode 100644
index 0000000..c1d0315
--- /dev/null
+++ b/openapi/src/main/openapi3/iotdb-rest.yaml
@@ -0,0 +1,167 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+openapi: 3.0.0
+info:
+ title: iotdb-rest
+ description: IoTDB Rest API for Grafana, Prometheus, etc..
+ license:
+ name: Apache 2.0
+ version: 1.0.0
+servers:
+- url: http://127.0.0.1:18080/
+ description: api
+security:
+- basic: []
+paths:
+ /ping:
+ get:
+ responses:
+ "200":
+ description: ExecutionStatus
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ExecutionStatus'
+ operationId: tryPing
+
+ /rest/v1/insertTablet:
+ post:
+ summary: insertTablet
+ description: insertTablet
+ operationId: insertTablet
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/InsertTabletRequest'
+ responses:
+ "200":
+ description: ExecutionStatus
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ExecutionStatus'
+
+ /rest/v1/nonQuery:
+ post:
+ summary: executeNonQueryStatement
+ description: executeNonQueryStatement
+ operationId: executeNonQueryStatement
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/SQL'
+ responses:
+ "200":
+ description: ExecutionStatus
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ExecutionStatus'
+
+ /rest/v1/query:
+ post:
+ summary: executeQueryStatement
+ description: executeQueryStatement
+ operationId: executeQueryStatement
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/SQL'
+ responses:
+ "200":
+ description: QueryDataSet
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/QueryDataSet'
+
+components:
+ schemas:
+ SQL:
+ title: SQL
+ type: object
+ properties:
+ sql:
+ type: string
+
+ InsertTabletRequest:
+ title: InsertTabletRequest
+ type: object
+ properties:
+ timestamps:
+ type: array
+ items:
+ type: long
+ measurements:
+ type: array
+ items:
+ type: string
+ dataTypes:
+ type: array
+ items:
+ type: string
+ values:
+ type: array
+ items:
+ type: array
+ items:
+ type: object
+ isAligned:
+ type: boolean
+ deviceId:
+ type: string
+
+ ExecutionStatus:
+ type: object
+ properties:
+ code:
+ type: integer
+ message:
+ type: string
+
+ QueryDataSet:
+ type: object
+ properties:
+ expressions:
+ type: array
+ items:
+ type: string
+ timestamps:
+ type: array
+ items:
+ type: long
+ values:
+ type: array
+ items:
+ type: array
+ items:
+ type: object
+
+ securitySchemes:
+ basic:
+ type: http
+ scheme: basic
+ APIKey:
+ type: apiKey
+ name: API Key
+ in: header
diff --git a/pom.xml b/pom.xml
index f8101d7..ff7da57 100644
--- a/pom.xml
+++ b/pom.xml
@@ -88,6 +88,7 @@
<module>jdbc</module>
<module>session</module>
<module>cli</module>
+ <module>openapi</module>
<module>server</module>
<module>example</module>
<module>grafana</module>
@@ -126,7 +127,7 @@
<!-- keep consistent with client-cpp/tools/thrift/pom.xml-->
<thrift.version>0.14.1</thrift.version>
<airline.version>0.8</airline.version>
- <jackson.version>2.11.0</jackson.version>
+ <jackson.version>2.10.0</jackson.version>
<antlr4.version>4.8-1</antlr4.version>
<common.cli.version>1.3.1</common.cli.version>
<common.codec.version>1.13</common.codec.version>
@@ -161,6 +162,10 @@
<spotless.version>2.4.2</spotless.version>
<httpclient.version>4.5.13</httpclient.version>
<httpcore.version>4.4.13</httpcore.version>
+ <!-- for REST service -->
+ <swagger.core.version>1.5.18</swagger.core.version>
+ <servlet.api.version>2.5</servlet.api.version>
+ <openapi.generator.version>5.0.0</openapi.generator.version>
</properties>
<!--
if we claim dependencies in dependencyManagement, then we do not claim
diff --git a/server/pom.xml b/server/pom.xml
index 62910ab..a03a150 100644
--- a/server/pom.xml
+++ b/server/pom.xml
@@ -88,10 +88,17 @@
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
+ <version>${jetty.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-webapp</artifactId>
+ <version>${jetty.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.iotdb</groupId>
+ <artifactId>openapi</artifactId>
+ <version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.dropwizard.metrics</groupId>
diff --git a/server/src/assembly/resources/conf/iotdb-engine.properties b/server/src/assembly/resources/conf/iotdb-engine.properties
index f306d17..ab779be 100644
--- a/server/src/assembly/resources/conf/iotdb-engine.properties
+++ b/server/src/assembly/resources/conf/iotdb-engine.properties
@@ -927,4 +927,4 @@ timestamp_precision=ms
# admin password, default is root
# Datatype: string
-# admin_password=root
+# admin_password=root
\ No newline at end of file
diff --git a/server/src/assembly/resources/conf/iotdb-rest.properties b/server/src/assembly/resources/conf/iotdb-rest.properties
new file mode 100644
index 0000000..af7d8de
--- /dev/null
+++ b/server/src/assembly/resources/conf/iotdb-rest.properties
@@ -0,0 +1,55 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+####################
+### REST Service Configuration
+####################
+
+# Is the REST service enabled
+# enable_rest_service=false
+
+# the binding port of the REST service
+# rest_service_port=18080
+
+# is SSL enabled
+# enable_https=false
+
+# SSL key store path
+# key_store_path=
+
+# SSL key store password
+# key_store_pwd=
+
+# SSL trust store path
+# trust_store_path=
+
+# SSL trust store password.
+# trust_store_pwd=
+
+# SSL timeout (in seconds)
+# idle_timeout_in_seconds=50000
+
+# the expiration time of the user login infomation cache (in seconds)
+# cache_expire_in_seconds=28800
+
+# maximum number of users can be stored in the user login cache.
+# cache_max_num=100
+
+# init capacity of users can be stored in the user login cache.
+# cache_init_num=10
diff --git a/server/src/main/java/org/apache/iotdb/db/conf/rest/IoTDBRestServiceCheck.java b/server/src/main/java/org/apache/iotdb/db/conf/rest/IoTDBRestServiceCheck.java
new file mode 100644
index 0000000..2e413d3
--- /dev/null
+++ b/server/src/main/java/org/apache/iotdb/db/conf/rest/IoTDBRestServiceCheck.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.iotdb.db.conf.rest;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+
+public class IoTDBRestServiceCheck {
+ private static final Logger LOGGER = LoggerFactory.getLogger(IoTDBRestServiceCheck.class);
+ private static final IoTDBRestServiceConfig CONFIG =
+ IoTDBRestServiceDescriptor.getInstance().getConfig();
+
+ public static IoTDBRestServiceCheck getInstance() {
+ return IoTDBRestServiceConfigCheckHolder.INSTANCE;
+ }
+
+ private static class IoTDBRestServiceConfigCheckHolder {
+
+ private static final IoTDBRestServiceCheck INSTANCE = new IoTDBRestServiceCheck();
+ }
+
+ public void checkConfig() throws IOException {
+ if (CONFIG.getRestServicePort() > 65535 || CONFIG.getRestServicePort() < 1024) {
+ printErrorLogAndExit("rest_service_port");
+ }
+ if (CONFIG.getIdleTimeoutInSeconds() <= 0) {
+ printErrorLogAndExit("idle_timeout_in_seconds");
+ }
+ if (CONFIG.getCacheExpireInSeconds() <= 0) {
+ printErrorLogAndExit("cache_expire_in_seconds");
+ }
+ if (CONFIG.getCacheMaxNum() <= 0) {
+ printErrorLogAndExit("cache_max_num");
+ }
+ if (CONFIG.getCacheInitNum() <= 0) {
+ printErrorLogAndExit("cache_init_num");
+ }
+ if (CONFIG.getCacheInitNum() > CONFIG.getCacheMaxNum()) {
+ printErrorLogAndExit("cache_init_num");
+ }
+ }
+
+ private void printErrorLogAndExit(String property) {
+ LOGGER.error("Wrong config {}, please check!", property);
+ System.exit(-1);
+ }
+}
diff --git a/server/src/main/java/org/apache/iotdb/db/conf/rest/IoTDBRestServiceConfig.java b/server/src/main/java/org/apache/iotdb/db/conf/rest/IoTDBRestServiceConfig.java
new file mode 100644
index 0000000..80ef8d4
--- /dev/null
+++ b/server/src/main/java/org/apache/iotdb/db/conf/rest/IoTDBRestServiceConfig.java
@@ -0,0 +1,143 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.iotdb.db.conf.rest;
+
+public class IoTDBRestServiceConfig {
+ static final String CONFIG_NAME = "iotdb-rest.properties";
+ /** if the enableRestService is true, we will start REST Service */
+ private boolean enableRestService = false;
+
+ /** set the REST Service port. */
+ private int restServicePort = 18080;
+
+ /** enable the REST Service ssl. */
+ private boolean enableHttps = false;
+
+ /** ssl key Store Path */
+ private String keyStorePath = "";
+
+ /** ssl trust Store Path */
+ private String trustStorePath = "";
+
+ /** ssl key Store password */
+ private String keyStorePwd = "";
+
+ /** ssl trust Store password */
+ private String trustStorePwd = "";
+
+ /** ssl timeout */
+ private int idleTimeoutInSeconds = 50000;
+
+ /** Session expiration time */
+ private int cacheExpireInSeconds = 28800;
+
+ /** maximum number of users stored in cache */
+ private int cacheMaxNum = 100;
+
+ /** init number of users stored in cache */
+ private int cacheInitNum = 10;
+
+ public String getTrustStorePwd() {
+ return trustStorePwd;
+ }
+
+ public void setTrustStorePwd(String trustStorePwd) {
+ this.trustStorePwd = trustStorePwd;
+ }
+
+ public int getIdleTimeoutInSeconds() {
+ return idleTimeoutInSeconds;
+ }
+
+ public void setIdleTimeoutInSeconds(int idleTimeoutInSeconds) {
+ this.idleTimeoutInSeconds = idleTimeoutInSeconds;
+ }
+
+ public String getKeyStorePath() {
+ return keyStorePath;
+ }
+
+ public void setKeyStorePath(String keyStorePath) {
+ this.keyStorePath = keyStorePath;
+ }
+
+ public String getTrustStorePath() {
+ return trustStorePath;
+ }
+
+ public void setTrustStorePath(String trustStorePath) {
+ this.trustStorePath = trustStorePath;
+ }
+
+ public String getKeyStorePwd() {
+ return keyStorePwd;
+ }
+
+ public void setKeyStorePwd(String keyStorePwd) {
+ this.keyStorePwd = keyStorePwd;
+ }
+
+ public int getRestServicePort() {
+ return restServicePort;
+ }
+
+ public void setRestServicePort(int restServicePort) {
+ this.restServicePort = restServicePort;
+ }
+
+ public boolean isEnableHttps() {
+ return enableHttps;
+ }
+
+ public void setEnableHttps(boolean enableHttps) {
+ this.enableHttps = enableHttps;
+ }
+
+ public boolean isEnableRestService() {
+ return enableRestService;
+ }
+
+ public void setEnableRestService(boolean enableRestService) {
+ this.enableRestService = enableRestService;
+ }
+
+ public int getCacheExpireInSeconds() {
+ return cacheExpireInSeconds;
+ }
+
+ public void setCacheExpireInSeconds(int cacheExpireInSeconds) {
+ this.cacheExpireInSeconds = cacheExpireInSeconds;
+ }
+
+ public int getCacheMaxNum() {
+ return cacheMaxNum;
+ }
+
+ public void setCacheMaxNum(int cacheMaxNum) {
+ this.cacheMaxNum = cacheMaxNum;
+ }
+
+ public int getCacheInitNum() {
+ return cacheInitNum;
+ }
+
+ public void setCacheInitNum(int cacheInitNum) {
+ this.cacheInitNum = cacheInitNum;
+ }
+}
diff --git a/server/src/main/java/org/apache/iotdb/db/conf/rest/IoTDBRestServiceDescriptor.java b/server/src/main/java/org/apache/iotdb/db/conf/rest/IoTDBRestServiceDescriptor.java
new file mode 100644
index 0000000..848c64e
--- /dev/null
+++ b/server/src/main/java/org/apache/iotdb/db/conf/rest/IoTDBRestServiceDescriptor.java
@@ -0,0 +1,162 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.iotdb.db.conf.rest;
+
+import org.apache.iotdb.db.conf.IoTDBConfig;
+import org.apache.iotdb.db.conf.IoTDBConstant;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Properties;
+
+public class IoTDBRestServiceDescriptor {
+ private static final Logger logger = LoggerFactory.getLogger(IoTDBRestServiceDescriptor.class);
+
+ private final IoTDBRestServiceConfig conf = new IoTDBRestServiceConfig();
+
+ protected IoTDBRestServiceDescriptor() {
+ loadProps();
+ }
+
+ public static IoTDBRestServiceDescriptor getInstance() {
+ return IoTDBRestServiceDescriptorHolder.INSTANCE;
+ }
+
+ /** load an property file. */
+ @SuppressWarnings("squid:S3776") // Suppress high Cognitive Complexity warning
+ private void loadProps() {
+ URL url = getPropsUrl();
+ if (url == null) {
+ logger.warn("Couldn't load the REST Service configuration from any of the known sources.");
+ return;
+ }
+ try (InputStream inputStream = url.openStream()) {
+ logger.info("Start to read config file {}", url);
+ Properties properties = new Properties();
+ properties.load(inputStream);
+ conf.setEnableRestService(
+ Boolean.parseBoolean(
+ properties.getProperty(
+ "enable_rest_service", Boolean.toString(conf.isEnableRestService()))));
+ conf.setRestServicePort(
+ Integer.parseInt(
+ properties.getProperty(
+ "rest_service_port", Integer.toString(conf.getRestServicePort()))));
+
+ conf.setEnableHttps(
+ Boolean.parseBoolean(
+ properties.getProperty("enable_https", Boolean.toString(conf.isEnableHttps()))));
+ conf.setKeyStorePath(properties.getProperty("key_store_path", conf.getKeyStorePath()));
+ conf.setKeyStorePwd(properties.getProperty("key_store_pwd", conf.getKeyStorePwd()));
+ conf.setTrustStorePath(properties.getProperty("trust_store_path", conf.getTrustStorePath()));
+ conf.setTrustStorePwd(properties.getProperty("trust_store_pwd", conf.getTrustStorePwd()));
+ conf.setIdleTimeoutInSeconds(
+ Integer.parseInt(
+ properties.getProperty(
+ "idle_timeout_in_seconds", Integer.toString(conf.getIdleTimeoutInSeconds()))));
+ conf.setCacheExpireInSeconds(
+ Integer.parseInt(
+ properties.getProperty(
+ "cache_expire_in_seconds", Integer.toString(conf.getCacheExpireInSeconds()))));
+ conf.setCacheInitNum(
+ Integer.parseInt(
+ properties.getProperty("cache_init_num", Integer.toString(conf.getCacheInitNum()))));
+ conf.setCacheMaxNum(
+ Integer.parseInt(
+ properties.getProperty("cache_max_num", Integer.toString(conf.getCacheMaxNum()))));
+
+ } catch (FileNotFoundException e) {
+ logger.warn("REST service fail to find config file {}", url, e);
+ } catch (IOException e) {
+ logger.warn("REST service cannot load config file, use default configuration", e);
+ } catch (Exception e) {
+ logger.warn("REST service Incorrect format in config file, use default configuration", e);
+ }
+ }
+
+ /**
+ * get props url location
+ *
+ * @return url object if location exit, otherwise null.
+ */
+ public URL getPropsUrl() {
+ // Check if a config-directory was specified first.
+ String urlString = System.getProperty(IoTDBConstant.IOTDB_CONF, null);
+ // If it wasn't, check if a home directory was provided (This usually contains a config)
+ if (urlString == null) {
+ urlString = System.getProperty(IoTDBConstant.IOTDB_HOME, null);
+ if (urlString != null) {
+ urlString =
+ urlString
+ + File.separatorChar
+ + "conf"
+ + File.separatorChar
+ + "rest"
+ + File.separatorChar
+ + IoTDBRestServiceConfig.CONFIG_NAME;
+ } else {
+ // If this too wasn't provided, try to find a default config in the root of the classpath.
+ URL uri = IoTDBConfig.class.getResource("/" + IoTDBRestServiceConfig.CONFIG_NAME);
+ if (uri != null) {
+ return uri;
+ }
+ logger.warn(
+ "Cannot find IOTDB_HOME or IOTDB_CONF environment variable when loading "
+ + "config file {}, use default configuration",
+ IoTDBRestServiceConfig.CONFIG_NAME);
+ // update all data seriesPath
+ return null;
+ }
+ }
+ // If a config location was provided, but it doesn't end with a properties file,
+ // append the default location.
+ else if (!urlString.endsWith(".properties")) {
+ urlString += (File.separatorChar + IoTDBRestServiceConfig.CONFIG_NAME);
+ }
+
+ // If the url doesn't start with "file:" or "classpath:", it's provided as a no path.
+ // So we need to add it to make it a real URL.
+ if (!urlString.startsWith("file:") && !urlString.startsWith("classpath:")) {
+ urlString = "file:" + urlString;
+ }
+ try {
+ return new URL(urlString);
+ } catch (MalformedURLException e) {
+ return null;
+ }
+ }
+
+ public IoTDBRestServiceConfig getConfig() {
+ return conf;
+ }
+
+ private static class IoTDBRestServiceDescriptorHolder {
+
+ private static final IoTDBRestServiceDescriptor INSTANCE = new IoTDBRestServiceDescriptor();
+
+ private IoTDBRestServiceDescriptorHolder() {}
+ }
+}
diff --git a/server/src/main/java/org/apache/iotdb/db/rest/RestService.java b/server/src/main/java/org/apache/iotdb/db/rest/RestService.java
new file mode 100644
index 0000000..75928f7
--- /dev/null
+++ b/server/src/main/java/org/apache/iotdb/db/rest/RestService.java
@@ -0,0 +1,156 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.iotdb.db.rest;
+
+import org.apache.iotdb.db.conf.rest.IoTDBRestServiceConfig;
+import org.apache.iotdb.db.conf.rest.IoTDBRestServiceDescriptor;
+import org.apache.iotdb.db.exception.StartupException;
+import org.apache.iotdb.db.rest.filter.ApiOriginFilter;
+import org.apache.iotdb.db.service.IService;
+import org.apache.iotdb.db.service.ServiceType;
+
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.eclipse.jetty.server.SecureRequestCustomizer;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.SslConnectionFactory;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.glassfish.jersey.servlet.ServletContainer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.DispatcherType;
+
+import java.util.EnumSet;
+
+public class RestService implements IService {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(RestService.class);
+
+ private static Server server;
+
+ private void startSSL(
+ int port,
+ String keyStorePath,
+ String trustStorePath,
+ String keyStorePwd,
+ String trustStorePwd,
+ int idleTime) {
+ server = new Server();
+
+ HttpConfiguration httpsConfig = new HttpConfiguration();
+ httpsConfig.setSecurePort(port);
+ httpsConfig.addCustomizer(new SecureRequestCustomizer());
+
+ SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
+ sslContextFactory.setKeyStorePath(keyStorePath);
+ sslContextFactory.setKeyStorePassword(keyStorePwd);
+ sslContextFactory.setTrustStorePath(trustStorePath);
+ sslContextFactory.setTrustStorePassword(trustStorePwd);
+
+ ServerConnector httpsConnector =
+ new ServerConnector(
+ server,
+ new SslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.asString()),
+ new HttpConnectionFactory(httpsConfig));
+ httpsConnector.setPort(port);
+ httpsConnector.setIdleTimeout(idleTime);
+ server.addConnector(httpsConnector);
+
+ server.setHandler(constructServletContextHandler());
+ serverStart();
+ }
+
+ private void startNonSSL(int port) {
+ server = new Server(port);
+ server.setHandler(constructServletContextHandler());
+ serverStart();
+ }
+
+ private ServletContextHandler constructServletContextHandler() {
+ ServletContextHandler context = new ServletContextHandler(ServletContextHandler.NO_SESSIONS);
+ context.addFilter(
+ ApiOriginFilter.class, "/*", EnumSet.of(DispatcherType.INCLUDE, DispatcherType.REQUEST));
+ ServletHolder holder = context.addServlet(ServletContainer.class, "/*");
+ holder.setInitOrder(1);
+ holder.setInitParameter(
+ "jersey.config.server.provider.packages",
+ "io.swagger.jaxrs.listing, io.swagger.sample.resource, org.apache.iotdb.db.rest");
+ holder.setInitParameter(
+ "jersey.config.server.provider.classnames",
+ "org.glassfish.jersey.media.multipart.MultiPartFeature");
+ holder.setInitParameter("jersey.config.server.wadl.disableWadl", "true");
+ context.setContextPath("/");
+ return context;
+ }
+
+ private void serverStart() {
+ try {
+ server.start();
+ } catch (Exception e) {
+ LOGGER.warn("RestService failed to start: {}", e.getMessage());
+ server.destroy();
+ }
+ }
+
+ @Override
+ public void start() throws StartupException {
+ IoTDBRestServiceConfig config = IoTDBRestServiceDescriptor.getInstance().getConfig();
+ if (IoTDBRestServiceDescriptor.getInstance().getConfig().isEnableHttps()) {
+ startSSL(
+ config.getRestServicePort(),
+ config.getKeyStorePath(),
+ config.getTrustStorePath(),
+ config.getKeyStorePwd(),
+ config.getTrustStorePwd(),
+ config.getIdleTimeoutInSeconds());
+ } else {
+ startNonSSL(config.getRestServicePort());
+ }
+ }
+
+ @Override
+ public void stop() {
+ try {
+ server.stop();
+ } catch (Exception e) {
+ LOGGER.warn("RestService failed to stop: {}", e.getMessage());
+ } finally {
+ server.destroy();
+ }
+ }
+
+ @Override
+ public ServiceType getID() {
+ return ServiceType.REST_SERVICE;
+ }
+
+ public static RestService getInstance() {
+ return RestServiceHolder.INSTANCE;
+ }
+
+ private static class RestServiceHolder {
+
+ private static final RestService INSTANCE = new RestService();
+
+ private RestServiceHolder() {}
+ }
+}
diff --git a/server/src/main/java/org/apache/iotdb/db/rest/filter/ApiOriginFilter.java b/server/src/main/java/org/apache/iotdb/db/rest/filter/ApiOriginFilter.java
new file mode 100644
index 0000000..fb3f912
--- /dev/null
+++ b/server/src/main/java/org/apache/iotdb/db/rest/filter/ApiOriginFilter.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.iotdb.db.rest.filter;
+
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletResponse;
+
+import java.io.IOException;
+
+public class ApiOriginFilter implements javax.servlet.Filter {
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+ throws IOException, ServletException {
+ HttpServletResponse res = (HttpServletResponse) response;
+ res.addHeader("Access-Control-Allow-Origin", "*");
+ res.addHeader("Access-Control-Allow-Methods", "GET, POST");
+ res.addHeader("Access-Control-Allow-Headers", "*");
+ chain.doFilter(request, response);
+ }
+
+ @Override
+ public void destroy() {}
+
+ @Override
+ public void init(FilterConfig filterConfig) throws ServletException {}
+}
diff --git a/server/src/main/java/org/apache/iotdb/db/rest/filter/AuthorizationFilter.java b/server/src/main/java/org/apache/iotdb/db/rest/filter/AuthorizationFilter.java
new file mode 100644
index 0000000..da21e09
--- /dev/null
+++ b/server/src/main/java/org/apache/iotdb/db/rest/filter/AuthorizationFilter.java
@@ -0,0 +1,125 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.iotdb.db.rest.filter;
+
+import org.apache.iotdb.db.auth.AuthException;
+import org.apache.iotdb.db.auth.authorizer.BasicAuthorizer;
+import org.apache.iotdb.db.auth.authorizer.IAuthorizer;
+import org.apache.iotdb.db.conf.rest.IoTDBRestServiceDescriptor;
+import org.apache.iotdb.db.rest.model.ExecutionStatus;
+import org.apache.iotdb.rpc.TSStatusCode;
+
+import org.glassfish.jersey.internal.util.Base64;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.annotation.WebFilter;
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.container.ContainerRequestFilter;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+import javax.ws.rs.ext.Provider;
+
+import java.io.IOException;
+
+@WebFilter("/*")
+@Provider
+public class AuthorizationFilter implements ContainerRequestFilter {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(AuthorizationFilter.class);
+
+ private final IAuthorizer authorizer = BasicAuthorizer.getInstance();
+ private final UserCache userCache = UserCache.getInstance();
+
+ public AuthorizationFilter() throws AuthException {}
+
+ @Override
+ public void filter(ContainerRequestContext containerRequestContext) throws IOException {
+ if ("OPTIONS".equals(containerRequestContext.getMethod())
+ || containerRequestContext.getUriInfo().getPath().equals("swagger.json")) {
+ return;
+ }
+
+ String authorizationHeader = containerRequestContext.getHeaderString("authorization");
+ User user = userCache.getUser(authorizationHeader);
+ if (user == null) {
+ user = checkLogin(containerRequestContext, authorizationHeader);
+ if (user == null) {
+ return;
+ } else {
+ userCache.setUser(authorizationHeader, user);
+ }
+ }
+
+ BasicSecurityContext basicSecurityContext =
+ new BasicSecurityContext(
+ user, IoTDBRestServiceDescriptor.getInstance().getConfig().isEnableHttps());
+ containerRequestContext.setSecurityContext(basicSecurityContext);
+ }
+
+ private User checkLogin(
+ ContainerRequestContext containerRequestContext, String authorizationHeader) {
+
+ String decoded = Base64.decodeAsString(authorizationHeader.replace("Basic ", ""));
+ // todo: support special chars in username and password
+ String[] split = decoded.split(":");
+ if (split.length != 2) {
+ Response resp =
+ Response.status(Status.BAD_REQUEST)
+ .type(MediaType.APPLICATION_JSON)
+ .entity(
+ new ExecutionStatus()
+ .code(TSStatusCode.SYSTEM_CHECK_ERROR.getStatusCode())
+ .message(TSStatusCode.SYSTEM_CHECK_ERROR.name()))
+ .build();
+ containerRequestContext.abortWith(resp);
+ return null;
+ }
+
+ User user = new User();
+ user.setUsername(split[0]);
+ user.setPassword(split[1]);
+ try {
+ if (!authorizer.login(split[0], split[1])) {
+ Response resp =
+ Response.status(Status.OK)
+ .type(MediaType.APPLICATION_JSON)
+ .entity(
+ new ExecutionStatus()
+ .code(TSStatusCode.WRONG_LOGIN_PASSWORD_ERROR.getStatusCode())
+ .message(TSStatusCode.WRONG_LOGIN_PASSWORD_ERROR.name()))
+ .build();
+ containerRequestContext.abortWith(resp);
+ return null;
+ }
+ } catch (AuthException e) {
+ LOGGER.warn(e.getMessage(), e);
+ Response resp =
+ Response.status(Status.INTERNAL_SERVER_ERROR)
+ .type(MediaType.APPLICATION_JSON)
+ .entity(
+ new ExecutionStatus()
+ .code(TSStatusCode.INTERNAL_SERVER_ERROR.getStatusCode())
+ .message(e.getMessage()))
+ .build();
+ containerRequestContext.abortWith(resp);
+ return null;
+ }
+ return user;
+ }
+}
diff --git a/server/src/main/java/org/apache/iotdb/db/rest/filter/BasicSecurityContext.java b/server/src/main/java/org/apache/iotdb/db/rest/filter/BasicSecurityContext.java
new file mode 100644
index 0000000..b25443d
--- /dev/null
+++ b/server/src/main/java/org/apache/iotdb/db/rest/filter/BasicSecurityContext.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.iotdb.db.rest.filter;
+
+import javax.ws.rs.core.SecurityContext;
+
+import java.security.Principal;
+
+public class BasicSecurityContext implements SecurityContext {
+
+ private final User user;
+ private final boolean secure;
+
+ public BasicSecurityContext(User user, boolean secure) {
+ this.user = user;
+ this.secure = secure;
+ }
+
+ @Override
+ public Principal getUserPrincipal() {
+ return user::getUsername;
+ }
+
+ public User getUser() {
+ return user;
+ }
+
+ @Override
+ public boolean isUserInRole(String role) {
+ return true;
+ }
+
+ @Override
+ public boolean isSecure() {
+ return secure;
+ }
+
+ @Override
+ public String getAuthenticationScheme() {
+ return BASIC_AUTH;
+ }
+}
diff --git a/server/src/main/java/org/apache/iotdb/db/rest/filter/User.java b/server/src/main/java/org/apache/iotdb/db/rest/filter/User.java
new file mode 100644
index 0000000..f0e505c
--- /dev/null
+++ b/server/src/main/java/org/apache/iotdb/db/rest/filter/User.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.iotdb.db.rest.filter;
+
+public class User {
+ private String username;
+ private String password;
+
+ public String getPassword() {
+ return password;
+ }
+
+ public void setPassword(String password) {
+ this.password = password;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public void setUsername(String username) {
+ this.username = username;
+ }
+}
diff --git a/server/src/main/java/org/apache/iotdb/db/rest/filter/UserCache.java b/server/src/main/java/org/apache/iotdb/db/rest/filter/UserCache.java
new file mode 100644
index 0000000..e9204a5
--- /dev/null
+++ b/server/src/main/java/org/apache/iotdb/db/rest/filter/UserCache.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.iotdb.db.rest.filter;
+
+import org.apache.iotdb.db.conf.rest.IoTDBRestServiceConfig;
+import org.apache.iotdb.db.conf.rest.IoTDBRestServiceDescriptor;
+
+import com.github.benmanes.caffeine.cache.Cache;
+import com.github.benmanes.caffeine.cache.Caffeine;
+
+import java.util.concurrent.TimeUnit;
+
+public class UserCache {
+ private static final IoTDBRestServiceConfig CONFIG =
+ IoTDBRestServiceDescriptor.getInstance().getConfig();
+ private final Cache<String, User> cache;
+
+ private UserCache() {
+ cache =
+ Caffeine.newBuilder()
+ .initialCapacity(CONFIG.getCacheInitNum())
+ .maximumSize(CONFIG.getCacheMaxNum())
+ .expireAfterWrite(CONFIG.getCacheExpireInSeconds(), TimeUnit.SECONDS)
+ .build();
+ }
+
+ public static UserCache getInstance() {
+ return UserCacheHolder.INSTANCE;
+ }
+
+ public User getUser(String key) {
+ return cache.getIfPresent(key);
+ }
+
+ public void setUser(String key, User user) {
+ cache.put(key, user);
+ }
+
+ private static class UserCacheHolder {
+ private static final UserCache INSTANCE = new UserCache();
+ }
+}
diff --git a/server/src/main/java/org/apache/iotdb/db/rest/handler/AuthorizationHandler.java b/server/src/main/java/org/apache/iotdb/db/rest/handler/AuthorizationHandler.java
new file mode 100644
index 0000000..4404a0b
--- /dev/null
+++ b/server/src/main/java/org/apache/iotdb/db/rest/handler/AuthorizationHandler.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.iotdb.db.rest.handler;
+
+import org.apache.iotdb.db.auth.AuthException;
+import org.apache.iotdb.db.auth.AuthorityChecker;
+import org.apache.iotdb.db.qp.physical.PhysicalPlan;
+import org.apache.iotdb.db.rest.model.ExecutionStatus;
+import org.apache.iotdb.rpc.TSStatusCode;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.SecurityContext;
+
+public class AuthorizationHandler {
+ private AuthorizationHandler() {}
+
+ public static Response checkAuthority(
+ SecurityContext securityContext, PhysicalPlan physicalPlan) {
+ try {
+ if (!AuthorityChecker.check(
+ securityContext.getUserPrincipal().getName(),
+ physicalPlan.getAuthPaths(),
+ physicalPlan.getOperatorType(),
+ null)) {
+ return Response.ok()
+ .entity(
+ new ExecutionStatus()
+ .code(TSStatusCode.NO_PERMISSION_ERROR.getStatusCode())
+ .message(TSStatusCode.NO_PERMISSION_ERROR.name()))
+ .build();
+ }
+ } catch (AuthException e) {
+ return Response.ok().entity(ExceptionHandler.tryCatchException(e)).build();
+ }
+ return null;
+ }
+}
diff --git a/server/src/main/java/org/apache/iotdb/db/rest/handler/ExceptionHandler.java b/server/src/main/java/org/apache/iotdb/db/rest/handler/ExceptionHandler.java
new file mode 100644
index 0000000..18a5263
--- /dev/null
+++ b/server/src/main/java/org/apache/iotdb/db/rest/handler/ExceptionHandler.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.iotdb.db.rest.handler;
+
+import org.apache.iotdb.db.auth.AuthException;
+import org.apache.iotdb.db.exception.IoTDBException;
+import org.apache.iotdb.db.exception.StorageEngineException;
+import org.apache.iotdb.db.exception.metadata.IllegalPathException;
+import org.apache.iotdb.db.exception.metadata.MetadataException;
+import org.apache.iotdb.db.exception.metadata.StorageGroupNotSetException;
+import org.apache.iotdb.db.exception.query.QueryProcessException;
+import org.apache.iotdb.db.rest.model.ExecutionStatus;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.ws.rs.core.Response.Status;
+
+public class ExceptionHandler {
+ private static final Logger LOGGER = LoggerFactory.getLogger(ExceptionHandler.class);
+
+ private ExceptionHandler() {}
+
+ public static ExecutionStatus tryCatchException(Exception e) {
+ ExecutionStatus responseResult = new ExecutionStatus();
+ if (e instanceof QueryProcessException) {
+ responseResult.setMessage(e.getMessage());
+ responseResult.setCode(((QueryProcessException) e).getErrorCode());
+ } else if (e instanceof StorageGroupNotSetException) {
+ responseResult.setMessage(e.getMessage());
+ responseResult.setCode(((StorageGroupNotSetException) e).getErrorCode());
+ } else if (e instanceof StorageEngineException) {
+ responseResult.setMessage(e.getMessage());
+ responseResult.setCode(((StorageEngineException) e).getErrorCode());
+ } else if (e instanceof AuthException) {
+ responseResult.setMessage(e.getMessage());
+ responseResult.setCode(Status.BAD_REQUEST.getStatusCode());
+ } else if (e instanceof IllegalPathException) {
+ responseResult.setMessage(e.getMessage());
+ responseResult.setCode(((IllegalPathException) e).getErrorCode());
+ } else if (e instanceof MetadataException) {
+ responseResult.setMessage(e.getMessage());
+ responseResult.setCode(((MetadataException) e).getErrorCode());
+ } else if (e instanceof IoTDBException) {
+ responseResult.setMessage(e.getMessage());
+ responseResult.setCode(((IoTDBException) e).getErrorCode());
+ } else {
+ responseResult.setMessage(e.getMessage());
+ responseResult.setCode(Status.INTERNAL_SERVER_ERROR.getStatusCode());
+ }
+ LOGGER.warn(e.getMessage(), e);
+ return responseResult;
+ }
+}
diff --git a/server/src/main/java/org/apache/iotdb/db/rest/handler/PhysicalPlanConstructionHandler.java b/server/src/main/java/org/apache/iotdb/db/rest/handler/PhysicalPlanConstructionHandler.java
new file mode 100644
index 0000000..0f2ec43
--- /dev/null
+++ b/server/src/main/java/org/apache/iotdb/db/rest/handler/PhysicalPlanConstructionHandler.java
@@ -0,0 +1,156 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.iotdb.db.rest.handler;
+
+import org.apache.iotdb.db.exception.WriteProcessRejectException;
+import org.apache.iotdb.db.exception.metadata.IllegalPathException;
+import org.apache.iotdb.db.metadata.PartialPath;
+import org.apache.iotdb.db.qp.physical.crud.InsertTabletPlan;
+import org.apache.iotdb.db.rest.model.InsertTabletRequest;
+import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
+import org.apache.iotdb.tsfile.utils.Binary;
+import org.apache.iotdb.tsfile.utils.BitMap;
+
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+
+public class PhysicalPlanConstructionHandler {
+ private PhysicalPlanConstructionHandler() {}
+
+ public static InsertTabletPlan constructInsertTabletPlan(InsertTabletRequest insertTabletRequest)
+ throws IllegalPathException, WriteProcessRejectException {
+ InsertTabletPlan insertTabletPlan =
+ new InsertTabletPlan(
+ new PartialPath(insertTabletRequest.getDeviceId()),
+ insertTabletRequest.getMeasurements());
+ List<List<Object>> rawData = insertTabletRequest.getValues();
+ List<String> rawDataType = insertTabletRequest.getDataTypes();
+
+ int rowSize = insertTabletRequest.getTimestamps().size();
+ int columnSize = rawDataType.size();
+
+ Object[] columns = new Object[columnSize];
+ BitMap[] bitMaps = new BitMap[columnSize];
+ TSDataType[] dataTypes = new TSDataType[columnSize];
+
+ for (int i = 0; i < columnSize; i++) {
+ dataTypes[i] = TSDataType.valueOf(rawDataType.get(i));
+ }
+
+ for (int columnIndex = 0; columnIndex < columnSize; columnIndex++) {
+ bitMaps[columnIndex] = new BitMap(rowSize);
+ switch (dataTypes[columnIndex]) {
+ case BOOLEAN:
+ boolean[] booleanValues = new boolean[rowSize];
+ for (int rowIndex = 0; rowIndex < rowSize; rowIndex++) {
+ if (rawData.get(columnIndex).get(rowIndex) == null) {
+ bitMaps[columnIndex].mark(rowIndex);
+ } else {
+ booleanValues[rowIndex] = (Boolean) rawData.get(columnIndex).get(rowIndex);
+ }
+ }
+ columns[columnIndex] = booleanValues;
+ break;
+ case INT32:
+ int[] intValues = new int[rowSize];
+ for (int rowIndex = 0; rowIndex < rowSize; rowIndex++) {
+ Object object = rawData.get(columnIndex).get(rowIndex);
+ if (object == null) {
+ bitMaps[columnIndex].mark(rowIndex);
+ } else if (object instanceof Integer) {
+ intValues[rowIndex] = (int) object;
+ } else {
+ throw new WriteProcessRejectException(
+ "unsupported data type: " + object.getClass().toString());
+ }
+ }
+ columns[columnIndex] = intValues;
+ break;
+ case INT64:
+ long[] longValues = new long[rowSize];
+ for (int rowIndex = 0; rowIndex < rowSize; rowIndex++) {
+ Object object = rawData.get(columnIndex).get(rowIndex);
+ if (object == null) {
+ bitMaps[columnIndex].mark(rowIndex);
+ } else if (object instanceof Integer) {
+ longValues[rowIndex] = (int) object;
+ } else if (object instanceof Long) {
+ longValues[rowIndex] = (long) object;
+ } else {
+ throw new WriteProcessRejectException(
+ "unsupported data type: " + object.getClass().toString());
+ }
+ }
+ columns[columnIndex] = longValues;
+ break;
+ case FLOAT:
+ float[] floatValues = new float[rowSize];
+ for (int rowIndex = 0; rowIndex < rowSize; rowIndex++) {
+ if (rawData.get(columnIndex).get(rowIndex) == null) {
+ bitMaps[columnIndex].mark(rowIndex);
+ } else {
+ floatValues[rowIndex] =
+ ((Double) rawData.get(columnIndex).get(rowIndex)).floatValue();
+ }
+ }
+ columns[columnIndex] = floatValues;
+ break;
+ case DOUBLE:
+ double[] doubleValues = new double[rowSize];
+ for (int rowIndex = 0; rowIndex < rowSize; rowIndex++) {
+ if (rawData.get(columnIndex).get(rowIndex) == null) {
+ bitMaps[columnIndex].mark(rowIndex);
+ } else {
+ doubleValues[rowIndex] = (double) rawData.get(columnIndex).get(rowIndex);
+ }
+ }
+ columns[columnIndex] = doubleValues;
+ break;
+ case TEXT:
+ Binary[] binaryValues = new Binary[rowSize];
+ for (int rowIndex = 0; rowIndex < rowSize; rowIndex++) {
+ if (rawData.get(columnIndex).get(rowIndex) == null) {
+ bitMaps[columnIndex].mark(rowIndex);
+ binaryValues[rowIndex] = new Binary("".getBytes(StandardCharsets.UTF_8));
+ } else {
+ binaryValues[rowIndex] =
+ new Binary(
+ rawData
+ .get(columnIndex)
+ .get(rowIndex)
+ .toString()
+ .getBytes(StandardCharsets.UTF_8));
+ }
+ }
+ columns[columnIndex] = binaryValues;
+ break;
+ default:
+ throw new IllegalArgumentException("Invalid input: " + rawDataType.get(columnIndex));
+ }
+ }
+
+ insertTabletPlan.setTimes(
+ insertTabletRequest.getTimestamps().stream().mapToLong(Long::longValue).toArray());
+ insertTabletPlan.setColumns(columns);
+ insertTabletPlan.setBitMaps(bitMaps);
+ insertTabletPlan.setRowCount(insertTabletRequest.getTimestamps().size());
+ insertTabletPlan.setDataTypes(dataTypes);
+ insertTabletPlan.setAligned(insertTabletRequest.getIsAligned());
+ return insertTabletPlan;
+ }
+}
diff --git a/server/src/main/java/org/apache/iotdb/db/rest/handler/QueryDataSetHandler.java b/server/src/main/java/org/apache/iotdb/db/rest/handler/QueryDataSetHandler.java
new file mode 100644
index 0000000..e0ebb8b
--- /dev/null
+++ b/server/src/main/java/org/apache/iotdb/db/rest/handler/QueryDataSetHandler.java
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.iotdb.db.rest.handler;
+
+import org.apache.iotdb.db.exception.StorageEngineException;
+import org.apache.iotdb.db.exception.metadata.MetadataException;
+import org.apache.iotdb.db.exception.query.QueryProcessException;
+import org.apache.iotdb.db.qp.executor.IPlanExecutor;
+import org.apache.iotdb.db.qp.physical.PhysicalPlan;
+import org.apache.iotdb.db.qp.physical.crud.QueryPlan;
+import org.apache.iotdb.db.query.context.QueryContext;
+import org.apache.iotdb.db.query.control.QueryResourceManager;
+import org.apache.iotdb.db.query.expression.ResultColumn;
+import org.apache.iotdb.tsfile.exception.filter.QueryFilterOptimizationException;
+import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
+import org.apache.iotdb.tsfile.read.common.Field;
+import org.apache.iotdb.tsfile.read.common.RowRecord;
+import org.apache.iotdb.tsfile.read.query.dataset.QueryDataSet;
+
+import org.apache.thrift.TException;
+
+import javax.ws.rs.core.Response;
+
+import java.io.IOException;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+public class QueryDataSetHandler {
+
+ public static Response fillDateSet(QueryDataSet dataSet, QueryPlan queryPlan) {
+ org.apache.iotdb.db.rest.model.QueryDataSet queryDataSet =
+ new org.apache.iotdb.db.rest.model.QueryDataSet();
+
+ try {
+ List<ResultColumn> resultColumns = queryPlan.getResultColumns();
+ int[] dataSetIndexes = new int[resultColumns.size()];
+ Map<String, Integer> sourcePathToQueryDataSetIndex = queryPlan.getPathToIndex();
+ for (int i = 0; i < resultColumns.size(); i++) {
+ ResultColumn resultColumn = resultColumns.get(i);
+ queryDataSet.addExpressionsItem(resultColumn.getResultColumnName());
+ queryDataSet.addValuesItem(new ArrayList<>());
+ dataSetIndexes[i] = sourcePathToQueryDataSetIndex.get(resultColumn.getResultColumnName());
+ }
+
+ while (dataSet.hasNext()) {
+ RowRecord rowRecord = dataSet.next();
+ List<Field> fields = rowRecord.getFields();
+
+ queryDataSet.addTimestampsItem(rowRecord.getTimestamp());
+ for (int i = 0; i < resultColumns.size(); i++) {
+ List<Object> values = queryDataSet.getValues().get(i);
+ Field field = fields.get(dataSetIndexes[i]);
+ if (field == null || field.getDataType() == null) {
+ values.add(null);
+ } else {
+ values.add(
+ field.getDataType().equals(TSDataType.TEXT)
+ ? field.getStringValue()
+ : field.getObjectValue(field.getDataType()));
+ }
+ }
+ }
+ } catch (IOException e) {
+ return Response.ok().entity(ExceptionHandler.tryCatchException(e)).build();
+ }
+ return Response.ok().entity(queryDataSet).build();
+ }
+
+ public static QueryDataSet constructQueryDataSet(
+ IPlanExecutor executor, PhysicalPlan physicalPlan)
+ throws TException, StorageEngineException, QueryFilterOptimizationException,
+ MetadataException, IOException, InterruptedException, SQLException,
+ QueryProcessException {
+ long queryId = QueryResourceManager.getInstance().assignQueryId(true);
+ QueryContext context = new QueryContext(queryId);
+ return executor.processQuery(physicalPlan, context);
+ }
+}
diff --git a/server/src/main/java/org/apache/iotdb/db/rest/handler/RequestValidationHandler.java b/server/src/main/java/org/apache/iotdb/db/rest/handler/RequestValidationHandler.java
new file mode 100644
index 0000000..27a06e1
--- /dev/null
+++ b/server/src/main/java/org/apache/iotdb/db/rest/handler/RequestValidationHandler.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.iotdb.db.rest.handler;
+
+import org.apache.iotdb.db.rest.model.InsertTabletRequest;
+import org.apache.iotdb.db.rest.model.SQL;
+
+import java.util.Objects;
+
+public class RequestValidationHandler {
+ private RequestValidationHandler() {}
+
+ public static void validateSQL(SQL sql) {
+ Objects.requireNonNull(sql.getSql(), "sql should not be null");
+ }
+
+ public static void validateInsertTabletRequest(InsertTabletRequest insertTabletRequest) {
+ Objects.requireNonNull(insertTabletRequest.getTimestamps(), "timestamps should not be null");
+ Objects.requireNonNull(insertTabletRequest.getIsAligned(), "isAligned should not be null");
+ Objects.requireNonNull(insertTabletRequest.getDeviceId(), "deviceId should not be null");
+ Objects.requireNonNull(insertTabletRequest.getDataTypes(), "dataTypes should not be null");
+ Objects.requireNonNull(insertTabletRequest.getValues(), "values should not be null");
+ }
+}
diff --git a/server/src/main/java/org/apache/iotdb/db/rest/impl/PingApiServiceImpl.java b/server/src/main/java/org/apache/iotdb/db/rest/impl/PingApiServiceImpl.java
new file mode 100644
index 0000000..d4df79e
--- /dev/null
+++ b/server/src/main/java/org/apache/iotdb/db/rest/impl/PingApiServiceImpl.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.iotdb.db.rest.impl;
+
+import org.apache.iotdb.db.rest.PingApiService;
+import org.apache.iotdb.db.rest.model.ExecutionStatus;
+import org.apache.iotdb.rpc.TSStatusCode;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.SecurityContext;
+
+public class PingApiServiceImpl extends PingApiService {
+ @Override
+ public Response tryPing(SecurityContext securityContext) {
+ return Response.ok()
+ .entity(
+ new ExecutionStatus()
+ .code(TSStatusCode.SUCCESS_STATUS.getStatusCode())
+ .message(TSStatusCode.SUCCESS_STATUS.name()))
+ .build();
+ }
+}
diff --git a/server/src/main/java/org/apache/iotdb/db/rest/impl/RestApiServiceImpl.java b/server/src/main/java/org/apache/iotdb/db/rest/impl/RestApiServiceImpl.java
new file mode 100644
index 0000000..c5c5582
--- /dev/null
+++ b/server/src/main/java/org/apache/iotdb/db/rest/impl/RestApiServiceImpl.java
@@ -0,0 +1,144 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.iotdb.db.rest.impl;
+
+import org.apache.iotdb.db.conf.IoTDBDescriptor;
+import org.apache.iotdb.db.exception.StorageEngineException;
+import org.apache.iotdb.db.exception.metadata.StorageGroupNotSetException;
+import org.apache.iotdb.db.exception.query.QueryProcessException;
+import org.apache.iotdb.db.qp.Planner;
+import org.apache.iotdb.db.qp.executor.IPlanExecutor;
+import org.apache.iotdb.db.qp.executor.PlanExecutor;
+import org.apache.iotdb.db.qp.physical.PhysicalPlan;
+import org.apache.iotdb.db.qp.physical.crud.InsertTabletPlan;
+import org.apache.iotdb.db.qp.physical.crud.QueryPlan;
+import org.apache.iotdb.db.qp.physical.sys.FlushPlan;
+import org.apache.iotdb.db.qp.physical.sys.SetSystemModePlan;
+import org.apache.iotdb.db.rest.RestApiService;
+import org.apache.iotdb.db.rest.handler.AuthorizationHandler;
+import org.apache.iotdb.db.rest.handler.ExceptionHandler;
+import org.apache.iotdb.db.rest.handler.PhysicalPlanConstructionHandler;
+import org.apache.iotdb.db.rest.handler.QueryDataSetHandler;
+import org.apache.iotdb.db.rest.handler.RequestValidationHandler;
+import org.apache.iotdb.db.rest.model.ExecutionStatus;
+import org.apache.iotdb.db.rest.model.InsertTabletRequest;
+import org.apache.iotdb.db.rest.model.SQL;
+import org.apache.iotdb.rpc.TSStatusCode;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.SecurityContext;
+
+public class RestApiServiceImpl extends RestApiService {
+
+ protected final IPlanExecutor executor = new PlanExecutor(); // todo cluster
+ protected final Planner planner = new Planner();
+
+ public RestApiServiceImpl() throws QueryProcessException {}
+
+ @Override
+ public Response executeNonQueryStatement(SQL sql, SecurityContext securityContext) {
+ try {
+ RequestValidationHandler.validateSQL(sql);
+
+ PhysicalPlan physicalPlan = planner.parseSQLToPhysicalPlan(sql.getSql());
+ Response response = AuthorizationHandler.checkAuthority(securityContext, physicalPlan);
+ if (response != null) {
+ return response;
+ }
+ return Response.ok()
+ .entity(
+ executor.processNonQuery(physicalPlan)
+ ? new ExecutionStatus()
+ .code(TSStatusCode.SUCCESS_STATUS.getStatusCode())
+ .message(TSStatusCode.SUCCESS_STATUS.name())
+ : new ExecutionStatus()
+ .code(TSStatusCode.EXECUTE_STATEMENT_ERROR.getStatusCode())
+ .message(TSStatusCode.EXECUTE_STATEMENT_ERROR.name()))
+ .build();
+ } catch (Exception e) {
+ return Response.ok().entity(ExceptionHandler.tryCatchException(e)).build();
+ }
+ }
+
+ @Override
+ public Response executeQueryStatement(SQL sql, SecurityContext securityContext) {
+ try {
+ RequestValidationHandler.validateSQL(sql);
+
+ PhysicalPlan physicalPlan = planner.parseSQLToPhysicalPlan(sql.getSql());
+ if (!(physicalPlan instanceof QueryPlan)) {
+ return Response.ok()
+ .entity(
+ new ExecutionStatus()
+ .code(TSStatusCode.EXECUTE_STATEMENT_ERROR.getStatusCode())
+ .message(TSStatusCode.EXECUTE_STATEMENT_ERROR.name()))
+ .build();
+ }
+
+ Response response = AuthorizationHandler.checkAuthority(securityContext, physicalPlan);
+ if (response != null) {
+ return response;
+ }
+ return QueryDataSetHandler.fillDateSet(
+ QueryDataSetHandler.constructQueryDataSet(executor, physicalPlan),
+ (QueryPlan) physicalPlan);
+ } catch (Exception e) {
+ return Response.ok().entity(ExceptionHandler.tryCatchException(e)).build();
+ }
+ }
+
+ @Override
+ public Response insertTablet(
+ InsertTabletRequest insertTabletRequest, SecurityContext securityContext) {
+ try {
+ RequestValidationHandler.validateInsertTabletRequest(insertTabletRequest);
+
+ InsertTabletPlan insertTabletPlan =
+ PhysicalPlanConstructionHandler.constructInsertTabletPlan(insertTabletRequest);
+
+ Response response = AuthorizationHandler.checkAuthority(securityContext, insertTabletPlan);
+ if (response != null) {
+ return response;
+ }
+
+ return Response.ok()
+ .entity(
+ executeNonQuery(insertTabletPlan)
+ ? new ExecutionStatus()
+ .code(TSStatusCode.SUCCESS_STATUS.getStatusCode())
+ .message(TSStatusCode.SUCCESS_STATUS.name())
+ : new ExecutionStatus()
+ .code(TSStatusCode.WRITE_PROCESS_ERROR.getStatusCode())
+ .message(TSStatusCode.WRITE_PROCESS_ERROR.name()))
+ .build();
+ } catch (Exception e) {
+ return Response.ok().entity(ExceptionHandler.tryCatchException(e)).build();
+ }
+ }
+
+ private boolean executeNonQuery(PhysicalPlan plan)
+ throws QueryProcessException, StorageGroupNotSetException, StorageEngineException {
+ if (!(plan instanceof SetSystemModePlan)
+ && !(plan instanceof FlushPlan)
+ && IoTDBDescriptor.getInstance().getConfig().isReadOnly()) {
+ throw new QueryProcessException(
+ "Current system mode is read-only, does not support non-query operation");
+ }
+ return executor.processNonQuery(plan);
+ }
+}
diff --git a/server/src/main/java/org/apache/iotdb/db/service/IoTDB.java b/server/src/main/java/org/apache/iotdb/db/service/IoTDB.java
index 29afb31..6ad6d5d 100644
--- a/server/src/main/java/org/apache/iotdb/db/service/IoTDB.java
+++ b/server/src/main/java/org/apache/iotdb/db/service/IoTDB.java
@@ -22,6 +22,8 @@ import org.apache.iotdb.db.concurrent.IoTDBDefaultThreadExceptionHandler;
import org.apache.iotdb.db.conf.IoTDBConfigCheck;
import org.apache.iotdb.db.conf.IoTDBConstant;
import org.apache.iotdb.db.conf.IoTDBDescriptor;
+import org.apache.iotdb.db.conf.rest.IoTDBRestServiceCheck;
+import org.apache.iotdb.db.conf.rest.IoTDBRestServiceDescriptor;
import org.apache.iotdb.db.cost.statistic.Measurement;
import org.apache.iotdb.db.cq.ContinuousQueryService;
import org.apache.iotdb.db.engine.StorageEngine;
@@ -40,6 +42,7 @@ import org.apache.iotdb.db.query.udf.service.UDFRegistrationService;
import org.apache.iotdb.db.rescon.PrimitiveArrayManager;
import org.apache.iotdb.db.rescon.SystemInfo;
import org.apache.iotdb.db.rescon.TVListAllocator;
+import org.apache.iotdb.db.rest.RestService;
import org.apache.iotdb.db.sync.receiver.SyncServerManager;
import org.apache.iotdb.db.writelog.manager.MultiFileLogNodeManager;
@@ -64,6 +67,7 @@ public class IoTDB implements IoTDBMBean {
public static void main(String[] args) {
try {
IoTDBConfigCheck.getInstance().checkConfig();
+ IoTDBRestServiceCheck.getInstance().checkConfig();
} catch (ConfigurationException | IOException e) {
logger.error("meet error when doing start checking", e);
System.exit(1);
@@ -139,7 +143,9 @@ public class IoTDB implements IoTDBMBean {
if (IoTDBDescriptor.getInstance().getConfig().isEnableMQTTService()) {
registerManager.register(MQTTService.getInstance());
}
-
+ if (IoTDBRestServiceDescriptor.getInstance().getConfig().isEnableRestService()) {
+ registerManager.register(RestService.getInstance());
+ }
logger.info("IoTDB is set up, now may some sgs are not ready, please wait several seconds...");
while (!StorageEngine.getInstance().isAllSgReady()) {
diff --git a/server/src/main/java/org/apache/iotdb/db/service/ServiceType.java b/server/src/main/java/org/apache/iotdb/db/service/ServiceType.java
index e3c53f0..7513291 100644
--- a/server/src/main/java/org/apache/iotdb/db/service/ServiceType.java
+++ b/server/src/main/java/org/apache/iotdb/db/service/ServiceType.java
@@ -56,7 +56,6 @@ public enum ServiceType {
SYSTEMINFO_SERVICE("MemTable Monitor Service", "MemTable, Monitor"),
CONTINUOUS_QUERY_SERVICE("Continuous Query Service", "Continuous Query Service"),
CLUSTER_INFO_SERVICE("Cluster Monitor Service (thrift-based)", "Cluster Monitor-Thrift"),
-
CLUSTER_RPC_SERVICE("Cluster RPC Service", "ClusterRPCService"),
CLUSTER_META_RPC_SERVICE("Cluster Meta RPC Service", "ClusterMetaRPCService"),
CLUSTER_META_HEART_BEAT_RPC_SERVICE(
@@ -66,7 +65,7 @@ public enum ServiceType {
"Cluster Data Heartbeat RPC Service", "ClusterDataHeartbeatRPCService"),
CLUSTER_META_ENGINE("Cluster Meta Engine", "ClusterMetaEngine"),
CLUSTER_DATA_ENGINE("Cluster Data Engine", "ClusterDataEngine"),
- ;
+ REST_SERVICE("REST Service", "REST Service");
private final String name;
private final String jmxName;
diff --git a/server/src/test/java/org/apache/iotdb/db/rest/IoTDBRestServiceIT.java b/server/src/test/java/org/apache/iotdb/db/rest/IoTDBRestServiceIT.java
new file mode 100644
index 0000000..c0496a8
--- /dev/null
+++ b/server/src/test/java/org/apache/iotdb/db/rest/IoTDBRestServiceIT.java
@@ -0,0 +1,280 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.iotdb.db.rest;
+
+import org.apache.iotdb.db.utils.EnvironmentUtils;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import org.apache.http.HttpEntity;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.util.EntityUtils;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Base64;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+@FixMethodOrder(MethodSorters.JVM)
+public class IoTDBRestServiceIT {
+ @Before
+ public void setUp() throws Exception {
+ EnvironmentUtils.closeStatMonitor();
+ EnvironmentUtils.envSetUp();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ EnvironmentUtils.cleanEnv();
+ }
+
+ private String getAuthorization(String username, String password) {
+ return Base64.getEncoder()
+ .encodeToString((username + ":" + password).getBytes(StandardCharsets.UTF_8));
+ }
+
+ @Test
+ public void ping() {
+ CloseableHttpClient httpClient = HttpClientBuilder.create().build();
+ HttpGet httpGet = new HttpGet("http://127.0.0.1:18080/ping");
+ CloseableHttpResponse response = null;
+ try {
+ String authorization = getAuthorization("root", "root");
+ httpGet.setHeader("Authorization", authorization);
+ response = httpClient.execute(httpGet);
+ HttpEntity responseEntity = response.getEntity();
+ String message = EntityUtils.toString(responseEntity, "utf-8");
+ JsonObject result = JsonParser.parseString(message).getAsJsonObject();
+ assertEquals(200, Integer.parseInt(result.get("code").toString()));
+ } catch (IOException e) {
+ e.printStackTrace();
+ fail(e.getMessage());
+ } finally {
+ try {
+ if (httpClient != null) {
+ httpClient.close();
+ }
+ if (response != null) {
+ response.close();
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ fail(e.getMessage());
+ }
+ }
+ }
+
+ private HttpPost getHttpPost(String url) {
+ HttpPost httpPost = new HttpPost(url);
+ httpPost.addHeader("Content-type", "application/json; charset=utf-8");
+ httpPost.setHeader("Accept", "application/json");
+ String authorization = getAuthorization("root", "root");
+ httpPost.setHeader("Authorization", authorization);
+ return httpPost;
+ }
+
+ public void rightInsertTablet(CloseableHttpClient httpClient) {
+ CloseableHttpResponse response = null;
+ try {
+ HttpPost httpPost = getHttpPost("http://127.0.0.1:18080/rest/v1/insertTablet");
+ String json =
+ "{\"timestamps\":[1635232143960,1635232153960],\"measurements\":[\"s3\",\"s4\",\"s5\",\"s6\",\"s7\",\"s8\"],\"dataTypes\":[\"TEXT\",\"INT32\",\"INT64\",\"FLOAT\",\"BOOLEAN\",\"DOUBLE\"],\"values\":[[\"2aa\",\"\"],[11,2],[1635000012345555,1635000012345556],[1.41,null],[null,false],[null,3.5555]],\"isAligned\":false,\"deviceId\":\"root.sg25\"}";
+ httpPost.setEntity(new StringEntity(json, Charset.defaultCharset()));
+ response = httpClient.execute(httpPost);
+ HttpEntity responseEntity = response.getEntity();
+ String message = EntityUtils.toString(responseEntity, "utf-8");
+ JsonObject result = JsonParser.parseString(message).getAsJsonObject();
+ assertEquals(200, Integer.parseInt(result.get("code").toString()));
+ } catch (IOException e) {
+ e.printStackTrace();
+ fail(e.getMessage());
+ } finally {
+ try {
+ if (response != null) {
+ response.close();
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ fail(e.getMessage());
+ }
+ }
+ }
+
+ @Test
+ public void errorInsertTablet() {
+ CloseableHttpResponse response = null;
+ CloseableHttpClient httpClient = HttpClientBuilder.create().build();
+ try {
+ HttpPost httpPost = getHttpPost("http://127.0.0.1:18080/rest/v1/insertTablet");
+ String json =
+ "{\"timestamps\":[1635232143960,1635232153960],\"measurements\":[\"s3\",\"s4\",\"s5\",\"s6\",\"s7\",\"s8\"],\"dataTypes\":[\"TEXT\",\"INT32\",\"INT64\",\"FLOAT\",\"BOOLEAN\",\"DOUBLE\"],\"values\":[[\"2aa\",\"\"],[111111112312312442352545452323123,2],[16,15],[1.41,null],[null,false],[null,3.55555555555555555555555555555555555555555555312234235345123127318927461482308478123645555555555555555555555555555555555555555555531223423534512312731892746148230847812364]],\"isAligned\":fal [...]
+ httpPost.setEntity(new StringEntity(json, Charset.defaultCharset()));
+ response = httpClient.execute(httpPost);
+ HttpEntity responseEntity = response.getEntity();
+ String message = EntityUtils.toString(responseEntity, "utf-8");
+ JsonObject result = JsonParser.parseString(message).getAsJsonObject();
+ assertEquals(413, Integer.parseInt(result.get("code").toString()));
+ } catch (IOException e) {
+ e.printStackTrace();
+ fail(e.getMessage());
+ } finally {
+ try {
+ if (response != null) {
+ response.close();
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ fail(e.getMessage());
+ }
+ }
+ }
+
+ @Test
+ public void insertAndQuery() {
+ CloseableHttpClient httpClient = HttpClientBuilder.create().build();
+ rightInsertTablet(httpClient);
+ query(httpClient);
+ try {
+ httpClient.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ fail(e.getMessage());
+ }
+ }
+
+ public void query(CloseableHttpClient httpClient) {
+ CloseableHttpResponse response = null;
+ try {
+ HttpPost httpPost = getHttpPost("http://127.0.0.1:18080/rest/v1/query");
+ String sql = "{\"sql\":\"select *,s4+1,s4+1 from root.sg25\"}";
+ httpPost.setEntity(new StringEntity(sql, Charset.defaultCharset()));
+ response = httpClient.execute(httpPost);
+ HttpEntity responseEntity = response.getEntity();
+ String message = EntityUtils.toString(responseEntity, "utf-8");
+ ObjectMapper mapper = new ObjectMapper();
+ Map map = mapper.readValue(message, Map.class);
+ List<Long> timestampsResult = (List<Long>) map.get("timestamps");
+ List<Long> expressionsResult = (List<Long>) map.get("expressions");
+ List<List<Object>> valuesResult = (List<List<Object>>) map.get("values");
+ Assert.assertTrue(map.size() > 0);
+ List<Object> expressions =
+ new ArrayList<Object>() {
+ {
+ add("root.sg25.s3");
+ add("root.sg25.s4");
+ add("root.sg25.s5");
+ add("root.sg25.s6");
+ add("root.sg25.s7");
+ add("root.sg25.s8");
+ add("root.sg25.s4 + 1");
+ add("root.sg25.s4 + 1");
+ }
+ };
+ List<Object> timestamps =
+ new ArrayList<Object>() {
+ {
+ add(1635232143960l);
+ add(1635232153960l);
+ }
+ };
+ List<Object> values1 =
+ new ArrayList<Object>() {
+ {
+ add("2aa");
+ add("");
+ }
+ };
+ List<Object> values2 =
+ new ArrayList<Object>() {
+ {
+ add(11);
+ add(2);
+ }
+ };
+ List<Object> values3 =
+ new ArrayList<Object>() {
+ {
+ add(1635000012345555l);
+ add(1635000012345556l);
+ }
+ };
+
+ List<Object> values4 =
+ new ArrayList<Object>() {
+ {
+ add(1.41);
+ add(null);
+ }
+ };
+ List<Object> values5 =
+ new ArrayList<Object>() {
+ {
+ add(null);
+ add(false);
+ }
+ };
+ List<Object> values6 =
+ new ArrayList<Object>() {
+ {
+ add(null);
+ add(3.5555);
+ }
+ };
+
+ Assert.assertEquals(expressions, expressionsResult);
+ Assert.assertEquals(timestamps, timestampsResult);
+ Assert.assertEquals(values1, valuesResult.get(0));
+ Assert.assertEquals(values2, valuesResult.get(1));
+ Assert.assertEquals(values3, valuesResult.get(2));
+ Assert.assertEquals(values4, valuesResult.get(3));
+ Assert.assertEquals(values5, valuesResult.get(4));
+ Assert.assertEquals(values6, valuesResult.get(5));
+ } catch (IOException e) {
+ e.printStackTrace();
+ fail(e.getMessage());
+ } finally {
+ try {
+ if (response != null) {
+ response.close();
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ fail(e.getMessage());
+ }
+ }
+ }
+}
diff --git a/server/src/test/resources/iotdb-rest.properties b/server/src/test/resources/iotdb-rest.properties
new file mode 100644
index 0000000..865f3ab
--- /dev/null
+++ b/server/src/test/resources/iotdb-rest.properties
@@ -0,0 +1,55 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+####################
+### REST Service Configuration
+####################
+
+# Is the REST service enabled
+enable_rest_service=true
+
+# the binding port of the REST service
+# rest_service_port=18080
+
+# is SSL enabled
+# enable_https=false
+
+# SSL key store path
+# key_store_path=
+
+# SSL key store password
+# key_store_pwd=
+
+# SSL trust store path
+# trust_store_path=
+
+# SSL trust store password.
+# trust_store_pwd=
+
+# SSL timeout (in seconds)
+# idle_timeout_in_seconds=50000
+
+# the expiration time of the user login infomation cache (in seconds)
+# cache_expire_in_seconds=28800
+
+# maximum number of users can be stored in the user login cache.
+# cache_max_num=100
+
+# init capacity of users can be stored in the user login cache.
+# cache_init_num=10
diff --git a/site/src/main/.vuepress/config.js b/site/src/main/.vuepress/config.js
index c3ea509..78160ab 100644
--- a/site/src/main/.vuepress/config.js
+++ b/site/src/main/.vuepress/config.js
@@ -1524,6 +1524,7 @@ var config = {
children: [
['Communication-Service-Protocol/Programming-Thrift','Thrift'],
['Communication-Service-Protocol/Programming-MQTT','MQTT'],
+ ['Communication-Service-Protocol/RestService','REST'],
]
},
{