CSS를 이용하여 테이블의 특정 column 보이지 않도록(hidden으로) 처리 하기


--------------------------------------------------

순번 | 이름 | 나이 | 성별 | 주소 | 전화번호 | 기타 |

--------------------------------------------------

  ...................... 중 략 ..................


위와 같은 테이블이 있다고 할때 코드는 아래와 같은 식이 될 것이다.


      <table>

        <thead>

          <th>순번</th>

          <th>이름</th>

          <th>나이</th>

          <th>성별</th>

          <th>주소</th>

          <th>전화번호</th>

          <th>기타</th>

        </thead>

        <tr>

          <td>순번 정보 표시...</td>

          <td>이름 정보 표시...</td>

          <td>나이 정보 표시...</td>

          <td>성별 정보 표시...</td>

          <td>주소 정보 표시...</td>

          <td>전화번호 정보 표시...</td>

          <td>기타 정보 표시...</td>

        </tr>

      </table>



이럴 경우 나이 항목(column), 나이 칼럼 전체가 보이지 않도록 즉 |순번|이름|성별|주소|... 와같이 보이도록 해야 할 경우 이를 CSS를 이용하면 아주 간단하게 처리가 된다.

즉 나이 칼럼이 hidden으로 처리되게 하는 것이다. 물론 이때 비록 보이지는 않지만 내부적으로는 나이 칼럼이 존재하고 이 값을 form 전송시 전송한다거나 하는 작업은 정상적으로 이뤄지게 된다.

이럴경우 다음과 같이 처리하면된다.


  <head>

      ... 중 략 ...

    <style type="text/css">

      ... 중 략 ...

      th:nth-of-type(3) { display: none; }   

      td:nth-of-type(3) { display: none; }

      ... 중 략 ...

    </style>

  </head>


nth-of-type()의 괄호안에 몇번째 칼럼을 보이지 않게 할지를 지정해 주면된다.




PHP에서 DB에 저장되어 있는 데이터를 가져와서 도표(table) 형태로 표시할 때 모든 행이 같은 색상이면 가독성의 측면에서 바람직하지 않을 것이다. 이럴때 홀수 행의 색상을 약간 다르게 표시하면 가독성이 훨씬 높아 질 것이다.

이럴 경우 CSS를 이용하면 매우 간단하게 처리를 할수가 있다.

아래와 같은 코드가 있다면 이 코드가 생성해 내는 테이블은 짝수 행의 색상이 약간 짙은 회색(#f2f2f2)으로 표현되어 한 행 건너마다 기본 색상과 다르게 표현되어 가독성이 훨신 좋아 질 것이다.

이러한 기능을 하는 핵심 코드가 아래에서 CSS의 #tmTable tr:nth-child(even){background-color: #f2f2f2;}이다.

물로 이를 위해서는 table의 id를 tmTable로 지정해 주어야 하는 것은 당연한 일이다.


<!DOCTYPE html>

<html lang="en" dir="ltr">

  <head>

    <meta charset="utf-8">

    <meta name="viewport" content="width=device-width, initial-scale=1">

    <title>TM현황</title>

    <style type="text/css">

     ... 중 략 ...

      #tmTable tr:nth-child(even){background-color: #f2f2f2;}

    </style>

  </head>

  <body>

    <div id="wrap">

      <h1>업체현황</h1>

      <a href="./login.html">로그인</a>

      <table id="tmTable">

        <thead>

          <th>No.</th>

          <th>날짜</th>

          <th>업체명</th>

          ... 중 략 ...

          <th>복합기</th>

        </thead>

    <?php

include_once 'config.php';

$conn = mysqli_connect(DB_HOST, DB_USER, DB_PASSWORD, DB_NAME);

if(!$conn){

echo "Error: Unable to connec to MySQL.".PHP_EOL;

echo "Debugging errno: ".mysqli_connect_errno().PHP_EOL;

echo "Debugging error: ".mysqli_connect_error().PHP_EOL;

exit;

}


if(mysqli_connect_errno()){

echo "Failed to connect to TM Database".mysqli_connect_error();

exit;

}


$query = "SELECT * FROM tm order by seq desc;";

$rt = mysqli_query($conn, $query);


while($row = mysqli_fetch_assoc($rt))

{

        echo "<tr>";

          echo "<td>".$row['seq']."</td>";

          echo "<td>".$row['year'].".".$row['month'].".".$row['day']."</td>";

          echo "<td>".$row['company']."</td>";

                            ... 중 략 ...

echo "<td>".$row['copier']."</td>";

        echo "</tr>";

}


mysqli_close($conn);

    ?>

      </table>

    </div>

  </body>

</html>



PHP로 웹 프로그래밍시 "미팅 일시"를 기준으로 DB의 정보를 정렬해서 웹에 보여준다고 가정할때 어떤 식으로 처리하면 좋을까?

예를 들어 미팅 일시는 2018.6.19 15:30과 같은 시간이라고 가정해 보자. DB의 특정 필드에 이 값 그대로를 저장한다면 추후에 미팅 일시 필드를 기준으로 오름차순이나 내림 차순으로 정렬하기가 불편해 지게 된다.

이럴 경우에 '타임스탬프'를 이용하면 모든 것이 깔끔해진다.


타임스탬프란 1970년 1월 1일 0시 0분 0초를 기점으로 총 경과한 시간을 초 단위로 표현한 정수 값이다.

가령 2018.6.19 4:28분 정도의 시간이면(초는 귀찮으니 그냥 생략) 타임스탬프로 1529393299 정도의 값이 나올 것이다.


정리하면

 -. "미팅 일시"를 사람이 입력할때는 2018.6.19 15:30과 같은 형식으로 입력

 -. 2018.6.19 15:30의 시간을 DB에 저장할 때는 이에 해당하는 타임스탬프로 저장

 -. DB에 저장되어 있는 "미팅 일시" 필드의 타임스탬프를 기준으로 정렬 후 웹 페이지에 보여줄 때는 타임스탬프를 "년.월.일 시:분"과 같은 형태로 표시


결국은 이 과정을 위해서는 

타임스탬프 시간 ↔ 년.월.일 시:분

의 시간 형태로 상호 변환을 처리하는 과정이 필요하게 되어진다.


이를 위한 함수를 PHP에서는 제공해 주고 있다. 그 함수가

date()

strtotime() 두 함수이다.


년.월.일 시:분의 값을 타임스탬프로 변환해 보자. 

여기서 참고적으로 "1970.01.1 13:22"과 같은 형태는 strtotime() 함수가 인식하지 못하는 형태이다. 인식할수 있는 형태는

1970-01-01 13:22 혹은

1970/01/01 13:22과 같은 형태라야 한다.


$meetingTime = '1970-01-1 13:22';

echo "1970년 1월 1일 13:22을 타임스탬프 값으로 : ".strtotime($meetingTime)."<br/>";


1970년 1월 1일 13:22의 타임스탬프 값은 15720이다.

이번에는 타임스탬프의 값을 년.월.일 시:분의 형태로 변환해 보자.


$mTimeStamp = '15720';

echo "타임스탬프의 값을 한국형 시간형태로 변환 : ".date('Y.m.d H:i', $mTimeStamp)."<br/>";



textarea 내용을 초기화 하는 법


textarea의 내용을 초기화 상태가 되고 "메시지를 입력하세요"와 같은 힌트 글이 희미하게 보이도록 하는 방법에 대한 것이다.


<html>

... 중략 ...


<body>

<form action="push_noti.php" method="post">

<textarea id="mTxtArea" onclick=this.value='';" name="message" rows="4" cols="50" placeholder="메시지를 입력하세요" required>

</textarea><br/>

<input type="submit" name="submit" value="Send" id="submitButton">

</form>

<script type="text/javascript">

document.getElementById("mTxtArea").value='';

</script>

</body> 

</html>


여기서 구현한 방법은 textarea에 id 값을 지정해서 JavaScript에서 이 id 값을 이용해서 textarea의 내용을 초기화 했다는 것이다.

JavaScript에서 textarea을 초기화한 핵심 코드는 아래 코드이다.


document.getElementById("mTxtArea").value='';


그리고 textarea를 클릭했을 때 onclick 이벤트를 활용해서 역시 내용을 초기화 하기도 했다.

   

placeholder="메시지를 입력하세요"는 textarea에 아무런 값도 없을 때 힌트 메시지를 보이게 하는 기능이다.



FCM 안드로이드 앱 개발시 error: cannot access zzbgl, class file for com.google.android.gms.internal.zzbgl not found 에러 해법


Firebase Cloud Messaging 안드로이드 개발시 아래와 같은 에러를 만나는 경우가 있다.

이 에러는 라이브러리로 인한 에러인데 FCM에서 library로 인한 에러의 경우 컴파일 단계에서 원천적으로 되지 않는 경우가 발생한다.

남의 라이브러리를 사용할때마다 늘상 씨름을 한판하지 않고는 쉽게 넘어가지를 않는 것 같다.

com.google.firebase.messaging.FirebaseMessagingService를 상속받은 MyFirebaseMessagingService 클래스에서 아래와 같은 에러가 발생하면서 컴파일이 되지를 않는다.


D:\ExFCMTest\app\src\main\java\com\example\joe\exfcmtest\MyFirebaseMessagingService.java:29: error: cannot access zzbgl

        Map<String, String> data = remoteMessage.getData();

                                                ^

  class file for com.google.android.gms.internal.zzbgl not found

Note: D:\ExFCMTest\app\src\main\java\com\example\joe\exfcmtest\MyFirebaseMessagingService.java uses or overrides a deprecated API.

Note: Recompile with -Xlint:deprecation for details.

1 error

:app:compileDebugJavaWithJavac FAILED

:app:buildInfoGeneratorDebug


FAILURE: Build failed with an exception.


* What went wrong:

Execution failed for task ':app:compileDebugJavaWithJavac'.

> Compilation failed; see the compiler error output for details.


* Try:

Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.


* Get more help at https://help.gradle.org


BUILD FAILED in 1s

18 actionable tasks: 7 executed, 11 up-to-date



이 에러가 발생했을 때의 앱 수준의 build.gradle(app/build.gradle)에 있는 dependencies의 내용이다.


dependencies {

    implementation fileTree(dir: 'libs', include: ['*.jar'])

    implementation 'com.android.support.constraint:constraint-layout:1.1.0'

    testImplementation 'junit:junit:4.12'

    androidTestImplementation 'com.android.support.test:runner:1.0.2'

    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'

    compile 'com.google.firebase:firebase-core:16.0.0'

    compile 'com.google.firebase:firebase-messaging:12.0.1'

}


이 문제는 앱 수준의 build.gradle의 dependencies의  


compile 'com.google.firebase:firebase-core:16.0.0'를 


compile 'com.google.firebase:firebase-core:11.8.0' 


로 바꾸었더니 정상적으로 compile이 되었다. 그래서 dependencies가 아래와 같은 모양을 이루었다.


dependencies {

    implementation fileTree(dir: 'libs', include: ['*.jar'])

    implementation 'com.android.support.constraint:constraint-layout:1.1.0'

    testImplementation 'junit:junit:4.12'

    androidTestImplementation 'com.android.support.test:runner:1.0.2'

    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'

    compile 'com.google.firebase:firebase-core:11.8.0'

    compile 'com.google.firebase:firebase-messaging:12.0.1'

}


이제 컴파일이 되어 Firebase Console 창에서 푸시 메시지를 해당 앱으로 날리니 이번에는 앱이 강제 종료가 된다. 아래와 같은 에러 메시지를 뿜으면서


06-06 18:40:14.223 13813-16293/com.example.joe.exfcmtest E/AndroidRuntime: FATAL EXCEPTION: pool-1-thread-1

    Process: com.example.joe.exfcmtest, PID: 13813

    java.lang.NoSuchMethodError: No static method zzamg()Lcom/google/android/gms/common/util/zzd; in class Lcom/google/android/gms/common/util/zzh; or its super classes (declaration of 'com.google.android.gms.common.util.zzh' appears in /data/app/com.example.joe.exfcmtest-1/split_lib_dependencies_apk.apk)

        at com.google.android.gms.internal.zzcim.<init>(Unknown Source)

        at com.google.android.gms.internal.zzcim.zzdx(Unknown Source)

        at com.google.android.gms.measurement.AppMeasurement.getInstance(Unknown Source)

        at com.google.firebase.messaging.zzd.zzde(Unknown Source)

        at com.google.firebase.messaging.zzd.zzc(Unknown Source)

        at com.google.firebase.messaging.zzd.zzf(Unknown Source)

        at com.google.firebase.messaging.FirebaseMessagingService.handleIntent(Unknown Source)

        at com.google.firebase.iid.zzc.run(Unknown Source)

        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)

        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)

        at java.lang.Thread.run(Thread.java:762)

06-06 18:40:14.287 13813-13813/com.example.joe.exfcmtest D/ViewRootImpl@c3f2698[MainActivity]: mHardwareRenderer.destroy()#4

    dispatchDetachedFromWindow

06-06 18:40:14.360 13813-13813/com.example.joe.exfcmtest D/InputTransport: Input channel destroyed: fd=70


이 문제는 라이브러리들의 버전이 일치하지 않아서 발생하는 문제이다. 위의 dependencies를 보면 아래와 같이 11.8.0과 12.0.1이 혼재되어 있다.


    compile 'com.google.firebase:firebase-core:11.8.0'

    compile 'com.google.firebase:firebase-messaging:12.0.1'


따라서     


compile 'com.google.firebase:firebase-messaging:12.0.1'를     


compile 'com.google.firebase:firebase-messaging:11.8.0'


으로 바꾸었다.

바꾸는건 12.0.1을 11.8.0으로 바꾸어서 타이핑해 주면 된다.


그래서 dependencies가 아래와 같이 되었다.


dependencies {

    implementation fileTree(dir: 'libs', include: ['*.jar'])

    implementation 'com.android.support.constraint:constraint-layout:1.1.0'

    testImplementation 'junit:junit:4.12'

    androidTestImplementation 'com.android.support.test:runner:1.0.2'

    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'

//    compile 'com.google.firebase:firebase-core:16.0.0' //error: cannot access zzbfm

    compile 'com.google.firebase:firebase-core:11.8.0' //O.K.

    compile 'com.google.firebase:firebase-messaging:11.8.0'

}


이제 컴파일도 정상적으로 되고 Firebase Console창에서 푸시 메시지를 날리면 정상적으로 잘 수신을 한다.

참고로 루트 수준의 build.gradle에 다음과 같이 구글의 maven 저장소를 추가해 준다.


allprojects {

    repositories {

        google()

        jcenter()

        maven {

            url "https://maven.google.com" //구글의 Maven repository

        }

    }

}


아래 사이트가 도움이 된다.


https://firebase.google.com/docs/android/setup?authuser=0#manually_add_firebase



FCM 안드로이드 앱을 만드는 중에 아래와 같은 에러가 발생하는 원인과 해결책


가정하기를 

 -. 패키지명 : com.example.joe.exfcmtest

 -. com.google.firebase.messaging.FirebaseMessagingService를 상속받을 클래스가 MyFirebaseMessagingService라고 할때


이러한 상황가운데서 아래와 같은 에러를 만난다면 


'com.example.joe.exfcmtest.MyFirebaseMessagingService' is not assignable to 'android.app.Service' less... (Ctrl+F1) 

Validates resource references inside Android XML files.


앱 수준의 build.gradle(프로젝트 수준의 build.gradle이 아님)에서 필요한 아래의 dependencies가 빠져 있어서의 문제다.


compile 'com.google.firebase:firebase-messaging:12.0.1'  (버전은 현재의 최신 버전과 다를수 있음)


이 문제는 참으로 황당하게도 구글의 Firebase SDK 추가에 대한 설명 자체에서 빼먹고 제시해 주지 않음으로 인해 만나게되는 황당한 에러이다.

Firebase 콘솔에서 제시한 대로 따라하다보면 아래 이미지에서 보듯이 compile 'com.google.firebase:firebase-messaging:12.0.1'를 포함시키라는 설명이 전혀 없다. 나쁜...




이로인해 Manifest 파일의 com.google.firebase.messaging.FirebaseMessagingService를 상속받을 Service 클래스에 대해 아래 그림과 같은 에러가 발생한다.


        <service android:name=".MyFirebaseMessagingService">

            <intent-filter>

                <action android:name="com.google.firebase.MESSAGING_EVENT" />

            </intent-filter>

        </service>




그리고 다음 에러




이 문제를 해결할려면 아래 그림에서 보는바와 같이 앱 수준의 build.gradle




compile 'com.google.firebase:firebase-messaging:12.0.1'를 dependencies에 포함시켜야 된다.




안드로이드는 너무 자주, 많이 바뀌는 통에...



Java/JSP Web Application을 WAR 파일로 만든 후 Tomcat 웹 애플리케이션 서버에 배포하는 법.


본 포스팅은 다음 상황에서의 설명이다.

 -. Ubuntu 16.04 LTS 버전

 -. Tomcat 8.0

 -. 톰캣 가상 호스트를 이용해서 joe.iptime.org에 대한 웹 루트를 /home/joe/www/ROOT/로 변경하였다.

    (톰캣 가상 호스트 설정하는 법은 여기를 참조)


Java/JSP로 개발된 웹 애플리케이션을 실제 서버에 배포할때 WAR 파일을 만들어 배포하게 된다. 그런데 톰캣 가상호스트를 만들어 디폴트 웹 root 디렉토리를 /home/joe/www/ROOT/로 변경하였을 경우 WAR 파일을 배포하는 방법에 대한 것이다.


Ubuntu의 경우 Tomcat의 웹 루트(DocumentRoot)는 /var/lib/tomcat8/webapps/ROOT/이다. 따라서 가상호스트를 만들지 않은 상황에서는 WAR 파일을 /var/lib/tomcat8/webapps 폴더로 FTP 등을 이용해서 올리면 자동으로 WAR 파일을 압축을 풀어서 해당 웹 애플리케이션이 배포가 된다. 만일 압축 해제및 배포가 자동으로 되지 않는다면 터미널 창에서 다음과 같이 tomcat을 재시작해 주면 정상적으로 배포가 된다.


# service tomcat8 restart


그런데 가상 호스트를 만들을 때는 WAR 파일을 어디로 올려야 되는가?

/home/joe/www로 WAR 파일을 올려도 WAR 파일이 자동으로 압축해제 및 배포가 되지를 않는다. 톰캣을 재시작해도 마찬가지이다.


/etc/tomcat8/server.xml을 열어보면 unpackWARs="true" autoDeploy="true"로 되어 있음에도 불구하고 /home/joe/www에 WAR 파일을 업로드해도, 혹은 톰캣을 재시작해도 WAR파일이 자동으로 배포가 되지 않는다.


      <Host name="joe.iptime.org"  appBase="/home/joe/www"

            unpackWARs="true" autoDeploy="true">

... 중 략 ...

       </Host>


이런 경우 다른 더 좋은 방법이 있는지 모르겠으나 톰캣의 디폴트 웹 root인 /var/lib/tomcat8/webapps/에 WAR 파일을 업로드하면 자동으로 배포가 이루어진다. 이렇게 압축이 풀려진 해당 디렉토리와 그 하위 디렉토리 및 전체를 가상 호스트를 만든 해당 웹 루트(여기서는 /home/joe/www/ROOT/)에 복사하는 수 밖에는 없다.  Flower.WAR 파일이라고 한다면 이 파일을 /var/lib/tomcat8/webapps/로 업로드하면 이 디렉토리에 Flower라는 새로운 디렉토리가 생성이 되고 그 하위에 개발했던 웹 애플리케이션의 파일과 해당 디렉토리들이 압축이 풀려져 있다. 따라서 다음 명령을 이용해서 가상 호스트의 해당 웹 root로 복사를 하면 정상적으로 웹 애플리케이션이 구동이 된다.


# pwd

/var/lib/tomcat8/webapps

# cp -r ./Flower/* /home/joe/www/


이클립스에서 WAR 파일을 만드는 방법은 해당 프로젝트 위에 마우스 우측 클릭하여 다음 그림과 같이 진행하면 WAR 파일을 얻을수 있다.






동일한 서버에서 Apache - Tomcat 연동하기


아래 내용은 다음의 상황에서 진행된 내용이다. apache2와 tomcat8, openjdk-8-jre-headless, openjdk-8-jdk 등이 이미 설치되어 있다고 가정한다.

-. Ubuntu 16.04

-. Apache 2.4

-. Tomcat 8.0


서버에 PHP와 JSP 모두를 개발하고 실행할수 있는 환경을 구축하고자 한다. 이 경우 우선적으로 문제가 되는 것은 apache의 경우는 80포트를 기본으로 사용하고 tomcat의 경우는 8080 포트를 기본으로 사용한다.

따라서 동일 서버상에서 80으로 접속해 오는 경우들에 대해서 상황에 맞게 때로는 apache를 통해 php, html 등을 실행시키고 혹은 tomcat을 통해 jsp를 실행하도록 할려면 Apache와 Tomcat을 연동하도록 해야한다.

그 외에 부하를 분산시키는 등의 잇점들이 있을 것이다. 


우선 다음과 같은 시나리오로 진행될 것이다.

 -. joekog.iptime.org에 대한 apache 가상 호스트를 생성해서 해당 도메인에 대한 php에 대한 웹 서비스를 처리하도록 할 것이다

 -. joekog.iptime.org에 대한 tomcat 가상 호스트를 생성해서 해당 도메인에 대한 jsp 웹 서비스를 처리하도록 할 것이다.

 -. joekog.iptime.org에 대한 apache와 tomcat이 처리할 웹 루트 경로는 /home/joekog/www/ROOT/가 될 것이다.

 -. /home/joekog/www/ROOT/ 아래에 .html, .php, .jsp 파일들이 위치해 있을 것이고 웹브라우저에서 http://joekog.iptime.org로 접속할 때 /home/joekog/www/ROOT/ 아래에 있는 .html, .php, .jsp 파일들이 실행될 것이다. 적절히 apache와 tomcat이 알아서 실행하게 될 것이다.

 -. IpTIME의 DDNS 기능을 이용하여 joekog.iptime.org라는 도메인을 생성해서 위의 내용을 테스트 할 것이다.


이상의 내용을 위해 다음의 내용을 사전에 알고 있어야 한다.

 -. apache 가상 호스트(Virtual Host) 생성하는 법 및 IpTIME DDNS 생성하는 법 : 여기를 참조

 -. tomcat 가상 호스트 생성하는 법 : 여기를 참조


우선 Ubuntu 터미널 창에서 root로 로그인 한 후 apache와 tomcat의 연동을 위한 다음 모듈을 설치한다.


# apt-get install libapache2-mod-jk


위의 파일을 설치하면 다음과 같은 경로와 파일이 생성이 된다.


/etc/libapache2-mod-jk/workers.properties


이 파일에서 3가지를 현재의 서버 환경에 맞게 설정해 주면 된다. 나머지는 디폴트 상태로 두면 된다.

 -. tomcat home 디렉토리

 -. java home 디렉토리

 -. 톰캣이 맡아서 처리할 내용을 아파치가 톰캣에게 넘기기 위해 톰캣을 지칭하기 위한 이름


workers.properties를 열어서 아래의 내용을 찾아서 해당하는 환경 정보를 입력해 준다.


workers.tomcat_home=①

workers.java_home=②

worker.list=③


① tomcat 홈 디렉토리를 찾을려면 아래 명령을 실행하면 아래와 같은 정보가 보일 텐데 여기서 CATALINA_HOME의 경로인 /usr/share/tomcat8가 tomcat의 홈 디렉토리 경로가 된다.


# /usr/share/tomcat8/bin/version.sh


Using CATALINA_BASE:   /usr/share/tomcat8

Using CATALINA_HOME:   /usr/share/tomcat8

Using CATALINA_TMPDIR: /usr/share/tomcat8/temp

Using JRE_HOME:        /usr

Using CLASSPATH:       /usr/share/tomcat8/bin/bootstrap.jar:/usr/share/tomcat8/bin/tomcat-juli.jar

Server version: Apache Tomcat/8.0.32 (Ubuntu)

Server built:   Sep 27 2017 21:23:18 UTC

Server number:  8.0.32.0

OS Name:        Linux

OS Version:     4.13.0-43-generic

Architecture:   amd64

JVM Version:    1.8.0_171-8u171-b11-0ubuntu0.16.04.1-b11

JVM Vendor:     Oracle Corporation


② java 홈 디렉토리를 찾기위해서는 다음 명령을 실행하면 


# find / -name '*-openjdk-*'


/usr/share/gdb/auto-load/usr/lib/jvm/java-8-openjdk-amd64

/usr/lib/jvm/java-1.8.0-openjdk-amd64

/usr/lib/jvm/.java-1.8.0-openjdk-amd64.jinfo

/usr/lib/jvm/java-8-openjdk-amd64

/usr/lib/debug/usr/lib/jvm/java-1.8.0-openjdk-amd64


여기서 /usr/lib/jvm/java-8-openjdk-amd64가 java의 홈 디렉토리이다. cd 명령으로  /usr/lib/jvm/ 이 위치로 가서 ls -l을 해 보면 다음과 같은 정보가 보일 것이다. java-8-openjdk-amd64가 디렉토리임을 알수 있다. 따라서 java 홈 디렉토리는 /usr/lib/jvm/java-8-openjdk-amd64가 되는 것이다.


-rw-r--r--   1 root root 2600  4월 28 03:42 .java-1.8.0-openjdk-amd64.jinfo

lrwxrwxrwx   1 root root   20  4월 28 03:42 java-1.8.0-openjdk-amd64 -> java-8-openjdk-amd64/

drwxr-xr-x   7 root root 4096  5월 11 15:56 java-8-openjdk-amd64/


③은 apache 웹 서버가 톰캣에게 실행을 넘길 때 톰캣을 지칭할 이름이다. 이 이름은 임의로 원하는대로 수정을 하면되는데 기본적으로는 ajp13_worker로 되어 있다. 이 이름을 그대로 사용한다면 여기까지가 workers.properties에 대한 설정 작업은 끝났다. 그러면 다음과 같은 형태가 될 것이다.


workers.tomcat_home=/usr/share/tomcat8

workers.java_home=/usr/lib/jvm/java-8-openjdk-amd64

worker.list=ajp13_worker


그런데 만일 ajp13_worker를 임의의 이름으로 변경한다면 workers.properties에서 ajp13_worker라는 이름도 모두 바꾸어 주어야 한다. 예를 들어 아래에서 ajp13_worker를 임의로 변경한 이름과 동일한 이름으로 고쳐주어야 한다.


worker.ajp13_worker.port=8009 

worker.ajp13_worker.host=localhost ⇒ 톰캣이 설치되어 있는 곳의 IP이다.

worker.ajp13_worker.type=ajp13


이제 apache 가상 호스트를 위한 설정을 할 차례이다. 

/etc/apache2/sites-available으로 이동해서 000-default.conf를 여기서 사용하게 될 도메인 이름으로 복사본을 만들고 필요한 환경설정을 한다. 아래와 같이


# cp 000-default.conf  joekog.iptime.org.conf

# vi joekog.iptime.org.conf


위 파일을 열어서 아래의 내용을 찾아 위에서 가정했던 해당 정보대로 입력한다.


      9         ServerName joekog.iptime.org 

     10 

     11         ServerAdmin webmaster@localhost

     12         DocumentRoot /home/joekog/www/ROOT


     20         ErrorLog ${APACHE_LOG_DIR}/joekog.iptime.org-error.log

     21         CustomLog ${APACHE_LOG_DIR}/joekog.iptime.org-access.log combined


그리고 이 파일의 끝 부분 </VirtualHost> 위에 다음 내용을 작성해서 jsp에 대해서는 tomcat(위에서 별칭으로 정한 ajp13_worker)이 처리한다는 것을 명시한다.


         JKMount /* ajp13_worker

</VirtualHost>


여기서 JKMount /* ajp13_worker로 표현한 것은 현재의 웹 root(/home/joekog/www/ROOT/) 아래에 있는 모든 파일을 ajp13_worker(톰캣)이 담당하게 된다는 뜻이다.

그런데 이 웹 root상에 .php가 있다면 이 파일도 톰캣이 담당하게 될텐데 톰캣은 php를 실행할수가 없게 된다. 따라서 php 소스 자체를 웹 브라우저에 출력하게 될 것이다.

그런데 만일 JKMount /*.jsp ajp13_worker로 한다면 .jsp 파일만 톰캣이 맡고 나머지는 아파치가 실행하게 되므로 동일 웹 루트상에 .php가 있더라도 정상적으로 php를 실행한 결과를 보여주게 될 것이다.

그런데 문제는 JKMount /*.jsp ajp13_worker 이렇게 하게되면 .jsp, .php, .html이 정상적으로 실행이 되지만 Java Servlet은 이 상태에서는 실행할수 없게 된다.

따라서 jsp와 php가 같은 경로상에서 실행되어야 할 경우는 없을 것이므로 JKMount /* ajp13_worker로 설정해서 .jsp와 Java Servlet도 정상적으로 실행되게 설정하면 될것이다.

참고로 Java Servlet의 .class 파일이 위치하는 경로는 웹 root 아래 /WEB-INF/classes/해당패키지명/ 아래에 위치하게 된다. 만일 해당 서블릿의 패키지 명이 com.joe.test라고 한다면 /WEB-INF/classes/com/joe/test/ 이 위치에 *.class 파일이 위치하게 될 것이다.


다음으로 할 것은 apache의 가상 호스트 설정에 대한 설정을 해 주어야 한다. 


# vi /etc/apache2/apache2.conf


아래 파일을 열어서 다음 내용을 추가해 준다.


    182 <Directory /home/joekog/www/ROOT/>

    183         Options FollowSymLinks

    184         AllowOverride None

    185         Require all granted 

    186 </Directory>


이제 톰캣에 대한 설정을 할 차례이다. 아래 파일을 열어서 


<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" /> 이 부분이 주석되어 있다면 주석을 해제하고 내용이 없다면 이 내용을 추가해준다.


# /etc/tomcat8/server.xml


이상을 작업을 하면 apache와 톰캣에서의 연동을 위한 설정은 끝이 났다. 그런데 여기서 우리는 joekog.iptime.org/joe.php라는 도메인으로 접속시 apahce를 통해 /home/joekog/www/ROOT/ 이 경로에 있는 joe.php를 실행할 것이고 동일하게  joekog.iptime.org/hi.jsp로 접속시에는 tomcat을 통해서 /home/joekog/www/ROOT/ 이 경로에 있는 hi.jsp를 실행해야 하므로 아파치 뿐만 아니라 톰캣에 대해서도 가상 호스트(Virtual Host)를 설정해 주어야 한다. 위의 server.xml을 열어서 아래 내용을 기존에 있던 <Host name... </Host> 아래 적당한 곳에 추가해 준다.


    161       <Host name="joekog.iptime.org"  appBase="/home/joekog/www"

    162             unpackWARs="true" autoDeploy="true">

    163 

    164         <!-- SingleSignOn valve, share authentication between web applications

    165              Documentation at: /docs/config/valve.html -->

    166         <!--

    167         <Valve className="org.apache.catalina.authenticator.SingleSignOn" />

    168         -->

    169 

    170         <!-- Access log processes all example.

    171              Documentation at: /docs/config/valve.html

    172              Note: The pattern used is equivalent to using pattern="common" -->

    173         <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"

    174                prefix="joekog.iptime.org_access_log" suffix=".txt"

    175                pattern="%h %l %u %t &quot;%r&quot; %s %b" />

    176       </Host>


이제 아파치와 톰캣을 재시작한다.


# service apache2 restart

# service tomcat8 restart


그리고 /home/joekog/www/ROOT/ 이 경로에 index.html, hi.jsp, joe.php 파일을 적당한 내용으로 작성해서 웹 브라우저에서 접속해 본다.


http://joekog.iptime.org/hi.jsp

http://joekog.iptime.org/joe.php

http://joekog.iptime.org/index.html


이제 톰캣의 기본 포트인 8080 없이 웹 브라우저에서 접속해서 필요를 따라 혹은 apache 혹은 tomcat이 실행되어 위의 3종류의 파일이 정상적으로 실행되고 웹 브라우저에서 보이게 될 것이다.

Apache - Tomcat의 연동과 Apache Virtual Host, Tomcat Virtual Host 등이 같이 엮이어 돌아가야 되는 상황이라 사실상 처음 하는 사람에게는 많이 복잡한 내용이다.

이상으로 아파치 톰캣 연동을 마무리하게 되었다. 휴~;;;



Tomcat 가상 호스트(Virtual Host) 구축 방법


서버에 tomcat이 설치되어 있을 경우 Java/JSP를 이용한 웹서비스를 개발할 때도 apache의 경우와 같이 하나의 서버에 여러개의 도메인으로 접속시 각각의 해당 웹 서비스가 실행되도록 가상 호스트를 만들수 있다.


Ubutnu 16.04, Tomcat 8.0의 상황에서 다음 경로에 있는 server.xml을 가상 호스트 환경으로 변경해 준다.


vi /etc/tomcat8/server.xml


위의 파일을 열어 보면 다음과 같은 내용이 있을 것이다.


    128       <Host name="localhost"  appBase="webapps"

    129             unpackWARs="true" autoDeploy="true">

    130 

    131         <!-- SingleSignOn valve, share authentication between web applications

    132              Documentation at: /docs/config/valve.html -->

    133         <!--

    134         <Valve className="org.apache.catalina.authenticator.SingleSignOn" />

    135         -->

    136 

    137         <!-- Access log processes all example.

    138              Documentation at: /docs/config/valve.html

    139              Note: The pattern used is equivalent to using pattern="common" -->

    140         <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"

    141                prefix="localhost_access_log" suffix=".txt"

    142                pattern="%h %l %u %t &quot;%r&quot; %s %b" />

    143       </Host>


이 상황에서 2개의 도메인에 대한 가상 호스트를 만들기로 해 본다.

joekog.iptime.org 

fullstack.iptime.org

이상의 2개의 도메인으로 동일 서버(Ubuntu 16.04, Tomcat 8이 설치된 서버)로 접속해 돌 때

joekog.iptime.org  ⇒ /home/joekog/www의 웹 서비스가 실행되고

fullstack.iptime.org  ⇒ /home/fullstack/www의 웹 서비스가 실행되도록 Tomcat virtual host(톰캣 가상호스트)를 설정해 보고자 한다.


위에서 열었던 server.xml의 위의 <Host name... 아랫쪽에 아래와 같이 도메인 관련정보를 추가해 준다.


    145       <Host name="joekog.iptime.org"  appBase="/home/joekog/www"

    146             unpackWARs="true" autoDeploy="true">

    147 

    148         <!-- SingleSignOn valve, share authentication between web applications

    149              Documentation at: /docs/config/valve.html -->

    150         <!--

    151         <Valve className="org.apache.catalina.authenticator.SingleSignOn" />

    152         -->

    153 

    154         <!-- Access log processes all example.

    155              Documentation at: /docs/config/valve.html

    156              Note: The pattern used is equivalent to using pattern="common" -->

    157         <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"

    158                prefix="joekog.iptime.org_access_log" suffix=".txt"

    159                pattern="%h %l %u %t &quot;%r&quot; %s %b" />

    160       </Host>

    161 

    162       <Host name="fullstack.iptime.org"  appBase="/home/fullstack/www"

    163             unpackWARs="true" autoDeploy="true">

    164 

    165         <!-- SingleSignOn valve, share authentication between web applications

    166              Documentation at: /docs/config/valve.html -->

    167         <!--

    168         <Valve className="org.apache.catalina.authenticator.SingleSignOn" />

    169         -->

    170 

    171         <!-- Access log processes all example.

    172              Documentation at: /docs/config/valve.html

    173              Note: The pattern used is equivalent to using pattern="common" -->

    174         <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"

    175                prefix="fullstack.iptime.org_access_log" suffix=".txt"

    176                pattern="%h %l %u %t &quot;%r&quot; %s %b" />

    177       </Host>



위 파일을 저장한 후 tomcat을 재시작한다.


# service tomcat8 restart


이제 각각의 경로에 해당하는 웹 서비스를 구현하면 된다.


joekog.iptime.org를 위해 joekog에 대한 사용자를 아래와 같이 추가해 준다.


# adduser joekog


위의 사용자를 추가하고 나면 /home/joekog라는 디렉토리가 생성되었을 것이다. 이후 아래 디렉토리 경로를 생성해 준다.


/home/joekog/www/ROOT/


이때 주의할 것은 server.xml에 appBase="/home/joekog/www"와 같이 설정되어 있지만 기본적으로 tomcat은 이 경로 아래의 ROOT를 웹의 루트 디렉토리로 인식하기 때문에 index.jsp와 같은 파일을 /home/joekog/www/index.jsp와 같이 위치시키면 파일을 찾을수 없다는 에러를 발생시킨다. 반드시 ROOT 디렉토리를 생성 후 /home/joekog/www/ROOT/index.jsp와 같이 웹 서비스 실행 파일을 위치시켜야 한다.


이제 웹 브라우저에서 http://joekog.iptime.org:8080/index.jsp로 접속하면 정상적으로 실행이 된다.

fullstack.iptime.org에 대한 것도 동일한 방법으로 테스트해 볼수 있다.


사용자 추가, IpTIME에서의 DDNS 설정 및 apache에 대한 가상 호스트 구축 방법은 아래 링크를 참조한다.

아파치 가상 호스트(Virtual host)를 이용한 하나의 서버에 여러개의 웹 서비스 구현하기




원격 IP로 MySQL(MariaDB)에 접속이 되지 않을 때의 해법


※ Ubuntu 16.04 MariaDb 10.0의 상황과 원격 IP의 주소가 111.222.33.44라고 가정한다.


Java에서 다음과 같이 원격의 MySQL에 접속을 시도할때


String url = "jdbc:mysql://111.222.33.44:3306/나의DB명";


Connection refused 에러가 발생하거나 telnet을 이용해서 MySQL이 설치된 IP의 3306포트로 접속시 연결이 되지 않을 때의 해법이다.

네트워크의 연결 상태에따라 다양한 이유가 있을 수 있다. 예를들어 공유기 아래 서버가 물려있을 때 공유기에서 3306에 대한 포트 포워딩이 되어있지 않다면 아래의 방법이 통할수 없을 것이다.

다양한 원인들이 있으나 본 포스트는 3306 포트에 listening이 외부 IP로 설정되지 않고 127.0.0.1로 설정되어 있을 경우에 대한 해법이다.


telnet을 이용해서 해당 IP로 3306포트로 접속이 되는지부터 확인해보자.

일단 Windows에서는 기본적으로 telnet을 사용할수 없는 상태로 설정되어 있다. 다음과 같이 하여 telnet을 사용할수 있도록 변경한다.


제어판 - 프로그램 제거 또는 변경 - Windows 기능 켜기/끄기 - 텔넷 클라이언트 체크


MS-DOS 창에서 telent으로 다음과 같이 먼저 확인해 본다.


telnet 111.222.33.44 3306

연결 대상 111.222.33.44...호스트에 연결할 수 없습니다. 포트 3306: 연결하지 못했습니다.


SQLyog에서 접속시도해 보면 다음과 같은 에러가 발생한다.


오류 번호 2003 Can't connect to MySQL server on '111.222.33.44'


이럴 경우 MySQL이 외부 IP의 3306포트로 접속이 가능하지 않은 상태이다. 

아래와 같이 자기 자신의 IP(여기서는 111.222.33.44)로 3306 포트로 접속시 접속을 시도했을 때 접속이 되지 않는다면 외부 IP에서 3306포트로 접속이 가능하지 않은 상태라는 뜻이다.


물론 아래의 접속으로 접속이 되기 위해서는 사전에 아래 명령어에 의해서 root가 외부 IP로 접속할수 있는 권한 설정이 mysql 데이터베이스에 설정 되어 있어야 하는 걸 전제로 한다.


MariaDB [(none)]> grant all privileges on *.* to 'root'@'%' identified by '비번';

MariaDB [(none)]> flush privileges;


만일 위와 같이 root가 외부 IP로의 자유로운 접속이 허락되어 있지 않으면 아래의 명령으로 접속이 안되는 건 당연한 일이다.

다음과 같이 MySQL에서는 권한 설정이 아래와 같이 정상적으로 되어 있다고 가정해 보자.


MariaDB [(none)]> use mysql;

Reading table information for completion of table and column names

You can turn off this feature to get a quicker startup with -A


Database changed

MariaDB [mysql]> select host, user, password from user;

+-----------+------+-------------------------------------------+

| host      | user | password                                  |

+-----------+------+-------------------------------------------+

| localhost | root | *8EA3CE4E6710007B6FD72BC385D48F3D985FD258 |

| %         | joe  | *8EA3CE4E6710007B6FD72BC385D48F3D985FD258 |

| %         | root | *8EA3CE4E6710007B6FD72BC385D48F3D985FD258 |

+-----------+------+-------------------------------------------+

3 rows in set (0.00 sec)


MariaDB [mysql]> select host, user, db from db;

+------+------+-------+

| host | user | db    |

+------+------+-------+

| %    | joe  | mysql |

+------+------+-------+

1 row in set (0.00 sec)


일단 여기서는 root에 대한 외부 IP로의 접속 권한이 설정되어 있다는 걸 전제로 했을 때 아래와 같이 접속이 안된다면 현재 MySQL(MariaDB)는 외부 IP로의 접속을 위한 설정에 문제가 있다는 것이다.


root@xxx:/home/joe# mysql -h 111.222.33.44 -P 3306 -uroot -p

Enter password: 


이 경우 다음과 같은 에러가 발생한다.


ERROR 2003 (HY000): Can't connect to MySQL server on '192.168.0.107' (111 "Connection refused")


이상과 같이 정상적으로 외부 IP로의 접속이 허용되어 있음에도 접속 불능사태가 발생한다면 다음 명령으로 3306 포트에 대해 listening 상태를 확인해 보자.


# netstat -ntlp | grep 3306

을 하면 


tcp        0      0 127.0.0.1:3306          0.0.0.0:*               LISTEN      1235/mysqld


이렇게 나오면 localhost에서는 접속이 되지만 원격 IP에서는 접속이 안된다.


이 문제에 대한 해법은 아래 파일에서

/etc/mysql/mariadb.conf.d/50-server.cnf

  

bind-address            = 127.0.0.1 를 주석처리하고

bind-address   = 0.0.0.0 을 추가한다. 다음과 같이


#bind-address            = 127.0.0.1 

bind-address   = 0.0.0.0


변경된 사항을 적용하기 위해서 

# service mysql restart

를 하면된다고 검색해 보면 나와있는데 이 방법으로는 netstat으로 확인해보면 여전히 3306에 대한 listening을 127.0.0.1로되어 있다.

현재 가장 확실한 방법은 서버를 재부팅하면 변경된 설정 값이 제대로 적용이되고 외부 IP로 접속이 정상적으로 된다.

재부팅 없이 되는 방법을 아직은 모르겠다....;;;



+ Recent posts