특수 문자(Special Character) 문제(&#65279)

개요

Windows 환경에서 작성한 설정 파일이나 소스 코드를 Linux 서버에 배포할 때 특수 문자 문제가 발생하는 경우가 있습니다. 대표적으로 UTF-8 BOM(Byte Order Mark)과 Windows 개행문자(CRLF)가 눈에 보이지 않기 때문에 원인 파악이 어렵습니다.

XML 파싱 또는 Hazelcast 세션 연동 환경에서 특수 문자(Special Character)가 포함된 데이터 처리 중 예기치 않은 오류가 발생하는 경우가 있습니다. 대표적으로 Unicode BOM 문자인 (U+FEFF, Zero Width No-Break Space)가 파일 앞에 숨겨져 있거나 XML 내에 포함되어 파싱 실패로 이어지는 케이스입니다. 이 글에서는 해당 문자로 인한 오류 패턴과 해결 방법을 정리합니다.

cvc-complex-type.2.3 오류

09:52:06,504 WARNING [com.hazelcast.web.ClusteredSessionService] (default task-1) Cannot connect to Hazelcast server: cvc-complex-type.2.3: Element 'near-cache' cannot have character [children], because the type's content type is element-only. 
09:52:06,962 WARNING [com.hazelcast.web.HazelcastHttpSession] (default task-1) Unexpected error occurred.: java.lang.NullPointerException 
 at com.hazelcast.web.ClusteredSessionService.updateAttributes(ClusteredSessionService.java:285) 
 at com.hazelcast.web.HazelcastHttpSession.sessionDeferredWrite(HazelcastHttpSession.java:300) 
 at com.hazelcast.web.WebFilter.doFilter(WebFilter.java:303) 
 at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61) 
 at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131) 
 at io.undertow.servlet.handlers.FilterHandler.handleRequest(FilterHandler.java:84) 
 at io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:62) 
 at io.undertow.jsp.JspFileHandler.handleRequest(JspFileHandler.java:32) 
 at io.undertow.servlet.handlers.ServletChain$1.handleRequest(ServletChain.java:68) 
 at io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36) 
 ...
  • 일반적인 오류 해결법
  1. XML 태그 정보 누락 여부 재확인
<?xml version="1.0" encoding="UTF-8" ?>
  1. IDE 문제 – 이클립스 또는 STS 재기동
  2. 오타 여부 재확인 – 특수문자의 오기입 또는 오탈자로 인해 발생 가능 합니다.
  • 그게 아니면…..
  1. UTF-8 인코딩의 BOM(Byte Order Mark) 문제….
  2. UTF-8, UTF-16 등의 유니코드 인코딩 방식을 알리기 위한 사인(Signature)으로 사용하기 위한 용도 입니다.
  3. UTF-8은 BOM 없이도 인코딩 인식이 가능하지만 노트패드등의 윈도우 환경의 일부 에디터가 BOM을 자동으로 추가 하게 되며 눈에 보이지 않는 특수 문자(여백 문자)가 추가 되게 됩니다. 이로 인해 UNIX 환경에서 예상치 않은 cvc-complex-type.2.3 오류가 발생할 수 있습니다.
  • 해결 방안
  1. Notepad++, Ultraeditor, EditPlus 등의 에디터를 이용해 ‘UTF-8 without BOM’ (BOM 없는 UTF-8) 으로 저장
  2. 개인적으로는 BOM 없는 UTF-8로 저장이 안되어서 태그 앞의 여백 부분을 모두 삭제하여 해결 하였습니다.
  3. 윈도우에서 코드를 저장할 때는 항상 인코딩에 주의를 해야할 듯 합니다. 🙂

출처

http://blog.wystan.net/2007/08/18/bom-byte-order-mark-problem

https://ko.wikipedia.org/wiki/%EB%B0%94%EC%9D%B4%ED%8A%B8_%EC%88%9C%EC%84%9C_%ED%91%9C%EC%8B%9D

Linux에서 BOM 문자 감지 및 제거

Windows에서 작성된 파일을 Linux 서버에 배포할 때 BOM 문자가 포함된 경우, XML/JSON 파싱 오류나 쉘 스크립트 실행 오류가 발생합니다. Linux에서 BOM 문자를 감지하고 제거하는 방법은 다음과 같습니다.

# 파일에 BOM이 있는지 확인 (UTF-8 BOM = EF BB BF)
hexdump -C target.xml | head -2
# 첫 줄이 "ef bb bf"로 시작하면 BOM 포함

# file 명령으로도 확인 가능
file target.xml
# "UTF-8 Unicode (with BOM) text" 출력 시 BOM 존재

# sed로 BOM 제거 (파일 덮어쓰기)
sed -i '1s/^//' target.xml

# Python으로 BOM 제거
python3 -c "
import codecs
with codecs.open('target.xml', 'r', 'utf-8-sig') as f:
    content = f.read()
with codecs.open('target.xml', 'w', 'utf-8') as f:
    f.write(content)
print('BOM removed')
"

Visual Studio Code, IntelliJ IDEA 등 현대적인 IDE는 파일 저장 시 BOM 포함 여부를 선택할 수 있습니다. VS Code에서는 우측 하단의 인코딩 표시(예: UTF-8)를 클릭하면 “UTF-8 with BOM”과 “UTF-8” 중 선택할 수 있습니다. 팀 공용 저장소라면 .editorconfigcharset = utf-8을 지정하여 BOM 없는 UTF-8을 표준으로 적용합니다.

참고

참고 문서: Unicode BOM FAQ · XML 1.0 문자 집합 명세

Hazelcast WEB Session Clustering

개요

Hazelcast WEB Session Clustering은 IMDG(In-Memory Data Grid)를 이용해 로드밸런서로 묶인 WAS 그룹 간에 세션 정보를 공유하는 기술입니다. 특정 WAS 노드에 장애가 발생해도 세션 데이터를 잃지 않고 다른 노드에서 세션을 이어받을 수 있어 고가용성(HA) 웹 서비스 환경에서 필수 구성 요소입니다. 이 글에서는 Hazelcast 3.12 기준으로 web.xml과 hazelcast-client.xml 설정 방법을 정리합니다.

개념 (Concept)

기본 목적은 로드 밸런싱으로 묶인 WAS 그룹간의 세션 정보의 공유이며 그룹내의 임의의 WAS가 Fail over되어도 세션 정보를 유실하지 않고 다른 WAS에서 세션 연결을 가능케 하는 것입니다.

그로 인해 부가적으로 이기종 WAS간에도 세션 공유를 가능케 할 수 있다는 큰 이점이 있습니다.

구동 방식

  1. 사용자의 request가 was에 도달(servlet container)
  2. request의 session id를 대조 하는 절차 진행시(WAS에서 세션 정보 조회 전)
  3.  webfilter를 통해 request의 쿠키가 가진 session id를 IMDG의 맵에서 조회
  4. 조회 값을 반환 할 경우 기존 session으로 할당 처리( 후 request 응답 처리)
  5. 조회 값이 없을 경우 webfilter를 통해 지정된 형식으로 session id 생성 후 그 id로 sessionlistener를 통해 httpsession 생성
  6. request가 완료되면 생성된 session 정보를 IMDG 맵에 저장

IMDG 기반 분산 세션 관리

노드들을 클러스터 구성 하여 여러 서버의 메모리를 파티셔닝 또는 샤딩이라는 기술을 통해 하나의 메모리 공간처럼 활용, 데이터를 임의의 노드 메모리에 분산 저장시 실시간 또는 비동기로 생성된 백업 데이터는 원본 데이터를 소유한 노드 이외의 다른 노드에 분산 저장됩니다.
이 기능을 이용하여 세션 정보의 손실을 방지 하며 빠른 I/O 성능을 보장합니다.

구성 (hazelcast 3.12, JDK 1.8)

  1. WEB-INF에 web.xml 및 hazelcast-client.xml 설정
  2. WEB-INF/lib 또는 Classpath에 hazelcast-wm.jar(hazelcast-all.jar 가능) 위치

web.xml 설정

<!-- hazelcast filter start -->
<filter>
    <filter-name>hazelcast-filter</filter-name>
    <filter-class>com.hazelcast.web.WebFilter</filter-class>
    <init-param>
        <param-name>map-name</param-name>
        <param-value>hazelcast-sessions</param-value>
    </init-param>
    <init-param> <!-- Embedded only -->
        <param-name>session-ttl-seconds</param-name>
        <param-value>0</param-value>
    </init-param>
    <init-param>
        <param-name>keep-remote-active</param-name>
        <param-value>false</param-value>
    </init-param>
    <init-param>
        <param-name>sticky-session</param-name>
        <param-value>true</param-value>
    </init-param>
    <init-param>
        <param-name>cookie-name</param-name>
        <param-value>hazelcast.sessionId</param-value>
    </init-param>
    <init-param> <!-- 도메인 없을 때 비울것 -->
        <param-name>cookie-domain</param-name>
        <param-value>www.example.com</param-value>
    </init-param>
    <init-param>
        <param-name>cookie-secure</param-name>
        <param-value>false</param-value>
    </init-param>
    <init-param>
        <param-name>cookie-http-only</param-name>
        <param-value>false</param-value><!-- default false -->
    </init-param>
    <init-param>
        <param-name>debug</param-name>
        <param-value>true</param-value><!-- default false -->
    </init-param>
    <init-param>
        <param-name>shutdown-on-destroy</param-name>
        <param-value>false</param-value>
    </init-param>
    <init-param>    <!--    이부분이 설정파일의 위치(Embaded 설정)    -->
        <param-name>config-location</param-name>
        <param-value>/WEB-INF/config/spring/my-hazelcast.xml</param-value>
    </init-param>
    <init-param>
        <param-name>instance-name</param-name>
        <param-value>hazel-ses</param-value>
    </init-param>
    <init-param> <!-- Client mode only -->
        <param-name>use-client</param-name>
        <param-value>true</param-value>
    </init-param>
    <init-param>  <!-- Client Configuration(or hazelcast-client.xml) -->
        <param-name>client-config-location</param-name>
        <param-value>/WEB-INF/classes/hazelcast-client.properties</param-value>
    </init-param>
    <init-param>
        <param-name>deferred-write</param-name>
        <param-value>true</param-value><!-- default false -->
    </init-param>
    <init-param>
        <param-name>cookie-path</param-name>
        <param-value>/</param-value>
    </init-param>
    <init-param>
        <param-name>cookie-max-age</param-name>
        <param-value>-1</param-value>
    </init-param>
    <init-param>
        <param-name>use-request-parameter</param-name>
        <param-value>false</param-value>
    </init-param>
</filter>

<filter-mapping>
    <filter-name>hazelcast-filter</filter-name>
    <url-pattern>/*</url-pattern>
    <dispatcher>FORWARD</dispatcher>
    <dispatcher>INCLUDE</dispatcher>
    <dispatcher>REQUEST</dispatcher>
</filter-mapping>

<listener>
    <listener-class>com.hazelcast.web.SessionListener</listener-class>
</listener>
<!-- hazelcast filter end -->
Topic Desc Default
map-name 세션 오브젝트가 저장될 분산맵의 이름 선언
session-ttl-seconds 세션 오브젝트가 저장될 분산맵의 수명(초) 설정, 0이상의 정수형(최대2147483647)으로 설정 1800(30분)
sticky-session true 설정시, 모든 세션 요청이 세션이 처음 만들어진 멤버로 라우팅 됨. 더 좋은 성능을 제공함. false 설정시, 세션이 멤버상에서 업데이트 될 때, 모든 멤버상의 이 세션 엔트리는 무효화 됨.
이 파라메터를 설정하기 전에 로드 밸런서(L4) 구성상황을 알 필요가 있음
true
Generic Parameter 의미
  • Client config 구성 ( hazelcast-client.xml )
<hazelcast-client>
...
    <group>
        <name>dev</name>
        <password>dev-pass</password>
    </group>

    <network>
        <cluster-members>
            <!--MEMBERS-->
            <address>192.168.0.101</address>
            <address>192.168.0.102</address>
            <address>192.168.0.103</address>
        </cluster-members>
    </network>
    <load-balancer type="round-robin"/>

...
</hazelcast-client>

참고

참고 문서: Hazelcast Web Session Replication 공식 문서 · Hazelcast IMDG 개요

vi editor 텍스트 밀림 현상

개요

Unix / Linux 환경에서 vi editor 텍스트 밀림 현상은 OS 언어 설정이 한글인 환경에서 자주 발생합니다. SSH 클라이언트의 인코딩 설정과 서버의 LANG 환경변수가 불일치할 때 vi로 파일을 편집하면 한글이 깨지거나 입력한 텍스트가 밀려서 표시됩니다. 이 글에서는 원인 분석과 해결 방법, 재발 방지 설정을 정리합니다.

증상

vi editor 실행 중 아래와 같은 현상이 나타납니다.

  • 한글 입력 시 글자가 밀려서 표시되거나 커서 위치가 어긋남
  • 화면이 깨져 기존 내용이 뭉개져 보임
  • 특정 계정에서만 발생하고 다른 계정에서는 정상 동작
  • 스크립트 파일 편집 시 들여쓰기가 흐트러짐

원인

주요 원인은 SSH 터미널의 인코딩 설정과 서버 LANG 환경변수의 불일치입니다. OS를 한글로 설치하면 LANG=ko_KR.UTF-8로 설정되는데, SSH 클라이언트(PuTTY, MobaXterm, SecureCRT 등)의 문자 집합이 UTF-8이 아닌 경우 vi가 멀티바이트 문자를 잘못 처리하여 텍스트 밀림이 발생합니다. 간헐적으로는 홈 디렉토리의 환경설정 파일(.bashrc, .bash_profile)이 누락된 경우에도 동일한 현상이 발생할 수 있습니다.

# 현재 서버 로케일 확인
$ locale
LANG=ko_KR.UTF-8
LC_CTYPE="ko_KR.UTF-8"
LC_NUMERIC="ko_KR.UTF-8"
LC_TIME="ko_KR.UTF-8"
LC_ALL=

해결 방법 1 — SSH 터미널 인코딩 설정 변경

SSH 클라이언트의 Character encoding(문자 집합) 설정을 서버 LANG과 일치하도록 변경합니다. 현재 설정이 Default이면 UTF-8로, UTF-8이면 Default로 변경하여 재연결합니다.

  • PuTTY: Configuration → Window → Translation → Remote character set
  • MobaXterm: Settings → Terminal → Default character set
  • SecureCRT: Session Options → Terminal → Emulation → Character Encoding

설정 변경 후 SSH 세션을 재연결하여 vi를 실행하면 대부분의 경우 정상 동작합니다.

해결 방법 2 — TERM 환경변수 설정

터미널 인코딩 변경 후에도 화면이 깨지는 현상이 남아 있다면 TERM 환경변수를 vt100으로 변경한 뒤 vi를 실행합니다.

# 현재 세션에만 적용 (임시)
$ export TERM=vt100
$ vi <파일명>

# 영구 적용 — .bashrc 또는 .bash_profile에 추가
$ echo 'export TERM=vt100' >> ~/.bashrc
$ source ~/.bashrc

재발 방지 — .vimrc 인코딩 설정

사전에 ~/.vimrc에 인코딩을 명시해 두면 SSH 클라이언트 설정과 무관하게 vi가 항상 UTF-8로 동작하므로 vi editor 텍스트 밀림 현상을 예방할 수 있습니다.

" ~/.vimrc
set encoding=utf-8
set fileencodings=utf-8,euc-kr,cp949
set termencoding=utf-8

파일이 없는 경우 vi ~/.vimrc로 새로 생성합니다. 저장 후 vi를 재실행하면 설정이 적용됩니다.

참고

관련 포스트:

참고 문서: Vim encoding 옵션 공식 문서 · Linux locale(7) man page