Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 0784aba

Browse files
Merge pull request #345 from lowcoder-org/rest_api_timeout
fix: honor timeout set for REST API calls
2 parents 1c11818 + 6025a80 commit 0784aba

File tree

13 files changed

+99
-42
lines changed

13 files changed

+99
-42
lines changed

‎deploy/docker/README.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ Image can be configured by setting environment variables.
3737
| `ENCRYPTION_SALT` | Salt used for encrypting password | `lowcoder.org` |
3838
| `CORS_ALLOWED_DOMAINS` | CORS allowed domains | `*` |
3939
| `LOWCODER_MAX_REQUEST_SIZE` | Lowcoder max request size | `20m` |
40+
| `LOWCODER_MAX_QUERY_TIMEOUT` | Lowcoder max query timeout (in seconds) | `120` |
4041
| `LOWCODER_API_SERVICE_URL` | Lowcoder API service URL | `http://localhost:8080` |
4142
| `LOWCODER_NODE_SERVICE_URL` | Lowcoder Node service (js executor) URL | `http://localhost:6060` |
4243
| `DEFAULT_ORGS_PER_USER` | Default maximum organizations per user | `100` |
@@ -77,6 +78,8 @@ Image can be configured by setting environment variables.
7778
| `DEFAULT_ORG_GROUP_COUNT` | Default maximum groups per organization | `100` |
7879
| `DEFAULT_ORG_APP_COUNT` | Default maximum applications per organization | `1000` |
7980
| `DEFAULT_DEVELOPER_COUNT` | Default maximum developers | `100` |
81+
| `LOWCODER_MAX_QUERY_TIMEOUT` | Lowcoder max query timeout (in seconds) | `120` |
82+
| `LOWCODER_MAX_REQUEST_SIZE` | Lowcoder max request size | `20m` |
8083

8184

8285

@@ -122,6 +125,7 @@ Image can be configured by setting environment variables.
122125
| --------------------------------| --------------------------------------------------------------------| ------------------------------------------------------- |
123126
| `PUID` | ID of user running services. It will own all created logs and data. | `9001` |
124127
| `PGID` | ID of group of the user running services. | `9001` |
128+
| `LOWCODER_MAX_QUERY_TIMEOUT` | Lowcoder max query timeout (in seconds) | `120` |
125129
| `LOWCODER_MAX_REQUEST_SIZE` | Lowcoder max request size | `20m` |
126130
| `LOWCODER_API_SERVICE_URL` | Lowcoder API service URL | `http://localhost:8080` |
127131
| `LOWCODER_NODE_SERVICE_URL` | Lowcoder Node service (js executor) URL | `http://localhost:6060` |

‎deploy/docker/docker-compose-multi.yaml‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ services:
3636
MONGODB_URL: "mongodb://lowcoder:secret123@mongodb/lowcoder?authSource=admin"
3737
REDIS_URL: "redis://redis:6379"
3838
LOWCODER_NODE_SERVICE_URL: "http://lowcoder-node-service:6060"
39+
LOWCODER_MAX_QUERY_TIMEOUT: 120
3940
ENABLE_USER_SIGN_UP: "true"
4041
ENCRYPTION_PASSWORD: "lowcoder.org"
4142
ENCRYPTION_SALT: "lowcoder.org"
@@ -76,6 +77,7 @@ services:
7677
PUID: "9001"
7778
PGID: "9001"
7879
LOWCODER_MAX_REQUEST_SIZE: 20m
80+
LOWCODER_MAX_QUERY_TIMEOUT: 120
7981
LOWCODER_API_SERVICE_URL: "http://lowcoder-api-service:8080"
8082
LOWCODER_NODE_SERVICE_URL: "http://lowcoder-node-service:6060"
8183
restart: unless-stopped

‎deploy/docker/docker-compose.yaml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ services:
3838
LOWCODER_NODE_SERVICE_URL: "http://localhost:6060"
3939
# frontend parameters
4040
LOWCODER_MAX_REQUEST_SIZE: 20m
41+
LOWCODER_MAX_QUERY_TIMEOUT: 120
4142
volumes:
4243
- ./lowcoder-stacks:/lowcoder-stacks
4344
restart: unless-stopped

‎deploy/docker/frontend/01-update-nginx-conf.sh‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ else
1818
ln -s /etc/nginx/nginx-http.conf /etc/nginx/nginx.conf
1919
fi;
2020

21+
sed -i "s@__LOWCODER_MAX_QUERY_TIMEOUT__@${LOWCODER_MAX_QUERY_TIMEOUT:=120}@" /etc/nginx/nginx.conf
2122
sed -i "s@__LOWCODER_MAX_REQUEST_SIZE__@${LOWCODER_MAX_REQUEST_SIZE:=20m}@" /etc/nginx/nginx.conf
2223
sed -i "s@__LOWCODER_API_SERVICE_URL__@${LOWCODER_API_SERVICE_URL:=http://localhost:8080}@" /etc/nginx/nginx.conf
2324
sed -i "s@__LOWCODER_NODE_SERVICE_URL__@${LOWCODER_NODE_SERVICE_URL:=http://localhost:6060}@" /etc/nginx/nginx.conf

‎deploy/docker/frontend/nginx-http.conf‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ http {
3535
listen 3000 default_server;
3636
root /lowcoder/client;
3737

38+
proxy_connect_timeout __LOWCODER_MAX_QUERY_TIMEOUT__;
39+
proxy_send_timeout __LOWCODER_MAX_QUERY_TIMEOUT__;
40+
proxy_read_timeout __LOWCODER_MAX_QUERY_TIMEOUT__;
3841

3942
location / {
4043
try_files $uri /index.html;

‎deploy/docker/frontend/nginx-https.conf‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ http {
3838
include /etc/nginx/ssl-certificate.conf;
3939
include /etc/nginx/ssl-params.conf;
4040

41+
proxy_connect_timeout __LOWCODER_MAX_QUERY_TIMEOUT__;
42+
proxy_send_timeout __LOWCODER_MAX_QUERY_TIMEOUT__;
43+
proxy_read_timeout __LOWCODER_MAX_QUERY_TIMEOUT__;
44+
4145
location / {
4246
try_files $uri /index.html;
4347

‎server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/query/service/QueryExecutionService.java‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import org.lowcoder.domain.plugin.client.DatasourcePluginClient;
1717
import org.lowcoder.domain.plugin.service.DatasourceMetaInfoService;
1818
import org.lowcoder.domain.query.util.QueryTimeoutUtils;
19+
import org.lowcoder.sdk.config.CommonConfig;
1920
import org.lowcoder.sdk.exception.BizException;
2021
import org.lowcoder.sdk.exception.PluginException;
2122
import org.lowcoder.sdk.models.QueryExecutionResult;
@@ -40,10 +41,14 @@ public class QueryExecutionService {
4041
@Autowired
4142
private DatasourcePluginClient datasourcePluginClient;
4243

44+
@Autowired
45+
private CommonConfig common;
46+
4347
public Mono<QueryExecutionResult> executeQuery(Datasource datasource, Map<String, Object> queryConfig, Map<String, Object> requestParams,
4448
String timeoutStr, QueryVisitorContext queryVisitorContext) {
4549

46-
int timeoutMs = QueryTimeoutUtils.parseQueryTimeoutMs(timeoutStr, requestParams);
50+
int timeoutMs = QueryTimeoutUtils.parseQueryTimeoutMs(timeoutStr, requestParams, common.getMaxQueryTimeout());
51+
queryConfig.putIfAbsent("timeoutMs", timeoutMs);
4752

4853
return Mono.defer(() -> {
4954
if (datasourceMetaInfoService.isJsDatasourcePlugin(datasource.getType())) {

‎server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/query/util/QueryTimeoutUtils.java‎

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,13 @@
1717
public final class QueryTimeoutUtils {
1818

1919
private static final int DEFAULT_QUERY_TIMEOUT_MILLIS = 10000;
20-
private static final int MAX_QUERY_TIMEOUT_SECONDS = 120;
2120

22-
public static int parseQueryTimeoutMs(String timeoutStr, Map<String, Object> paramMap) {
23-
return parseQueryTimeoutMs(renderMustacheString(timeoutStr, paramMap));
21+
public static int parseQueryTimeoutMs(String timeoutStr, Map<String, Object> paramMap, intmaxQueryTimeout) {
22+
return parseQueryTimeoutMs(renderMustacheString(timeoutStr, paramMap), maxQueryTimeout);
2423
}
2524

2625
@VisibleForTesting
27-
public static int parseQueryTimeoutMs(String timeoutStr) {
26+
public static int parseQueryTimeoutMs(String timeoutStr, intmaxQueryTimeout) {
2827
if (StringUtils.isBlank(timeoutStr)) {
2928
return DEFAULT_QUERY_TIMEOUT_MILLIS;
3029
}
@@ -44,10 +43,10 @@ public static int parseQueryTimeoutMs(String timeoutStr) {
4443
if (value < 0) {
4544
throw new PluginException(QUERY_ARGUMENT_ERROR, "INVALID_TIMEOUT_SETTING", timeoutStr);
4645
}
47-
46+
4847
int millis = convertToMs(value, unit);
49-
if (millis > Duration.ofSeconds(MAX_QUERY_TIMEOUT_SECONDS).toMillis()) {
50-
throw new PluginException(EXCEED_MAX_QUERY_TIMEOUT, "EXCEED_MAX_QUERY_TIMEOUT", MAX_QUERY_TIMEOUT_SECONDS);
48+
if (millis > Duration.ofSeconds(maxQueryTimeout).toMillis()) {
49+
throw new PluginException(EXCEED_MAX_QUERY_TIMEOUT, "EXCEED_MAX_QUERY_TIMEOUT", maxQueryTimeout);
5150
}
5251

5352
return millis;

‎server/api-service/lowcoder-plugins/restApiPlugin/src/main/java/org/lowcoder/plugin/restapi/RestApiExecutor.java‎

Lines changed: 62 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,48 @@
1919

2020
package org.lowcoder.plugin.restapi;
2121

22-
import com.fasterxml.jackson.core.JsonProcessingException;
23-
import com.fasterxml.jackson.databind.JsonNode;
24-
import com.fasterxml.jackson.databind.node.ObjectNode;
25-
import com.google.common.collect.ImmutableMap;
26-
import lombok.Builder;
27-
import lombok.Getter;
22+
import static com.google.common.base.MoreObjects.firstNonNull;
23+
import static org.apache.commons.collections4.MapUtils.emptyIfNull;
24+
import static org.apache.commons.lang3.StringUtils.trimToEmpty;
25+
import static org.lowcoder.plugin.restapi.RestApiError.REST_API_EXECUTION_ERROR;
26+
import static org.lowcoder.plugin.restapi.helpers.ContentTypeHelper.isBinary;
27+
import static org.lowcoder.plugin.restapi.helpers.ContentTypeHelper.isJson;
28+
import static org.lowcoder.plugin.restapi.helpers.ContentTypeHelper.isJsonContentType;
29+
import static org.lowcoder.plugin.restapi.helpers.ContentTypeHelper.isPicture;
30+
import static org.lowcoder.plugin.restapi.helpers.ContentTypeHelper.isValidContentType;
31+
import static org.lowcoder.plugin.restapi.helpers.ContentTypeHelper.parseContentType;
32+
import static org.lowcoder.sdk.exception.PluginCommonError.JSON_PARSE_ERROR;
33+
import static org.lowcoder.sdk.exception.PluginCommonError.QUERY_ARGUMENT_ERROR;
34+
import static org.lowcoder.sdk.exception.PluginCommonError.QUERY_EXECUTION_ERROR;
35+
import static org.lowcoder.sdk.plugin.restapi.DataUtils.convertToMultiformFileValue;
36+
import static org.lowcoder.sdk.plugin.restapi.auth.RestApiAuthType.DIGEST_AUTH;
37+
import static org.lowcoder.sdk.plugin.restapi.auth.RestApiAuthType.OAUTH2_INHERIT_FROM_LOGIN;
38+
import static org.lowcoder.sdk.util.ExceptionUtils.propagateError;
39+
import static org.lowcoder.sdk.util.JsonUtils.readTree;
40+
import static org.lowcoder.sdk.util.JsonUtils.toJsonThrows;
41+
import static org.lowcoder.sdk.util.MustacheHelper.renderMustacheJson;
42+
import static org.lowcoder.sdk.util.MustacheHelper.renderMustacheString;
43+
import static org.lowcoder.sdk.util.StreamUtils.collectList;
44+
45+
import java.io.IOException;
46+
import java.net.URI;
47+
import java.net.URISyntaxException;
48+
import java.nio.charset.StandardCharsets;
49+
import java.text.ParseException;
50+
import java.time.Duration;
51+
import java.util.ArrayList;
52+
import java.util.Base64;
53+
import java.util.HashMap;
54+
import java.util.HashSet;
55+
import java.util.List;
56+
import java.util.Map;
57+
import java.util.Set;
58+
import java.util.function.Consumer;
59+
import java.util.stream.Collectors;
60+
import java.util.stream.Stream;
61+
62+
import javax.annotation.Nullable;
63+
2864
import org.apache.commons.collections4.CollectionUtils;
2965
import org.apache.commons.lang3.ObjectUtils;
3066
import org.apache.commons.lang3.StringUtils;
@@ -51,41 +87,29 @@
5187
import org.lowcoder.sdk.query.QueryVisitorContext;
5288
import org.lowcoder.sdk.webclient.WebClientBuildHelper;
5389
import org.pf4j.Extension;
54-
import org.springframework.http.*;
90+
import org.springframework.http.HttpCookie;
91+
import org.springframework.http.HttpHeaders;
92+
import org.springframework.http.HttpMethod;
93+
import org.springframework.http.HttpStatus;
94+
import org.springframework.http.MediaType;
95+
import org.springframework.http.ResponseEntity;
5596
import org.springframework.http.client.reactive.ClientHttpRequest;
97+
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
5698
import org.springframework.util.MultiValueMap;
5799
import org.springframework.web.reactive.function.BodyInserter;
58100
import org.springframework.web.reactive.function.BodyInserters;
59101
import org.springframework.web.reactive.function.client.ExchangeStrategies;
60102
import org.springframework.web.reactive.function.client.WebClient;
61-
import reactor.core.publisher.Mono;
62103

63-
import javax.annotation.Nullable;
64-
import java.io.IOException;
65-
import java.net.URI;
66-
import java.net.URISyntaxException;
67-
import java.nio.charset.StandardCharsets;
68-
import java.text.ParseException;
69-
import java.util.*;
70-
import java.util.function.Consumer;
71-
import java.util.stream.Collectors;
72-
import java.util.stream.Stream;
104+
import com.fasterxml.jackson.core.JsonProcessingException;
105+
import com.fasterxml.jackson.databind.JsonNode;
106+
import com.fasterxml.jackson.databind.node.ObjectNode;
107+
import com.google.common.collect.ImmutableMap;
73108

74-
import static com.google.common.base.MoreObjects.firstNonNull;
75-
import static org.apache.commons.collections4.MapUtils.emptyIfNull;
76-
import static org.apache.commons.lang3.StringUtils.trimToEmpty;
77-
import static org.lowcoder.plugin.restapi.RestApiError.REST_API_EXECUTION_ERROR;
78-
import static org.lowcoder.plugin.restapi.helpers.ContentTypeHelper.*;
79-
import static org.lowcoder.sdk.exception.PluginCommonError.*;
80-
import static org.lowcoder.sdk.plugin.restapi.DataUtils.convertToMultiformFileValue;
81-
import static org.lowcoder.sdk.plugin.restapi.auth.RestApiAuthType.DIGEST_AUTH;
82-
import static org.lowcoder.sdk.plugin.restapi.auth.RestApiAuthType.OAUTH2_INHERIT_FROM_LOGIN;
83-
import static org.lowcoder.sdk.util.ExceptionUtils.propagateError;
84-
import static org.lowcoder.sdk.util.JsonUtils.readTree;
85-
import static org.lowcoder.sdk.util.JsonUtils.toJsonThrows;
86-
import static org.lowcoder.sdk.util.MustacheHelper.renderMustacheJson;
87-
import static org.lowcoder.sdk.util.MustacheHelper.renderMustacheString;
88-
import static org.lowcoder.sdk.util.StreamUtils.collectList;
109+
import lombok.Builder;
110+
import lombok.Getter;
111+
import reactor.core.publisher.Mono;
112+
import reactor.netty.http.client.HttpClient;
89113

90114
@Extension
91115
public class RestApiExecutor implements QueryExecutor<RestApiDatasourceConfig, Object, RestApiQueryExecutionContext> {
@@ -176,6 +200,7 @@ public RestApiQueryExecutionContext buildQueryExecutionContext(RestApiDatasource
176200
.authConfig(datasourceConfig.getAuthConfig())
177201
.sslConfig(datasourceConfig.getSslConfig())
178202
.authTokenMono(queryVisitorContext.getAuthTokenMono())
203+
.timeoutMs(queryConfig.getTimeoutMs())
179204
.build();
180205
}
181206

@@ -235,9 +260,13 @@ public Mono<QueryExecutionResult> executeQuery(Object webClientFilter, RestApiQu
235260
webClientBuilder.filter(new BufferingFilter());
236261
}
237262

263+
HttpClient httpClient = HttpClient.create()
264+
.responseTimeout(Duration.ofMillis(context.getTimeoutMs()));
265+
238266
webClientBuilder.defaultCookies(injectCookies(context));
239267
WebClient client = webClientBuilder
240268
.exchangeStrategies(exchangeStrategies)
269+
.clientConnector(new ReactorClientHttpConnector(httpClient))
241270
.build();
242271

243272
BodyInserter<?, ? super ClientHttpRequest> bodyInserter = buildBodyInserter(

‎server/api-service/lowcoder-plugins/restApiPlugin/src/main/java/org/lowcoder/plugin/restapi/model/RestApiQueryConfig.java‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,17 +30,19 @@ public class RestApiQueryConfig {
3030
private final List<Property> params;
3131
private final List<Property> headers;
3232
private final List<Property> bodyFormData;
33+
private final long timeoutMs;
3334

3435
@JsonCreator
3536
private RestApiQueryConfig(HttpMethod httpMethod, boolean disableEncodingParams, String body, String path,
36-
List<Property> params, List<Property> headers, List<Property> bodyFormData) {
37+
List<Property> params, List<Property> headers, List<Property> bodyFormData, longtimeoutMs) {
3738
this.httpMethod = httpMethod;
3839
this.disableEncodingParams = disableEncodingParams;
3940
this.body = body;
4041
this.path = path;
4142
this.params = params;
4243
this.headers = headers;
4344
this.bodyFormData = bodyFormData;
45+
this.timeoutMs = timeoutMs;
4446
}
4547

4648
public static RestApiQueryConfig from(Map<String, Object> queryConfigs) {

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /