|
22 | 22 | import org.apache.hc.client5.http.config.ConnectionConfig; |
23 | 23 | import org.apache.hc.client5.http.config.RequestConfig; |
24 | 24 | import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder; |
25 | | -import org.apache.hc.client5.http.entity.mime.MultipartPartBuilder; |
26 | 25 | import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; |
27 | 26 | import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; |
28 | 27 | import org.apache.hc.client5.http.impl.io.BasicHttpClientConnectionManager; |
|
86 | 85 | import java.net.UnknownHostException; |
87 | 86 | import java.nio.charset.StandardCharsets; |
88 | 87 | import java.security.NoSuchAlgorithmException; |
| 88 | +import java.util.Arrays; |
89 | 89 | import java.util.Base64; |
90 | 90 | import java.util.Collection; |
91 | 91 | import java.util.Collections; |
| 92 | +import java.util.HashMap; |
92 | 93 | import java.util.HashSet; |
93 | 94 | import java.util.List; |
94 | 95 | import java.util.Map; |
95 | 96 | import java.util.Objects; |
96 | | -import java.util.Optional; |
97 | 97 | import java.util.Properties; |
98 | | -import java.util.Arrays; |
99 | | -import java.util.HashMap; |
100 | 98 | import java.util.Set; |
101 | 99 | import java.util.concurrent.ConcurrentLinkedQueue; |
102 | 100 | import java.util.concurrent.TimeUnit; |
103 | 101 | import java.util.concurrent.atomic.AtomicLong; |
104 | 102 | import java.util.function.BiConsumer; |
105 | 103 | import java.util.function.Function; |
106 | 104 | import java.util.regex.Pattern; |
| 105 | +import java.util.stream.Stream; |
107 | 106 |
|
108 | 107 | public class HttpAPIClientHelper { |
109 | 108 |
|
@@ -341,85 +340,130 @@ public CloseableHttpClient createHttpClient(boolean initSslContext, Map<String, |
341 | 340 | return clientBuilder.build(); |
342 | 341 | } |
343 | 342 |
|
344 | | -// private static final String ERROR_CODE_PREFIX_PATTERN = "Code: %d. DB::Exception:"; |
345 | 343 | private static final String ERROR_CODE_PREFIX_PATTERN = "%d. DB::Exception:"; |
346 | 344 |
|
347 | | - |
348 | 345 | /** |
349 | 346 | * Reads status line and if error tries to parse response body to get server error message. |
350 | 347 | * |
351 | 348 | * @param httpResponse - HTTP response |
352 | 349 | * @return exception object with server code |
353 | 350 | */ |
354 | | - public Exception readError(ClassicHttpResponse httpResponse) { |
355 | | - final Header qIdHeader = httpResponse.getFirstHeader(ClickHouseHttpProto.HEADER_QUERY_ID); |
356 | | - final String queryId = qIdHeader == null ? "" : qIdHeader.getValue(); |
| 351 | + public Exception readError(HttpPost req, ClassicHttpResponse httpResponse) { |
| 352 | + final Header serverQueryIdHeader = httpResponse.getFirstHeader(ClickHouseHttpProto.HEADER_QUERY_ID); |
| 353 | + final Header clientQueryIdHeader = req.getFirstHeader(ClickHouseHttpProto.HEADER_QUERY_ID); |
| 354 | + final Header queryHeader = Stream.of(serverQueryIdHeader, clientQueryIdHeader).filter(Objects::nonNull).findFirst().orElse(null); |
| 355 | + final String queryId = queryHeader == null ? "" : queryHeader.getValue(); |
357 | 356 | int serverCode = getHeaderInt(httpResponse.getFirstHeader(ClickHouseHttpProto.HEADER_EXCEPTION_CODE), 0); |
358 | | - InputStream body = null; |
359 | 357 | try { |
360 | | - body = httpResponse.getEntity().getContent(); |
361 | | - byte[] buffer = new byte[ERROR_BODY_BUFFER_SIZE]; |
362 | | - byte[] lookUpStr = String.format(ERROR_CODE_PREFIX_PATTERN, serverCode).getBytes(StandardCharsets.UTF_8); |
363 | | - StringBuilder msgBuilder = new StringBuilder(); |
364 | | - boolean found = false; |
365 | | - while (true) { |
366 | | - int rBytes = -1; |
367 | | - try { |
368 | | - rBytes = body.read(buffer); |
369 | | - } catch (ClientException e) { |
370 | | - // Invalid LZ4 Magic |
371 | | - if (body instanceof ClickHouseLZ4InputStream) { |
372 | | - ClickHouseLZ4InputStream stream = (ClickHouseLZ4InputStream) body; |
373 | | - body = stream.getInputStream(); |
374 | | - byte[] headerBuffer = stream.getHeaderBuffer(); |
375 | | - System.arraycopy(headerBuffer, 0, buffer, 0, headerBuffer.length); |
376 | | - rBytes = headerBuffer.length; |
| 358 | + return serverCode > 0 ? readClickHouseError(httpResponse.getEntity(), serverCode, queryId, httpResponse.getCode()) : |
| 359 | + readNotClickHouseError(httpResponse.getEntity(), queryId, httpResponse.getCode()); |
| 360 | + } catch (Exception e) { |
| 361 | + LOG.error("Failed to read error message", e); |
| 362 | + String msg = String.format(ERROR_CODE_PREFIX_PATTERN, serverCode) + " <Unreadable error message> (transport error: " + httpResponse.getCode() + ")"; |
| 363 | + return new ServerException(serverCode, msg + " (queryId= " + queryId + ")", httpResponse.getCode(), queryId); |
| 364 | + } |
| 365 | + } |
| 366 | + |
| 367 | + private ServerException readNotClickHouseError(HttpEntity httpEntity, String queryId, int httpCode) { |
| 368 | + |
| 369 | + byte[] buffer = new byte[ERROR_BODY_BUFFER_SIZE]; |
| 370 | + |
| 371 | + String msg = null; |
| 372 | + InputStream body = null; |
| 373 | + int offset = 0; |
| 374 | + for (int i = 0; i < 2; i++) { |
| 375 | + try { |
| 376 | + if (body == null) { |
| 377 | + body = httpEntity.getContent(); |
| 378 | + } |
| 379 | + int msgLen = body.read(buffer, offset, buffer.length - offset); |
| 380 | + if (msgLen > 0) { |
| 381 | + msg = new String(buffer, 0, msgLen, StandardCharsets.UTF_8).trim(); |
| 382 | + if (msg.isEmpty()) { |
| 383 | + msg = "<empty body response>"; |
377 | 384 | } |
378 | 385 | } |
379 | | - if (rBytes == -1) { |
380 | | - break; |
| 386 | + break; |
| 387 | + } catch (ClientException e) { |
| 388 | + // Invalid LZ4 Magic |
| 389 | + if (body instanceof ClickHouseLZ4InputStream) { |
| 390 | + ClickHouseLZ4InputStream stream = (ClickHouseLZ4InputStream) body; |
| 391 | + body = stream.getInputStream(); |
| 392 | + byte[] lzHeader = stream.getHeaderBuffer(); // Here is read part of original body |
| 393 | + offset = Math.min(lzHeader.length, buffer.length); |
| 394 | + System.arraycopy(lzHeader, 0, buffer, 0, offset); |
| 395 | + continue; |
| 396 | + } |
| 397 | + throw e; |
| 398 | + } catch (Exception e) { |
| 399 | + LOG.warn("Failed to read error message (queryId = " + queryId + ")", e); |
| 400 | + break; |
| 401 | + } |
| 402 | + } |
| 403 | + |
| 404 | + String errormsg = msg == null ? "unknown server error" : msg; |
| 405 | + return new ServerException(ServerException.CODE_UNKNOWN, errormsg + " (transport error: " + httpCode +")", httpCode, queryId); |
| 406 | + } |
| 407 | + |
| 408 | + private static ServerException readClickHouseError(HttpEntity httpEntity, int serverCode, String queryId, int httpCode) throws Exception { |
| 409 | + InputStream body = httpEntity.getContent(); |
| 410 | + byte[] buffer = new byte[ERROR_BODY_BUFFER_SIZE]; |
| 411 | + byte[] lookUpStr = String.format(ERROR_CODE_PREFIX_PATTERN, serverCode).getBytes(StandardCharsets.UTF_8); |
| 412 | + StringBuilder msgBuilder = new StringBuilder(); |
| 413 | + boolean found = false; |
| 414 | + while (true) { |
| 415 | + int rBytes = -1; |
| 416 | + try { |
| 417 | + rBytes = body.read(buffer); |
| 418 | + } catch (ClientException e) { |
| 419 | + // Invalid LZ4 Magic |
| 420 | + if (body instanceof ClickHouseLZ4InputStream) { |
| 421 | + ClickHouseLZ4InputStream stream = (ClickHouseLZ4InputStream) body; |
| 422 | + body = stream.getInputStream(); |
| 423 | + byte[] headerBuffer = stream.getHeaderBuffer(); |
| 424 | + System.arraycopy(headerBuffer, 0, buffer, 0, headerBuffer.length); |
| 425 | + rBytes = headerBuffer.length; |
381 | 426 | } |
| 427 | + } |
| 428 | + if (rBytes == -1) { |
| 429 | + break; |
| 430 | + } |
382 | 431 |
|
383 | | - for (int i = 0; i < rBytes; i++) { |
384 | | - if (buffer[i] == lookUpStr[0]) { |
385 | | - found = true; |
386 | | - for (int j = 1; j < Math.min(rBytes - i, lookUpStr.length); j++) { |
387 | | - if (buffer[i + j] != lookUpStr[j]) { |
388 | | - found = false; |
389 | | - break; |
390 | | - } |
391 | | - } |
392 | | - if (found) { |
393 | | - msgBuilder.append(new String(buffer, i, rBytes - i, StandardCharsets.UTF_8)); |
| 432 | + for (int i = 0; i < rBytes; i++) { |
| 433 | + if (buffer[i] == lookUpStr[0]) { |
| 434 | + found = true; |
| 435 | + for (int j = 1; j < Math.min(rBytes - i, lookUpStr.length); j++) { |
| 436 | + if (buffer[i + j] != lookUpStr[j]) { |
| 437 | + found = false; |
394 | 438 | break; |
395 | 439 | } |
396 | 440 | } |
397 | | - } |
398 | | - |
399 | | - if (found) { |
400 | | - break; |
| 441 | + if (found) { |
| 442 | + msgBuilder.append(new String(buffer, i, rBytes - i, StandardCharsets.UTF_8)); |
| 443 | + break; |
| 444 | + } |
401 | 445 | } |
402 | 446 | } |
403 | 447 |
|
404 | | - while (true) { |
405 | | - int rBytes = body.read(buffer); |
406 | | - if (rBytes == -1) { |
407 | | - break; |
408 | | - } |
409 | | - msgBuilder.append(new String(buffer, 0, rBytes, StandardCharsets.UTF_8)); |
| 448 | + if (found) { |
| 449 | + break; |
410 | 450 | } |
| 451 | + } |
411 | 452 |
|
412 | | - String msg = msgBuilder.toString().replaceAll("\\s+", " ").replaceAll("\\\\n", " ") |
413 | | - .replaceAll("\\\\/", "/"); |
414 | | - if (msg.trim().isEmpty()) { |
415 | | - msg = String.format(ERROR_CODE_PREFIX_PATTERN, serverCode) + " <Unreadable error message> (transport error: " + httpResponse.getCode() + ")"; |
| 453 | + while (true) { |
| 454 | + int rBytes = body.read(buffer); |
| 455 | + if (rBytes == -1) { |
| 456 | + break; |
416 | 457 | } |
417 | | - return new ServerException(serverCode, "Code: " + msg + " (queryId= " + queryId + ")", httpResponse.getCode(), queryId); |
418 | | - } catch (Exception e) { |
419 | | - LOG.error("Failed to read error message", e); |
420 | | - String msg = String.format(ERROR_CODE_PREFIX_PATTERN, serverCode) + " <Unreadable error message> (transport error: " + httpResponse.getCode() + ")"; |
421 | | - return new ServerException(serverCode, msg + " (queryId= " + queryId + ")", httpResponse.getCode(), queryId); |
| 458 | + msgBuilder.append(new String(buffer, 0, rBytes, StandardCharsets.UTF_8)); |
| 459 | + } |
| 460 | + |
| 461 | + String msg = msgBuilder.toString().replaceAll("\\s+", " ").replaceAll("\\\\n", " ") |
| 462 | + .replaceAll("\\\\/", "/"); |
| 463 | + if (msg.trim().isEmpty()) { |
| 464 | + msg = String.format(ERROR_CODE_PREFIX_PATTERN, serverCode) + " <Unreadable error message> (transport error: " + httpCode + ")"; |
422 | 465 | } |
| 466 | + return new ServerException(serverCode, "Code: " + msg + " (queryId= " + queryId + ")", httpCode, queryId); |
423 | 467 | } |
424 | 468 |
|
425 | 469 | private static final long POOL_VENT_TIMEOUT = 10000L; |
@@ -536,7 +580,7 @@ private ClassicHttpResponse doPostRequest(Map<String, Object> requestConfig, Htt |
536 | 580 | throw new ClientException("Server returned '502 Bad gateway'. Check network and proxy settings."); |
537 | 581 | } else if (httpResponse.getCode() >= HttpStatus.SC_BAD_REQUEST || httpResponse.containsHeader(ClickHouseHttpProto.HEADER_EXCEPTION_CODE)) { |
538 | 582 | try { |
539 | | - throw readError(httpResponse); |
| 583 | + throw readError(req, httpResponse); |
540 | 584 | } finally { |
541 | 585 | httpResponse.close(); |
542 | 586 | } |
|
0 commit comments