@@ -10,10 +10,38 @@ public record ProbeResult(
|
|||||||
int rowCount,
|
int rowCount,
|
||||||
List<String> maskedColumns,
|
List<String> maskedColumns,
|
||||||
String errorCode,
|
String errorCode,
|
||||||
String errorMessage
|
String errorMessage,
|
||||||
|
String requestHeaders,
|
||||||
|
String requestPayload,
|
||||||
|
String responseHeaders,
|
||||||
|
String responseBody
|
||||||
) {
|
) {
|
||||||
|
|
||||||
public static ProbeResult blocked(ProbeStatus status, String errorCode, String errorMessage) {
|
public static ProbeResult blocked(ProbeStatus status, String errorCode, String errorMessage) {
|
||||||
return new ProbeResult(status, List.of(), List.of(), 0, List.of(), errorCode, errorMessage);
|
return blocked(status, errorCode, errorMessage, null, null, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ProbeResult blocked(
|
||||||
|
ProbeStatus status,
|
||||||
|
String errorCode,
|
||||||
|
String errorMessage,
|
||||||
|
String requestHeaders,
|
||||||
|
String requestPayload,
|
||||||
|
String responseHeaders,
|
||||||
|
String responseBody
|
||||||
|
) {
|
||||||
|
return new ProbeResult(
|
||||||
|
status,
|
||||||
|
List.of(),
|
||||||
|
List.of(),
|
||||||
|
0,
|
||||||
|
List.of(),
|
||||||
|
errorCode,
|
||||||
|
errorMessage,
|
||||||
|
requestHeaders,
|
||||||
|
requestPayload,
|
||||||
|
responseHeaders,
|
||||||
|
responseBody
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,21 +97,47 @@ public class OrdsProbeService {
|
|||||||
ProbeStatus.OBJECT_DISABLED, "OBJECT_DISABLED", e.getMessage()));
|
ProbeStatus.OBJECT_DISABLED, "OBJECT_DISABLED", e.getMessage()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String requestHeaders = null;
|
||||||
|
String requestPayload = null;
|
||||||
try {
|
try {
|
||||||
URI uri = buildUri(object.ordsPath(), command.limit());
|
URI uri = buildUri(object.ordsPath(), command.limit());
|
||||||
HttpHeaders headers = new HttpHeaders();
|
HttpHeaders headers = new HttpHeaders();
|
||||||
headers.setBearerAuth(command.bearerToken());
|
headers.setBearerAuth(command.bearerToken());
|
||||||
|
requestHeaders = prettyHeaders(maskedRequestHeaders(headers));
|
||||||
|
requestPayload = prettyJson("{}");
|
||||||
ResponseEntity<String> response = ordsRestTemplate.exchange(
|
ResponseEntity<String> response = ordsRestTemplate.exchange(
|
||||||
uri, HttpMethod.POST, new HttpEntity<>(headers), String.class);
|
uri, HttpMethod.POST, new HttpEntity<>(headers), String.class);
|
||||||
ProbeResult result = parseSuccess(response.getBody(), object.objectId());
|
ProbeResult result = parseSuccess(
|
||||||
|
response.getBody(),
|
||||||
|
object.objectId(),
|
||||||
|
requestHeaders,
|
||||||
|
requestPayload,
|
||||||
|
prettyHeaders(response.getHeaders()),
|
||||||
|
prettyJson(response.getBody())
|
||||||
|
);
|
||||||
return auditAndReturn(command, result);
|
return auditAndReturn(command, result);
|
||||||
} catch (HttpStatusCodeException e) {
|
} catch (HttpStatusCodeException e) {
|
||||||
ProbeStatus status = errorClassifier.classify(e.getStatusCode(), e.getResponseBodyAsString());
|
ProbeStatus status = errorClassifier.classify(e.getStatusCode(), e.getResponseBodyAsString());
|
||||||
return auditAndReturn(command, ProbeResult.blocked(
|
return auditAndReturn(command, ProbeResult.blocked(
|
||||||
status, status.name(), trimMessage(e.getResponseBodyAsString())));
|
status,
|
||||||
|
status.name(),
|
||||||
|
trimMessage(e.getResponseBodyAsString()),
|
||||||
|
requestHeaders,
|
||||||
|
requestPayload,
|
||||||
|
prettyHeaders(e.getResponseHeaders()),
|
||||||
|
prettyJson(e.getResponseBodyAsString())
|
||||||
|
));
|
||||||
} catch (ResourceAccessException e) {
|
} catch (ResourceAccessException e) {
|
||||||
ProbeStatus status = classifyResourceAccess(e);
|
ProbeStatus status = classifyResourceAccess(e);
|
||||||
return auditAndReturn(command, ProbeResult.blocked(status, status.name(), resourceAccessMessage(status, e)));
|
return auditAndReturn(command, ProbeResult.blocked(
|
||||||
|
status,
|
||||||
|
status.name(),
|
||||||
|
resourceAccessMessage(status, e),
|
||||||
|
requestHeaders,
|
||||||
|
requestPayload,
|
||||||
|
"{}",
|
||||||
|
""
|
||||||
|
));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return auditAndReturn(command, ProbeResult.blocked(
|
return auditAndReturn(command, ProbeResult.blocked(
|
||||||
ProbeStatus.INVALID_ORDS_RESPONSE, "INVALID_ORDS_RESPONSE", e.getMessage()));
|
ProbeStatus.INVALID_ORDS_RESPONSE, "INVALID_ORDS_RESPONSE", e.getMessage()));
|
||||||
@@ -129,7 +155,14 @@ public class OrdsProbeService {
|
|||||||
.toUri();
|
.toUri();
|
||||||
}
|
}
|
||||||
|
|
||||||
private ProbeResult parseSuccess(String body, long objectId) throws Exception {
|
private ProbeResult parseSuccess(
|
||||||
|
String body,
|
||||||
|
long objectId,
|
||||||
|
String requestHeaders,
|
||||||
|
String requestPayload,
|
||||||
|
String responseHeaders,
|
||||||
|
String responseBody
|
||||||
|
) throws Exception {
|
||||||
JsonNode root = objectMapper.readTree(body);
|
JsonNode root = objectMapper.readTree(body);
|
||||||
JsonNode rowsNode = root.has("rows") ? root.get("rows") : root;
|
JsonNode rowsNode = root.has("rows") ? root.get("rows") : root;
|
||||||
rowsNode = root.has("items") ? root.get("items") : rowsNode;
|
rowsNode = root.has("items") ? root.get("items") : rowsNode;
|
||||||
@@ -153,7 +186,11 @@ public class OrdsProbeService {
|
|||||||
rows.size(),
|
rows.size(),
|
||||||
findMaskedColumns(objectId, rows),
|
findMaskedColumns(objectId, rows),
|
||||||
null,
|
null,
|
||||||
null
|
null,
|
||||||
|
requestHeaders,
|
||||||
|
requestPayload,
|
||||||
|
responseHeaders,
|
||||||
|
responseBody
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,4 +267,53 @@ public class OrdsProbeService {
|
|||||||
}
|
}
|
||||||
return body.length() <= 500 ? body : body.substring(0, 500);
|
return body.length() <= 500 ? body : body.substring(0, 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private HttpHeaders maskedRequestHeaders(HttpHeaders headers) {
|
||||||
|
HttpHeaders masked = new HttpHeaders();
|
||||||
|
masked.putAll(headers);
|
||||||
|
List<String> authorization = headers.get(HttpHeaders.AUTHORIZATION);
|
||||||
|
if (authorization != null && !authorization.isEmpty()) {
|
||||||
|
masked.set(HttpHeaders.AUTHORIZATION, maskBearer(authorization.get(0)));
|
||||||
|
}
|
||||||
|
return masked;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String maskBearer(String value) {
|
||||||
|
if (value == null || value.isBlank()) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
if (!value.toLowerCase(Locale.ROOT).startsWith("bearer ")) {
|
||||||
|
return "****";
|
||||||
|
}
|
||||||
|
String token = value.substring("Bearer ".length());
|
||||||
|
String suffix = token.length() <= 6 ? "" : token.substring(token.length() - 6);
|
||||||
|
return "Bearer ****" + suffix;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String prettyHeaders(HttpHeaders headers) {
|
||||||
|
if (headers == null || headers.isEmpty()) {
|
||||||
|
return "{}";
|
||||||
|
}
|
||||||
|
return prettyObject(headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String prettyJson(String body) {
|
||||||
|
if (body == null || body.isBlank()) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return objectMapper.writerWithDefaultPrettyPrinter()
|
||||||
|
.writeValueAsString(objectMapper.readTree(body));
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String prettyObject(Object value) {
|
||||||
|
try {
|
||||||
|
return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(value);
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
return String.valueOf(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,3 +105,37 @@ body {
|
|||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
margin-bottom: .75rem;
|
margin-bottom: .75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.probe-exchange-grid {
|
||||||
|
display: grid;
|
||||||
|
gap: .75rem;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
||||||
|
margin: 1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.probe-exchange {
|
||||||
|
border: 1px solid #e4e7ec;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.probe-exchange h3 {
|
||||||
|
background: #f2f4f7;
|
||||||
|
border-bottom: 1px solid #e4e7ec;
|
||||||
|
font-size: .85rem;
|
||||||
|
font-weight: 700;
|
||||||
|
margin: 0;
|
||||||
|
padding: .55rem .75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.probe-exchange pre {
|
||||||
|
background: #101828;
|
||||||
|
color: #f9fafb;
|
||||||
|
font-size: .78rem;
|
||||||
|
margin: 0;
|
||||||
|
max-height: 320px;
|
||||||
|
overflow: auto;
|
||||||
|
padding: .75rem;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|||||||
@@ -20,6 +20,25 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="probe-exchange-grid">
|
||||||
|
<section class="probe-exchange">
|
||||||
|
<h3>Request Headers</h3>
|
||||||
|
<pre th:text="${result.requestHeaders()} ?: '{}'">{}</pre>
|
||||||
|
</section>
|
||||||
|
<section class="probe-exchange">
|
||||||
|
<h3>Request Payload</h3>
|
||||||
|
<pre th:text="${result.requestPayload()} ?: '{}'">{}</pre>
|
||||||
|
</section>
|
||||||
|
<section class="probe-exchange">
|
||||||
|
<h3>Response Headers</h3>
|
||||||
|
<pre th:text="${result.responseHeaders()} ?: '{}'">{}</pre>
|
||||||
|
</section>
|
||||||
|
<section class="probe-exchange">
|
||||||
|
<h3>Response Body</h3>
|
||||||
|
<pre th:text="${result.responseBody()} ?: ''"></pre>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="table-responsive" th:if="${!#lists.isEmpty(result.rows())}">
|
<div class="table-responsive" th:if="${!#lists.isEmpty(result.rows())}">
|
||||||
<table class="table table-sm table-striped align-middle">
|
<table class="table table-sm table-striped align-middle">
|
||||||
<thead>
|
<thead>
|
||||||
|
|||||||
Reference in New Issue
Block a user