ReflectHeaderFilter 필터 추가
This commit is contained in:
@@ -0,0 +1,263 @@
|
||||
package com.eactive.eai.adapter.http.dynamic.filter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import com.eactive.eai.common.property.PropManager;
|
||||
import com.eactive.eai.common.util.Logger;
|
||||
|
||||
/**
|
||||
* 수신 Http Header 중 <b>허용 목록(white list)에 등록된 헤더만</b> 응답 헤더로 복사하는 Inbound Adapter Filter.
|
||||
*
|
||||
* <p>모든 헤더를 복사하고 제외 목록으로 걸러내는 {@link ReflectAllHeaderFilter} 와 달리,
|
||||
* 복사 대상을 어댑터 그룹별로 명시하는 방식이다.
|
||||
*
|
||||
* <p>설정은 <code>HttpHeaderFilter</code> property 그룹에 등록하며, 아래 순서로 가장 먼저 찾은 키 하나만 사용한다.
|
||||
* (합집합이 아니라 override 이므로 어댑터 그룹 키를 지정하면 전역 키는 무시된다.)
|
||||
*
|
||||
* <pre>
|
||||
* 1. ReflectHeaderFilter.whiteList.{어댑터그룹명} 어댑터 그룹 단위
|
||||
* 2. ReflectHeaderFilter.whiteList 전역 기본값
|
||||
* </pre>
|
||||
*
|
||||
* <p>값은 콤마로 구분한 헤더명 목록이며 대소문자를 구분하지 않는다.
|
||||
* 헤더명 끝에 <code>*</code> 를 붙이면 접두사 일치로 처리한다.
|
||||
*
|
||||
* <pre>
|
||||
* HttpHeaderFilter.ReflectHeaderFilter.whiteList = x-elink-client-id
|
||||
* HttpHeaderFilter.ReflectHeaderFilter.whiteList.djbTrans = x-obp-txid, x-obp-partnercode, X-KKB-*
|
||||
* </pre>
|
||||
*
|
||||
* <p>설정이 없거나 비어 있으면 어떤 헤더도 복사하지 않는다.
|
||||
*/
|
||||
public class ReflectHeaderFilter implements HttpAdapterFilter {
|
||||
|
||||
protected static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
|
||||
|
||||
public static final String PROPERTIES_GROUP_NAME = "HttpHeaderFilter";
|
||||
public static final String HEADER_KEY_NAMES = "ReflectHeaderFilter.whiteList";
|
||||
|
||||
/**
|
||||
* 허용 목록에 등록되어 있어도 복사하지 않는 헤더.
|
||||
* 요청측 값이 응답 본문/커넥션과 불일치하면 응답 자체가 깨지므로 설정으로 열 수 없게 한다.
|
||||
*/
|
||||
private static final Set<String> NEVER_REFLECT = createHeaderSet(
|
||||
"Content-Length",
|
||||
"Transfer-Encoding",
|
||||
"Connection",
|
||||
"Keep-Alive",
|
||||
"Upgrade",
|
||||
"TE",
|
||||
"Trailer");
|
||||
|
||||
/** 어댑터 그룹별 허용 목록 캐시. 필터 인스턴스는 HttpAdapterFilterFactory 에서 싱글톤으로 공유된다. */
|
||||
private final ConcurrentHashMap<String, CachedWhiteList> whiteListCache = new ConcurrentHashMap<String, CachedWhiteList>();
|
||||
|
||||
public ReflectHeaderFilter() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object doPreFilter(String adptGrpName, String adptName, Object message, Properties prop,
|
||||
HttpServletRequest request, HttpServletResponse response) throws Exception {
|
||||
logger.debug("doPreFilter ReflectHeaderFilter Start.");
|
||||
|
||||
reflectHeaders(adptGrpName, request, response);
|
||||
|
||||
logger.debug("doPreFilter ReflectHeaderFilter End.");
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object doPostFilter(String adptGrpName, String adptName, Object resultMessage, Properties prop,
|
||||
HttpServletRequest request, HttpServletResponse response) throws Exception {
|
||||
logger.debug("doPostFilter ReflectHeaderFilter Start.");
|
||||
|
||||
reflectHeaders(adptGrpName, request, response);
|
||||
|
||||
logger.debug("doPostFilter ReflectHeaderFilter End.");
|
||||
|
||||
return resultMessage;
|
||||
}
|
||||
|
||||
private void reflectHeaders(String adptGrpName, HttpServletRequest request, HttpServletResponse response) {
|
||||
|
||||
WhiteList whiteList = getWhiteList(adptGrpName);
|
||||
|
||||
if (whiteList.isEmpty()) {
|
||||
logger.debug("No reflect header configured for adapter group [" + adptGrpName + "]. Skip all.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
for (String headerName : whiteList.getNames()) {
|
||||
|
||||
String headerValue = request.getHeader(headerName);
|
||||
if (headerValue == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
setHeader(headerName, headerValue, response);
|
||||
}
|
||||
|
||||
// 접두사(*) 설정이 있을 때만 수신 헤더를 순회한다.
|
||||
if (whiteList.hasPrefix()) {
|
||||
java.util.Enumeration<String> headerNames = request.getHeaderNames();
|
||||
while (headerNames != null && headerNames.hasMoreElements()) {
|
||||
|
||||
String headerName = headerNames.nextElement();
|
||||
if (!whiteList.matchesPrefix(headerName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
setHeader(headerName, request.getHeader(headerName), response);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void setHeader(String headerName, String headerValue, HttpServletResponse response) {
|
||||
|
||||
if (NEVER_REFLECT.contains(headerName)) {
|
||||
logger.debug("Skip Processing Key [" + headerName + "] in NEVER_REFLECT list");
|
||||
return;
|
||||
}
|
||||
|
||||
// 헤더명/값에 CR, LF 가 있으면 응답 분할(response splitting) 위험이 있다.
|
||||
if (containsCrLf(headerName) || containsCrLf(headerValue)) {
|
||||
logger.error("Skip Processing Key [" + headerName + "] - CR/LF detected in header name or value");
|
||||
return;
|
||||
}
|
||||
|
||||
response.setHeader(headerName, headerValue);
|
||||
logger.debug("Processing Key [" + headerName + "], value [" + headerValue + "]");
|
||||
}
|
||||
|
||||
private boolean containsCrLf(String value) {
|
||||
return value != null && (value.indexOf('\r') >= 0 || value.indexOf('\n') >= 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 어댑터 그룹 → 전역 순으로 property 를 찾아 허용 목록을 반환한다.
|
||||
* property 값이 바뀌지 않는 동안은 파싱 결과를 재사용한다.
|
||||
*/
|
||||
private WhiteList getWhiteList(String adptGrpName) {
|
||||
|
||||
String cacheKey = StringUtils.defaultString(adptGrpName);
|
||||
String propValue = findPropValue(adptGrpName);
|
||||
|
||||
CachedWhiteList cached = whiteListCache.get(cacheKey);
|
||||
if (cached != null && StringUtils.equals(cached.propValue, propValue)) {
|
||||
return cached.whiteList;
|
||||
}
|
||||
|
||||
WhiteList whiteList = parse(propValue);
|
||||
whiteListCache.put(cacheKey, new CachedWhiteList(propValue, whiteList));
|
||||
return whiteList;
|
||||
}
|
||||
|
||||
private String findPropValue(String adptGrpName) {
|
||||
|
||||
PropManager propManager = PropManager.getInstance();
|
||||
|
||||
if (StringUtils.isNotBlank(adptGrpName)) {
|
||||
String value = propManager.getProperty(PROPERTIES_GROUP_NAME, HEADER_KEY_NAMES + "." + adptGrpName, "");
|
||||
if (StringUtils.isNotBlank(value)) {
|
||||
return StringUtils.trimToEmpty(value);
|
||||
}
|
||||
}
|
||||
|
||||
return StringUtils.trimToEmpty(propManager.getProperty(PROPERTIES_GROUP_NAME, HEADER_KEY_NAMES, ""));
|
||||
}
|
||||
|
||||
private static WhiteList parse(String propValue) {
|
||||
|
||||
Set<String> names = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
|
||||
List<String> prefixes = new ArrayList<String>();
|
||||
|
||||
if (StringUtils.isNotBlank(propValue)) {
|
||||
for (String token : propValue.split(",")) {
|
||||
String name = StringUtils.trimToEmpty(token);
|
||||
if (name.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (name.endsWith("*")) {
|
||||
String prefix = name.substring(0, name.length() - 1);
|
||||
if (!prefix.isEmpty()) {
|
||||
prefixes.add(prefix);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
names.add(name);
|
||||
}
|
||||
}
|
||||
|
||||
return new WhiteList(names, prefixes);
|
||||
}
|
||||
|
||||
private static Set<String> createHeaderSet(String... names) {
|
||||
Set<String> set = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
|
||||
Collections.addAll(set, names);
|
||||
return Collections.unmodifiableSet(set);
|
||||
}
|
||||
|
||||
/** 파싱된 허용 목록. 헤더명 완전일치 목록과 접두사(*) 목록으로 구성된다. */
|
||||
private static class WhiteList {
|
||||
|
||||
private final Set<String> names;
|
||||
private final List<String> prefixes;
|
||||
|
||||
WhiteList(Set<String> names, List<String> prefixes) {
|
||||
this.names = Collections.unmodifiableSet(names);
|
||||
this.prefixes = Collections.unmodifiableList(prefixes);
|
||||
}
|
||||
|
||||
boolean isEmpty() {
|
||||
return names.isEmpty() && prefixes.isEmpty();
|
||||
}
|
||||
|
||||
Set<String> getNames() {
|
||||
return names;
|
||||
}
|
||||
|
||||
boolean hasPrefix() {
|
||||
return !prefixes.isEmpty();
|
||||
}
|
||||
|
||||
boolean matchesPrefix(String headerName) {
|
||||
for (String prefix : prefixes) {
|
||||
if (StringUtils.startsWithIgnoreCase(headerName, prefix)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static class CachedWhiteList {
|
||||
|
||||
private final String propValue;
|
||||
private final WhiteList whiteList;
|
||||
|
||||
CachedWhiteList(String propValue, WhiteList whiteList) {
|
||||
this.propValue = propValue;
|
||||
this.whiteList = whiteList;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user