docker나 기타 리눅스 서버로 서버를 실행하면 로그를 살펴보거나 기타 모니터링 하기 편리합니다.

하지만 iis에서 flask 서버를 서비스하면 기본적인 debug 로그도 살펴보기가 매우 어렵습니다.

 

anaconda3 환경에서 iis에 flask 서비스를 설정해둔 상태여서 기본적은 에러조차 로그로 파악하기가 어렵습니다

불친절한 IIS 서버

local 상에서 정상작동하는 서버인데 iis에서만 위와같은 500에러만 나오는 상태, 맨 윗줄부터 디버깅을 하다가 작성한 코드입니다.

from flask import Flask, render_template

app = Flask(__name__)

@app.route('/')
def check_packages():
    packages = [
        'flask', 'watchdog', 'pysftp', 'PIL', 'psycopg2', 'requests'
    ]

    package_status = {}
    for package_name in packages:
        try:
            __import__(package_name)
            package_status[package_name] = 'Installed'
        except ImportError:
            package_status[package_name] = 'Not Installed'

    return render_template('packages.html', package_status=package_status)

if __name__ == '__main__':
    app.run(debug=True)

 

templates/packages.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Package Status</title>
</head>
<body>
    <h1>Package Status</h1>
    <table border="1">
        <tr>
            <th>Package Name</th>
            <th>Status</th>
        </tr>
        {% for package_name, status in package_status.items() %}
        <tr>
            <td>{{ package_name }}</td>
            <td>{{ status }}</td>
        </tr>
        {% endfor %}
    </table>
</body>
</html>

 

dataframe의 한 컬럼에 대해 unique한 값을 얻기위해 'datafame['컬럼명'].uique()' 메소드를 사용할 수 있고 결과값이 list가 아닌 ndarray형태로 생성돼죠.

 

unique() 메소드를 이용하여 생성된 list를 다른 컬럼으로 생성하거나 비교를 위한 list로 활용하려하던 중 ndarray 형태로는 원하는 작업에 지장이 생겨서 list형태로 변환하는 과정입니다.

 

numpy 형태의 배열의 경우 list와 다르게 ','로 구분이 안되어있습니다.

list형태로 간단하게 변경할 수 있습니다.

import numpy as np
unique_values = df_proceed['시간대별'].unique()
print(unique_values)
print(type(unique_values))

# ndarray to list
python_list = unique_values.tolist()
# 결과 확인
print(python_list)

ndarray

 flask로 간단한 웹 페이지를 만들어서 Local 환경에서 테스트한 후, 서버에 docker container로 서비스를 가동하니 접속이 안되는 문제가 발생하였습니다.

if __name__ == '__main__':
    cache.clear()
    app.run(debug=True, port=8888)

1. port binding도 잘 해주었고,

2. host도 오타없이 잘 작성하였고,

3. python 파일도 정상적으로 실행되고 있었습니다.

 


host='0.0.0.0'

해결책은 간단했습니다.

 

Flask 애플리케이션을 실행할 때, Flask 서버는 기본적으로 127.0.0.1 또는 localhost에서만 연결을 허용합니다.

따라서 "Running on http://127.0.0.1:8888"과 같이 로컬 주소에서만 서버가 실행되는 것이 기본 동작입니다.

하지만 "Running on all addresses (0.0.0.0)" 또는 "Running on http://172.17.0.2:8100"와 같이 모든 주소에서 서버를 실행하려면 Flask 애플리케이션을 아래와와 같은 코드로 실행해야합니다.

if __name__ == '__main__':
    cache.clear()
    app.run(debug=True, host='0.0.0.0', port=8888)

바로 run()함수에 'host = '0.0.0.0'으로 지정해두니 서버 호스트로도 접속이 정상적으로 이루어졌습니다.

 

아래는 chat-gpt가 답해준 내용입니다. 훌륭한 설명이네요.

이렇게 하면 Flask 서버가 모든 주소 (0.0.0.0)에서 클라이언트 연결을 허용하며, 외부 클라이언트도 액세스할 수 있게 됩니다. 
단, 이렇게 서버를 열면 보안상 주의가 필요하며, 특히 개발 환경에서만 사용해야 합니다. 
production(운영) 환경에서는 보안 검토와 관련된 설정을 고려해야 합니다.
따라서 Flask 애플리케이션을 모든 주소에서 실행하고 싶다면 위의 코드를 Flask 애플리케이션 코드 안에서 사용하십시오.

 

python flask로 구현한 웹 페이지를 가상화공간(docker container)에서 서비스하려할 때 위와 같은 문제가 발생하시는 분들께 도움이 되었으면 합니다.

 

아래처럼 pattern 속성값을 추가해줍니다 (출처 : 출처: https://kcmschool.com/184 [web sprit])

<input type="number" pattern="\d*">

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using System.Diagnostics;

namespace RunIE
{
    class Program
    {
        static void Main(string[] args)
        {

            Process.Start("microsoft-edge:www.mysite.com");

            //We need to find the most recent MicrosoftEdgeCP process that is active
            Process[] edgeProcessList = Process.GetProcessesByName("MicrosoftEdgeCP");
            Process newestEdgeProcess = null;

            foreach (Process theprocess in edgeProcessList)
            {
                if (newestEdgeProcess == null || theprocess.StartTime > newestEdgeProcess.StartTime)
                {
                    newestEdgeProcess = theprocess;
                }
            }

            //newestEdgeProcess.WaitForExit();

            System.Diagnostics.Process.Start("iexplore.exe", "http://naver.com");



            string museServerURL = "https://google.com";
            try
            {
                ProcessStartInfo startInfo = new ProcessStartInfo("microsoft-edge:" + museServerURL);
                startInfo.WindowStyle = ProcessWindowStyle.Maximized;
                startInfo.Arguments = museServerURL;
                Process.Start(startInfo);
            }
            catch (Exception)
            {
                System.Diagnostics.Process.Start("iexplore.exe", "http://naver.com");
                //WebBrowser webBrowser = new WebBrowser();
                //webBrowser.Navigate(museServerURL, true);
            }
        }
    }    
}

특정 도메인(url)을 브라우저에서 바로 실행시키고 싶을 때 사용하는 함수입니다.

아래 코드는 edge에서 실행이 안 될 경우 explorer에서 실행이 되게 하는 코드입니다.

서비스 이용자들 중에 여전히 IE유저가 많아서 아래와 같은 코드로 수정했었죠. ㅜ0ㅜ

 

 MSSQL 서버에 사용자 PASSWORD 데이터가 'SHA2_xxx'방식으로 암호화되어 저장되있습니다.

 

그럼 php로 입력받은 id와 password값을 처리하기 위해서 아래와 같은 쿼리를 날려 준 후 결과값이 있으면 로그인 허용을 해주면 될 것입니다.

select * from 사용자정보
where 유저id = $id
and 비밀번호 = $password

하지만 위처럼  $password 값이 암호화 처리되지 않은 값이라면 쿼리를 아래처럼 수정해야합니다.

select * from 사용자정보
where 유저id = $id
and 비밀번호 =  HASHBYTES('SHA2_256' , $password)

하지만 sql 수준의 hashbytes 함수의 return값은 binary형(0x.....)식 입니다.

만약 사용자정보 테이블의 유저id 컬럼이 binary형식으로 저장이 안 되어있는 경우라면 아래처럼 character 형태로 convert를 해줄 수도 있습니다.

select * from 사용자정보
where 유저id = $id
and 비밀번호 =  CONVERT(NVARCHAR(32),HASHBYTES('SHA2_256' , $password))

아니면 php 수준에서 아래와 같이 hash()함수로 처리한 뒤에 쿼리로 넘겨주는 방법도 활용할 수 있습니다.

$password_hash = hash("sha256", $password)

 

간혹 css를 아무리 변경해도 바로 적용이 안 되는 경우가 있습니다.

서버 스크립트 문제인가 싶어서 설정을 해봐도 적용이 되지 않았는데, 크롬에서 시크릿모드로 호출시 바로 적용이 되더라구요.

 여기서 브라우저 캐시문제라고 예상되어 해결책을 찾아봤는데 생각보다 간단했습니다.

    <link href="<?php echo base_url(); ?>static/css/sb-admin-2.css?after" rel="stylesheet">

 css link 경로에서 css끝에 아무 파라미터만 연결해주면 됩니다.

위에서는 경로에 '?after'라는 파라미터를 추가해 주니 바로 반영이 잘 되었습니다.

 

브라우저가 다른 css를 인식하게 해주는 방법 중 하나인데요, 아래 블로그 글에서 참고하여 잘 해결하였습니다.

감사합니다.

https://meaownworld.tistory.com/89

* 참고 : pytutbe 모듈은 python 3.6 버전 이상에서 작동합니다!

 

 유튜브 영상을 다운로드 받는 방법은 여러가지지만 써드파티 유틸리티를 다운받거나 사용하려니 번거로움이 있어서 파이썬으로 간단하게 구현해봤습니다.

 유튜브 플레이리스트에 있는 영상도 한 번에 다운로드 할 수 있게 구현한 코드이니 주석 참고해주시고 궁금하신 점은 무엇이든 남겨주세요 ㅎ


프로그램 순서는 대략적으로 아래와 같습니다.

1. 다운로드 받을 유튜브 플레이리스트 URL을 리스트로 생성해줌

2. 유튜브 플레이리스트 제목을 이름으로 갖는 폴더를 생성함

3. 생성한 폴더 안에 유튜브 플레이리스트 안에 있는 모든 영상을 다운로드 해줌

from pytube import YouTube
from pytube import Playlist
import os

# 참고 1) 폴더 생성 : https://data-make.tistory.com/170
# '폴더명'을 매개변수로 받는 함수, 파라미터로 받은 폴더명과 같은 폴더가 없으면 폴더를 새로 생성해줌
def createFolder(directory):
    try:
        if not os.path.exists(directory):
            os.makedirs(directory)
    except OSError:
        print ('Error: Creating directory. ' +  directory)


# 참고 2) pytube 기본 사용법 :  https://pytube.io/en/latest/user/playlist.html
# '플레이리스트 URL'을 매개변수로 받는 함수
def playlist_download(playlist_url):
    # 플레이리스트 제목을 폴더명으로 사용하여 폴더를 생성할 것 -> 플레이리스트 이름 가져오기
    p = Playlist(playlist_url)
    
    ############################################################################
    # 참고) 아래 방법으로 간다하게 구현할 수 있으나 고해상도 영상을 다운받을 수 없음!!    
    # for video in p.videos:
    #     video.streams.first().download(DOWNLOAD_FOLDER)
    ############################################################################
    
    print(p.title)
    DOWNLOAD_FOLDER = p.title
    
    # 플레이리스트 제목으로 폴더 생성
    createFolder(DOWNLOAD_FOLDER)

    # 플레이리스트 안의 각 영상마다 URL을 가져온 후 지정한한 경로에 다운로드를 해줌
    for url in p.video_urls:
        print(url)
        yt = YouTube(url)
        stream = yt.streams.get_highest_resolution()            
        stream.download(output_path=DOWNLOAD_FOLDER)
        
    print(p.title + " 다운로드 완료\n")

if __name__ == "__main__":
    
    # 다운로드 받을 플레이리스트 url을 리스트에 넣어줌
    playlist_url =  ['https://www.youtube.com/watch?v=~~~~~~', 
                 'https://youtube.com/playlist?list=~~~~~~~~~~~',
                 'https://youtube.com/playlist?list=~~~~~~~',
                 'https://youtube.com/playlist?list=~~~~~~~~~',
                 'https://youtube.com/playlist?list=~~~~~~~~-',
                 'https://youtube.com/playlist?list=~~~~~~-5mWbx0zdG0betdeoL']
    
    # 플레이리스트 url을 담고 있는 리스트를 반복문 안 에서 다운로드 함수 매개변수에 할당해주면서 실행
    for i in playlist_url:
        playlist_download(i)

추가로 단일 유튜브 영상을 받고 싶으시면 아래 처럼 간단하게 구현할 수 있습니다.

from pytube import YouTube
url = '유튜브 동영상 url (플레이리스트 url 아님!)'
DOWNLOAD_FOLDER = '다운로드 받을 폴더 경로' #빈값('')으로 두면 파이썬 소스파일이 있는 폴더에 다운로드 받습니다
yt = YouTube(url)
stream = yt.streams.get_highest_resolution()            
stream.download(output_path=DOWNLOAD_FOLDER)

 앞서 예를 든 상황처럼 게시글이 등록했을 때 teams로 알림을 받고 싶은 내용 중에 '제목, 글쓴이, 내용, 날짜'등이 있을 수 있습니다.

 이런 내용들을 파라미터로 url에 실어서 호출해주면 그 내용도 teams로 받을 수 있습니다.

http://localhost/send_message.php?param0=0번&param1=1번&param2=2번&param3=3번

 위와 같이 (pram0 ~ param3, 4개의 파라미터)url을 호출하여 실제로 teams에 메시지가 어떻게 전송되는지 확인해보겠습니다. 


 1. 코드 작성

 php 서버에 'send_message.php'라는 파일을 생성하여 아래처럼 코드를 작성해줍니다.

param0 부터 param10까지 11개로 요청받은 파라미터를 처리하는 예제입니다.

<?php
error_reporting(E_ALL);
ini_set("display_errors", 1);

if(extension_loaded("curl")){
	echo "cUrl extension is loaded";
}else{
 echo "cUrl extension is not available";
}

function WebhookSendMessage($text){
    $url = '생성한 WEBHOOK 주소';
    $ch = curl_init();
    
    $test =[
        "@type" => "MessageCard",
        "@context" => "http://schema.org/extensions",
        "summary" => $text["param5"]." ".$text["param4"]." ".$text["param3"]." ".$text["param2"],
        "themeColor" => "0076D7",
        "title" => "메시지 제목",
        "sections" => [
            [
                "activityTitle" => "",
                "activitySubtitle" => "",
                "activityImage" => "",
                "facts" => [
                    [
                        "name" => "Parameter 0",
                        "value" => $text["param0"]
                    ],
                    [
                        "name" => "Parameter 1",
                        "value" => $text["param1"]
                    ],
                    [
                        "name" => "Parameter 2",
                        "value" => $text["param2"]
                    ],                    
                    [
                        "name" => "Parameter 3",
                        "value" => $text["param3"]
                    ]    
                ],
                "text" => "테스트 메시지 입니다."
            ]
        ]
    ];

    $jsonDataEncoded = json_encode($test, true);  
    
    $header = array();
    $header[] = 'Content-type: application/json';

    curl_setopt($ch, CURLOPT_URL,$url);
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1 );
    curl_setopt($ch, CURLOPT_POST, 1);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $jsonDataEncoded);
    curl_setopt($ch, CURLOPT_HEADER, true);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_HTTPHEADER, $header);

    echo "<br><br> 전송결과 <Br>";
    $result = curl_exec($ch);
    curl_close($ch);
    echo "결과값 ";
    var_dump($result);
}

$messageArray = array(
    "param0" => trim($_GET['param0']),
    "param1" => trim($_GET['param1']),
    "param2" => trim($_GET['param2']),
    "param3" => trim($_GET['param3']),
    "param4" => trim($_GET['param4']),
    "param5" => trim($_GET['param5']), 
    "param6" => trim($_GET['param6']),
    "param7" => trim($_GET['param7']),
    "param8" => trim($_GET['param8']), 
    "param9" => trim($_GET['param9']), 
    "param10" => trim($_GET['param10']), 
);

echo "입력받은 파라미터 출력 <Br>";
print_r($messageArray);

WebhookSendMessage($messageArray);
?>

2. Get 방식으로 url에 파라미터를 전달하여 메시지가 전달되는지 확인해봅니다.

http://localhost/send_message.php?param0=0번&param1=1번&param2=2번&param3=3번

3. URL 호출 결과확인

 여기서 warning 경고는 무시하셔도 됩니다.

 위에 코드에서처럼 모든 에러를 출력하게 작성하였기 때문에 선언이 안 된 param4~ param10까지의 파라미터에 값이 없어서 발생한 경고입니다.

결과확인

 결과 화면을 살펴보시면 아래 부분에 http/2 200 부분에 정상적으로 호출된 것을 확인할 수 있습니다.


4. Teams 메시지 확인

 그럼 실제로 teams에 메시지가 잘 도착했을까요

팀즈에도 잘 도착한 것을 확인할 수 있습니다.


 php curl, json 처리 등의 방식을 활용하여 teams webhook을 활용하여 notification을 간단하게 구현해봤습니다.

 

 MS Teams 메신저의 Notification 기능을 간단하게 구현해봤습니다.

 예를 들어 웹 사이트에 게시글이 등록(수정) 될 때 마다 알림을 받고 싶은 경우를 들어 설명하겠습니다.

대략적인 시스템 구성은 아래와 같습니다.

API 순서

 먼저 teams에 알림을 받기 위해선 webhook을 생성해 줘야하며 순서는 아래를 참고해주세요

*참고 : Webhook(웹훅)이란?
https://simsimjae.medium.com/%EC%9B%B9%ED%9B%85%EC%9D%B4%EB%9E%80-e41cf1ba92f0
(참고 : https://g.co/kgs/Zv5x7p)

1. 팀즈 '팀 만들기'

팀 - 팀 만들기


2. 팀 - 채널 만들기

채널 만들기


3. 채널 - 커넥터 생성

 


4. 커넥터 - Incoming Webhook 구성하기


5. webhook 생성


5. Webhook 복사

 아래 생성된 Webhook URL을 복사해둡니다.

만약 URL을 다시 확인하려면?

왼쪽에 '구성됨' 메뉴를 선택후 구성한 Webhook을 클릭 후 '관리'를 누르시면 확인할 수 있습니다.


 

 

 

 

+ Recent posts