분류

2021년 4월 2일 금요일

java runtime exec() 에서 curl post 사용 오류 및 해결

 개요 

오픈 소스 기반 프로그램 수행을 위해 curl 커맨드를 외부 데이터베이스와 연결하여 결과를 받는데 windows 환경에서는 잘 되었지만 linux 기반에 동일한 tomcat과 java 환경에서 수행 시 오류가 발생하였습니다. 오류 메시지는 종류도 다양하지만 주로 아래와 같은 메시지가 나왔습니다. 

보내는 메시지는 다음과 같았습니다. 

String queryTable = "curl -X POST -H \"Content-Type:application/json\" " + " -d  @" + filePath + " " + serverUrl;

형태의 비교적 단순한 문장이었고, 쿼리를 담은 파일을 포함하여 발송합니다. 

여기서 filePath 는 데이터를 전송하기 위해 생성한 데이터 파일의 절대경로입니다.  serverUrl은 전송 대상 서버의 전체 url 입니다. 

옵션 중 -d 뒤의 @ 옵션을 제거하면 파일이 아닌 문자 데이터 형태로 전송할 수 있습니다. 

에러 메시지는 대략 모두 비슷한 유형으로 <h1>이 H4로 바뀌는 정도의 오류였습니다. 

 illegal character varchar='" 

<h1>bad message 400

windows 기반의 tomcat9에서 수도 없이 많은 테스트를 거친 프로그램이기에 믿고 리눅스에 맡겼더니 배신도 이런 배신이 없습니다. 


문제코드 

여러 검색 결과 문제는 java 자체의 Process 클래스에 의한 처리였습니다. 외부 명령을 실행하기 위해 다음과 같은 코드를 작성하여 사용해왔습니다. 

public static String executeCommand(String command) {
StringBuffer output = new StringBuffer();
Process p;
String outString = ""; 
try {
p = Runtime.getRuntime().exec(command);
BufferedReader reader new BufferedReader(new  InputStreamReader(p.getInputStream()));
String line = "";
while ((line reader.readLine()) != null) {
output.append(line + "\n");
}
p.waitFor();
outStringoutput.toString();
            if (p.exitValue() != 0) {
                // shell 실행이 비정상 종료되었을 경우
            BufferedReader errorBufferReader = null
            StringBuffer errorOutput new StringBuffer();
                errorBufferReader new BufferedReader(new InputStreamReader(p.getErrorStream(), "EUC-KR"));
                String msg  = "";
                while ((msg = errorBufferReader.readLine()) != null) {
                    errorOutput .append(msg + System.getProperty("line.separator"));
                }
            }
 
} catch (Exception e) {
logger.error(e.getMessage());
}
return output.toString();
}

상위 스크립트 처럼 java 자체의 process runtime exec를 사용 할 경우 linux나 macos 환경에서 알 수 없는 오류로 고생하게 됩니다. 또한 java 자체의 processbuilder를 활용하더라도 동일한 현상이 나타나는 것을 확인하였습니다. 

여러 문서들을 검토 한 결과 Apache commons common-exec 를 사용할 경우 해당 문제가 해결된다고 합니다. 


https://commons.apache.org/proper/commons-exec/

위 라이브러리를 직접 다운 받아 사용하시면 됩니다. maven 환경이라면 dependency에
아래와 같은 항목을 추가해주세요
pom.xml
<dependency>  
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-exec</artifactId>
    <version>1.3</version>
</dependency>  

해결 코드 

package Controller;
import org.apache.commons.exec.CommandLine;
import org.apache.commons.exec.DefaultExecutor;
import org.apache.commons.exec.PumpStreamHandler;
import org.apache.commons.io.output.ByteArrayOutputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class apCmdLine {

private static final Logger logger = LoggerFactory.getLogger(apCmdLine.class);
 
/**
  * linux 에서 shell script가 비정상 실행되는 오류에 대한 fix 
  * @param command
  * @return
  * @throws Exception
  */

 public String execAndRtnResult(CommandLine command) throws Exception {
      String rtnStr"";
              DefaultExecutor executor new DefaultExecutor();
      ByteArrayOutputStream bs new ByteArrayOutputStream();
      PumpStreamHandler streamHandler = new PumpStreamHandler(bs);
      executor.setStreamHandler(streamHandler);
      int exitCode executor.execute(new CommandLine(command));
      rtnStr = bs .toString();
      logger.info("exitCode : "exitCode);
      logger.debug("outputStr : "rtnStr );
      return rtnStr;
}
}


추가오류 

1. 전송 메시지 출력 

linux 환경에서 정상적으로 데이터 처리가 되고 난 이후 rtnStr로 받은 결과에 위 사진과 같은 메시지가 중간중간에 끼어드는 문제가 발생하였습니다. 
r -   % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
100    90  100    70  100    20  14629   4179 --:--:-- --:--:-- --:--:-- 17500

이 문제를 해결 하기 위해 JSON 스트링이 아닌 데이터에 대한 필터를 고려해보았으나, 라인단위의 중간에 해당 데이터가 끼어드는 경우도 발생하였습니다. 검토 결과 CURL로 데이터를 전송할 경우 전송 결과에 대한 알림이 발생한다고 합니다. 이것 역시 WINDOWS 환경에선 발생하지 않습니다. 

검색에 시간은 제법 들었고 필터도 해보고 여러 삽질을 해봤지만 실제 고치는 방법은 매우 단순했습니다. URL 앞에 -s만 붙여주면 되는 일이었습니다.  

보내는 메시지에 대상 서버 주소 앞에 -s 옵션을 붙여줍니다. -s 는 silent 시스템 메시지를 보내지 마라는 명령어입니다. 
String queryTable "curl -X POST -H \"Content-Type:application/json\" " + " -d  @" + filePath + " -s "queryServer

비슷한 유형을 정리해 놓은 글을 링크합니다. (영문입니다.)

2. 메시지 길이가 길어 스트림을 초과 

이런 경우는 흔치 않을 것 같습니다. json 형태의 데이터를 보내고 그 결과를 받다보니 curl에서 전송할 수 있는 범위를 넘어가더군요. 길이가 길 경우 교환 데이터를 파일 형식으로 보내면 해당 문제는 사라집니다. 하지만 이 방법 역시 문제가 있습니다. json 파일 형태로 보내다보니 파일을 작성한 시점에 동시 트랜잭션이 여러 건 일어나고 서로 다른 데이터를 요구했을 경우 해당 트랜잭션에서 내가 원하는 데이터가 정말 갔다는 것을 보장할 수 없죠. 결국은 runtime exec를 통해 데이터를 교환하는 것 보단 httpconnection을 통해 데이터를 교환하는 것이 적절한 처리 방법으로 생각됩니다. 


이상입니다. 
읽어주셔서 감사합니다. 
읽으신 분에게 도움이 되면 좋겠네요.