[Prefilter - order 1]

→ Beast를 거치는 API와 System에 대한 정보로부터 요청 정보가 어디로 가는지에 대한 정보를 추출하는 역할. (Transaction 객체에 넣어주기 위함)

package com.ktds.act.apigw.filter;

import java.net.InetAddress;
import java.util.HashMap;
import java.util.UUID;

import com.ktds.act.apigw.handler.HandlerFactory;
import com.ktds.act.apigw.license.LicenseCheck;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.factory.rewrite.ModifyRequestBodyGatewayFilterFactory;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;

import com.ktds.act.apigw.api.db.entity.AdmApiDply;
import com.ktds.act.apigw.filter.function.GetExchangeRequest;
import com.ktds.act.apigw.filter.function.RequestMethod;
import com.ktds.act.apigw.filter.model.ComnonDto;
import com.ktds.act.apigw.filter.model.RequestDto;
import com.ktds.act.apigw.filter.model.StoreDto;
import com.ktds.act.apigw.filter.model.TransactionDto;
import com.ktds.act.apigw.system.SystemFactory;
import com.ktds.act.apigw.system.db.entity.AdmSys;
import com.ktds.act.apigw.system.db.entity.Edpt;

import lombok.extern.slf4j.Slf4j;
import reactor.core.publisher.Mono;

@Component
@Slf4j
public class PreFilter implements GlobalFilter, Ordered, InitializingBean{
	@Value("${spring.application.name}")
	private String appName;

	private static String hostNm = null; 
	private static String hostIp = null; 

	@Autowired
	private ModifyRequestBodyGatewayFilterFactory modifyRequestBodyFilter;
	@Autowired
	private GetExchangeRequest getExchangeRequest;

//	@Autowired(required = false)
//	private LicenseCheck licenseCheck;

	@Value("${common.license.necessary}")
	private boolean lChknecessary;

	@Value("${common.header.txid}")
	private String txidForHeadNm;

	@Value("${common.header.clientip}")
	private String clientIpForHeadNm;

	@Override
	public void afterPropertiesSet() throws Exception {
		hostNm = InetAddress.getLocalHost().getHostName(); **//현재 Beast가 띄워져 있는 host (Pc)이름 받아오기**
		hostIp = InetAddress.getLocalHost().getHostAddress(); **//현재 Beast가 띄워져 있는 IP 받아오기**
	}

	@Override
	public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
		long startTime = System.currentTimeMillis();

		
		// 요청 헤더의 주요정보 추출
		String txId = getHeaderF(exchange.getRequest().getHeaders(), txidForHeadNm); **//헤더 이름의 txId를 받아온다.**
		String clientIp = getHeaderF(exchange.getRequest().getHeaders(), clientIpForHeadNm); **//고객 IP를 받아온다.**

		if(!StringUtils.hasText(clientIp)){
			clientIp = exchange.getRequest().getRemoteAddress().getAddress().getHostAddress(); **//고객 IP가 없을때**
		}
		// TODO 실 Client IP 추출 로직으로 검증시 확인 필요
		// XForwardedRemoteAddressResolver resolver = XForwardedRemoteAddressResolver.maxTrustedIndex(1);
        // InetSocketAddress inetSocketAddress = resolver.resolve(exchange);
		// String clientIp = inetSocketAddress.getAddress().getHostAddress();

		// 트랜잭션 ID 추출 및 생성
		if(!StringUtils.hasText(txId)) {
			txId = UUID.randomUUID().toString(); **//헤더 이름이 없을때 UUID로 txId를 생성.**
		}
		
		// 쓰레드명을 Txid로 변경
		//Thread.currentThread().setName(txId);

		log.info("PreFilter.filter - Start");
		

		// 요청 정보 추출
		String reqMthd = exchange.getRequest().getMethod().name(); **//HTTP 요청 method를 받아와서 넣어준다.**
		String reqUri = exchange.getRequest().getURI().getPath(); **//요청 URI에서 Path를 받아와서 넣어준다.**

		//COMPLETED: SI api 식별 방식을 해당 방식과 동일하게 적용해도 되는지 확인 필요.

		// API 식별
		//AdmApiDply api = ApiFactory.getApi(reqMthd, reqUri);
		AdmApiDply api = null;
		if(exchange.getAttributes().containsKey("api")) {
			api = exchange.getAttribute("api"); **//exchange객체에 "api"가 있을때 (어떻게 생겼는지 확인)**
		}

		// System 식별
		AdmSys system = null;
		Edpt systemEdpt = null;
		String out = null;
		if(api!=null){
			system = SystemFactory.getSystem(api.getSysId()); **//SystemFactory에서 api에서 systemId얻어오는게 있나 봄.**
			if(system!=null){
				systemEdpt = system.getEdpt(); **//System의 Endpoint gateway를 거쳐 어느 IP:port로 가게 될지 넣어준다.**
			}
			out = api.getOut(); **//어느 서버로 연결시켜줄지는 endpoint에서 여기서는 URI의 path를 받아와서 넣어준다.**
		}
		
		// 트랜젝션 객체 생성 **(Beast Api-gateway의 기능에서 필요한 정보들을 함축해 놓은 객체)**
		TransactionDto transaction = TransactionDto.builder()
				.comn(ComnonDto.builder()
						.applId(appName)
						.hostNm(hostNm)
						.hostIp(hostIp)
						.txId(txId)
						.api(api!=null?AdmApiDply.copy(api):null) //오 copy 메소드가 존재했네?
						.build())
				.req(RequestDto.builder() **//Client의 요청에 따른 DTO**
						.stTm(startTime)
						.svcIp(clientIp)
						.meth(reqMthd)
						.in(reqUri)
						.out(out)
						.edpt(systemEdpt)
						.build())
				.store(StoreDto.builder() **//이건 뭐지?**
						.cstmLog(new HashMap<String, String>())
						.data(new HashMap<String, Object>())
						.build()
				)	
				.build();

		// 트랜젝션 객체 저장 **위의 정보들을 exchange객체에 태워서 필터를 순차적으로 태워야 하니까**
		exchange.getAttributes().put(TransactionDto.TRANSACTION, transaction);

		//license check
		// if(lChknecessary){
		// 	if(!licenseCheck.isLicensed()){
		// 		transaction.setError(AgwErrorDto.UNAUTHORIZED);
		// 		return Mono.error(new AuthenticationException(AgwErrorDto.UNAUTHORIZED.getErrMsg()));
		// 	}
		// }

		log.info("PreFilter.filter - End");

		// 헤더 추출
		RequestMethod.extractHeaderAndQueryParams(exchange, transaction);
		if(api!=null){
			if(HandlerFactory.isHandlerGroupBodyRef(api.getReqHndlr())) {
				// ModifyRequestBodyGatewayFilterFactory를 사용하여 requestbody추출 -> transaction객체 저장
				return modifyRequestBodyFilter
						.apply(
								new ModifyRequestBodyGatewayFilterFactory.Config()
										.setRewriteFunction(byte[].class, byte[].class, getExchangeRequest)) // extractRequestBody -> extractRequest : header, body, querystring
						.filter(exchange, chain);

			}
		}

		log.info("Transaction : " + transaction.toString());
		return  chain.filter(exchange);
	}

	@Override
	public int getOrder() {
		return 1;
	}

	// 헤더 값 추출
	private String getHeaderF(HttpHeaders header, String key){
		if(header.containsKey(key)) {
			return header.getFirst(key);
		}
		return "";
	}

}

블로킹 가능성이 있는 부분

  1. (ipNet)
	@Override
	public void afterPropertiesSet() throws Exception {
		hostNm = InetAddress.getLocalHost().getHostName(); **//현재 Beast가 띄워져 있는 host (Pc)이름 받아오기**
		hostIp = InetAddress.getLocalHost().getHostAddress(); **//현재 Beast가 띄워져 있는 IP 받아오기**
	}

<aside> 💬 PC의 이름이나 주소를 알기 위해서는 시스템 콜을 해야할 경우가 있다.

InetAddress.getLocalHost().getHostName() 메소드는 실행 환경에 따라서 시스템 콜을 호출할 수도 있습니다. 이 경우에는 시스템 콜이 블로킹될 수 있습니다.

예를 들어, 리눅스나 유닉스에서는 gethostname() 시스템 콜을 호출해서 호스트 이름을 가져옵니다. 이 시스템 콜은 호스트 이름을 결정하는 데 네트워크 연결이 필요하지 않기 때문에 보통 블로킹되지 않습니다.

하지만, 만약 시스템에서 DNS 조회를 사용해서 호스트 이름을 가져오도록 설정한 경우에는, DNS 조회의 응답이 지연될 수 있어서 블로킹될 가능성이 있습니다. 따라서, 이 경우에는 InetAddress.getLocalHost().getHostName() 메소드는 블로킹될 수 있습니다.

</aside>

  1. System.currentTimeMillis();
long startTime = System.currentTimeMillis(); //I/O가 발생하지만, 엄청 빠르게 전환 논블로킹처럼

<aside> 💬 System 콜 System.currentTimeMillis()는 운영체제의 gettimeofday() 시스템 콜을 호출하여 현재 시간을 얻어옵니다. 그러므로 이 메소드를 호출하는 것은 운영체제와의 상호작용을 필요로 하며, 이 과정에서 I/O 작업이 발생합니다. 하지만 이 작업은 매우 짧은 시간 동안만 수행되므로, 블로킹되지 않습니다. System.currentTimeMillis() 메소드는 시스템 호출을 통해 시스템의 현재 시간을 가져옵니다. 시스템 호출은 운영체제와 상호작용을 필요로 하며, 이 작업에서 I/O 작업이 발생합니다. 따라서 System.currentTimeMillis() 메소드를 호출하는 것은 I/O 작업이 발생한다고 볼 수 있습니다.

</aside>

참고 사이트1