20
return HTTP_RESP_FORBIDDEN;
21
}
22
23
-static inline int web_client_crock_socket(struct web_client *w) {
23
+static inline int web_client_crock_socket(struct web_client *w __maybe_unused) {
24
#ifdef TCP_CORK
25
if(likely(web_client_is_corkable(w) && !w->tcp_cork && w->ofd != -1)) {
26
- w->tcp_cork = 1;
26
+ w->tcp_cork = true;
27
if(unlikely(setsockopt(w->ofd, IPPROTO_TCP, TCP_CORK, (char *) &w->tcp_cork, sizeof(int)) != 0)) {
28
error("%llu: failed to enable TCP_CORK on socket.", w->id);
29
30
- w->tcp_cork = 0;
30
+ w->tcp_cork = false;
31
return -1;
32
}
33
}
34
-#else
35
- (void)w;
34
#endif /* TCP_CORK */
35
36
return 0;
48
}
49
}
50
53
-static inline int web_client_uncrock_socket(struct web_client *w) {
51
+static inline int web_client_uncrock_socket(struct web_client *w __maybe_unused) {
52
#ifdef TCP_CORK
53
if(likely(w->tcp_cork && w->ofd != -1)) {
56
- w->tcp_cork = 0;
54
if(unlikely(setsockopt(w->ofd, IPPROTO_TCP, TCP_CORK, (char *) &w->tcp_cork, sizeof(int)) != 0)) {
55
error("%llu: failed to disable TCP_CORK on socket.", w->id);
59
- w->tcp_cork = 1;
56
+ w->tcp_cork = true;
57
return -1;
58
}
59
}
63
-#else
64
- (void)w;
60
#endif /* TCP_CORK */
61
62
+ w->tcp_cork = false;
63
return 0;
64
}
65
75
return url;
76
}
77
78
+static void web_client_reset_allocations(struct web_client *w, bool free_all) {
79
+
80
+ if(free_all) {
81
+ // the web client is to be destroyed
82
+
83
+ buffer_free(w->url_as_received);
84
+ w->url_as_received = NULL;
85
+
86
+ buffer_free(w->url_path_decoded);
87
+ w->url_path_decoded = NULL;
88
+
89
+ buffer_free(w->url_query_string_decoded);
90
+ w->url_query_string_decoded = NULL;
91
+
92
+ buffer_free(w->response.header_output);
93
+ w->response.header_output = NULL;
94
+
95
+ buffer_free(w->response.header);
96
+ w->response.header = NULL;
97
+
98
+ buffer_free(w->response.data);
99
+ w->response.data = NULL;
100
+
101
+ freez(w->post_payload);
102
+ w->post_payload = NULL;
103
+ w->post_payload_size = 0;
104
+
105
+#ifdef ENABLE_HTTPS
106
+ if ((!web_client_check_unix(w)) && (netdata_ssl_srv_ctx)) {
107
+ if (w->ssl.conn) {
108
+ SSL_free(w->ssl.conn);
109
+ w->ssl.conn = NULL;
110
+ }
111
+ }
112
+#endif
113
+ }
114
+ else {
115
+ // the web client is to be re-used
116
+
117
+ buffer_reset(w->url_as_received);
118
+ buffer_reset(w->url_path_decoded);
119
+ buffer_reset(w->url_query_string_decoded);
120
+
121
+ buffer_reset(w->response.header_output);
122
+ buffer_reset(w->response.header);
123
+ buffer_reset(w->response.data);
124
+
125
+ // leave w->post_payload
126
+ // leave w->ssl
127
+ }
128
+
129
+ freez(w->server_host);
130
+ w->server_host = NULL;
131
+
132
+ freez(w->forwarded_host);
133
+ w->forwarded_host = NULL;
134
+
135
+ freez(w->origin);
136
+ w->origin = NULL;
137
+
138
+ freez(w->user_agent);
139
+ w->user_agent = NULL;
140
+
141
+ freez(w->auth_bearer_token);
142
+ w->auth_bearer_token = NULL;
143
+
144
+ // if we had enabled compression, release it
145
+#ifdef NETDATA_WITH_ZLIB
146
+ if(w->response.zinitialized) {
147
+ deflateEnd(&w->response.zstream);
148
+ w->response.zsent = 0;
149
+ w->response.zhave = 0;
150
+ w->response.zstream.avail_in = 0;
151
+ w->response.zstream.avail_out = 0;
152
+ w->response.zstream.total_in = 0;
153
+ w->response.zstream.total_out = 0;
154
+ w->response.zinitialized = false;
155
+ w->flags &= ~WEB_CLIENT_CHUNKED_TRANSFER;
156
+ }
157
+#endif // NETDATA_WITH_ZLIB
158
+}
159
+
160
void web_client_request_done(struct web_client *w) {
161
web_client_uncrock_socket(w);
162
163
debug(D_WEB_CLIENT, "%llu: Resetting client.", w->id);
164
87
- if(likely(w->last_url[0])) {
165
+ if(likely(buffer_strlen(w->url_as_received))) {
166
struct timeval tv;
89
- now_realtime_timeval(&tv);
167
+ now_monotonic_high_precision_timeval(&tv);
168
169
size_t size = (w->mode == WEB_CLIENT_MODE_FILECOPY)?w->response.rlen:w->response.data->len;
170
size_t sent = size;
175
// --------------------------------------------------------------------
176
// global statistics
177
100
- global_statistics_web_request_completed(dt_usec(&tv, &w->tv_in),
101
- w->stats_received_bytes,
102
- w->stats_sent_bytes,
178
+ global_statistics_web_request_completed(dt_usec(&tv, &w->timings.tv_in),
179
+ w->statistics.received_bytes,
180
+ w->statistics.sent_bytes,
181
size,
182
sent);
183
106
- w->stats_received_bytes = 0;
107
- w->stats_sent_bytes = 0;
184
+ w->statistics.received_bytes = 0;
185
+ w->statistics.sent_bytes = 0;
186
187
188
// --------------------------------------------------------------------
201
mode = "STREAM";
202
break;
203
126
- case WEB_CLIENT_MODE_NORMAL:
204
+ case WEB_CLIENT_MODE_POST:
205
+ case WEB_CLIENT_MODE_GET:
206
mode = "DATA";
207
break;
208
221
, sent
222
, size
223
, -((size > 0) ? ((double)(size - sent) / (double) size * 100.0) : 0.0)
145
- , (double)dt_usec(&w->tv_ready, &w->tv_in) / 1000.0
146
- , (double)dt_usec(&tv, &w->tv_ready) / 1000.0
147
- , (double)dt_usec(&tv, &w->tv_in) / 1000.0
224
+ , (double)dt_usec(&w->timings.tv_ready, &w->timings.tv_in) / 1000.0
225
+ , (double)dt_usec(&tv, &w->timings.tv_ready) / 1000.0
226
+ , (double)dt_usec(&tv, &w->timings.tv_in) / 1000.0
227
, w->response.code
149
- , strip_control_characters(w->last_url)
228
+ , strip_control_characters((char *)buffer_tostring(w->url_as_received))
229
);
230
}
231
243
}
244
}
245
167
- w->last_url[0] = '\0';
168
- w->cookie1[0] = '\0';
169
- w->cookie2[0] = '\0';
170
- w->origin[0] = '*';
171
- w->origin[1] = '\0';
172
-
173
- freez(w->user_agent); w->user_agent = NULL;
174
- if (w->auth_bearer_token) {
175
- freez(w->auth_bearer_token);
176
- w->auth_bearer_token = NULL;
177
- }
246
+ web_client_reset_allocations(w, false);
247
179
- w->mode = WEB_CLIENT_MODE_NORMAL;
248
+ w->mode = WEB_CLIENT_MODE_GET;
249
181
- w->tcp_cork = 0;
250
web_client_disable_donottrack(w);
251
web_client_disable_tracking_required(w);
252
web_client_disable_keepalive(w);
185
- w->decoded_url[0] = '\0';
186
-
187
- buffer_reset(w->response.header_output);
188
- buffer_reset(w->response.header);
189
- buffer_reset(w->response.data);
190
- w->response.rlen = 0;
191
- w->response.sent = 0;
192
- w->response.code = 0;
253
254
w->header_parse_tries = 0;
255
w->header_parse_last_size = 0;
257
web_client_enable_wait_receive(w);
258
web_client_disable_wait_send(w);
259
200
- w->response.zoutput = 0;
201
-
202
- // if we had enabled compression, release it
203
-#ifdef NETDATA_WITH_ZLIB
204
- if(w->response.zinitialized) {
205
- debug(D_DEFLATE, "%llu: Freeing compression resources.", w->id);
206
- deflateEnd(&w->response.zstream);
207
- w->response.zsent = 0;
208
- w->response.zhave = 0;
209
- w->response.zstream.avail_in = 0;
210
- w->response.zstream.avail_out = 0;
211
- w->response.zstream.total_in = 0;
212
- w->response.zstream.total_out = 0;
213
- w->response.zinitialized = 0;
214
- w->flags &= ~WEB_CLIENT_CHUNKED_TRANSFER;
215
- }
216
-#endif // NETDATA_WITH_ZLIB
260
+ w->response.has_cookies = false;
261
+ w->response.rlen = 0;
262
+ w->response.sent = 0;
263
+ w->response.code = 0;
264
+ w->response.zoutput = false;
265
}
266
267
static struct {
486
}
487
488
w->response.zsent = 0;
441
- w->response.zoutput = 1;
442
- w->response.zinitialized = 1;
489
+ w->response.zoutput = true;
490
+ w->response.zinitialized = true;
491
w->flags |= WEB_CLIENT_CHUNKED_TRANSFER;
492
493
debug(D_DEFLATE, "%llu: Initialized compression.", w->id);
587
return check_host_and_call(host, w, url, func);
588
}
589
542
-int web_client_api_request(RRDHOST *host, struct web_client *w, char *url)
590
+int web_client_api_request(RRDHOST *host, struct web_client *w, char *url_path_fragment)
591
{
592
// get the api version
545
- char *tok = mystrsep(&url, "/");
593
+ char *tok = strsep_skip_consecutive_separators(&url_path_fragment, "/");
594
if(tok && *tok) {
595
debug(D_WEB_CLIENT, "%llu: Searching for API version '%s'.", w->id, tok);
596
if(strcmp(tok, "v2") == 0)
549
- return web_client_api_request_v2(host, w, url);
597
+ return web_client_api_request_v2(host, w, url_path_fragment);
598
else if(strcmp(tok, "v1") == 0)
551
- return web_client_api_request_v1(host, w, url);
599
+ return web_client_api_request_v1(host, w, url_path_fragment);
600
else {
601
buffer_flush(w->response.data);
602
w->response.data->content_type = CT_TEXT_HTML;
777
uint32_t hash = simple_uhash(s);
778
779
if(hash == hash_origin && !strcasecmp(s, "Origin"))
732
- strncpyz(w->origin, v, NETDATA_WEB_REQUEST_ORIGIN_HEADER_SIZE);
780
+ w->origin = strdupz(v);
781
782
else if(hash == hash_connection && !strcasecmp(s, "Connection")) {
783
if(strcasestr(v, "keep-alive"))
789
}
790
else if(parse_useragent && hash == hash_useragent && !strcasecmp(s, "User-Agent")) {
791
w->user_agent = strdupz(v);
744
- } else if(hash == hash_authorization&& !strcasecmp(s, "X-Auth-Token")) {
792
+ }
793
+ else if(hash == hash_authorization&& !strcasecmp(s, "X-Auth-Token")) {
794
w->auth_bearer_token = strdupz(v);
795
}
747
- else if(hash == hash_host && !strcasecmp(s, "Host")){
748
- strncpyz(w->server_host, v, ((size_t)(ve - v) < sizeof(w->server_host)-1 ? (size_t)(ve - v) : sizeof(w->server_host)-1));
796
+ else if(hash == hash_host && !strcasecmp(s, "Host")) {
797
+ char buffer[NI_MAXHOST];
798
+ strncpyz(buffer, v, ((size_t)(ve - v) < sizeof(buffer) - 1 ? (size_t)(ve - v) : sizeof(buffer) - 1));
799
+ w->server_host = strdupz(buffer);
800
}
801
#ifdef NETDATA_WITH_ZLIB
802
else if(hash == hash_accept_encoding && !strcasecmp(s, "Accept-Encoding")) {
816
w->ssl.flags |= NETDATA_SSL_PROXY_HTTPS;
817
}
818
#endif
768
- else if(hash == hash_forwarded_host && !strcasecmp(s, "X-Forwarded-Host")){
769
- strncpyz(w->forwarded_host, v, ((size_t)(ve - v) < sizeof(w->server_host)-1 ? (size_t)(ve - v) : sizeof(w->server_host)-1));
819
+ else if(hash == hash_forwarded_host && !strcasecmp(s, "X-Forwarded-Host")) {
820
+ char buffer[NI_MAXHOST];
821
+ strncpyz(buffer, v, ((size_t)(ve - v) < sizeof(buffer) - 1 ? (size_t)(ve - v) : sizeof(buffer) - 1));
822
+ w->forwarded_host = strdupz(buffer);
823
}
824
825
*e = ':';
841
// is is a valid request?
842
if(!strncmp(s, "GET ", 4)) {
843
s = &s[4];
791
- w->mode = WEB_CLIENT_MODE_NORMAL;
844
+ w->mode = WEB_CLIENT_MODE_GET;
845
}
846
else if(!strncmp(s, "OPTIONS ", 8)) {
847
s = &s[8];
848
w->mode = WEB_CLIENT_MODE_OPTIONS;
849
}
850
+ else if(!strncmp(s, "POST ", 5)) {
851
+ s = &s[5];
852
+ w->mode = WEB_CLIENT_MODE_POST;
853
+ }
854
else if(!strncmp(s, "STREAM ", 7)) {
855
s = &s[7];
856
893
return s;
894
}
895
839
-/**
840
- * Set Path Query
841
- *
842
- * Set the pointers to the path and query string according to the input.
843
- *
844
- * @param w is the structure with the client request
845
- * @param s is the first address of the string.
846
- * @param ptr is the address of the separator.
847
- */
848
-static void web_client_set_path_query(struct web_client *w, const char *s, char *ptr) {
849
- w->url_path_length = (size_t)(ptr -s);
850
- w->url_search_path = ptr;
851
-}
852
-
853
-/**
854
- * Split path query
855
- *
856
- * Do the separation between path and query string
857
- *
858
- * @param w is the structure with the client request
859
- * @param s is the string to parse
860
- */
861
-void web_client_split_path_query(struct web_client *w, char *s) {
862
- //I am assuming here that the separator character(?) is not encoded
863
- char *ptr = strchr(s, '?');
864
- if(ptr) {
865
- w->separator = '?';
866
- web_client_set_path_query(w, s, ptr);
867
- return;
868
- }
869
-
870
- //Here I test the second possibility, the URL is completely encoded by the user.
871
- //I am not using the strcasestr, because it is fastest to check %3f and compare
872
- //the next character.
873
- //We executed some tests with "encodeURI(uri);" described in https://www.w3schools.com/jsref/jsref_encodeuri.asp
874
- //on July 1st, 2019, that show us that URLs won't have '?','=' and '&' encoded, but we decided to move in front
875
- //with the next part, because users can develop their own encoded that won't follow this rule.
876
- char *moveme = s;
877
- while (moveme) {
878
- ptr = strchr(moveme, '%');
879
- if(ptr) {
880
- char *test = (ptr+1);
881
- if (!strncmp(test, "3f", 2) || !strncmp(test, "3F", 2)) {
882
- w->separator = *ptr;
883
- web_client_set_path_query(w, s, ptr);
884
- return;
885
- }
886
- ptr++;
887
- }
888
-
889
- moveme = ptr;
890
- }
891
-
892
- w->separator = 0x00;
893
- w->url_path_length = strlen(s);
894
-}
895
-
896
/**
897
* Request validate
898
*
917
if(w->header_parse_last_size < last_pos)
918
last_pos = 0;
919
920
- is_it_valid = url_is_request_complete(s, &s[last_pos], w->header_parse_last_size);
920
+ is_it_valid = url_is_request_complete(s, &s[last_pos], w->header_parse_last_size, &w->post_payload, &w->post_payload_size);
921
if(!is_it_valid) {
922
if(w->header_parse_tries > HTTP_REQ_MAX_HEADER_FETCH_TRIES) {
923
info("Disabling slow client after %zu attempts to read the request (%zu bytes received)", w->header_parse_tries, buffer_strlen(w->response.data));
933
is_it_valid = 1;
934
} else {
935
last_pos = w->header_parse_last_size;
936
- is_it_valid = url_is_request_complete(s, &s[last_pos], w->header_parse_last_size);
936
+ is_it_valid = url_is_request_complete(s, &s[last_pos], w->header_parse_last_size, &w->post_payload, &w->post_payload_size);
937
}
938
939
s = web_client_valid_method(w, s);
974
// we have the end of encoded_url - remember it
975
char *ue = s;
976
977
- //Variables used to map the variables in the query string case it is present
978
- int total_variables;
979
- char *ptr_variables[WEB_FIELDS_MAX];
980
-
977
// make sure we have complete request
978
// complete requests contain: \r\n\r\n
979
while(*s) {
990
if(unlikely(*s == '\r' && s[1] == '\n')) {
991
// a valid complete HTTP request found
992
993
+ char c = *ue;
994
*ue = '\0';
998
- //This is to avoid crash in line
999
- w->url_search_path = NULL;
1000
- if(w->mode != WEB_CLIENT_MODE_NORMAL) {
1001
- if(!url_decode_r(w->decoded_url, encoded_url, NETDATA_WEB_REQUEST_URL_SIZE + 1))
1002
- return HTTP_VALIDATION_MALFORMED_URL;
1003
- } else {
1004
- web_client_split_path_query(w, encoded_url);
1005
-
1006
- if (w->url_search_path && w->separator) {
1007
- *w->url_search_path = '\0';
1008
- }
1009
-
1010
- if(!url_decode_r(w->decoded_url, encoded_url, NETDATA_WEB_REQUEST_URL_SIZE + 1))
1011
- return HTTP_VALIDATION_MALFORMED_URL;
1012
-
1013
- if (w->url_search_path && w->separator) {
1014
- *w->url_search_path = w->separator;
995
+ web_client_decode_path_and_query_string(w, encoded_url);
996
+ *ue = c;
997
1016
- char *from = (encoded_url + w->url_path_length);
1017
- total_variables = url_map_query_string(ptr_variables, from);
1018
-
1019
- if (url_parse_query_string(w->decoded_query_string, NETDATA_WEB_REQUEST_URL_SIZE + 1, ptr_variables, total_variables)) {
1020
- return HTTP_VALIDATION_MALFORMED_URL;
1021
- }
1022
- } else {
1023
- //make sure there's no leftovers from previous request on the same web client
1024
- w->decoded_query_string[1]='\0';
1025
- }
1026
- }
1027
- *ue = ' ';
1028
-
1029
- // copy the URL - we are going to overwrite parts of it
1030
- // TODO -- ideally we we should avoid copying buffers around
1031
- snprintfz(w->last_url, NETDATA_WEB_REQUEST_URL_SIZE, "%s%s", w->decoded_url, w->decoded_query_string);
998
#ifdef ENABLE_HTTPS
999
if ( (!web_client_check_unix(w)) && (netdata_ssl_srv_ctx) ) {
1000
if ((w->ssl.conn) && ((w->ssl.flags & NETDATA_SSL_NO_HANDSHAKE) && (web_client_is_using_ssl_force(w) || web_client_is_using_ssl_default(w)) && (w->mode != WEB_CLIENT_MODE_STREAM)) ) {
1001
w->header_parse_tries = 0;
1002
w->header_parse_last_size = 0;
1037
- // The client will be redirected for Netdata and we are preserving the original request.
1038
- *ue = '\0';
1039
- strncpyz(w->last_url, encoded_url, NETDATA_WEB_REQUEST_URL_SIZE);
1040
- *ue = ' ';
1003
web_client_disable_wait_receive(w);
1004
return HTTP_VALIDATION_REDIRECT;
1005
}
1013
}
1014
1015
// another header line
1054
- s = http_header_parse(w, s,
1055
- (w->mode == WEB_CLIENT_MODE_STREAM) // parse user agent
1056
- );
1016
+ s = http_header_parse(w, s, (w->mode == WEB_CLIENT_MODE_STREAM)); // parse user agent
1017
}
1018
}
1019
1050
// set a proper expiration date, if not already set
1051
if(unlikely(!w->response.data->expires)) {
1052
if(w->response.data->options & WB_CONTENT_NO_CACHEABLE)
1093
- w->response.data->expires = w->tv_ready.tv_sec + localhost->rrd_update_every;
1053
+ w->response.data->expires = w->timings.tv_ready.tv_sec + localhost->rrd_update_every;
1054
else
1095
- w->response.data->expires = w->tv_ready.tv_sec + 86400;
1055
+ w->response.data->expires = w->timings.tv_ready.tv_sec + 86400;
1056
}
1057
1058
// prepare the HTTP response header
1078
"HTTP/1.1 %d %s\r\n"
1079
"Location: https://%s%s\r\n",
1080
w->response.code, code_msg,
1121
- w->server_host,
1122
- w->last_url);
1081
+ w->server_host ? w->server_host : "",
1082
+ buffer_tostring(w->url_as_received));
1083
}else {
1084
buffer_sprintf(w->response.header_output,
1085
"HTTP/1.1 %d %s\r\n"
1093
code_msg,
1094
web_client_has_keepalive(w)?"keep-alive":"close",
1095
VERSION,
1136
- w->origin,
1096
+ w->origin ? w->origin : "*",
1097
content_type_string,
1098
date);
1099
}
1101
if(unlikely(web_x_frame_options))
1102
buffer_sprintf(w->response.header_output, "X-Frame-Options: %s\r\n", web_x_frame_options);
1103
1144
- if(w->cookie1[0] || w->cookie2[0]) {
1145
- if(w->cookie1[0]) {
1146
- buffer_sprintf(w->response.header_output,
1147
- "Set-Cookie: %s\r\n",
1148
- w->cookie1);
1149
- }
1150
-
1151
- if(w->cookie2[0]) {
1152
- buffer_sprintf(w->response.header_output,
1153
- "Set-Cookie: %s\r\n",
1154
- w->cookie2);
1155
- }
1156
-
1104
+ if(w->response.has_cookies) {
1105
if(respect_web_browser_do_not_track_policy)
1106
buffer_sprintf(w->response.header_output,
1159
- "Tk: T;cookies\r\n");
1107
+ "Tk: T;cookies\r\n");
1108
}
1109
else {
1110
if(respect_web_browser_do_not_track_policy) {
1111
if(web_client_has_tracking_required(w))
1112
buffer_sprintf(w->response.header_output,
1165
- "Tk: T;cookies\r\n");
1113
+ "Tk: T;cookies\r\n");
1114
else
1115
buffer_sprintf(w->response.header_output,
1168
- "Tk: N\r\n");
1116
+ "Tk: N\r\n");
1117
}
1118
}
1119
1211
1212
if(bytes != (ssize_t) buffer_strlen(w->response.header_output)) {
1213
if(bytes > 0)
1266
- w->stats_sent_bytes += bytes;
1214
+ w->statistics.sent_bytes += bytes;
1215
1216
if (bytes < 0) {
1217
1224
}
1225
}
1226
else
1279
- w->stats_sent_bytes += bytes;
1227
+ w->statistics.sent_bytes += bytes;
1228
}
1229
1282
-static inline int web_client_process_url(RRDHOST *host, struct web_client *w, char *url);
1283
-
1284
-static inline int web_client_switch_host(RRDHOST *host, struct web_client *w, char *url) {
1230
+static inline int web_client_switch_host(RRDHOST *host, struct web_client *w, char *url, bool nodeid, int (*func)(RRDHOST *, struct web_client *, char *)) {
1231
static uint32_t hash_localhost = 0;
1232
1233
if(unlikely(!hash_localhost)) {
1240
return HTTP_RESP_BAD_REQUEST;
1241
}
1242
1297
- char *tok = mystrsep(&url, "/");
1243
+ char *tok = strsep_skip_consecutive_separators(&url, "/");
1244
if(tok && *tok) {
1245
debug(D_WEB_CLIENT, "%llu: Searching for host with name '%s'.", w->id, tok);
1246
1301
- if(!url) { //no delim found
1302
- debug(D_WEB_CLIENT, "%llu: URL doesn't end with / generating redirect.", w->id);
1303
- char *protocol, *url_host;
1247
+ if(nodeid) {
1248
+ host = find_host_by_node_id(tok);
1249
+ if(!host) {
1250
+ host = rrdhost_find_by_hostname(tok);
1251
+ if (!host)
1252
+ host = rrdhost_find_by_guid(tok);
1253
+ }
1254
+ }
1255
+ else {
1256
+ host = rrdhost_find_by_hostname(tok);
1257
+ if(!host) {
1258
+ host = rrdhost_find_by_guid(tok);
1259
+ if (!host)
1260
+ host = find_host_by_node_id(tok);
1261
+ }
1262
+ }
1263
+
1264
+ if(!host) {
1265
+ // we didn't find it, but it may be a uuid case mismatch for MACHINE_GUID
1266
+ // so, recreate the machine guid in lower-case.
1267
+ uuid_t uuid;
1268
+ char txt[UUID_STR_LEN];
1269
+ if (uuid_parse(tok, uuid) == 0) {
1270
+ uuid_unparse_lower(uuid, txt);
1271
+ host = rrdhost_find_by_guid(txt);
1272
+ }
1273
+ }
1274
+
1275
+ if (host) {
1276
+ if(!url) { //no delim found
1277
+ debug(D_WEB_CLIENT, "%llu: URL doesn't end with / generating redirect.", w->id);
1278
+ char *protocol, *url_host;
1279
#ifdef ENABLE_HTTPS
1305
- protocol = ((w->ssl.conn && !w->ssl.flags) || w->ssl.flags & NETDATA_SSL_PROXY_HTTPS) ? "https" : "http";
1280
+ protocol = ((w->ssl.conn && !w->ssl.flags) || w->ssl.flags & NETDATA_SSL_PROXY_HTTPS) ? "https" : "http";
1281
#else
1307
- protocol = "http";
1282
+ protocol = "http";
1283
#endif
1309
- url_host = (!w->forwarded_host[0])?w->server_host:w->forwarded_host;
1310
- buffer_sprintf(w->response.header, "Location: %s://%s%s/\r\n", protocol, url_host, w->last_url);
1311
- buffer_strcat(w->response.data, "Permanent redirect");
1312
- return HTTP_RESP_REDIR_PERM;
1313
- }
1284
1315
- // copy the URL, we need it to serve files
1316
- w->last_url[0] = '/';
1285
+ url_host = w->forwarded_host;
1286
+ if(!url_host) {
1287
+ url_host = w->server_host;
1288
+ if(!url_host) url_host = "";
1289
+ }
1290
+
1291
+ buffer_sprintf(w->response.header, "Location: %s://%s/%s/%s/%s",
1292
+ protocol, url_host, nodeid?"node":"host", tok, buffer_tostring(w->url_path_decoded));
1293
+
1294
+ if(buffer_strlen(w->url_query_string_decoded)) {
1295
+ const char *query_string = buffer_tostring(w->url_query_string_decoded);
1296
+ if(*query_string) {
1297
+ if(*query_string != '?')
1298
+ buffer_fast_strcat(w->response.header, "?", 1);
1299
+ buffer_strcat(w->response.header, query_string);
1300
+ }
1301
+ }
1302
+ buffer_fast_strcat(w->response.header, "\r\n", 2);
1303
+ buffer_strcat(w->response.data, "Permanent redirect");
1304
+ return HTTP_RESP_REDIR_PERM;
1305
+ }
1306
1318
- if(url && *url) strncpyz(&w->last_url[1], url, NETDATA_WEB_REQUEST_URL_SIZE - 1);
1319
- else w->last_url[1] = '\0';
1307
+ size_t len = strlen(url) + 2;
1308
+ char buf[len];
1309
+ buf[0] = '/';
1310
+ strcpy(&buf[1], url);
1311
+ buf[len - 1] = '\0';
1312
1321
- host = rrdhost_find_by_hostname(tok);
1322
- if (!host)
1323
- host = rrdhost_find_by_guid(tok);
1324
- if (host) return web_client_process_url(host, w, url);
1313
+ buffer_flush(w->url_path_decoded);
1314
+ buffer_strcat(w->url_path_decoded, buf);
1315
+ return func(host, w, buf);
1316
+ }
1317
}
1318
1319
buffer_flush(w->response.data);
1323
return HTTP_RESP_NOT_FOUND;
1324
}
1325
1334
-static inline int web_client_process_url(RRDHOST *host, struct web_client *w, char *url) {
1326
+int web_client_api_request_with_node_selection(RRDHOST *host, struct web_client *w, char *decoded_url_path) {
1327
+ static uint32_t
1328
+ hash_api = 0,
1329
+ hash_host = 0,
1330
+ hash_node = 0;
1331
+
1332
+ if(unlikely(!hash_api)) {
1333
+ hash_api = simple_hash("api");
1334
+ hash_host = simple_hash("host");
1335
+ hash_node = simple_hash("node");
1336
+ }
1337
+
1338
+ char *tok = strsep_skip_consecutive_separators(&decoded_url_path, "/?");
1339
+ if(likely(tok && *tok)) {
1340
+ uint32_t hash = simple_hash(tok);
1341
+
1342
+ if(unlikely(hash == hash_api && strcmp(tok, "api") == 0)) {
1343
+ // current API
1344
+ debug(D_WEB_CLIENT_ACCESS, "%llu: API request ...", w->id);
1345
+ return check_host_and_call(host, w, decoded_url_path, web_client_api_request);
1346
+ }
1347
+ else if(unlikely((hash == hash_host && strcmp(tok, "host") == 0) || (hash == hash_node && strcmp(tok, "node") == 0))) {
1348
+ // host switching
1349
+ debug(D_WEB_CLIENT_ACCESS, "%llu: host switch request ...", w->id);
1350
+ return web_client_switch_host(host, w, decoded_url_path, hash == hash_node, web_client_api_request_with_node_selection);
1351
+ }
1352
+ }
1353
+
1354
+ buffer_flush(w->response.data);
1355
+ buffer_strcat(w->response.data, "Unknown API endpoint.");
1356
+ w->response.data->content_type = CT_TEXT_HTML;
1357
+ return HTTP_RESP_NOT_FOUND;
1358
+}
1359
+
1360
+static inline int web_client_process_url(RRDHOST *host, struct web_client *w, char *decoded_url_path) {
1361
if(unlikely(!service_running(ABILITY_WEB_REQUESTS)))
1362
return web_client_permission_denied(w);
1363
1364
static uint32_t
1365
hash_api = 0,
1366
hash_netdata_conf = 0,
1341
- hash_host = 0;
1367
+ hash_host = 0,
1368
+ hash_node = 0;
1369
1370
#ifdef NETDATA_INTERNAL_CHECKS
1371
static uint32_t hash_exit = 0, hash_debug = 0, hash_mirror = 0;
1375
hash_api = simple_hash("api");
1376
hash_netdata_conf = simple_hash("netdata.conf");
1377
hash_host = simple_hash("host");
1378
+ hash_node = simple_hash("node");
1379
#ifdef NETDATA_INTERNAL_CHECKS
1380
hash_exit = simple_hash("exit");
1381
hash_debug = simple_hash("debug");
1383
#endif
1384
}
1385
1358
- char *tok = mystrsep(&url, "/?");
1386
+ // keep a copy of the decoded path, in case we need to serve it as a filename
1387
+ char filename[FILENAME_MAX + 1];
1388
+ strncpyz(filename, buffer_tostring(w->url_path_decoded), FILENAME_MAX);
1389
+
1390
+ char *tok = strsep_skip_consecutive_separators(&decoded_url_path, "/?");
1391
if(likely(tok && *tok)) {
1392
uint32_t hash = simple_hash(tok);
1393
debug(D_WEB_CLIENT, "%llu: Processing command '%s'.", w->id, tok);
1394
1395
if(unlikely(hash == hash_api && strcmp(tok, "api") == 0)) { // current API
1396
debug(D_WEB_CLIENT_ACCESS, "%llu: API request ...", w->id);
1365
- return check_host_and_call(host, w, url, web_client_api_request);
1397
+ return check_host_and_call(host, w, decoded_url_path, web_client_api_request);
1398
}
1367
- else if(unlikely(hash == hash_host && strcmp(tok, "host") == 0)) { // host switching
1399
+ else if(unlikely((hash == hash_host && strcmp(tok, "host") == 0) || (hash == hash_node && strcmp(tok, "node") == 0))) { // host switching
1400
debug(D_WEB_CLIENT_ACCESS, "%llu: host switch request ...", w->id);
1369
- return web_client_switch_host(host, w, url);
1401
+ return web_client_switch_host(host, w, decoded_url_path, hash == hash_node, web_client_process_url);
1402
}
1403
else if(unlikely(hash == hash_netdata_conf && strcmp(tok, "netdata.conf") == 0)) { // netdata.conf
1404
if(unlikely(!web_client_can_access_netdataconf(w)))
1434
buffer_flush(w->response.data);
1435
1436
// get the name of the data to show
1405
- tok = mystrsep(&url, "&");
1437
+ tok = strsep_skip_consecutive_separators(&decoded_url_path, "&");
1438
if(tok && *tok) {
1439
debug(D_WEB_CLIENT, "%llu: Searching for RRD data with name '%s'.", w->id, tok);
1440
1484
#endif /* NETDATA_INTERNAL_CHECKS */
1485
}
1486
1455
- char filename[FILENAME_MAX+1];
1456
- url = filename;
1457
- strncpyz(filename, w->last_url, FILENAME_MAX);
1458
- tok = mystrsep(&url, "?");
1487
buffer_flush(w->response.data);
1460
- return mysendfile(w, (tok && *tok)?tok:"/");
1488
+ return mysendfile(w, filename);
1489
}
1490
1491
void web_client_process_request(struct web_client *w) {
1492
1493
// start timing us
1466
- now_realtime_timeval(&w->tv_in);
1494
+ web_client_timeout_checkpoint_init(w);
1495
1496
switch(http_request_validate(w)) {
1497
case HTTP_VALIDATION_OK:
1502
return;
1503
}
1504
1477
- w->response.code = rrdpush_receiver_thread_spawn(w, w->decoded_url);
1505
+ w->response.code = rrdpush_receiver_thread_spawn(w, (char *)buffer_tostring(w->url_query_string_decoded));
1506
return;
1507
1508
case WEB_CLIENT_MODE_OPTIONS:
1524
break;
1525
1526
case WEB_CLIENT_MODE_FILECOPY:
1499
- case WEB_CLIENT_MODE_NORMAL:
1527
+ case WEB_CLIENT_MODE_POST:
1528
+ case WEB_CLIENT_MODE_GET:
1529
if(unlikely(
1530
!web_client_can_access_dashboard(w) &&
1531
!web_client_can_access_registry(w) &&
1537
break;
1538
}
1539
1511
- w->response.code = web_client_process_url(localhost, w, w->decoded_url);
1540
+ w->response.code = web_client_process_url(localhost, w, (char *)buffer_tostring(w->url_path_decoded));
1541
break;
1542
}
1543
break;
1544
1545
case HTTP_VALIDATION_INCOMPLETE:
1546
if(w->response.data->len > NETDATA_WEB_REQUEST_MAX_SIZE) {
1518
- strcpy(w->last_url, "too big request");
1547
+ buffer_flush(w->url_as_received);
1548
+ buffer_strcat(w->url_as_received, "too big request");
1549
1550
debug(D_WEB_CLIENT_ACCESS, "%llu: Received request is too big (%zu bytes).", w->id, w->response.data->len);
1551
1559
// set to normal to prevent web_server_rcv_callback
1560
// from going into stream mode
1561
if (w->mode == WEB_CLIENT_MODE_STREAM)
1532
- w->mode = WEB_CLIENT_MODE_NORMAL;
1562
+ w->mode = WEB_CLIENT_MODE_GET;
1563
return;
1564
}
1565
break;
1611
}
1612
1613
// keep track of the processing time
1584
- now_realtime_timeval(&w->tv_ready);
1614
+ web_client_timeout_checkpoint_response_ready(w, NULL);
1615
1616
w->response.sent = 0;
1617
1618
// set a proper last modified date
1619
if(unlikely(!w->response.data->date))
1590
- w->response.data->date = w->tv_ready.tv_sec;
1620
+ w->response.data->date = w->timings.tv_ready.tv_sec;
1621
1622
web_client_send_http_header(w);
1623
1634
debug(D_WEB_CLIENT, "%llu: Done preparing the OPTIONS response. Sending data (%zu bytes) to client.", w->id, w->response.data->len);
1635
break;
1636
1607
- case WEB_CLIENT_MODE_NORMAL:
1637
+ case WEB_CLIENT_MODE_POST:
1638
+ case WEB_CLIENT_MODE_GET:
1639
debug(D_WEB_CLIENT, "%llu: Done preparing the response. Sending data (%zu bytes) to client.", w->id, w->response.data->len);
1640
break;
1641
1678
bytes = web_client_send_data(w,buf,strlen(buf),0);
1679
if(bytes > 0) {
1680
debug(D_DEFLATE, "%llu: Sent chunk header %zd bytes.", w->id, bytes);
1650
- w->stats_sent_bytes += bytes;
1681
+ w->statistics.sent_bytes += bytes;
1682
}
1683
1684
else if(bytes == 0) {
1700
bytes = web_client_send_data(w,"\r\n",2,0);
1701
if(bytes > 0) {
1702
debug(D_DEFLATE, "%llu: Sent chunk suffix %zd bytes.", w->id, bytes);
1672
- w->stats_sent_bytes += bytes;
1703
+ w->statistics.sent_bytes += bytes;
1704
}
1705
1706
else if(bytes == 0) {
1722
bytes = web_client_send_data(w,"\r\n0\r\n\r\n",7,0);
1723
if(bytes > 0) {
1724
debug(D_DEFLATE, "%llu: Sent chunk suffix %zd bytes.", w->id, bytes);
1694
- w->stats_sent_bytes += bytes;
1725
+ w->statistics.sent_bytes += bytes;
1726
}
1727
1728
else if(bytes == 0) {
1800
1801
// ask for FINISH if we have all the input
1802
int flush = Z_SYNC_FLUSH;
1772
- if(w->mode == WEB_CLIENT_MODE_NORMAL
1803
+ if((w->mode == WEB_CLIENT_MODE_GET || w->mode == WEB_CLIENT_MODE_POST)
1804
|| (w->mode == WEB_CLIENT_MODE_FILECOPY && !web_client_has_wait_receive(w) && w->response.data->len == w->response.rlen)) {
1805
flush = Z_FINISH;
1806
debug(D_DEFLATE, "%llu: Requesting Z_FINISH, if possible.", w->id);
1834
1835
len = web_client_send_data(w,&w->response.zbuffer[w->response.zsent], (size_t) (w->response.zhave - w->response.zsent), MSG_DONTWAIT);
1836
if(len > 0) {
1806
- w->stats_sent_bytes += len;
1837
+ w->statistics.sent_bytes += len;
1838
w->response.zsent += len;
1839
len += t;
1840
debug(D_WEB_CLIENT, "%llu: Sent %zd bytes.", w->id, len);
1889
1890
bytes = web_client_send_data(w,&w->response.data->buffer[w->response.sent], w->response.data->len - w->response.sent, MSG_DONTWAIT);
1891
if(likely(bytes > 0)) {
1861
- w->stats_sent_bytes += bytes;
1892
+ w->statistics.sent_bytes += bytes;
1893
w->response.sent += bytes;
1894
debug(D_WEB_CLIENT, "%llu: Sent %zd bytes.", w->id, bytes);
1895
}
1984
#endif
1985
1986
if(likely(bytes > 0)) {
1956
- w->stats_received_bytes += bytes;
1987
+ w->statistics.received_bytes += bytes;
1988
1989
size_t old = w->response.data->len;
1990
(void)old;
2024
2025
return HTTP_RESP_OK;
2026
}
2027
+
2028
+void web_client_decode_path_and_query_string(struct web_client *w, const char *path_and_query_string) {
2029
+ char buffer[NETDATA_WEB_REQUEST_URL_SIZE + 2];
2030
+ buffer[0] = '?';
2031
+ buffer[0] = '\0';
2032
+
2033
+ buffer_flush(w->url_path_decoded);
2034
+ buffer_flush(w->url_query_string_decoded);
2035
+
2036
+ if(buffer_strlen(w->url_as_received) == 0)
2037
+ // do not overwrite this if it is already filled
2038
+ buffer_strcat(w->url_as_received, path_and_query_string);
2039
+
2040
+ if(w->mode == WEB_CLIENT_MODE_STREAM) {
2041
+ // in stream mode, there is no path
2042
+
2043
+ url_decode_r(buffer, path_and_query_string, NETDATA_WEB_REQUEST_URL_SIZE + 1);
2044
+
2045
+ buffer[NETDATA_WEB_REQUEST_URL_SIZE + 1] = '\0';
2046
+ buffer_strcat(w->url_query_string_decoded, buffer);
2047
+ }
2048
+ else {
2049
+ // in non-stream mode, there is a path
2050
+
2051
+ // FIXME - the way this is implemented, query string params never accept the symbol &, not even encoded as %26
2052
+ // To support the symbol & in query string params, we need to turn the url_query_string_decoded into a
2053
+ // dictionary and decode each of the parameters individually.
2054
+ // OR: in url_query_string_decoded use as separator a control character that cannot appear in the URL.
2055
+
2056
+ char *question_mark_start = strchr(path_and_query_string, '?');
2057
+ if (question_mark_start)
2058
+ url_decode_r(buffer, question_mark_start, NETDATA_WEB_REQUEST_URL_SIZE + 1);
2059
+
2060
+ buffer[NETDATA_WEB_REQUEST_URL_SIZE + 1] = '\0';
2061
+ buffer_strcat(w->url_query_string_decoded, buffer);
2062
+
2063
+ if (question_mark_start) {
2064
+ char c = *question_mark_start;
2065
+ *question_mark_start = '\0';
2066
+ url_decode_r(buffer, path_and_query_string, NETDATA_WEB_REQUEST_URL_SIZE + 1);
2067
+ *question_mark_start = c;
2068
+ } else
2069
+ url_decode_r(buffer, path_and_query_string, NETDATA_WEB_REQUEST_URL_SIZE + 1);
2070
+
2071
+ buffer[NETDATA_WEB_REQUEST_URL_SIZE + 1] = '\0';
2072
+ buffer_strcat(w->url_path_decoded, buffer);
2073
+ }
2074
+}
2075
+
2076
+#ifdef ENABLE_HTTPS
2077
+void web_client_reuse_ssl(struct web_client *w) {
2078
+ if (netdata_ssl_srv_ctx) {
2079
+ if (w->ssl.conn) {
2080
+ SSL_SESSION *session = SSL_get_session(w->ssl.conn);
2081
+ SSL *old = w->ssl.conn;
2082
+ w->ssl.conn = SSL_new(netdata_ssl_srv_ctx);
2083
+ if (session) {
2084
+#if OPENSSL_VERSION_NUMBER >= OPENSSL_VERSION_111
2085
+ if (SSL_SESSION_is_resumable(session))
2086
+#endif
2087
+ SSL_set_session(w->ssl.conn, session);
2088
+ }
2089
+ SSL_free(old);
2090
+ }
2091
+ }
2092
+}
2093
+#endif
2094
+
2095
+void web_client_zero(struct web_client *w) {
2096
+ // zero everything about it - but keep the buffers
2097
+
2098
+ web_client_reset_allocations(w, false);
2099
+
2100
+ // remember the pointers to the buffers
2101
+ BUFFER *b1 = w->response.data;
2102
+ BUFFER *b2 = w->response.header;
2103
+ BUFFER *b3 = w->response.header_output;
2104
+ BUFFER *b4 = w->url_path_decoded;
2105
+ BUFFER *b5 = w->url_as_received;
2106
+ BUFFER *b6 = w->url_query_string_decoded;
2107
+
2108
+#ifdef ENABLE_HTTPS
2109
+ web_client_reuse_ssl(w);
2110
+ SSL *ssl = w->ssl.conn;
2111
+#endif
2112
+
2113
+ size_t use_count = w->use_count;
2114
+ size_t *statistics_memory_accounting = w->statistics.memory_accounting;
2115
+
2116
+ // zero everything
2117
+ memset(w, 0, sizeof(struct web_client));
2118
+
2119
+ w->ifd = w->ofd = -1;
2120
+ w->statistics.memory_accounting = statistics_memory_accounting;
2121
+ w->use_count = use_count;
2122
+
2123
+#ifdef ENABLE_HTTPS
2124
+ w->ssl.conn = ssl;
2125
+ w->ssl.flags = NETDATA_SSL_START;
2126
+ debug(D_WEB_CLIENT_ACCESS,"Reusing SSL structure with (w->ssl = NULL, w->accepted = %u)", w->ssl.flags);
2127
+#endif
2128
+
2129
+ // restore the pointers of the buffers
2130
+ w->response.data = b1;
2131
+ w->response.header = b2;
2132
+ w->response.header_output = b3;
2133
+ w->url_path_decoded = b4;
2134
+ w->url_as_received = b5;
2135
+ w->url_query_string_decoded = b6;
2136
+}
2137
+
2138
+struct web_client *web_client_create(size_t *statistics_memory_accounting) {
2139
+ struct web_client *w = (struct web_client *)callocz(1, sizeof(struct web_client));
2140
+ w->use_count = 1;
2141
+ w->statistics.memory_accounting = statistics_memory_accounting;
2142
+
2143
+ w->url_as_received = buffer_create(NETDATA_WEB_DECODED_URL_INITIAL_SIZE, w->statistics.memory_accounting);
2144
+ w->url_path_decoded = buffer_create(NETDATA_WEB_DECODED_URL_INITIAL_SIZE, w->statistics.memory_accounting);
2145
+ w->url_query_string_decoded = buffer_create(NETDATA_WEB_DECODED_URL_INITIAL_SIZE, w->statistics.memory_accounting);
2146
+ w->response.data = buffer_create(NETDATA_WEB_RESPONSE_INITIAL_SIZE, w->statistics.memory_accounting);
2147
+ w->response.header = buffer_create(NETDATA_WEB_RESPONSE_HEADER_INITIAL_SIZE, w->statistics.memory_accounting);
2148
+ w->response.header_output = buffer_create(NETDATA_WEB_RESPONSE_HEADER_INITIAL_SIZE, w->statistics.memory_accounting);
2149
+
2150
+ __atomic_add_fetch(w->statistics.memory_accounting, sizeof(struct web_client), __ATOMIC_RELAXED);
2151
+
2152
+ return w;
2153
+}
2154
+
2155
+void web_client_free(struct web_client *w) {
2156
+ web_client_reset_allocations(w, true);
2157
+
2158
+ __atomic_sub_fetch(w->statistics.memory_accounting, sizeof(struct web_client), __ATOMIC_RELAXED);
2159
+ freez(w);
2160
+}
2161
+
2162
+inline void web_client_timeout_checkpoint_init(struct web_client *w) {
2163
+ now_monotonic_high_precision_timeval(&w->timings.tv_in);
2164
+}
2165
+
2166
+inline void web_client_timeout_checkpoint_set(struct web_client *w, int timeout_ms) {
2167
+ w->timings.timeout_ut = timeout_ms * USEC_PER_MS;
2168
+
2169
+ if(!w->timings.tv_in.tv_sec)
2170
+ web_client_timeout_checkpoint_init(w);
2171
+
2172
+ if(!w->timings.tv_timeout_last_checkpoint.tv_sec)
2173
+ w->timings.tv_timeout_last_checkpoint = w->timings.tv_in;
2174
+}
2175
+
2176
+inline usec_t web_client_timeout_checkpoint(struct web_client *w) {
2177
+ struct timeval now;
2178
+ now_monotonic_high_precision_timeval(&now);
2179
+
2180
+ if (!w->timings.tv_timeout_last_checkpoint.tv_sec)
2181
+ w->timings.tv_timeout_last_checkpoint = w->timings.tv_in;
2182
+
2183
+ usec_t since_last_check_ut = dt_usec(&w->timings.tv_timeout_last_checkpoint, &now);
2184
+
2185
+ w->timings.tv_timeout_last_checkpoint = now;
2186
+
2187
+ return since_last_check_ut;
2188
+}
2189
+
2190
+inline usec_t web_client_timeout_checkpoint_response_ready(struct web_client *w, usec_t *usec_since_last_checkpoint) {
2191
+ usec_t since_last_check_ut = web_client_timeout_checkpoint(w);
2192
+ if(usec_since_last_checkpoint)
2193
+ *usec_since_last_checkpoint = since_last_check_ut;
2194
+
2195
+ w->timings.tv_ready = w->timings.tv_timeout_last_checkpoint;
2196
+
2197
+ // return the total time of the query
2198
+ return dt_usec(&w->timings.tv_in, &w->timings.tv_ready);
2199
+}
2200
+
2201
+inline bool web_client_timeout_checkpoint_and_check(struct web_client *w, usec_t *usec_since_last_checkpoint) {
2202
+
2203
+ usec_t since_last_check_ut = web_client_timeout_checkpoint(w);
2204
+ if(usec_since_last_checkpoint)
2205
+ *usec_since_last_checkpoint = since_last_check_ut;
2206
+
2207
+ if(!w->timings.timeout_ut)
2208
+ return false;
2209
+
2210
+ usec_t since_reception_ut = dt_usec(&w->timings.tv_in, &w->timings.tv_timeout_last_checkpoint);
2211
+ if (since_reception_ut >= w->timings.timeout_ut) {
2212
+ buffer_flush(w->response.data);
2213
+ buffer_strcat(w->response.data, "Query timeout exceeded");
2214
+ w->response.code = HTTP_RESP_BACKEND_FETCH_FAILED;
2215
+ return true;
2216
+ }
2217
+
2218
+ return false;
2219
+}