Merge branch 'master' into design
This commit is contained in:
@@ -68,8 +68,16 @@ public class ApiTesterFilter implements Filter {
|
|||||||
ApiService apiSpecInfoDtoService = ApplicationContextUtil.getContext().getBean(ApiService.class);
|
ApiService apiSpecInfoDtoService = ApplicationContextUtil.getContext().getBean(ApiService.class);
|
||||||
String url = httpServletRequest.getHeader("original-url");
|
String url = httpServletRequest.getHeader("original-url");
|
||||||
|
|
||||||
|
// 감사 로그: 요청 1건당 REQ/RES 두 줄을 같은 auditId 로 남긴다 (전용 파일, 1년 보관)
|
||||||
|
String auditId = ApiTesterAuditLogger.newAuditId();
|
||||||
|
long auditStart = System.currentTimeMillis();
|
||||||
|
String auditType = "-";
|
||||||
|
|
||||||
// original-url 헤더가 없으면 프록시 대상을 알 수 없음 → 400 (NPE 방지)
|
// original-url 헤더가 없으면 프록시 대상을 알 수 없음 → 400 (NPE 방지)
|
||||||
if (url == null || url.trim().isEmpty()) {
|
if (url == null || url.trim().isEmpty()) {
|
||||||
|
ApiTesterAuditLogger.logRequest(auditId, httpServletRequest, null, "-", false, null);
|
||||||
|
ApiTesterAuditLogger.logResult(auditId, HttpServletResponse.SC_BAD_REQUEST, "BAD_REQUEST",
|
||||||
|
System.currentTimeMillis() - auditStart);
|
||||||
writeJson(response, HttpServletResponse.SC_BAD_REQUEST, "{\"error\":\"original-url 헤더가 없습니다.\"}");
|
writeJson(response, HttpServletResponse.SC_BAD_REQUEST, "{\"error\":\"original-url 헤더가 없습니다.\"}");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -84,16 +92,15 @@ public class ApiTesterFilter implements Filter {
|
|||||||
boolean tokenRequest = url.contains(DjbTestbedGatewayProperty.PORTAL_MOCK_TOKEN_PATH)
|
boolean tokenRequest = url.contains(DjbTestbedGatewayProperty.PORTAL_MOCK_TOKEN_PATH)
|
||||||
|| url.contains(gatewayProperty.tokenPath());
|
|| url.contains(gatewayProperty.tokenPath());
|
||||||
|
|
||||||
|
// 본문은 한 번만 읽어 프록시 forward 와 감사 로그에 함께 사용 (GET 이면 빈 문자열)
|
||||||
|
String requestBody = readBody(httpServletRequest);
|
||||||
|
ApiTesterAuditLogger.logRequest(auditId, httpServletRequest, url, gatewayMode.name(), tokenRequest, requestBody);
|
||||||
|
|
||||||
if (tokenRequest) {
|
if (tokenRequest) {
|
||||||
StringBuilder sb = new StringBuilder();
|
String body = requestBody;
|
||||||
BufferedReader reader = httpServletRequest.getReader();
|
|
||||||
String line;
|
|
||||||
while ((line = reader.readLine()) != null) {
|
|
||||||
sb.append(line);
|
|
||||||
}
|
|
||||||
String body = sb.toString();
|
|
||||||
|
|
||||||
if (gatewayMode == DjbGatewayMode.PORTAL_MOCK) {
|
if (gatewayMode == DjbGatewayMode.PORTAL_MOCK) {
|
||||||
|
auditType = "TOKEN_MOCK";
|
||||||
// PortalMock: 고정 mock 토큰 반환 (기존 동작 유지)
|
// PortalMock: 고정 mock 토큰 반환 (기존 동작 유지)
|
||||||
Map<String, String> params = new HashMap<>();
|
Map<String, String> params = new HashMap<>();
|
||||||
String[] pairs = body.split("&");
|
String[] pairs = body.split("&");
|
||||||
@@ -106,7 +113,7 @@ public class ApiTesterFilter implements Filter {
|
|||||||
String scope = params.getOrDefault("scope", "default");
|
String scope = params.getOrDefault("scope", "default");
|
||||||
|
|
||||||
String token = "{\n" +
|
String token = "{\n" +
|
||||||
" \"access_token\": \"djbank_gw_sample_token\",\n" +
|
" \"access_token\": \"" + escapeJson(gatewayProperty.mockAccessToken()) + "\",\n" +
|
||||||
" \"token_type\": \"bearer\",\n" +
|
" \"token_type\": \"bearer\",\n" +
|
||||||
" \"expires_in\": 86400,\n" +
|
" \"expires_in\": 86400,\n" +
|
||||||
" \"scope\": \""+scope +"\",\n" +
|
" \"scope\": \""+scope +"\",\n" +
|
||||||
@@ -117,6 +124,7 @@ public class ApiTesterFilter implements Filter {
|
|||||||
response.getWriter().println(token);
|
response.getWriter().println(token);
|
||||||
} else {
|
} else {
|
||||||
// GATEWAY: 실 게이트웨이 토큰 엔드포인트로 forward (token 발급만)
|
// GATEWAY: 실 게이트웨이 토큰 엔드포인트로 forward (token 발급만)
|
||||||
|
auditType = "TOKEN_GW";
|
||||||
APISender apiSender = ApplicationContextUtil.getContext().getBean(APISender.class);
|
APISender apiSender = ApplicationContextUtil.getContext().getBean(APISender.class);
|
||||||
Map<String, String> headers = new HashMap<>();
|
Map<String, String> headers = new HashMap<>();
|
||||||
headers.put("Content-Type", "application/x-www-form-urlencoded");
|
headers.put("Content-Type", "application/x-www-form-urlencoded");
|
||||||
@@ -133,6 +141,7 @@ public class ApiTesterFilter implements Filter {
|
|||||||
|
|
||||||
// URL/메서드에 해당하는 API 명세가 없으면 404 (NPE 방지)
|
// URL/메서드에 해당하는 API 명세가 없으면 404 (NPE 방지)
|
||||||
if (apiSpecInfoDto == null) {
|
if (apiSpecInfoDto == null) {
|
||||||
|
auditType = "SPEC_NOT_FOUND";
|
||||||
writeJson(response, HttpServletResponse.SC_NOT_FOUND,
|
writeJson(response, HttpServletResponse.SC_NOT_FOUND,
|
||||||
"{\"error\":\"해당 URL/메서드의 API 명세를 찾을 수 없습니다.\"}");
|
"{\"error\":\"해당 URL/메서드의 API 명세를 찾을 수 없습니다.\"}");
|
||||||
return;
|
return;
|
||||||
@@ -142,6 +151,7 @@ public class ApiTesterFilter implements Filter {
|
|||||||
|
|
||||||
// sample(기본): 저장된 샘플 응답 반환 (실호출 없음)
|
// sample(기본): 저장된 샘플 응답 반환 (실호출 없음)
|
||||||
if (responseType == null || responseType.equalsIgnoreCase("sample")) {
|
if (responseType == null || responseType.equalsIgnoreCase("sample")) {
|
||||||
|
auditType = "SAMPLE";
|
||||||
response.setContentType("application/json");
|
response.setContentType("application/json");
|
||||||
response.getWriter().println(apiSpecInfoDto.getSampleResponse());
|
response.getWriter().println(apiSpecInfoDto.getSampleResponse());
|
||||||
return;
|
return;
|
||||||
@@ -151,6 +161,7 @@ public class ApiTesterFilter implements Filter {
|
|||||||
// - gw : djb.gateway.base-url + path == original-url 전체 (spec servers[0].url + path)
|
// - gw : djb.gateway.base-url + path == original-url 전체 (spec servers[0].url + path)
|
||||||
// - mock : ApiSpecInfo.mockUrl (기존 동작)
|
// - mock : ApiSpecInfo.mockUrl (기존 동작)
|
||||||
boolean gw = "gw".equalsIgnoreCase(responseType);
|
boolean gw = "gw".equalsIgnoreCase(responseType);
|
||||||
|
auditType = gw ? "GW" : "MOCK";
|
||||||
|
|
||||||
Map<String, String> headers = new HashMap<>();
|
Map<String, String> headers = new HashMap<>();
|
||||||
Enumeration<String> headerNames = httpServletRequest.getHeaderNames();
|
Enumeration<String> headerNames = httpServletRequest.getHeaderNames();
|
||||||
@@ -160,6 +171,10 @@ public class ApiTesterFilter implements Filter {
|
|||||||
}
|
}
|
||||||
headers.remove("original-url");
|
headers.remove("original-url");
|
||||||
headers.remove("original-api-id");
|
headers.remove("original-api-id");
|
||||||
|
// readBody()가 개행을 제거해 원본 Content-Length와 실제 전송 바이트가 달라질 수 있고,
|
||||||
|
// WebLogic HTTP 클라이언트는 이 불일치를 IOException으로 처리하므로 length 계열 헤더는
|
||||||
|
// 전달하지 않는다(HttpURLConnection이 실제 바이트 수로 재설정).
|
||||||
|
headers.keySet().removeIf(k -> "content-length".equalsIgnoreCase(k) || "transfer-encoding".equalsIgnoreCase(k));
|
||||||
|
|
||||||
String targetUri;
|
String targetUri;
|
||||||
Map<String, String[]> paramMap;
|
Map<String, String[]> paramMap;
|
||||||
@@ -179,7 +194,7 @@ public class ApiTesterFilter implements Filter {
|
|||||||
APISender apiSender = ApplicationContextUtil.getContext().getBean(APISender.class);
|
APISender apiSender = ApplicationContextUtil.getContext().getBean(APISender.class);
|
||||||
String responseStr;
|
String responseStr;
|
||||||
if ("post".equalsIgnoreCase(apiSpecInfoDto.getApiMethod())) {
|
if ("post".equalsIgnoreCase(apiSpecInfoDto.getApiMethod())) {
|
||||||
responseStr = apiSender.requestPost(targetUri, headers, paramMap, readBody(httpServletRequest));
|
responseStr = apiSender.requestPost(targetUri, headers, paramMap, requestBody);
|
||||||
} else {
|
} else {
|
||||||
responseStr = apiSender.requestGet(targetUri, headers, paramMap);
|
responseStr = apiSender.requestGet(targetUri, headers, paramMap);
|
||||||
}
|
}
|
||||||
@@ -202,6 +217,9 @@ public class ApiTesterFilter implements Filter {
|
|||||||
logger.error("테스트베드 프록시 처리 오류", e);
|
logger.error("테스트베드 프록시 처리 오류", e);
|
||||||
writeJson(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
|
writeJson(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
|
||||||
"{\"error\":\"요청 처리 중 오류\",\"detail\":\"" + escapeJson(e.getMessage()) + "\"}");
|
"{\"error\":\"요청 처리 중 오류\",\"detail\":\"" + escapeJson(e.getMessage()) + "\"}");
|
||||||
|
} finally {
|
||||||
|
ApiTesterAuditLogger.logResult(auditId, ((HttpServletResponse) response).getStatus(), auditType,
|
||||||
|
System.currentTimeMillis() - auditStart);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+10
@@ -32,6 +32,7 @@ public class DjbTestbedGatewayProperty {
|
|||||||
public static final String KEY_TIMEOUT_SEC = "djb.gateway.timeout";
|
public static final String KEY_TIMEOUT_SEC = "djb.gateway.timeout";
|
||||||
public static final String KEY_USE_PROXY = "djb.gateway.use-proxy";
|
public static final String KEY_USE_PROXY = "djb.gateway.use-proxy";
|
||||||
public static final String KEY_TOKEN_USE_PROXY = "djb.gateway.token-use-proxy";
|
public static final String KEY_TOKEN_USE_PROXY = "djb.gateway.token-use-proxy";
|
||||||
|
public static final String KEY_MOCK_ACCESS_TOKEN = "djb.gateway.mock-access-token";
|
||||||
|
|
||||||
public static final String DEFAULT_BASE_URL = "PortalMock";
|
public static final String DEFAULT_BASE_URL = "PortalMock";
|
||||||
public static final String DEFAULT_TIMEOUT_SEC = "10";
|
public static final String DEFAULT_TIMEOUT_SEC = "10";
|
||||||
@@ -47,6 +48,9 @@ public class DjbTestbedGatewayProperty {
|
|||||||
/** PortalMock 모드에서 사용하는 포털 기존 mock 토큰 경로. */
|
/** PortalMock 모드에서 사용하는 포털 기존 mock 토큰 경로. */
|
||||||
public static final String PORTAL_MOCK_TOKEN_PATH = "/api/v1/oauth/token";
|
public static final String PORTAL_MOCK_TOKEN_PATH = "/api/v1/oauth/token";
|
||||||
|
|
||||||
|
/** PortalMock 모드 토큰 응답의 access_token 기본값. */
|
||||||
|
public static final String DEFAULT_MOCK_ACCESS_TOKEN = "djbank_gw_sample_token";
|
||||||
|
|
||||||
public String baseUrl() {
|
public String baseUrl() {
|
||||||
return resolve(KEY_BASE_URL, DEFAULT_BASE_URL,
|
return resolve(KEY_BASE_URL, DEFAULT_BASE_URL,
|
||||||
"GW Base URL. 문자열 \"PortalMock\" 이면 ApiTesterFilter 가 mock 토큰 반환");
|
"GW Base URL. 문자열 \"PortalMock\" 이면 ApiTesterFilter 가 mock 토큰 반환");
|
||||||
@@ -86,6 +90,12 @@ public class DjbTestbedGatewayProperty {
|
|||||||
"테스트베드 토큰 발급 시 서버 프록시(/api/call-api) 사용 여부(true/false). false 이면 브라우저에서 직접 호출."), true);
|
"테스트베드 토큰 발급 시 서버 프록시(/api/call-api) 사용 여부(true/false). false 이면 브라우저에서 직접 호출."), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** PortalMock 모드 토큰 응답에 넣을 access_token 값. */
|
||||||
|
public String mockAccessToken() {
|
||||||
|
return resolve(KEY_MOCK_ACCESS_TOKEN, DEFAULT_MOCK_ACCESS_TOKEN,
|
||||||
|
"PortalMock 모드 토큰 발급 응답의 access_token 값");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 프로퍼티 값을 boolean 으로 해석. 레거시 {@code Y/N} 값도 자동 변환한다.
|
* 프로퍼티 값을 boolean 으로 해석. 레거시 {@code Y/N} 값도 자동 변환한다.
|
||||||
* {@code true}/{@code Y} → true, {@code false}/{@code N} → false, 그 외/null → {@code def}.
|
* {@code true}/{@code Y} → true, {@code false}/{@code N} → false, 그 외/null → {@code def}.
|
||||||
|
|||||||
+25
-8
@@ -25,8 +25,11 @@ import org.springframework.web.bind.annotation.RestController;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* testbed spec(swagger.json/yaml)에 AUTHTYPE 기반 securityScheme 를 주입하고,
|
* testbed spec(swagger.json/yaml)에 AUTHTYPE 기반 securityScheme 를 주입하고,
|
||||||
* 서버 sentinel({@link DjbTestbedSpecServerRewriter#SERVER_SENTINEL})을 API SPEC 설정(responseType)에
|
* 서버 sentinel({@link DjbTestbedSpecServerRewriter#SERVER_SENTINEL})을 실주소로 치환해 반환한다.
|
||||||
* 따른 실주소로 치환해 반환한다.
|
* <ul>
|
||||||
|
* <li>{@code swagger.json}/{@code swagger.yaml} : 외부 공개/다운로드용 — 항상 GW 주소로 치환</li>
|
||||||
|
* <li>{@code swagger-ui.json} : Swagger UI 전용 — API SPEC 설정(responseType: sample/mock/gw)에 따라 치환</li>
|
||||||
|
* </ul>
|
||||||
* {@code default-token-api-spec} 은 클래스패스 기본 spec 을 그대로 반환(auth enrich 대상 외).
|
* {@code default-token-api-spec} 은 클래스패스 기본 spec 을 그대로 반환(auth enrich 대상 외).
|
||||||
*/
|
*/
|
||||||
@RestController
|
@RestController
|
||||||
@@ -45,22 +48,34 @@ public class DjbTestbedSpecController {
|
|||||||
|
|
||||||
@GetMapping(value = "/{id}/swagger.json", produces = MediaType.APPLICATION_JSON_VALUE)
|
@GetMapping(value = "/{id}/swagger.json", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||||
public ResponseEntity<String> swaggerWithAuth(@PathVariable String id, HttpServletRequest request) throws IOException {
|
public ResponseEntity<String> swaggerWithAuth(@PathVariable String id, HttpServletRequest request) throws IOException {
|
||||||
String json = buildSpecJson(id, request);
|
String json = buildSpecJson(id, request, true);
|
||||||
return json == null ? ResponseEntity.notFound().build() : ResponseEntity.ok(json);
|
return json == null ? ResponseEntity.notFound().build() : ResponseEntity.ok(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping(value = "/{id}/swagger.yaml", produces = "application/x-yaml")
|
@GetMapping(value = "/{id}/swagger.yaml", produces = "application/x-yaml")
|
||||||
public ResponseEntity<String> swaggerYamlWithAuth(@PathVariable String id, HttpServletRequest request) throws IOException {
|
public ResponseEntity<String> swaggerYamlWithAuth(@PathVariable String id, HttpServletRequest request) throws IOException {
|
||||||
String json = buildSpecJson(id, request);
|
String json = buildSpecJson(id, request, true);
|
||||||
return json == null ? ResponseEntity.notFound().build() : ResponseEntity.ok(serverRewriter.toYaml(json));
|
return json == null ? ResponseEntity.notFound().build() : ResponseEntity.ok(serverRewriter.toYaml(json));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** default 토큰 spec 또는 저장 spec(auth enrich + 서버 sentinel 치환)을 JSON 으로 반환. 없으면 null. */
|
/** Swagger UI 전용 spec — 서버 주소를 responseType(sample/mock/gw) 설정에 따라 치환. */
|
||||||
private String buildSpecJson(String id, HttpServletRequest request) throws IOException {
|
@GetMapping(value = "/{id}/swagger-ui.json", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||||
|
public ResponseEntity<String> swaggerForUi(@PathVariable String id, HttpServletRequest request) throws IOException {
|
||||||
|
String json = buildSpecJson(id, request, false);
|
||||||
|
return json == null ? ResponseEntity.notFound().build() : ResponseEntity.ok(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* default 토큰 spec 또는 저장 spec(auth enrich + 서버 sentinel 치환)을 JSON 으로 반환. 없으면 null.
|
||||||
|
* @param alwaysGateway true 면 항상 GW 주소 치환(다운로드용), false 면 responseType 설정 기반(UI용)
|
||||||
|
*/
|
||||||
|
private String buildSpecJson(String id, HttpServletRequest request, boolean alwaysGateway) throws IOException {
|
||||||
if (DEFAULT_TOKEN_API_ID.equals(id)) {
|
if (DEFAULT_TOKEN_API_ID.equals(id)) {
|
||||||
Resource resource = new ClassPathResource(DEFAULT_SPEC_PATH);
|
Resource resource = new ClassPathResource(DEFAULT_SPEC_PATH);
|
||||||
String content = new String(FileCopyUtils.copyToByteArray(resource.getInputStream()), StandardCharsets.UTF_8);
|
String content = new String(FileCopyUtils.copyToByteArray(resource.getInputStream()), StandardCharsets.UTF_8);
|
||||||
return serverRewriter.rewriteServer(content, null, request);
|
return alwaysGateway
|
||||||
|
? serverRewriter.rewriteServerToGateway(content, request)
|
||||||
|
: serverRewriter.rewriteServer(content, null, request);
|
||||||
}
|
}
|
||||||
|
|
||||||
Optional<ApiSpecInfo> spec = apiSpecInfoService.findById(id);
|
Optional<ApiSpecInfo> spec = apiSpecInfoService.findById(id);
|
||||||
@@ -70,6 +85,8 @@ public class DjbTestbedSpecController {
|
|||||||
|
|
||||||
DjbAuthType authType = authService.resolveAuthType(id);
|
DjbAuthType authType = authService.resolveAuthType(id);
|
||||||
String enriched = enricher.enrich(spec.get().getTestbedSpec(), authType);
|
String enriched = enricher.enrich(spec.get().getTestbedSpec(), authType);
|
||||||
return serverRewriter.rewriteServer(enriched, spec.get(), request);
|
return alwaysGateway
|
||||||
|
? serverRewriter.rewriteServerToGateway(enriched, request)
|
||||||
|
: serverRewriter.rewriteServer(enriched, spec.get(), request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+16
@@ -50,6 +50,22 @@ public class DjbTestbedSpecServerRewriter {
|
|||||||
return specJson.replace(SERVER_SENTINEL, base);
|
return specJson.replace(SERVER_SENTINEL, base);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* sentinel → GW 주소({@link DjbTestbedGatewayProperty#resolveApiBaseUrl}) 치환한 spec JSON 반환.
|
||||||
|
* responseType 을 무시하고 항상 GW 기준으로 치환한다 — 외부 공개/다운로드용 spec(swagger.json/yaml)
|
||||||
|
* 단일 기준. (Swagger UI 표시용은 {@link #rewriteServer} 의 responseType 분기를 그대로 사용.)
|
||||||
|
*/
|
||||||
|
public String rewriteServerToGateway(String specJson, HttpServletRequest request) {
|
||||||
|
if (specJson == null || !specJson.contains(SERVER_SENTINEL)) {
|
||||||
|
return specJson;
|
||||||
|
}
|
||||||
|
String base = stripTrailingSlash(gatewayProperty.resolveApiBaseUrl(originOf(request)));
|
||||||
|
if (!StringUtils.hasText(base)) {
|
||||||
|
return specJson; // 실주소 미확정 시 sentinel 유지
|
||||||
|
}
|
||||||
|
return specJson.replace(SERVER_SENTINEL, base);
|
||||||
|
}
|
||||||
|
|
||||||
/** spec JSON → YAML 문자열. 변환 실패 시 JSON 원본 반환. */
|
/** spec JSON → YAML 문자열. 변환 실패 시 JSON 원본 반환. */
|
||||||
public String toYaml(String specJson) {
|
public String toYaml(String specJson) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -56,6 +56,19 @@
|
|||||||
</encoder>
|
</encoder>
|
||||||
</appender>
|
</appender>
|
||||||
|
|
||||||
|
<!-- API 테스트베드(/api/call-api) 감사 로그: 요청지/헤더/마스킹된 본문 기록, 1년(365일) 보관 -->
|
||||||
|
<appender name="API_TESTER_AUDIT" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
|
<file>${LOG_PATH}/apitester-audit.log</file>
|
||||||
|
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||||
|
<fileNamePattern>${LOG_PATH}/backup/apitester-audit.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
|
||||||
|
<maxFileSize>200MB</maxFileSize>
|
||||||
|
<maxHistory>365</maxHistory>
|
||||||
|
</rollingPolicy>
|
||||||
|
<encoder>
|
||||||
|
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %msg%n</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
||||||
<level>${CONSOLE_EFFECTIVE_LEVEL}</level>
|
<level>${CONSOLE_EFFECTIVE_LEVEL}</level>
|
||||||
@@ -68,6 +81,10 @@
|
|||||||
<appender-ref ref="HTTP_SESSION" />
|
<appender-ref ref="HTTP_SESSION" />
|
||||||
</logger>
|
</logger>
|
||||||
|
|
||||||
|
<logger name="eapim.portal.apitester.audit" level="INFO" additivity="false">
|
||||||
|
<appender-ref ref="API_TESTER_AUDIT" />
|
||||||
|
</logger>
|
||||||
|
|
||||||
|
|
||||||
<root level="INFO">
|
<root level="INFO">
|
||||||
<appender-ref ref="ROLLING"/>
|
<appender-ref ref="ROLLING"/>
|
||||||
|
|||||||
@@ -305,7 +305,9 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const swaggerUrl = `/djb/testbed/apis/${apiId}/swagger.json`;
|
// UI 전용 spec: 서버 주소가 responseType(sample/mock/gw) 설정에 따라 치환됨.
|
||||||
|
// (swagger.json 은 외부 다운로드용으로 항상 GW 주소 고정)
|
||||||
|
const swaggerUrl = `/djb/testbed/apis/${apiId}/swagger-ui.json`;
|
||||||
|
|
||||||
// Load Swagger UI scripts dynamically
|
// Load Swagger UI scripts dynamically
|
||||||
const loadScript = (src) => {
|
const loadScript = (src) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user