[Developer] #424 show ORDS request response details

Refs #424
This commit is contained in:
devmrko
2026-06-23 11:54:57 +09:00
parent ad7764ecd3
commit 7d3bc02123
4 changed files with 174 additions and 7 deletions

View File

@@ -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
);
} }
} }

View File

@@ -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);
}
}
} }

View File

@@ -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;
}

View File

@@ -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>