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
- 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['특정키'] : 지정값 } |
이름 | 값 (매번 재생성) | 클라이언트는 알 수 없음 |
'Python > Flask' 카테고리의 다른 글
Flask + PostgreSQL + React - 1 (0) | 2024.03.05 |
---|---|
Jinja2 and Werkzeug? (0) | 2024.03.04 |
flask 웹개발 기초 정리 - 3 (feat. SQLAlchemy) (1) | 2024.02.28 |
flask 웹개발 기초 정리 - 2 (feat. SQLAlchemy) (1) | 2024.02.27 |
Flask 시작하기 - flask-restx (Namespace, Model) (1) | 2024.02.24 |