(python) Flask
<converter:variable_name>
http://flask.pocoo.org/docs/1.0/quickstart/#variable-rules
http://jinja.pocoo.org/docs/2.10/templates/
컨텍스트 전역 변수
수신한 HTTP request 정보에 접근하기 위해서컨텍스트 전역 변수 를 사용한다. 이런 정보들을 리퀘스트를 수신할 때 마다 뷰 함수의 인자로 넘기도록 해도 되는데, 쓸데없이 인자를 많이 가지고 있게 되는 것을 피하기 위해 플라스크는 이를 전역 변수 형식으로 제공한다. 근데 실제 전역 변수는 아니다. 멀티스레드 환경에서 못쓰니까. request
변수가 전역변수 처럼 동작하기는 하지만 실제로는 스레드들은 각각 다른 오브젝트를 처리하게 된다.
컨텍스트는 어플리케이션 컨텍스트/리퀘스트 컨텍스트로 나눌 수 있다.
current_app | 어플리케이션 컨텍스트 | 활성화된 어플리케이션을 위한 어플리케이션 인스턴스 |
g | 어플리케이션 컨텍스트 | 리퀘스트를 처리하는 동안 어플리케이션이 임시 스토리지로 사용할 수 있는 오브젝트. 리퀘스트 마다 리셋됨. |
주로request hooks 와 뷰 함수가 데이터를 주고 받을 때 사용한다. 예를 들어 before\_request 훅이 DB에서 정보를 받아와서 g변수에 넣어두면 뷰 함수가 이를 사용하는 식. | ||
request | 리퀘스트 컨텍스트 | HTTP request의 컨텐츠를 캡슐화하는 리퀘스트 오브젝트. |
session | 리퀘스트 컨텍스트 | 사용자 세션. “remembered”인 값들을 저장하는데 사용하는 딕셔너리. |
플라스크는 리퀘스트를 디스패치(처리)하기 전에 어플리케이션과 리퀘스트 컨텍스트를 활성화(push)하며, 리퀘스트가 처리된 후 컨텍스트를 삭제한다. current_app, g는 어플리케이션 컨텍스트가 push되어야 사용 가능하고, request, session도 마찬가지로 리퀘스트 컨텍스트가 push 되어야 사용 가능하기 때문에 그냥 이 변수에 접근하기 위해서는 먼저 push해주어야 한다.
리퀘스트 훅 (request hooks)
1
2
3
4
5
before\_first\_request : 첫 번째 리퀘스트가 처리되기 전에 실행
before\_request : 각 리퀘스트 전에 실행됨.
after\_request : 처리되지 않는 예외가 발생하지 않는다면 리퀘스트 이후 실행
teardown\_request : 처리되지 않는 예외가 발생하더라도 리퀘스트 이후 실행
Jinja2의 유용한 기능
- macro - import : 함수같은.
- include : inline같은거라고 이해하면 된다.
- extends : 상속.
웹 소켓 Flask-SocketIO
https://stackoverflow.com/questions/10930286/socket-io-rooms-or-namespacing Flask-SocketIO 라이브러리를 사용하게 되는데… 이게 좀 문제가 있다. 웹소켓은 이벤트로 이루어져 있다. 클라이언트 측에서 서버 측의 이벤트 함수가 동작하도록 요청할 수 있고, 그 반대도 가능하다. Flask-Socket는 이런 웹소켓 이벤트 함수를 처리하기 위해서 요청이 들어오면 (eventlet | gevent | threading)를 사용해 이벤트 핸들러 함수를 실행하게 되며 eventlet과 gevent는 코루틴을 사용하는 방식이므로 이벤트 핸들러 함수를 코루틴을 이용해 실행하게 된다.
문제는 이런 경우 별의별 방법을 다 동원해서 뭔 지랄을 해도 해당 함수(이벤트 핸들러 함수)가 끝날 때 까지는 절대 buffer flush가 안된다!!
개발자 말로는 eventlet.sleep()
하면 된다는데, 안되는 이슈가 있다. 왜 안되냐니까 원래 슬립하면 flush 되어야 하는게 맞는데 자기도 놀랍다고 그냥 쓰레드 쓰란다. 그래서 주기적인 서버 푸시가 필요하거나, 단발성 이벤트가 아닌 지속되어야 하는 이벤트 함수의 경우는 async\_mode="threading"
으로 주어야 제대로 버퍼가 flush된다.
이 밖에도 여러모로 이슈가 좀 있다… 자꾸 어디선가 에러가 나고… 스택오버플로우에도 자료가 부족하고… 플라스크로 SocketIO쓰려면 스트레스에 대비하는 것이 좋을 듯.
room
이게 엄청 헷갈리는데, room은 클라이언트와 연관이 큰 속성이다. 서버에서 join_room(room_name) 하게 되면 그 웹소켓 객체가 room에 속하게 되는거라고 생각하면 안되고,그 요청을 보낸 클라이언트가 room에 속하게 되는거라고 생각해야 한다. 그래서 emit할 때 room_name을 적어주는건, 그 room에 속한 클라이언트 브라우저에만 데이터를 보내겠다는 거다.
flask-bootstrap
이름은 거창하지만 다음 두 가지 기능.
- bootstrap CDN에서 가져와 로딩하는걸 자동으로 해주는 기능 (
BOOTSTRAP\_SERVE\_LOCAL
주면 local에서 하도록 설정 되고.) - 상속 받아서 재정의 할 수 있는 base 템플릿을 제공. 안써도 크게 상관 없음. 아니, 오히려 안쓰는게 나을 수도 있음. 어차피 테마 가져다 쓰든, 페이지를 만들든 base.html 을 정의해야 하는 경우가 많은데 미리 정의된거 extends해서 재정의하려다가 꼬이는 경우도 있어서.
json 데이터 리턴할 때 ( ajax )
1
2
3
4
5
6
json\_data = json.dumps(dict\_data)
## json\_data(str, byte 타입)가 이미 있는 경우
return flask.Response(response=json\_data, status=200, mimetype="application/json")
## dict 데이터를 json으로 만들어서 전송하는걸 자동으로 처리
return flask.jsonify(dict\_data)
보통 객체를 갖다 넣으면 다음과 같은 에러가 발생할텐데,
1
2
TypeError: Object of type 'ClsName' is not JSON serializable
객체를 dict로 반환해주는 obj.\_\_dict\_\_
속성을 이용한다. 내부적으로는 똑같은데 Encoder를 정의하는 방법과 lambda를 사용하는 방법이 있음.
1
2
json.dumps(log\_records, default= lambda o: o.\_\_dict\_\_)