PoolingHttpClientConnectionManager 를 xml기반 Spring bean으로 등록하기.

개발한 서비스 중 commons httpclient 로 서버 <-> 서버 간 rest api 호출하는 부분에  부분에서 간헐적으로 NoHttpResponseException : The target server failed to respond 예외가 발생하기 시작했다. 이를 해결하기위해 자료를 좀 찾아봤더니 HttpClient 4.4에서 존재하던 버그였고 4.4.1에서 해결된 문제( https://issues.apache.org/jira/browse/HTTPCLIENT-1610 )라고 하는데... 개발한 서비스에는 4.5.1을 쓰는데?

현상의 이유는 HTTP/1.1의 Keep-Alive로 인해 httpclient는 통신이 끝난 connection을 종료하지 않고 동일host:port에 대해 동일한 커넥션을 이용하려하기 때문이다.

비록 서버측은 통신이 완료되어 해당 연결을 close 할지라도 client 측은 커넥션 객체가 여전히 열여있고 데이터가 인입되길 기다리고 있게된다.  ( close()의 실제 의미는 소켓의 단절이 아닌 "나는 더 이상 보낼 데이터가 없습니다."로 상대측에서는 해당 커넥션을 단절하지 않는 이상 여전히 데이터가 인입될 수 있음을 의미한다. )
이때를 half-closed connection 으로 표현하며 이는 TCP가 그렇게 동작하게끔 설계되었기 때문으로 버그가 아니다. 이런 상황이 되면 JVM상의 connection 객체는 당연히 살아있지만 내부 소켓은 CLOSE_WAIT 상태가 된다.

문제는 httpclient가 이 CLOSE_WAIT 상태에있는 connection 객체를 다시 사용하려고 할때 앞에서 설명한것과 같이 서버 측은 이미 연결을 끊어버렸기 때문에 NoHttpResponseException - The target server failed to respond 예외를 발생한다.
이를 해결하기위해서는 httpclient의 connectionManager에서 통신이 완료된 connection을 적절하게 제거할 필요가 있다.

Spring 4.X 환경에서 commons HttpClient 4.5.x 를 소켓 설정과 KeepAlive 설정등을 포함하여 xml로 설정하는 방법.

PoolingHttpClientConnectionManager를 통하여 CloseableHttpClient 를 사용하는 과정에 ConnectionManager의 closeIdleConnections 설정 통해 특정시간 idle인 커넥션을 종료하고 싶은 경우 java 코드가 아닌 spring xml 설정정으로 bean을 등록하려 할 때. 

사용은 당연히
@Autowired
private CloseableHttpClient httpClient;

[code]
<!-- ===================================================================== -->
<!-- =======================   HttpClient 4.5.X   ======================== -->
<!-- ===================================================================== -->        
<bean id="requestConfigBuilder" class="org.apache.http.client.config.RequestConfig" factory-method="custom">
        <property name="socketTimeout" value="10000" /> 
        <property name="connectTimeout" value="12000" /> 
        <property name="connectionRequestTimeout" value="12000" />
</bean>

<bean id="requestConfig" factory-bean="requestConfigBuilder" factory-method="build" />

<bean id="socketConfigBuilder" class="org.apache.http.config.SocketConfig" factory-method="custom">
    <!-- 소켓이 연결된후 InputStream에서 읽을때 timeout -->
    <property name="soTimeout" value="10000" /> 
    <!-- SO_KEEPALIVE를 활성화 할 경우 소켓 내부적으로 일정시간 간격으로 heartbeat을 전송하여, 비정상적인 세션 종료에 대해 감지.
    unix 계열 : /etc/sysctl.conf
    windows : \HKEY_LOCAL_MACHINE\SystemCurrentControlSet\Services\TCPIP\Parameters
    -->
    <property name="soKeepAlive" value="true" /> 
    <!-- 비정상종료된 상태에서 아직 커널이 소켓의 bind정보를 유지하고 있을 때 해당 소켓을 재사용 할 수 있도록 -->
    <property name="soReuseAddress" value="true" /> 
    <!-- nagle 알고리즘 적용 여부 -->
    <property name="tcpNoDelay" value="true" /> 
    <!-- socket이 close 될 때 버퍼에 남아 있는 데이터를 보내는데 기다려주는 시간(blocked)-->
    <property name="soLinger" value="100" /> 
</bean>

<bean id="poolingHttpClientConnectionManager" class="org.apache.http.impl.conn.PoolingHttpClientConnectionManager" destroy-method="shutdown">
    <constructor-arg value="2000" type="long" index="0" /> <!-- pool에 있는 커넥션 제거 idle time -->
    <constructor-arg value="MILLISECONDS" type="java.util.concurrent.TimeUnit" index="1" />
    <property name="maxTotal" value="60" />
    <property name="defaultMaxPerRoute" value="15" />
    <property name="defaultSocketConfig"><bean factory-bean="socketConfigBuilder" factory-method="build" /></property>
</bean>

<bean id="connectionKeepAliveStrategy" class="com.http.client.HttpShortKeepAliveStrategy" />

<bean id="httpClientBuilder" class="org.apache.http.impl.client.HttpClientBuilder" factory-method="create">            
    <property name="defaultRequestConfig" ref="requestConfig" />
    <property name="connectionManager" ref="poolingHttpClientConnectionManager" />
    <property name="userAgent" value="Mozilla/5.0 (Windows NT 6.1; WOW64) CUSTOM-CLIENT" />
    <property name="keepAliveStrategy" ref="connectionKeepAliveStrategy" />
</bean>

<bean id="httpClient" factory-bean="httpClientBuilder" factory-method="build" destroy-method="close" />
[/code]

HttpShortKeepAliveStrategy 클래스는...
[code]
package com.http.client;

import org.apache.http.HeaderElement;
import org.apache.http.HeaderElementIterator;
import org.apache.http.HttpResponse;
import org.apache.http.conn.ConnectionKeepAliveStrategy;
import org.apache.http.message.BasicHeaderElementIterator;
import org.apache.http.protocol.HTTP;
import org.apache.http.protocol.HttpContext;

/**
 * HttpShortKeepAliveStrategy (UTF-8) created : 2016. 5. 25
 *

 */
public class HttpShortKeepAliveStrategy implements ConnectionKeepAliveStrategy {

    /**
     *
     * @param response
     * @param context
     * @return
     */
    @Override
    public long getKeepAliveDuration(HttpResponse response, HttpContext context) {
        // Honor 'keep-alive' header
        HeaderElementIterator it = new BasicHeaderElementIterator(
                response.headerIterator(HTTP.CONN_KEEP_ALIVE));
        while (it.hasNext()) {
            HeaderElement he = it.nextElement();
            String param = he.getName();
            String value = he.getValue();
            if (value != null && param.equalsIgnoreCase("timeout")) {
                try {
                    return Long.parseLong(value) * 100;
                } catch (NumberFormatException ignore) {
                }
            }
        }


        HttpHost target = (HttpHost) context.getAttribute(HttpClientContext.HTTP_TARGET_HOST);
        if ("www.mydomain.com".equalsIgnoreCase(target.getHostName())) {
            // Keep alive for 5 seconds only
            return 5 * 1000;
        } else {
            // otherwise keep alive for 1 seconds
            return 1 * 1000;
        }
    }

}
[/code]

코드로 표현하면 대강 다음과 같음.
[code]
static final CloseableHttpClient httpClient;
static {
        PoolingHttpClientConnectionManager pooledManager = new PoolingHttpClientConnectionManager(20L,java.util.concurrent.TimeUnit.MILLISECONDS);
        pooledManager.setMaxTotal(15);
        pooledManager.setDefaultMaxPerRoute(5);
        pooledManager.closeIdleConnections(20L, TimeUnit.MILLISECONDS);
        pooledManager.setDefaultSocketConfig(SocketConfig.custom()
                // nagle 알고리즘 적용 여부
                .setTcpNoDelay(true)
                // SO_KEEPALIVE를 활성화 할 경우 소켓 내부적으로 일정시간 간격으로 heartbeat을 전송하여, 비정상적인 세션 종료에 대해 감지.
                // unix 계열 : /etc/sysctl.conf
                // windows : \HKEY_LOCAL_MACHINE\SystemCurrentControlSet\Services\TCPIP\Parameters
                .setSoKeepAlive(true)
                // socket이 close 될 때 버퍼에 남아 있는 데이터를 보내는데 기다려주는 시간(blocked)
                .setSoLinger(200)
                // 비정상종료된 상태에서 아직 커널이 소켓의 bind정보를 유지하고 있을 때 해당 소켓을 재사용 할 수 있도록
                .setSoReuseAddress(true)
                //소켓이 연결된후 InputStream에서 읽을때 timeout
                .setSoTimeout(10000)
                .build()
        );
        httpClient = HttpClients.custom()
                        .setConnectionManager(pooledManager)
                        .setUserAgent("Mozilla/5.0 (Windows NT 6.1; WOW64) API-CLIENT")
                        .setRedirectStrategy(new DefaultRedirectStrategy() {
                                @Override
                                public boolean isRedirected(HttpRequest request, HttpResponse response, HttpContext context) {
                                        boolean isRedirect = false;
                                        try {
                                                isRedirect = super.isRedirected(request, response, context);
                                        } catch (ProtocolException e) {
                                                logger.error(null, e);
                                        }
                                        if (!isRedirect) {
                                                int responseCode = response.getStatusLine().getStatusCode();
                                                if (responseCode == 301 || responseCode == 302) {
                                                        return true;
                                                }
                                        }
                                        return false;
                                }
                        })
                        .setKeepAliveStrategy(new ConnectionKeepAliveStrategy() {
                                @Override
                                public long getKeepAliveDuration(HttpResponse response, HttpContext context) {
                                        // Honor 'keep-alive' header
                                        HeaderElementIterator it = new BasicHeaderElementIterator(
                                                        response.headerIterator(HTTP.CONN_KEEP_ALIVE));
                                        while (it.hasNext()) {
                                                HeaderElement he = it.nextElement();
                                                String param = he.getName();
                                                String value = he.getValue();
                                                if (value != null && param.equalsIgnoreCase("timeout")) {
                                                        try {
                                                                return Long.parseLong(value) * 100;
                                                        } catch (NumberFormatException ignore) {
                                                        }
                                                }
                                        }
                                        HttpHost target = (HttpHost) context.getAttribute(
                                                        HttpClientContext.HTTP_TARGET_HOST);
                                        if ("www.mydomain.com".equalsIgnoreCase(target.getHostName())) {
                                                // Keep alive for 5 seconds only
                                                return 5 * 1000;
                                        } else {
                                                // otherwise keep alive for 0.1 seconds
                                                return 1 * 100;
                                        }
                                }
                        })
                        .build();
}
[/code]

2016/05/25 15:28 2016/05/25 15:28
Trackback Address:이 글에는 트랙백을 보낼 수 없습니다
  1. Blog Icon
    개발 김

    해당 글 너무나도 잘 봤습니다!! ㅎㅎ 해결하신 방법은 일정한 타임 아웃을 주고 해당 시간동안 아무런 반응이 없으면 해당 커넥션을 없애는 방식인데 그렇다면 사용자가 잠깐 아무 입력없이 가만히 있게 되면 커넥션이 죽게 될 텐데 이 부분에 대해서는 어떻게 생각하시는지요???

  2. Blog Icon
    서비

    개발 김님 안녕하세요.
    질문 내용은 일반적인 연결 지향인 소켓 상황과 헷갈리신것 같습니다.
    http 역시 소켓에 기반하고있습니다만 아무입력없이 대기 시 커넥션이 죽는
    현상은 일반적인 http 에서는 해당되지 않는 사항입니다.
    http 프로토롤 자체가 필요 시 연결, 데이터 송수신하고 연결을 끊는 컨셉이기 때문입니다.
    keep-alive 는 비교적 짧은 리드타임으로 송수신을 반복할 경우 접속 오버헤드를 줄이고자하는 기법의 하나입니다.

  3. Blog Icon
    개발 김

    너무 감사합니다~ 최근 이부분을 좀 관심있게 보고 있는 중이여서요!!
    HttpShortKeepAliveStrategy 클래스에서
    위에 while문이 어떤 부분을 하는 건가요??
    밑에는 해당 도메인이면 keep alive 시간을 조금 더 주는 건가요??
    직접 이 에러를 발생시켜보고 싶은데 잘 안돼네요 일부러 버전도 4.3.6 쓰는데 close_wait인 커넥션은 자기가 알아서 피해가네요;;

  4. Blog Icon
    서비

    @개발 김님
    링크의 기술문서가 도움이 되지 않을까해서 소개 드립니다.
    http://tech.kakao.com/2016/04/21/closewait-timewait/

Spring Framework 5.0

2017년 이후를 겨냥한 다음 세대 Spring 프레임워크 소식이 들립니다.
프레임워크의 전체 코드를 java 8 기반( lambda , stream api , nio2 등..)으로 작성.
JDK 9 포괄적 지원과 HTTP/2에 집중하며 Spring-style의 reactive 아키텍처를 목표로 개발 진행 중.
5.0 milestone 1 : 2016년 중순
5.0 RC1 : 2016년 12월
최소 사양
JDK 8+, Servlet 3.0+, JMS 2.0+, JPA 2.1+, JUnit 5
주요 기반
JDK 9 과 Jigsaw ( 모듈러 java를 구현하기 위한 java key feature )
Servlet 4.0 과 HTTP/2
Reactive 아키텍처

2016/04/07 14:57 2016/04/07 14:57
Trackback Address:이 글에는 트랙백을 보낼 수 없습니다

MSSQL Procedure 변경이력 확인

[code sql]SELECT *
FROM sys.sql_modules sm 
inner join sys.all_objects ao 
on sm.object_id = ao.object_id 
order by modify_date desc
[/code]
2016/03/23 14:57 2016/03/23 14:57
Trackback Address:이 글에는 트랙백을 보낼 수 없습니다

Executable jar 를 메이븐 exec:exec 로 실행 하기

Executable JAR 빌드 후 메이븐 exec:exec 로 실행하고 싶다면 exec-maven-plugin 을 사용할 수 있다. [code xml]<plugin>
  <groupId>org.codehaus.mojo</groupId>
  <artifactId>exec-maven-plugin</artifactId>
  <version>1.4.0</version>
  <executions>
    <execution>
      <id>default-cli</id>
      <goals>
          <goal>exec</goal>
      </goals>
      <configuration>
        <executable>java</executable>
        <!-- optional -->
        <workingDirectory>/tmp</workingDirectory>
        <arguments>
          <argument>-jar</argument>
          <argument>${basedir}/target/${project.artifactId}-${project.version}.jar</argument>
        </arguments>
      </configuration>                        
    </execution>
  </executions>
</plugin>[/code]

mvn exec:exec 가 아닌 mvn exec:java 로 컴파일 output 디렉토리에서 main class를 바로 실행하고 싶다면 다음과 같이 설정한다.
[code xml]<plugin>
  <groupId>org.codehaus.mojo</groupId>
  <artifactId>exec-maven-plugin</artifactId>
  <version>1.4.0</version>
  <executions>
    <executions>
      <execution>
        <goals>
            <goal>java</goal>
        </goals>
      </execution>
      <configuration>
        <mainClass>com.app.MainClass</mainClass>
        <arguments></arguments>
      </configuration>
    </executions>
  </executions>
</plugin> [/code]
2016/02/18 14:35 2016/02/18 14:35
Trackback Address:이 글에는 트랙백을 보낼 수 없습니다

Apache Kafka 0.9.0.0 변경점

아파치 카프카 0.9.0.0 는 이전 버전 대비 인증, SSL레이어 추가 등 많은 변화가 있었습니다.
kafka topic 중심의 변경점은 다음과 같음.

변경
  • 더 이상 Java 1.6 지원하지않음.
  • 더 이상 Scala 2.9 지원하지 않음.
  • 1000이상의 Broker ID는 자동으로 Broker ID를 할당하기 위해 예약되어 있음. 이미 1000 이상의 Broker ID를 사용하고 있다면 Broker Configuration의 reserved.broker.max.id 값을 임계값 이상으로 설정 해야 함.
  • replica.lag.max.messages 설정은 제거되었으며, 동기화 되는 복제본 결정할 때 파티션 리더는 더 이상 후행(lag) 메시지의 수를 고려 하지 않음.
  • replica.lag.time.max.ms 설정은 마지막 복제 요청으로 경과한 시간이 아닌 복제가 마지막으로 이뤄진 시간까지를 포함. 복제는 여전히 리더로부터 패치하며  replica.lag.time.max.ms 시간내에 복제가 되지 않으면 sync가 어긋난것으로 간주.
  • log.cleaner.enable 는 true 가 기본값이 됨. 이는 cleanup.policy=compact 을 설정 시 topic은 기본적으로 compact 적용, 128MB 힙사이즈가 log.cleaner.dedupe.buffer.size 설정을 통한 clear process에게 할당. compacted topics의 사용량에 따라 log.cleaner.dedupe.buffer.size 와 다른 log.cleaner 계열의 설정을 조정해야 할 수 있음.
  • MirrorMaker는 더 이상 multiple target clusters를 지원하지 않음. 그 결과로 단일 consumer.config 설정만 허용되며, 다중 소스 클러스터를 미러링 하려면 각각의 consumer configuration의 소스 클러스터당 적어도 하나 이상의 MirrorMaker 인스턴스가 필요함.
  • org.apache.kafka.clients.tools.* 는 org.apache.kafka.tools.* 로 이관. 
  • kafka-run-class.sh 내의 JVM 성능 옵션 (KAFKA_JVM_PERFORMANCE_OPTS) 변경.
  • kafka-topics.sh 스크립트 ( kafka.admin.TopicCommand )는 이제 실행 실패 시 0이 아닌 종료 코드로 종료.
  • kafka-topics.sh 스크립트 ( kafka.admin.TopicCommand )는 이제 '.' 이나 '_' 등 topic 이름이 충돌할 수 있는 경우 경고 메세지를 출력.
  • kafka-console-producer.sh 스크립트 ( kafka.tools.ConsoleProducer )는 기본값으로 이전 생산자 대신 새 프로듀서 를 사용,  기존의 프로듀서를 사용하려면  이전버전의 producer 사용을 명시 해야 함.
  • 기본적으로 명령줄 메세지는 stderr를 통해 출력.


삭제
  • kafka-topics.sh script (kafka.admin.TopicCommand)를 통한 topic 변경 명령은 deprecate됨. 앞으로는 kafka-configs.sh script (kafka.admin.ConfigCommand)를 사용할 것.
  • 오프셋을 확인용 명령 kafka-consumer-offset-checker.sh (kafka.tools.ConsumerOffsetChecker) 역시 deprecate됨. kafka-consumer-groups.sh (kafka.admin.ConsumerGroupCommand)를 사용 할 것.
  • kafka.tools.ProducerPerformance 클래스 deprecate 됨. org.apache.kafka.tools.ProducerPerformance 클래스를 사용 할 것. kafka-producer-perf-test.sh 역시 새로운 클래스 사용으로 변경 됨.
2016/02/03 09:53 2016/02/03 09:53
Trackback Address:이 글에는 트랙백을 보낼 수 없습니다

ASP.NET 5을 ASP.NET Core 1.0로 리네이밍

네이밍은 정말 중요하죠... 어렵기도하고..
There are only two hard things in Computer Science: cache invalidation and naming things. - Phil Karlton
그런의미에서 보면 ASP.NET 5는 완전히 잘못된 네이밍이었습니다.
사람들이 4.6의 개선되거나 새로운 상위 버전 같은 느낌을 갖게 만들거든요.. 
그래서 MS는 이 ASP.NET 5 를 개명했습니다.

ASP.NET Core 1.0 과 .NET Core 1.0 으로 말이죠...
  • ASP.NET 5 는 ASP.NET Cpre 1.0 으로 변경
  • .NET Core 5는 .NET Core 1.0 으로 변경
  • Entity Framework 7는 이제 Entity Framework Core 1.0 혹은, 줄여서 EF Core 1.0

왜 1.0 이냐구요? 왜냐하면 완전히 새로운 .net core 컨셉이라는 점에서 그렇다는군요..  The .NET Core 1.0 CLI 는 완전히 새로운 프로덕트구요. 
server side graphics library 같은 것들 등 몇몇 부분의 차이로 .Net Core는 .Net Framework 4.6과 완전히 동일하지는 않다고하네요.. 
ASP.NET 4.6과 ASP.NET Core 1.0 관계

출처 http://www.hanselman.com/


그럼 어떤걸 사용해야하는지...?
실제로 ASP.Net 4.6 쪽이 더 성숙한 플랫폼입니다.ASP.NET Core 1.0 는 이제 1.0을 릴리즈 했을 뿐이고 아직 SignalR 이나 Web Pages와 같은 기능은 없죠...  아직 VB 나 F#으로 작성할수도 없구요.. 언젠간 지원이되겠지만 지금은 아니예요.

ASP.NET Core 1.0은 이제 막 시작되었을 뿐입니다. 물론, ASP.NET 4.6의 출시와 지원은 계속될거구요.
2016/01/22 14:24 2016/01/22 14:24
Trackback Address:이 글에는 트랙백을 보낼 수 없습니다

CSS는 대략 이런 느낌?

데햇..
CSS는 넘 어려워 T.T
2016/01/21 21:40 2016/01/21 21:40
Trackback Address:이 글에는 트랙백을 보낼 수 없습니다

하악 하악... Code-a-Pillar

피셔프라이스에서 올 가을 출시될 유아용 완구. 영유아를 대상으로 논리/인지력 향상을 위해 고안됐다는데.. 왜 내가 갖고 싶지?

출처 : http://mashable.com/


Code-a-Pillar
2016/01/21 13:05 2016/01/21 13:05
Trackback Address:이 글에는 트랙백을 보낼 수 없습니다

Gson ExclusionStrategy 를 활용한 annotation기반 json 출력 제외

2015/12/18 10:53

서비 JAVA ,

Json Object Mapper로 널리 쓰이고 있는 Gson,
객체를 Json으로 변환하는데 클래스의 특정 필드를 출력하지 않고 싶을 때 사용 가능한 방법.
아쉽게 Gson은 필드를 json으로 출력하라는 @Expose 어노테이션만 존재할 뿐 제외하라는 @Exclude 같은 어노테이션은 아직까진 없다.

그러니까 현재까지는 어떤 필드 출력을 제외하고 싶으면 출력할 모든 필드에 @Expose 어노테이션을 적용 하고 Gson create 시에 다음처럼 @Expose 어노테이션이 없는 필드를 제외하라.. 고 설정할 필요가 있다.
[code java]
Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
[/code]

그런데 대부분의 필드를 출력하고 일부만 제외하고 싶을 경우가 더 많을것 같은데.. 그 몇개 제외하자고 모든 필드에 @Expose 붙이는것도 귀찮은 일.
간단하게 @NonExpose 라든가 @Exlude 같은 어노테이션을 제공해주면 편할텐데... 우리가 생각치 못한 어떤 이유가 있겠지...

다행히 Gson 생성시 제외 조건을 설정할 수 있는 .setExclusionStrategies() 메소드를 제공한다. 그러면 다음처럼 @Exclude 어노테이션 기반 출력 제외 처리가 가능하다.

우선, 우리가 사용할 어노테이션을 정의하고...
[code java]
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Exclude {
}
[/code]

feildSkip 확인 코드에서 위에서 작성한 어노테이션이 있으면 제외처리되도록 Gson의 ExclusionStrategy 구현체를 작성 
[code java]
public class AnnotationBasedExclusionStrategy implements ExclusionStrategy {
    @Override
    public boolean shouldSkipField(FieldAttributes f) {
        return f.getAnnotation(Exclude.class) != null;
    }
    @Override
    public boolean shouldSkipClass(Class<?> clazz) {
        return false;
    }
}
[/code]

마지막으로 Gson 객체 생성 시 위 AnnotationExclusionStrategy 를 적용.
[code java]
new GsonBuilder().setExclusionStrategies(new AnnotationBasedExclusionStrategy()).create();
[/code]
이제 제외하고 싶은 필드에 @Exclude 적용하면 끝~.
2015/12/18 10:53 2015/12/18 10:53
Trackback Address:이 글에는 트랙백을 보낼 수 없습니다
  1. 저는 GSon 을 사용안하고 Jackson을 주로 사용하는 편입니다.
    exclude,include 관련 옵션은 Jackson이 더 편한것 같네요.

    하지만 아무튼 가급적 exclude를 지정하기 보다는 expose(include)를 지정하는 것이 올바를 것 같습니다. 그래서 GSon도 exclude관련 기본 옵션을 두지 않은 것은 아닌가 싶습니다.

    white list 방식(expose할 필드를 명시적으로 지정하는 방식)의 처리를 하는게 맞다고 보는 것이지요.

    black list 방식(exclude를 설정하는 방식)으로 하게되면 신규 필드가 추가되었는데 그게 보안상 노출되면 안되는 필드라고 했을 때 해당 필드를 추가한 개발자가 명시적으로 exclusion을 하지 않으면 의도치 않게 그것을 외부에 노출시켜버리게 됩니다.

    더불어 다른 문제도 있는데 객체가 양방향 관계를 맺은 상태일 때입니다. 양방향 관계를 맺은 필드를 추가한 상황에서 exclusion을 잊어버리고 표시하지 않으면 실행시점이 될 때까지 오류 상황 파악을 못하고 stackoverflow를 만나게 됩니다.

    내부 API용이라면 그런대로 괜찮겠지만 특히나 아무나 데이터를 볼 수 있는 web browser와 통신하는 경우에는 white list 방식을 사용하는게 조금 귀찮더라도 보안성 더 좋을 것 같습니다.

  2. Blog Icon
    서비

    권남님 의견 감사합니다.
    말씀 주신 의도가 맞는것 같습니다.GSon 개발팀은 보안과 편의 중 보안 쪽 손을 들었을듯 합니다.... 댓글 달면서 갑자기 궁금해졌는데 @Expose 와 ExclusionStrategy를 동시에 적용하면 어느 쪽이 적용될까요? 구글 개발팀의 원래 의도대로라면 비노출 되어야 할것 같은데... 한번 확인해봐야 겠네요.

TimerTask와 ExecutorService의 차이점

  • Timer 는 시스템 시간에 의존적임. TimerTask 동작 후 시스템 시간을 변경하면 task 동작 주기도 변경된다는 말. ScheduledThreadPoolExecutor은 그렇지 않음.
  • Timer는 해당 VM안에서 하나의 실행 쓰레드만 가짐. 이런 이유로 긴 시간동안 수행되는 task는 다른 task를 지연시킬 가능성 있음. ScheduledThreadPoolExecutor은 동시 수행될 쓰레드 수를 조절가능하며, 원한다면 ThreadFactory를 통해서, 생성된 쓰레드에 대한 제어권을 획득할 수 있음.
  • TimerTask에서 Runtime exception이 발생하면 그 하나 뿐인 쓰레드가 죽어버림. 고로 모든 나머지 스케줄링된 task들은 더 이상 수행되지 않음.. ScheduledThreadPoolExecutor는 runtime exception을 catch할 뿐만 아니라 ThreadPoolExecutor의 afterExecute 오버라이딩을 통해 익셉션을 핸들링할 수도 있으며 익셉션이 발생한 task는 cancel되지만 나머지 task는 계속 수행 됨.


ScheduledThreadPoolExecutor를 사용할 수 있는 환경이라면 Timer 대신에 ScheduledThreadPoolExecutor를 써라.
[code java]
java.util.concurrent.Executors.newScheduledThreadPool(8);
[/code]

ScheduledThreadPoolExecutor는 java5에 도입되었지만 java1.4 이전 환경을 위한 backport 을 통해
http://backport-jsr166.sourceforge.net/  ScheduledThreadExecutor 클래스를 제공함.

2015/12/10 13:06 2015/12/10 13:06
Trackback Address:이 글에는 트랙백을 보낼 수 없습니다

java 8 DateTime

With Java < 8, you would need to write something like:

Calendar cal = Calendar.getInstance();
cal.set(Calendar.HOUR, cal.get(Calendar.HOUR) + 2);

vs. with Java 8:

LocalTime now = LocalTime.now();
LocalTime later = now.plus(2, HOURS);

The improvements are essentially on

  • readability:
    • Calendar.getInstance() is not very well named: it is hard to tell which instance you are getting without reading the Javadoc. LocalTime.now() is quite self-describing: you get a time and it is now.
    • To offset a date, you call an offsetting method (plus) whereas with the Calendar API, you have to manually change the fields of the object (in this example, the hour) which is error prone.
  • ease of use (see for example the table towards the bottom of this page for a comparison):
    • the Calendar API is complicated to use because it mixes concepts, such as a simple date (June 26th 2015) and an instant in time (June 26th 2015 at 10am UTC) - there isn't a class for the former concept
    • The new Time API has a clear separation between the various date/time concepts
  • safety:
    • The Calendar API is not safe: nothing prevents you from writing cal.set(123, 2) which would throw a not-so-helpful ArrayOutOfBoundsException. The new API uses enums which solves that problem.
    • The new API uses immutable objects, which makes it thread safe.

Overall, the new API is significantly inspired from jodatime which has been the preferred Java Date API for quite some time now. You can also read this detailed comparison of Java (<1.8) date vs. JodaTime (most of it should apply to the Java 8 Date API).

참고 : http://www.oracle.com/technetwork/articles/java/jf14-date-time-2125367.html

2015/10/28 10:05 2015/10/28 10:05
Trackback Address:이 글에는 트랙백을 보낼 수 없습니다

crontab 설정

필요할때마다 검색하기 구찮아서 정리해 둠

crontab [ -e | -l | -r | -v | File ]
-e : 수정 -l : 내용보기 -r : 삭제 -v : 작업상태보기

>crontab -e #주석 15 3 * * *      /home/koglo.com/public_html/system/autoftp.sh              >       /dev/null 2>&1
minute(분) hour(시) day_of_month(일) month(월) weekday(요일) command(명령)
minute(분) 0 - 59 hour(시) 0 - 23 day_of_month(일) 1 - 31 month(월) 1 - 12 weekday(요일) 일요일부터 금요일까지를 나타내는 0 - 6 command(명령) 쉘 명령

> /dev/null 표준출력내용을 null로보낸다
명령 지정 cron 데몬은 선택된 날짜와 시간의 여섯 번째 필드에 지정된 명령을 수행합니다. 
여섯 번째 필드에 %(퍼센트 기호)를 포함하면, cron 데몬은 그 앞에 오는 모든 것을 명령 호출로서 취급하고, 퍼센트 기호(\%)를 탈출하지 않는 한 그 뒤에 오는 모든 것을 표준 입력에 사용 가능하게 만듭니다. 공백 행과 첫번째 비공백 문자가 번호 기호(#)인 행을 무시됩니다.

예제 
mycronjobs이라는 파일을 /var/admn/cron/crontabs 디렉토리에 복사하려면, 다음과 같이 입력하십시오. 
crontab mycronjobs 

매 시간마다 콘솔에 시간을 기록하려면, 다음과 같이 입력하십시오. 
0 * * * * echo The hour is `date` . >/dev/console 

모든 월요일, 수요일 및 금요일 오전 6시 30분에 calendar 명령을 수행하려면, 다음과 같이 입력하십시오. 
30 6 * * 1,3,5 /usr/bin/calendar 

일년 내내 매일 6시 30분에 calendar 명령을 수행하려면, 다음과 같이 입력하십시오. 
30 6 * * * /usr/bin/calendar 

8월 동안 매일 자정에 maintenance라는 스크립트를 수행하려면, 다음과 같이 입력하십시오. 
0 0 * 8 * /u/harry/bin/maintenance 

명령에 대한 표준 입력에 대해 텍스트를 정의하려면, 다음과 같이 입력하십시오. 
0 16 * 12 5 /usr/sbin/wall%HAPPY HOLIDAY!%Remember to turn in your time card. 

%(퍼센트 기호) 뒤에 오는 텍스트가 다음과 같이 wall 명령에 대한 표준 입력을 정의합니다. 
HAPPY HOLIDAY! Remember to turn in your time card. 
파일 
/var/adm/cron/FIFO crontab 또는 at 명령으로 새 작업이 제출될 때 cron 디먼으로 메세지를 전송하는 명명된 파이프. 
/var/spool/cron/crontabs crontab 스풀 영역을 지정합니다. 
/var/adm/cron/cron.allow  crontab 명령에 대한 액세스가 허용되는 사용자 리스트를 지정합니다. 
/var/adm/cron/cron.deny crontab 명령에 대한 액세스가 거부되는 사용자 리스트를 지정합니다

0,30 * 1,15 * 1-5 fsck -y 
상기의 예에서 보면 매달 1일과 15일날 30분마다 요일에 관계없이 fsck -y명령을 실행하도록 되어 있다.
(위의 예는 있을 수 없는 상황이겠지만 예로 들어본다.) 이러한 방식으로 명령어필드에 여러가지의 내용을 넣을 수 있다.

20 1 * * * root find /tmp -atime +3 -exec rm -f () ';' 
매일 새벽 1시 20분에 3일간 접근하지 않은 /tmp내의 파일을 삭제하는 내용 

0 4 * * * root find / -xdev -name core +7 -exec rm -f {}';' 
매일 새벽 4시 정각에 지난 일주일간 접근하지 않은 core파일들을 삭제한다. -xdev옵션은 독립형시스템이면 사용하지 않아도 된다.

수정하는 방법은 리눅스의 경우에는 crontab -e명령으로 에디팅을 할 수 있다. 
수정하기 전에 지금 등록된 crontab을 보고자 하면 -l옵션을 사용하고 등록된 내용을 삭제하려면 -d옵션을 사용하면 된다.(-r옵션을 사용하는 유닉스도 있다.) 
실행결과는 메일로 알려주게 되므로 자신의 메일을 읽어보면 된다.
CRON 메일 전송 차단을 원하면
MAILTO=""

crontab을 직접 고칠 수 없는 상황이면 crontab을 -l옵션을 사용하여 새로운 화일명으로 리다이렉션 받아서 편집한 뒤에 crontab filename의 형태로 등록하면 된다. 
리눅스의 경우에는 /var/spool/cron/crontabs/에 자신의 계정을 화일명으로 crontab이 존재하고 있다.
2015/10/23 09:37 2015/10/23 09:37
Trackback Address:이 글에는 트랙백을 보낼 수 없습니다

메이븐 레파지토리에 없는 jar 추가하고 manifest 에 기술하기

IBM DB2를 가지고 개발할 일이 생겼다.
늘 하던대로 executeable-jar 형식으로 빌딩을 수행하는 메이븐 프로젝트를 구성하는데.. 으잉? DB2의 jdbc 드라이버 db2jcc4.jar가 메이븐센트럴에 등록되어있지 않는것이다. 뭐지.. 이건?
회사 넥서스에 등록하고 땡겨쓰면 되긴 하지만 넥서스같은 로컬 메이븐 레파지토리가 없는 환경에서 이런경우는 어떻게 해야하는지 궁금했다.
귀찮겠지만 빌드하고 라이브러리 폴더에 jar를 복사해도 되긴하지만 문제는 POM파일에 기술되지 않으면 dependancy 관리가 안된다는것. 곧 IDE에서 클래스를 참조할 수 없다는 치명적인 문제가 발생한다.

이럴때를 대비해 메이븐은 dependencies.dependency.scope 속성 값으로 system을 제공하고 있고 <scope>system</scope> 를 사용한 경우 추가로 systemPath 속성을 정의할 수 있다.

이렇게 말이지..
[code xml]     <dependencies>
        <dependency>
            <groupId>ibm.db2</groupId>
            <artifactId>db2jcc4</artifactId>
            <version>10.5</version>
            <scope>system</scope>
            <systemPath>${basedir}/lib/db2jcc4.jar</systemPath>
        </dependency>
    </dependencies>
[/code]
이제, 코드를 짤 수 있고 라이브러리 참조도 된다. 그런데.. 문제가 하나 있는데 <scope>system</scope> 속성은 메이븐 빌드 시 <scope>provided</scope> 와 마찬가지로 라이브러리를 빌드 결과물에 포함시키기 않는다.
이 현상은 maven-dependency-plugin의 configuration 조정을 통해 우회할 수 있다.
plugins.plugin.executions.execution.configuration.includeScope 속성을 추가하고 그 값으로 system 을 설정한다.
[code xml]             <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <version>2.10</version>
                <executions>
                    <execution>
                        <id>copy-dependencies</id>
                        <phase>package</phase>
                        <goals>
                            <goal>copy-dependencies</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>${project.build.directory}/lib</outputDirectory>
                            <overWriteReleases>false</overWriteReleases>
                            <overWriteSnapshots>false</overWriteSnapshots>
                            <overWriteIfNewer>true</overWriteIfNewer>
                            <includeScope>system</includeScope>
                        </configuration>
                    </execution>
                </executions>
            </plugin>[/code]
이제 빌드를 수행하면 system 스코프로 설정된 db2jcc4.jar 가 /lib/db2jcc4-10.5.jar 란 이름으로 이쁘게 들어와 있는걸 확인할 수 있다.

나는 이 프로그램을 executeable-jar 로 뽑을거라서 maven-jar-plugin 을 사용한다. 그런데 빌드 후 jar 내부의 MENIFEST 파일을 까 보니 Class-Path 정의에 db2jcc4-10.5.jar 가 기술되어 있지 않다... maven-dependency-plugin을 통해 복사까진 어떻게 처리했는데 메니페스트를 만드는건 maven-jar-plugin 역할이니 당연한건지도 모르겠다.
이제 마지막으로 /lib/db2jcc4-10.5.jar 에 들어온 jar를 메니페스트의 class path에 꾸겨넣어보자.
 plugin.configuration.archive.manifestEntries.Class-Path 속성을 사용했다.

[code xml]    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-jar-plugin</artifactId>
        <version>2.6</version>
        <configuration>
            <archive>
                <manifest>
                    <addClasspath>true</addClasspath>
                    <addExtensions>true</addExtensions> 
                    <mainClass>com.usbcopy.repository.DBProcess</mainClass>
                    <classpathPrefix>lib/</classpathPrefix>                            
                </manifest>
                <!--MANIFEST  with systemPath-->
                <manifestEntries>
                    <Class-Path>lib/db2jcc4-10.5.jar</Class-Path>
                </manifestEntries>
            </archive>
        </configuration>
    </plugin>[/code]
이제 빌드하고 java -jar app.jar 로 실행도 잘 된다.
2015/10/22 19:19 2015/10/22 19:19
Trackback Address:이 글에는 트랙백을 보낼 수 없습니다

java json 라이브러리 별 parser 속도 비교.

우선, 테스트 진행한 json 라이브러리 후보군은
  • JSON.simple ( Yidong Fang )
  • GSON ( Google )
  • Jackson ( FasterXML )
  • JSONP  ( Oracle )
과 같음. 상기 라이브러리를 이용한 비교적 큰 사이즈의 json 문서 파싱 속도 벤치마킹 결과를 java 어플리케이션 성능평가/모니터링 도구를 개발하는 Takipi에서 공개.

벤치마크는 190MB 짜리( https://github.com/zeMirco/sf-city-lots-json )와 1KB 짜리( http://www.json-generator.com/ ) JSON 파일을 파싱하는 속도를 측정. AWS c3.large 인스턴스에서 큰 파일은 라이브러리당 10회 씩, 작은 파일은 10,000회 씩 수행한 결과를 정리했다고 함.

큰 파일 파싱
대용량 파일 처리 결과

이미지 출처 http://blog.takipi.com

큰 파일 파싱에는 Jackson과 Json.simple 이 빠르고 Jsonp와 gson이 상대적으로 느린걸로...

가장 빠른 Jakson 성능을 기준으로 나타낸 상대 수치.
Jackson 기준 상대 속도

이미지 출처 http://blog.takipi.com


작은 파일 파싱
작은 파일 파싱

이미지 출처 http://blog.takipi.com

큰 파일 때와는 다른 양상을 보임. 작은 파일일때는 정도의 차이는 있지만 모든 라이브러리들이 측정 때마다 들쑥날쑥한 결과를 보임.  GSON 이 1순위를 가장많이 나타내고 있고 Json.simple 은 1순위가 한번도 없음.

가장 빠른 GSON 성능을 기준으로 나타낸 상대 수치.
Gson 기준 상대 비교

이미지 출처 http://blog.takipi.com


결론.
  • 당신의 개발 환경이 빅데이터처리와 같이 주로 큰사이즈의 JSON을 처리해야한다면 Jackson 을 써라. 대용량 환경에서 GSON은 좋지않은 선택임.
  • 마이크로 서비스와 분산아키텍처 설정등과 같이 작은 용량의 많은 json 파일을 처리하는 환경이라면 GSON을 써라.
  • 대용량과 소용량 모두를 다양하게 처리하는 환경이라면 양쪽에서 2순위 정도를 기록한 JSON.simple 이 좋을수도 있겠다.
2015/10/20 10:19 2015/10/20 10:19
Trackback Address:이 글에는 트랙백을 보낼 수 없습니다

java 객체 직렬화

자바 객체 캐싱에 적용하기위한 자바 객체 직렬화 관련 자료를 찾던 중 괜찮은 자료 발견.
구글의 protocol buffers 명성은 익히 들어 알고 있었는데.. kryo 라는 녀석도 있네.. 
속도도 빠른데 복잡한 객체 직렬화에도 어울린다니 한번 검토해 볼만한 듯.

출처는 http://www.slideshare.net/sunnykwak90/java-serialization-46382579

사용자 삽입 이미지
사용자 삽입 이미지
사용자 삽입 이미지
사용자 삽입 이미지


2015/09/18 09:42 2015/09/18 09:42
Trackback Address:이 글에는 트랙백을 보낼 수 없습니다