개발

MySQL Workbench

MySQL의 GUI 툴이 2010년 부터 Workbench로 완전 전환되었다.

지난 1월 14일 5.2.14 Beta4 가 릴리즈되었다.

주요 기능은 SQL Development, Data Modeling, Server Administration이다.

장점

  • Windows의 한글입력 문제 해결
  • 실행한 SQL에 대한 History
  • Windows와 Mac OS 등에 대한 동일한 사용자 인터페이스(UI) 제공

단점

  • 동일 SQL 탭에서 개별 SQL을 선택하지 않고 Control+Enter로 실행 불가능

알려진 문제

  • Home탭의 Open Connection 목록의 한글 깨짐

위의 단 점은 상당히 개인적일 수 있다. 이전의 MySQL Query Browser의 경우

여러 개의 SQL이 하나의 탭에 있을 때 해당 SQL에서 Control+Enter로 그 SQL 만 실행되었다면

이제는 모든 SQL이 실행된다는 것이다.

그래서 결과 탭이 SQL 개수 만큼 출력된다.

이 방식은 MS SQL의 GUI 툴에서 사용하는 방식이라 이미 여러 개발자는 익숙해져있을 수 있고,

여러 개 결과를 동시에 볼 수 있다는 장점이기도 하지만 나로선 사용 습관을 바꿔야 한다.

예를 들어 UPDATE나 DELETE가 포함된 여러개 SQL 파일을 열어서 작업할 때

지금까지는 필요할 때만 그 줄에서 실행했는데

이제는 낱 개로 SQL을 실행하기위해 해당 SQL을 선택하고 실행해야 한다.

참고

Tomcat Parameters: Invalid chunk ignored.

Tomcat을 사용할 때 가끔씩 다음과 같은 오류가 발생할 때가 있다.

경고: Parameters: Invalid chunk ignored.

이것은 Tomcat의 Parameters 클래스에서 출력하는 것으로 Tomcat 5.5.27 소스를 보면 다음과 같다.

apache-tomcat-5.5.27-src/connectors/util/java/org/apache/tomcat/util/Parameters.java

public void processParameters( byte bytes[], int start, int len,
        String enc )
{
...
    if( nameEnd<=nameStart ) {
        log.warn("Parameters: Invalid chunk ignored.");
        continue;
        // invalid chunk - it's better to ignore
    }
...
}

소스를 보면 이 경고 메시지는 매개변수 명과 값이 구분안되는 경우 (예를 들어 요청중에  &= 또는 &&와 같은 경우) 나타날 수 있다.

호출하는 쪽을 수정하면 좋으나 이것이 불가능할 때는 오류 메시지가 출력되지 않도록 할 수 밖에 없다.

CATALINA_HOME/conf/logging.properties 파일의 마지막에 다음 줄을 추가하고 Tomcat을 재시작 한다.

org.apache.tomcat.util.http.Parameters.level = SEVERE

Java UTF-8과 서울신용평가정보 가상식별 실명확인서비스

공공 프로젝트에서 서울신용평가정보의 가상식별 실명확인서비스를 적용하다 보니
확인된 성명(한글)이 깨지는 문제를 만나게 되었다.

여러가지 테스트를 해보니 문제 상황은 다음과 같다.

Java 소스 및 JSP와 Tomcat의 Connector의 URIEncoding은 UTF-8이다.

Tomcat을  Locale을 UTF-8 상태에서 실행하거나

Tomcat 실행 옵션에 file.encoding 시스템 프로퍼티를 UTF-8로 설정하고 실행하면 문제가 발생되었다.

만약 Locale이 EUC-KR이거나 file.encoding=EUC-KR 이면 성명이 깨지지 않았다.

이제 UTF-8 상태에서 Tomcat을 실행하는 것이 기본이 된 것 같은데 아직도 2% 부족하다는 것을 알 수 있다.

단순한 서비스 하나를 위해서 EUC-KR로 Tomcat을 실행하는 것은 앞뒤가 맞지 않다고 생각한다.

이를 해결하기 위해 서울신용평가정보에서 제공된 클래스의 메소드 com.sci.vname.secu.aes.SciPacketConversion#RecvWritePacket(String)를 보니 역시 bytes를 String으로 만들 때 그냥 만들도록 되어있었다.

문제를 해결할 겸 좀 더 쉽게 사용하기 위해서 클래스를 만들었다.

/**
 * @(#)SciVname.java
 *
 * Copyright (C) 2009 D.TRIBE. All rights reserved.
 *
 * THIS SOFTWARE IS THE PROPRIETARY INFORMATION OF DTRIBE, INC.
 * USE IS SUBJECT TO LICENSE TERMS.
 */
package com.dtribe.scivname;

import com.dtribe.model.BaseModel;
import com.sci.vname.secu.aes.SciHttpSecuX;
import com.sci.vname.secu.hmac.SciHmac;

/**
 * 서울신용평가정보 가상 식별 실명 확인<br/>
 * <p>
 * 서울신용평가정보에서 제공하는 클래스 코드 사용 예:
 * <pre>{@code
 * String org = request.getParameter("retInfo");
 * String result = com.sci.vname.secu.aes.SciPacketConversion.RecvWritePacket(org).toString();
 * String[] data = result.split("/");
 * String name = "";
 * if(data.length == 7) {
 *   log.debug("reqNum=" + data[0]);
 *   log.debug("vDiscrNo=" + data[1]);
 *   log.debug("name=" + data[2]);
 *   log.debug("result=" + data[3]);
 *   log.debug("discrHash=" + data[4]);
 *   log.debug("hashMsg=" + data[5]);
 *   String checksum = com.sci.vname.secu.hmac.SciHmac.HMacEncript(data[0]+data[1]+data[3]);
 *   if(checksum.equals(data[5])) {
 *   	name = data[2];
 *   }
 * }}</pre>
 * </p>
 *
 * <p>디트라이브 클래스 코드 사용 예:
 * <pre>{@code
 * String org = request.getParameter("retInfo");
 * SciVname vname = SciVname.parse(org);
 * if(vname != null && vname.isValid()) {
 *   //
 * }}</pre>
 * </p>
 *
 * @author	Barney Kim
 * @version	$Revision: 1.2 $, $Date: 2009/08/19 20:36:53 $
 */
public class SciVname extends BaseModel {

	/** 시리얼 버전 UID */
	private static final long serialVersionUID = 429479309042299567L;

	/** 요청 번호 */
	private String reqNum;

	/** 가상 식별 번호 */
	private String vDiscrNo;

	/** 성명  */
	private String name;

	/** 식별검증 결과 */
	private String result;

	/** 중복가입확인 정보 */
	private String discrHash;

	/** 위/변조 검증값 */
	private String hashMsg;

	// 속성 ------------------------------------------------------------------

	/**
	 * 요청 번호 (최대 30 자)
	 * 실명확인 요청마다 유일하게 생성.
	 */
	public String getReqNum() {
		return reqNum;
	}

	/**
	 * 요청번호
	 */
	public void setReqNum(String reqNum) {
		this.reqNum = reqNum;
	}

	/**
	 * 가상 식별 번호 (13 자)
	 * 주민등록번호와 매칭되는 SCI에서 생성한 가상의 13자리 번호
	 * 성공: SCI+식별번호11자리
	 * 실패: SCI+99999999999
	 */
	public String getVDiscrNo() {
		return vDiscrNo;
	}

	/**
	 * 가상 식별 번호
	 */
	public void setVDiscrNo(String discrNo) {
		vDiscrNo = discrNo;
	}

	/**
	 * 성명 (최대 20자)
	 * 실패: null
	 */
	public String getName() {
		return name;
	}

	/**
	 * 성명
	 */
	public void setName(String name) {
		this.name = name;
	}

	/**
	 * 식별검증 결과 (1자)
	 * 1: 정상
	 * 2: 오류.
	 *    입력된 주민번호의 성명이 실명확인 DB의 성명과 불일치,
	 *    주민번호가 숫자가 아닌경우, 주민번호가 13자리가 아닌경우
	 *    성명이 한글이 아닌경우, 성명이 2~10자 아닌 경우
	 * 3: 없음.
	 *    입력된 주민번호와 성명이 실명확인 DB에 업ㅎㅅ는 경우
	 * 4: 주민민번호 오류
	 *    입력된 주민번호의 조합이 오류인 경우: 생년이 70년이후인 경우만.
	 * 5: 시스템 오류
	 */
	public String getResult() {
		return result;
	}

	/**
	 * 식별검증 결과
	 */
	public void setResult(String result) {
		this.result = result;
	}

	/**
	 * 중복가입확인 정보 (최대 64자)
	 * 아이핀서비스를 이용하는 경우만 값이 유효
	 */
	public String getDiscrHash() {
		return discrHash;
	}

	/**
	 * 중복가입확인 정보
	 */
	public void setDiscrHash(String discrHash) {
		this.discrHash = discrHash;
	}

	/**
	 * 위/변조 검증값 (최대 40자)
	 */
	public String getHashMsg() {
		return hashMsg;
	}

	/**
	 * 위/변조 검증값
	 */
	public void setHashMsg(String hashMsg) {
		this.hashMsg = hashMsg;
	}

	// 메소드 ----------------------------------------------------------------

	/**
	 * 서울신용평가정보의 실명확인결과의 암호화된 문자열을 복호화하여
	 * 평문으로 반환한다.
	 *
	 * SciPacketConversion.RecvWritePacket은 file.encoding 시스템 프로퍼티가
	 * EUC_KR이 아닌 UTF-8일 경우 한글이 깨지기 때문에 이를 방지하기 위해
	 * RecvWritePacket 메소드를 일부 수정함.
	 *
	 * @param s 인코드된 문자열
	 * @return 디코드된 문자열
	 * @see com.sci.vname.secu.aes.SciPacketConversion#RecvWritePacket(String)
	 */
	public static String decode(String s) {
		if(s == null) {
			return null;
		}
		StringBuffer sb = new StringBuffer("");
		int buffer = 1000;
		try {
			byte dec[] = SciHttpSecuX.Decryption(s);
			byte bytes[] = new byte[buffer];
			int i=0, j=0;
			do {
				if(j >= dec.length) {
					break;
				}
				bytes[i] = 0;
				if(dec[j] == 0 || dec[j] == 10) {
					// ORG
					// sb.append((new String(bytes)).trim());
					// for UTF-8
					sb.append((new String(bytes, "EUC_KR")).trim());
					break;
				}
				if(dec[j] >= 1 && dec[j] <= 126) {
					bytes[i] = dec[j];
					i++;
				} else {
					bytes[i] = dec[j];
					bytes[i + 1] = dec[j + 1];
					i += 2;
					j++;
				}
				if(i >= 998) {
					// ORG
					// sb.append(new String(bytes));
					// for UTF-8
					sb.append(new String(bytes, "EUC_KR"));
					i = 0;
					bytes = new byte[buffer];
				}
				j++;
			} while(true);
		}
		catch(Exception ex) {
			ex.printStackTrace();
		}
		return sb.toString();
	}

	/**
	 * 서울신용평가정보의 실명확인결과의 암호화된 문자열을 복호화하고 이를 이용하여
	 * SciVname 객체를 생성하여 반환한다.
	 *
	 * @param s 인코드된 문자열
	 * @return SciVname 객체
	 */
	public static SciVname parse(String s) {
		if(r == null) {
			return null;
		}
		String r = decode(s);
		String data[] = r.split("/");
		if(data.length != 7) {
			return null;
		}
		SciVname vname = new SciVname();
		vname.setReqNum   (data[0]);
		vname.setVDiscrNo (data[1]);
		vname.setName     (data[2]);
		vname.setResult   (data[3]);
		vname.setDiscrHash(data[4]);
		vname.setHashMsg  (data[5]);
		return vname;
	}

	/**
	 * 가상 식별 실명 확인이 성공하고 올바른지 확인한다.
	 *
	 * @return 위/변조 검증값이 올바르고 결과가 성공이면 true
	 */
	public boolean isValid() {
		if (this.reqNum == null   || this.reqNum.length() == 0 ||
			this.vDiscrNo == null || this.vDiscrNo.length() != 13 ||
			this.result == null   || this.result.length() != 1 ||
			this.hashMsg == null  || this.hashMsg.length() == 0) {
			return false;
		}
		StringBuffer sb = new StringBuffer();
		sb.append(this.reqNum).append(this.vDiscrNo).append(this.result);
		String checksum = SciHmac.HMacEncript(sb.toString());
		return (checksum.equals(this.hashMsg) && this.result.equals("1"));
	}

	/**
	 * 디코드된 문자열로 가상 식별 실명 확인이 성공하고 올바른지 확인한다.
	 *
	 * @param s 디코드된 문자열
	 * @return 위/변조 검증값이 올바르고 결과가 성공이면 true
	 */
	public static boolean isValid(String s) {
		String[] data = s.split("/");
		if(data.length != 7) {
			return false;
		}
		if (data[0] == null || data[0].length() == 0 ||
			data[1] == null || data[1].length() != 13 ||
			data[3] == null || data[3].length() != 1 ||
			data[5] == null || data[5].length() == 0) {
			return false;
		}
		StringBuffer sb = new StringBuffer();
		sb.append(data[0]).append(data[1]).append(data[3]);
		String checksum = SciHmac.HMacEncript(sb.toString());
		return (checksum.equals(data[5]) && data[3].equals("1"));
	}
}
// EOF

이것을 Spring Framework에서 사용하면 다음과 같다.

import com.dtribe.scivname.SciVname;

public ModelAndView output(HttpServletRequest request,
		HttpServletResponse response)
	throws Exception
{
	String org = ServletRequestUtils.getRequiredStringParameter(request, "retInfo");
	SciVname vname = SciVname.parse(org);
	log.debug("org=" + org);
	log.debug("vname=" + vname);

	ModelAndView mav = new ModelAndView("vname/output");
	if(vname != null && vname.isValid()) {
		mav.addObject("vname", vname);
	}
	return mav;
}

JSP 페이지 (vname/output.jsp)

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"
%><%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"
%><c:if test="${vname != null}">
<script type="text/javascript">
top.document.getElementById("name").value="${vname.name}";
</script>
</c:if>