[Javascript]

// https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.2/rollups/aes.js
// https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.2/rollups/pbkdf2.js

var passphrase = "key...변경";
var iv = CryptoJS.lib.WordArray.random(128/8);
var key = CryptoJS.enc.Hex.parse(CryptoJS.SHA1(passphrase).toString().substring(0,32));
console.log("key: ", key)
var ct = CryptoJS.AES.encrypt(str, key, { iv: iv });
var enc = iv.concat(ct.ciphertext).toString();

console.log("enc: ", enc)
## enc:  b3ff8dd004bb643f0aba857baccb0d45e1565d2f4a1d727c9a268580d3a2031b

console.log("Result: " + CryptoJS.AES.decrypt({
        ciphertext: CryptoJS.enc.Hex.parse(enc.substring(32))
    }, CryptoJS.enc.Hex.parse(CryptoJS.SHA1(passphrase).toString().substring(0,32)),
    {
        iv: CryptoJS.enc.Hex.parse(enc.substring(0,32)),
    }).toString(CryptoJS.enc.Utf8));
    
## Result: abcd

enc = "B8160A9EDCA2CFECE3E6444BFDE09B780D8DD2D873B93080E7F5A6F4B5644217";
console.log("JAVA 에서 암호화한 문자열 복호화 Result: " + CryptoJS.AES.decrypt({
        ciphertext: CryptoJS.enc.Hex.parse(enc.substring(32))
    }, CryptoJS.enc.Hex.parse(CryptoJS.SHA1(passphrase).toString().substring(0,32)),
    {
        iv: CryptoJS.enc.Hex.parse(enc.substring(0,32)),
    }).toString(CryptoJS.enc.Utf8));
    
## JAVA 에서 암호화한 문자열 복호화 Result: abcd

[Java]

// com.google.guava:guava:29.0-jre


import com.google.common.base.Charsets;
import com.google.common.hash.Hashing;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import java.security.AlgorithmParameters;
import java.util.Arrays;

public class AesCryptUtil {
    private static Logger logger = LoggerFactory.getLogger(AesCryptUtil.class);

    public static String encryptAES(String data, String secretKey) {
        try {
            byte[] secretKeys = Arrays.copyOfRange(Hashing.sha1().hashString(secretKey, Charsets.UTF_8).asBytes(), 0, 16);
            SecretKey secret = new SecretKeySpec(secretKeys, "AES");

            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            cipher.init(Cipher.ENCRYPT_MODE, secret);

            AlgorithmParameters params = cipher.getParameters();

            byte[] iv = params.getParameterSpec(IvParameterSpec.class).getIV();
            byte[] cipherText = cipher.doFinal(data.getBytes(Charsets.UTF_8));

            return DatatypeConverter.printHexBinary(iv) + DatatypeConverter.printHexBinary(cipherText);
        } catch (Exception e) {
            logger.error("===== exception", e);
            throw new RuntimeException(e);
        }
    }

    public static String decryptAES(String data, String secretKey) {
        try {
            byte[] secretKeys = Arrays.copyOfRange(Hashing.sha1().hashString(secretKey, Charsets.UTF_8).asBytes(), 0, 16);

            String hexedIv = data.substring(0, 32);

            String hexedCipherText = data.substring(32);

            byte[] iv = DatatypeConverter.parseHexBinary(hexedIv);
            byte[] cipherText = DatatypeConverter.parseHexBinary(hexedCipherText);

            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");

            cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(secretKeys, "AES"), new IvParameterSpec(iv));

            return new String(cipher.doFinal(cipherText), Charsets.UTF_8);
        }catch (Exception e) {
            logger.error("===== exception", e);
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) throws Exception {
        String key = "key...변경";


        String abcd = AesCryptUtil.encryptAES("abcd", key);
        System.out.println("enc: " + abcd);
        System.out.println("dec: " + AesCryptUtil.decryptAES(abcd, key));
        System.out.println("javascript 암호화한 문자열 복호화 dec: " + AesCryptUtil.decryptAES("b3ff8dd004bb643f0aba857baccb0d45e1565d2f4a1d727c9a268580d3a2031b", key));
    }
}

[참고] https://stackoverflow.com/questions/37368710/decrypt-aes-cbc-pkcs5padding-with-cryptojs

 

Decrypt AES/CBC/PKCS5Padding with CryptoJS

I generate 128bit AES/CBC/PKCS5Padding key using Java javax.crypto API. Here is the algorithm that I use: public static String encryptAES(String data, String secretKey) { try { byte[]

stackoverflow.com

 

 

방법

  1. HttpClient 등 모듈에서 유효한 인증서를 체크하는 로직을 예외 시키는 방법
  2. certificate 인증서를 저장하여 호출하는 서버의 keystore 에 등록해주는 방법

방법 1에 대한 방법

            ## HttpClient 4.x 
            
            SSLContextBuilder builder = new SSLContextBuilder();
            builder.loadTrustMaterial(null, (certificate, authType) -> true);

            SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
                    builder.build(),
                    NoopHostnameVerifier.INSTANCE);

            httpClientBuilder.setSSLSocketFactory(sslsf);

방법 2에 대한 방법

  1. 크롬에서 아래와 같이 test1.cer 파일로 저장 (이름 무관)
  2. keytool -import -alias test1 -keystore $JAVA_HOME/lib/security/cacerts -file test1.cer -storepass changeit (alias 기존과 겹치지 않는 적당한 명칭, pass는 적당하게)
    1. 해당 파일만 가져와서 하드코딩으로 지정 할때
      System.setProperty("javax.net.ssl.trustStore", "$JAVA_HOME/lib/security/cacerts");

2024현재

 

과거

 

javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
	at sun.security.ssl.Alerts.getSSLException(Alerts.java:192)
	at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1946)
	at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:316)
	at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:310)
	at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1639)
	at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:223)
	at sun.security.ssl.Handshaker.processLoop(Handshaker.java:1037)
	at sun.security.ssl.Handshaker.process_record(Handshaker.java:965)
	at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1064)
	at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1367)
	at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1395)
	at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1379)
	at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:559)
	at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:185)
	at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1564)
	at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1492)
	at java.net.HttpURLConnection.getResponseCode(HttpURLConnection.java:480)
	at sun.net.www.protocol.https.HttpsURLConnectionImpl.getResponseCode(HttpsURLConnectionImpl.java:347)
	at UrlConTest.main(UrlConTest.java:15)
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
	at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:397)
	at sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:302)
	at sun.security.validator.Validator.validate(Validator.java:262)
	at sun.security.ssl.X509TrustManagerImpl.validate(X509TrustManagerImpl.java:324)
	at sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:229)
	at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:124)
	at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1621)
	... 14 more
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
	at sun.security.provider.certpath.SunCertPathBuilder.build(SunCertPathBuilder.java:141)
	at sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:126)
	at java.security.cert.CertPathBuilder.build(CertPathBuilder.java:280)
	at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:392)
	... 20 more

 

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.stream.Stream;

public class TextTest {
	public static void main(String[] args) {
		Path path1 = Paths.get("test.log");
		Path path2 = Paths.get("test.log-2");

		try (Stream stream = Files.lines(path1)) {
			Files.write(path2, (Iterable)stream.filter(s->s.trim().startsWith("{")).filter(s->s.trim().endsWith("}"))::iterator);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}


json-lib, com.fasterxml.jackson 둘의 차이점을 비교해본다.

별다른 옵션 없는 심플한 상황이다.

json-lib의 경우 Object형의 NULL 변환에 문제가 있어보인다.


[JsonTest .java]

import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;

import java.io.IOException;

import org.junit.Test;

import com.fasterxml.jackson.databind.ObjectMapper;

import net.sf.json.JSONObject;
import net.sf.json.JSONSerializer;

public class JsonTest {
	@Test
	public void test_jsonlib_BeanCase() {
		try {
			TestBean inBean = new TestBean();
			inBean.setA("a Value");
			inBean.setB("");
			inBean.setC(null);
			//inBean.setD();
			
			inBean.setInt11(1);
			//inBean.setInt12();
			inBean.setInt21(1);
			inBean.setInt21(null);
			
			inBean.setBool11(true);
			//inBean.setBool12();
			inBean.setBool21(true);
			inBean.setBool21(null);
			
			JSONObject jsonObj = JSONObject.fromObject( JSONSerializer.toJSON(inBean) );
			String jsonStr = jsonObj.toString();
			System.out.println("jsonStr: " + jsonStr);
			
			jsonObj = (JSONObject) JSONObject.fromObject(jsonStr);
			TestBean outBean = (TestBean) JSONObject.toBean(jsonObj, TestBean.class);
			System.out.println(outBean);
			
			assertEquals(inBean.getA(), outBean.getA());
			assertEquals(inBean.getB(), outBean.getB());
			//assertEquals(inBean.getC(), outBean.getC());				// 문제 소지
			//assertEquals(inBean.getD(), outBean.getD());				// 문제 소지
			
			assertEquals(outBean.getC(), "");
			assertEquals(outBean.getD(), "");
			
			assertEquals(inBean.getInt11(), outBean.getInt11());
			assertEquals(inBean.getInt12(), outBean.getInt12());
			//assertEquals(inBean.getInt21(), outBean.getInt21());		// 문제 소지(아예 변환 안됨)
			//assertEquals(inBean.getInt22(), outBean.getInt22());		// 문제 소지(아예 변환 안됨)
			assertEquals(outBean.getInt21(), new Integer(0));
			assertEquals(outBean.getInt22(), new Integer(0));
			
			assertEquals(inBean.isBool11(), outBean.isBool11());
			assertEquals(inBean.isBool12(), outBean.isBool12());
			//assertEquals(inBean.getBool21(), outBean.getBool21());	// 문제 소지(아예 변환 안됨)
			//assertEquals(inBean.getBool22(), outBean.getBool22());	// 문제 소지(아예 변환 안됨)
			assertEquals(outBean.getBool21(), new Boolean(false));
			assertEquals(outBean.getBool22(), new Boolean(false));
		} catch (Exception e) {
			System.err.println("-- e: " + e);
		}
		
	}
	
	@Test
	public void test_jackson_BeanCase() {
		try {
			ObjectMapper jacksonMapper = new ObjectMapper();
			
			TestBean inBean = new TestBean();
			inBean.setA("a Value");
			inBean.setB("");
			inBean.setC(null);
			//inBean.setD();
			
			inBean.setInt11(1);
			//inBean.setInt12();
			inBean.setInt21(1);
			inBean.setInt21(null);
			
			inBean.setBool11(true);
			//inBean.setBool12();
			inBean.setBool21(true);
			inBean.setBool21(null);
			System.out.println(inBean);
			
			String jsonStr = jacksonMapper.writeValueAsString(inBean);
			System.out.println("jsonStr: " + jsonStr);
			TestBean outBean = jacksonMapper.readValue(jsonStr, TestBean.class);
			
			assertEquals(inBean.getA(), outBean.getA());
			assertEquals(inBean.getB(), outBean.getB());
			assertEquals(inBean.getC(), outBean.getC());
			assertEquals(inBean.getD(), outBean.getD());
			assertEquals(outBean.getC(), null);
			assertEquals(outBean.getD(), null);
			
			assertEquals(inBean.getInt11(), outBean.getInt11());
			assertEquals(inBean.getInt12(), outBean.getInt12());
			assertEquals(inBean.getInt21(), outBean.getInt21());
			assertEquals(inBean.getInt22(), outBean.getInt22());
			
			assertEquals(inBean.isBool11(), outBean.isBool11());
			assertEquals(inBean.isBool12(), outBean.isBool12());
			assertEquals(inBean.getBool21(), outBean.getBool21());
			assertEquals(inBean.getBool22(), outBean.getBool22());
		} catch (IOException e) {
			System.err.println("-- e: " + e);
		}
	}
}

[TestBean.java]

import lombok.Getter;
import lombok.Setter;

@Setter @Getter
public class TestBean {
	String a;
	String b;
	String c;
	String d;
	
	int int11;
	int int12;
	Integer int21;
	Integer int22;
	
	boolean bool11;
	boolean bool12;
	Boolean bool21;
	Boolean bool22;
}

[build.gradle]

sourceCompatibility='1.7'

dependencies {
    //compile group: 'commons-collections', name: 'commons-collections', version: '3.2'
    //compile 'org.apache.maven.plugins:maven-war-plugin:+'
    
    compile 'net.sf.json-lib:json-lib:2.4:jdk15@jar'
    compile 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.6.4'
    
    compile 'commons-lang:commons-lang:2.6'
    compile 'commons-logging:commons-logging:1.2'
    compile 'commons-collections:commons-collections:3.2.2'
    compile 'commons-beanutils:commons-beanutils:1.9.2'
    compile 'net.sf.ezmorph:ezmorph:1.0.6'
    
    testCompile 'junit:junit:4.11'
	testCompile 'org.hamcrest:hamcrest-all:1.3'
}


import org.apache.commons.lang.math.NumberUtils;
import org.apache.commons.lang.StringUtils;

// 자연수 + 공백
assertFalse(StringUtils.isNumeric(null));
assertTrue (StringUtils.isNumeric(""));
assertFalse(StringUtils.isNumeric("  "));
assertTrue (StringUtils.isNumeric("0"));
assertTrue (StringUtils.isNumeric("123"));
assertFalse(StringUtils.isNumeric("12 3"));
assertFalse(StringUtils.isNumeric("ab2c"));
assertFalse(StringUtils.isNumeric("12-3"));
assertFalse(StringUtils.isNumeric("12.3"));
assertFalse(StringUtils.isNumeric("-123"));
assertFalse(StringUtils.isNumeric("+123"));

// 자연수
assertFalse(NumberUtils.isDigits(null));
assertFalse(NumberUtils.isDigits(""));
assertFalse(NumberUtils.isDigits("  "));
assertTrue (NumberUtils.isDigits("0"));
assertTrue (NumberUtils.isDigits("123"));
assertFalse(NumberUtils.isDigits("12 3"));
assertFalse(NumberUtils.isDigits("ab2c"));
assertFalse(NumberUtils.isDigits("12-3"));
assertFalse(NumberUtils.isDigits("12.3"));
assertFalse(NumberUtils.isDigits("-123"));
assertFalse(NumberUtils.isDigits("+123"));

// 정수 + 실수 (+ 앞에 붙으면 안됨)
assertFalse(NumberUtils.isNumber(null));
assertFalse(NumberUtils.isNumber(""));
assertFalse(NumberUtils.isNumber("  "));
assertTrue (NumberUtils.isNumber("0"));
assertTrue (NumberUtils.isNumber("123"));
assertFalse(NumberUtils.isNumber("12 3"));
assertFalse(NumberUtils.isNumber("ab2c"));
assertFalse(NumberUtils.isNumber("12-3"));
assertTrue (NumberUtils.isNumber("12.3"));
assertTrue (NumberUtils.isNumber("-123"));
assertFalse(NumberUtils.isNumber("+123"));

Cluster 된 개별 WAS들이 모두 정상적으로 가동되고 있는지 모니터링 화면에서 표현해보자.


기준은 각 WAS의 Memory MBean 정보를 활용해서 WAS가 정상적인지도 체크 하고, 현재 Memory 상황은 어떤지 파악도 함께 할 수 있게 해본다.


[각각의 WAS Agent] -> vertx Net-> [모니터링 서버] -> vertx SockJs -> [Client Broswer] 방향으로 Memory 정보를 보낸다.



장애판단 기준은 WAS Agent에서 현재 5초 기준으로 모니터링 서버쪽으로 보내주고 있다.

그에 따라 Client Borswer 부분에서는 해당 정보가 (5초*1.5) 이후에도 정보가 오지 않으면 서버가 내려갔다고 판단한다.


또한 이 정보를 DB화 하여, Memory 증가량을 파악하고, GC 이후에도 Memory 가 부족하지는 않은지 판단해본다.






[MonitorAgent.java]

import java.lang.instrument.Instrumentation;
import java.util.Timer;

import pe.kr.ddakker.monitor.agent.timer.MBeanTimer;

public class MonitorAgent {
	Timer timer = new Timer("ddakker Agent Timer", true);
    Instrumentation instrumentation;
    static MonitorAgent monitorAgent;
    public static boolean isDebug = false;
	
	public static void premain(String args, Instrumentation instrumentation) throws Exception {
		
		//instrumentation.addTransformer(new TomcatTransformer());
		monitorAgent = new MonitorAgent(instrumentation);
		monitorAgent.start();
	}

	public final void start(boolean isRequest, boolean isMBean) {
		MBeanTimer mBeanTimer = new MBeanTimer();
        timer.scheduleAtFixedRate(mBeanTimer, 5000, 5000);
	}
}


[MBeanTimer.java]

import java.lang.management.ManagementFactory;
import java.util.TimerTask;

import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.management.openmbean.CompositeData;

import pe.kr.ddakker.monitor.agent.send.VertxClient;

public class MBeanTimer extends TimerTask {
	public void run() {
		try {
			ObjectName om = new ObjectName("java.lang:type=Memory");
			
			MBeanServer connection = ManagementFactory.getPlatformMBeanServer();
			
			
			Object attrValue = connection.getAttribute(om, "HeapMemoryUsage");
			
			long max = Long.parseLong(((CompositeData)attrValue).get("max").toString());
			long used = Long.parseLong(((CompositeData)attrValue).get("used").toString());
			long heapUsedPercent = Math.round((used*1.0 / max*1.0) * 100.0);
			
			String msg = "{name: '" + System.getProperty("jvmRoute") + "', heapUsedPercent: '" + heapUsedPercent + "', time: '" + System.currentTimeMillis() + "'}";
			msg = "{result: '0000', grp: 'grp_was', msg: '성공', data: " + msg + "}";
			
			VertxClient.send(msg);
		} catch (Exception e) {
			System.err.println();
		}

	}

}


slf4j + logback 환경에서 이니지스 결재 모듈(INIpay50.jar) 사용 시 이니시스 로깅에 문제가 발생한다.


이니시스측에서도 log4j 를 사용하라고 권고한 상황..


하지만 쓰고 싶다면...


'org.slf4j:log4j-over-slf4j' 부분을 exclude 하면 된다.



※ 'org.slf4j:log4j-over-slf4j' 는 기존 라이브러리가 log4j를 사용하고 있을때, slf4j로 넘겨주는 역할은 하는거란다.



아시는분이 Java Agent에 대한 정보를 주셔서 해당 기능을 Tomcat 요청에 대한 모니터링툴을 만들어보고 있다.


현재 겉모습은 제니퍼의 X-View 와 비슷하게 따라 하려는중이다.ㅎㅎ




1차적으로 요청 URI, 요청 시간, 종료 시간, 처리 시간, HttpStatus 값 정도만 차트에 표현하였다.


구조는 Tomat에 Agent를 띄운 후 Agent에서 모니터링툴 WAS에 WebSocket 로 보낸 후 데이터를 받은 모니터링툴에서 모니터링에 접속한 Client Browser 에 WebSocket로 데이터를 보내주는 방식이다.


모니터링툴에 붙은 Client가 많았을 경우 어떻게 될지 아직 잘 모르겠다.

상황봐서 Queue에 담아서 한다던가 고려해볼 생각이고 현재에는 비슷하게 구현만 하는게 목적이다.




인터넷의 자료를 통해서 javassist 를 활용하여 Byte Code Instrumentation 시도해보았지만, 일반적인 Java Main Program은 잘 되는데 Tomcat 의 특정 Class를 제어 하려고 했더니 잘 되지 않았다.

많은 삽질 끝에 ClassPool을 얻어 오는 부분을 조금 수정 하니 잘 되었다.

(이 부분때문에 몇일을 삽질 하다.. 포기도 할까 하고, Application Level Servlet Filter를 이용하는 방법으로 선회 할까도 했음...)


검색을 하두 해서 어디서 이 정보를 얻었는지 기억이 안나네요.



[해결 부분]

ClassLoader classLoader = Thread.currentThread().getContextClassLoader();

ClassPool pool = ClassPool.getDefault();

pool.insertClassPath(new LoaderClassPath(classLoader));


[2015. 7. 6.]

WAS Application 요청시 org.apache.catalina.core.StandardEngineValve.invoke(Request request, Response response) 부분을 잡아 처리 하였다.

(만들어 가며 바뀔 수도 있는 부분임..)

이렇게 함으로 인해서 어떠한 문제가 발생할지는 미지수임...

HttpStatus=302 케이스에서 문제 발생


[2015. 8. 10.]

javax.servlet.http.HttpServlet.service(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse  으로 변경

이러면.. JSP Direct 호출이 안 잡히는군.. 쉽지 않구만...


다시 org.apache.catalina.core.StandardEngineValve.invoke(Request request, Response response) 원복!!

Spring Security LOGOUT HttpStatus 302일때문 문제가 발생!!

문제 발생 부분 request.getSession().getId()

이 부분을 Before 일때만 하고, After 일때는 주석


Before, After 요청 정보 맵핑을 Thread.currentThread().getId() 로 처리


우선 잘 된다...


[MonitorAgent.java]

package pe.kr.ddakker.monitor.agent;

import java.lang.instrument.Instrumentation;

import pe.kr.ddakker.monitor.agent.transformer.TomcatTransformer;

public class MonitorAgent {
	
	public static void premain(String args, Instrumentation inst) throws Exception {
		inst.addTransformer(new TomcatTransformer());
	}

	public static void agentmain(String args, Instrumentation inst) throws Exception {
		premain(args, inst);
	}
}


[TomcatTransformer.java]

package pe.kr.ddakker.monitor.agent.transformer;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtBehavior;
import javassist.CtClass;
import javassist.LoaderClassPath;
import javassist.NotFoundException;

/**
 * Tomcat 요청 가로채서 수정!!
 * @author ddakker 2015. 6. 14.
 */
public class TomcatTransformer implements ClassFileTransformer {
	ClassPool pool = null;
	public TomcatTransformer() {
		this.pool = ClassPool.getDefault();
	}
	
	public byte[] transform(ClassLoader loader, String className, Class redefiningClass, ProtectionDomain domain,
			byte[] bytes) throws IllegalClassFormatException {
		if (className.contains("StandardEngineValve")) {
			System.out.println("className: " + className);
			return transformClass(redefiningClass, bytes);
		} else {
			return bytes;
		}
	}

	private byte[] transformClass(Class classToTransform, byte[] b) {
		CtClass cl = null;
		try {
			ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
			
			ClassPool pool = ClassPool.getDefault();
			pool.insertClassPath(new LoaderClassPath(classLoader));
			if (pool != null) {
				cl = pool.makeClass(new java.io.ByteArrayInputStream(b));
				if (cl.isInterface() == false) {
					CtBehavior[] methods = cl.getDeclaredBehaviors();
					for (int i = 0; i < methods.length; i++) {
						System.out.println("methods[" + i + "]: " + methods[i]);
						if (methods[i].isEmpty() == false) {
							doTransform(methods[i]);
						}
					}
				}
				b = cl.toBytecode();
			}
		} catch (Exception e) {
			System.err.println("e111: " + e);
		} finally {
			if (cl != null) {
				cl.detach();
			}
		}
		return b;
	}
	
	private void doTransform(CtBehavior method) throws NotFoundException, CannotCompileException {
		if (method.getName().equals("invoke")) {
			
			System.out.println("0");
			try {
				System.out.println("1");
				method.insertBefore(""
						+ "String jvmRoute = System.getProperty(\"jvmRoute\");"
						+ "String sessionId = $1.getSession().getId();"
						+ "String uri = $1.getRequestURI();"
						+ "pe.kr.ddakker.monitor.websocket.WSClient.send(\"{"
						+ "server: '\" + jvmRoute + \"' "
						+ ", sessionId: '\" + sessionId + \"' "
						+ ", uri: '\" + uri + \"' "
						+ ", stTime: '\" + System.currentTimeMillis() + \"' "
						+ "}\");");
				System.out.println("2");
				method.insertAfter(""
						+ "String jvmRoute = System.getProperty(\"jvmRoute\");"
						+ "String sessionId = $1.getSession().getId();"
						+ "String uri = $1.getRequestURI();"
						+ "pe.kr.ddakker.monitor.websocket.WSClient.send(\"{"
						+ "server: '\" + jvmRoute + \"' "
						+ ", sessionId: '\" + sessionId + \"' "
						+ ", uri: '\" + uri + \"' "
						+ ", edTime: '\" + System.currentTimeMillis() + \"' "
						+ ", status: '\" + $2.getStatus() + \"' "
						+ "}\");");
				System.out.println("3");
			} catch (Exception e) {
				System.err.println("Aa e: " + e);
			}
			System.out.println("4");
		}
		
	}

}



[build.gradle]

jar {
	manifest {
		attributes 'Implementation-Title': 'Gradle Quickstart',
				   'Implementation-Version': version,
				   'Premain-Class': 'pe.kr.ddakker.monitor.agent.MonitorAgent',
				   'Agent-Class': 'pe.kr.ddakker.monitor.agent.MonitorAgent',
				   'Can-Redefine-Classes': true
	}
}


instrument 시 필요한 외부 Library들은 Agent jar에 포함 시켰다.


[실행]

-javaagent:D:\..\tomcat-monitor-agent-1.0.jar

최근 fastjson 이란 알리바바에서 공개한 JSON Parser 을 보게되어 현재 회사에서 사용중인 Library 들을 비교해 보았다.


jackson-databind-2.1.3.jar

json-lib-2.2.1-jdk15.jar

fastjson-1.2.5.jar


[테스트 상황]

Vo 안에 2개의 Vo가 있는 객체 활용하여, to JSON String, to JAVA Object 로 변환 및 역변환을 10,000 실행하였다.




결과는 위에 보여지는 바와 같습니다.
  - to JSON String = fastjson > jackson > json-lib

  - to Object = jackson > fastjson > json-lib

변환 및 역변환 수치 상으로는 알리바바의 fastjson와 jackson 두가지가 근소한 차이의 성능을 보였습니다.
하지만 알리바바의 fastjson의 경우 유효하지 않는 데이터의 경우 표현자체를 하지 않으므로 인해서 JSON String 길이가 작습니다.
따라서 네트워크 전송 비용까지 감안 한다면 알리바바의 fastjson 의 가장 우수 한것으로 판단됩니다.

※ JSON Str Size가 다른 것은 각 Libaray 마다 null 데이터 처리 규칙이 조금 달라서 그렇고, 유효한 데이터에서는 동일합니다.
    - jackjon = null -> 문자열 null 로 표시
    - json-lib = null -> 문자열 공백으로 표시
    - fastjson = null -> key/value 모두 표시하지 않음


서버 모니터링 시스템 구축 중 Tomcat Comet으로 했던걸 WebSocket로 변경 하였다.

단순함!


WebSocket Servlet 에서는 Client Open/Close 만 관리하는 public static Set 객체에 Client 정보를 관리하고, Queue Receive 에서 해당 Set 객체에 접근 하여 각각의 Client 에 데이터를 Push 한다.


Thread 간 통신에 public static 변수를 사용하는게 좋은건지 모르겠으나.. Thread 관련 지식이 부족한 관계로 현재는 우선 패스한다.



import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;

import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


@ServerEndpoint(value = "/ws/was")
public class WasWebSocket {

	private static Logger log = LoggerFactory.getLogger(WasWebSocket.class);
	

    public static Set connections = new CopyOnWriteArraySet<>();

    private Session session;
    
    public WasWebSocket() {
        log.info("----------- thread start");
	}

    public Session getSession() {
    	return this.session;
    }

    @OnOpen
    public void onOpen(Session session) {
    	log.info("onOpen this: {}, this.sessson: {}", this.getClass(), session);
        this.session = session;
        synchronized (connections){
        	connections.add(this);
        }
    }


    @OnClose
    public void onClose() {
    	log.info("onClose");
    	synchronized (connections){
    		connections.remove(this);
    	}
    }


    @OnMessage
    public void onMessage(String message) {
    	log.info("@OnMessage");
    	log.info("websocket message: " + message);
    }



    @OnError
    public void onError(Throwable t) throws Throwable {
        log.error("e: ", t.toString());
        synchronized (connections){
    		connections.remove(this);
    	}
    }
}



function wasStatusListen() {
	var oSocket = new WebSocket(WS_URL + "/ws/was");
	 
    oSocket.onmessage = function (e) { 
        var json = eval("(" + e.data + ")");
		if (json.result && json.result == "0000") {
			var wasNode = json.data.name;
			var heapUsedPercent = json.data.heapUsedPercent;
			..
			..
			처리
			
		}
    };
 
    oSocket.onopen = function (e) {
        console.log("open");
    };
 
    oSocket.onclose = function (e) {
        console.log("close");
        wasStatusListen();
    };
    
    window.unload = function() {
		if (oSocket.readyState != 3)
			oSocket.disconnect();
	}
}
wasStatusListen();

+ Recent posts