반응형

이번에는 futures에 대해서 알아보겠습니다.

 

futures는 요청들을 비동기적으로 수행하기 위한 인터페이스를 제공해 주는 모듈입니다. 

 

 

Executor

concurrent.futures.Excutor 는 비동기 요청을 수행할 때 필요한 메써드들을 정의해 둔 추상 클래스 입니다. 여기에는 submit, map, shutdown 메써드들이 정의되어 있습니다.

 

submit 수행될 요청을 제출하는 메써드입니다. 결과로 (뒤에서 설명할) Future 인스턴스를 반환합니다.
map 같은 function에 list형태로 정의된 매개변수들을 각각 전달해서 수행하는 경우 사용합니다.
shutdown executor에게 할당된 리소스를 정리하라는 signal을 보냅니다.
shutdown이 호출된 executor에게 submit 혹은 map 를 호출하면, RuntimeError가 발생합니다.

참고: shutdown 을 호출했다고 해서, 수행 중인 모든 동작이 바로 멈추는 것은 아닙니다. 다만 cancel_futures 매개변수 값에 따라서, 아직 시작되지 않고 대기중인 요청을 바로 취소할 건지, 아니면 대기중인 요청까지 모두 끝나고 나서 리소스를 정리할지가 결정됩니다.

 

 

python (버전 3.12.4 기준)에서 기본으로 제공되는 executor에는 ThreadPoolExecutor와 ProcessPoolExecutor 가 있습니다. 이름에서 유추할 수 있듯이, ThreadPoolExecutor는 요청을 수행할 때, ThreadPool을 사용하고, ProcessPoolExecutor는 요청을 수행할 때, ProcessPool을 사용합니다.

Process 와 Thread의 차이를 간략하게 정리하면, Process는 독립적으로 수행되며, 메모리 등의 리소스를 따로 할당해서 수행하게 됩니다. Thread는 Process 내에서 수행이 되며, 하나의 Process 안에서 수행되는 Thread들 간에는 리소스를 공유하게 됩니다. 

따라서 ProcessPoolExecutor 의 경우, 요청들이 Process 단위로 수행되기 때문에, 수행시에 Process에게 리소스를 따로 할당되어야 하기 때문에, overhead가 발생됩니다. 반면 ThreadPoolExecutor의 경우, 리소스가 공유되기 때문에 리소스 할당에 따른 overhead는 없지만, 잘못 사용하게 되면 deadlock이 발생할 수 있습니다.

결론을 이야기하면, 요청 하나 하나의 수행 작업이 오래걸리는 경우 (CPU-bound tasks)에는 리소스 할당에 시간이 소요되더라도 ProcessPoolExecutor가 적합하고, 처리할 데이터가 많지만, 하나 하나의 수행 작업은 빠르게 처리되는 경우 (I/O bound tasks)에는 ThreadPoolExecutor를 사용하는 것이 좋습니다.

 

 

Future

executor에게 요청을 submit하면, Future 인스턴스를 반환해 줍니다. 이 Future 인스턴스를 통해서, executor에게 전달한 요청의 상태를 확인할 수 있습니다. 다음은 Future 객체에서 사용할 수 있는 method 들 입니다. (일부만 발췌했으며 전체 리스트를 확인하고 싶으시면 다음의 링크를 참하세요. https://docs.python.org/3/library/concurrent.futures.html)

 

canceled 수행될 요청이 취소되었는지에 대한 결과를 돌려줍니다.
running 요청이 지금 현재 수행되고 있는지 상태를 돌려줍니다.
done 요청이 취소되었거나 완료되었으면 True를 돌려줍니다. 
cancel 아직 대기 중인 요청에 한해서 요청을 취소합니다.
result 요청 수행에 대한 결과를 돌려줍니다. 만약 요청이 아직 끝나지 않았다면, 결과가 나올때까지 기다립니다. (block)

 

 

Module functions

concurrent.futures에 정의되어 있는 futures 에서 사용하는 funciton들 입니다. future 요청은 한 가지 요청만 비동기로 처리하는 경우도 있지만, 보통의 경우 여러 요청을 동시에 처리하기 위해서 많이 사용되기 때문에, 전체 요청들에 대한 결과를 받아서 처리해야 하는 경우, 아래의 function들을 사용하게 됩니다.

wait Future 인스턴스들의 요청이 끝날 때까지 기다립니다. timeout을 지정할 수도 있고, return_when argument를 이용해서 첫 번째 요청이 끝날때, 처음 exception 이 발생했을 때, 모든 요청이 다 처리되었을 때 등의 옵션을 지정할 수 있습니다.
as_completed Future 인스턴스들의 iterator를 반환해 줍니다. iterator는 generator처럼 동작합니다. 완료된 요청 순으로 결과가 나온 Futuer 인스턴스를 yield 해 줍니다. (비동기적으로 결과를 반환해 줍니다.) 

 

 

 

Futures 사용 예제

아래 코드는 간단히 작성한 사용 예제 입니다. (docs.python.org 에 있는 예시를 활용하였습니다.)

 

 

먼저 map을 이용해서 요청을 처리하는 예시입니다. 

import concurrent.futures
import urllib.request

TEST_URLS = ['https://www.google.com', 'https://www.naver.com', 'https://www.tistory.com']

def load_url(url):
    try:
        with urllib.request.urlopen(url) as conn:
            return url, conn.read(), None
    except Exception as exc:
        return url, None, str(exc)

with concurrent.futures.ThreadPoolExecutor() as executor:
    for url, data, err in executor.map(load_url, TEST_URLS, timeout=60):
        if data:
            print('%r page is %d bytes' % (url, len(data)))
        else:
            print('%r generated an exception: %s' % (url, err))

 

 

map은 as_completed 와 비슷한데, 결과 iterator에는 Future 인스턴스가 아닌, 인스턴스의 result 들을 반환해 줍니다. map을 억지로 풀어쓴다면 다음과 같습니다.

with concurrent.futures.ThreadPoolExecutor() as executor:
    for url, data, err in [f_instance.result() for f_instance in 
        concurrent.futures.as_completed([executor.submit(load_url, url) for url in TEST_URLS])]:
        if data:
            print('%r page is %d bytes' % (url, len(data)))
        else:
            print('%r generated an exception: %s' % (url, err))

 

 

여기서 이야기하고자 하는 바는, map의 경우에는 Future 인스턴스에서 result를 읽는 과정이 포함되어 있기 때문에, 특정요청에서 exception 이 발생하는 경우, iterator를 수행하는 과정에서 exception이 발생하게 됩니다. 이 exception으로 문제가 생기는 것을 막기 위해서는, 요청 내에서 exception이 발생하더라도 같은 포맷으로 결과를 리턴해 줄 수 있게 디자인해야 합니다.

 

위의 예시를 as_completed 를 사용해서 변경하면 다음과 같습니다.

import concurrent.futures
import urllib.request

TEST_URLS = ['https://www.google.com', 'https://www.naver.com', 'https://www.tistory.com']

def load_url(url, timeout):
    with urllib.request.urlopen(url, timeout=timeout) as conn:
        return conn.read()

with concurrent.futures.ThreadPoolExecutor() as executor:
    future_to_url = {executor.submit(load_url, url, 60): url for url in TEST_URLS}
    for future in concurrent.futures.as_completed(future_to_url):
        url = future_to_url[future]
        try:
            data = future.result()
        except Exception as exc:
            print('%r generated an exception: %s' % (url, exc))
        else:
            print('%r page is %d bytes' % (url, len(data)))

 

 

여기서는 as_completed가 future instance 에서 결과를 받아오는 시점에서 exception을 처리할 수 있기 때문에 요청에서 Exception 이 발생한다고 해도, iterator에서 처리가 가능합니다.

 

 

마지막으로 wait을 사용하면, list에 future instance를 호출한 순서대로 저장하여, list를 이용해서 호출한 순서대로 결과를 처리할 수 있습니다. 참고로 wait function은 반환값으로 as_completed와 같이 처리된 순서대로 결과를 처리할 수 있는 generator 형태의 iterator를 돌려줍니다.

 

import concurrent.futures
import urllib.request

TEST_URLS = ['https://www.google.com', 'https://www.naver.com', 'https://www.tistory.com']

def load_url(url, timeout):
    with urllib.request.urlopen(url, timeout=timeout) as conn:
        return conn.read()

with concurrent.futures.ThreadPoolExecutor() as executor:
    future_list = [executor.submit(load_url, url, 60) for url in TEST_URLS]
    concurrent.futures.wait(future_list)
    for idx, future in enumerate(future_list):
        url = TEST_URLS[idx]
        try:
            data = future.result()
        except Exception as exc:
            print('%r generated an exception: %s' % (url, exc))
        else:
            print('%r page is %d bytes' % (url, len(data)))

 

 

반응형
반응형

Python 의 decorator 는 funciton 이나 class에 코드의 내용을 변경하지 않고도 기능을 확장할 수 있는 아주 유용한 툴입니다. 

Decorator를 이해하기 위해서는 몇 가지 알아두면 좋은 내용들이 있어서 먼저 알아보고, decorator에 대해서 알아보도록 하겠습니다. 

 

 

First Class Objects

먼저 한국말로는 일급 객체라고 이야기하는 first class objects에 대해서 알아보겠습니다. 일급 객체는 다른 객체들에 일반적으로 적용 가능한 연산을 모두 지원하는 객체를 가리킵니다. 보통 함수에 인자로 넘기기, 수정하기, 변수에 대입하기와 같은 연산을 지원할 때 일급 객체라고 합니다.

 

일급 객체는 다음과 같은 특징이 있습니다. (by 로빈 포플스톤)

  • 모든 요소는 함수의 실제 매개변수가 될 수 있다.
  • 모든 요소는 함수의 반환 값이 될 수 있다.
  • 모든 요소는 할당 명령문의 대상이 될 수 있다.
  • 모든 요소는 동일 비교의 대상이 될 수 있다.

 

Python에서는 function 도 일급 객체로 분류됩니다. 아래 예시를 보면 쉽게 이해가 될 겁니다.

def add_10(val):
    return val + 10

def run_func(f, val):
    return f(val)

# (add_10 함수를) 매개변수로 사용
run_func(add_10, 10)

# (add_10 함수를) 반환값으로 사용
def plus_10():
    return add_10

# (add_10 함수를) 다른 변수에 할당
plus_ten = add_10

# 비교의 대상으로 사용
print(add_10 is run_func)

 

여기서 일급객체를 특별히 설명한 이유는 Python에서는 "function"이 일급객체이기 때문에, 매개변수로 사용할 수 있으며, function을 리턴 값으로 사용할 수 있다는 특징을 상기시키기 위함입니다. 많은 프로그래밍 언어들이 function을 일급객체로 사용하기 때문에 특별히 거부감이 있는 컨셉은 아닐겁니다.

 

 

Inner Functions

Python에서는 함수의 내부에 다른 함수를 정의하는 것이 가능합니다. 내부에 정의된 함수를 inner functions 라고 부르는 데요.. 한국말로는 내부 함수가 적절할 표현일 것 같습니다. (어떤 분은 내장 함수라고 번역하시기도 했는데요, 일반적으로 내장함수는 프로그래밍 언어에서 지원해주는 기본 함수를 이야기 하기 때문에 내부 함수라는 표현이 더 적절할 것 같습니다.)

 

아래 예시를 보면, parent 함수 내에 child  함수를 정의해서 사용하였습니다. child 함수의 scope은 parent 함수 내에서만 유효합니다.

def parent():
    print("parent")
    def child():
    	print("child")
    child()

 

 

바로 전에서 이야기했던 일급객체의 개념을 같이 사용하면 아래와 같은 사용이 가능합니다.

def parent(num):
    def first_child():
        return "I'm first child"
    def second_child():
        return "I'm second child"
    def others():
        return "not exist"
    
    # match - case는 python 3.10 이후 부터 지원
    match num:
        case 1:
            return first_child
        case 2:
            return second_child
        case _:
            return others

child1 = parent(1)
print(child1())

child2 = parent(2)
print(child2())

child3 = parent(3)
print(child3())

 

 

Simple Decorators

위에서 decorator를 구현하기 위한 중요한 컨셉들에 대해서 알아보았으니, 이제 decorator로 넘어갈 차례입니다. 먼저 아래 예시를 살펴보겠습니다.

 

def decorator(func):
    def wrapper():
        print("BEFORE")
        func()
        print("AFTER")
    return wrapper

def some_function():
    print("RUN")

some_function = decorator(some_function)

 

위의 예시에는 decorator() 함수와, some_function() 함수가 정의되어 있고, decorator() 함수에는 wrapper() 라는 내부 함수가 정의되어 있습니다. 그리고 마지막 줄에서는 some_function()을 decorator() 함수를 실행한 결과로 변경하였습니다.

 

여기서 some_function()을 실행하면 아래와 같은 결과가 화면에 출력될 겁니다.

>>> some_function()
BEFORE
RUN
AFTER

 

 

원래 some_function() 함수에서는 "RUN"만 출력해 주는데, decorator()를 통해서 변경한 some_function()은 "BEFORE", "RUN", "AFTER"를 차례로 출력해 줍니다. 쉽게 이야기해서 decorator() 함수가 some_function() 함수를 감싸서, 변경해 주었다고 할 수 있습니다.

 

실제로 decorator를 사용할 때는, 아래와 같이 @ 기호를 붙여서 함수 앞에 붙여 주면 됩니다. 아래 예시에서는 @decorator 는 some_function = decorator(some_function) 를 짧게 표현해 주는 방법으로 생각할 수 있습니다.

def decorator(func):
    def wrapper():
        print("BEFORE")
        func()
        print("AFTER")
    return wrapper

@decorator
def some_function():
    print("RUN")

 

 

아래처럼 check_performance() decorator를 이용하면, 각 함수의 수행 시간을 알 수 있습니다.

import time
def check_performance(func):
    def wrapper():
        begin = time.time()
        func()
        end = time.time()
        print("Time: ", func.__name__, end - begin)
    return wrapper

@check_performance
def some_function():
    print("RUN")

 

 

매개 변수가 있는 함수를 위한 decorator

이전 예시에서는 매개변수가 없는 간단한 함수에 대한 decorator를 정의해서 사용해 봤는데요, 이번에는 원래 함수에 매개변수가 있는 경우에 대해서 decorator를 작성해 보겠습니다.

 

위의 예시를 조금 변경해보면, 다음과 같습니다. *args 와 **kwargs 를 이용해서 매개변수를 전달해 주면 됩니다.

import time
def check_performance(func):
    def wrapper(*args, **kwargs):
        begin = time.time()
        func(*args, **kwargs)
        end = time.time()
        print("Time: ", func.__name__, end - begin)
    return wrapper

@check_performance
def some_function(some_param):
    print("RUN", some_param)

 

 

Decorator 함수에서 결과를 돌려 받기

지금까지의 예시에서는 화면에 결과를 출력하고 끝내는 간단한 예시였는데, 이번에는 결과를 돌려받는 경우를 가정해 보겠습니다.

 

import time
def check_performance(func):
    def wrapper(*args, **kwargs):
        begin = time.time()
        func(*args, **kwargs)
        end = time.time()
        print("Time: ", func.__name__, end - begin)
    return wrapper

@check_performance
def say_hi(name):
    return f"Hi {name}"

 

 

print( say_hi("Alex") ) 를 실행해 보면, 함수의 수행 시간과 "None" 만 출력될 뿐, "Hi Alex" 는 출력되지 않습니다.

>>> print(say_hi("Alex"))
Time:  say_hi 2.1457672119140625e-06
None

 

 

아래와 같이 wrapper를 수정하면, 원하는 결과를 돌려받을 수 있습니다. 

 

import time
def check_performance(func):
    def wrapper(*args, **kwargs):
        begin = time.time()
        ret = func(*args, **kwargs)
        end = time.time()
        print("Time: ", func.__name__, end - begin)
        return ret
    return wrapper

@check_performance
def say_hi(name):
    return f"Hi {name}"

 

 

Decorator 에 parameter 설정하기

이번에는 decorator 에 parameter를 설정하는 방법을 살펴보겠습니다.

 

아래 예시는 Django에 내장되어 있는 login_required decorator를 커스텀 버전으로 구현해 본 코드입니다. (실제 구현은 아래 코드와는 상이하게 작성이 되어 있습니다. 실제 코드가 궁금하신 분은 다음 링크 참고하세요. https://github.com/django/django/blob/main/django/contrib/auth/decorators.py)

from django.http import HttpResponse
from django.http import HttpResponseRedirect

def custom_login_required(*args, **kwargs):
    def _inner_func(func):
        def wrapper(*f_args, **f_kwargs):
            request = f_args[0]
            if request.user.is_authenticated:
                return func(*f_args, **f_kwargs)
            else:
                return HttpResponseRedirect(kwargs.get('login_url', '/') + f"?next={request.path}")
        return wrapper
    return _inner_func

@custom_login_required(login_url='/login/')
def test_view(request):
	return HttpResponse('success')

 

 

참고로 parameter가 필요한 decorator를 사용하게 되면, 실제 전달 인자가 없는 경우에도 함수를 호출하는 형태로 decorator를 사용해야 합니다. 

# Error: 'function' object has no attribute 'get'
@custom_login_required
def test_view_wrong(request):
	return HttpResponse('success')
    
# working!
@custom_login_required()
def test_view_correct(request):
	return HttpResponse('success')

 

 

참조 웹 사이트

이 글을 작성하면서 아래 웹 사이트들을 참조하였습니다. 이 글에서 다루지 않은 내용들도 있으니 한번 확인해 보시는 것도 좋을 듯 합니다.

 

https://realpython.com/primer-on-python-decorators/

 

Primer on Python Decorators – Real Python

In this tutorial, you'll look at what Python decorators are and how you define and use them. Decorators can make your code more readable and reusable. Come take a look at how decorators work under the hood and practice writing your own decorators.

realpython.com


https://www.geeksforgeeks.org/decorators-in-python/

 

Decorators in Python - GeeksforGeeks

A Computer Science portal for geeks. It contains well written, well thought and well explained computer science and programming articles, quizzes and practice/competitive programming/company interview Questions.

www.geeksforgeeks.org

 

반응형

'프로그래밍 > Python' 카테고리의 다른 글

9. 파일 관리  (0) 2019.02.21
8. 파일 읽고 쓰기  (0) 2019.02.21
7. 정규 표현식  (0) 2019.01.09
6. 문자열 다루기  (0) 2019.01.08
5. 사전 (Dictionary)  (0) 2019.01.07
반응형


기본 압축 명령어 사용법

gzip, gunzip 을 사용한다.

gzip, gunzip 커맨드는 기본적으로 개별 파일 단위로 압축 / 해제를 진행하며, 압축 / 해제 진행시 원래 파일은 삭제된다. 압축시에는 원래 파일명에 .gz 확장자가 추가된 파일이 생성된다.

 

 

@ 압축하기

> gzip test.txt

 

위의 명령어가 실행되고 나면, test.txt.gz 파일이 생성되며, test.txt 파일은 삭제된다.

 

 

@ 압축 해제

> gunzip test.txt.gz

 

압축 해제시에도 마찬가지로 test.txt 파일이 생성되며, test.txt.gz 파일은 삭제된다.

 

압축 해제는 gzip의 -d 옵션으로도 가능하다. (decompress)

> gzip -d test.txt.gz

 


@ 압축 / 해제 후 원래 파일을 유지하기

-k 옵션을 사용하면 된다. (keep)

 

> gzip -k test.txt

 

 

@ 여러 파일을 동시에 압축하기

파일명을 차례대로 적어 주면 된다.

> gzip a.txt b.txt c.txt

 

혹은 

> gzip *.txt

 

실행후에는 a.txt.gz, b.txt.gz, c.txt.gz 의 형태로, 개별 파일이 생성된다. 그리고 원래 파일인 a.txt, b.txt, c.txt 파일은 삭제가 된다.

 

 

pigz 명령어 사용법

여러 파일을 압축해야 한다면, 멀티 코어 CPU를 지원하는 pigz를 사용하면 더 빠른게 압축이 진행된다.

아래 링크에서 배포하고 있으며, 최신 리눅스에서는 apt나 yum으로 설치가 가능하다.

http://zlib.net/pigz/

 

기본 동작 방식이나 옵션은 gzip, gunzip 과 매우 유사하다. 

> pigz *.txt

 

위의 명령어가 실행되면 .txt.gz 파일들이 생성되고, 원본 txt 파일들은 삭제된다.

 

압축해제

> pigz -d *.txt.gz

 

 

tar 명령어 사용법

여러 파일들을 하나의 파일로 묶어줄 때는 tar 명령어를 사용한다.

 

@ tar 명령어를 이용해서 하나의 파일로 묶기

> tar -cvf all.tar *.txt

 

위의 명령어를 실행하면, all.tar 파일이 생성되며, 원래 txt 파일들은 삭제되지 않는다.

 

 

@ tar 명령어를 이용해서 묶인 파일을 해제하기

> tar -xvf all.tar

 

위의 명령어를 실행하면 all.tar 파일에 묶여 있던 txt 파일들이 생성되며, all.tar 파일은 삭제되지 않는다. 

 

묶을 때는 c 옵션, 해제할 때는 x 옵션

-v 옵션은 verbose를 의미하며, 압축 / 해제되는 파일을 화면에 표시되게 해준다.

-f 옵션은 파일명을 지정할 때 사용한다.

 

 

@ 하나의 파일로 묶으면서 gzip으로 압축하기

-z 옵션을 추가한다.

> tar -czvf all.tar.gz *.txt

 

 

@ 하나의 파일로 묶으면서 bzip2로 압축하기

-j 옵션을 추가한다.

> tar -cjvf all.tar.bz2 .

 

 

@ 압축된 tar 파일 해제하기

> tar -xvf all.tar.gz
> tar -xvf all.tar.bz2

 

 

 

cf. all.tar.gz 파일을 gzip (혹은 gunzip)으로 압축 해제하면, all.tar 파일이 생성된다. gzip (혹은 gunzip)으로 압축 해제해서 생성된 all.tar 파일은 -cvf 옵션으로 그냥 tar로 묶은 파일과 동일하다.

 

cf2. 압축 혹은 묶을 대상 파일들을 '*' 대신 '.' 으로 쓰는 이유는.. '*' 을 사용할 경우, 현재 폴더에 있는 hidden 파일들이 포함되지 않기 때문임 (sub 폴더 안에 있는 hidden 파일들은 포함이 됨)

 

 

응용편

@ tar에서 pigz을 이용하기

> tar -cvf all.tar.gz -I pigz .

 

현재 경로에 있는 모든 파일들을 pigz를 사용해서 압축하라는 의미

-I 옵션은 --use-compress-program 의 짧은 표현

 

아래 명령어와 결과는 같지만 CPU 코어 수에 따라 훨씬 빠른 결과가 나올 수 있음

> tar -czvf all.tar.gz .

 

아래와 같이 사용도 가능함

> tar -cvf - . | pigz > all.tar.gz

 

압축을 해제할 때도 마찬가지로 사용가능

> tar -xvf all.tar.gz -I pigz

 

 

@ tar 작업시 working directory 지정하기

> tar -xzvf all.tar.gz -C ../test

 

-C 옵션은 --directory 의 짧은 표현으로, 원래 GNU 스펙상으로는 tar로 묶는 경우에도 작동하게 되어 있는데.. 실제로 많은 linux 배포판에서 묶는 경우에는 제대로 작동하지 않고, 해제시에만 제대로 작동한다고 함.. Ubuntu 에서 테스트시에도 묶는 경우에는 제대로 동작하지 않았음.

 

만약 묶는 경우에도 directory 옵션을 사용하고 싶다면.. 아래와 같이 압축될 파일명에 절대 경로를 지정하면, 우회해서 사용이 가능함.

> cd /opt/test && tar -cvf /root/test.tar.gz -I pigz . && cd -

 

 

@ standard output 

gzip, gunzip, pigz 등은 -c 옵션으로 결과를 standard out (화면출력) 으로 처리가 가능.

이 경우 원본 파일이 삭제 되지 않음

 

따라서 아래 커맨드들은 모두 결과가 (거의) 같음

> gzip -k abc.txt
> gzip -c abc.txt > abc.txt.gz
> pigz -c abc.txt > abc.txt.gz

 

테스트 결과 Ubuntu기준 -k 옵션을 사용한 경우에는 abc.txt.gz 의 파일 생성 일자가 abc.txt와 같은 날짜로 기록되는데, redirect (>) 를 사용한 경우에는 파일 생성 일자가 실행시간으로 기록됨

반응형
반응형

cURL (client URL) 은 오픈소스 커맨드 라인 툴로, 서버간에 데이터를 전송할 때 사용합니다. 저는 주로 웹 서비스의 method들을 테스트 할 때나 웹에서 간단하게 파일을 다운로드 받을 때 많이 사용했었는데, 실제로는 HTTP, HTTPS 프로토콜 이외에도 FTP, SCP, SMB, LDAP, IMAP, SMTP 등을 포함한 상당수의 인터넷 프로토콜을 지원한다고 합니다.

 

 

기본 사용법

cURL은 대부분의 리눅스와 윈도우(10 이후), MacOS에도 기본으로 설치가 되어 있기 때문에 범용적으로 사용이 가능합니다. 터미널을 열어서 "curl --help"를 입력해보면, 주로 사용하는 옵션에 대한 설명이 나옵니다. 전체 옵션을 보고 싶다면 "curl --help all" 을 입력하면 됩니다. 참고로 Windows Powershell에서는 커맨드를 wrapping 하기 때문에 조금 사용법이 다릅니다. 다음 섹션부터 기본적인 http(s) 요청을 테스트하는 방법에 대해서 알아보겠습니다.

 

 

GET

cURL에서 아무런 옵션없이 URL을 입력하면 GET 방식의 요청으로 인식됩니다.

 

아래 요청은 www.google.com  에 GET 방식으로 해당 URL의 데이터를 가지고 옵니다.

> curl https://www.google.com

 

 

실제로 실행을 해보면, google.com 의 html 소스 코드가 화면에 텍스트 형태로 출력됩니다.

 

 

POST

POST 방식으로 데이터를 요청하기 위해서는 -X 옵션을 사용합니다.

 

ex)

> curl -X POST http://localhost:3000/data

 

 

보통 POST 방식으로 데이터를 요청할 때는, header나 request body (payload) 등의 데이터를 같이 전송하게 됩니다. 각각 -H, -d 옵션으로 추가할 수 있습니다.

 

ex)

> curl -X POST -d "param1=value1&param2=value2" -H "Content-Type: application/x-www-form-urlencoded" http://localhost:3000/data

 

 

데이터 (-d) 옵션은 여러 개로 나눠서 쓸 수 있습니다. 이 때 전송되는 데이터는 "&" 로 연결이 되어서 전송됩니다. 즉, 아래 요청과 바로 위에서 사용한 -d "param1=value1&param2=value2" 요청은 같은 데이터로 요청이 됩니다.

> curl -X POST -d "param1=value1" -d "param2=value2" -H "Content-Type: application/x-www-form-urlencoded" http://localhost:3000/data

 

 

request body 가 json 형태인 경우 아래와 같이 사용하면 됩니다.

 

ex)

> curl -X POST -d "{\"key1\": \"value1\", \"key2\": \"value2\"}" -H "Content-Type: application/json" http://localhost:3000/data

 

 

request body를 파일에서 읽어서 전송할 수도 있습니다. -d 옵션 혹은 -T 옵션을 사용할 수 있습니다.

 

ex)

> curl -X POST -d "@data.json" -H "Content-Type: application/json" http://localhost:3000/data
> curl -X POST -T data.json -H "Content-Type: application/json" http://localhost:3000/data

 

 

웹 페이지에서 폼 전송하는 것을 테스트하고 싶다면, -F 옵션을 사용합니다. 폼 데이터를 전송하게 되면 default 헤더는 multipart/form-data 로 변경이 됩니다.

> curl -F "username=testuser" -F "password=1234" -X POST http://localhost:3000/form

 

 

폼 전송으로 파일도 업로드 할 수 있습니다.

> curl -F "file1=@data.json" -X POST http://localhost:3000/form

 

 

그 외의 옵션들

cURL 옵션은 상당히 많기도 하고, 조합해서 사용할 수 있기 때문에 모두 기술하는 것은 시간낭비가 될 것 같아서, 자주 쓰이거나 유용한 옵션에 대해서 이야기 해 보겠습니다.

 

Option Option (long) 기능
-k --insecure https 프로토콜 사용시 안전하지 않은 연결도 허용
-I --head 화면 출력시 헤더만 출력
-i --include 화면 출력시 헤더 및 내용 출력
-O --remote-name 내용을 remote file 이름으로 저장
-o --output <file> 내용을 지정한 파일 이름으로 저장
-v --verbose 전송 내용을 상세히 출력
-V --version cURL 버전 내용 출력
-u --user <user:password> 유저 / 패스워드 전송
-s --silent Slient 모드, 다운로드시 전송 정보를 화면에 출력하지 않음
-L --location redirect 추적 - L 옵션이 없으면 서버에서 redirect 되는 경우 컨텐츠를 받아오지 않음
-b --cookie <data|filename> 쿠키 정보 전송
-C --continue-at <offset> 특정 위치에서부터 이어서 전송

 

 

파일 다운로드 하기

웹 서버의 URL에서 파일을 다운로드 받아야 한다면 다음과 같이 쓸 수 있습니다. (옵션은 연결가능..)

> curl -LOk http://localhost:3000/filename

 

 

원하는 경로에 원하는 이름으로 파일을 지정해서 저장하고 싶다면 -o 옵션을 사용합니다. (파일 이름만 지정해도 됨)

> curl -Lk -o filefullpath http://localhost:3000/filename

 

 

서버에서 redirect 하지 않는 경우에는 L 옵션을 생략 가능하지만, 갑자기 컨텐츠 다운로드가 안되서 당황하는 경우를 피하고 싶다면 그냥 LOk 옵션을 기억해 두는 편이 나을 듯 합니다.

 

 

SFTP / SCP 프로토콜 이용하기

cURL은 sftp 나  scp 등의 프로토콜도 지원하는데요, 이 섹션에서 어떻게 사용하는 지 간단히 알아보겠습니다.

 

ssh 서비스가 활성화되어 있다고 가정하고 아래와 같이 접속해 봅니다. 참고로 sftp와 scp 는 ssh 기반의 프로토콜이기 때문에, ssh 서비스가 활성화되어 있으면 사용이 가능합니다.

> curl sftp://localhost -u <username>

 

접속을 시도하면 password를 입력하는 프롬프트가 나오고, password를 입력하면 다음 단계로 넘어갑니다.

 

만약 localhost에 ssh로 접속한 적이 없다면.. 아래와 같은 에러 메시지를 보게 될 것입니다.

 

 

 

remote 서버가 known_hosts에 등록되어 있지 않아서 생기는 문제인데요.. 시스템 정책상 ssh, scp 등에서만 ~/.ssh/known_hosts 파일을 편집할 수 있는 권한이 있어서 그렇다고 합니다.

 

해결책 방법은 ssh 로 한번 접속해서 remote 서버를 등록해주면 됩니다.

> ssh localhost

 

 

아마 아래와 비슷한 메시지가 나올거고, yes를 입력하면 remote 서버가 known_hosts 에 등록됩니다.

 

 

remote 서버를 등록할 수 없는 상황이라면.. -k 옵션을 사용하면 됩니다. 위에서 설명한 -k 옵션은 insecure 를 의미하기 때문에 등록되지 않아 신뢰할 수 없는 서버도 그냥 접속하겠다라는 의미입니다.

> curl sftp://localhost -ku <username>

 

 

기본적으로 curl sftp 로 접속을 하게 되면 sftp 루트의 파일 리스트를 출력해 줍니다. 만약 특정 경로의 파일 리스트를 보고 싶다면 아래와 같이 입력하면 되는데, 끝에 "/"를 붙여 주셔야 합니다.

 

> curl sftp://localhost/home/<username>/ -ku <username>

 

 

 

그리고 해당 경로에 있는 파일을 다운로드 하려면 파일 경로를 지정해 주면 되고, 파일로 저장할 경우 -o (파일 경로를 지정 해서 저장) 혹은 -O (remote 서버의 파일이름으로 저장) 옵션을 사용하면 됩니다.

 

> curl sftp://localhost/home/<username>/test.txt -kOu <username>

 

 

 

마지막으로 보안상 좋지는 않지만.. 꼭 필요하다면 아래와 같이 password를 붙여서 전송할 수 있습니다. 그러면 password를 물어보는 프롬프트가 나오지 않습니다.

 

> curl sftp://localhost/home/<username>/test.txt -kOu <username>:<password>

 

반응형
반응형

vscode로 개발할 때, debugger 를 사용하면 조금 더 편하게 개발할 수 있습니다.

 

자세한 설명은 아래 링크를 참조하시면 됩니다. (영문)

https://code.visualstudio.com/docs/editor/debugging

 

Debugging in Visual Studio Code

One of the great things in Visual Studio Code is debugging support. Set breakpoints, step-in, inspect variables and more.

code.visualstudio.com

 

 

먼저 debugger를 사용하기 위해서는 각 프로그래밍 언어에 맞는 debugger를 설치해 줘야 합니다. Node.js의 경우에는, 개발에 필요한 debugger가 내장되어 있기 때문에 별도의 설치가 필요 없지만, python 이나 java, C/C++ 등을 debug하기 위해서는 debugger를 설치해 주어야 합니다.

 

여기에서는 Python Django 프로젝트를 진행한다고 가정하고, Python debugger를 다운로드하여 설치하도록 하겠습니다.

 

Debugger를 설치하고 vscode의 왼쪽 아이콘 중에 debug 아이콘을 클릭해 줍니다.

 

 

처음 실행시에는 debugger 설정 파일인 launch.json 파일이 없기 때문에, "create a launch.json file" 링크를 클릭해서 launch.json 파일을 생성합니다.

 

 

링크를 클릭하면 화면 가운데 상단에 아래와 같은 팝업이 나오게 되는데, Python Debugger를 선택합니다.

 

 

그리고 프로젝트에 맞는 템플릿을 선택합니다.

 

 

그러면 아래와 같은 launch.json 파일이 .vscode 폴더 안에 생성이 됩니다.

 

 

파일을 편집해서 아래와 같이 설정하였습니다.

아래 설정은 debug 실행시 Django App이 실행될 때, 아이피 주소는 0.0.0.0, 포트는 8000으로 바인딩을 해 주었고, debugger에 Django Shell 을 추가하여, 필요한 경우 django shell 실행을 할 수 있는 설정입니다.

{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Python Debugger: Django",
            "type": "debugpy",
            "request": "launch",
            "program": "${workspaceFolder}\\manage.py",
            "args": [
                "runserver", "0.0.0.0:8000"
            ],
            "django": true,
            "autoStartBrowser": false
        },
        {
            "name": "Python Debugger: Django Shell",
            "type": "debugpy",
            "request": "launch",
            "program": "${workspaceFolder}\\manage.py",
            "args": [
                "shell"
            ],
            "django": true,
            "autoStartBrowser": false
        }
    ]
}

 

 

debugger를 실행할 때는 debug 탭에서 실행할 debug 프로그램을 선택한 후에, 초록색 실행 (플레이) 아이콘을 눌러주면 됩니다. 참고로 위에 launch.json 에 설정했던 "configurations"."name" 이 선택 가능한 프로그램 이름으로 화면에 나오게 됩니다.

 

 

실행 후에는 필요한 위치에 break point 를 설정할 수 있으며, break point 가 설정되면, runtime 상황에서 해당 위치에서 실행이 중단되어, break point 시점에서 메모리에 올라가 있는 변수들의 실제 값을 확인해 볼 수 있습니다.

 

 

아래 화면에서 처럼 실행이 중단된 시점에서는, 왼쪽 상단의 VARIABLES 탭에서는 tree를 확장하여 각 변수들의 중단된 지점에서의 값을 확인해 볼 수 있으며, 왼쪽 아래에 있는 WATCH 탭에서는 변수명을 입력하거나 function, method 등을 호출해서 결과 값을 출력해서 볼 수 있습니다.

 

 

debugger가 실행되면 화면 어딘가에 나타나는 (보통 상단에 나타남) 아래와 같은 debug toolbar를 이용해서 컨트롤을 할 수 있습니다.

 

 

각각의 아이콘은 순서대로 다음의 동작을 수행합니다.

Continue (F5) / Pause (F6) Continue: 다음 break point로 넘김
Pause: break point 시점에서 값을 확인
Step Over (F10) 현재 break point 위치에서 다음 줄로 break point 이동
Step Into (F11) 현재 break point의 위치가 method 혹은 subroutine 이라면 해당 method 혹은 subroution 의 첫째 줄로 break point 이동, 그렇지 않으면 다음 줄로 break point 이동
Step Out (Shift + F11) 현재 break point 기준으로 현재 method 혹은 subroutine 을 벗어나서 해당 method 혹은 subroutine이 호출되었던 곳으로 break point 이동
Restart (Ctrl + Shift + F5) 현재 실행 중인 debugger 프로그램을 종료하고 다시 시작
Stop (Shift + F5) 현재 실행 중인 debugger 프로그램을 종료

 

반응형
반응형

@ path()

path(route, view, kwargs=None, name=None)

from django.urls import include, path

urlpatterns = [
    path("index/", views.index, name="index"),
    path("book/<name>/", views.book_detail, name="book_detail"),
    path("articles/<slug:title>/", views.article, name="article-detail"),
    path("articles/<slug:title>/<int:section>/", views.section, name="article-section"),
    path("blog/", include("blog.urls")),
    ...,
]

 

 

@ re_path() - route에 regex 패턴을 적용

re_path(route, view, kwargs=None, name=None)

from django.urls import include, path

urlpatterns = [
    re_path(r"^index/$", views.index, name="index"),
    re_path(r"^book/(?P<name>\w+)/$", views.book_detail, name="book_detail"),
    re_path(r"^blog/", include("blog.urls")),
    ...,
]

 

 

@ include()

include(module, namespace=None)

include(pattern_list)

include((pattern_list, app_namespace), namespace=None)

 

 

@ reverse()

reverse(viewname, urlconf=None, args=None, kwargs=None, current_app=None)

 

<app>/urls.py

from django.urls import include, path
from . import views

urlpatterns = [
    path("test/", views.test, name="name-test")
]

 

 

<app>/views.py

from django.urls import reverse
from django.http import HttpResponse

def test(reqeust):
    return HttpResponse("Hello World!")

# using the named URL
# will return "index/"
reverse("name-test")

# passing a callable object
# will return "index/"
reverse(test)

 

 

@ url namespace - case 1

 

<app>/urls.py

from django.urls import include, path
from . import views

app_name = "poll"
urlpatterns = [
    path("test/", views.test, name="name-test")
]

 

 

<app>/views.py

from django.urls import reverse
from django.http import HttpResponse

def test(reqeust):
    return HttpResponse("Hello World!")

# using the named URL with app_name
# will return "index/"
reverse("poll:name-test")

 

 

@ url namespace - case 2

 

<config>/urls.py

from django.urls import include, path

urlpatterns = [
    path("poll/", include(("poll.urls", "url_name_space"))
]

 

include에 module 혹은 module name으로 매핑하는 경우에는 namespace는 <app>/urls.py 에 있는 app_name 이 우선 적용된다. 즉, <app>/urls.py 에 app_name 이 있으면 <app_name>:<path_name> 형식

ex) reverse("poll:name-test")

만약 <app>/urls.py 에 app_name이 없다면 <app_namespace>:<path_name> 이 된다.

ex) reverse("url_name_space:name-test")

 

 

@ url namespace - case 3

 

<config>/urls.py

from django.urls import include, path
from poll.urls import urlpatterns as poll_urlpatterns

urlpatterns = [
    path("poll/", include((poll_urlpatterns, "url_name_space"))
]

 

include에 urlpattern 을 매핑하는 경우에는 <app>/urls.py 전체가 로딩되지 않고, <app>/urls.py/urlpatterns만 읽어서 등록하기 때문에, namespace는 <config>/urls.py 에 있는 app_namespace (여기서는 "url_name_space") 가 적용이 된다. <app_namespace>:<path_name> 형식

ex) reverse("url_name_space:name-test")

 

 

@ url in Django Template

 

Template 에서는 url 키워드를 이용해서 reverse와 같은 결과를 받아올 수 있다. 

<a href="{% url 'poll:name-test' %}">Poll Test</a>

 

 

요약

1. python 코드에서는 url path를 가지고 오기 위해서는 reverse() 를 사용

2. reverse에서 사용하는 namespace는 include를 등록하는 방식에 따라서 약간 차이가 있음.

    - urlpatterns 형식으로 등록했다면, 등록할 때 사용한 app_namespace를 적용

    - module 혹은 module name 으로 등록했다면, 모듈에 있는 app_name이 우선 적용됨

3. Template 에서는 url path를 가지고 오기 위해서 'url' 을 사용

반응형
반응형

이번 포스트에서는 RSA 알고리즘에 대해서 알아보고, openssl 을 이용해서 RSA 키를 생성해보고, 몇몇 프로그래밍 언어에서 RSA 를 이용해서 encrypt / decrypt 하는 방법에 대해서 소개해 보겠습니다.

 

 

RSA 알고리즘

RSA 알고리즘은 비대칭키 알고리즘으로 두 개의 서로 다른 키를 사용해서 암호화와 복호화를 하는 방식입니다. 알고리즘을 개발했던 개발자들(Ron Rivest, Adi Shamir, Leonard Adleman)의 이름 이니셜을 따서 명명하였습니다. 자세한 알고리즘의 원리는 위키를 참고해 주세요.

 

RSA 암호화 - 나무위키 (namu.wiki)

 

RSA 암호화

RSA key cryptosystem 현재 SSL/TLS 에 가장 많이 사용되는 공개키 암호화 알고리즘 이다.

namu.wiki

 

 

OpenSSL로 RSA 키 생성하기

키 파일 생성하는 방법을 요약하면 다음과 같습니다. 

# private key 파일 생성
openssl genrsa -out private-key.pem 4096

# 생성한 private key를 이용해서 public key 파일 생성
openssl rsa -in private-key.pem -pubout -out public-key.pem

# private key를 이용해서 (self-signed) 인증서 발급
openssl req -new -x509 -key private-key.pem -out cert.pem -days 360

# pem 파일을 pfx 파일로 변경
openssl pkcs12 -export -inkey private-key.pem -in cert.pem -out cert.pfx

 

 

위의 내용은 아래 사이트에서 가지고 왔습니다.

https://www.scottbrady91.com/openssl/creating-rsa-keys-using-openssl

 

Creating RSA Keys using OpenSSL

An OpenSSL cheat sheet for creating RSA private keys, public keys, and certificates for use with RSASSA-PKCS1-v1_5 and RSASSA-PSS.

www.scottbrady91.com

 

 

RSA encrypt (with .net 8.0)

이번에는 .net8.0 라이브러리 (System.Security.Cryptography)를 이용해서 데이터를 encrypt 하는 코드 샘플입니다. 참고로 .net5.0 이후는 같은 코드로 동작하지만 .netframework에서는 작동하지 않습니다.

encrypt는 public key를 사용하게 됩니다. 미리 public key를 준비해 둡니다. 저는 위에서 설명한 openssl로 생성한 pem 파일을 열어서 PUB_KEY라는 이름으로 문자열 형태의 리소스를 등록해 두었습니다.

 

using System.Security.Cryptography;

...


        byte[] rsaEncrypt(byte[] encodeData)
        {
            byte[] ret = null;
            // rsa 객체 생성
            using (var rsa = RSA.Create())
            {
            	// resource에서 pem 형식의 public 키를 가지고 옵니다.
                var pubKeyStr = Properties.Resources.ResourceManager.GetString("PUB_KEY");
                // public 키를 import 합니다.
                rsa.ImportFromPem(pubKeyStr);
                // 데이터를 encrypt 합니다.
                ret = rsa.Encrypt(encodeData, RSAEncryptionPadding.OaepSHA256);
            }
            return ret;
        }

 

 

RSA decrypt (with .net 8.0)

이번에는 .net8.0 라이브러리 (System.Security.Cryptography)를 이용해서 데이터를 decrypt 하는 코드 샘플입니다. 참고로 .net5.0 이후는 같은 코드로 동작하지만 .netframework에서는 작동하지 않습니다.

decrypt는 private key를 사용하게 됩니다. 미리 private key를 준비해 둡니다. 저는 위에서 설명한 openssl로 생성한 pem 파일을 열어서 PRI_KEY라는 이름으로 문자열 형태의 리소스를 등록해 두었습니다.

 

using System.Security.Cryptography;

...


        byte[] rsaDecrypt(byte[] encodedData)
        {
            byte[] ret = null;
            // rsa 객체 생성
            using (var rsa = RSA.Create())
            {
            	// resource에 저장된 private 키를 가지고 옵니다.
                var priKeyStr = Properties.Resources.ResourceManager.GetString("PRI_KEY");
                
                //  private 키를 import 합니다.
                rsa.ImportFromPem(priKeyStr);
                
                // decryhpt 합니다.
                ret = rsa.Decrypt(encodedData, RSAEncryptionPadding.OaepSHA256);
            }
            return ret;
        }

 

 

RSA 코드 테스트

아래 코드를 이용하면, encrypt / decrypt 결과를 테스트 할 수 있습니다.

 

        public void test()
        {
            string str = "Hello";

            // encrypt string data - string을 byte[] 형태로 변경해서 전달
            byte[] buf = rsaEncrypt(Encoding.UTF8.GetBytes(str));
            // buf를 base64 string 형태로 출력
            Debug.WriteLine(Convert.ToBase64String(buf));

            // decrypt encrypted data
            buf = rsaDecrypt(buf);
            // buf를 string 형태로 출력
            Debug.WriteLine(Encoding.UTF8.GetString(buf));
        }
반응형
반응형

 

@ User가 속한 Group 리스트 가져오기

from django.contrib.auth import get_user_model

user = get_user_model().objects.get(username='<username>')
groups = user.groups.all()

 

cf. django.contrib.auth.models.User 를 이용해도 상관 없음

 

 

@ Group에 속한 User 리스트 가져오기

from django.contrib.auth.models import Group

group = Group.objects.get(name='<group_name>')
users = group.user_set.all()

 

 

@ User Group Check

from django.contrib.auth import get_user_model

user = get_user_model().objects.get(username='<username>')

# 유저가 특정 그룹에 속해 있는지 체크
user.groups.filter(name='<groupname>').exists()

# 유저가 그룹 리스트에 속해 있는지 체크
user.groups.filter(name__in=['<groupname1>', '<groupname2>']).exists()

# 유저 instance 없이 체크
get_user_model().objects.filter(username='<username>', groups__name='<groupname>').exists()

 

 

@ User Permission Check

# 유저가 가진 개별 permission 체크
user.user_permissions.filter(codename='<permission_code>').exists()

# 유저가 속한 그룹의 permission 체크
user.groups.filter(permission__codename='<permission_code>').exists()

 

 

@ 대소문자 구분하지 않고 찾기

get_user_model().objects.filter(username__iexact='<usernmae>').exists()

 

반응형

'프로그래밍 > Python - Django' 카테고리의 다른 글

Django URL patterns  (0) 2024.03.17
Django 프로젝트 배포하기  (0) 2024.02.29
Django - Proxy Models  (0) 2023.02.23
Django Tutorial Part 1 - HelloWorld  (0) 2022.11.01
Django - 프로젝트 시작하기  (0) 2022.10.28

+ Recent posts