해당 에러로그의 원인을 결론부터 말하자면 필요한 key 값이 저장된 파일이 제대로 된 위치에 존재하지 않고 있었다.

 

현재 진행 중인 프로젝트에서 솔라피를 통해 고객들에게 문자를 보내는 유료 서비스를 사용 중이다.

배포 중인 서버에서는 정상 작동하는데 개발용으로 운영 중인 서버들에서 문자가 제대로 전송되지 않고있다는 이슈를 전달받아 디버깅을 진행했다.

 

이전 개발자 분이 작성한 코드라 코드 분석과 함께 먼저 상황을 파악해보니 문자 전송이 되는 api를 스웨거로 실행해보면 api가 정상적으로 요청 성공했다는 response가 오고있지만 문자는 오고 있지 않았다.

 

배포 중인 서버와 문자가 오고 있지 않은 서버들의 코드또한 동일한 코드로 코드상으로 큰 특이점이 보이지 않았다.

코드에서 다른 점이 없다면 solapi내 설정이나 key 값과 같은 설정 값들에 문제가 있을 거라 생각하고 좀더 깊게 로그들과 설정 정보들을 뜯어볼 필요가 생겼다.

 

먼저, 솔라피 사이트에 로그인하여 정보들을 확인해봤는데 별다른 특이사항이 없었다.

 

다음으로, 관련 자료를 찾아보던 중 솔라피에서 제공해주는 java 예시 코드들 깃헙을 발견했고 해당 레포에서 아래 링크의 문자 전송관련 정보를 출력할 수 있는 코드를 발견했다. 

https://github.com/solapi/solapi-java/blob/main/app/src/main/java/solapi/app/SendJsonLMS.java

 

GitHub - solapi/solapi-java: SOLAPI SDK for Java

SOLAPI SDK for Java. Contribute to solapi/solapi-java development by creating an account on GitHub.

github.com

위 링크의 코드를 참고해 아래와 같은 코드를 넣어 디버깅을 해보고자 시도했다.

                    System.out.println("statusCode : " + response.code());
                    System.out.println("groupId : " + body.getGroupId());
                    System.out.println("messageId : " + body.getMessageId());
                    System.out.println("to : " + body.getTo());
                    System.out.println("from : " + body.getFrom());
                    System.out.println("type : " + body.getType());
                    System.out.println("statusCode : " + body.getStatusCode());
                    System.out.println("statusMessage : " + body.getStatusMessage());
                    System.out.println("customFields : " + body.getCustomFields());

로컬에서 돌려 실험해봤는데 해당 정보들에는 문제가 없었다.

 

마지막으로 무중단 서비스를 돌리고 있었기 때문에 nohup.out 파일을 서버에 접속해 로그를 확인해봤다.

 

api 요청의 응답은 api 요청이 성공했다고 오고있었지만 위 파일에서 로그를 보니 문자 전송을 진행하는 과정에서 에러가 발생하고있었다.

에러가 발생한 경우 슬랙과 연동된 웹훅으로 에러 메시지를 받고있는데 해당 에러 로그는 웹훅 메시지가 오고 있지 않았다.

그래서 제일 마지막에 확인하게 된건데 아마 가끔씩 웹훅이 바로 안뜨고 엄청 늦게 연기되다 올때가 있는데 그런 케이스였던 것 같다..

12:31:01.726 ERROR [File:SolapiMessageSender.java] [Func:onResponse] [Line:40] [Message:{"errorCode":"Unauthorized","errorMessage":"권한이 없습니다."}]
12:38
java.io.FileNotFoundException: 파일경로임당 파일명임당 (No such file or directory)

 

해당 로그를 통해 문자 전송에 필요한 특정 파일이 존재하지 않고있다는 걸 알게됐다.

해당 파일은 키값과 같은 중요한 값들이 담긴 파일이었다.

빌드 파일만 서버로 옮겨 서버에서 돌리면 문자가 안오고 있었는데 빌드할 때 프로젝트 내에 해당 파일이 존재하고 있었지만 이와 상관없이 운영중인 서버에 해당 파일이 특정한 위치에 존재해야지만 문자 전송이 정상적으로 이루어지는 케이스였다. 

 

배포 중인 서버에서 문자가 가고있던 건 이전 개발자분이 서버의 관련된 위치에 파일을 넣어두셨었다!

운영 중인 서버의 개발 서버 관련 폴더에 접근해 해당 파일을 정해진 경로에 위치시킨 후 다시 api를 실행해보니 정상작동됐다.

 

필자는 주로 키값을 다룰 때 프로젝트 내에 파일을 위치시키고 깃에서 제외시킨 후 빌드만 해도 바로 사용할 수 있도록 사용하고 있었는데 이전 개발자분은 프로젝트 빌드와 상관없는 위치에 따로 키값 정보가 담긴 파일을 위치시키고 해당 파일의 경로를 통해 값을 가져오는 방식을 사용하신 것 같다. 

이런 방법은 생각해본 없었는데 서버에 접속해서 해당 파일을 열어봐야지만 키값을 확인할 수 있기때문에 조금 더 신경써야되는 부분이 많지만 유출에 있어서 더 안전한 방법인 것 같다!

아래와 같은 443 포트 서버 블록의 root와 index 값을 지정하고 index 파일을 띄우려고 했는데 계속 404 에러가 떴다.

다른 서버 블록들에서는 다 잘 적용됐는데 443 포트를 바라보고 있는 서버 블록만 계속 index가 적용이 안되는 문제가 발생했다.

server {
        # SSL configuration
        listen 443 ssl default_server;
        listen [::]:443 ssl default_server ipv6only=on;
        server_name 도메인명;
        root 루트폴더경로;
        index i.html;

        ssl_certificate 키경로;
        ssl_certificate_key 키경로;
        ssl_prefer_server_ciphers on;
        ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
        
        location / {
          		proxy_pass http://localhost:포트번호;
                proxy_read_timeout 6000;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header Host $http_host;
                }
}

 

구글링을 통해 443 포트 서버 블록의 location = / {} 블록을 통해  root와 index를 다시 지정한 후  해당 도메인명 주소로 들어올 때만 지정된 html 파일을 띄우려고 했는데 계속 404 에러가 발생했다.

다른 서버 블록들에선 다 정상 작동하는데 해당 블록에서만 발생한 문제로 구글링을 통해 계속 여러 방식들을 적용해봤지만 404 에러가 사라지지 않았다.

 

그러다 우연히 root 대신 alias를 사용한 코드를 보게 됐고 해당 문법을 활용해 404 에러를 해결했다.

root는 상대 경로를 사용하고 alias는 절대경로를 사용하는데 alias를 사용했을 때 404 에러가 사라진 걸 보니 기본으로 설정된 상대 경로에서 다시 동일한 위치 상대 경로로 접근을 하려고 해서 잘못된 주소로 접근을 했던게아닌가싶다.

 

server {
        # SSL configuration
        listen 443 ssl default_server;
        listen [::]:443 ssl default_server ipv6only=on;
        server_name 도메인명;
        root 루트폴더경로;
        index i.html;

        ssl_certificate 키경로;
        ssl_certificate_key 키경로;
        ssl_prefer_server_ciphers on;
        ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
 		location = / {
             default_type "text/html"; //해당 코드가 없으면 html이 띄워지지 않고 다운된다고 한다.
             alias 루트폴더경로;
             index i.html;
        }
        location / {
          		proxy_pass http://localhost:포트번호;
                proxy_read_timeout 6000;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header Host $http_host;
                }
}

해당 코드를 통해 404 에러를 없애는 것 까지는 성공했는데 이후 403 에러가 발생했다.

 

관련해서 다시 구글링도 해보고 nginx 에러 로그도 들어가서 보니 권한 문제라고 한다.

해결법을 찾아보니 권한을 가진 nginx 설정 파일의 사용자와 html 파일의 사용자가 동일한지 체크해야하고 그래도 안되면 권한 설정 명령어를 통해 해당 root 폴더의 권한을 변경해줘야한다고 돼있었다. 권한 설정을 바꾸는 건 보안상 좋은 해결법은 아닌 것 같다.

 

띄우고자 하는 html 파일 경로에 들어가서 아래 명령어를 치면 권한이 있는 사용자와 권한 허용영역을 알 수 있다.

ls -al

사용자의 이름이 ubuntu임을 확인한 후 nginx 설정 파일에서 사용자 권한을 찾아봤다.

cd /etc/nginx
vi nginx.conf

해당 설정 파일의 첫줄에 user 사용자명;이라고  적힌 부분을 통해 권한이 있는 사용자명을 확인할 수 있는데 html 파일 권한자의 이름과 일치 하지 않았다.

nginx.conf 파일에서 사용자명을 html 파일의 사용자명으로 바꾼후 저장을 하고 nginx를 재시작하면 해결된다고 한다.

다른 서버 블록들은 알아서 잘 띄어지던데 해당 서버 블록만 왜 적용이 안됐던 건지 nginx 설정에 관해 더 공부를 해보면 좋을 것 같다.

 

참고 사이트

https://mik-a.com/m/88

https://ganbarujoy.tistory.com/113

https://bing-su-b.tistory.com/7

진행 중인 프로젝트 서버가 내일 더 큰 서버로 확장 이전되며 S3 관련 설정을 바꿔주는 작업이 필요해졌다!

내일 관련 회의에 참여해 작업을 진행하기 전에 미리 S3에 관해 정리를 해가면 좋을 것 같아 공부할 겸 포스팅을 하게됐다.

 

Amazon Simple Storage Service(Amazon S3)란?

AWS에서 제공하는 파일 서버의 역할을 하는 객체 스토리지 서비스

일반적인 파일 서버는 트래픽이 증가함에 따라 장비를 증설하는 작업이 필요한데 S3가 이를 대행해줘 트래픽에 따른 시스템적인 문제를 해결해준다.

Amazon Simple Storage Service(Amazon S3) 특징

-원하는 양의 데이터를 저장, 검색, 삭제 가능

-내구성과 확장성이 뛰어나며 사용한 스토리지 용량만큼 요금이 청구

-저장할 수 있는 파일 수의 제한 x

-최소 1바이트에서 최대 5TB의 데이터를 저장하고 서비스 제공 가능

-파일에 인증을 붙여서 무단으로 엑세스 하지 못하도록 가능

-정보의 중요도에 따라서 보호 수준을 차등화 가능

-버킷(bucket)과 키(key)로 구성

Amazon Simple Storage Service(Amazon S3) 사용 용어

객체

저장된 데이터(파일) 하나 하나를 객체라고 명명

버킷

연관된 객체들을 그룹핑한 최상위 디렉토리

버킷 단위로 지역 지정 가능

버킷에 포함된 모든 객체에 대해 일괄적으로 인증 및 접속 제한 가능

버킷 내 객체의 고유한 식별자

버킷 내 모든 객체는 고유한 키를 가짐

버전 관리

저장된 객체들의 변화를 저장

RSS(Reduced Redundancy Storage)

객체에 비해 데이터가 손실될 확률이 높은 형태의 저장 방식

대신 가격 저렴

복원이 가능한 데이터를 저장하는데 적합

 

Amazon Simple Storage Service(Amazon S3) 간략한 사용 흐름

AWS S3 페이지에서 버킷 생성

버킷 생성시 원하는 지역 설정

버킷 퍼블릭 액세스 설정

버킷에 객체(파일) 업로드

업로드시 권한 관련 설정 진행

업로드 후 다운로드 가능

ec2에서 s3 접근 위해서는 IAM 역할 설정 필요

 

참고 사이트

https://aws.amazon.com/ko/s3/

https://seoyeonhwng.medium.com/aws-s3%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80-b0da502b0504

https://dev.classmethod.jp/articles/for-beginner-s3-explanation/

https://vlee.kr/4765

 

알고리즘 스터디에서 발표를 맡은 문제 중 다른 코드를 참고해 좀 특이한 방식으로 sort 함수 비교 부분을 커스터마이징한 코드가 있었다. 필자도 이번에 처음 본 작성 방식이라 정리해두면 좋을 것 같아 포스팅을 하게됐다.

 

기본 세팅

#include <algorithm>

 

위 헤더파일을 include해야 sort 함수를 사용할 수 있다.

 

compare 커스터마이징 사용안할 시 sort 함수 

vector <int> v(6,6);
sort(v.begin(),v.end());

첫  번째 인자로 정렬을 시작할 부분의 iterator 혹은 포인터를 넣어주고

두 번째 인자로 정렬의 끝인 부분의 iterator 혹은 포인터를 넣어주면 된다.

참고로, 알고리즘 헤더파일을 삽입해 사용하는 sort 함수는 quick sort와 insertion sort를 섞은 intro sort를 사용한다.

해당 함수의 시간 복잡도는 평균 nlogn, 최악일 경우 n^2이다.

 

compare 커스터마이징 사용할 시 sort 함수 type 1

vector <int> v(6,6);
 
bool compare(int i, int j) { return i > j; }
 
sort(v.begin(),v.end(),compare);

첫 번째 인자와 두 번째 인자는 동일하며 세 번째 인자로 커스터마이징한 compare 함수를 넣어준다.

함수명이 compare일 필요는 없다. 

예시로 적은 위 코드는 내림차순으로 정렬되는 코드이다.

 

compare 커스터마이징 사용할 시 sort 함수 type 2

이 포스팅을 하게 된 계기는 아래 코드인데 배열 하나에서의 값들만 비교해서 정렬을 해주는 것이 아니라

다른 배열의 값을 기준으로 정렬을 할 수도 있다.

또한, 함수를 따로 분리하여 작성하지 않아도 sort 함수 내에서 아래와 같이 작성가능하다.

이때 주의해야되는 점이 c++14 이상 버전에서만 문제 없이 빌드된다.

vector<int> a[10000000];
vector<int> order(n);

for (int i=0; i<n; i++) {
        sort(a[i].begin(), a[i].end(), [&](const int &k, const int &l) {
            return order[k] < order[l];
        });
   }

 

 

참고 사이트 

https://yeondube.tistory.com/entry/sort-%ED%95%A8%EC%88%98-%EC%BB%A4%EC%8A%A4%ED%84%B0%EB%A7%88%EC%9D%B4%EC%A7%95

https://charles098.tistory.com/130

https://seongjuk.tistory.com/entry/c-sort-%ED%95%A8%EC%88%98-%EC%BB%A4%EC%8A%A4%ED%85%80-%ED%8A%B9%EC%A0%95-%EC%A1%B0%EA%B1%B4-%EC%A0%95%EB%A0%AC-%EC%9A%B0%EC%84%A0%EC%88%9C%EC%9C%84-%ED%81%90-%EC%A0%95%EB%A0%AC

https://leeeegun.tistory.com/5

'프로그래밍 언어 > c++' 카테고리의 다른 글

[Algorithm] c++ 순열 구하는 함수 next_permutation, prev_permutation  (0) 2022.05.04
[STL] 덱(Deque)  (0) 2021.10.12
[STL] 큐(Queue)  (0) 2021.10.11
[STL] vector 컨테이너  (0) 2021.09.19
STL  (0) 2021.09.19

로컬 커맨드 창에서 스프링 프로젝트 빌드 시 발생한 오류다.

현재 진행 중인 프로젝트의 jdk가 8버전인데 자바 버전과 롬복 버전이 호환되지 않아 발생하는 문제라고한다.

이전에는 빌드가 잘 됐었는데 이틀 전에 깃 공부를 하면서 로컬 커맨드 창에서 새로 프로그램을 깔고 업데이트 작업을 좀 했는데 그때 롬복 버전이 업그레이드 되면서 발생한 문제 같다.

해결 방법은 아래와 같다.

기존 build.gradle

dependencies{
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
}

수정한 build.gradle

dependencies{
    compileOnly 'org.projectlombok:lombok:1.18.20'
    annotationProcessor 'org.projectlombok:lombok:1.18.20'
}

버전을 직접 명시해줬다.

 

참고 사이트

https://jin2rang.tistory.com/entry/javalangIllegalAccessError-class-lombokjavacaptLombokProcessor-in-unnamed-module-0x2fbb01ba-cannot-access-class-comsuntoolsjavacprocessingJavacProcessingEnvironment-in-module-jdkcompiler

 

개발 환경

스프링 부트  2.4.0 + ubuntu 20.04+ nginx1.18.0



진행 중인 프로젝트에서 기존 로그인 로직 수정 요청이 들어오며 추가로 다날 SMS 본인인증 처리가 필요해졌다.

서브 도메인을 연결하여 SMS 인증 페이지를 띄워 웹뷰를 통해 클라이언트 분들이 사용할 수 있도록 해야했고 보안을 위해 access 토큰을 서버사이드에서 받아 클라이언트로 보낼 수 있도록 관련 API를 개발했다.

다날 아임포트사에서 코드 예시와 함께 가이드 라인을 제공해주고 있는데 다 node.js다..깃헙 레포를 통해 java 사용자들도 사용할 수 있도록 maven 플러그인을 제공해주고 있으니 참고하면 좋을 것 같다. 필자는 gradle을 사용하고 있기도하고 제공해주는 모듈에서 안쓰는 기능들이 많아 간단하게 코드로 직접 만들어서 개발했다.
https://github.com/iamport/iamport-rest-client-java

 

GitHub - iamport/iamport-rest-client-java: JAVA사용자를 위한 아임포트 REST API 연동 모듈입니다

JAVA사용자를 위한 아임포트 REST API 연동 모듈입니다. Contribute to iamport/iamport-rest-client-java development by creating an account on GitHub.

github.com

 

<서브 도메인으로 본인인증 페이지 띄우기>

다날 SMS 본인 인증 페이지를 띄울 수 있는 html 파일의 코드는 아래와 같다.

<!DOCTYPE html>
<html>
<head>
<!-- jQuery -->
<script type="text/javascript" src="https://code.jquery.com/jquery-1.12.4.min.js" ></script>
<!-- iamport.payment.js -->
<script type="text/javascript" src="https://cdn.iamport.kr/js/iamport.payment-1.1.4.js"></script>
</head>

<body>
<script type="text/javascript">
IMP.init('가맹점식별코드를입력해주세요.');
IMP.certification({
merchant_uid : 'merchant_' + new Date().getTime() //본인인증과 연관된 가맹점 내부 주문번호가 있다면 넘겨주세요
}, function(rsp) {
if ( rsp.success ) {
    // 인증성공
    console.log(rsp.imp_uid);
    console.log(rsp.merchant_uid);

$.ajax({
    type : 'POST',
    url : '/certifications/confirm',
    dataType : 'json',
    data : {
    imp_uid : rsp.imp_uid
    }
    }).done(function(rsp) {
    // 이후 Business Logic 처리하시면 됩니다.
    });

} else {
    // 인증취소 또는 인증실패
    var msg = '인증에 실패하였습니다.';
    msg += '에러내용 : ' + rsp.error_msg;

    alert(msg);
    }
});

</script>
</body>
</html>

가맹점 식별 코드를 적어 해당 html 파일을 만들었다면 운영 중인 서버의 원하는 위치에 파일을 위치시킨다.


다음으로 사용 중인 도메인에 서브 도메인을 하나 새로 파자!

필자가 참여 중인 프로젝트는 가비아라는 도메인 판매 사이트를 이용하고 있어 해당 사이트에서 서브 도메인을 추가해줬다.

각자의 상황에 맞는 도메인 판매 사이트에서 서브 도메인 관련 설정을 맞췄다면 ec2 nginx의 설정을 변경해 서브 도메인을 연결해줘야한다.

위 내용은 이전 포스팅에서 다룬 적 있어 해당 포스팅 링크로 대체!

https://cofls6581.tistory.com/64

 

[서브 도메인] 서브도메인 적용 및 프로젝트 폴더 분리 (가비아)

개발용 서버와 배포용 서버를 분리하기 위해 서브 도메인을 적용해보자. 개발용 주소는 dev.도메인 주소로 설정하고 배포용 주소는 prod.도메인 주소를 사용하겠다. 이름은 취사 선택의 문제로 원

cofls6581.tistory.com

root에는 위에서 만든 html 파일이 존재하는 파일 경로를 써주고 index에는 html 파일의 이름을 적어주면 된다.

이후 연결한 서브 도메인으로 접속하면 본인 인증 페이지가 잘 뜸을 확인할 수 있다.

만약, 이때 제대로 뜨지 않는다면 포트 번호와 서버의 인바운드 규칙 등을 확인해보자.


<아임포트 access 토큰 전달 API 구축>

컨트롤러단

    
    private final UserService userService;
    
    @ApiOperation(value = "아임포트 access token 발급 API") //스웨거 사용안할 시 생략 가능
    @GetMapping("/iamports/accessToken")
    public BaseResponse<String> getIamportAccessToken() {
        return new BaseResponse<>(userService.getIamportAccessToken());
    }

 

서비스단

    @Transactional
    public String getIamportAccessToken () {
        RestTemplate restTemplate = new RestTemplate();
        String requestUrl = "https://api.iamport.kr/users/getToken";

        Map<String, String> iamportKey = new HashMap();
        iamportKey.put("imp_key", imp_key); //발급받은 REST API key 값을 넣어주세요
        iamportKey.put("imp_secret",imp_secret); //발급받은 REST API secret 값을 넣어주세요.

        ResponseEntity<Object> responseData = restTemplate.postForEntity(requestUrl, iamportKey, Object.class);
        LinkedHashMap responseBody = (LinkedHashMap) responseData.getBody();
        LinkedHashMap responseBodyProps = (LinkedHashMap) responseBody.get("response");
        String accessToken = (String) responseBodyProps.get("access_token");

        return accessToken;
    }

필자는 키 값들을 노출시키지 않기 위해 다른 파일에 적은 후 git ignore에 해당 파일을 추가하여 개발했다.

 

참고 사이트

https://docs.iamport.kr/tech/mobile-authentication

https://blog.naver.com/iamport/221004352427

https://cordingmonster.tistory.com/76

+ Recent posts