본문 바로가기
Python/Flask

flask 웹개발 기초 정리 - 1

by 쿠리의일상 2024. 2. 26.

https://youtu.be/u2KnTZa1_WU?si=r8kwQJOQ2ysXKxxs

플라스크의 기본 쓰임새는 알지만~ 막상 프로젝트를 만드려고보니 막막해서 강의를 볼 필요성을 느꼈다. 위의 강의를 참고하여 정리했다.

 

 

MVC 패턴

Model, View, Controller

플라스크에선 Model은 데이터 모델, View의 경우 templates 에 해당하는 웹 페이지, Controller 는 Router(Route) 에 해당한다. 

 

__init__.py

모듈의 시작점, 파이썬의 생성자에 해당

 

Flask Context

플라스크는 다른 웹 프레임워크와 달리 controller 의 매개변수로 request 를 받지 않음을 알 수 있다. 그럼 해당 router 에서 발생한 요청을 어떻게 처리하게 될까? 이를 위해 사용되는 것이 Context 이다.

각각의 웹 어플리케이션 영역 안에는 Application Context가 존재한다. 이는 모든 영역에서 공유되는 부분이라 이해하면 된다. 반대의 의미로는 Session(=Request) Context 이라 한다. 즉 Application Context 는 한 웹 페이지에서 모든 사용자가 접근할 수 있는 영역이며 Session Context 는 특정 사용자가 접근할 수 있는 영역인 것이다. 

  • Application Context
    • logger, config 같이 시스템 전반에서 공유 되는 데이터를 담게 된다
    • 실제 사용자는 proxy 객체를 이용하여 활용해준다
    • current_app: 현재 request를 핸들링하고 있는 app instance 를 참조
    • g: request 를 처리할 때 임시 저장소, request 가 끝나면 리셋 처리됨
  • Session(Requset) Context
    • 요청 데이터 및 개개인의 정보를 저장
    • URL, HTTP method, session, request header
    • request: 현재 핸들링하고 있는 요청에 대한 정보
    • session: 사전형 자료구조와 비슷한 자료구조로 암호화된 정보를 담고 있음

 

Grobal Object - g

Application Context 에 담길 임시 저장소

예시 작성을 위해 @app.before_request 데코레이터를 사용하여 모든 라우터에 우선적으로 g의 str 을 지정해준다. 지정해준 값이 key 처럼 문자열로 들어가게 되어 아래처럼 접근할 수 있게 된다.

from flask import g

//...
@app.before_request
def before_request():
    print('Before Request')
    g.str = '한글'
    
@app.route('/')
def helloworld():
    return 'Hello flask' + getattr(g, 'str', '기본값')
getattr(object, name, default)

object 에 존재하는 속성의 값을 가져올 수 있는 파이썬 내장함수, object에 name으로 직접 접근하는 ex) g.str 과 동일하나 값이 없을 경우 기본값을 지정해줄 수 있음

반대의 함수로는 객체에 값을 넣어줄 수 있는 setattr() 가 있으며, 객체의 속성을 없애주는 delattr()과 속성의 유무를 확인해주는 hashattr() 이 있다.

 

 

Response(data, http code, header)

요청 받은 내용에 대한 응답

간단한 string이나 json 파일 형태는 그 자체로 return 해줘도 되지만, 파일 등을 보낼 때는 Response 클래스에 담아서 아래처럼 보내줘야 한다. make_response 에 담아서 보내주면 스트림 형태로 보내지게 된다.

from flask import Response, make_response

# 정석
res = Response("Custom Response", 200, { 'test':'test' })
return make_response(res)

 

https://tedboy.github.io/flask/generated/generated/flask.Response.html

 

flask.Response — Flask API

content_length The Content-Length entity-header field indicates the size of the entity-body, in decimal number of OCTETs, sent to the recipient or, in the case of the HEAD method, the size of the entity-body that would have been sent had the request been a

tedboy.github.io

  • headers
  • status
  • status_code
  • data
    • set_data()
    • get_data()
  • mimetype: application/json 같은 컨텐츠의 타입
res = Response('Test')
res.headers.add('Program-Name', 'Test Response') # 응답 헤더에 특정 키와 값을 추가
res.set_data('This is Test Program') # 응답 data 를 담아주기
res.set_cookie('UserToken', 'ASD123D') # 쿠키 담기

 

Cookie

쿠키는 사용자경험과 사이트 통계등을 위해 텍스트 파일로 클라이언트 PC에 저장된다. 하지만 로그인 정보를 기억하는지 여부 등의 경우는 로컬 스토리지에 저장해준다. 즉 서버딴에서 해당 정보가 필요한 경우에 쿠키를 사용해준다. 

쿠키 사용법은 위처럼 Response의 set_cookie() 로 쿠키를 굽고 request의 cookie 속성에 정보가 담겨 있으므로 request.cookies.get() 으로 쿠키값을 불러올 수 있다.

쿠키의 경우 key, value, max_age, expire, domain, path 속성들로 이루어진다.

domain

네이버 같은 포털 사이트를 이용한다고 가정했을 때 네이버 카페 cafe.naver.com 와 blog.naver.com 사이에 이동하더라도 재로그인을 할 필요가 없음을 알 수 있다. 이는 naver.com (domain) 상에 로그인 쿠키를 줬기 때문에 가능하다.

@app.route('/cookie')
def cook():
    res = Response('Test')
    res.headers.add('HeaderKey', 'headerValue')
    res.set_data('Data!!')
    res.set_cookie('Token~!', '1234')
    
    return res

 

 

WSGI (WebServer Gateway Interface)

기본적으로 플라스크에 지정해주는 app은 WSGI 어플리케이션이다. 그럼 WSGI란 파이썬 표준으로 HTTP를 통하여 요청을 받아서 응답하는 어플리케이션의 명세를 만족시키는 객체, 클래스 등을 의미한다. 그리고 environ 과 start_response 를 매개변수로 받는다.

environ

딕셔너리 형태로 HTTP 요청을 처리하는데 필요한 정보(환경변수)가 저장되어 있다.

start_response

실제 서버에서 어플리케이션으로부터 응답, 헤더, 상태, 예외의 유무를 확인 받아 실행하는 콜백함수, body 를 제외한 정보들을 담아서 처리한 후 아래처럼 body를 리스트로 담고, make_response() 를 사용하는 형태가 일반적이라고 한다.

@app.route('/test_wsgi')
def wsgi_text():
    def application(environ, start_response):
        body = f'The request method was {environ['REQUEST_METHOD']}'
        headers = [('Content-Type', 'text/plain'),
                   ('Content-Length', str(len(body)))]
        start_response('200 OK', headers)
        return [body]
        
    return make_response(application)

 

Request 이벤트 핸들러

위에서 잠시 나왔던 @app.before_request 이외의 다른 이벤트 상황에서 발생하는 데코레이터도 존재한다.

  • @app.before_first_request: 요청의 가장 처음 1번만 실행
  • @app.before_request: 요청의 바로 전 실행, 매 요청마다 실행됨
    • 필터 부분을 담당
    • DB의 connect 부분에 주로 사용
  • @app.after_request: 요청이 실행되기 바로 직전 실행 
    • response 를 매개변수로 받음
    • DB 작업을 마치고 disconnect 해줄 때 사용 
  • @app.teardown_request: 요청이 끝나고 나서 실행
    • exception 을 매개변수로 받음
    • SQL 실행 중 오류가 발생했을 때 원복시키는 과정에서 사용됨
  • @app.teardown_appcontext: 어플리케이션 컨텍스트가 끝나고 나서 실행
    • exception 을 매개변수로 받음

 

Routing

@app.route('주소')
def ...

@app.route('주소', method=['POST', DELETE']) # 해당 요청 메서드에서만 실행
def ...
@app.route('/api/<tid>')
def 함수명(tid):
	# ...
    # <> 안의 변수명을 가져와서 활용 가능
@app.route('/api', defaults = {'page': 'index'})
@app.route('/api/<page>')
def ...

이 경우 <page> 에 파라미터가 들어오지 않으면 기본적으로 defaults에 있는 값인 index 가 들어가지게끔 처리

@app.route('주소', host='xxx.com')
def ...

@app.route('/addr', redirect_to='/new_addr')
def ...

host는 특정 도메인을 지정해줄 수 있는 속성, redirect_to 의 경우 접근하려는 주소를 변경시켜준다.

 

request parameter

from flask import request

request.args.get() 으로 Get 요청 메서드의 쿼리스트링을 받아올 수 있다. 쿼리스트링은 주소창 ? 다음에 쓰이는 키=값 형태의 문자열이다. 예를 들어 아래처럼 쿼리스트링을 가져오면,

# GET 의 경우
@app.route('/rp')
def rp():
    q = request.args.get('q')
    return 'q=%s' %str(q)

이렇게 값을 받아올 수 있다. 만약 post 로 쿼리스트링을 받아오고자 한다면 form 속성으로 get 해줘야 한다.

다만 GET, POST 에 무관하게 값을 가져오고 싶다면 values 로 get 해오면 된다. 하지만 args와 form 의 차이는 GET/POST의 차이이며, get보다는 post 가 훨씬 느리므로 가급적 GET으로 사용해준 거라면 args 를 통하여 작성하는 것이 좋다.

GET request.args.get()
POST request.form.get()
GET, POST request.values.get()

 

@app.route('/rp')
def rp():
    q = request.args.getlist('q')
    return 'q=%s' %str(q)

쿼리스트링 값이 여러개라면 위처럼 getlist로 가져올 수 있다. 쿼리스트링간 구분은 &을 사용한다.

 

request.environ

  • 'REQUEST_METHOD'
  • 'PATH_INFO'
  • 'SCRIPT_NAME'
  • 'QUERY_STRING'
  • 'SERVER_NAME'
  • 'SERVER_PORT'
  • 'SERVER_PROTOCOL'
  • 'wsgi.url_schema'
  • 'wsgi.version'
  • 'wsgi.input'
  • 'wsgi.errors'
  • 'wsgi.multithread'
  • 'wsgi.multiprocess'
  • 'wsgi.run_once'

위와 같은 정보들이 들어가 있다.

해당 정보를 확인해보려면 아래와 같이 작성해준다.

@app.route('/reqenv')
def reqenv():
    return ( 'REQUEST_METHOD: %(REQUEST_METHOD) s <br>'
        'PATH_INFO: %(PATH_INFO) s <br>'
        'SCRIPT_NAME: %(SCRIPT_NAME) s <br>'
        'QUERY_STRING: %(QUERY_STRING) s <br>'
        'SERVER_NAME: %(SERVER_NAME) s <br>'
        'SERVER_PORT: %(SERVER_PORT) s <br>'
        'SERVER_PROTOCOL: %(SERVER_PROTOCOL) s <br>'
        'wsgi.version: %(wsgi.version) s <br>'
        'wsgi.url_scheme: %(wsgi.url_scheme) s <br>'
        'wsgi.input: %(wsgi.input) s <br>'
        'wsgi.errors: %(wsgi.errors) s <br>'
        'wsgi.multithread: %(wsgi.multithread) s <br>'
        'wsgi.multiprocess: %(wsgi.multiprocess) s <br>'
        'wsgi.run_once: %(wsgi.run_once) s' ) % request.environ

이때 % 연산자는 숫자의 경우 나머지 연산자지만, 위와 같은 경우 % 뒤의 피연산자의 키들을 앞의 피연산자의 값에 맞춰서 넣어주게 되므로 아래와 같이 확인할 수 있다.

 

request.is_xhr

AJAX(XMLHTTPRequest)를 통한 요청인지 확인, 즉 True 이면 AJAX 로 요청한 것이라는 의미이니 json 파일을 넘겨주면 되고 False 면 html 파일을 넘겨주면 될 것이다. 

request.endpoint

@app.route() 안에 작성해주는 uri 에 해당

request.get_json()

json 요청을 파이썬으로 받아줄 때 사용해준다. 

 

그밖에 request 에는...
  • request.path: 요청 주소
  • request.method: 요청 메서드
  • request.remote_addr: 출처의 ip 요청
  • request.headers: 헤더 정보
  • request.url: url 전체 주소

 

 

app.config.update()

어플리케이션의 환경변수 내용을 바꿔줄 수 있다. 예를 들어 클라이언트에서 이미지를 서버에 보내고자할 때 크기를 지정해주고 싶다면 app.config.update(MAX_CONTENT_LENGTH=5*1024*1024) 형식으로 작성해주면 5mb 로 설정해줄 수 있다. 

기본적으로 app.config 에 지정된 값은 아래와 같다. Flask 로 app 을 만들 때 키값에 접근하여 수정해줄 수도 있다.

 

Session

서버 메모리에 존재하는 싱글톤 객체로 이해, 서버 메모리에만 존재하므로 클라이언트가 알 수 없다. 그리고 싱글톤 형식이므로 한 프로젝트에 하나만 존재하는 전역 객체라 이해하면 된다. 주로 로그인 정보를 담을 때 사용하게 된다.

예를 들어 로그인을 구현할 때 로그인한 정보를 간단히 쿠키에 담아주면, 클라이언트에서도 조작이 가능하므로 보안상 위험이 생길 수 있다. 그래서 세션을 따로 두어 서버측에 해당 유저가 맞는지 쿠키와 세션으로 매 요청마다 확인해준다.

from flask import session

 

세션 지정 방법은 아래와 같다.

app.secret_key = 'rfefsf231'
app.config.update(
	SECRET_KEY = 'rfefsf231',
    SESSION_COOKIE_NAME = 'pyweb_session',
    PERMANENT_SESSION_LIFETIME = timedelta(31)
)
  • SECRET_KEY의 경우 해당 유저의 암호화된 값에 해당되며 cookie 부분에 값으로 보이게 된다.
  • SESSION_COOKIE_NAME 은 세션의 틀 이름에 해당하며, 모든 사용자들에게 보이는 공통값이다. cookie 에서 이름으로 보이게 된다.
  • PERMANENT_SESSION_LIFETIME 은 세션의 지속기간을 의미한다. (timedelta는 기본 int 값이 day 임) 이값을 활용하여 자동 로그인 기능을 구현해준다.
app.config.update(
    SECRET_KEY = '232dfsf',
    SESSION_COOKIE_NAME = 'pyweb_session',
    PERMANENT_SESSION_LIFETIME = timedelta(1)
)

@app.route('/wc')
def cookcookie():
    key = request.args.get('key')
    value = request.args.get('val')
    
    res = Response()
    res.set_data(key + ' = ' + value)
    res.set_cookie(key, value)
    session['Token'] = '1123'
    
    return make_response(res)

@app.route('/rc')
def getcook():
    key = request.args.get('key')
    
    return request.cookies.get(key) + ' - ' + session.get('Token')

쿠키를 만들면서 함께 세션을 만드는 예제이다. 생성 결과는 아래와 같다.

SESSION_COOKIE_NAME  SECRET_KEY { session['특정키'] : 지정값 }
이름 값 (매번 재생성) 클라이언트는 알 수 없음