Jun 27, 2018 - serialVersionUID 선언

@SuppressWarnings(“serial”) 선언한 class 에서 에러가 발생했습니다.

java.io.InvalidClassException: com.openrtb.api.dto.xxx; local class incompatible: stream classdesc serialVersionUID = -8253581617857313489, local class serialVersionUID = -5676156168200907805
	at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:616)
	at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1630)
	at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1521)
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1781)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1353)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:373)
	at java.util.ArrayList.readObject(ArrayList.java:791)
	at sun.reflect.GeneratedMethodAccessor77.invoke(Unknown Source)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:1058)
	at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1909)
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1808)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1353)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:373)

개발하면서 @SuppressWarnings(“serial”) 선언한 클래스에, 타개발자가 나중에 사용할 특정 변수를 추가하면서, JVM 에서 발급해주는 serialVersionUID가 변경되면서 발생한 문제.

해당 서비스는 혼자 관리하기 때문이라는 안일하게 SuppressWarnings을 걸어놓았는데… ㅠㅜ

class 가 내부 클래스에서 처리하면 문제가 없었지만, redis 를 사용하고 있었고, 해당 nosql 에 해당 클래스의 객체 정보를 바이너리화 하여 저장하고 있었기 때문에 조금의 실수라도 바로 문제가 발생할 수 밖에 없었던 겁니다!!!!!!

JVM에 자동으로 변경된 Default 시리얼 번호와, redis에 바이너리로 저장한 class의 시리얼 번호가 틀리면서 문제가 발생했습니다.

serialVersionUID 는 직렬화에 사용되는 고유 아이디인데, 다른 대부분의 redis 사용하는 객체클래스에는 적용해놓고, 이 녀석만 @SuppressWarnings(“serial”)을 선언해놓았더군요.

잠시 배포 중단을 할 수 밖에 없었습니다.

해당 클래스에 @SuppressWarnings(“serial”)을 선언할 때는, 해당 객체가 더이상 변경될 이유가 없고, 내가 관리하는 부분이니, 누군가 수정하지 않을 것이니 문제없겠지 하고 작업했던 걸로 기억합니다.

정말 안일했고, 권장하는 방법을 무시하면 안된다는 것을 다시 한번 깨닫습니다.

예전에 봤던 글의 결론을 첨부합니다.

결론

자바 직렬화는 장점이 많은 기술입니다만 단점도 많습니다.
문제는 이 기술의 단점은 보완하기 힘든 형태로 되어 있기 때문에 사용 시 제약이 많습니다.
그래서 이 글을 적는 저는 직렬화를 사용할 때에는 아래와 같은 규칙을 지키려고 합니다.

  1. 외부 저장소로 저장되는 데이터는 짧은 만료시간의 데이터를 제외하고 자바 직렬화를 사용을 지양합니다.
  2. 역직렬화시 반드시 예외가 생긴다는 것을 생각하고 개발합니다.
  3. 자주 변경되는 비즈니스적인 데이터를 자바 직렬화을 사용하지 않습니다.
  4. 긴 만료 시간을 가지는 데이터는 JSON 등 다른 포맷을 사용하여 저장합니다.

Jun 26, 2018 - ATOM 의 Github 설정 및 Git commit

Github blog를 만들 때, 지금 ATOM Editor을 사용 중이다.

기본적으로 Markdown preview 기능을 제공해주기 때문에 (ctrl + shift + m : 단축키) 미리보기 기능이 좋아, 사용 중이다.

github 연동이라든가, 브런치 및 여러가지 기능을 쓰려면 IntelliJ가 편하나, Github blog만 관리할 경우에는 Atom이 매우 편했다.

물론 사람마다 다 다르겠지만, 나의 경우는 IntellJ 나 비쥬얼 스튜디오 2017의 경우 프리뷰 기능을 제공은 해주나, Atom 처럼 거의 html 을 완벽하게 구현해주지는 못하는 느낌이 들어서 그렇다. (아마 Atom 자체가 html, js, javascript 가 지원되는 에디터이기 때문일 거 같다.)

인터넷에서 처음 Atom 사용시 git-clone 을 패키지 설치하면 편하다는 글을 보았다.

패키지에서 git-clone, git-plus 를 다운받았는데, 어떤 글에는 markdown preview 로 다운받으라는 글도 있었다.

Atom 이 계속 업글되면서 default pakage 에 포함되어있는 것들도, 예전 글에서는 install pakage 로 등록해야하는 것 같다.

git-clone 또한, 그냥 ctrl + shift + p 를 눌러,
커맨드 창을 호출하여 Git clone 을 치면, 나오는 Github 에서 제공하는 clone 을 쓰는 것이 더 편한 느낌이 든다.

git-plus 기능 또한, 우측 버튼을 눌러 메뉴를 띄어 처리할 때 말고는 슈퍼검색(command line)을 눌러(ctrl+shift+p)서 git tab 을 검색해 띄우는 git 관련 툴이 조금씩 손에 익으니 더 편하게 되는거 같다.

markdown preview

ctrl + shift + m

git commit, push, pull 등등

ctrl+shift+p > git tab

git clone

ctrl+shift+p > git clone

Jun 20, 2018 - ProxyApi 처리 방안 및 고민

강의 준비를 하느라 일일 커밋을 못하고 있는 상황이라, 작년 여름에 있었던 재미난 일을 적습니다.

전 연동 관련하여, 이미지 캐싱 기능을 사용하기 위해, CDN 회사 중 한곳에게 이미지 캐싱을 위한 프록시 서버를 오청했습니다.

https://img.xxx.com/?src=이미지주소

형태로 요청하면, 해당 CDN 회사는 하루동안의 해당 이미지를 캐싱하는 것인데, 자체 개발하는 공수 및 서버 증설 비용보다, 프록시 서버를 두어 이용하는 것이 가격대비 높기 때문에 결정되었습니다.

http://zzzzz.com/images/upload/./repo/targetDir/p/AM/460x640/AMMP3WA9775A_118_01.png

특정 광고주 서버에서 위와같이 /./ 형태로 이미지를 수집하여, 노출시킬때, 이미지를 그냥 호출하면 자동으로 /./ 를 없애주면서 이미지가 정상적으로 떠야하는데,

https://img.xxx.com/?src=http://zzzzz.com/images/upload/./repo/targetDir/p/AM/460x640/AMMP3WA9775A_118_01.png

프록시 서버를 통해 요청했을 경우에는 엑박이 발생하여 담당자에게 문의해보았습니다. 담당자의 대답은,

URI 에 "./" 이 포함되어 요청되는 경우. 해당 client application (Web browser, web query, etc) 에서 "./" 을 제거하여 요청합니다.

즉, 서버측에서 제거해주는게 아닙니다. 그리고 param 에 포함된 값 "./"은 browser 에서 제거해주지 않습니다.

가능하다면 request 시 ./ 이 없는 param value 로 사용 해 주시길 바랍니다.
param value 에서 "./" 을 무조건 제거 해야 하는지도 확인 바랍니다.

클라이언트의 어플리케이션 문제랍니다. 이런걸 사용자 이슈라고 하지요. 우선 전화로 이야기를 드렸습니다.

해당 이미지를 보내주는 광고주가 상대경로로 이미지를 잡아서 제공해주는 것으로 보이니, 정상적인 상황이며, 이것은 브라우져의 어플리케이션 문제가 아니므로,

CDN 프록시 서버에서 대응해주셔야한다는 이야기를 반복했지만,

이야기가 안통했습니다. 테스트한 사람의 브라우져 문제고, 사용자 이슈이므로, 프록시 서버에서 대응 해줄 수 없으므로, 프록시 서버에 이미지를 보낼때 ‘./’ 를 제거해서 보내달라고 합니다.


개발자는 개발적인 이야기 말고는 언변에서 약하다는 것을 다시 한번 느끼며, 코딩을 시작했습니다.

public class ProxyApi extends HttpServlet {
	private static final long serialVersionUID = -1501736936092164738L;

	@Override
	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		String imgUrl = request.getParameter("imgUrl");

		response.setContentType("image/jpeg");
		double len = 0;
		byte[] buffer = new byte[1024];
		BufferedOutputStream outs = null;
		InputStream inputStream = null;
		URL url = null;
		// out.flush(); // 미리 에러나 여러가지 상황 때문에 flush 시킨다.
		try {
			url = new URL(imgUrl); // 요청 url
			inputStream = url.openStream();
			outs = new BufferedOutputStream(response.getOutputStream());
			while ((len = inputStream.read(buffer)) != -1) {
				outs.write(buffer, 0, (int) len);
			}
		} catch (MalformedURLException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (outs != null)
				outs.close();
			if (inputStream != null)
				inputStream.close();
		}
	}
}

개발자는 말로 싸우면 안되는 것을 느낍니다. 쉽고 빠르게 서블릿 API를 만들어 개인서버에 뛰웠습니다.

그리고 문의글을 남겼습니다.

안녕하세요.
개발팀의 XXXX입니다.

제 개인서버에 단순히 이미지를 프록시하는 기능을 만들어 테스트 해보았습니다.

http://ttttt/test/ProxyApi?imgUrl=http://zzzzz.com/images/upload/./repo/targetDir/p/AF/460x640/AFMP3EB9882A_001_05.png

잘 뜹니다.

client application (Web browser, web query, etc) 에서 "./" 을 제거하는 것과 별개로,

단순히 ./ 는 상대경로로 해당 광고주의 같은 경로의 이미지를 가져오기 위해 사용된 것이므로,

/ : 루트
./ : 현재 위치
../ : 현재 위치의 상단 폴더

를 의미하여 해당 경로의 이미지를 가져오는 것으로 보입니다.

그러므로, 브라우져와는 상관없는 이슈로, 다시 한번 확인해주시기 바랍니다.

만약, 전화로 니가 맞네, 내가 맞네 서로 핑퐁하다가 하루를 보내기에 그때 당시 너무나 바쁘고 멘탈이 나갔기 때문에 오히려 빨리 처리해야겠다 싶어 개발을 하여 증명했던 것인데, 다른 이슈보다 더 빨리 클리어했던 걸로 기억이 납니다.

안녕하세요.

해당건은 "./" 에 대한 문제로 보였으나 실제 zzzzz.com 에서 Request 에 포함되는 일부 Header 에 대하여 필터링 하고 있습니다.

CDN 에서 External Origin 으로 보내는 일부 Request custom header 가 해당 hfashionmall 에서 필터링 되고 있었습니다.

필터되는 Header 모두 Origin 요청시 제거되도록 하였으며, 모두 정상 응답 되고 있습니다.

혼선을 드려 죄송합니다.

감사합니다.

지금도, 가끔가다가 나는 모르겠고, 어찌되었건 니네 문제니까 해결해라는 식의 담당자를 만나게 되어 그대로 이슈가 허공에 증발하는 경우가 있기 때문에, 어찌되었건 도메인 주도에 의한 개발일이라는 것은 참 힘든 것을 느낍니다.