[Javascript] [JAVA] html 코드를 엑셀파일 형식으로 저장하기

[Javascript] [JAVA] html 코드를 엑셀파일 형식으로 저장하기

html 코드를 엑셀파일 형식으로 저장하는 방법이다.

html 코드로 테이블(table)을 그려서 엑셀파일로 저장하는 방식이다.

저장 시 파일형식은 xls 확장자다.

1. javascript(js)에서 html 코드를 엑셀파일 저장하기

먼저 자바스크립트에서 html 코드를 엑셀파일로 만드는 방법이다.

크롬 브라우저에서 html 을 띄우고 [실행] 버튼을 클릭하면 strHtml 변수 내용에 해당하는 html 코드가 fileName.xls 형식의 엑셀파일로 저장된다.

참고로 아래 코드는 IE11에서는 동작하지 않고 크롬 브라우저에서 동작한다.

test.html 내용

<!DOCTYPE html>
<html>
<head>
<meta charset=“UTF-8” />
<script>
function saveExcelFile() {
    var strHtml = “”;
    strHtml += “<html>\n”;
    
    strHtml += “<head>\n”;
    strHtml += “<meta http-equiv=\”Content-Type\” content=\”application/vnd.ms-excel; charset=utf-8\”>\n”;
    strHtml += “<meta name=ProgId content=Excel.Sheet>\n”;
    strHtml += “<meta name=Generator content=\”Microsoft Excel 11\”>\n”;
    strHtml += “<style>\n”;
    strHtml += “td, th {\n”;
    strHtml += ” border: 1px solid #000000;\n”;
    strHtml += “}\n”;
    strHtml += “</style>\n”;
    strHtml += “</head>\n”;
    
    strHtml += “<body>\n”;
    strHtml += “<table>\n”;
    strHtml += “<tr>\n”;
    strHtml += “<th style=\”background-color: #EEEEEE;\”>국어</th>\n”;
    strHtml += “<th style=\”background-color: #EEEEEE;\”>영어</th>\n”;
    strHtml += “<th style=\”background-color: #EEEEEE;\”>수학</th>\n”;
    strHtml += “</tr>\n”;
    strHtml += “<tr>\n”;
    strHtml += “<td>100점</td>\n”;
    strHtml += “<td>90점</td>\n”;
    strHtml += “<td>95점</td>\n”;
    strHtml += “</tr>\n”;
    strHtml += “</table>\n”;
    strHtml += “</body>\n”;
    
    strHtml += “</html>”;
    
    var elemA = document.createElement(“a”);
    elemA.href = URL.createObjectURL(new Blob([strHtml], {type: “application/csv;charset=utf-8;”}));
    elemA.download = “fileName.xls”;
    elemA.style.display = “none”;
    
    document.body.appendChild(elemA);
    elemA.click();
    document.body.removeChild(elemA);
}
</script>
</head>
<body>
    <input type=“button” value=“실행” onclick=“saveExcelFile();” />
</body>
</html>

2. JAVA에서 html 코드를 엑셀파일 저장하기

다음으로 자바에서 html 코드를 엑셀파일로 만드는 방법이다.

test.jsp 내용

<%@ page language=“java” pageEncoding=“UTF-8”%><%@
page contentType=“text/html; charset=UTF-8”%><%@
page import=“java.net.URLEncoder”%><%
    request.setCharacterEncoding(“utf-8”);
    response.setContentType(“application/vnd.ms-excel”);
    response.setCharacterEncoding(“utf-8”);
    
    response.setHeader(“Content-Disposition”“attachment; filename=\”” + “fileName.xls” + “\””);
    response.setHeader(“Pragma”“no-cache”);
    response.setHeader(“Cache-Control”“no-cache”);
    
    StringBuilder strHtml = new StringBuilder();
    strHtml.append(“<html>\n”);
    
    strHtml.append(“<head>\n”);
    strHtml.append(“<meta http-equiv=\”Content-Type\” content=\”application/vnd.ms-excel; charset=utf-8\”>\n”);
    strHtml.append(“<meta name=ProgId content=Excel.Sheet>\n”);
    strHtml.append(“<meta name=Generator content=\”Microsoft Excel 11\”>\n”);
    strHtml.append(“<style>\n”);
    strHtml.append(“td, th {\n”);
    strHtml.append(” border: 1px solid #000000;\n”);
    strHtml.append(“}\n”);
    strHtml.append(“</style>\n”);
    strHtml.append(“</head>\n”);
    
    strHtml.append(“<body>\n”);
    strHtml.append(“<table>\n”);
    strHtml.append(“<tr>\n”);
    strHtml.append(“<th style=\”background-color: #EEEEEE;\”>국어</th>\n”);
    strHtml.append(“<th style=\”background-color: #EEEEEE;\”>영어</th>\n”);
    strHtml.append(“<th style=\”background-color: #EEEEEE;\”>수학</th>\n”);
    strHtml.append(“</tr>\n”);
    strHtml.append(“<tr>\n”);
    strHtml.append(“<td>100점</td>\n”);
    strHtml.append(“<td>90점</td>\n”);
    strHtml.append(“<td>95점</td>\n”);
    strHtml.append(“</tr>\n”);
    strHtml.append(“</table>\n”);
    strHtml.append(“</body>\n”);
    
    strHtml.append(“</html>”);
    
    out.print(strHtml.toString());
%>

3. 결과

결과는 아래와 같이 엑셀파일이 만들어진다.

js와 자바의 차이가 있다면 js 코드는 한 줄씩 해석해서 실행하는 인터프리터 언어이므로 html 코드의 양이 많을 경우 그려내는 속도가 다소 느릴 수 있다.

 

4. 제약사항

제약사항으로는 (1) 디자인적 제약사항과 (2) 확장자 제약사항(xlsx 확장자 불가능)이 있다.

예를 들어 테두리 선을 적용할 때 border를 1px solid #000000; 로 그려내도 엑셀에서 직접 적용할 때처럼 얇게 표시되지 않는다.

만약 xls 확장자가 아닌 xlsx 확장자를 사용하고 싶거나, 디테일하게 디자인을 적용하고 싶다면 자바 POI 라이브러리를 찾아보는 것을 권장한다.

[Java] 자바 마우스 이동, 마우스 클릭, 마우스 위치 알아내는 방법

[Java] 자바 마우스 이동, 마우스 클릭, 마우스 위치 알아내는 방법

자바에서 단순 마우스 이동은 Robot 클래스를 이용해서 아래와 같이 작성하면 된다. (import java.awt.Robot;)

Thread.sleep(100); 은 0.1초 대기하는 코드인데 불필요하면 삭제해도 무방하다.

public void moveMouse(int mx, int my) {
    try {
        Robot robot = new Robot();

        robot.mouseMove(mx, my);

        Thread.sleep(100);


    catch (Exception e) {
        e.printStackTrace();

    }
}

자바 마우스 클릭(좌클릭, 왼클릭)은 아래처럼 작성하면 된다.

마우스를 이동시킨 후 클릭(mousePress) 및 클릭해제(mouseRelease)를 차례로 실행하는 방식이다.

public void clickMouse(int mx, int my) {
    try {
        Robot robot = new Robot();

        robot.mouseMove(mx, my);

        Thread.sleep(100);

        robot.mousePress(InputEvent.BUTTON1_MASK);

        Thread.sleep(100);

        robot.mouseRelease(InputEvent.BUTTON1_MASK);

        Thread.sleep(100);    

            
    catch (Exception e) {
        e.printStackTrace();

    }
}

현재 화면상 마우스 위치는 MouseInfo 클래스를 이용하면 알아낼 수 있다. (import java.awt.MouseInfo;)

Point point = MouseInfo.getPointerInfo().getLocation();
int mx = (int) point.getLocation().getX();
int my = (int) point.getLocation().getY();

[Windows] 윈도우 10 한글 자음모음 분리 현상 해결방법

[Windows] 윈도우 10 한글 자음모음 분리 현상 해결방법

한글을 입력할 때 아래처럼 자음, 모음이 분리되어 입력되는 현상이다.

ㅇㅜㅣㄴㄷㅗㅇㅜ 10 ㅎㅏㄴㄱㅡㄹ ㅈㅏㅇㅡㅁ ㅁㅗㅇㅡㅁ ㅂㅜㄴㄹㅣ 

실수로 윈도우 키 + H키를 눌렀을 때 발생하는 경우가 많다. 원래는 음성인식 기능(받아쓰기 기능)을 수행하는 Microsoft Text Input Application 프로그램을 실행하는 단축키다.

 

윈도우 + H 키를 누르면 “현재 언어로는 받아쓰기를 사용할 수 없습니다 : 한국어” 라는 메시지가 뜬다. 이처럼 한국은 아직 음성인식 기능(받아쓰기 기능)이 지원되지 않는 국가이기 때문에 한글 자모음이 분리되는 불편함만 남게 된다.

해결방법

 

Ctrl + Alt + Delete 키(또는 Ctrl + Shift + Esc 키)를 눌러 [작업 관리자]를 열고, Microsoft Text Input Application 을 찾아서 [작업 끝내기(E)] 버튼을 클릭하면 해결된다.

참고사이트 : https://itfix.tistory.com/556

[Javascript] 클립보드 데이터 복사

[Javascript] 클립보드 데이터 복사

function copyToClipboard(_val) {
    var textareaObj = document.createElement(“textarea”);
    document.body.appendChild(textareaObj);
    textareaObj.value = _val;
    textareaObj.select();
    document.execCommand(“copy”);
    document.body.removeChild(textareaObj);
}

copyToClipboard(“클립보드 복사할 내용”);

확인결과 IE11, Chrome 에서 잘 동작한다.

참고사이트 : https://zetawiki.com/wiki/JavaScript_%ED%81%B4%EB%A6%BD%EB%B3%B4%EB%93%9C%EB%A1%9C_%EB%B3%B5%EC%82%AC%ED%95%98%EA%B8%B0

[JAVA] 자바 byte 단위로 문자열 자르기 (java substring by bytes)

[JAVA] 자바 byte 단위로 문자열 자르기 (java substring by bytes)

자바에서 문자열 길이를 byte 단위로 가져오는 방법은 다음과 같다.

UTF-8 기준 한글은 3바이트, 알파벳 대소문자나 숫자 및 띄어쓰기는 1바이트로 계산된다.

String str = “테스트”;
int bytesLen = str.getBytes().length;

문자열을 byte 단위로 자르기 위해서는 String 생성자를 이용하는 방법이 있다.

알파벳 대소문자와 숫자만으로 이뤄진 문자열을 자르기 위해서는 가장 좋은 방법이다.

아래는 6바이트까지 문자열을 자르는 예제다.

String str = “테스트”;

int endBytes = 6;
String result = new String(str.getBytes(), 0, endBytes);

그런데 new String(str.getBytes(), 0, endBytes) 코드를 사용하면 한글 바이트에 딱 맞춰 자르지 않는 경우 깨지는 문제가 있었다.

예를 들어 “테스트”를 3바이트까지 자르면 “테”가 되고, 6바이트까지 자르면 “테스”가 되지만, “테스트”를 7바이트까지 자르면 끝에 깨진 문자열이 붙는다.

따라서 한글을 포함하는 문자열을 고려한 메서드를 따로 만들었다.

/**
 * 문자열을 바이트 단위로 substring하기
 *
 * new String(str.getBytes(), 0, endBytes) 코드를 사용하면
 * 한글 바이트에 딱 맞춰 자르지 않는 경우 깨지는 문제가 있어서 따로 메서드를 만들었다.
 *
 * UTF-8 기준 한글은 3바이트, 알파벳 대소문자나 숫자 및 띄어쓰기는 1바이트로 계산된다.
 *
 * @param str
 * @param beginBytes
 * @param endBytes
 * @return
 */

public static String substringByBytes(String str, int beginBytes, int endBytes) {
    if (str == null || str.length() == 0) {
        return “”;
    }
 

     if (beginBytes < 0) {
        beginBytes = 0;
    }

    if (endBytes < 1) {
        return “”;
    }

    int len = str.length();

    int beginIndex = -1;
    int endIndex = 0;

    int curBytes = 0;
    String ch = null;
    for (int i = 0; i < len; i++) {
        ch = str.substring(i, i + 1);
        curBytes += ch.getBytes().length;
 

        if (beginIndex == -1 && curBytes >= beginBytes) {
            beginIndex = i;
        }

        if (curBytes > endBytes) {
            break;
        } else {
            endIndex = i + 1;
        }
    }
 

    return str.substring(beginIndex, endIndex);
}

참고사이트 1 : https://goni9071.tistory.com/237

참고사이트 2 : https://m.blog.naver.com/mk1126sj/221037624213

[Javascript] iframe 내부 변수, 함수 사용하기

[Javascript] iframe 내부 변수, 함수 사용하기

contentWindow 를 사용하면 iframe 내부 변수, 내부 함수를 사용할 수 있다.


var x = document.getElementsByTagName(“iframe”)[0];

var myVal = x.contentWindow.변수명;

var myFunc = x.contentWindow.함수명();


jquery 에서도 마찬가지로 contentWindow 를 사용하면 된다.


var x = $(“iframe”).get(0);

var myVal = x.contentWindow.변수명;

var myFunc = x.contentWindow.함수명();

참고사이트 : https://toe10.tistory.com/1690

[WordPress] 설치형 워드프레스 수동업데이트 방법

[Wordpress] 설치형 워드프레스 수동업데이트 방법

워드프레스 관리자 페이지(wp-admin)에서 워드프레스 업데이트를 시도했을 때 아래처럼 “다운로드 실패.: No working transports found” 오류가 발생했다.

 

검색을 동원해 어떻게든 워드프레스 자동 업데이트를 해보려고 했지만 동일한 오류가 계속 발생했다.

결국 워드프레스 수동 업데이트를 하기로 했다.

워드프레스 수동업데이트 방법

1. [워드프레스주소]/wp-admin 에 접속해서 기존에 사용하고 있는 플러그인을 모두 비활성화 처리

2. 워드프레스 폴더 및 파일 백업(다른 위치에 복사/붙여넣기하거나 압축하여 백업), 가능하다면 DB(데이터베이스)도 백업

3. 기존 워드프레스 폴더 및 파일 모두 삭제 (단 wp_content 폴더와 wp_config.php 파일은 제외하고 삭제)

4. 버전업하고 싶은 워드프레스 파일들로 덮어쓰기 (단 wp_content 폴더와 wp_config.php 파일은 제외하고 덮어쓰기)

5. [워드프레스주소]/wp-admin/upgrade.php 접속해서 나머지 업데이트 진행하기

참고사이트 : http://jazzman.pe.kr/blog/index.php/2018/08/18/403/

[Linux] 리눅스 특정 포트 사용중인 프로세스 확인

[Linux] 리눅스 특정 포트 사용중인 프로세스 확인

1. netstat 명령어 사용

netstat -ntlp | grep :포트번호

사용 예 (8080 포트 사용중인지 확인)

netstat -ntlp | grep :8080

결과 예

tcp        0      0 :::8080                     :::*                        LISTEN      16751/java

2. lsof 명령어 사용

lsof -nP -iTCP:포트번호 | grep LISTEN

사용 예 (8080 포트 사용중인지 확인)

lsof -nP -iTCP:8080 | grep LISTEN

결과 예

COMMAND   PID     USER   FD   TYPE  DEVICE SIZE/OFF NODE NAME

java    16751 ec2-user   19u  IPv6 1030293      0t0  TCP *:8080 (LISTEN)

[iOS] 아이폰 푸시알림 오지 않는 현상 수정 (HTTP/2 APNs Provider API 적용)

[iOS] 아이폰 푸시알림 오지 않는 현상 수정 (HTTP/2 APNs Provider API 적용)

기존에 잘 동작하던 아이폰 푸시알림이 오지 않는 문제가 있었다.

기존에 푸시알림을 gateway.push.apple.com:2195 (개발은 gateway.sandbox.push.apple.com:2195) 로 보내고 있었는데 보내는 것은 문제가 없었다. 로그상 아무런 오류도 없었고 gateway.push.apple.com:2195 까지 잘 도달하는 것 같았다.

문제는 아이폰에 푸시알림이 오지 않는 것이었다. 나의 역할은 gateway.push.apple.com:2195 까지 정보를 보내는 것까지이고, gateway.push.apple.com:2195 에서 아이폰에 푸시알림을 왜 보내주지 않는지는 알 수도 없고 통제할 수도 없었다.

앱 4개(서로 다른 번들 아이디를 가진 앱) 중 사이트 3개가 푸시알림이 오지 않았다. 앱 1개는 오고 있다는 점 때문에 상당히 헷갈렸는데, 테스트를 거듭한 결과 푸시알림이 오는 앱은 adhoc 프로비저닝 프로파일을 입힌 앱이었고, 나머지 3개 사이트는 enterprise 프로비저닝 프로파일을 입힌 앱이었다.

결론은 2021년 3월 31일 이후로 기존 애플 푸시알림 방식이 레거시로 분류되어 더 이상 지원하지 않게 되었다.

이제 gateway.push.apple.com:2195 가 아닌 api.push.apple.com:443 으로 푸시알림을 보내야 한다.


APNs 제공업체 API 기한 업데이트 : https://developer.apple.com/kr/news/?id=c88acm2b

구분

Binary Provider API (APNs Legacy API)

APNs Provider API

Protocol

TCP

HTTP/2, TLS1.2 이상

URL/PORT

gateway.push.apple.com:2195,2196

api.push.apple.com:443

payload

2KB

4KB

인증방식

인증서(.p12 파일)

인증서(.p12 파일)

토큰방식도 사용 가능(.p8)

관련 오픈소스 라이브러리

apns-1.0.0.Beta6.jar

https://github.com/notnoop/java-apns

apns-http2-1.0.5.jar

https://github.com/CleverTap/apns-http2

참고사이트 : http://photogrammer.pe.kr/?p=265

푸시알림을 적용하는 방법은 크게 2가지가 있다고 한다.

(1) HTTP/2 사용하는 방법 (직접 APNs로 푸시알림 전달)

(2) Google Firebase를 사용하는 방법 (Firebase에 보내면 Firebase가 APNs로 푸시알림 전달)

개인적으로 iOS 앱을 Firebase 에 맞게 변경하는 것을 원하지 않았기 때문에 1번 방법을 선택했다.

이에 따라 기존 푸시알림 코드를 걷어내고 새로운 푸시알림 코드를 작성했다.

만약 2번 Firebase를 사용하는 방법을 택하려면 다음 주소를 참고하면 된다.

iOS 프로젝트에 Firebase 추가 : https://firebase.google.com/docs/ios/setup?hl=ko#objective-c

[APNs/Firebase] Google Firebase에 iOS App 등록 및 통신하기 : http://photogrammer.pe.kr/?p=288

1번 방법에서는 JDK 1.8 이상을 사용해야 한다.

그런데 openjdk 에서는 protocol 오류(Caused by: javax.net.ssl.SSLException: Received fatal alert: protocol_version)가 발생해서 오라클 JDK를 사용했다.

참고로 오라클 JDK는 1.8.0_202 버전까지 무료이고 이후 1.8.0_211 버전부터 기업에서 유료다.

참고사이트 : 오라클 Java SE(JRE/JDK) 유료버전, 무료버전

https://broccolies.com/2021/03/%EC%98%A4%EB%9D%BC%ED%81%B4-java-sejre-jdk-%EC%9C%A0%EC%83%81%EB%B2%84%EC%A0%84/

아래와 같이 새 프로그램을 작성해서 테스트해봤다.

잘 동작한다.

import java.io.FileInputStream;
import java.io.IOException;
import java.net.URLEncoder;

import com.clevertap.apns.ApnsClient;
import com.clevertap.apns.Notification;
import com.clevertap.apns.Notification.Builder;
import com.clevertap.apns.NotificationResponse;
import com.clevertap.apns.clients.ApnsClientBuilder;

public class MainClass {

    public static void main(String[] args) {
        
        String sendMessage = “Hello World”;
        
        String p12FilePath = “p12파일 경로”;
        String p12FilePassword = “p12파일 패스워드”;
        
        String deviceToken = “아이폰 디바이스 토큰”;
        String iosAppBundleId = “아이폰 앱 번들아이디(패키지명)”;
        
        FileInputStream cert = null;
        
        try {
            cert = new FileInputStream(p12FilePath);
            final ApnsClient client = new ApnsClientBuilder()
             .withProductionGateway()
             .inSynchronousMode()
             .withCertificate(cert)
             .withPassword(p12FilePassword)
             .withDefaultTopic(iosAppBundleId)
             .build();
            
            Builder builder = new Notification.Builder(deviceToken);
            
            int badgeCount = 1;
            builder.badge(badgeCount);
            
            builder = builder.sound(“defualt”);
            builder = builder.alertBody(sendMessage);
            
            builder = builder.customField(“field1”“value1”);
            builder = builder.customField(“field1”“value2”);
            builder = builder.customField(“field1”“value3”);
            
            Notification notification = builder.build();
            
            NotificationResponse result = client.push(notification);
            System.out.println(result);
            
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (cert != null) {
                    cert.close();
                }
            } catch (IOException e) { 

              } catch (Exception e) {
            } finally {
                cert = null;
            }
        }
    }
}

■ JDK 버전

jdk1.8.0_112

■ 사용 라이브러리

apns-http2-1.0.5.jar

commons-codec-1.11.jar

jackson-annotations-2.7.0.jar

jackson-core-2.7.0.jar

jackson-databind-2.1.4.jar

okhttp-3.2.0.jar

okio-1.6.0.jar

기존 라이브러리에 해당하는 apns-1.0.0.Beta6.jar, bcprov-ext-jdk15on-153.jar, javapns.jar 는 걷어냈다. 직접적인 라이브러리는 아니지만 jackson-annotations-2.1.1.jar, jackson-core-2.1.1.jar, jackson-databind-2.1.1.jar 도 걷어냈다.

■ 기타

HTTP/2 통신을 하기 위해 APLN Boot 를 사용해야 한다. APLN Boot 는 jetty 를 기반으로 한 것으로 보인다.

APLN Boot 의 버전은 사용하는 JDK 버전에 맞게 구해야 한다.

참고사이트 1 : https://www.ibm.com/docs/ko/was-liberty/base?topic=40-alpn-support

1.8.0_5=8.1.0.v20141016

1.8.0_11=8.1.0.v20141016

1.8.0_20=8.1.0.v20141016

1.8.0_25=8.1.2.v20141202

1.8.0_31=8.1.3.v20150130

1.8.0_40=8.1.3.v20150130

1.8.0_45=8.1.3.v20150130

1.8.0_51=8.1.4.v20150727

1.8.0_60=8.1.5.v20150921

1.8.0_65=8.1.6.v20151105

1.8.0_66=8.1.6.v20151105

1.8.0_71=8.1.7.v20160121

1.8.0_72=8.1.7.v20160121

1.8.0_73=8.1.7.v20160121

1.8.0_74=8.1.7.v20160121

1.8.0_77=8.1.7.v20160121

1.8.0_91=8.1.7.v20160121

1.8.0_92=8.1.8.v20160420

1.8.0_101=8.1.9.v20160720

1.8.0_102=8.1.9.v20160720

1.8.0_111=8.1.9.v20160720

1.8.0_112=8.1.10.v20161026

1.8.0_121=8.1.11.v20170118

1.8.0_131=8.1.11.v20170118

1.8.0_141=8.1.11.v20170118

1.8.0_144=8.1.11.v20170118

1.8.0_151=8.1.11.v20170118

1.8.0_152=8.1.11.v20170118

1.8.0_161=8.1.12.v20180117

1.8.0_162=8.1.12.v20180117

1.8.0_171=8.1.12.v20180117

1.8.0_172=8.1.12.v20180117

1.8.0_181=8.1.12.v20180117

1.8.0_191=8.1.13.v20181017

1.8.0_192=8.1.13.v20181017

1.8.0_201=8.1.13.v20181017

1.8.0_202=8.1.13.v20181017

1.8.0_211=8.1.13.v20181017

1.8.0_212=8.1.13.v20181017

1.8.0_221=8.1.13.v20181017

1.8.0_222=8.1.13.v20181017

1.8.0_231=8.1.13.v20181017

1.8.0_232=8.1.13.v20181017

1.8.0_241=8.1.13.v20181017

1.8.0_242=8.1.13.v20181017

위 표와는 조금 다르지만 나는 jdk1.8.0_112 버전을 사용하고 alpn-boot-8.1.8.v20160420.jar 를 사용했다. ALPN Boot jar 는 일반적인 jar 처럼 classpath에 추가하는 것이 아니라, bootclasspath (부트스트랩 클래스패스) 에 넣어야 한다.

-Xbootclasspath/p:D:\alpn-boot-8.1.8.v20160420.jar

 

이클립스 톰캣에서도 위와 같이 VM arguments 에 추가하면 된다.

파일시스템에 설치된 톰캣에 적용하려면 catalina.bat 파일을 수정하면 된다.

[톰캣경로]\bin\catalina.bat 149라인 부근 수정

[AS-IS]

set CLASSPATH=

[TO-BE]

set CLASSPATH=

set CATALINA_OPTS=-Xbootclasspath/p:D:\alpn-boot-8.1.8.v20160420.jar

참고사이트 : https://heowc.dev/2019/05/13/ios-push-to-java/

[Javascript] js 깊은 복사(Deep Copy), 객체 복제(Clone Object)

[Javascript] js 깊은 복사(Deep Copy), 객체 복제(Clone Object)

자바스크립트 문자열(String)과 숫자(Number)는 native 타입이어서 또 다른 변수에 대입하면 동일해진다(값 복사).

그러나 객체(Object)와 배열(Array)은 또 다른 변수에 대입하면 참조 상태(개념적으로 보면 일종의 포인터만 갖고 있는 상태)가 된다.

관련한 오류를 피하려면 얕은 복사(Shallow Copy) 대신 깊은 복사(Deep Copy)를 수행해야 한다.

객체/배열을 복사해서 새로운(기존과 독립인) 새로운 객체/배열을 만드는 것이다.

자바스크립트 깊은 복사는 eval을 쓰는 등 여러가지 방법이 있다.

1. JSON 객체를 사용한 깊은 복사 (객체/배열 복제)


JSON 객체를 사용해서 깊은 복사를 한다. 먼저 문자열로 만들고, 문자열을 다시 파싱하는 식이다.


// js 깊은 복사
function deepCopy(_obj) {
    if (_obj == null) {
        return null;
    }


    return JSON.parse(JSON.stringify(_obj));
}

2. 직접 구현한 깊은 복사 (객체/배열 복제)


아래는 JSON 객체를 사용하지 않고 직접 짠 함수다. 이 함수의 특징/사용상 주의사항은 다음과 같다.

1. 객체(Object, {}) 또는 배열(Array, [])을 깊은 복사한다.

2. 함수(Function)는 복사하지 않는다.

3. 배열과 객체의 구분은 length 요소가 있는지로 판별한다.

  (배열이 아닌데 하필 length 요소가 존재한다면 오류가 발생할 수 있다.)

4. 리턴용 객체 인스턴스를 만들 때 생성자를 활용하지 않는다.

  (생성자를 활용하려면 var newObj = new Object(); 대신 var newObj = _obj.constructor(); 식으로 코딩하면 된다.)

// js객체를 깊은 복사한다.
function deepCopy(_obj) {
    if (_obj == null || typeof(_obj) == null) {
        return null;
    }

    // 숫자 복사
    if (typeof(_obj) == “number”) {
        return parseInt(_obj + “”, 10);
    }

    // 문자열 복사
    if (typeof(_obj) == “string”) {
        return _obj + “”;
    }

    if (typeof(_obj) == “object”) {
    
        var bArray = false;
    
        try {
            // 배열 여부는 length 로만 판단한다.
            if (_obj.length === parseInt(_obj.length, 10)) {
                bArray = true;
            }
        } catch (e) {}
    
        // 배열 복사
        if (bArray) {
            var newArray = new Array();
        
            var cnt = _obj.length;
            for (var i=0; i<cnt; i++) {
                newArray[i] = deepCopy(_obj[i]);
            }
        
            return newArray;
        }
    
        // 객체 복사
        var newObj = new Object();
    
        for (var attr in _obj) {
            if (_obj.hasOwnProperty(attr)) {
                newObj[attr] = deepCopy(_obj[attr]);
            }
        }
    
        return newObj;
    }

    // 숫자, 문자열, 배열 외에는 복사하지 않는다. (ex: function)
    return null;
}

Well-known port

Well-known port

1024 미만 (0 ~ 1023) 의 포트를 Well-known port라고 한다.

이 포트들을 열 때에는 관리자 권한이 필요하다.

예를 들어 80포트를 사용하는 아파치 웹서버 또는 nginx를 띄울 때는 root 계정으로 접근해야 한다.

[Eclipse] 이클립스(STS)에서 SVN keywords 일괄 설정하는 방법

[Eclipse] 이클립스(STS)에서 SVN keywords 일괄 설정하는 방법

1. SVN keywords 사용방법과 장점

파일에 SVN keywords 값을 세팅하면 SVN 커밋 시, 속성을 나타내는 특정 문자열(예를 들면 $Id$)이 최근 수정내역 데이터로 교체된다.

예를 들어서 파일 내용에 아래와 같이 쓴다.

// $Id$

이후 파일을 커밋하면 자동으로 아래와 같이 최근 수정내역 데이터로 교체된다.

// $Id: JavaFileName.java 5 2021-05-28 19:05:25Z bb_ $

장점은 해당 파일의 최근 수정내역을 확인하기 좋다.

꼭 주석 형태가 아니어도 된다.

예를 들어 아래처럼 소스코드 변수에 넣어도 된다.

public static final String SVN_KEYWORDS = “$Id$”;

이 경우 class 파일을 java 파일로 디컴파일했을 때 수정내역 데이터를 확인할 수 있다.

유사시 class 파일의 히스토리를 추적하기에 좋다.

2. 이클립스(STS)에서 SVN keywords 일괄 설정하는 방법

폴더 째로 SVN keywords 값을 한꺼번에 세팅하는 방법이다.

이클립스 또는 STS 에서 세팅하는 방법이다.

(1) 이클립스 좌측 프로젝트 트리에서 특정 폴더(여기서는 자바 파일들이 모두 들어있는 src 폴더를 대상으로 함) 선택

폴더 째로 적용하고 싶지 않다면 특정 파일을 대상으로 해도 된다.

(2) 마우스 우클릭 – [Team] – [Set Property…] 항목 클릭

 

(3) Set Property 창이 뜨면 상단 Property name 에 “svn:keywords” 입력

(4) Property content 텍스트 영역에 “Author Date Id Revision URL Header” 입력

(5) Set property recursively 체크박스 체크

(6) [OK] 버튼 클릭


이렇게 하면 선택한 폴더 하위 모든 파일에 SVN keywords 가 한꺼번에 일괄 세팅된다.

이후 SVN 에 적용된 파일들을 커밋하면 된다.

참고사이트 1 : https://bugmaster.tistory.com/17

참고사이트 2 : https://blog.naver.com/vikong/60183655029 

[Windows] 윈도우 10 방화벽 아웃바운드 특정 포트(port)만 허용

[Windows] 윈도우 10 방화벽 아웃바운드 특정 포트(port)만 허용

아이폰(iOS) 푸시 알림을 위해 윈도우 서버에서 APNs(Apple Push Notification service) 쪽으로 메시지를 보내려는데 JAVA 단에서 Connection timed out 오류가 발생했다.

error: com.notnoop.exceptions.NetworkIOException: java.net.ConnectException: Connection timed out: connect

확인결과 gateway.push.apple.com 의 2195 포트에 대해 아웃바운드가 막혀있어서 Connection timed out 오류가 발생했다.

cmd 에서 telnet gateway.push.apple.com 2195 라고 입력했을 때,

“연결 대상 gateway.push.apple.com… 호스트에 연결할 수 없습니다. 포트 2195: 연결하지 못했습니다.” 라고 나오는 경우 아웃바운드 포트가 막혀있는지 의심해봐야 한다.

 

1. Windows Defender 방화벽 열기

[제어판] – [모든 제어판 항목]- [Windows Defender 방화벽]

좌측의 [고급 설정] 클릭

 

2. 새 규칙 생성

좌측 트리의 [아웃바운드 규칙] 항목 클릭 – 우측의 [새 규칙…] 항목 클릭

3. 새 아웃바운드 규칙 마법사

[포트(O)] 라디오 버튼 선택

[다음(N)] 버튼 클릭



[TCP(T)] 라디오 버튼 선택

[특정 원격 포트(S)] 라디오 버튼 선택 – 인풋박스에 포트 입력 (ex : 2195)

[다음(N)] 버튼 클릭



[연결 허용(A)] 라디오 버튼 선택

[다음(N)] 버튼 클릭


[도메인(D)], [개인(P)], [공용(U)] 체크박스 체크한 상태로 유지

[다음(N)] 버튼 클릭



원하는 규칙 이름을 입력 (ex : APNs 아웃바운드 테스트)

[마침(F)] 버튼 클릭

 

이제 cmd 에서 telnet gateway.push.apple.com 2195 라고 입력했을 때 연결되어야 한다.

[Android] 안드로이드 앱 배지(badge) 카운트 초기화, 앱 배지 숫자 삭제 코드

[Android] 안드로이드 앱 배지(badge) 카운트 초기화, 앱 배지 숫자 삭제 코드

안드로이드 어플리케이션에 표시된 숫자 카운트, 즉 배지 카운트는 알림의 개수를 표시한다.

사용자가 알림을 조회하면 배지 카운트가 감소된다.

그런데 꼭 상단바의 알림 메시지 자체를 조회할 때만 배지 카운트가 감소되는 특징이 있다.

알림을 읽지 않고 어플리케이션을 실행할 때는 배지 카운트가 감소되지 않는다.

개발자가 강제로 배지 카운트를 초기화(삭제)하기 위해서는 아래와 같이 쓰면 된다.

try {
    NotificationManager nm = (NotificationManager) getSystemService([자바클래스명].NOTIFICATION_SERVICE);
    nm.cancelAll();
} catch (Exception e) {
    e.printStackTrace();
}

안드로이드 앱이 실행됐을 때 배지 카운트를 초기화하려면 액티비티의 onCreate() 메서드 안에 코드를 넣어야 하지만, 액티비티의 onResume() 메서드 안에도 코드를 넣어주는 게 좋다고 생각한다.

이유는 사용자들이 홈 버튼을 눌러서 안드로이드 앱을 잠시 최소화시켜뒀다가 다시 앱을 열었을 때, onCreate() 가 아닌 onResume() 메서드의 내용이 실행되기 때문이다.

사용자는 안드로이드 생명주기를 잘 모르기 때문에 앱을 최소화했다가 다시 앱을 열었다고 생각하지 않고, 앱을 새로 실행했다고 생각한다. 때문에 onCreate(), onResume() 메서드 둘 다에 코드 넣기를 권장한다.

[AS-IS]

@Override
protected void onResume() {
    super.onResume();
}

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

}

[TO-BE]

@Override
protected void onResume() {
    super.onResume();

    try {
        NotificationManager nm = (NotificationManager) getSystemService([자바클래스명].NOTIFICATION_SERVICE);
        nm.cancelAll();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    try {
        NotificationManager nm = (NotificationManager) getSystemService([자바클래스명].NOTIFICATION_SERVICE);
        nm.cancelAll();
    } catch (Exception e) {
        e.printStackTrace();
    }

}

마지막으로 앱 배지 카운트를 초기화(삭제)하는 게 아니라 원하는 값으로 바꾸고 싶다면 ShortcutBadger 라이브러리를 검토해보는 것을 추천한다.

안드로이드는 제조사마다 배지를 다루는 코드가 조금씩 다른 것 같다. 이를 고려한 라이브러리가 ShortcutBadger 인 것으로 보인다. 아래 깃헙 주소로 들어가면 확인할 수 있다. 라이센스는 (안드로이드의 라이센스와 같은) 아파치 라이센스다. 상당히 자유로운 라이센스에 해당한다.

ShortcutBadger 라이브러리 : https://github.com/leolin310148/ShortcutBadger

[iOS] Provisioning profile “프로파일명” doesn’t include signing certificate “Apple Distribution: 이름(고유코드)”

[iOS] Provisioning profile “프로파일명” doesn’t include signing certificate “Apple Distribution: 이름(고유코드)”

Provisioning profile “프로파일명” doesn’t include signing certificate “Apple Distribution: 이름(고유코드)” 오류가 발생하는 경우.

확실하지는 않으나 아래처럼 시도했을때 해결됐다.

1. CSR 파일 생성부터 다시 해본다.

순서는 CSR 파일 생성 => Certificate 생성 => Provisioning profile 생성 순이다.

2. CSR 파일을 생성할 때 키체인 목록에서 정확히 원하는 Certificate를 선택해서 CSR 을 생성한다.

예를 들어서 Provisioning profile “프로파일명” doesn’t include signing certificate “Apple Development: Company Name(ABCDEFGHIJ)” 오류가 발생하는 경우, 키체인 기존 목록에서 “Company Name(ABCDEFGHIJ)” 에 해당하는 로우를 선택하고, 상단메뉴 [키체인 접근] – [인증서 지원] – [인증 기관에서 인증서 요청…] 을 클릭해서 CSR 을 생성해본다.

3. doesn’t include signing certificate~ 다음의 명칭을 정확히 확인해야 한다.

“Apple Development”인지 “Apple Distribution”인지 “iOS Development”인지 “iOS Distribution”인지 정확히 확인해서 필요한 Certificate 를 생성한다.

iOS 앱을 배포할 목적이더라도 Xcode 11 또는 그 이후(Xcode 11 or later)부터 개발용은 Apple Development, 배포용은 Apple Distribution 으로  Provisioning profile 을 생성해야 한다.

4. Provisioning profile 을 생성할 때 Decives 아래의 Include Mac Devices 도 체크해주는 것을 권장한다.

[SpringBoot] 스프링부트 메이븐 빌드 시 Found multiple occurrences 오류

[SpringBoot] 스프링부트 메이븐 빌드 시 Found multiple occurrences 오류

스프링부트에서 메이븐 빌드 시 아래처럼 Found multiple occurrences 오류가 발생하며 BUILD FAILURE 되는 경우.

사용한 라이브러리에 따라 세부적인 오류내용은 다를 수 있다.

Found multiple occurrences of org.json.JSONObject on the class path:

    jar:file:/C:/Users/gendev/.m2/repository/com/vaadin/external/google/android-json/0.0.20131108.vaadin1/android-json-0.0.20131108.vaadin1.jar!/org/json/JSONObject.class

    jar:file:/C:/Users/gendev/.m2/repository/org/json/json/20200518/json-20200518.jar!/org/json/JSONObject.class

You may wish to exclude one of them to ensure predictable runtime behavior

아래처럼 org.json 을 pom.xml 에 추가한 이후로 오류가 발생하게 됐다.

<!– https://mvnrepository.com/artifact/org.json/json –>

<dependency>

    <groupId>org.json</groupId>

    <artifactId>json</artifactId>

    <version>20200518</version>

</dependency>

동일한 클래스명이 발견되어 충돌한 것으로 보인다.

메이븐 빌드 시 사용하지 않는 클래스가 속한 패키지를 제외시켜야 한다.

pom.xml 을 아래와 같이 수정했다.

[AS-iS]

<dependency>

    <groupId>org.springframework.boot</groupId>

    <artifactId>spring-boot-starter-test</artifactId>

    <scope>test</scope>

</dependency>

[TO-BE]

<dependency>

    <groupId>org.springframework.boot</groupId>

    <artifactId>spring-boot-starter-test</artifactId>

    <scope>test</scope>

    <exclusions>

        <exclusion>

            <groupId>com.vaadin.external.google</groupId>

            <artifactId>android-json</artifactId>

        </exclusion>

    </exclusions>

</dependency>

이후 빌드에 성공했다.

참고사이트 : https://stackoverflow.com/questions/52980064/maven-spring-boot-found-multiple-occurrences-of-org-json-jsonobject-on-the-cl

[Objective-c] AES128 암호화, 복호화 (AES128 Encrypt, Decrypt)

[Objective-c] AES128 암호화, 복호화 (AES128 Encrypt, Decrypt)

Objective-c 에서 AES128 로 암호화, 복호화하는 방법을 작성했다.

구글에 검색해보면 보통 NSData+AES.h 파일과 NSData+AES.m 파일이 나오는데, 막상 암호화와 복호화를 사용하는 예제가 부실해서 활용하기가 좋지 않았다.

검색을 거듭해서 AESExtention.h, AESExtention.m 이라는 파일을 찾았고, 쓸만해보여서 해당 코드를 기반으로 전체적으로 수정, 보완했다.

(1) AESExtension.h, AESExtension.m 이라고 파일명을 변경했다(Extention 은 오타로 판단됨. Extension 이 맞는 철자임)

(2) Hex 값으로만 암호화, 복호화할 수 있었던 코드였는데 Base64 로 암호화, 복호화할 수 있는 메서드를 추가했다.

(3) 기존 소스코드는 ECB 방식으로 되어있었는데, iv 값을 사용하는 CBC 방식으로 수정했다.

(4) 기타 코드를 전체적으로 수정, 보완했다.

1. 새 파일 작성 (Make new files)

1-1. AESExtension.h

#import <Foundation/Foundation.h>

#import <CommonCrypto/CommonCryptor.h>

@interface AESExtension : NSObject

– (NSString *) aesEncryptNSData🙁NSString *)textString;

– (NSString *) aesEncryptHexString🙁NSString *)textString;

– (NSString *) aesEncryptBase64🙁NSString *)textString;

– (NSString *) aesDecryptNSData🙁NSString *)textString;

– (NSString *) aesDecryptHexString🙁NSString *)textString;

– (NSString *) aesDecryptBase64🙁NSString *)textString;

– (NSData *) AES128Encrypt🙁NSData *)Data;

– (NSData *) AES128Decrypt🙁NSData *)Data;

– (NSString *) encodeNSDataToHexString🙁NSData *)data;

– (NSData *) decodeHexStringToNSData🙁NSString *)hexString;

@end

1-2. AESExtension.m

#import “AESExtension.h”

@implementation AESExtension

NSString *m_key = @”aaaaaaaaaabbbbbb”;

NSString *m_ivText = @”aaaaaaaaaabbbbbb”;

int m_ivLength = 16;

– (NSData *) aesEncryptNSData🙁NSString *)textString {

    NSData *data = [textString dataUsingEncoding:NSUTF8StringEncoding];

    

    NSData *ret = [self AES128Encrypt:data];

    return ret;

}

– (NSString *) aesEncryptHexString🙁NSString *)textString {

    NSData *ret = [self aesEncryptNSData:textString];

    

    // NSData to HexString

    NSString *hexString = [self encodeNSDataToHexString:ret];

    return hexString;

}

– (NSString*) aesEncryptBase64🙁NSString *)textString {

    NSData *ret = [self aesEncryptNSData:textString];

    

    // NSData to Base64String

    NSData *base64Data = [ret base64EncodedDataWithOptions:0];

    NSString *base64String = [[NSString alloc] initWithData:base64Data encoding:NSUTF8StringEncoding];

    return base64String;

}

– (NSString *) aesDecryptNSData🙁NSData *)nsdata {

    NSData *ret = [self AES128Decrypt:nsdata];

    

    NSString *plainText = [[NSString alloc] initWithData:ret encoding:NSUTF8StringEncoding];

    return plainText;

}

– (NSString *) aesDecryptHexString🙁NSString *)hexString {

    NSData *hexData = [self decodeHexStringToNSData:hexString];

    NSString *plainText = [self aesDecryptNSData:hexData];

    return plainText;

}

– (NSString *) aesDecryptBase64🙁NSString *)base64String {

    // Base64String to NSData

    NSData *cryptData = [[NSData alloc] initWithBase64EncodedString:base64String options:0];

    

    NSString *plainText = [self aesDecryptNSData:cryptData];

    return plainText;

}

– (NSData *) AES128Encrypt🙁NSData *)Data {

    // ‘key’ should be 32 bytes for AES256, will be null-padded otherwise

    char keyPtr[kCCKeySizeAES128+1]; // room for terminator (unused) // oorspronkelijk 256

    bzero(keyPtr, sizeof(keyPtr)); // fill with zeroes (for padding)

    

    // fetch key data

    [m_key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding];

    

    NSUInteger dataLength = [Data length];

    

    // See the doc: For block ciphers, the output size will always be less than or

    // equal to the input size plus the size of one block.

    // That’s why we need to add the size of one block here

    size_t bufferSize = dataLength + kCCBlockSizeAES128;

    void *buffer = malloc(bufferSize);

    

    size_t numBytesEncrypted = 0;

    

    // ECB 방식

    /*

    CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt,

                                          kCCAlgorithmAES128,

                                          kCCOptionECBMode + kCCOptionPKCS7Padding,

                                          keyPtr,

                                          kCCKeySizeAES128, // oorspronkelijk 256

                                          NULL, // iv : initialization vector (optional)

                                          [Data bytes],

                                          dataLength, // input

                                          buffer,

                                          bufferSize, // output

                                          &numBytesEncrypted);

     */

    

    // CBC 방식

    unsigned char iv[] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};

    for (int i=0; i<m_ivLength; i++) {

        iv[i] = [m_ivText characterAtIndex:i];

    }

    

    NSData *ivData = [NSData dataWithBytes:iv length:sizeof(iv)];

    uint8_t clv[m_ivLength];

    bzero(clv, m_ivLength);

    [ivData getBytes:clv length:m_ivLength];

    

    CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt,

                                          kCCAlgorithmAES128,

                                          kCCOptionPKCS7Padding,

                                          keyPtr,

                                          kCCKeySizeAES128, // oorspronkelijk 256

                                          clv, // iv : initialization vector (optional)

                                          [Data bytes],

                                          dataLength, // input

                                          buffer,

                                          bufferSize, // output

                                          &numBytesEncrypted);

        

    if (cryptStatus == kCCSuccess) {

        // the returned NSData takes ownership of the buffer and will free it on deallocation

        return [NSData dataWithBytesNoCopy:buffer length:numBytesEncrypted];

    }

    

    // free the buffer

    free(buffer);

    

    return nil;

}

– (NSData *) AES128Decrypt🙁NSData *)Data {

    // ‘key’ should be 32 bytes for AES256, will be null-padded otherwise

    char keyPtr[kCCKeySizeAES128+1]; // room for terminator (unused) // oorspronkelijk 256

    bzero(keyPtr, sizeof(keyPtr)); // fill with zeroes (for padding)

    

    // fetch key data

    [m_key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding];

    

    NSUInteger dataLength = [Data length];

    

    // See the doc: For block ciphers, the output size will always be less than or

    // equal to the input size plus the size of one block.

    // That’s why we need to add the size of one block here

    size_t bufferSize = dataLength + kCCBlockSizeAES128;

    void *buffer = malloc(bufferSize);

    

    size_t numBytesDecrypted = 0;

    

    // ECB 방식

    /*

    CCCryptorStatus cryptStatus = CCCrypt(kCCDecrypt,

                                          kCCAlgorithmAES128,

                                          kCCOptionECBMode + kCCOptionPKCS7Padding,

                                          keyPtr, kCCKeySizeAES128, // oorspronkelijk 256

                                          NULL, // iv : initialization vector (optional)

                                          [Data bytes], dataLength, // input

                                          buffer, bufferSize, // output

                                          &numBytesDecrypted);

    */

    

    // CBC 방식

    unsigned char iv[] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};

    for (int i=0; i<m_ivLength; i++) {

        iv[i] = [m_ivText characterAtIndex:i];

    }

    

    NSData *ivData = [NSData dataWithBytes:iv length:sizeof(iv)];

    uint8_t clv[m_ivLength];

    bzero(clv, m_ivLength);

    [ivData getBytes:clv length:m_ivLength];

    

    CCCryptorStatus cryptStatus = CCCrypt(kCCDecrypt,

                                          kCCAlgorithmAES128,

                                          kCCOptionPKCS7Padding,

                                          keyPtr, kCCKeySizeAES128, // oorspronkelijk 256

                                          clv, // iv : initialization vector (optional)

                                          [Data bytes], dataLength, // input

                                          buffer, bufferSize, // output

                                          &numBytesDecrypted);

    

    if (cryptStatus == kCCSuccess) {

        // the returned NSData takes ownership of the buffer and will free it on deallocation

        return [NSData dataWithBytesNoCopy:buffer length:numBytesDecrypted];

    }

    

    // free the buffer

    free(buffer);

    

    return nil;

}

– (NSString *) encodeNSDataToHexString🙁NSData *)data {

    NSMutableString *hexString = [NSMutableString string];

    unsigned char *bytes = (unsigned char *)[data bytes];

    char temp[3];

    NSUInteger i=0;

    

    for (i=0; i<[data length]; i++){

        temp[0] = temp[1] = temp[2] =0;

        (void)sprintf(temp, “%02x”,bytes[i]);

        [hexString appendString:[NSString stringWithUTF8String:temp]];

        

    }

    

    return hexString;

}

– (NSData *) decodeHexStringToNSData🙁NSString *)hexString {

    unsigned long tlen = [hexString length]/2;

    

    char tbuf[tlen];

    int i,k,h,l;

    bzero(tbuf, sizeof(tbuf));

    

    for (i=0, k=0; i<tlen; i++) {

        h=[hexString characterAtIndex:k++];

        l=[hexString characterAtIndex:k++];

        h=(h >= ‘A’) ? h-‘A’+10 : h-‘0’;

        l=(l >= ‘A’) ? l-‘A’+10 : l-‘0’;

        tbuf[i]= ((h<<4)&0xf0)| (l&0x0f);

    }

    

    return [NSData dataWithBytes:tbuf length:tlen];

}

@end

2. AES128 암호화, 복호화 방법

2-1. 원하는 파일 상단에 아래 내용 추가 (Add the below content to the top of the desired file)

#import “AESExtension.h”

2-2. HEX 문자열로 AES128 암호화 (AES128 encrypt to HEX String)

NSString *plainText = @”1234″;

AESExtension *aes = [[AESExtension alloc] init];

NSString *hexString = [NSString stringWithFormat:@”%@”, [aes aesEncryptHexString:plainText]];

[aes release];


// hexString : a01a8acf2a239460f98439ee148e75c1

NSLog(@”hexString : %@”, hexString);

2-3. HEX 문자열로부터 AES128 복호화 (AES128 descrypt from HEX String)

NSString *hexString = @”a01a8acf2a239460f98439ee148e75c1″;


AESExtension *aes = [[AESExtension alloc] init];

NSString *decodedString = [NSString stringWithFormat:@”%@”, [aes aesDecryptHexString:hexString]];

[aes release];

// decodedString : 1234

NSLog(@”decodedString : %@”, decodedString);

2-4. Base64 문자열로 AES128 암호화 (AES128 encrypt to Base64 String)

NSString *plainText = @”1234″;

AESExtension *aes = [[AESExtension alloc] init];

NSString *base64string = [NSString stringWithFormat:@”%@”, [aes aesEncryptBase64:plainText]];

[aes release];

// base64string : oBqKzyojlGD5hDnuFI51wQ==

NSLog(@”base64string : %@”, base64string);

2-5. Base64 문자열로부터 AES128 복호화 (AES128 descrypt from Base64 String)

NSString *base64string = @”oBqKzyojlGD5hDnuFI51wQ==”;


AESExtension *aes = [[AESExtension alloc] init];

NSString *decodedString = [NSString stringWithFormat:@”%@”, [aes aesDecryptBase64:base64string]];

[aes release];


// decodedString : 1234

NSLog(@”decodedString : %@”, decodedString);

3. 결과 (Result)

m_key : aaaaaaaaaabbbbbb

m_ivText : aaaaaaaaaabbbbbb

salt : undefined

plainText : 1234

HEX String encryption value : a01a8acf2a239460f98439ee148e75c1

Base64 String encryption value : oBqKzyojlGD5hDnuFI51wQ==

decodedString : 1234

참고사이트 1 : https://blog.suromind.com/34

[Objective-c] encode NSData To HexString, decode HexString To NSData

[Objective-c] encode NSData To HexString, decode HexString To NSData

convert NSData To HexString (convert to HexString)

encode NSData To HexString (encode to HexString)

 – (NSString *) encodeNSDataToHexString🙁NSData *)data {

    NSMutableString *hexString = [NSMutableString string];

    unsigned char *bytes = (unsigned char *)[data bytes];

    char temp[3];

    NSUInteger i=0;

    

    for (i=0; i<[data length]; i++){

        temp[0] = temp[1] = temp[2] =0;

        (void)sprintf(temp, “%02x”,bytes[i]);

        [hexString appendString:[NSString stringWithUTF8String:temp]];

        

    }

    return hexString;

}

convert HexString To NSData (convert from HexString)

decode HexString To NSData (decode from HexString)

– (NSData *) decodeHexStringToNSData🙁NSString *)hexString {

    unsigned long tlen = [hexString length]/2;

    

    char tbuf[tlen];

    int i,k,h,l;

    bzero(tbuf, sizeof(tbuf));

    

    for (i=0, k=0; i<tlen; i++) {

        h=[hexString characterAtIndex:k++];

        l=[hexString characterAtIndex:k++];

        h=(h >= ‘A’) ? h-‘A’+10 : h-‘0’;

        l=(l >= ‘A’) ? l-‘A’+10 : l-‘0’;

        tbuf[i]= ((h<<4)&0xf0)| (l&0x0f);

    }

    

    return [NSData dataWithBytes:tbuf length:tlen];

}

[Objective-c] Base64 String to NSData, NSData to Base64 String

[Objective-c] Base64 String to NSData, NSData to Base64 String

Base64 String to NSData

// Base64String to NSData

NSString *str = @”SGVsbG8gV29ybGQ=”;

NSData *base64Data = [[NSData alloc] initWithBase64EncodedString:str options:0];

NSData to Base64 String

// NSData to Base64String

NSData *data = [base64Data base64EncodedDataWithOptions:0];

NSString *str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];

// str : SGVsbG8gV29ybGQ=

NSLog(@”str : %@”, str);

참고로 “SGVsbG8gV29ybGQ=” 는 “Hello World” 를 Base64 로 인코딩한 값이다.

[Objective-c] Base64 인코딩(Encoding Base64), Base64 디코딩(Decoding Base64)

[Objective-c] Base64 인코딩(Encoding Base64), Base64 디코딩(Decoding Base64)

Base64 인코딩(Encoding Base64)

// Encoding Base64, Encode Base64 (plainText to Base64)

NSString *plainText = @”Hello World”;

NSData *plainData = [plainText dataUsingEncoding:NSUTF8StringEncoding];

NSString *encodedText = [plainData base64EncodedStringWithOptions:0];

// encodedText : SGVsbG8gV29ybGQ=

NSLog(@”encodedText : %@”, encodedText);

Base64 디코딩(Decoding Base64)

// Decoding Base64, Decode Base64 (Base64 to plainText)

NSString *encodedText = @”SGVsbG8gV29ybGQ=”;

NSData *encodedData = [[NSData alloc]initWithBase64EncodedString:encodedText options:0];

NSString *decodedText = [[NSString alloc] initWithData:encodedData encoding:NSUTF8StringEncoding];

// decodedText : Hello World

NSLog(@”decodedText : %@”, decodedText);

[Objective-c] NSString to NSData, NSData to NSString

[Objective-c] NSString to NSData, NSString to NSData

// NSString to NSData

NSString *str1 = @”test”;

NSData *data1 = [str1 dataUsingEncoding:NSUTF8StringEncoding];


// NSData to NSString

NSString *str2 = [[NSString alloc] initWithData:data1 encoding:NSUTF8StringEncoding];

[iOS] In House distribution provisioning profiles for this account will be available two weeks after program activation.

[iOS] In House distribution provisioning profiles for this account will be available two weeks after program activation.

 

엔터프라이즈 계정이 발급되고 프로비저닝 프로파일을 생성하려는데 “In House distribution provisioning profiles for this account will be available two weeks after program activation.”라는 메시지가 뜨는 경우가 있다.

이때 Ad Hoc 이나 Development 프로비저닝 프로파일은 생성할 수 있지만 In House 프로비저닝 프로파일은 만들 수가 없다.

문자 그대로 2주 기다려야 한다. 엔터프라이즈 계정 발급 후 2주가 지나야 In House 프로비저닝 프로파일을 만들 수 있다.

애플은 도대체 왜 그러는걸까. 나도 모르겠다. 기다리자.

[Eclipse] 이클립스 또는 STS를 실행했을 때 로고만 나타나고 종료되는 경우

[Eclipse] 이클립스 또는 STS를 실행했을 때 로고만 나타나고 종료되는 경우

이클립스(STS)가 비정상적으로 종료된 이후, 이클립스를 다시 실행하면 로고만 나타나고 꺼지는/종료되는/실행이 되지 않는 경우가 있다.

이클립스가 최종 화면 상태를 비정상적으로 저장했을 가능성이 있다고 한다.

해당 워크스페이스의 관련 설정파일(org.eclipse.e4.workbench 폴더)을 삭제하고 이클립스를 다시 실행해본다.

C:\[워크스페이스 위치]\.metadata\.plugins\org.eclipse.e4.workbench 삭제
ex) C:\project\workspaces\ProjectName\.metadata\.plugins\org.eclipse.e4.workbench 삭제

참고사이트 : https://jiwontip.tistory.com/31

[Linux] nohup 명령어 2>&1 의미

[Linux] nohup 명령어 2>&1 의미

nohup 명령어는 터미널이 끊긴 이후에도 프로세스를 종료하지 않기 위해 사용한다.

nohup test.sh 2>&1 &

예를 들어 스프링부트로 작성한 war 파일(또는 jar 파일)을 실행하는 nohup 명령어는 다음과 같다.

nohup java -jar test.war 2>&1 &

우선 2>&1 의 의미는 표준오류(stderr)를 표준출력(stdout)이 전달되는 곳으로 보내라는 뜻이다.

숫자 0은 표준입력(stdin), 숫자 1은 표준출력(stdout), 숫자 2는 표준오류(stderr)를 의미한다.

nohup은 기본적으로 nohup.out 파일에 표준출력(stdout)을 쓰기 때문에, 2>&1 명령어를 사용하면 표준오류(stderr)도 nohup.out에 같이 쓴다.

마지막 &의 의미는 명령을 백그라운드 작업으로 실행한다는 뜻이다.

만약 표준출력이든 표준오류든 아무 것도 쓰고 싶지 않다면 명령어는 다음과 같다.

nohup java -jar test.war >/dev/null 2>&1 &

첫번째로 >/dev/null 의 의미는 표준출력을 기록하지 않는다는 뜻이다. 원래는 표준출력을 /dev/null 로 리다이렉션하라는 뜻인데, /dev/null 이 출력을 기록하지 않는 더미 장치이므로 이쪽으로 전달하면 아무 것도 쓰지 않는다.

두번째로 2>&1 의 의미는 표준오류를 표준출력이 전달되는 곳으로 보내라는 뜻인데, 이미 표준출력을 /dev/null 로 리다이렉션했으므로 표준출력과 마찬가지로 표준오류를 기록하지 않는다.

마지막 &의 의미는 명령을 백그라운드 작업으로 실행한다는 뜻이다.

표준출력과 표준오류를 각각 다른 파일에 쓰고 싶다면 명령어는 다음과 같다.

nohup java -jar test.war 1>stdout.txt 2>stderr.txt &

첫번째로 1>stdout.txt 의 의미는 표준출력을 stdout.txt 파일에 기록한다는 뜻이다.

두번째로 2>stderr.txt 의 의미는 표준오류를 stderr.txt 파일에 기록한다는 뜻이다.

마지막 &의 의미는 명령을 백그라운드 작업으로 실행한다는 뜻이다.

[PHP] Fatal error: Uncaught Error: Call to undefined function mysqli_connect()

[PHP] Fatal error: Uncaught Error: Call to undefined function mysqli_connect()

1. php.ini 파일 수정

[php설치경로] 의 php.ini 파일을 수정한다.

(ex : C:\coding\php7\php.ini)


1-1. extension_dir 경로 지정

[AS-IS]

; Directory in which the loadable extensions (modules) reside.

; http://php.net/extension-dir

; extension_dir = “./”

; On windows:

; extension_dir = “ext”


[TO-BE]

; Directory in which the loadable extensions (modules) reside.

; http://php.net/extension-dir

; extension_dir = “./”

; On windows:

; extension_dir = “ext”

extension_dir = “C:/coding/php7/ext”


1-2. php_mysqli.dll 사용하도록 주석해제

[AS-IS]

;extension=php_mysqli.dll

[TO-BE]

extension=php_mysqli.dll

저장하고 아치피 웹서버를 재기동해본다.

2. Configuration File 확인

재기동해도 똑같은 에러가 발생된다면 phpinfo.php 를 만들어서 php.ini 파일이 제대로 로드되었는지 확인해보자.


[Apache 설치폴더]/htdocs에 phpinfo.php 파일을 만든다.

파일 내용은 다음과 같이 입력한다.


<?php phpinfo(); ?>


 

phpinfo.php 페이지에 접속했을 때 “Loaded Configuration File” 항목의 값이 (none)이라면 php.ini 파일이 제대로 로드되지 않은 것이다.


2-1. php.reg 작성 및 실행

원하는 위치에 아래 내용으로 php.reg 파일을 작성한다.


Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SOFTWARE\PHP]

“IniFilePath”=”C:\coding\php7”


“IniFilePath”의 내용은 실제 php가 설치된 위치여야 한다.

이후 php.reg 파일을 실행한다. 경고 메시지가 나오면 [예]를 클릭한다.

2-2. 레지스트리 확인

[시작] – [실행] – [regedit] 를 실행한다.

컴퓨터\HKEY_LOCAL_MACHINE\SOFTWARE\PHP 위치에 들어가서 IniFilePath 값이 제대로 들어갔는지 확인한다.

 

필자의 경우 값이 제대로 들어가지 않아서 여기서 수동으로 다시 추가했다.

마우스 우클릭 – [새로 만들기] – [문자열 값]으로 만들면 된다.

아파치 웹서버를 재기동해본다.

phpinfo.php 페이지에 접속했을 때 “Loaded Configuration File” 항목의 값이 php.ini 파일경로면 성공이다.


 

이제 Fatal error: Uncaught Error: Call to undefined function mysqli_connect() 오류는 발생하지 않을 것이다.

참고사이트 : https://m.blog.naver.com/whj6648/221912391412

[Apache] 아파치 웹서버 index.php 디폴트 페이지로 설정하기

[Apache] 아파치 웹서버 index.php 디폴트 페이지로 설정하기

[Apache 설치경로\conf] 폴더의 httpd.conf 파일을 수정한다.

ex) C:\Apache2.4\conf\httpd.conf


[AS-IS]

<IfModule dir_module>

    DirectoryIndex index.html

</IfModule>


[TO-BE]

<IfModule dir_module>

    DirectoryIndex index.html index.php

</IfModule>


적용하려면 아파치 웹서버를 재기동해야 한다.


참고사이트 : https://bitsoul.tistory.com/70

[JAVA] 자바 SFTP 파일 업로드, 파일 다운로드 (jsch 라이브러리 사용방법)

[JAVA] 자바 SFTP 파일 업로드, 파일 다운로드 (jsch 라이브러리 사용방법)

자바 FTP 파일 업로드, 다운로드를 구현하기 위해서 ​FTPSClient 사용하려고 했는데(관련 라이브러리 파일은 commons-net-2.2.jar) FTPSClient 는 SFTP 접속을 할 수 없었다.

아래처럼 SSH-2.0-OpenSSH_7.4 오류가 발생하면 SFTP 이므로 접속이 안되는 경우다.

org.apache.commons.net.MalformedServerReplyException: Could not parse response code.

Server Reply: SSH-2.0-OpenSSH_7.4

    at org.apache.commons.net.ftp.FTP.__getReply(FTP.java:316)

    at org.apache.commons.net.ftp.FTP._connectAction_(FTP.java:365)

    at org.apache.commons.net.ftp.FTPClient._connectAction_(FTPClient.java:630)

    at org.apache.commons.net.SocketClient.connect(SocketClient.java:164)

    at org.apache.commons.net.SocketClient.connect(SocketClient.java:184)

SFTP 접속을 하기 위해서는 jsch 라이브러리를 사용하면 된다. 관련 라이브러리 파일은 jsch-0.1.49.jar 이고, 다음 주소에서 다운로드 받을 수 있다. https://mvnrepository.com/artifact/com.jcraft/jsch/0.1.49


우선 JSchWrapper 라는 클래스를 만들었다.

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.Properties;

import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;

public class JSchWrapper {

    private Session jschSession = null;
    private Channel channel = null;
    private ChannelSftp channelSftp = null;

    /**
     * 파일 업로드
     *
     * @param fileName
     * @param dirPath
     * @throws Exception
     */

    public boolean uploadFile(String fileName, String dirPath) throws Exception {
        boolean isSuccess = false;

        FileInputStream fis = null;

        try {
            // 대상폴더 이동
            channelSftp.cd(dirPath);

            File file = new File(fileName);
            fis = new FileInputStream(file);

            // 파일 업로드
            channelSftp.put(fis, file.getName());
            isSuccess = true;

            System.out.println(“File uploaded : “ + file.getAbsolutePath() + ” => “ + dirPath + “/” + file.getName());

        } catch (Exception e) {
            throw e;

        } finally {
            close(fis);
        }

        return isSuccess;
    }

    /**
     * 파일 다운로드
     *
     * @param remoteFilePath
     * @param localDirPath
     * @param overwrite
     * @return
     * @throws Exception
     */

    public boolean downloadFile(String remoteFilePath, String localDirPath, boolean overwrite) throws Exception {
        if (remoteFilePath == null || remoteFilePath.length() == 0) {
            return false;
        }

        boolean isSuccess = false;

        byte[] buffer = new byte[1024];

        BufferedInputStream bis = null;
        FileOutputStream fos = null;
        BufferedOutputStream bos = null;

        try {
            if (remoteFilePath.indexOf(“\\”) > -1) {
                remoteFilePath = remoteFilePath.replace(“\\”“/”);
            }

            String remoteFileName = “”;

            // 대상폴더 이동
            int lastSlashIndex = remoteFilePath.lastIndexOf(“/”);
            if (lastSlashIndex > -1) {
                String cdDir = remoteFilePath.substring(0, lastSlashIndex);
                remoteFileName = remoteFilePath.substring(lastSlashIndex + 1);
                channelSftp.cd(cdDir);
            } else {
                remoteFileName = remoteFilePath;
                channelSftp.cd(“/”);
            }

            File destFile = new File(localDirPath + File.separator + remoteFileName);
            if (destFile.exists()) {
                if (overwrite) {
                    destFile.delete();
                } else {
                    System.out.println(“File Download canceled. File already exists : “ + destFile.getAbsolutePath());
                    return false;
                }
            }

            // 파일 다운로드
            bis = new BufferedInputStream(channelSftp.get(remoteFileName));
            fos = new FileOutputStream(destFile);
            bos = new BufferedOutputStream(fos);
            int readCount = 0;
            while ((readCount = bis.read(buffer)) > 0) {
                bos.write(buffer, 0, readCount);
            }

            isSuccess = true;
            System.out.println(“File downloaded : “ + remoteFilePath + ” => “ + destFile.getAbsolutePath());

        } catch (Exception e) {
            throw e;

        } finally {
            close(bos);
            close(fos);
            close(bis);
        }

        return isSuccess;
    }

    /**
     * 폴더 생성
     *
     * @param dirPath
     * @param dirName
     * @throws Exception
     */

    public boolean mkdir(String dirPath, String dirName) throws Exception {
        boolean isSuccess = false;

        String destDirPath = dirPath + “/” + dirName;

        boolean destDirExists = false;

        try {
            channelSftp.cd(destDirPath);
            destDirExists = true;

        } catch (Exception e) {
            destDirExists = false;
        }

        if (destDirExists) {
            System.out.println(“Folder Creation canceled. Folder already exists : “ + destDirPath);
            return false;
        }

        // 대상폴더 이동
        channelSftp.cd(dirPath);

        // 폴더 생성
        channelSftp.mkdir(dirName);
        isSuccess = true;

        System.out.println(“Folder created : “ + destDirPath);
        return isSuccess;
    }

    /**
     * SFTP 접속하기
     *
     * @return
     * @throws JSchException
     * @throws Exception
     */

    public void connectSFTP(String host, int port, String userName, String password) throws Exception {
        // JSch 객체를 생성
        JSch jsch = new JSch();

        // JSch 세션 객체를 생성 (사용자 이름, 접속할 호스트, 포트 전달)
        jschSession = jsch.getSession(userName, host, port);

        // 패스워드 설정
        jschSession.setPassword(password);

        // 기타설정 적용
        Properties config = new Properties();
        config.put(“StrictHostKeyChecking”“no”);
        jschSession.setConfig(config);

        // 접속
        jschSession.connect();

        // sftp 채널 열기
        channel = jschSession.openChannel(“sftp”);

        // sftp 채널 연결
        channelSftp = (ChannelSftp) channel;
        channelSftp.connect();
    }

    /**
     * SFTP 접속해제
     */

    public void disconnectSFTP() {
        try {
            if (channelSftp != null && channelSftp.isConnected()) {
                channelSftp.disconnect();
            }
        } catch (Exception e) {
        } finally {
            channelSftp = null;
        }

        try {
            if (channel != null && channel.isConnected()) {
                channel.disconnect();
            }
        } catch (Exception e) {
        } finally {
            channel = null;
        }

        try {
            if (jschSession != null && jschSession.isConnected()) {
                jschSession.disconnect();
            }
        } catch (Exception e) {
        } finally {
            jschSession = null;
        }
    }

    /**
     * FileInputStream 객체 닫기
     *
     * @param fis
     */

    private void close(FileInputStream fis) {
        try {
            if (fis != null) {
                fis.close();
            }
        } catch (Exception e) {
        } finally {
            fis = null;
        }
    }

    /**
     * BufferedInputStream 객체 닫기
     *
     * @param bis
     */

    private void close(BufferedInputStream bis) {
        try {
            if (bis != null) {
                bis.close();
            }
        } catch (Exception e) {
        } finally {
            bis = null;
        }
    }

    /**
     * FileOutputStream 객체 닫기
     *
     * @param fos
     */

    private void close(FileOutputStream fos) {

        try {
            if (fos != null) {
                fos.flush();
            }
        } catch (Exception e) {
        }

        try {
            if (fos != null) {
                fos.close();
            }
        } catch (Exception e) {
        } finally {
            fos = null;
        }
    }

    /**
     * BufferedOutputStream 객체 닫기
     *
     * @param bos
     */

    private void close(BufferedOutputStream bos) {

        try {
            if (bos != null) {
                bos.flush();
            }
        } catch (Exception e) {
        }

        try {
            if (bos != null) {
                bos.close();
            }
        } catch (Exception e) {
        } finally {
            bos = null;
        }
    }
}

위와 같이 JSchWrapper 라는 클래스를 만들어뒀기 때문에 실제 사용은 간편하다.

아래는 SFTP 접속, 폴더 생성, 파일 업로드, 파일 다운로드, SFTP 접속해제를 하는 자바코드다.


실행하기 전에 C:\ 드라이브에 test라는 폴더를 만들고, test 폴더 안에 download 폴더와 upload 폴더를 만든다.

(C:\test\download 폴더 생성, C:\test\upload 폴더 생성)


그리고 C:\test\upload 폴더 안에 임의의 그림파일을 만들고 파일명을 0001.png 이라고 정한다.

(C:\test\upload\0001.png 그림파일 준비)


이후 아래 코드를 실행했을 때 C:\test\download 폴더 안에 0001.png 파일이 다운로드 받아지면 성공이다.


public class TestClass {

    public static void main(String[] args) {
        JSchWrapper jschWrapper = null;

        try {
            jschWrapper = new JSchWrapper();

            // SFTP 접속하기 (주소, 포트번호, 사용자아이디, 패스워드)
            jschWrapper.connectSFTP(“itarchives.cafe24.com”, 22, “username”“1234”);

            // /itarchives 폴더 위치에 test 폴더 생성
            jschWrapper.mkdir(“/itarchives”“test”);

            // C:\\test\\upload\\0001.png 파일을 /itarchives/test 위치에 업로드
            jschWrapper.uploadFile(“C:\\test\\upload\\0001.png”“/itarchives/test”);

            // /itarchives/test/0001.png 파일을 C:\\test\\download 위치에 다운로드
            jschWrapper.downloadFile(“/itarchives/test/0001.png”“C:\\test\\download”, false);

        } catch (Exception e) {
            e.printStackTrace();

        } finally {
            // SFTP 접속해제
            jschWrapper.disconnectSFTP();
        }
    }
}


실행결과

Folder created : /itarchives/test

File uploaded : C:\test\upload\0001.png => /itarchives/test/0001.png

File downloaded : /itarchives/test/0001.png => C:\test\download\0001.png



참고사이트 : https://steemit.com/kr-dev/@capslock/java-sftp-using-jsch-sftp

[HTML] html 페이지 모바일에서 반응형으로 나오게 하는 방법 (viewport)

[HTML] html 페이지 모바일에서 반응형으로 나오게 하는 방법 (viewport)

아래와 같이 viewport 메타 태그를 사용하면 html 페이지가 모바일 화면에 맞게 보인다.

<!DOCTYPE html>

<html>

<head>

    <meta charset=”UTF-8″ />

    <meta http-equiv=”Content-Type” content=”text/html; charset=utf-8″ />

    <meta name=”viewport” content=”width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no” />

</head>

<body>

</body>

</html>

[Javascript] CryptoJS AES 128 암호화/복호화

[Javascript] CryptoJS AES 128 암호화/복호화

vue.js 에서 AES 128 암복호화를 하기 위해 CryptoJS 를 사용해보았다.

인터넷에서 찾은 흔한 예제들로 해보니 key 값과 iv 값이 계속 변경되는 현상이 있었다.

이 포스트에서는 key 값이 변경되지 않도록 암호화하는 예제를 정리해서 공유한다.

CryptoJS AES 128 암호화/복호화 (salt 키 없이 암호화/복호화)

const key = “aaaaaaaaaabbbbbb”;
const iv = “aaaaaaaaaabbbbbb”;

// CryptoJS AES 128 암호화
const keyutf = CryptoJS.enc.Utf8.parse(key);
const ivutf = CryptoJS.enc.Utf8.parse(iv);

var encObj = CryptoJS.AES.encrypt(this.message, keyutf, { iv: ivutf });
console.log(“key : “ + encObj.key.toString(CryptoJS.enc.Utf8));
console.log(“iv : “ + encObj.iv.toString(CryptoJS.enc.Utf8));
console.log(“salt : “ + encObj.salt);
console.log(“ciphertext : “ + encObj.ciphertext);

var encStr = encObj + “”;
console.log(“encStr : “ + encStr);

// CryptoJS AES 128 복호화
const decObj = CryptoJS.AES.decrypt({ ciphertext: CryptoJS.enc.Base64.parse(encStr) }, keyutf, { iv: ivutf });

const decStr = CryptoJS.enc.Utf8.stringify(decObj);
console.log(“decStr : “ + decStr);

결과

key : aaaaaaaaaabbbbbb

iv : aaaaaaaaaabbbbbb

salt : undefined

ciphertext : a01a8acf2a239460f98439ee148e75c1

encStr : oBqKzyojlGD5hDnuFI51wQ==

decStr : 1234

참고사이트 1 : https://jsfiddle.net/qwd035sk/45/

[HTML] 웹폰트 css 적용하기 (Noto Sans KR 웹폰트 적용하기)

[HTML] 웹폰트 css 적용하기 (Noto Sans KR 웹폰트 적용하기)

<html>

<head>

    <link href=”https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@100;300;400;500;700;900&display=swap” rel=”stylesheet”>

    <style>

    * {

        font-family: ‘Noto Sans KR’, sans-serif;

    }

    </style>

</head>

<body>

    Hello World

</body>

</html>

참고사이트 : https://iflue.tistory.com/172

[PHP] 이클립스 php 개발환경 세팅

[PHP] 이클립스 php 개발환경 세팅

윈도우 10 환경이고 이클립스에서 php 개발환경 세팅하는 방법을 공유한다.

1. php용 이클립스 다운로드

https://www.eclipse.org/ 접속 – 우측상단 [Download] 버튼 클릭



[Download Packages] 클릭



Eclipse IDE for PHP Developers 항목을 찾고 [Windows x86_64] 를 클릭해서 php용 이클립스를 다운로드한다.

그런데 현재 최신버전(2021-03)의 경우 JDK11을 요구한다.

좀 더 하위버전에 해당하는 과거버전을 찾고자 하면 우측 MORE DOWNLOADS 항목에서 [Older Versions] 를 클릭한다.

Neon 버전을 받을 예정이다. Neon 클릭.



Eclipse Neon 항목에서 제일 위의 3 Packages 클릭.




Eclipse for PHP Developers 항목에서 [Windows 32-bit x86_64] 클릭해서 php용 이클립스를 다운로드 받는다.


 

다운로드받은 php용 이클립스는 특정위치에 압축을 해제하면 바로 사용 가능하다.

개인적으로 C:\coding\eclipse_php 위치에 압축해제했다.

(eclipse.exe 파일위치가 C:\coding\eclipse_php\eclipse.exe 가 되도록 위치시킴)

2. php 설치

원하는 버전에 맞게 php를 설치한다. 여기서는 php7.0.0 을 설치한다.

http://windows.php.net/download/ 에 접속한다. 과거버전을 받기 위해 Past release 버튼을 클릭한다.

또는 곧바로 https://windows.php.net/downloads/releases/archives/ 에 접속한다.



php-7.0.0 텍스트를 찾는다.

5 종류가 있다.

php-7.0.0-nts-Win32-VC14-x64.zip : 64비트용 nts(Non Thread Safety) 버전

php-7.0.0-nts-Win32-VC14-x86.zip : 32비트용 nts(Non Thread Safety) 버전

php-7.0.0-src.zip : php 소스파일

php-7.0.0-Win32-VC14-x64.zip : 64비트용 ts(Thread safety) 버전

php-7.0.0-Win32-VC14-x86.zip : 32비트용 ts(Thread safety) 버전

보다시피 nts 버전과 ts 버전이 제공되고 있는데, 차이는 간단하다.

IIS 웹서버 위에 FastCGI로 PHP를 사용하는 경우에는 NTS버전을 사용하면 된다.

Apache 웹서버 위에 PHP를 사용하는 경우에는 TS버전을 사용하면 된다.

여기서는 아파치를 사용할 것이라서 ts 버전을 다운로드 받으면 된다.

즉 64비트 컴퓨터 사용자는 php-7.0.0-Win32-VC14-x64.zip 파일을, 32비트 컴퓨터 사용자는 php-7.0.0-Win32-VC14-x86.zip 파일을 다운로드받으면 된다.

 

다운로드받은 압축파일을 원하는 위치에 압축해제한다.

개인적으로 C:\coding\php7 위치에 압축해제했다.

(php.exe 파일위치가 C:\coding\php7\php.exe 가 되도록 위치시킴)

3. php 설정파일 수정

[php7 설치폴더]/php.ini-production 파일을 복사/붙여넣기해서 같은 위치에 php.ini 파일을 만든다.

php.ini 파일 내용을 수정한다.

ex) C:\coding\php7\php.ini

[AS-IS] (721라인 부근)

; Directory in which the loadable extensions (modules) reside.

; http://php.net/extension-dir

; extension_dir = “./”

; On windows:

; extension_dir = “ext” 

[TO-BE]

; Directory in which the loadable extensions (modules) reside.

; http://php.net/extension-dir

; extension_dir = “./”

; On windows:

; extension_dir = “ext”

extension_dir = “C:/coding/php7/ext”

이때 extension_dir 의 값은 php7 이 위치한 실제경로에 맞게 입력해야 한다.

4. 아파치​ 웹서버 다운로드

php를 구동하기 위한 아파치 웹서버를 다운로드해야 한다.

아파치 2.4 버전을 사용하기로 했다.

apache_2.4.4-x64-openssl-1.0.1e.msi (64비트용)

다운로드 링크 : http://www.mediafire.com/file/utf9k7qf64equt6/apache_2.4.4-x64-openssl-1.0.1e.msi

apache_2.4.4-x86-openssl-1.0.1e.msi (32비트용)

다운로드 링크 : http://www.mediafire.com/file/1l78i2tve4vsowy/apache_2.4.4-x86-openssl-1.0.1e.msi

설치 과정에서 domain, server name, email 등을 입력하라고 나오는데 그냥 아무 값이나 입력하고 진행하면 된다.

전부 test라고 입력하고 넘어가도 상관없다.

설치 과정에서 설치되는 위치를 지정할 수 있다.

기본 경로로 설치해도 상관없고, 개인적으로 C:\coding\php_server\Apache2.4 위치에 설치했다.

4-1. 아파치 2.4 버전 EnableMMAP 관련 충돌방지

참고로 윈도우용 아파치 2.4 버전의 경우 일종의 속도느림 버그같은게 있어서 아래 내용을 적용하는 것을 권장한다.

아파치 2.4 기준 httpd.conf 파일을 아래와 같이 수정하면 된다. ([Apache 설치폴더]\conf\httpd.conf 파일 수정)

[AS-IS]

#EnableMMAP off

#EnableSendfile on

[TO-BE]

EnableMMAP off

EnableSendfile off

AcceptFilter http none

AcceptFilter https none

위 내용에 대한 자세한 내용은 https://blog.naver.com/bb_/222315194371 에서 확인할 수 있다.

5. 아파치(apache)와 php 연동

[Apache 설치폴더]/conf/httpd.conf 파일 내용을 수정한다.

ex) C:\coding\php_server\Apache2.4\conf\httpd.conf


파일 최하단에 아래 내용을 추가한다.

LoadModule php7_module “C:\coding\php7\php7apache2_4.dll”

AddType application/x-httpd-php .php .html


이때 php7_module 의 값은 php7 이 위치한 실제경로에 맞게 입력해야 한다.



6. 아파치 웹서버 재기동 및 php 실행 확인

[Apache 설치폴더]/htdocs에 phpinfo.php 파일을 만든다.

파일 내용은 다음과 같이 입력한다.

<?php phpinfo(); ?>

이후 아파치 웹서버를 재기동하자.

아파치 재기동 방법은 작업표시줄의 아파치 아이콘(자주색 깃털) 위에서 마우스 우클릭 – [Open Apache Monitor] 메뉴를 클릭



Apache Service Monitor 창이 뜨면 우측의 [Start] 버튼을 클릭하면 아파치가 기동된다.


만약 이미 웹서버가 기동 중이라서 Start 버튼이 비활성화 상태라면, [Stop] – [Start] 버튼을 차례로 클릭하거나, [Restart] 버튼을 클릭하면 아파치 웹서버를 재기동할 수 있다.


아피치 웹서버를 재기동했으면 http://localhost/phpinfo.php 에 접속해서 페이지가 잘 뜨는지 확인한다.

 

7. php용 이클립스 실행 및 관련설정

php용 이클립스를 실행한다.


이클립스 상단메뉴의 [Window] – [Preferences] 메뉴를 클릭한다.



Preferences 윈도우가 뜨면 좌측트리의 [PHP] – [PHP Executables] – [Add] 버튼을 클릭한다.



New PHP Executable 윈도우가 뜨면 Name 에 PHP 7.0 을 입력, Executable path 에 [php설치경로\php.exe] 를 입력 (ex : C:\coding\php7\php.exe), PHP ini file (optional) 에 [php설치경로\php.ini] 를 입력 (ex : C:\coding\php7\php.ini)하고 하단의 [Finish] 버튼을 클릭한다. 



이어서 Preferences 윈도우 좌측트리의 [PHP] – [Servers]에 들어가서 기존 서버를 클릭하고 [Edit] 버튼을 클릭한다.

기존 서버가 없다면 [New] 버튼을 클릭해서 만들면 된다.



Document Root의 위치를 [Apache 설치경로\htdocs] 위치로 지정한다. (ex : C:\coding\php_server\Apache2.4\htdocs)




8. 이클립스 프로젝트 생성 및 php 실행 확인

​이클립스 상단 메뉴의 [File] – [New] – [PHP Project] 메뉴를 이용해 새 php 프로젝트를 생성한다.

 


프로젝트 내에 test.php 파일을 생성한다.

파일 내용은 다음과 같이 입력한다.

<?php phpinfo(); ?>


test.php 파일 마우스 우클릭 – [Run As] – [PHP Web Application] 메뉴 클릭



http://localhost/[프로젝트명]/phpinfo.php 에 접속해서 페이지가 잘 뜨는지 확인한다.

물론 아파치 웹서버가 기동되어있는 상태여야 한다.


9. Configuration File 확인

phpinfo.php 페이지에 접속했을 때 “Loaded Configuration File” 항목의 값이 (none)이라면 php.ini 파일이 제대로 로드되지 않은 것이다.


 

9-1. php.reg 파일 작성 및 실행

원하는 위치에 아래 내용으로 php.reg 파일을 작성한다.

Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SOFTWARE\PHP]

“IniFilePath”=”C:\coding\php7”

“IniFilePath”의 내용은 실제 php가 설치된 위치여야 한다.

이후 php.reg 파일을 실행한다. 경고 메시지가 나오면 [예]를 클릭한다.

9-2. 레지스트리 확인

[시작] – [실행] – [regedit] 를 실행한다.

컴퓨터\HKEY_LOCAL_MACHINE\SOFTWARE\PHP 위치에 들어가서 IniFilePath 값이 제대로 들어갔는지 확인한다.



 

필자의 경우 값이 제대로 들어가지 않아서 여기서 수동으로 다시 추가했다.

마우스 우클릭 – [새로 만들기] – [문자열 값]으로 만들면 된다.

아파치 웹서버를 재기동해본다.

phpinfo.php 페이지에 접속했을 때 “Loaded Configuration File” 항목의 값이 php.ini 파일경로면 성공이다.

 

참고사이트 1 : https://blog.naver.com/sakura_pink/221017504361

참고사이트 2 : https://mkklab.tistory.com/8

참고사이트 3 : https://arisu1000.tistory.com/27706

[WordPress] 워드프레스 글에서 br 태그 사라지는 현상

[Wordpress] 워드프레스 글에서 br 태그 사라지는 현상

워드프레스 게시글 내용이 br 태그가 포함되어 있는데 글 읽기 시 제거되어 글 내용이 나오는 문제가 있었다. DB 상(wp_posts 테이블의 post_content 컬럼)에는 <p><br><p>이라고 들어있어도 실제로 글을 읽으면 <p></p>로 표시되는 문제였다.

구글링 결과 wpautop 함수의 영향이라고 나왔다.

wpautop 함수가 있는 파일은 워드프레스 버전에 따라 파일명이 다소 다른 것 같다.

워드프레스 2.0 의 경우 wp-includes/functions-formatting.php 파일이라고 한다.

워드프레스 폴더 전체를 대상으로 wpautop 를 내용검색해서 파일을 찾았다.

워드프레스 폴더 하위 wp-includes/formatting.php 파일을 수정했다. (워드프레스 4.19.16 버전)

[AS-IS]

function wpautop( $pee, $br = true ) {

    $pre_tags = array();

    if ( trim($pee) === ” )

        return ”;

    // Just to make things a little easier, pad the end.

    $pee = $pee . “\n”;

(중략)

[TO-BE]

function wpautop( $pee, $br = true ) {

    return $pee;


    $pre_tags = array();

    if ( trim($pee) === ” )

        return ”;

    // Just to make things a little easier, pad the end.

    $pee = $pee . “\n”;

(중략)

인자로 넘어온 변수 그대로 리턴하도록 수정했다.

참고사이트 : http://dobiho.com/466/

[WordPress] 워드프레스 HTTP ERROR 500 에러

[Wordpress] 워드프레스 HTTP ERROR 500 에러

워드프레스에서 아래와 같이 500 에러가 발생한 경우.

페이지가 작동하지 않습니다.

현재 주소에서 요청을 처리할 수 없습니다.

HTTP ERROR 500


 

500 에러는 서버 단에서 발생한 에러다.

그런데 이와 같이 500 에러라고만 나오는 상황에서는 왜 에러가 발생했는지 확인이 불가능하다.

500 에러가 발생하는 세부이유를 확인하려면 FTP로 접속해서 wp-config.php 파일을 수정해야 한다.

워드프레스 폴더 하위의 wp-config.php 파일을 수정한다.

[AS-IS]

define(‘WP_DEBUG’, false);

[TO-BE]

define(‘WP_DEBUG’, true);

이렇게 수정하면 아래와 같이 워드프레스의 에러 메시지가 화면에 표시된다.

 

이러한 에러 메시지를 로그 파일(/wp-content/debug.log 파일)에 기록하려면 아래 코드를 추가하면 된다.

define(‘WP_DEBUG_LOG’, true);

만약 /wp-content/debug.log 파일에만 에러 메시지를 기록하고 화면에는 표시하지 않으려면 아래 코드를 추가하면 된다.

define(‘WP_DEBUG_DISPLAY’, false);

이렇게 500 에러 발생에 대한 에러 메시지를 볼 수는 있었지만 저 에러 메시지를 보고 php 파일을 고쳐서 해결하기는 어려워보였다.

내 문제는 테마를 업데이트한 이후 워드프레스 사이트에 접속하면 500 에러가 뜨는 현상이었다.

wp-config.php 파일을 제외한 모든 파일을 설치하기 이전 워드프레스 파일들로 덮어썼더니 문제가 해결됐다(혹시나 적어두는데 덮어쓰기 전 기존 폴더는 확실하게 백업해둬야 한다).

테마가 버전 업이 되면서 낮은 버전의 워드프레스와 충돌이 일어난 것 같다.

참고사이트 : http://blog.naver.com/PostView.nhn?blogId=chan2rrj&logNo=221223664194

[한글2010] 한글 문서 열었을 때 "[숨은 설명]을 제외하고 문서를 열까요?" 메시지 나오지 않도록 설정하기

[한글2010] 한글 문서 열었을 때 “[숨은 설명]을 제외하고 문서를 열까요?” 메시지 나오지 않도록 설정하기

한글 문서를 열었을 때 “[숨은 설명]을 제외하고 문서를 열까요?” 메시지 나오지 않도록 설정하는 방법이다.

아래처럼 메시지가 나타나는 경우다.

문서 보안 설정이 [높음]으로 되어 있어 [숨은 설명]을 불러오지 않습니다.

[숨은 설명]을 불러오지 않은 상태에서 문서를 저장하면 문서에 포함된 [숨은 설명]이 모두 손실됩니다.

[숨은 설명]을 유지하려면 [문서 보안 설정]을 [낮음]으로 설정해야 합니다.

[숨은 설명]을 제외하고 문서를 열까요?

 

이 메시지가 뜨면 [보안 설정] 버튼을 클릭하자.

문서 보안 설정 창이 뜨면 라디오 버튼 [낮음(권장하지 않음)]을 선택하고 [설정] 버튼을 클릭하면 된다.

이제 다음부터는 “[숨은 설명]을 제외하고 문서를 열까요?” 메시지가 뜨지 않는다.

또는 다른 방법도 있다.

한글 프로그램 상단 [보안] 메뉴 -> [문서 보안 설정] 메뉴를 클릭한다.
 

문서 보안 설정 창이 뜨면 (마찬가지로) 라디오 버튼 [낮음(권장하지 않음)]을 선택하고 [설정] 버튼을 클릭하면 된다.

이후로는 “[숨은 설명]을 제외하고 문서를 열까요?” 메시지가 뜨지 않는다.

2021 스마트민방위교육 평가 답안

2021 스마트민방위교육 평가 답안 

1. 다음 중 민방위 대원은?

① 만 20세~만 40세까지의 대한민국 남성 ★

② 예비군

③ 군인

④ 외국인

2. 민방위 교육에 대한 설명으로 적절하지 않은 것은?

① 민방위 교육은 연차에 따라 교육방법이 다르게 적용 된다

② 민방위 5년차 이상 대원은 연 1회 1시간 교육을 받아야 한다

③ 만 40세까지 민방위교육을 받아야 한다

④ 민방위 교육은 받고 싶을 때만 받아도 된다 ★

3. 다음 중 민방위 단위대 제대편성 기준은?

① 대원 인원수를 기준으로 편성한다 ★

② 대장이 정한다

③ 통합해서 운영한다

④ 동네별로 편성한다

4. 다음 중 민방위대 분대편제에 대한 설명으로 적절한 것은?

① 사태발생 시 효과적인 대응을 위함이다 ★

② 대원 간 의사소통을 차단하기 위함이다

③ 감시체계를 확고히 하기 위함이다

④ 책임소재를 확실히 해서 벌을 주기 위함이다

5. 다음 중 민방위대 동원명령을 발령할 수 있는 경우는?

① 민방위사태가 발생하거나 발생할 우려가 있다고 인정할 때 ★

② 예비군 중대장이 필요하다고 인정할 때

③ 통·리장이 필요하다고 인정할 때

④ 학교장이 필요하다고 인정할 때

6. 다음 중 핵 및 화생방전에 대비한 설명으로 적절하지 않은 것은?

① 화학 가스는 공기보다 무겁기 때문에 고지대로 대피해야 한다

② 핵 공격시 민방공 대피소나 가까운 지하시설로 대피한다

③ 오염된 물은 마셔도 된다 ★

④ 수상한 물건이 발견될 시 즉시 112, 119에 신고한다

7. 다음 중 적 공격시 발령되는 경보는 ?

① 공습경보 ★

② 화재경보

③ 해제경보

④ 일반경보

8. 완강기 구매시 로프 길이는?

① 100m

② 500m

③ 1000m

④ 지면과 완강기 길이를 먼저 확인하고 구입 ★

9. 심정지 환자 발생 시 행동요령으로 적절하지 않은 것은 ?

① 가족에게 먼저 연락한다 ★

② 주변사람에게 119신고 요청

③ 주위에 사람이 없다면 직접 119신고

④ 자동심장충격기 요청

10. 소화기 사용법으로 맞는 것은?

① 소화기는 옮기면 안된다

② 호스를 잡고 안전핀을 뽑는다

③ 바람이 부는 곳을 향해서 뿌린다

④ 손잡이를 힘껏 움켜쥐고 빗자루로 쓸 듯이 뿌린다 ★

11. 지진 발생 시 행동요령으로 적절하지 않은 것은?

① 엘리베이터를 절대 이용하지 않는다

② 건물, 나무, 고가도로, 전선 아래 등은 피한다

③ 라디오나 공공기관의 안내에 따른다

④ 차량을 이용하여 대피한다 ★

12. 지진해일의 특성에 대한 설명으로 적합하지 않은 것은?

① 지진해일에 의한 해면의 진동은 10시간 이상 지속되기도 한다

② 해안 부근에서는 지진해일 증폭이 매우크다

③ 지진해일은 우리나라에서 절대 발생하지 않는 자연재해 중 하나이다 ★

④ 지진해일 특보가 해제될 때까지 안전한 대피장소에 머무는 것이 중요하다

13. 태풍이 지나간 후 행동요령으로 잘못된 것은?

① 파손된 상하수도 관계기관 신고

② 바닥에 떨어진 전선 밟기 ★

③ 가스 누출될 수 있으므로 환기 후 출입

④ 전기, 가스, 수도 안전점검

14. 화학·생물테러 시 나타나는 증상이 아닌 것은?

① 호흡곤란

② 특별한 증상이 없다 ★

③ 근육경련

④ 고열

15. 민방위 훈련 시 임해야 할 태도로 알맞은 것은?

① 대충 한다

② 나대신 다른 사람이 잘 하겠지 생각한다

③ 실제 상황처럼 훈련에 임한다 ★

④ 적당히 임한다

16. 코로나19 예방을 위해 해야 할 행동이 아닌 것은?

① 발열,기침 등 증상이 있으면 자가격리한다

② 마스크를 잘 쓰고 다닌다

③ 출입명부 작성을 철저히 한다

④ 수돗물을 그냥 마신다 ★

17. 다음 중 민방위대원 설명으로 적절하지 않은 것은?

① 대한민국 국적을 가진 탈북자는 민방위대원이 될 수 없다 ★

② 대한민국 국민인 남성으로 구성한다

③ 민방위대원은 20세가 되는 1월 1일부터 40세가 되는 해의 12월 31일까지이다

④ 여성도 지원에 의해 민방위 대원이 될 수 있다

18. 민방위대원의 교육훈련에 대한 설명으로 맞는 것은?

① 민방위사태 사전예방과 응급조치 등 국민행동 요령 교육 ★

② 군사교육

③ 건축설계 교육

④ 세무회계 교육

19. 다음 중 정당한 사유 없이 교육훈련에 불참하였을 경우 부과되는 과태료는?

① 1억원

② 5천만원

③ 3천만원

④ 30만원 이하 ★

20. 심폐소생술 가슴압박 시 적절한 압박 회수는 ?

① 분당 10번

② 시간당 20번

③ 분당 100~120회 속도로 압박한다 ★

④ 시간에 구분 없이 적당히 하면 된다

[iOS] 아이폰 앱(ipa) 설치 실패 : 무결성을 확인할 수 없기 때문에 이 앱을 설치할 수 없습니다.

[iOS] 아이폰 앱(ipa) 설치 실패 : 무결성을 확인할 수 없기 때문에 이 앱을 설치할 수 없습니다.

 

plist 파일을 통해 아이폰 앱(확장자 ipa) 설치 시 “무결성을 확인할 수 없기 때문에 이 앱을 설치할 수 없습니다.” 오류 메시지가 발생하면서 앱이 설치되지 않는 문제가 있었다.

애플 엔터프라이즈 계정을 통해 인하우스(in-house) 방식으로 빌드한 앱(ipa 파일)이었다.

다른 아이폰 사용자들은 문제가 없는데 특정 아이폰에서만 해당 오류가 발생하는 경우였다.

다시 말해 앱의 문제가 아니라 특정 폰의 문제로 보였다.

검색을 해도 국내 페이지에서는 내용이 없어서 영어로 검색했더니 결과가 조금 있었다. “무결성을 확인할 수 없기 때문에 이 앱을 설치할 수 없습니다.” 대신 “This app cannot be installed because its integrity could not be verified.” 문장으로 구글링해보기 바란다.

정확한 원인은 알 수 없었다. 검색해서 나온대로 앱 빌드번호를 높여서 앱을 다시 archive했다.

이후 사용자가 앱을 삭제하고 다시 설치했더니 해결됐다고 한다.

Xcode 에서 바꾼 것은 General 탭 identity 의 Version, Build 값만 바꿨다. 기존 Version, Build 1.5 였던 것을 둘 다 1.6 으로 올려서 다시 배포했다. Display Name(앱 이름), Bundle Identifier(패키지명) 은 그대로 유지했다.

참고사이트 : https://developer.apple.com/forums/thread/668447

[SpringBoot] 이클립스(STS) 에서 스프링부트(Spring Boot) jar, war 파일 배포하기

[SpringBoot] 이클립스(STS) 에서 스프링부트(Spring Boot) jar, war 파일 배포하기

우선 이클립스(STS) 에서 만든 스프링부트(Spring Boot) 프로젝트가 있어야 한다.

스프링부트 프로젝트가 없다면 다음 포스트를 참고해서 만들면 된다. ([SpringBoot] 이클립스(STS) 에서 스프링부트(Spring Boot) 시작하기 : https://blog.naver.com/bb_/222141978468 )

1. 배포 확장자 설정(war 또는 jar)

jar 파일로 배포할 것인지 war 파일로 배포할 것인지 결정해서 pom.xml 을 열고 입력하자.

<packaging> 태그값을 바꾸면 된다. 기존에 해당 태그가 없다면 추가하자.

나는 war를 선택했는데 이유는 jsp를 사용하기 위해서다.

STS 상에서 기동하면 jsp가 잘 나오더라도, jar로 만들어서 java -jar 로 기동하면 jsp가 나오지 않고 Whitelabel Error Page 에러가 발생한다. jsp를 사용하려면 war로 묶도록 하자. (참고사이트 : https://regyu.tistory.com/3 )



 

2. 메이븐(Maven) 빌드 설정

이클립스(STS) 에서 스프링부트(Spring Boot) 프로젝트를 연다.

좌측트리의 프로젝트 폴더 위에서 마우스 우클릭 – [Run As] – [Maven build] 항목을 클릭하면 메이븐 빌드 설정할 수 있는 Run Configurations 윈도우가 뜬다.

한 번 빌드 설정을 세팅하고 나면 [Maven build] 항목을 클릭 시 곧바로 메이븐 빌드가 실행된다.

이후에 빌드 설정을 바꾸려면 맨아래 [Run Configurations…] 항목을 클릭하자.


3. 메이븐 빌드 설정 : Main 탭

Run Configurations 윈도우가 떴으면, 좌측 트리에서 메이븐 빌드(Maven Build) 항목을 선택한다.

Main 탭은 Goals 값을 “package” 로 바꿔준다.

Profiles 는 기존값으로 pom.xml 이 들어가 있는데 값을 지우자. 값을 지우지 않으면 빌드할 때 에러가 발생할 수 있다.



4. 메이븐 빌드 설정 : JRE 탭

JRE 탭에서는 JRE 가 아닌 JDK 를 지정하도록 한다.

그냥 JRE 로 진행하면 아래 에러가 발생할 수 있다.

[ERROR] No compiler is provided in this environment. Perhaps you are running on a JRE rather than a JDK?


 


5. 메이븐 빌드 설정 : Common 탭
Common 탭에서는 Encoding 값을 UTF-8 로 맞춰준다.


 

6. 메이븐 빌드 수행 

이후 창 하단의 Run 버튼을 누르면 jar 또는 war 파일이 특정 위치에 생성된다.

jar 또는 war 파일이 생성되는 위치는 이클립스(STS) 콘솔 로그에 출력된다.

7. 윈도우에서 기동하기

로컬환경(윈도우 OS)에서 기동 테스트해본다.

cmd 를 열고 java -jar [파일명].war 또는 java -jar [파일명].jar 명령어를 사용하면 된다.

  

 

8. 리눅스에서 기동하기

윈도우에서와 마찬가지로 [java -jar 파일명] 명령어를 사용하면 기동된다.

이후 Ctrl + C를 누르거나 터미널이 끊기면 WAS가 내려가므로, nohup 명령어로 기동하는 것을 권장한다.

nohup 은 프로그램을 백그라운드에서 실행하는 명령어다.

nohup java -jar [파일명].war 2>&1 &

또는

nohup java -jar [파일명].jar 2>&1 &

참고사이트 : https://www.leafcats.com/178

[Spring] @RequestMapping 정규식, 정규표현식

[Spring] @RequestMapping 정규식, 정규표현식

@RequestMapping 정규식은 중괄호를 이용하면 된다.

중괄호 안쪽 정규식에 해당하는 부분이 변수명에 매핑된다.

1. @RequestMapping 정규표현식 문법


@RequestMapping(value = “{변수명:정규식}”)

public void handle(@PathVariable String 변수명) {

    // (중략)

}

2. @RequestMapping 정규표현식 예시

실제 적용은 아래처럼 하면 된다.

// 소문자 주소 매핑 ex) http://localhost:8080/abced

@RequestMapping(value = “/{path:[a-z]*}”)

public void handleLowerCase(@PathVariable String path) {

    // (중략)

}

// 대문자 주소 매핑 ex) http://localhost:8080/ABCDE

@RequestMapping(value = “/{path:[A-Z]*}”)

public void handleUpperCase(@PathVariable String path) {

    // (중략)

}

// 숫자 주소 매핑 ex) http://localhost:8080/12345

@RequestMapping(value = “/{path:[0-9]*}”)

public void handleNumber(@PathVariable String path) {

    // (중략)

}

다음 예시처럼 중괄호를 여러 개 사용하면 복잡한 정규식도 적용할 수 있다.

@RequestMapping(“/spring-web/{symbolicName:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{extension:\\.[a-z]+}”)

public void handle(@PathVariable String version, @PathVariable String extension) {

    // (중략)

}

참고사이트 : https://docs.spring.io/spring-framework/docs/3.2.16.RELEASE/spring-framework-reference/html/mvc.html

[Apache] (OS 64)지정된 네트워크 이름을 더 이상 사용할 수 없습니다. : AH00341: winnt_accept: Asynchronous AcceptEx failed.

[Apache] (OS 64)지정된 네트워크 이름을 더 이상 사용할 수 없습니다.  : AH00341: winnt_accept: Asynchronous AcceptEx failed.

윈도우 서버에서 아파치 웹서버 2.4 버전을 띄우고 일정 시간이 지나면 접속되지 않는 현상이 있었다.

이때 error.log 를 열어보면(ex : C:\Apache2.4\logs\error.log) 아래 오류가 발견되었다.

(OS 64)지정된 네트워크 이름을 더 이상 사용할 수 없습니다.  : AH00341: winnt_accept: Asynchronous AcceptEx failed.

윈도우 기반 환경에서 아파치를 구동시 EnableMMAP 설정과 관련해 충돌이 일어나는 것이 원인이라고 하는데 정확한 원인은 파악할 수 없었다.

아파치 2.4 기준 httpd.conf 파일을 아래와 같이 수정하고(ex: C:\Apache2.4\conf\httpd.conf) 재기동하면 문제가 해결된다.

[AS-IS]

#EnableMMAP off

#EnableSendfile on

[TO-BE]

EnableMMAP off

EnableSendfile off

AcceptFilter http none

AcceptFilter https none

참고사이트 : https://blog.daum.net/dosman1/1707

[JAVA] containsIgnoreCase

[JAVA] containsIgnoreCase

대소문자 상관없이 문자열 포함관계 비교.

public static boolean containsIgnoreCase(String str1, String str2) {
    if (str1 == null || str2 == null) {
        return false;
    }

    return str1.toLowerCase().contains(str2.toLowerCase());
}

[게임] 디아블로1(Diablo1) 쉐어웨어 버전

[게임] 디아블로1(Diablo1) 쉐어웨어 버전

디아블로1의 쉐어웨어 버전을 크롬 브라우저에서 플레이할 수 있다.

미쳤다… 어떻게 작동 가능한 건지 상상도 안간다.

세이브/로드도 된다.

전사 캐릭터로만 플레이할 수 있고, 던젼 2까지 플레이 가능하다고 한다.

https://d07riv.github.io/diabloweb/ 주소에 접속하면 된다.

[Play Shareware] 버튼을 클릭하면 시작한다.

[JAVA] 자바에서 파일 내용 지우는 방법

[JAVA] 자바에서 파일 내용 지우는 방법

자바에서 특정 경로의 파일 내용을 지우는 방법이다. 다시 말해 파일은 존재하되 빈 파일로 만드는 방법이다.

단 한 줄이면 된다.

new FileOutputStream(FILE_PATH).close();

참고사이트 : https://www.baeldung.com/java-delete-file-contents

[Chrome] 크롬 노란색 블록이 생김. 글자 지우면 반복 입력되는 오류 해결

[Chrome] 크롬 노란색 블록이 생김. 글자 지우면 반복 입력되는 오류 해결

크롬에서 한글 문자열을 입력할 때 노란색 블록이 잡히는 현상. 백스페이스 키로 문자열을 지우려고 시도할 때마다 글자가 복제되어 반복 입력되는 현상이다.

아래는 구글 사이트에서 “검색어”라고 입력하려고 시도하는 모습. 도저히 “검색어”를 원하는대로 입력할 수 없다.

 

1. Microsoft 입력기 선택

한글과컴퓨터의 한글 프로그램이 설치되어있는 경우, 윈도우 키 + Space키를 누르면 한컴 입력기와 Miscosoft 입력기 중에 입력기를 선택할 수 있는데, 이때 Microsoft 입력기를 선택하면 해결된다.

 

2. 크롬 실험기능 사용하지 않도록 리셋

크롬 주소창에 chrome://flags/#enable-npapi 을 입력해서 들어간 후, 우측 상단의 Reset all 버튼을 클릭하면 해결된다.

 

참고사이트 : https://blog.naver.com/pilg99/221612299373

[Windows] 윈도우서버 mstsc 멀티세션 해제 방법 (접속할 때마다 새로운 바탕화면이 나타나는 경우)

[Windows] 윈도우서버 mstsc 접속할 때마다 새로운 바탕화면이 나타나는 경우 (멀티세션 해제 방법)

원격 데스크톱 프로그램(mstsc)으로 원격지 윈도우 서버에 접속 시 계속 새로운 바탕화면이 나타나서 난감할 때가 있다. mstsc 로 접속할 때마다 기존에 작업하던 내용이 사라지고 깨끗한 초기 바탕화면이 나오는 경우다.

이는 윈도우 서버에 멀티세션(Multi session) 정책이 설정되어 있기 떄문이다.

1. 윈도우서버 mstsc 멀티세션 해제 방법

아래처럼 설정하면 멀티세션이 해제되고 기존 바탕화면 1개만 볼 수 있게 된다.

1) [시작] – [실행] (또는 윈도우키 + R키) 창 띄우기

2)  gpedit.msc 입력하고 실행

 

3) [로컬 그룹 정책 편집기] 창 좌측 트리에서 [로컬 컴퓨터 정책] – [컴퓨터 구성] – [관리 템플릿] – [Windows 구성 요소] – [터미널 서비스] – [원격 데스크톱 세션 호스트] – [연결] 폴더 선택

4) 우측 목록에서 [원격 데스크톱 서비스 사용자를 하나의 원격 데스크톱 서비스 세션으로 제한] 항목을 더블클릭


 

5) [사용 안 함] 라디오 버튼을 선택하고 [확인] 버튼 클릭

 

2. 기존 사용자 계정 연결 끊는 방법, 사용자 전환 방법

위 정책을 바꾸지 않고 사용하려면 바탕화면 하단 작업표시줄 마우스 우클릭 – [작업 관리자] – [사용자] 탭에서 기존 사용자로 전환할 수 있다. 마우스 우클릭하여 [연결 끊기] 메뉴를 통해 기존 사용자의 연결을 끊을 수도 있고, 사용자 전환을 할 수도 있다.

 

3. [연결 개수 제한] 변경 방법

마지막으로 [로컬 그룹 정책 편집기] 에서 [연결 개수 제한] 항목을 수정하는 방법도 있다. 연결 개수를 2~3개 정도로 적게 잡아서 사용하면 덜 헷갈리고 사용자 전환하기도 용이하다.

참고사이트 : https://studyforus.tistory.com/145

[Javascript] DOM 자식요소를 첫번째 요소로 추가하는 방법 (prepend)

[Javascript] DOM 자식요소를 첫번째 요소로 추가하는 방법 (prepend)

자바스크립트로 DOM 요소(엘리먼트) 하위에 자식요소를 추가하기 위해서는 appendChild 함수를 사용하면 된다.

DOM 자식요소를 마지막 요소로 추가하는 방법이다.

다시 말해 appendChild 함수를 사용하면 기존 엘리먼트 하위 가장 마지막 부분(끝 부분)에 새 엘리먼트가 삽입된다.

var pNode = document.createElement(“p”);

pNode.id = “test”;

document.body.appendChild(pNode);

반대로 기존 엘리먼트 하위 시작 부분(처음 부분)에 추가하기 위해서는 prepend 를 사용하면 된다. (ES6 이상의 경우)

var pNode = document.createElement(“p”);

pNode.id = “test”;

document.body.prepend(pNode);

ES6 이상이 아닌 경우(prepend 함수를 지원하지 않을 경우) appendFirst 라는 함수를 HTMLElement 프로토타입에 추가하여 사용하면 된다.

HTMLElement.prototype.appendFirst = function(childNode) {

    if (this.firstChild) {

        this.insertBefore(childNode, this.firstChild);

    } else {

        this.appendChild(childNode);

    }

};

var pNode = document.createElement(“p”);

pNode.id = “test”;

document.body.appendFirst(pNode);

HTMLElement 프로토타입을 변경하고 싶지 않다면 appendFirst 함수를 따로 만들어서 사용하면 된다.

function appendFirst(node, childNode) {

    if (node.firstChild) {

        node.insertBefore(childNode, node.firstChild);

    } else {

        node.appendChild(childNode);

    }

};

var pNode = document.createElement(“p”);

pNode.id = “test”;

appendFirst(document.body, pNode);

참고사이트 1 : https://stackoverflow.com/questions/2007357/how-to-set-dom-element-as-the-first-child

참고사이트 2 : https://developer.mozilla.org/ko/docs/Web/API/ParentNode/prepend

[JAVA] \x 형태로(\xc1\xf6\xc1 …) 한글깨짐, 한글 깨진 경우 복원하는 방법

[JAVA] \x 형태로(\xc1\xf6\xc1 …) 한글깨짐, 한글 깨진 경우 복원하는 방법

한글이 다음과 같은 형태로 깨져있을 때는 EUC-KR 로 디코딩해서 원래 내용을 복원할 수 있다.

\xc1\xf6\xc1\xa4\xb5\xc8 \xb8\xf0\xb5\xe2\xc0\xbb \xc3\xa3\xc0\xbb \xbc\xf6 \xbe\xf8\xbd\xc0\xb4\xcf\xb4\xd9.

● 자바(JAVA)에서 \x 형태로(\xc1\xf6\xc1 …) 깨진 한글 복원하는 방법

try {
    String str = “\\xc1\\xf6\\xc1\\xa4\\xb5\\xc8 \\xb8\\xf0\\xb5\\xe2\\xc0\\xbb \\xc3\\xa3\\xc0\\xbb \\xbc\\xf6 \\xbe\\xf8\\xbd\\xc0\\xb4\\xcf\\xb4\\xd9.”;
    str = str.replace(“\\x“, “%”);
    System.out.println(URLDecoder.decode(str, “EUC-KR”));

} catch (Exception e) {
    e.printStackTrace();
}

● 파이썬(Python)에서 \x 형태로(\xc1\xf6\xc1 …) 깨진 한글 복원하는 방법

print unicode(“\xc1\xf6\xc1\xa4\xb5\xc8 \xb8\xf0\xb5\xe2\xc0\xbb \xc3\xa3\xc0\xbb \xbc\xf6 \xbe\xf8\xbd\xc0\xb4\xcf\xb4\xd9.”, encoding=’euc-kr’);

결과는 “지정된 모듈을 찾을 수 없습니다.”라고 나온다.

[Windows] 한글 안됨, 한글 안될때 gksrmf dksehla, gksrmf dksehlfeo

[Windows] 한글 안됨, 한글 안될때 gksrmf dksehla, gksrmf dksehlfeo

윈도우에서 한글 입력이 안될 때, 한영전환이 안될 때 아래와 같이 하면 된다.

1. 윈도우 좌측하단 [시작] – [실행] 또는 윈도우키 + R키를 눌러서 실행창을 띄운다.

2. 실행창에 [ctfmon.exe] 를 입력한다.

3. 한영전환키를 눌러서 한글 입력을 해본다.

참고사이트 : https://looxem.tistory.com/68 

[Eclipse] Path is not a working copy directory

[Eclipse] Path is not a working copy directory

이클립스 또는 STS 에서 SVN Commit 또는 Show History 등을 시도했을 때 Path is not a working copy directory 오류가 발생한다면, Cleanup을 실행하면 된다.

프로젝트 폴더 위에서 마우스 우클릭 – [Team] – [Refresh/Cleanup] 을 선택하면 된다.

Cleanup을 수행했더라도 또 다시 같은 오류가 발생하는 경우, 아까 선택한 폴더에서 다시 Cleanup을 해보고, 그 상위폴더에서도 Cleanup을 해본다.

참고사이트 : https://m.blog.naver.com/inasa/150067235341 

[Oracle] java.sql.SQLException ORA-03115: 지원되지 않은 네트워크 데이터 유형 또는 표현이 있습니다

[Oracle] java.sql.SQLException ORA-03115: 지원되지 않은 네트워크 데이터 유형 또는 표현이 있습니다

자바 코드에서 ResultSet rs = pstmt.executeQuery(sql); 을 ResultSet rs = pstmt.executeQuery(); 로 고치면 해결된다.

Statement는 executeQuery 메서드에 매개변수로 sql 스트링을 넘겨줘야 한다.

Statement stmt = conn.createStatement();

ResultSet rs = stmt.executeQuery(sql);

반면 PreparedStatement는 executeQuery 메서드에서 매개변수를 제거해야 한다.

PrepareStatement pstmt = conn.prepareStatement(sql);

ResultSet rs = pstmt.executeQuery();

참고사이트 : https://wakestand.tistory.com/71 

[iOS] 아이폰에서 ipa 다운로드 시 itms-services 링크 반응없음

[iOS] 아이폰에서 ipa 다운로드 시 itms-services 링크 반응없음

아이폰에서 itms-services 링크를 클릭했는데 아무 반응이 없는 경우. 또는 itms-services 링크를 클릭했는데 앱 아이콘은 생성되지만 설치가 진행되지 않는 경우.

엔터프라이즈 계정을 쓰는 어떤 고객사로부터 아이폰 앱의 인증서가 만료되었다는 연락을 받았다. 인증서를 갱신하고 앱을 새로 빌드했는데 다운로드 및 설치가 안되는 문제가 있었다.

앱은 정상적이었다. 맥과 아이폰을 연결하고 XCode 에서 실행을 해봐도 정상적으로 구동했다. XCode 에서 상단메뉴 [Window] – [Devices] 에 들어가서 [+] 버튼을 이용해 직접 ipa 파일을 추가해서 설치해도 마찬가지로 정상적이었다.

다만 정상적인 ipa 파일을 업로드해서 다운로드 받을 때 itms-services 링크를 클릭해도 반응이 없었다.

문제의 심각성을 느낀 건 그 이후인데, 정상적으로 앱을 다운로드 및 설치할 수 있었던 4~5개의 사이트가 모두 같은 현상을 보이고 있었다. 아이폰을 껐다 켜도 마찬가지고, 다른 아이폰으로 테스트해도 똑같았다.

확인결과 드랍박스 주소체계의 문제였다.

참고로 아이폰에서 ipa 파일을 다운로드 받기 위해서는 plist 와 ipa 파일을 준비해야 한다. plist 가 ipa 파일을 바라보게 하고 plist 를 itms-services 링크로 다운로드 받는 것이다. 무슨 얘기인지 모르겠으면 다음 포스트를 참고하면 된다. [iOS] 아이폰 ipa 다운로드 링크 방법 (plist 파일 작성방법) https://blog.naver.com/bb_/222136562978

이때 필자의 경우 ipa 파일과 plist 파일 모두 드랍박스에 업로드가 되어있는 상태였는데, 주소체계가 https://dl.dropbox.com/s/~ 일 때 문제가 됐다. 이를 https://dl.dropboxusercontent.com/s/~ 로 바꾸니까 해결됐다.

1. plist 파일의 링크주소가 itms-services://?action=download-manifest&url=https://dl.dropbox.com/s/~/test.plist 이면, 아이폰에서 링크를 클릭했을 때 아무 반응이 없었다. dl.dropbox.com 을 dl.dropboxusercontent.com 로 변경하면 해결된다.

2. 항목 1번을 해결했더라도, plist 내의 ipa 파일에 대한 링크주소가 https://dl.dropbox.com/s/~/test.ipa 이면, 앱 아이콘은 생성되지만 설치가 진행되지 않는다.

결론은 https://dl.dropbox.com 을 https://dl.dropboxusercontent.com 으로 바꾸면 해결된다.

이유는 잘 모르겠다. 둘 다 정상적인 주소이고 PC의 인터넷 브라우저로는 접근이 가능한데 말이다.

내 기억으로는 올해(2021년) 2월 중순까지는 문제가 없었다. 이후 아이폰 업데이트나 사파리 업데이트를 하지 않았으므로 아마 드랍박스의 링크관련 정책이 바뀌지 않았을까 생각이 든다.