블로그 이미지
자유로운설탕

calendar

1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31

Notice

2018. 3. 2. 23:37 프로그래밍

   안녕하세요. 블로그에 연재했던 "구글로 공부하는 파이썬" 글이 출간 제의를 받아 책으로 만들어 졌습니다.

 

  책의 경우 조금은 장점을 더해야 할 듯 해서, 아래의 내용들이 추가 및 수정 되었습니다.

  • 현재 최신 버전인 파이썬 3.6.4 로 업데이트 하여 진행(5월인가 3.7 정식 버전이 나오는 것으로 알고 있습니다. 그때까지는 일단 최신이겠네요;)
  • 작년말 업데이트 된 장고 2.0 으로 진행(장고 1.11 과 비교하면 라우팅 부분의 코드가 조금 변경되었습니다)
  • 4장 SQL 부분에 MySQL, 몽고디비, 오라클, Sqlite3 설치 및 세팅, 조회 예제 추가
  • 각 장의 뒤에 예제에 나온 문법 요소들을 설명하는 미니문법 섹션 추가
  • 3.6 버전 pymssql 한글 깨짐 현상에 의해 pyodbc 사용 예제로 변경
  • 자잘한 예제 및 구성 변경(Plotly 오프라인 예제, 작업 자동화 예제 간략화, d3.js 에러에 대한 장고 어플리케이션 측면에서의 해결 코드 추가 등)
  • 3가지 무료 편집기(PyCharm, Visual Studio Code, Atom) 설치 및 예제 실행을 위한 세팅 방법 부록으로 추가
  • 모호한 내용을 다듬거나, 링크로 소개했던 부분을 내용으로 구성하여 업데이트 함(잘못 설명한 내용들은 블로그에도 곧 업데이트 할 예정 입니다)

 

   계약 관계로 책의 내용을 블로그에 업데이트 하진 못하지만, 답변에는 제약이 없도록 출판사 쪽에 양해를 얻은 상태 입니다. 해당 부분 이해해 주시길 바라며, 혹시 블로그를 참조해서 진행하시다가 막히시는 경우 언제라도 댓글로 문의해 주세요^^.

 

   그럼 이런 글을 올리는게 좀 민망스럽긴 하지만, 모쪼록 이해 부탁 드립니다.

 

 

posted by 자유로운설탕
2018. 1. 1. 19:15 보안

  이번 시간에는 코드 읽기라는 주제로, 어플리케이션 보안에 있어 코드를 읽는 부분이 어떤 의미를 가지고 있는지에 대해서 이야기 해보고, 현실적으로 코드 읽기를 적용하려 할때 어떤 문제가 있는지에 대해서도 잠시 얘기해 보려 한다. 개인적으로는 일반적인 테스트든, 보안에 관한 테스트든 가능하면 블랙이나, 그레이 박스 관점에서 진행하는 것에 추가해 소스를 이용하는 것을 선호하는 편이다. 그렇게 함으로써 미지의 범위에 대한 불안감도 많이 덜어주고, 적절히 사용하면 효율성 면에서도, 원래대로 라면 당연히 해야 되는 단순 작업을(인형눈 끼우기 같은...) 많이 줄여주기 때문이다.

 

 

[목차]

[목차]

1. 보안을 바라보는 방법

2. 보안에서의 코드 읽기

3. 인젝션(Injection) 살펴보기 #1, #2

4. 암복호화

5. 클라이언트 코드

6. 업로드, 다운로드

7. 스크립트 문제

8. API

9. 설정, 패치, 하드닝

10. 설계문제

11. 스캐너 vs 수동 테스트

12. 자동화 잡

13. 리버싱과 포렌식

14. 모니터링 문제

15. 악성코드

16. 보안과 데이터

 

 

 

1. 들어가면서

  만약에 아래와 같은 단순한 터치스크린 화면 기반의 프로그램에 대한 보안 테스트를 한다고 가정해 보자. 로직은 단순해서 금액을 넣을 경우 돈이 인출되는 프로그램이다(단순화를 위해 인증이나, 비밀번호 등의 체크는 앞에서 이미 진행했다고 가정).

 

 

  그럼 일반적인 테스트나 보안 테스트 요소를 적당히 리스트업 해 보면, 아래와 같은 여러가지 케이스 들이 생길 수 있다.

1) {-1}, {0, 1, 2}, {999, 1000, 1001} - 마이너스 값, 경계 값 
2) {10000}, {32768} - 아주 큰 값
3) SQL 등 Injection 에 관련된 문자나 특수 문자 

4) show me the money 같은 치트키 입력

5) 그 밖의 예상 못한 결과들(버튼을 빠르게 두 번 누르거나, 화면의 구석에 "Hi" 라고 그리면, 비밀 메뉴가 열린다 든지)

 

 

  위의 케이스 중에서 1~3번 정도는 어떻게든 커버할 수 있겠지만, 4~5의 영역은 블랙박스(외부의 UI만 보고 하는)나 그레이 박스(외부의 UI 와 일부 내부 테이터를 보면서 하는) 테스트 기법을 가지고는 사실 커버하기가 힘들다. 4번 같은 경우는 잘 악용되는 문구의 사전식 대입 정도가 최선일 것이다(보안에서 스캐너가 어드민 페이지 경로를 찾을때 하듯이 말이다). 결국 보안 테스트에 대한 케이스는 요즘 같은 복잡한 어플과 인터페이스 및 서비스 들이 엮어 있는 상태에서는 사실 거의 무한대라고 봐도 될 것이다.

 

 

  그런데 만약 우리가 해당 화면에 대한 소스를 볼 수 있어서, 입력 박스가 "intMyMoney"라는 이름을 가지고 있고, 인출 버튼이 "btnWithdraw" 라는 이름을 가진것을 알수 있고,

 

 

  버튼을 눌렀을 때의 실행되는 코드가 아래와 같이 이루어 졌다는 것을 볼 수 있다면 어떻게 될까?(참고로 아래의 코드는 예를 들기 위한 가상의 코드이다)

1
2
3
4
5
6
7
8
On_btnWithdraw{
 
If(isNumeric(intMyMoney) & intMyMoney >= 1 & intMyMoney <= 1000)
    { 인출 하는 함수 }
Else 
    { error 를 처리하는 함수 } 
 
}
cs

 

 

  충분히 코드의 관리나 빌드 및 배포가 안전한 프로세스 상에서 이루어지고 있다는게 보장되고 있는 상태라면, 우리가 앞에 리스트업 했던 여러 상상의 테스트 케이스 들은 현실적인 케이스로 좁혀질 수 있게된다. 예를 들어 아래와 같은 코드라면 isNumeric 함수가 잘 동작되는지 보기위해 문자, 특수문자 정도를 넣어서 예외처리가 잘되는지 보고, 1과 1000 사이의 경계값 정도를 체크해 보면 테스트가 마무리 될 것 같다.

 

  다만 한가지 눈에 코드가 보인다고 해서, 해당 코드의 동작을 증명하는 실제 테스트 조차 안 하는건 아주 위험한 행위이다. 사람이란 언제나 편견에 의한 착각과 실수를 하기 때문에 눈으로 보기에 확실해 보이는 것도, 실제로는 아닌 경우가 많기 때문이다(개인적으로 자신있게 그러다가 몇번 돌을 맞은 후 얻게 된 교훈으로, 개발자와 같이 동시에 보더라도 서로 의견을 나누다가 편견이 생기기도 한다. 만약 두사람이 라면 서로 상대방의 가정을 모르는 상태에서 보는게 더 날것 같다-비슷한 경험으로 백화점 알바할때 재고 숫자를 세기 위해서 두 명의 알바가 따로 따로 세서 서로 숫자가 맞으면 넘어갔던 적이 있다).  

 

  게다가 만약 위의 코드에 개발자가 테스트를 위해서(또는 나중에 이익을 얻기 위해서) 아래와 같은 코드를 심어 놓았다면 어떻게 될까?  물론 저런 사고를 막기위해서, 개발자 간 피어 리뷰 또는 커밋이나 빌드 전 커미터라고 불리우는 시니어들의 최종 코드 리뷰를 하기도 하고, 개발 환경에서만 유효한 안전한 디버킹 코드를 넣는 방식을 가이드 하기도 한다지만 해당 부분은 한쪽으로 밀어놓고 무시해 보자.

1
2
3
4
5
6
7
8
9
10
11
On_btnWithdraw{
 
If(intMyMoney == "Show Me the Money")
    { 무한대 인출 가능 함수 }
 
If(isNumeric(intMyMoney) & intMyMoney >= 1 & intMyMoney <= 1000)
    { 인출 해줘 함수 }
Else 
    { error 처리 함수 } 
 
}
cs

 

  아마 블랙박스 스타일의 검증으로는 위의 부분을 절대 잡을 수 없을 것 이다. 실제 저런 일은 드물거나, 어딘가 안 들키고 조심스럽게 숨겨져 있겟지만, 비슷한 느낌의 사건이 맥 OS 쪽에서 발생했었다(아마 해당 경우는 최초 root 계정이 비활성화 된 상태에서, 패스워드를 지정하면서 명시적으로 활성화 할수 있는데, 어떤 이유인진 모르지만 패스워드가 없는 상태에서 디폴트로 활성화 된것으로 추정되긴 한다).

 

  여튼 위와 같은 방식의 코드는 아니겠지만, 백도어 기능이 생겨버렸던 것도 맞고, 리그레이션 케이스(새로운 빌드가 나왔을 때 기존의 주요 취약점이나 버그등을 확인하는 것)로도 잡기는 힘들었을 것이다(왜냐하면 나름 저런 희귀 케이스들은 보통 사건이 일어난 후 리그레이션 테스트에 추가되기 때문이다). 저런 사실을 해커가 아는 상태에서 모르는 척 사용한다면 얼마나 큰일이 될까 생각해보면, 화이트 박스 개념의 취약점 체크가 블랙 박스 만큼 중요한 의미를 가진다는 것을 조금은 어필이 되지 않을까 싶다.

 

 [애플, 하이 시에라 ‘루트’ 취약점 수정한 보안 업데이트 배포 - IT World]

http://www.itworld.co.kr/news/107371

 

 

 

 

2. 다른 예 - 복잡성의 단순화

  또는 만약 특정 사이트에 있는 모든 이미지 업로드 기능을 체크 하거나, 이미지 업로드 기능이 많은 관리자 페이지에 대해서 업로드 취약점이 있는지 체크를 한다고 할 경우, 만약 소스의 내용을 전혀 파악할 수 없는 상태라고 해보자. 이 경우 모든 페이지를 일일히 살펴보면서 체크하거나, 시간이나 리소스가 충분하지 않다면 업로드 로직 호출 패턴이나, 감에 의해서 같아 보이는 종류로 묶어서 유형별로 한개씩만 테스트 하고 무사하길 빌 수 밖에 없다(물론 운이 좋은 경우는 공통 업로드 URL 이 존재해서, 해당 URL만 호출 할 경우도 있을 수는 있을 것 같지만...). 

 

 

  이렇게 내부적으로 공통적인 로직이 들어있는지 알 수 없는 경우 막막하게 느껴지지만, 반대로 소스를 볼수 있을 경우에는 문제가 달라지게 된다. 소스내에서 공통된 업로드 처리 함수가 있는지 체크하고, 해당 함수내에서 외부에서 들어온 업로드 관련 인자들을 어떻게 처리하는지, 어느 폴더에, 어떤 이름으로 저장하는지를 체크 후, 실제 존재하는 업로드 관련 페이지들이 해당 안전한 공통 로직을 이용하는지만 여러 방법으로 찾아 증명만 하면 된다(해당 부분은 나중에 파일 업/다운로드에 대한 시간에 자세히 살펴 보려고 한다).

 

  최악의 경우 개발자 들이 각각 공통 함수를 무시하고, 자신만의 로직으로 업로드 코드를 개발하였다고 하더라도 어떤 부분이 공통이 아니여서 좀더 세심하게 들여다 볼 필요가 있는지를 정확하게 알 수 있게 된다. 물론 이런 작업을 페이지 소스를 살펴보면서도 할 수 있지만, 들이는 에너지와 정확도 면에서 소스를 보는 쪽이 휠씬 유리하다.

 

 

 

 

3. 일반 언어와의 비교

  그럼 프로그래밍 언어에만 한정 짓지 말고 일반 언어와 한번 비교해 보려고 한다. 언어에도 읽기, 쓰기, 듣기, 말하기가 있듯, 코드에 대해서도 비슷한 부분이 있다.

 

 

3.1 읽기
  읽기 측면에서 코드를 통해서 여러 점검이 필요한 중요 로직들을 얻어낼 수 있다. 특히 전형적인 취약점이 아닌 설계에 대한 보안 문제는 전체적인 로직을 잘 살펴보아야만 발견할 수 있을 가능성이 높다. 물론 관련 기획 문서를 보거나, 실제 해당 페이지나 기능들을 실행해 보는 것도 효과가 있지만, 소스 읽기는 밖에선 잘 안 보이는 숨겨진 부분들을 다른 관점에서 살펴보게 해준다.

 

  다만 여기서 한가지 짚고 넘어가고 싶은 것은 해당 효과를 꼭 소스 읽기를 통해서만 얻을 수 있는 것은 아니라는 점이다. 기존에 알고 있는 여러 지식으로 부터의 유추나, 관련 개발자와의 대화 및 문의에 의해서도 많은 것을 얻어낼 수 있다. 다만 이 경우도 소스를 어느정도 이해하고 있는 것은 정확한 질문을 던져서 원하는 해답을 얻어낼 수 있을 가능성을 높여주게 된다. 보안과는 조금 먼 관점의 해당 개발자 스타일의 설명을 잘 이해해, 점검에 도움이 되는 형태로 스스로 재가공 할 수도 있다. 경험상 맘에 맞거나, 일하는 스타일이 맞는 개발자 동료가 있는 것은, 구글 검색엔진이 도와주는 것과 비슷하게 든든한 효과를 주게 되므로, 상황이 어쩔 수 없다면 모르겠지만 꼭 혼자서 모든 걸 부딛치려고 하는 것은 좋은 것은 아니다. 

 

  또한 로직을 이해하기 위해 코드를 읽는 것은 개발을 하기위해 코드를 읽는 것과는 조금 다른 측면이 있다. 만들기 위한 코드 읽기는 프로그램의 세부 로직과 문법의 디테일한 구조를 동시에 살펴봐야 하지만, 보안을 위한 코드 읽기는 데이터의 흐름에 따른 요점만을 읽는다고 보면 된다. 예를 들면 입력 인자들이 어떤 외부에서 들어오고, 해당 인자들이 내부 로직에 어떻게 전달되고, 어떤 처리를 거쳐서, 어떻게 안전하게 사라지게 되는가의 라이프 사이클을 살펴보는 작업이다. 또한 현실과 차단되어 코드 만을 보는 것 보다, 블랙박스 같이 바깥 쪽의 흐름도 참고하면서 비교해 보는게 좀더 난듯 하다.

 

 

3.2 코드 쓰기
  현대의 코드 쓰기 작업은 많은 부분이 수정(modify)의 측면에서 사용되는 경우가 많은 듯 하다. 예를 들어 API 를 호출해 결과를 화면에 뿌려줄 경우도 처음 부터 맨땅에서 시작하는게 아니라, API 를 만든 사람이 제공하는 샘플을 기반으로 구현하게 된다. 또 구글링을 통해서 많은 코드를 얻고 있고, 해당 언어에 대한 샘플이 없더라도, 다른 언어로 이루어진 샘플을 보면서도 아이디어를 얻게 된다.

 

  그래서 패턴이 되는 조각 코드들을 적절히 만들거나 참조해 쓸 수 있게 되면(예를 들어 HTTP 요청과 받기, HTTP 소스 분석, 주기적인 처리), 어느 언어를 사용하든 구글을 이용하여 적당히 원하는 동작을 하는 코드를 참조해 만들어낼 수 있다(어떤 사람들은 이런 웹에 돌아다니는 코드는 돌아가긴 하지만 유지보수가 성능이 보장 못 한다고 저평가 하는 측면도 있지만 해당 부분은 관점에 따라 맞는 것도 틀린 것도 같다. 그렇게 따지면 우리가 쓰는 오픈소스도 웹에서 돌아다니는 코드니까 말이다). 이러한 작업이 가능하기 위해서는 정규 표현식, 여러 데이터베이스, 스크립트언어, 빌드 방식 언어, 빅데이터 오픈소스 등등 여러 언어와 배경 지식들을 평소에 습득하되, 특정한 기술에 의존적인 부분 보다는 기반적인 부분에 초점을 익히는 것이 좀더 바람직 하지 않을까 싶다.

 

  또 이러한 "서당개 3년" 스타일의 쓰기는 보안 테스팅을 할때도, 실제 환경상으로 불가능하거나, 실 사이트에 영향을 미쳐 수행하기 힘든 부분을, 코드를 적절히 변경하여 테스트 환경에서 체크함으로서, 최소한의 안전을 보장하게 만들 수도 있다(예를 들어 위의 예를 든 업로드 코드를 실제 운영 환경에서 돌려볼 순 없지만, 방어를 하는 필터 함수만을 잘라내어 체크한다든지 하는 상황이 있을 수 있다). 

 

  그래서 코드를 백지부터 만드는 능력이 떨어져도(요즘 이런 사람이 얼마나 될까는 싶다), 우선은 기존 프로그램들을 조금씩 수정해 보는 모드로 공부하는 것을 추천한다. 언어로 따지면 쓰기 전에 우선 많이 읽어서 여러 패턴을 익힌후, 해당 패턴을 이용해서 더듬더듬 쓴다고 봐도 될것 같다. 프로그램의 좋은 점은 언어의 말과는 달리 잘못 쓰게 되면 아예 안돌아가거나 버그가 바로 나오기 때문에 짧은 한마디를 할때부터 자동 교정의 훈련이 되는 점이다. 다만 충분한 경험이 쌓이기 전에는 이해가 안 되는 에러가 나서 답답함이 많긴 하지만 말이다(그 경우 구글을 도움을 받으면 된다).

 

 

3.3 듣기 & 말하기
  취약점이 발견됬을때 해당 취약점을 올바르게 고치려면 해당 언어와 돌아가는 환경을 올바르게 이해할 수 있으면 좋다. 해당 지식을 기반으로 개발자와 의논하게 되면, 앞에서 얘기한 바와 같이 개발자의 언어로 설명하는 주제들에 대해서도 비교적 쉽게 이해도 할수 있고(또 이러면서 많이 배우게 된다), 적절한 논의 및 보정, 협의를 할수 있게 된다.

 

  또한 이쪽에서 원하는 부분에 대한 빠른 피드백을 얻을 수도 있고, 단순히 어떠한 작업을 해달라고 요구사항 만을 기계적으로 전달하는 것보다는(보통 취약점은 찾을 수 있지만, 대상 코드를 어떻게 수정해야되는지에 대해 정확하게 모르는 경우 이런 태도를 취한다. 극단적으로 보면 "네가 알아서 고쳐" 이런 모드다) 사이드 이펙트가 없는 정확한 코드의 수정이 이루어질 수 있다. 개발 쪽에서는 의외로 보안 쪽에서 당연하다고 생각하는 언어의 보안적 관점에 대해서 모르는 경우가 많기 때문에, 원하는 방식과 결과만을 이야기 하는것만으로 충분하지 못할 때가 많다.(원리와 결과에 이르는 과정을 이해시켜 주는 것이 종종 필요하다).

 

  다만 이 부분은 이상적인 얘기고, 개발자에 따라서는 자신의 코드 영역에 들어오는 것을 싫어하는 사람들도 꽤 많다. 그런 경우 본인의 코드 영역을 간섭 받고 있다고 받아들이게 되어 역효과를 가져올수도 있다. 반대로 적극적으로 코드의 흐름에 대해서 얘기하면서 의논하는걸 좋아하는 개발자도 많이 있다. 상황 상황에 따라 틀리지만, 적절히 상대의 성향에 맞추어서 잘 협상하는 것이 좋다고 생각한다(개인적으로 이런 문제로 부딛쳐 사이가 안 좋아진 경우가 있는데 지금은 좀 후회가 된다. 지나고 나면 좀 부질 없는 일들 같기도 하다)

 

 

4. 보안에서의 코드 읽기의 문제점

  아마 어떤 분들은 위의 글들을 읽으면서 현실과 동떨어진 얘기라고 생각 할수도 있을 것 같은데, 그런 부분들에 대해서 언급해 보려고 한다.

 

 

4.1 현실적으로 어려운 점

  확실히 코드를 읽으면서 보안 설계가 잘 되어 있는지 체크하는 방법은 단점 또한 많다. 첫 번째로 한정된 시간이다. 주어진 시간이 몇 일 밖에 안되는 상황에는 남이 만든 소스를 들여다 보면서 로직을 파악하는 것보다는, 주어진 시간을 효율적으로 쓸수 있는 다른 방법들을 찾아보는 것이 낫다. 시간과 리소스를 고려하여 블랙박스 접근법 만을 쓸지, 화이트 박스 접근법을 같이 쓸지, 아니면 일부만을 보조해서 쓸지를 선택해야 한다.

 

  두 번째는 "1 vs N" 의 문제로써, 인원 비율 상 수 많은 개발자들이 만든 소스를 보통 한 사람이 살펴봐야하기 때문에 적절히 범위를 제한하거나, 요점만을 읽는 속도를 높이는 방법을 계속 고민하고 연습해야 한다.

 

  세 번째는 초기 진입 장벽으로 이것은 영어를 처음 배울때와 비슷하다. 처음에 소스를 읽게되면 하나하나 문법적인 부분도 따져가면서 읽어야 해서 모르는 것도 많고, 느리고, 난해한 해석 과정을 거쳐야 한다. 이 부분은 프로그래밍을 만드는 것을 배우는 것과 비슷하다고 보고, 피할 수 없는 측면이 조금은 있다. 분명한 것은 영어책과 마찬가지로 많이 읽으면 읽을 수록 빨라진다.

 

  네 번째는 회사에서 보안이나, 지적재산권 보호 등의 여러가지 이유로 특정 개발자 이외에는 소스를 보여주지 않는 경우도 있을 수 있다. 이 경우는 세월을 이용한 증명과 설득 밖에 없어 보이는데, 해당 회사에서 꾸준히 근무하여 신뢰를 쌓고, 검증을 위해 코드를 보는 방법이 효율적이고, 안전한 코드를 만드는데 도움이 된다는 부분을 설득해서 권한을 획득하는 방법밖에 없을 것 같다. 어떤 회사는 내부자에게 모두 공개하는 회사도 있으니 복불복일 듯은 싶다. 근데 막상 공개해 줘도 실력이 안되 못 볼 수도 있으니 자기 실력은 잘 객관적으로 파악하고 있어야 하는 듯도 싶다.

 

  마지막으로 세상엔 많은 언어들이 있고, 자신의 일하는 환경에도 많은 언어들이 혼재되어 있을 수 있다는 부분이다. ASP, PHP, C#, 자바, SQL, 자바스크립트, jQuery, Node.js, 스칼라 등등의 수 많은 언어들과 언어의 배경지식들을 적절히 따라잡아야 한다. 또 앞으로 나오는 새로운 기술들도 새 프로젝트에서 기존의 패턴들을 사용하기 위해서 어느 정도의 깊이로는 따라가야 한다고 본다. 또 은근 코드를 볼때, 코드가 저장된 저장소 특성에 따라서, 해당 저장소에 적합한 검색 방식을 습득하거나 궁리해야 하기 때문에 문제가 된다. 낯설지 않은 저장소에 담겨진 소스를 봐야하는 경우는 이전 버전의 비교라든지 하는 작업 부분에서 병목을 가져올수 있게 된다.

 

 

4.1 현실적으로 다행인 점

  위와 같이 난해한 코드 읽기의 세계에서도 다행인 점들이 있다. 

 

  첫째로 패턴화가 가능하다는 것이다. 한 프로젝트 안에는 많은 유사한 구조와 로직들이 있고, 그 구조와 로직은 다른 프로젝트에서도 존재할 가능성이 아주 높다(코드나 구조의 복사는 바이러스와 같고 생각보다 정답은 비슷한 경우가 많다). 또 많은 소스들이 구글 검색이나, 해당 언어 및 라이브러리의 가이드, 다른 시니어, 이전 개발자의 코드 등을 기반으로 만들어졌기 때문에, 이런저런 사유로 유사한 로직과 구조들도 많은 편이다. 또한 웹같은 경우는 동작이나, 디자인 상의 유사적인 부분이 많다(이런 것을 편하게 만들기 위해서 파이썬의 장고나, 자바의 스프링, .NET의 MVC 패턴 같은 프레임워크 들이 있는 것일테고..). 

 

  둘째로 영어, 중국어 등의 언어와 마찬가지로 소스를 많이 보게되면 그만큼 읽는 속도가 빨라지게 된다. 요점만 골라읽는 요령도 조금이나마 늘게 되고 말이다. 또 스스로 로직을 정리하기 때문에 계속적으로 업그레이드가 되는 프로그램 이라면, 향후 추가적인 점검을 진행 할 때 해당 지식들이 좋은 백그라운드를 형성하게 된다(그래서 도메인 전문가가 생기는 걸꺼다..). 그리고 아마도 직접 만드는 만큼은 아니지만, 간접적으로 프로그래밍에 대한 경험적 노하우가 쌓이게 되어 나중에 자동화 등의 분야에서 코드를 만들어 볼때도 도움이 된다.

 

  마지막으로 앞에도 잠시 얘기 했지만 잡(Job), API 같은 UI 적인 형태가 없는 프로그램을 분석하고 테스트 할 때, 프로그램의 형태 및 어떻게 돌아가는지를 이해하기 쉬워 해당 검증 작업을 용이하게 해줄 수 있다.

 

 

 

5. 실행 전략

   조금 앞의 내용과는 겹치는 듯 하지만, 정리하는 의미에서 어떻게 그럼 코드 읽기를 이용할까를 생각해 보자.

 


  첫 번째로, 항상 "시간 vs 선택한 방법의 효율"을 생각해야 한다. 자신이 선호하는 한 두가지 기술에만 집착하면서 진행하다 보면, 오히려 자가당착에 빠지게 된다. 정말로 안으로부터 보는 게 가치가 있는가를 검토해서 확신이 있을 경우만 코드 읽기 방법을 이용해야 한다. 프로그램의 도메인에 따라서 밖에서 보는 것이 더 단순하고 명확할 때도 많다.

 

  두 번째로, 코드를 읽으면서 습득한 지식은 가능하면 문서화를 해두는 것이 좋다. 시간은 기억을 희미하게 만들어서, 다 이해했던 코드도 몇달만 지나면 가물가물 해서 다시보면 이게 뭔가 싶어진다. 이건 매일 코드를 보는 개발자들도 마찬가지라고 한다.

 

  세 번째로, 자신이 선호하는 언어는 있어도 괜찮지만, 보통 주력 언어의 업무를 가능한 고집할 수 있는 개발쪽 과는 달리(물론 새로운 기술의 압박은 계속 되겠지만), QA나 보안 같은 경우는 언제 어떤 언어와 환경을 체크해 해달라고 던져올지 알수가 없는 부분이 있다. 그래서 가능한 언어 자체보다는 언어가 의존하는 여러 배경 지식을 익히고, 언제라도 새로운 언어나 환경에 적응해야 된다는 마음가짐을 가지고 있어야 한다고 생각한다. 다만 다행히 패턴이란게 있고, 새 언어는 기존 언어를 벤치마킹 하는 경향이 있기 때문에, 스크립트 언어를 하나 열심히 경험에 두면 다른 스크립트 언어에 대해 적응이 많이 수월하고, NET, 자바 같은 객체지향의 언어도 마찬가지라고 본다.  

 

  네 번째로, 읽는 속도도 분명이 중요한 요소중 하나라는 것이다. 코드를 읽고, 기능을 분류하고, 케이스를 제외하거나, 뽑아내는 것도 보안 테스팅의 한 요소이긴 하지만, 그 부분에 너무 시간을 쏟다보면, 실제 해당 분석에서 세운 가설들을 직접 해보면서 증명할 시간이 모자랄 수도 있게 된다. 그리고 보통 개발 뒤에 일어나는 검수 업무에 대해서는 생각보다 충분한 시간이 주어지진 않는 경우가 많아서 마린의 스팀팩과 같이 집중해서 속도를 낼 수 있는 훈련을 꾸준히 하는것도 중요한 것 같다(뭐 이렇게 보면 스포츠 경기의 준비 단계와 비슷한 것도 같다). 더 좋은 방법은 분석에 대한 시간을 미리 벌기 위해 개발 단계에서부터 참여가 가능하다면 좋을 것 같다.


   다섯번째로, 역시 앞에서 한 얘기지만 혼자서 모든걸 할수 있다고 생각하는 것 보다, 믿을 수 있는 동료들과의 좋은 네트워크를 가지는 것이 좋다. 일하는 스타일이 맞는 친한 개발자들을 만들고(물론 일을 위해서 억지로 관계를 만들라는건 아니다), 개발자들이 프로그래밍 언어에 접근하는 방식을 계속 관찰하다보면, 단순히 책이나 혼자서 공부하는 과정에서는 발견하지 못하는 사실들을 배울 수 있게 되는 것 같다.

 

  마지막으로 쓰기의 경우는 스스로도 잘 못하는 부분이긴 하지만, 좋은 라이브러리들의 사용에 집중을 하고, 오픈소스들을 이해하고, 실용성에 중심을 두고 접근을 하는게 어떨까 싶다. 앞의 파이썬 글도 사실 그런 바램에서 진행된 활동이라고 보면 된다. 

 

 

 

 

6. 이런 저런 꼬투라기

  꼬리가 길지만 마지막으로 몇가지 생각나는 부분을 적어보면 

 

 

  첫 번째로 화이트 박스 기법을 적용하는데 있어서 가장 중요한 부분 중 하나가 소스 컨트롤이 얼마나 잘 구축되 있으냐 인것 같다. 소스가 자기 멋대로 변경되어 버린다면, 기껏 분석 및 테스트 했던 내용들이 도로묵이 될 수 있으니 말이다. 또는 소스 비교나 변경이력 추적도 분석에 도움이 되는데, 해당 부분이 체계적이고 원활하게 되는 환경이 지원된다면 좀더 유리할 것 같다. 다만 개발쪽에서 편한 툴은 검증 하는 쪽에서는 좀 정보가 부족해 불편한 경우가 많긴 한다.
 

 두 번째로 방법론이 검증하고자 하는 대상을 앞서서는 안 된다. 방법론은 효과적인 점검을 위한 수단이 뿐이지 가장 중요한 것은 대상에 대한 분석적 파악이 우선이다. 이후 어떠한 방법론을 쓸까 하는 것은 분석의 결과지 행동의 목적이 되어서는 안된다.

 

  세 번째로 방어측면 에서 OWASP 같은 데서 제공하는 여러 방어 함수의 효율성과 성능에 대해 꾸준히 관심을 가지는것도 좋은것 같고, 파이썬 등으로 조그마한 툴을 만들어 업무에 사용해 보는 것도 괜찮은 것 같다. 보안 쪽은 의외로 패턴화된 문제들이 많아서 자동화의 ROI 가 좋고 생색내기도 편한 것 같다. 또한 오픈 소스의 사용은 내부 로직을 제대로 이해한 채 사용해야, 필요한 커스터마이징도 할수 있고(외국은 보통 이렇게 쓰는 듯하다), 해당 프로그램의 적절성과 한계를 인식할 수 있다고 생각한다.

 

 

[마무리 하면서]

  스스로 코드 읽기에 대해서 충분히 잘 한다고는 할수 없다 생각되지만, 보안이나 QA에 새로 들어오는 사람들이 직업의 초창기 때부터 의식적으로 노력했으면 하는 부분이라서, 이렇게 얘기를 하게 되었다(개인적으로 더 열심히 할걸 하는 아쉬운 부분이기 때문이기도 하다). 아무쪼록 공감하는 분이 있음 좋겠다.

 

posted by 자유로운설탕
2017. 12. 2. 22:35 보안

  파이썬에 대한 글을 마치고, 이제 두 번째로 보안에 대해서 이야기를 한번 해보려고 한다. 개인적으로 생각했을때 아직 잘 모르는 영역들도 많고, 관심은 있지만 공부를 안해서 발전이 거의 없는 영역들도 많다고 생각한다. 

 

  이 글에서는 보안을 처음 공부하거나, 공부는 하고 있지만 너무 범위가 넓고 어렵게 느껴지거나, 개발이나 시스템, 테스팅 같은 연관이 있는 일을 하는 사람들에게 보안이 생각보다 그렇게 어려운 분야는 아니며, 개발이나 테스팅, 시스템, 네트워크 쪽의 지식처럼 해당 분야의 패턴을 익혀두면 여러 다른 분야에서도 유용하게 쓸 수 있다는 것을 얘기해 보려 한다. 나아가 얼마나 잘 될지는 모르겠지만 보안의 어떤 부분이 보안을 어렵게 생각하게 만들게 되는지도 개인적인 경험에 비추어 설명해보고 싶다. 이전 파이썬 글과 마찬가지로 하나의 접근 방식이라는 관점으로 비판적으로 받아들이기를 바라며 역시 재밌는 시간이 되길 바라면서 글을 시작하려 한다.

 

 

[목차]

1. 보안을 바라보는 방법

2. 보안에서의 코드 읽기

3. 인젝션(Injection) 살펴보기

4. 암복호화

5. 클라이언트 코드

6. 업로드, 다운로드

7. 스크립트 문제

8. API

9. 설정, 패치, 하드닝

10. 설계문제

11. 스캐너 vs 수동 테스트

12. 자동화 잡

13. 리버싱과 포렌식

14. 모니터링 문제

15. 악성코드

16. 보안과 데이터


 

1. 들어가면서

  보안 공부를 어떻게 해야하느냐를 알고 싶어서 검색엔진을 찾아보면 해야될 일이 엄청 많아 보인다. C++와 같은 프로그래밍 언어부터 시작해서, 웹 및 시스템 프로그래밍, 객체지향 개념, 운영체제, 웹서버, 네트워크 장치들, 프로토콜, 암호학, 리버싱, 포렌식, 악성코드 등에 대해 하나하나 이해해야 하고, 나아가 오픈소스, 모의해킹, 보안 관련 툴 들을 이해해야 한다고 한다. 

 

  사실 위의 요소 중 하나로만 범위를 제한하게 되더라도 엄청 많은 하위 카테고리로 확장되게 된다. 예를 들면 웹은 CGI(Common Gateway Interface) 같은 초창기 서비스 구조로부터 ASP, PHP 언어 같은 스크립트 베이스의 언어, JAVA, NET 같은 좀 더 포말한 프로그래밍 형태의 웹으로 확장되고, 여러 타입의 서비스와 API 등도 포함되게 된다.

 

  네트워크도 이름도 낯선 OSI 7 계층 구조부터, TCP/IP, 방화벽, IPS, 스위치, 라우터 등의 여러 이슈들로 나누어지고, 그런 부분도 또 관련 장비의 밴더나 각 진화된 세대에 따라서 서로 다른 영역으로 갈라질 수 있다. 나머지 필수 기술 항목들도 역시 마찬가지로 많은 갈림길이 있다. 웹 서버들도 아파치, IIS, 톰캣 등 조금씩 특성이 다른 여러가지의 웹 서버들이 있고, 각 서버는 각각의 버전마다 설정이나 지원 기능 등에 따라 특성이 또 조금은 다르다고 볼 수 있으며, 다른 웹 언어들과도 연결된다.

 

  게다가 현대의 빠른 기술의 변화에 맞춰서, 빅데이터나 머신러닝등의 새롭게 보안이 필요해지는 영역과 언어들이 계속 쏟아져나오고 있다. 또한 좀 더 넓게 현실적으로 보면 IT 기술의 범위를 벗어나서 물리적인 영역이나, 작업 및 관리 프로세스, 심리적인 부분도 관련 되게 된다.

 

 

  개인적인 관점에서 또 하나 보안을 어려운 분야로 생각하게 하는 부분은, 보안 측면을 바라보는 시각이 매력적인 모의해킹이라는 공격적 측면과, 자격증 획득이라는 측면에 많이 중심이 치우쳐 있기 때문인 것 같다.

 

  보안 지식 전체가 몸을 건강하게 해주는 운동들의 구성이라면, 모의해킹은 프로격투기 선수의 실전 스파링 훈련과 같다고 본다. 모의해킹은 시스템을 구성하는 모든 제반 기술들에 대한 균형있는 검증을 통해서, 안전한 디자인이 이루어졌나를 확인하는 활동이라고 생각한다. 그래서 모의해킹 기법 관련 책에서 얘기하는 많은 백과사전 방식의 기법을 익혀서 대상에 대해서 테스트를 하고, 취약점을 찾거나 찾지 못한 결과에 따라 대상이 불안하거나, 안전하다고 판단하는 것은, 사실 그렇게 많은 의미는 없어 보인다. 중요한 것은 그러한 기법들을 보안 세계 쪽에서 정리하고 권장하게 된 배경을 이해하고, 점검 하려는 대상에 대해 기술적으로 정확하게 이해하여 적절한 기법을 적용하여 검증하는 부분인 것 같다.

 

  자격증 또한 해당 분야를 마스터(사실 사람 자체가 불완전 하며, 모든 분야에서 한 개인은 역사를 스쳐가는 디딤돌 같은 역활인지라 어떤 분야를 마스터했다는 말은 신기루 같은 것 같기도 하지만...)한 전문가를 보면서 행동을 따라하는 것과 비슷하다고 본다. 자격증에 나오는 지식은 현실의 많은 보안 지식을 모아서, 전문가들이 정리하고 체계화된 지식에 불과하다고 본다. 해당 지식 체계가 보안의 전체적인 모습을 정리하고, 체계적으로 접근을 하게 해주는 것은 부정할 수 없는 사실이지만, 해당 지식안에는 현실을 객관화한 지식은 있지만, 현실 자체는 없는 경우가 많다.

 

   지식 자체는 손가락이고, 그 손가락이 가르키는 곳을 봐야지 의미가 있지 손가락 자체를 아무리 외어도 의미는 없다고 본다(자격증 책 본문의 시험용 암기 내용보다는, 각 챕터의 뒤에 있는 그 내용이 나오게 된 근거인 참고 서적이나 관련 링크들에 대한 포괄적인 이해가 사실 더 중요한것 같아 보이지만, 시험공부를 위해서 기본적으로 외우거나 이해해야 할 내용이 엄청 많기 때문에 그런 부분까지 차분히 읽을 분위기는 되지 않는다). 훌륭한 전문가의 행동을 따라하는 것보다, 그 행동을 일으키게 한 마음의 흐름을 이해해야지만, 실제 해당 전문가의 노하우의 그림자라도 익힐수 있게 되지 않을까 싶다.

 

 

 

2. 보안이 어렵게 느껴지는 이유

  우리가 어떤 것을 배우려면, 우리가 어떤 것을 배우고 싶어 하는지를 우선 명확히 알아야 하는 아이러니가 있다(파이썬 글 21교시의 5 orders of ignorance 를 참고). 보안이란 분야가 어떤 것 인지에 대해서 많은 사람들이 서로 다른 정의를 가지고 접근하겠지만, 개인적인 관점에서 가장 주요한 부분은 데이터의 흐름을 따라가는 것이라고 본다. 데이터의 관점에서 한정해 보면 개발은 데이터가 설계된 길로 가게하는 것이고, 테스팅은 데이터가 주어진 길로 제대로 가는지를 증명하는 일이며, 보안은 데이터가 주어진 길에서 납치나 회유되지 않는지 살펴보는 일이라고 볼수 있다고 생각한다. 

 

  또한 보안은 일반적인 프로그래밍이나 테스팅 보다는 우리의 일상생활이나 시스템 바깥의 물리적, 프로세스적 환경의 관리까지 생각하기 때문에 조금 더 범위가 넓게 된다. 여담이지만 점점 개인정보보호 분야의 영역과 보안의 영역이 많이 겹치게 되고 있는데, 개인정보는 (개인의) 중요한 데이터를 보호하는 분야이고, 보안은 중요한 데이터를 보호 하는 분야이기 때문에, 어쩔 수 없이 기술적인 영역에 대해서 관심사는 살짝 다르지만 상당 부분 겹치게 되어있다고 본다.

 

 

  그럼 데이터의 흐름을 따라가려면 어떻게 해야 할까? 데이터가 보여지거나, 지나가거나, 처리되거나, 저장되는 모든 영역을 잘 이해해야 한다. 컴퓨터 안의 CPU, 레지스터부터, 메모리, 디스크 내의 바이너리 형태의 데이터, 운영체제, 프로세스, 프레임워크, 응용 프로그램, 웹서버, 네트워크(프로토콜), 서버, 관리프로그램, 사용자 프로그램, 사용자, 외부 장치, 업무 프로세스 또는 은행의 비밀번호를 기억하고 있는 고객들에 이르기 까지 데이터가 현실의 물리적 형태, 0과 1의 형태의 바이너리 형태로 존재하거나, 레지스트 값, 변수, 메모리 값, 프로그램 내의 변수, 설정 값, 입력 값, 전송 값 등 모든 머무는 영역에 대해서 올바르게 이해해야 한다. 결론적으로 얘기하면 앞의 많은 기술의 요소 부분을 이해해야만 보안을 잘 할 수 있는 것이 아니고, 꺼꾸로 보안을 잘하기 위해서, 그러기 위해서 데이터를 이해하기 위해서, 데이터의 흐름을 이해하고 적절히 보호하기 위하여 해당 기술들을 익혀야 한다고 본다. 

 

  이러한 끝이 잘 보이지 않는 넓은 범위의 기술 범위들은, 앞의 파이썬 글에서 얘기했던 프로그래밍의 배경지식과 비슷하다고 본다. 보안은 보안의 원리 자체가 어려운게 아니라(뭐 쉽다는 의미는 아니고, 배경지식을 잘 안다면 생각보다는... 이라는 말이지만... 반대로 얘기하면 배경지식을 모르면 실제보다 과도하게 어려워 보인다) 이러한 배경지식의 균형있는 습득이 어려우며, 프로그래밍에서의 게임 프로그래밍의 위치와 비슷하게, IT계의 종합 예술 영역 같은 성격을 띄고 있다고 생각한다. 그래서 보안은 데이터를 제대로 보고 흐름을 따라가기 위해서 많은 배경 지식이 필요하게 된다. 프로그래밍도 그러한 배경 지식 중 하나이고, 앞서 얘기한 많은 분야들도 그래서 필요하게 된다. 그리고 모든 분야가 마찬가지겠지만 모르는 분야나, 새로운 기술을 계속 따라가야 하는 어려움도 있게 된다.

 

 

  예를 들어 보안을 모의해킹 같은 공격의 관점으로 봐서, 아주 뛰어난 도둑이 있다고 가정을 해보자. 해당 도둑은 사회공학적 기법을 이용해 사람들을 속여 정보를 얻거나, 원하는 행동을 하게 하는 것도 능숙하고, 담을 타거나 빠르게 이동하기 위한 신체능력도 뛰어나며, 일반 자물쇠나, 생체인식등과 같은 현존하는 모든 자물쇠의 이해에 능통한 상태로 그러한 기법들을 이용하여 원하는 목표를 놓친적이 없다고 하자.

 

  그런데 어느날 열쇠의 주인이 아니면 절대 풀수 없다는 양자역학을 이용한 자물쇠(무슨 자물쇠인지는 모르지만 그런 어려운게 생겼다고 해보자)가 발명되면서 주요 보물들이 해당 자물쇠에 의해서 보호되게 되었다. 이제 도둑이 계속 목표를 달성하려면 어떻게 해야될까? 해당 양자역학적 자물쇠를 이해하여 속일 수 있는 방법을 찾는 수 밖에 없다(미션 임파서블 영화를 생각하면 된다. 물론 영화에서처럼 어떤 자물쇠로 잠겼는 지에 상관없이 사람이나 환경을 이용하여 사회공학적 기법으로 푸는 방법도 있긴 하겠지만 말이다. 기술은 기술을 운영하는 사람의 스마트함에 비례해 효과가 있으니 말이다).

 

  또는 반대로 여러분이 자물쇠가 얼마나 안전한지 체크를 해주는 보안 전문가고, 해당 양자역학적 자물쇠를 사용하는 사람으로부터 해당 자물쇠가 얼마나 안전한지 검증을 해달라고 요청을 받았다고 해보자. 새로운 자물쇠의 안전을 검증하려면 해당 자물쇠의 원리를 우선 이해해야만 한다. 물론 해당 자물쇠를 이해 못한다고 기존의 뛰어난 도둑(악의적인 해커)이 뛰어나지 않은 것은 아니고, 뛰어난 보안 전문가가 전문가가 아니게 되는 것은 아니다. 하지만 해당 분야에서 전문가 명성을 계속 유지하기 위해서는 새로운 기술을 이해하여, 자신이 기존 체계로 부터 이해하고 있는 패턴을 적용하여 공략점을 찾거나, 외부의 공격에 안전한 설계인가를 증명하지 않으면 안된다.

 

 

  다른 예로 당신이 모든 사기 유형을 마스터한 유명한 사기꾼이고, 새로운 매력적인 파생 상품이 나와서 해당 부분을 이용해서 사기를 치고 싶을 때도, 마찬가지로 대상에 대한 이해가 필요하게 될것 이다(사기는 상대방에 대한 이해와 공감을 바탕으로 한, 9개의 진실과 1개의 거짓으로 이루어진 행위라고 생각한다).

 

  현실의 보안 영역으로 내려오면, 윈도우 보안의 전문가인 당신이 리눅스, IOS, 안드로이드의 보안에 대해 체크하고, 적절한 가이드를 하려면 해당 OS 환경을 잘 이해하는 수밖에 없다. 마찬가지로 새로운 데이터베이스, 프레임워크나, 빅데이터 기술에 대한 보안을 체크하려면 해당 분야를 잘 이해할 수 밖에 없다(물론 관련 영역에 대한 스캐너 같은 여러 솔루션을 사용해서 해결하는 방법도 있겠지만, 원리와 무슨 일을 왜 수행 하는지 정확히 모르는 블랙박스 형태의 툴에 의존하는 것은 보안 일을 하는 사람으로서는 너무 순진한 행동이라고 본다). 물론 기존의 다른 유사한 분야에 대한 풍부한 이해는 새로운 분야에 대해서도 많은 부분 패턴으로 적용될 가능성이 높지만, 그것은 새로운 분야를 제대로 이해한 후에야 완전하게 발휘될 수 있는 능력일 것이다(물론 대상의 빠른 이해에도 일정부분은 도움은 된다고 본다). 새로운 분야에서 데이터가 어떻게 흘러가는지를 확실히 이해할 수 없다면, 기존에 익힌 보안에 대한 패턴들을 어디에 적용할지 몰라서 손가락만 빨고 있게 될 수도 있다(아마 아는척 사기를 치지 않는 이상은 말이다)

 

[거짓말 잘하는 비결 - 동아사이언스]

http://m.dongascience.donga.com/news.php?idx=6462

 

 

  또한 보안은 언제나 사실에 기반한 마술과 비슷하다. 취약점은 이해하기 어렵거나 신비로울 수는 있지만, 항상 사실(코드나 설계)에 기반하여 일어나는 것임은 분명하다(그래야 방어의 설계도 가능한 것일테고...). 보안 취약점은 기술의 빈 틈을 파고 들어가는것이며, 기술 자체의 안전한 구성원으로 위장하여 들어가, 데이터베이스 등 외부 요소나 기술 자체에 영향을 미치는 것을 의미한다.

 

  웹 보안에서 가장 흔하게 나오는 SQL Injection 같은 부분은, 만약 여러가지 데이터베이스의 명령어나 관리 기술에 두루 능통하다면, 정말 빠르게 공격과 방어에 대한 원리를 이해할 수 있다(A piece of cake라고 하고 싶다) . 그런데 만약 데이터베이스를 모르는 상태에서 SQL Injection 을 이해하려 한다면, 보안 자체의 측면보다는 데이터베이스와 SQL 문장의 이해의 늪에 빠져서 시간도 많이 걸리고 불완전하게 단편적으로 이해하고 넘어갈 가능성이 높다.

 

  비슷하게 자바스크립트를 기반으로 한 XSS 나 CSRF 같은 취약점 패턴 등도 자바스크립트를 얼마나 잘 이해하고 있느냐에 이해의 난이도와 깊이가 달려있고, 커맨드 인젝션이나, XML 인젝션 등도 얼마나 시스템 명령어나, 프로그램의 내부 구조 및 관련 함수들, XML 구조를 잘 이해하느냐에 중요 포인트가 있다(물론 특정 취약점은 프로그램의 다른 요소와 믹스되어서 효과가 증폭되거나 경감되는 부분도 있긴 때문에 한 기술의 타입으로 단순화 하기는 힘들지만 말이다). 해당 부분에 대한 이해가 충분하다면 보안 이라는 패턴을 적용해 어떻게 해당 기술이 악용이 되고, 방지할 수 있는지를 쉽게 이해할 수 있게 된다. 나아가 그 방어 방식의 한계 및 제약과, 한계에 따른 리스크를 다른 수단으로 보완하여 경감해야 된다는 사실도 인지할 수 있게 된다. 해킹이란게 시스템을 속이는 것이라 얘기되지만, 그 앞에는 사실 시스템을 (잘 이해하고) 속이는 것 이라는 중요한 말이 숨어 있다고 생각한다.

 

  그럼 이 글에서는 맘대로긴 하지만 (기술적인) 보안 공부를 다음과 같이 정의해 보도록 하겠다. "데이터의 흐름을 이해하기 위해서, 데이터가 흘러가는 공간을 구성하고 있는 기술에 대해서 이해하는 과정". 앞으로의 시간들은 쓰는 사람의 지식의 한계 때문에, 그러한 부분의 아주 깊은 곳까지는 안내하진 못하겠지만, 데이터의 흐름을 이해하기 위해 이용되는 여러가지 방법들을 살펴보는 방식으로 진행될 것 같다. 하지만 뭐 항상 그렇지만 진행되다보면 원래 의도와 다르게 흘러가기는 한다...

 

 

 

3. 보안의 공격과 방어 측면의 차이

  프로그래머들에게 조금 미안하긴 하지만 개인적으로 IT 쪽의 많은 분야에서 그다지 바람직하지는 못하다고 생각되는 흐름 중 하나는, 프로그래밍 경험이 있느냐에 대한 부가가치를 너무 크게 잡는 다는 부분이라고 본다. 물론 QA나 보안이나, 시스템, 빅데이터 등의 여러 분야에 대해서 프로그래밍 능력이 상당한 도움이 되는 것은 맞다.

 

  하지만 프로그래밍 실력을 미리 갖추고 일을 시작 하는게 중요한 거라기 보다는, 해당 업무의 특정 레벨에 올라가게 될때 자연스럽게 업무의 효율성과 확장성을 위해서 자연스럽게 필요성이 생기게 되는 것이라고 본다. 무언가 귀찮거나 반복되서 의미 없는 일을 줄이기 위해 위해서는 어쩔 수 없이 자동화의 힘을 빌려야 하고, 그러다 보면 조금씩 자연스럽게 프로그램이란 세계에 발을 들이게 된다. 물론 그 전에도 API 사용이나, 명령어 스크립트 작성 등 기본적인 프로그래밍 지식이 있으면 좀더 적응이 용이한 부분들도 분명히 있다. 

 

  개인적으로는 프로그래밍 능력이 있느냐가 원천적으로 있느냐가 중요하다기 보다는, 업무상 자연스럽게 필요하게 되서 프로그래밍 능력을 습득하게 된다는 것이 좀더 맞을 것 같다. 물론 해당 부분에 대한 부분은 금방 휘리릭 습득되는 부분은 아니기 때문에 직업의 초기부터 조금씩 꾸준히 노력하는 것이 맞아보인다. 추가로 분야에 따라 프로그래밍 이란 것도 각 분야에 적절한 스타일로 다르게 접근 되기 때문에 쓰이는 포커스가 다른 경우도 많다. 제일 중요한 것은 해당 분야의 도메인 지식을 기반으로 적절하게 프로그래밍 언어의 능력을 쓰는 것인것 같다.  파이썬 글 17교시의 머신러닝 부분에서 프로그래밍이 어떤 식으로 쓰이고 있는지와 비슷하게 보면 된다고 할까? 표현하긴 좀 어렵지만 프로그래밍은 프로그래머 들에게만 생기는 직업적 기술이라기 보다는 약간 공공재 적인 기술적 영역이라고 보고 싶다. 그래서 두려워 하지 말고 일단 뛰어들어 보는 것을 추천한다. 시간을 들여 익숙해 지면 야매 정도는 되니까...

 

 

 보안 쪽도 비슷한 흐름이 있다고 보는데, 웹어플리케이션 보안 부분을(특히 모의해킹이라고 칭해지는 부분) 특수한 인력이 특수한 절차를 밟아서 획득할 수 있는 완전한 검증 방식으로 생각한다는 부분이다. 모의해킹은 표면적인 효과 이외에 생각할 점이 많은 분야라고 본다. 

 

  우선 수행하는 사람의 능력에 많이 좌우된다. 보안 분야도 개발이나 테스팅과 비슷하게 사람들마다 접근하는 전략과 효율성의 차이가 많이 나고, 기술 및 도메인에 대한 이해도도 틀리며, 그 차이가 외부에서 볼때 확실히 구분되기 힘들다. 인력이나 도메인에 많은 영향을 받아 어떤 기술셋을 가진 사람이 어떤 분야를 점검하느냐도 중요하고, 어떤 언어나, 개발프로세스를 거쳐 만든 도메인을 만나느냐에 대한 운도 작용하는 것 같다. 또 점검 후 점검한 영역에서 실제 사고가 나거나, 비슷하거나 더 상위 수준(또는 비슷하지만 시간에 쫒기지 않은 한가한)의 인력이 충분한 시간을 가지고 크로스 체크 하기 전에는 효과를 증명하기 힘든 경우도 종종 있는 듯 싶다. 또 이해한 만큼만 점검이 가능하기 때문에, 시행자의 대상의 이해 정도에 의해서도 많이 차이가 나고, 일반적으로 리소스나 범위 등의 여러가지 사유로 소스 기반이 아닌 블랙박스나 그레이박스 기반, 그리고 제한된 시간 프레임 내에서 진행되기 때문에, 구성되어 있는 자원이나 소스에 대한 완전한 커버리지를 가지긴 힘든 경우도 많은것 같다.

 

  그리고 대부분의 경우 증명보다는 시나리오 기반으로 진행되기 때문에, 해당 시나리오를 벗어난 전체적인 보안성 측면에 대해서는 보장(Assureance)하지는 못한다고 보는게 맞을 것 같다. 또한 불행하게도 점검한 소스나 환경은 계속 변할 수도 있기 때문에, 해당 점검 시점 후 시간이 지나게 되면 다시 코드의 안전함을 보장하지 못한다는 모순에 빠지게 된다(개발자중 한명이 모의해킹이 완료된 후의 코드 베이스에 취약한 코드를 하나 넣었다면 어떻게 될까?).

 

  마지막으로 시스템이 너무 기초적인 보안 설계가 안 되어 있다면 중요한 취약점들을 다수 발견할 수는 있겠지만, 해당 취약점에 대부분의 시간을 쏟고, 진행이 막히게 되어, 더 미묘한 내부를 들여다 보지 못할 수도 있다. 뭐 중요한 기본 취약점들을 찾았다는 의미는 있긴 하지만, 해당 취약점들에 갈 길이 막혀 깊이 있는 취약점은 찾지 못할 수 있다. 기본적인 기능의 동작도 제대로 안되서 중요도가 높은 버그를 잔뜩 올렸지만, 해당 가능에 대해 깊이 있는 테스트를 하지 못하는 QA 테스트와 비슷한 상황이라고 보면 된다. 해당 경우는 모의해킹이 중요한게 아니라 시스템이나 프로세스를 기초부터 개선 하는게 더 중요할지도 모르는 상황이라고 본다. 분명히 모의해킹은 보안쪽의 중요한 요소이기도 하고, 수술에 꼭 필요한 잘 드는 메스이기는 하지만 은총알은 아니여서, 환자의 상태나 다른 검사 장비들과, 의사에 판단에 의해 잘 조합하여 사용해야 효과가 큰 것 같다.

 

  그래서 모의해킹은 어떻게 보면 공부를 열심히 하고 보게되는 고3의 수능 같은 평가 활동 같다고 보고 있다. 테스팅을 아무리 하더라도 최초부터 없는 품질을 얻을 수는 없는 것과 마찬가지로(중요한 버그들을 발견하고 고치는 행위자체가 반드시 품질을 보장하진 못한다 파이썬 머신러닝 파트에서 얘기했던 garbage in garbage out 과 마찬가지로...), 모의해킹으로 애초부터 들어 있지 않은 보안성을 잡을 수는 없다. 그런 믿음은 이미 다 지어진 집에서 발견된 균열들을 급하게 메우면서 집이 안전하길 바라는 것과 비슷하다. 사실은 설계 부터의 과정에서 뭔가 어긋나서 생긴 문제인데도 말이다. 

 

  보안성은 시험 전에 한참 꾸준히 해왔던 공부 같이, 여러 요구사항의 수집에서 시작하여, 안전한 설계, 개발의 과정 중에 이루어지는 여러 관점의 보안적 리뷰와 체크에서 생기는 것이라고 보는게 좀더 근본적일 것 같다. 모의해킹은 어플리케이션 보안에 대한 오랜 동안의 업계 사람들의 시각과 기술적 노하우, 취약점에 대한 개인의 감, 실제 경험들이 총 집성된 평가 체계라고 생각하지만, 역으로 그런 기본 역량을 충분히 갖춘 사람들이 평가를 해야지 충분한 의미가 있다는 단점도 분명히 있다는 것을 잊으면 안된다(물론 절차적인 면죄부라는 느낌이 좀 있긴 하다...) 

 

 

  그럼 해킹, 모의해킹과 같은 공격과 시큐어 코딩과 같은 방어의 차이는 무엇일까? 우선 둘의 공통점은 공격하고자 하는 대상과 방어하고자 하는 대상이 먼저 얘기한 데이터라는 것이다. 두 가지의 차이점은 , IT를 무협과 비교하는 걸 별로 선호하는 편은 아니지만, 무협만화에 나오는 빠른 검과 느린 도의 대결과 비슷하다고 본다(멋진 캐릭이 나오는 게임 또는 만화의 삽화를 넣고 싶긴 하지만, 저작권 문제로.. 무협 만화 좋아하는 사람은 열혈강호의 담화린(빠른칼)과 도제(느리지만 무거운 도)가 싸우는 장면을 상상해 보면 어떨까 싶다^^). 공격은 시스템의 헛점을 노리고 다양한 영향을 미치려고 계속 변화하면서 수행되는 반면, 방어는 사실 기본기에 충실해서 느리지만 핵심을 지키면서(靜中動) 공격으로 들어온 외부 코드들을 무용지물로 만들게 된다고 본다.

 

 

  그럼 느리지만 무거운 도에 해당하는 방어를 코드로 예를 들면 어떻게 될까? 개인적인 생각에는 아래와 같이 (외부에 입력된 변수로 부터 영향을 받을 가능성이 있는) 내부에서 사용할 변수의 타입과 범위를 체크하는 코드가 아닐까 싶다. 아래와 같이 정확한 정수 타입과 필요한 수의 범위를 제한하게 되면, (아래와 같은 숫자 변수에 대한 코드에 경우에는) 어떠한 인젝션 시도에도 안전한 코드가 될 것 같다.

1
2
3
4
5
6
ext_var = "union all ... --"
 
if type(ext_var) is int and ext_var > 0 and ext_var <= 10:
    print ("use variable");
else:
    print ("not safe");
cs

 

   해당 방어 코드를 모르는 공격자는 공격을 위해서 많은 화려한 기법을 사용할테고, 그 중에 똑똑한 공격자는 금새 잘 막혀 있다는 걸 알아채고, 시간낭비 없이 다른 취약한 변수나 코드들을 찾아 다니게 될 것이다. 좀 더 나아가면 해당 부분은 관련 방어를 지원하는 프레임워크를 쓰거나, 해당 프로그래밍 언어에서 지원하는 범용적인 보안 함수를 사용하는게 더 맞겠지만, 여기서는 방어하는 무거운 도(刀)에 대한 극단적인 이미지를 나타내기 위해서 아래와 같은 코드로 제시해 보았다. 문자열 변수 등과 같이 좀더 상세하게 따져가며 저울질할 부분들은 나중에 해당 주제에 대해서 다루면서 얘기하면 될 것 같다.

 

  파이썬 코드의 실행을 모를 경우 파이썬 2교시 글을 참고하면 된다

c:\Python\code>python variable_check.py
not safe

 

 

  한가지 더 인터넷에서 많이 언급되는 얘기를 생각해 보면, 공격의 대표적인 방식인 모의해킹의 기법을 이해해야만, 코드의 방어를 어떻게 해야 할지 제대로 알수 있다는 부분이 있다. 해당 부분은 맞는 얘기기는 하지만, 다른 한편으로 조금 더 생각해 볼  부분이 있다고 본다.

 

  방어 측면에서의 모의해킹의 이해란, 모의해킹에서 사용하는 기법이나 다양한 툴의 원리와 사용의 이해의 측면 보다는, 해당 공격으로 일어나는 데이터들의 변화에 초점을 맞추는 것이 맞을 듯 싶다(이 부분은 보안 테스팅을 위해 코드를 읽을 때도 비슷하게 적용된다. 물론 취약점의 원리를 이해하면 자동으로 툴이 무엇을 하려 하는지를 어느정도 이해하게 되겠지만 말이다..). 어차피 방어 측면에서는 공격과 같이 현란한 데이터의 변경으로 맞설 수는 없기 때문에(물론 뭐 요즘의 OS 등에서 실행파일이 로드되는 메모리 번지를 계속 변경 시킨다든지, 솔루션 등에서 머신러닝 등을 이용해서 비 정상적인 통계 패턴을 계속 추적한다든지 하는 등은 공격과 비슷한 다이나믹한 방어로 볼수 있긴 하겠지만), 일반적으로는 공격의 원리에 대치되는 배치로 대응하는 수밖에 없다. 공방 게임에서 공격 유닛에 대한 방어는, 해당 유닛에 상응하는 방어 건물의 배치로 이루어지는 것과 비슷하지 않을까 싶다. 그 방어 건물이 우리가 자주보게 되는 "시큐어 코딩 가이드" 같은 거고 말이다.

 

 물론 모의해킹의 아주 미묘한 부분까지 이해하여, 관련 전문가 수준으로 알게되면 분명 더 좋은 방어를 하게 될듯 싶다(물론 공격 뿐만 아닌 방어 관점의 이슈들도 충분히 잘 안다는 가정하에 얘기이다). 

 

  보안에 대한 분야는 사실 너무 넓은 분야이고, 모든 존재하는게 사실 보안과 연관이 있고, 그 안에 서식하고 있는 많은 요소들도, 계속 살아있는 생태계 처럼 변하고 있기 때문이다. 지금 해당 영역에서 완벽하더라도 앞으로 계속 따라기기 위해서 해야 될 공부가 너무 많다. 실제로 외부에서 보기엔 완벽해 보이는 사람들도, 좀 더 자세히 들여다 보면 생각보다는 완벽하지 못한 경우가 많고, 그것은 비단 보안 뿐만 아니라 모든 지식의 영역이 마찬가지인듯 싶다.

 

 

  

4. 마무리 하면서

  앞으로 글을 진행 하면서 앞에서 한 얘기들을 좀더 자세히 설명하기 위해서, 몇 가지 방향을 잡고 진행하려 한다. 우선 공격보다는 방어에 필요한 부분을 위주로 설명할 예정이며, 공격을 얘기할 경우는 방어 측면에서 이해가 필요한 공격의 측면을 중심으로 얘기하려고 한다. ASP, Python, PHP 와 같은 스크립트 언어와, 자세히는 모르지만 .NET 이나, JAVA 같은 주제를 잘 이해시켜 줄 수 있는 언어를 가지고, 가능한 실제 움직이는 코드를 구현하고자 한다. 그리고 피들러와 같은 관찰 및 조작 할수 있는 툴을 통해 데이터가 흘러가고, 변조 되는 부분을 시연하고, 방어를 위한 코드를 제시하며, 해당 방어의 원리와 제한 들에 대해서 얘기하려 한다. 

 

  비교적 잘 설명이 가능할 거리고 생각하는 웹 기술을 중심으로 이야기를 시작하며(사실 웹이나 다른 분야나 넓게 보면 기본적인 보안 패턴은 비슷하다고 생각하지만 아직은 크게 자신은 없다^^), 후반에 일반 어플리케이션에 대한 등에 대한 얘기도 얇은 지식이나마 정리해 볼까 한다. 이 글의 목표는 읽은 사람들이 각 보안 영역에 대해서 어떻게 접근해야 겠다는 스스로의 생각을 가지게 되고, 여러 보안 가이드를 볼 때, 해당 가이드를 작성한 사람이, 어떤 관점에서 그러한 방어 전략을 제시했는지에 대한 이해의 기반을 가지게 하는 것이다. 

 

  물론 앞의 파이썬 글과 마찬가지로 각 주제들에 대해 깊은 부분 까지 설명하기에는 스스로 아는 것이 적다고 생각하고, 시간의 제한도 받기 때문에 "보안에 대한 이런 저런 주제를 가볍게 다루는 기술적 산문집" 이라고 기대하고 읽어주면 좋을 듯하다. 내용 중 잘못됬거나 의견이 다른 부분은 댓글을 달아 알려주심 감사할듯 싶다.

 

그럼 재미있는 보안공부가 되길 바라며...

posted by 자유로운설탕
2017. 8. 19. 23:28 프로그래밍

  이제 개인적으로 소프트웨어를 바라 보는 관점에 영향을 주었던 글을 하나 소개하면서, 구글로 공부하는 파이썬 시리즈를 마무리 하려 한다.

 

 

[목차]

0. 왜 파이썬 공부에 구글을 이용하는게 좋은가?

1. 언어를 바라보는 방법. 파이썬을 어떻게 바라봐야 할까?

2. 파이썬 설치와 환경, 버전 선택 하기의 이유.

3. 만들고자 하는 기능을 모르는 조각으로 나눠 조사해 보기

4. 데이터 베이스에서 내용 가져와 출력하기

5. 암호화 모듈을 이용해 암복호화 해보기

6. 퍼즐 조각들을 합쳐보기

7. 엑셀 파일 사용해 보기 -> 부록 : fuction 을 이용해서, 코드 정리해 보기

8. 정규표현식을 왜 사용해야 할까? 언어속의 미니 언어 정규표현식 살펴보기

9. 입력과 결과를 GUI 화면과 연결해 보기

10. Whois API 이용해 보기

11. 웹페이지 호출해 내용 파싱 하기(BeautifulSoup 그리고 한계)

12. 자동화 - 웹 자동화(with Selenium)

13. 자동화 - 윈도우즈 GUI 자동화(with pywinauto)

14. 자동화 - 작업 자동화

15. 수학 라이브러리 살펴보기

16. 그래픽 라이브러리 살펴보기

17. 머신러닝에서의 파이썬의 역활

18. 웹 프로그래밍 - Legacy Web

19. 웹 프로그래밍 - Flask 살펴보기(feat. d3.js)

20. 웹 프로그래밍 - Django 살펴보기

21. 정리 - 이런저런 이야기

 

 

[추가]

현재 모바일에서 코드(colorscript 이용)가 나온 후에 글 색깔이 흐려져서 읽기가 어렵네요. 티스토리 개편하고 현재 스킨이 너무 예전것이라서 그런가도 싶긴한데, 수정이 쉽진 않을것 같아서 일단 웹에서는 정상적으로 보이니 웹을 이용해 주세요. 조만간 수정해 보도록 하겠습니다.

감사합니다.

 

 

[The five orders of ignorance]

  저도 몇년전에 누군가에게 소개 받았던 글이긴 하지만, 'Communications of the ACM' 저널에 실렸던 'The five orders of ignorance(무지의 5계층)' 라는 소프트웨어 개발에 대한 글을 소개하려 한다(아래 URL 안의 링크를 클릭하면 pdf 로 된 전체 글을 다운받을 수 있다).

http://www.corvusintl.com/CACM002-5OI.htm

 

  약간의 개인적인 해석을 추가해 요약하자면 아래와 같다.

 

  인류가 가지고 있는 지식을 저장하는 매체는 DNA, 뇌, 하드웨어, 책을 거쳐 현재는 소프트웨어라는 형태가 생겨나게 되었다. 소프트웨어는 뇌처럼 공간이 제한되어 있거나, 쉽게 변하지 않고, 책처럼 수동적이지도 않으며(뭐 읽는 사람에게 감성과 상상력을 일으킨다는 측면에서는 그렇지 않을지도 모르지만...), 하드웨어나, DNA 처럼 느린 변화를 가지거나, 유연성이 부족하지도 않다.

 

  우리가 소프트웨어 제품을 만든다는 것은, 특정한 제품 자체를 만드는것이 아닌 우리가 알고 있는 지식들을 저장하는 매체를 구현하는 행위라는 것이다. 그래서 소프트웨어의 잘 알려진 지식들은 프레임워크나, IDE 를 통해서 자동으로 코드를 만들어 낼 수 있는 반면에, 다른 특정한 부분들은, 직접 해당 지식을 획득하여 구현해 내야 한다.

 

  예를 들어 현재의 파이썬 시리즈를 다 읽은 시점에서, 새로운 파이썬 프로그램을 만든다고 해보자. 그 중에 어떤 부분들은 우리가 이미 알고 있는 지식을 재사용하여 쉽게 구현할 수 있으며, 만약 새로운 프로그램의 주요한 부분들이 기존에 지식들과 유사하다면, 정말 쉽게 짧은 시간에 새로운 프로그램을 만들 수 있을 것이다. 그런데 만약에 우리가 현재 다루지 않았던 기능을 가진 프로그램을 만들려 한다면 해당 분야에 대한 지식을 추가로 획득해야 한다. 추가적인 지식이 필요한 경우인 경우라도 다른 유사한 프로그래밍 경험을 통해서, 해당 지식의 영역(예를 들어 쓰레드 구현)을 경험해 본적이 있다면 프로그래밍 언어의 차이점만을 잘 극복한다면 백지에서 시작하는 것보다 좀더 쉽게 원하는 지식을 획득할 수 있을 것이다. 아마도 최악의 경우는 어떻게 해당 부분을 해결해야 할지에 대한 방법이 전혀 감이 잡히지 않는 경우일 것이다.

 

  이런 관점에서 볼때 소프트웨어 개발은 지식을 획득하는 활동인 동시에, '무지(ignorance)' 를 줄이거나 제거하는 활동으로 바라볼 수 있다. 이 글에서는 이러한 측면에서 무지의 계층을 5단계로 나누어 제시한다.

  • 0 계층 - 무지의 결핍(Lack of Ignorance) : 무언가를 오랬동안 경험해 왔기 때문에 그것에 대해 잘 아는 상태이다. 오랫동안 보트를 타봤다면 어느 정도 항해에 대해서 잘 안다고 얘기할 수 있는 것처럼 말이다.
  • 1 계층 - 지식의 결핍(Lack of Knowledge) : 무언가를 모른다는 것을 정확히 알고 있는 상태이다. 예를 들어 러시아어를 못한다는 것을 안다면, 러시아어를 공부하기 위해 학원을 다니거나 책을 봐야한다는 것을 명확히 알수 있다.
  • 2 계층 - 의식의 결핍(Lack of Awareness) : 무엇을 모른다는 것 자체를 인지하지 못하고 있는 상태이다. 무엇을 모르는지 자체를 모르기 때문에, 무엇을 해야 하는지도 모르는 상태 이다.
  • 3 계층 - 프로세스의 결핍(Lack of Process) : 인지하지 못하는 무지를 알수 있게 해주는 수단 자체가 없는 상태를 얘기한다. 그것은 특정한 프로세스의 부재 일수도 있고, 특정한 환경의 부재일 수도 있다.
  • 4 계층 - 약간 유머 같긴 하지만, 무지의 5계층 자체를 모르는 단계라고 한다. 이 글을 읽게 되면 이 단계에서 벗어나는 셈이라고 한다.

 

  개인적으로는 이 글에 공감이 가서 소프트웨어를 바라보는 관점이 많이 바뀌게 되었고 설명은 하기 어렵지만 왠지 좀더 대상을 편하게 바라보게 되었다. 소프트웨어에 대한 여러 다양한 기술 업무들이 비슷하게 무지를 줄이고, 숨어있는 무지들을 찾아내는 활동이라는 생각이 들었으며, 결국 소프트웨어에 담겨있는 내용은 지식이기 때문에, 소프트웨어이외의 다른 분야들의 좋은 접근 방법이나 개념들도 무형적 지식이라는 측면에서 비슷하게 연결이 되는게 아닐까하는 생각도 들었다. 스타크래프트의 'Black Sheep Wall' 처럼 무지의 영역을 한순간에 밝혀주는 마법의 치트키는 없겠지만, 자신이 모르는 필요한 부분들을 계속 발견하고 해당 영역들을 채우는 것이, 소프트웨어에 관한 직업을 가져가면서 스트레스를 덜 받는 방법이 아닌가 싶다.

 

 

 

 

[마무리 하면서]

  이 글의 목적은 읽는 사람을 파이썬에 대해서 어느 수준까지 끌어가는 것은 아니다(개인적으로 그럴만한 능력도 안된다고 생각하고...). 오히려 프로그래밍이란 생각보다 모호한 일이며, 다른 사람들이 미리 만들어 놓은 수많은 지식과 접근법들을 조합하여 문제를 해결하는, 정답이 여러개이며 완벽한 정답은 없는 선택적 영역이라는 것을 얘기하고 싶었다. 또한 코드를 만드는 과정에서 무언가가 막혔을때, 검색 엔진을 이용해 문제를 찾아서 해결하는 과정에서 일어나는 '헤멤'이라는 활동을 실제로 보여주고 싶기도 했다(뭐 다른 사람은 다른 방식으로 헤멜 수는 있을 것 같다). 그리고 조금 욕심을 내자면 각 주제 영역에 대해서 스스로 공부할 수 있게, 조금 먼저 길을 걸어본 입장에서 해당 지식들에 대한 접근 방법을 안내를 하고 싶었다. 얼마나 해당 부분을 충족 시켰는지 자신은 없지만, 목표로 했던 이야기들은 다 한 것 같아서 여기서 일단 글을 멈추려고 한다. 다들 너무 부담 갖지는 말고 천천히, 하지만 꾸준히 공부 하시기를 바란다.

 

 

 

 

2017.8.20 by 자유로운설탕
cs

 

  

 

 

posted by 자유로운설탕
2017. 8. 6. 00:30 프로그래밍

  이번 시간에는 앞 시간의 플라스크(flask) 시간에 이어서, 파이썬을 대표하는 웹 프레임워크로 알려져 있는 장고(Django) 를 살펴보는 시간을 가지려고 한다. 플라스크를 살펴볼 때와 비슷한 방식으로 공식 메뉴얼을 기준으로 전체적인 장고의 구조에 대해서 살펴보고, 플라스크에서 구현했던 2개의 예제(MSSQL 테이블 표시, d3.js 그래프 그리기)를 장고 환경에서 똑같이 구현하는 과정을 보여 줌으로서, 플라스크와는 어떤 다른 측면들이 있는지를 설명하려고 한다. 

 

 

 

[목차]

0. 왜 파이썬 공부에 구글을 이용하는게 좋은가?

1. 언어를 바라보는 방법. 파이썬을 어떻게 바라봐야 할까?

2. 파이썬 설치와 환경, 버전 선택 하기의 이유.

3. 만들고자 하는 기능을 모르는 조각으로 나눠 조사해 보기

4. 데이터 베이스에서 내용 가져와 출력하기

5. 암호화 모듈을 이용해 암복호화 해보기

6. 퍼즐 조각들을 합쳐보기

7. 엑셀 파일 사용해 보기 -> 부록 : fuction 을 이용해서, 코드 정리해 보기

8. 정규표현식을 왜 사용해야 할까? 언어속의 미니 언어 정규표현식 살펴보기

9. 입력과 결과를 GUI 화면과 연결해 보기

10. Whois API 이용해 보기

11. 웹페이지 호출해 내용 파싱 하기(BeautifulSoup 그리고 한계)

12. 자동화 - 웹 자동화(with Selenium)

13. 자동화 - 윈도우즈 GUI 자동화(with pywinauto)

14. 자동화 - 작업 자동화

15. 수학 라이브러리 살펴보기

16. 그래픽 라이브러리 살펴보기

17. 머신러닝에서의 파이썬의 역활

18. 웹 프로그래밍 - Legacy Web

19. 웹 프로그래밍 - Flask 살펴보기(feat. d3.js)

20. 웹 프로그래밍 - Django 살펴보기

21. 정리 - 이런저런 이야기

 

 

 

 

[들어가면서]

  우선 시작하기 전에 혹시 19교시의 플라스크(flask)에 대해서 보지 않은 상태라면, 먼저 해당 내용을 보고 오기를 바란다. 장고를 살펴본 결과 플라스크와 대비되는 부분을 짚어가며 설명하면 효율적이라는 생각이 들었다. 플라스크에서 개념을 설명했던 url routing(url 라우팅), static files(정적 파일), rendering templates(템플리트 랜더링)의 개념은 거의 동일 하게 사용되기 때문이다. 또 템플릿 소스도 일부만 수정해 그대로 사용할 계획이다. 이번 시간에는 ORM, MVT(MVC), ADMIN 등 장고에 특화된 개념만 추가로 설명할 계획이다. 개인적으로 플라스크를 살펴볼때 보다 2~3배 정도의 시간이 장고에 소요됬었고, 플라스크를 이해한 상태라면 장고도 비교적 접근하기 수월하다는 생각이 들었다.

 

 

[한글 문서 여부]

  역시 플라스크와 비슷하게, 구글을 찾다보면 아래의 한글 페이지가 있긴한데, 제목까지만 다 번역되어있고, 실제 내용은 튜토리얼 까지만 번역되어 있다. 다만 튜토리얼에 전체 구조를 이해시키는 핵심적인 얘기들이 많으므로, 튜토리얼을 한글로 보며 대충 돌아가는 상황을 파악후, 영문 문서들을 그담에 보길 권장한다.

https://django-document-korean.readthedocs.io/ko/master/

 

  또 다른 튜토리얼 레벨의 괜찮은 한글 문서는 구글에서 'django sample' 로 찾으면 나오는 아래의 장고걸 사이트 문서이다. 개인적으로 봤을때 체계적으로 잘 정리되어 있어 처음에 개념을 잡는데 도움이 됬었다. 파이썬 버전도 3.5 버전 기준이라 문법에 위화감도 없다.

https://tutorial.djangogirls.org/ko/django_start_project/

 

  그리고 마지막으로 영문 예제 사이트는 아래 사이트가 괜찮았다. 여기도 마찬가지로 3.5.x 버전이고, 위의 3개 사이트의 내용을 보면 대충 장고 사이트가 어떻게 돌아가는지에 대해 전체적으로 감이 잡히기 시작할 것이다.

https://scotch.io/tutorials/build-your-first-python-and-django-application

 

 

[Django Documentaton 보기]

  구글에서 'django document' 라고 검색하면 아래의 문서가 나오는데, 현재 최신이 1.11 버전(이게 '일점 일일' 버전이 아니라 '일점 십일' 버전이다. 첨에 이것땜에 구글을 검색하때 1.7, 1.8 같은 미래 버전이 왜 있지 하고 좀 헷깔렸다--;)의 공식 메뉴얼 페이지를 볼수 있다.

https://docs.djangoproject.com/en/1.11/

 

  'First steps' 안에 있는 Overview, Installation, Tutorial 에는 장고를 이용해 사이트를 세팅하는 것부터, 라우팅(URLconfs. 라고 보통 말한다)을 설정하는 법(urls.py), 모델을 만들어 해당 구조를 데이터베이스 및 어드민 기능과 싱크(migration) 시키는 법, 뷰와 템플릿을 사용하여 표시하고, 폼을 전송하여 받아 처리하고, 정적(static) 파일들을 설정하고 접근하는 법에 대한 전체적인 흐름을 보여준다.

 

  'The model layer' 에서는 모델을 상세하게 다루어, 모델에 대해 어떻게 클래스로 정의하고, 정의된 모델들을 지원하는 메쏘드들을 이용해 쿼리를 요청해 값을 가져오고(QuerySet), 데이터베이스와 모델을 어떻게 싱크시키고(Migrations), 모델을 벗어난 커스텀 쿼리를 데이터베이스에 어떻게 날리며(Raw SQL), 데이터베이스별로 모델을 적용하는데 필요한 여러가지 참고사항과 주의사항들을 얘기한다.

 

  'The view Layer' 에서는 어떻게 라우팅을 구성 하며(URLconfs), 어떻게 요청(request)을 하고받아서, 모델에 대한 검색을 지원하는 QuerySet 를 이용하여 결과를 가져와서, 어떻게 응답을 하는지(Requsest and response objects), 파일 업로드를 구현하는 법(File upload), 내장된 뷰(generic view)를 이용하는 방법(Built-in display view) 등을 다룬다.

 

  'The template layer' 에서는 템플릿에서 여러가지 장고에서 지원하는 템플릿 지원 로직들을 사용하여 표현하는 방법을 얘기한다(for 라든지 url 이라든지 여러 문법들을 사용하는 방법을 얘기하는데, 앞의 플라스크와 비슷하지만 조금 더 확장된 기능이라고 보면 될듯 하다) 
 

  이후 'Forms' 에서 폼을 넘기고 처리하는 부분을, 'The Admin' 에서 모델과 연관되어 자동으로 업데이트 되는 관리 페이지를 커스터 마이즈해 사용하는 부분을, 'Security' 에서 잘 알려진 보안 이슈들에 대응하는 설계를 구현하는 방법을, 'Common Web application tools' 에서 웹사이트를 개발하면서 주로 만나게 되는 세션, 캐싱, 스태틱 파일, 사이트맵 등등에 대한 구현을 지원하는 기능들을 얘기한다. 그 외의 섹션에서는 유니코드라든지, 로케일이라든지, 개발, 테스팅 방법이라든지 하는 여러 내용들을 다루고 있다.

 

 

  위에서 얘기했던 내용들을 이해한대로 그려보면 아래의 그림과 같다. 브라우저가 폼이나 API를 통해 요청을 하면, 장고의 웹 모듈이(WGSI - 메뉴얼에 이 기능은 테스트에만 사용하고 운영시에는 아파치 등을 연동해 쓰라고 명시되어 있다) 요청을 받아, 라우팅(URLConfs) 기능을 통해 해당되는 뷰의 콜백 함수에 전달한다.

 

  모델은 ORM(Object-relational mapping)이라는 패턴 기법을 이용해 데이터베이스를 가상의 프로그래밍적 객체로 모델링하여 정의하고, migrate 명령어를 이용해, 실제 데이터베이스에 테이블을 만들거나, 수정하여, 장고와 데이터베이스 사이의 구조를 싱크(뭐 migration 이 한 방향의 의미긴 하지만 넓게 보면 싱크 개념인것 같다) 시킨다. 또한 데이터 베이스에 SQL 문을 날리듯, 모델에서 QuerySet 이라는 검색용 메써드를 제공해, ORM 객체로부터 데이터를 조회해오게 한다(select, where, order by, join 등을 실제 비슷하게 구현한다. 앞에 진행했던 시간을 생각해 보면, 'Panda' 가 가상의 메모리 객체(dataframe)를 만들어서 비슷한 행동을 했었다).

 

  양 쪽을 싱크하는 과정에서 어드민에서 필요한 몇몇 테이블도 데이터베이스 안에 들어가게 되고, Admin 쪽에는 모델에서 정의한 ORM 객체들을 살펴보고, 데이터를 넣거나, 수정하거나, 지우거나 하는 관리 행동을 할수 있는 기본적인 인터페이스가 자동으로 싱크되어 구현된다.

 

  이러한 모델에서 만들어진 ORM 개체들은 뷰에서 QuerySet 을 이용하여 호출되어, 사용자가 요청한 조건에 적합하도록 가공되며, 템플릿과 조합되어 동적인 UI를 생성해 제공되거나, Json 응답 등으로 템플릿과 상관없이 독립된 형태로 사용자에게 응답을 줄 수 있다. 템플릿은 기본적으로 HTML 베이스로 구성된 응답(response)을 위한 기본 문서이며 여러가지 장고에서 제공되는 템플릿용 지원 기능 들과 CSS, Javascript 같은 스태틱 파일들을 이용하여 적절한 UI 를 구성하여 사용자에게 처리결과를 보여 주게 된다. 

 

  위의 그림중에 대부분의 요소들은 앞의 레거시 웹이나, 플라스크(flask) 살펴보기 시간에 얘기했던 주제들이거나, 추후 메뉴얼을 보면서 자세히 항목들을 살펴봐야 될 주제들 같아서, 이 시간에는 ORM 과 그에 연관된 주제들(QuerySet, Migration), 그리고 장고의 뼈대를 구성하는 MVT 라는 패턴 구조에 대해서 설명한 후 나머지 플라스크와의 자잘한 차이는, 실제 샘플을 구현하면서 중간 중간 얘기하려 한다.

 

 

[ORM]

  ORM 은 앞에서도 얘기했지만, 데이터베이스의 테이블의 구조와 관계를 클래스와 프로퍼티(속성), 메쏘드를 이용해, 객체의 관계로서 모델링 하는 기법이다. 구글에서 'orm pros and cons' 나 'orm 장단점' 이라고 검색하면, 아래와 같은 많은 페이지가 나오면서 난상 토론을 보게 되는데,

 

https://stackoverflow.com/questions/494816/using-an-orm-or-plain-sql

https://stackoverflow.com/questions/35955020/hibernate-orm-framework-vs-jdbc-pros-and-cons

https://gs.saro.me/#!m=elec&jn=718

http://layered.tistory.com/entry/ORM%EC%9D%80-%EC%95%88%ED%8B%B0%ED%8C%A8%ED%84%B4%EC%9D%B4%EB%8B%A4-ORM-is-an-antipattern

https://okky.kr/article/286812


 

  ORM 사용에 대한 판단은 각각 하는게 맞겠지만, 몇 가지 생각해 봐야할 것 같은 부분들이 있다. 우선 1번째는 'ORM' 은 'ODBC' 나 Selenium 의 'webdriver' 처럼 원소스 멀티유즈를 표방한다. 그러다 보니, 같은 모델이 여러 데이터베이스와 100% 호환된다는 보장은 사실 힘들것 같다(메뉴얼의 'The model layer' 의 뒷 부분이 이런 차이들과, 모델로서 해결하기 힘든 경우 raw sql 을 사용하는 부분들을 가이드 하고 있다). 데이터베이스마다 미묘한 문법차이나, 설계 차이가 있을 수 있고, 데이터베이스 버전 별로 100% 호환이 되도록 충분히 지원하고 테스트 되었다고 보기 힘들 수 있다. 마치 셀레늄에서 webdriver 의 종류에 따라 서로 다른 브라우저 끼리의 동작이 미묘하게 달랐던 것 처럼 말이다. 그래서 가능한 ORM 을 쓸 경우, 해당 프레임워크가 가장 기본으로 지원하는 데이터베이스를 사용하는게 업데이트도 계속 지원되고, 호환이 안되어 곤란한 일이 안 생길 것 같다(예를 들어 Django 는 PostgreSQL, MySQL, Oracle, Sqlite3 를 공식적으로 메뉴얼에서 다룬다).

 

  2번째는 ORM 이 데이터베이스에서 구조와 성능을 위해서 지원하는 주요 기능 요소들을 충분히 모델상에서 포함하고 있는지에 대해서이다. 장고에서 기본적으로 하나의 모델 객체는 하나의 실제 테이블에 마이그레이션이되어 매칭 되는 구조인데, 실제 복잡한 사이트에서 효율적인 설계가 해당 방식으로 100% 이루어질 수 있는지를 체크해 봐야한다(현실에서는 수많은 테이블이 서로 join 등으로 엃혀있는 관계를 가진 경우가 많아서, ORM 모델로 구현시 가독성이나, 복잡도가 감당할만 한지 등을 따져봐야 할 것 같다)

 

  3번째는 레거시 데이터베이스를 사용하는 경우이다. 관계형 데이터베이스의 특성상 많은 테이블들이 서로 연관을 가지면서 파편화 되있을 가능성이 많은데, 해당 부분을 현재의 모델로 흡수하여 구현하게 되면, DB프로시저 등을 생산해서 추가적인 중계가 필요할 수도 있고, 그런 경우엔 장고의 ORM 에서 구현한 장점 중 하나인 마이그레이션 기능을 원활히 사용할수 없을 가능성이 높아진다. 

 

  4번째는 DBA 인력들과의 협업이다. 개발 쪽에서 독자적으로 데이터베이스를 관리하고 책임지는 구조라면 모르겠지만(뭐 요즘 유행인 DevOps 등의 조직에선 모르겠다), DB 관련 팀 쪽에서 데이터베이스의 성능, 테이블 스키마 관리 등을 책임 지고 있다면, 만들어진 모델을 검증하기 위해서, DBA 인력이 장고의 ORM 을 구현한 클래스들과 QuerySet 구성을 이해하고, 해당 부분이 실제 현재의 데이터베이스에 어떤 영향을 주는지 모델의 변경이 생길때마다 매번 검증을 해야하는 상황이 생기게 되는데, 이런 방식이 가능한 시나리오인지는 의문이 생긴다.

 

  마지막으로는, 장고 쪽에 마이그레이션 기능을 위해, 데이터베이스의 스키마를 자동으로 바꿀 수 있는 권한을 주는 부분이 보안적으로 적절한가에 대한 고려와, Admin 의 요구사항이 경험적으로 단순히 모델 객체에 데이터를 넣거나 편집하는 것으로 단순하게 이루어지진 않기 때문에 자동화된 어드민이 실제 얼마나 유용할까에 대한 의문이다.

 

  결론적으로 적절히 우호적인 환경에서는 장고의 ORM 을 사용해 데이터베이스를 마이그레이션 하여 관리하는 것도 좋지만, 해당 경우 발생할 여러가지 반대 급부를  생각해 봐야 하며, ORM 을 사용하는 것 자체가 개발자에게 데이터베이스를 덜 이해해도 된다는 면죄부가 되는 것은 아니라는 것을 얘기하고 싶다. 오히려 개인적인 생각에는 ORM 을 사용해 모델을 설계해서 테이블과 싱크 시키고 싶다면, 장고의 모델과 QuerySets, 마이그레이션 쿼리, 객체지향 설계를 잘 이해하고, 동시에 사용하는 데이터베이스의 여러 성능 요소를 결정하는 미묘한 특징들에 대해서 잘 이해해야지만 충분히 규모가 커져도 유지보수가 가능한 좋은 설계가 나오지 않을까 싶다. 뭐 그냥 개인적인 생각이라는 것을 꼬리로 단다.

 

 

[MVT]

  MVT(Model, View, Template) 패턴은, 기존에 많이 쓰이는 용어가 MVC 이니 두 개를 비교해 보면서 살펴보도록 하자, 구글에서 'mvc vs mvt' 로 검색하면 아래의 페이지가 나온다.

https://stackoverflow.com/questions/6621653/django-vs-model-view-controller

 

If you’re familiar with other MVC Web-development frameworks, such as Ruby on Rails, you may consider Django views to be the controllers and Django templates to be the views.

 

  해당 내용 중 위와 같은 내용이 있는데, MVC 의 model 은 장고에서 설명한 구조와 거의 같이 데이터베이스와 매핑되는(사실 어떻게 보면 웹 프로그램이라서 그렇지 원래 ORM 은 굳이 매핑되는 대상이 데이터베이스일 필요는 없는것 같다) 부분을 얘기하고, 컨트롤러(controller)는 장고나 플라스크의 url 요청을 받아 해당되는 함수에 연결해 주는 라우팅 부분을 얘기한다. 그리고 뷰(View)는 실제 모델로부터 데이터를 받아서 보여주는 역활을 한다.

 

  그래서 장고의 경우 위의 그림에서 그렸듯이, 뷰에 URLconfs 기능이 있기 때문에 MVT의 뷰는 컨트롤러 개념을 가지고 있다고 하고(사실 뷰에서 템플릿을 사용안하고 바로 응답값을 줄수도 있기때문에, 템플릿과 뷰 개념을 같이 가지고 있다고 보는것도 맞을듯 싶다), 실제 상으로 템플릿에서 모든 결과를 보여주기 때문에 MVT 의 템플릿은, MVC 의 뷰와 같다고 말하는 것이다. 사실 좀 말장난 같은 요소가 있으며, 기능이 어느 편에 붙었는지에 상관없이 전체적인 기능 요소들은 거의 동일 하므로, 두 개가 사실상 같은 개념이라고 봐도 무방할듯 싶다(언어로 따짐 사투리 관계인데 뭐가 표준어인지는 모르겠다^^;).

 

 

[사전 준비 - Django 설치]

  파이썬 3를 지원하므로 pip 명령어로 설치하면 된다.

c:\Python\code>pip install django
Collecting django
...
Successfully installed django-1.11.4

 

 

 

 

[DB 에서 데이터 불러와 HTML 테이블로 보여주기]

  그럼 본격적으로 장고를 이용해서 앞의 시간에 플라스크로 구현해 봤던, 4교시에서 만들었던 예제를 응용한, MSSQL 데이터베이스에서 데이터를 불러다 HTML 테이블 형태로 웹에 표시하는 부분을 구현해 보기로 하자. ORM 모델을 무시하고, 4교시와 비슷하게 pymssql 모듈을 사용하여 모델을 무시하고 호출하는 것도 가능하겠지만, 그러면 장고의 모델 부분을 살펴 볼수 있는 기회가 없어지기 때문에 ORM 을 최대한 이용하도록 구현을 해보려고 한다.

 

 

[장고의 모델에서 MSSQL 을 지원하게 해주는 모듈 찾기]

  우선 공식 메뉴얼에는 MSSQL 지원에 대해서 언급된 내용이 없으므로, 구글에서 'django mssql' 이라 검색해서, 아래의 페이지를 찾는다.

https://django-mssql.readthedocs.io/en/latest/

 

  스토어드 프로시저(stored procedure)도 호출할 수 있고, 메뉴얼이랑 사용법등이 그런데로 괜찮은듯 한데, 지원하는 버전을 보니, django 1.8 까지 지원한다. 적용하면 돌아갈것 같긴한데 이젠 지원이 끊긴듯 해서 찜찜하다. 그래서 다시 다른 페이지를 찾다보니 아래 레딧 페이지에 'django-pyodbc-azure' 를 쓰라는 조언이 있다.

https://www.reddit.com/r/Python/comments/4iq7zb/django_19_with_mssql_as_backend/

 

  그래서 다시 구글을 검색해, 해당 모듈의 깃허브 페이지로 가니, 장고 1.11 최신버전과 MSSQL 2016 까지 지원해 주는, 현재 활발히 유지되고 있는 모듈이 있다.

https://github.com/michiya/django-pyodbc-azure

 

 

  그럼 설치 가이드에 있는 것처럼, pyodbc 와 django-pyodbc-azure 를 각각 설치해 보자(ODBC 개념은 4교시때 간단히 설명했다)

c:\Python\code>pip install pyodbc
Collecting pyodbc
...
Successfully installed pyodbc-4.0.17


c:\Python\code>pip install django-pyodbc-azure
Collecting django-pyodbc-azure
...

Successfully installed django-pyodbc-azure-1.11.0.0

 

 

 

[이미 만들어진 스키마를 장고 모델로 가져오는 방법 찾기]

  이제 모듈이 설치되었는데, 모듈이 잘 동작하는지 보려면, 장고 모델을 만들어서 마이그레이션으로 MSSQL 데이터베이스와 싱크를 해야될 것 같다. 그런데 다시 supermarket 테이블에 대한 모델을 만들어, 마이그레이션을 시키고 다시 장고의 어드민이나 SQL Management Studio 를 통해 데이터를 넣고 하는게 귀찮기도 하고, 기존 레거시 데이터베이스를 사용하는 경우 모델을 어떻게 구성하나도 궁금하기도 해서 구글에 'django pre existing database' 라고 검색해 아래의 메뉴얼 페이지를 찾았다. 근데 다행히도 설정파일에 연결문자열만 맞춰 놓으면, manage.py 의 inspectdb 명령어를 사용하면, 데이터베이스의 테이블을 자동으로 읽어와서,  해당 되는 테이블 구조에 맞는 model.py 파일을 만들어 준단다. 그럼 조금 뒤에서 모델을 만들때 이 기능을 사용하기로 해본다.

https://docs.djangoproject.com/en/1.11/howto/legacy-databases/


 

[프로젝트 만들기]

  일단 앞의 플라스크와 마찬가지로 가상환경을 쓸 필요는 없을 듯해 virtul env 설정은 생략하고, 앞의 장고걸의 예제들을 따라해 보다보니 설정 문제인지 현재 환경에서(windows 10) 일부 명령어 실행에 에러가 나서, 첨에 소개한 아래의 영문 설명 페이지를 기준으로 virtual env 설정 하는 부분과 migration 하는 부분만 제외하고 적당히 따라해 보기로 했다.

https://scotch.io/tutorials/build-your-first-python-and-django-application

 


  먼저 프로젝트를 생성한다. 아래의 명령어를 치면, c:\python\code 밑에 djangoweb 이라는 프로젝트를 만든다. 뭐랄까 GUI 메뉴를 사용하는 방식은 아니지만, 이제 부터는 비주얼 스튜디오나 이클립스 등으로 프로젝트를 만드는 느낌으로 따라오면 된다.
c:\Python\code>django-admin startproject djangoweb

 

  이렇게 되면 메뉴얼에도 나오지만 아래와 같이 c:\python\code 폴더 아래 djangoweb 이란 폴더가 생성되면서, 아래와 같은 서브폴더와 파일 구조를 가지게 된다.

1
2
3
4
5
6
7
djangoweb/
    manage.py
    djangoweb/
        __init__.py
        settings.py
        urls.py
        wsgi.py
cs

 

  뭐 각각의 파일이 모두 나름의 역활은 있지만, 자세한건 메뉴얼을 보고, 현재 시간에 의미 있는 요소들만 얘기하면,

1) mamage.py - 어플리케이션(프로젝트 안에서 실제 돌아가는 프로그램 모듈 1개를 얘기한다)을 만들거나, 데이터베이스에 모델 정보를 마이그레이션 하거나 하는 등의 여러 프로젝트의 관리에 필요한 기능들을 모아논 장고 전용의 작은 파이썬 프로그램이라고 보면 된다.

2) settings.py - 프로젝트 전반에 필요한 설정들을 저장한 파일. 데이터베이스 연결 문자열, 사용하는 어플리케이션들의 정의, 로케일, 스태틱 파일 경로, wsgi(web server gateway interface - 사용자로부터의 웹 요청을 처리하는 모듈) 지정 등등이 들어가 있다고 보면 된다/

3) urls.py - 1차 라우팅 설정이 들어 있다. 1차라고 하는 이유는 나중에 어플리케이션을 만들면 또 그 안에 라우팅 설정이 또 있다. 들어온 요청을 각 어플리케이션에 분배하는 1차 라우팅 단계라고 보면 된다.

 

 

[프로젝트 안에 어플리케이션 만들기]

  그 담에는 어플리케이션을 만들기 위해서, 'cd' 명령어를 이용하여 새로 만들어진 'djangoweb' 폴더 안으로 이동하여 'supermarket' 테이블에서 조회해서 보여줄 어플리케이션을 생성하는 명령어를 실행 한다(뭐 복잡해 보인다고 생각할지 모르지만, 어차피 장고를 사용하려면 정해져 있는 방식이라서 그대로 따라해야 되는 부분이다).

c:\Python\code>cd djangoweb
c:\Python\code\djangoweb>python manage.py startapp supermarket

 

  이 후에는 내부의 폴더에 supermarket 폴더가 생기며 안에 담긴 파일들은 아래와 같다.

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
djangoweb/
    manage.py
    djangoweb/
        __init__.py
        settings.py
        urls.py
        wsgi.py
    supermarket/
        __init__.py
        admin.py
        apps.py
        migrations/
            __init__.py
        models.py
        tests.py
        views.py
cs

 

  역시 이번 시간에 주로 의미가 있는 파일들만 언급하면,

1) models.py - ORM 모델들이 클래스로 정의되어 있는 파일이다. 현재는 빈 어플리케이션을 만들었기 때문에 안을 보면 내용이 비어있다.

2) views.py - 뷰가 정의되어 있는 파일이다.

-> 위의 생성된 리스트를 보면 좀 이상하다라고 생각할수 있는데, 'templates' 폴더나, 어플리케이션 용 'urls.py' 파일은 안보인다. 뒤에서 보겠지만, 해당 파일은 수동으로 생성해 주어야 된다.

 

 

[프로젝트 파일 설정에 어플리케이션 추가]

  가이드에 나와 있는데로, 프로젝트의 셋팅 파일에, 새로 만든 supermarket 어플리케이션을 인식할 수 있도록 추가해 주자. c:\Python\code\djangoweb\djangoweb\settings.py 파일에서 아래 부분을 추가해준다. 

1
2
3
4
5
6
7
8
9
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'supermarket',
]
cs

 

 

[프로젝트 라우팅에 어플리케이션 쪽 라우팅 추가]

  그 다음엔 '프로젝트의 urls.py' 를 수정해 요청이 들어왔을때, 요청의 처리를 'supermarket 어플리케이션' 쪽에 위임하도록 해보자. c:\Python\code\djangoweb\djangoweb\urls.py 파일에서 아래 import 문과, supermarket.url 항목 부분을 추가해 준다. url 쪽이 플라스크와 하나 다른건 8교시때 배웠던 정규표현식으로 파싱을 한다는 것이다. 플라스크 처럼 사용하려면 '^원하는문자열$'(시작과 끝 사이에 원하는 문자열만 있음) 패턴으로만 url 을 정의해 쓰면 될듯 하다. 아래에서는 ^(시작하자마자) 후에 바로 supermarket.urls 로 라우팅 되기 때문에 결국 supermarket 쪽으로 전체 라우팅 제어권을 넘기는 것과 마찬가지가 된다. 

1
2
3
4
from django.conf.urls import include
urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^', include('supermarket.urls')),
cs

 

 

[어플리케이션 라우팅 파일 만들기]

  자 그럼 이제 프로젝트에서 supermarket 어플리케이션으로 던진 경로를 해석해주는 어플리케이션 라우팅 부분이 있어야 한다. c:\Python\code\djangoweb\supermarket\urls.py 을 메모장에서 만들어서 아래의 내용을 넣어 utf-8 인코딩으로 저장한다(혹시 한글 주석을 달려면 utf-8로 파일이 저장되어야 장고 실행시 에러가 안 난다). 안의 내용은 일단 아직 뭐가 뭔지 잘 모르기 때문에, 위의 튜토리얼의 예제 내용을 그대로 넣어보자. 웹 서버 루트 경로(^$-시작과 끝 사이에 아무 값도 없음)를 호출했을 경우, 앞서 잠시 얘기한 장고에서 제공하는 generic view 를 사용하여 보여주는 형태 이다(뭐 장고가 잘 돌아가는지만 보려 하는거니 상세한 문법은 넘어가기로 하자)

1
2
3
4
5
6
from django.conf.urls import url
from supermarket import views
 
urlpatterns = [
    url(r'^$', views.HomePageView.as_view()),
]
cs

 

 

[어플리케이션 뷰 파일에 샘플 뷰 추가하기]

  자 그럼 이제 어플리케이션에서 해당 되는 라우팅에 대한 샘플 뷰를 만들어 보자. c:\Python\code\djangoweb\supermarket\views.py 파일에서 아래의 내용을 추가한다. 대충 문법을 보면 generic view 중 TemplateView 라는 것을 이용해 index.html 이라는 템플릿 파일을 지정했다.

1
2
3
4
5
6
7
from django.shortcuts import render
from django.views.generic import TemplateView
 
# Create views
class HomePageView(TemplateView):
    def get(self, request, **kwargs):
        return render(request, 'index.html', context=None)
cs

 

 

 

[어플리케이션 뷰 파일에 해당 되는 템플릿 만들기]

  마지막으로 템플릿 파일을 만들면 된다. c:\Python\code\djangoweb\supermarket\ 폴더에 templates 폴더를 생성한다(c:\Python\code\djangoweb\supermarket\templates\). 이후 해당 폴더안에 c:\Python\code\djangoweb\supermarket\templates\index.html 을 만들어 저장한다. 역시 한글 주석을 위해서는 utf-8 인코딩으로 저장하자.

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>장고샘플</title>
    </head>
    <body>
        <h1>동작함</h1>
    </body>
</html>
cs

 

 

[샘플 페이지 실행]
  그럼 다시 c:\Python\code\djangoweb 경로에서 아래의 명령어를 실행해 보자(manage.py 를 실행 시켜야 해서 실행 경로가 맞아야 한다)

c:\Python\code\djangoweb>python manage.py runserver
Performing system checks...
You have 13 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
...

Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.
[05/Aug/2017 17:30:55] "GET / HTTP/1.1" 200 222

 

  뭐 기본 기능에 대한 마이그레이션이 안됬으니 어쩌니 얘기가 나오긴 하지만, 해당 부분은 무시하고 일단 웹페이지를 띄워, http://127.0.0.1:8000/ 을 호출하면 아래와 같이 웹페이지가 동작함을 볼 수 있다.

 

 

  참고로 현재 까지의 파일 구조는 아래와 같다. 추가된 파일과 폴더들은 색으로 표시했다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
djangoweb/
    manage.py
    djangoweb/
        __init__.py
        settings.py
        urls.py
        wsgi.py
    supermarket/
        __init__.py
        admin.py
        apps.py
        migrations/
            __init__.py
        models.py
        tests.py
        templates/
            index.html
        urls.py
        views.py
cs

 

 

[MSSQL 연결 문자열 추가하기]

  MSSQL 연결 문자열을 추가하기 위해(앞의 'django-pyodbc-azure' git 페이지의 문서를 참고했다), 프로젝트 내의 c:\Python\code\djangoweb\djangoweb\settings.py 을 열어 DATABASE 설정 부분을 디폴트인 sqlite3 에서 MSSQL 로 수정한다.  
<원본>

1
2
3
4
5
6
DATABASES = {
    'default': {
        'ENGINE''django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}
cs

 

<수정>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
DATABASES = {
    'default': {
        'ENGINE''sql_server.pyodbc',
        'NAME''mytest',
        'USER''pyuser',
        'PASSWORD''test1234',
        'HOST''localhost',
        'PORT''1433',
 
        'OPTIONS': {
            'driver''ODBC Driver 13 for SQL Server',
        },
    },
}
cs

 

 

[모델 자동으로 만들기]

  앞에서 봤던 레거시 테이블을 자동으로 만드는 inspectdb 명령어를 이용하여 model.py 파일을 만들어 본다.

https://docs.djangoproject.com/en/1.11/howto/legacy-databases/

 

 

  일단 제대로 연결 문자열이 설정됬나 보기 위해서 아래의 명령어를 실행해 본다. 잘은 모르겠지만, 대충 출력된 내용을 보니 해당 데이터베이스안에 기존에 만들어 놓은 'Play' 와 'supermarket' 테이블을 가져오고, 필드 이름들도 정상적으로 파싱해 가져오는거 같다.
c:\Python\code\djangoweb>python manage.py inspectdb
# This is an auto-generated Django model module.
....

class Play(models.Model):
...

    class Meta:
        managed = False
        db_table = 'play'


class Supermarket(models.Model):
    itemno = models.IntegerField(db_column='Itemno', blank=True, null=True)  # Field name made lowercase.
...
    price = models.IntegerField(db_column='Price', blank=True, null=True)  # Field name made lowercase.

    class Meta:
        managed = False
        db_table = 'supermarket'

 

 

 그럼 models.py 파일을 만들어 내기 위해서, 아래의 명령어를 실행한다. inspectdb 를 하여 출력된 내용을 리다이렉션('>') 을 이용해서 기존의 자동 생성된 models.py 파일을 덮어쓰기 한다.

c:\Python\code\djangoweb>python manage.py inspectdb > supermarket\models.py

 

 

  확인을 하고 싶으면 c:\Python\code\djangoweb\supermarket\models.py 파일을 열어보면, 테이블의 구조가 아래와 같이 model 파일로 자동으로 만들어 진다. 내용을 보면 우리가 클래스는 배운적이 없지만 걍 내용을 담는 박스라고 생각하자. 클래스 안에 각 디비의 컬럼들이 정의 되어 있는데, 앞의 소문자로 된 itemno, categoty 등이 나중에 QuerySet 등에서 명시되어 사용되게 된다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# This is an auto-generated Django model module.
# You'll have to do the following manually to clean this up:
#   * Rearrange models' order
#   * Make sure each model has one field with primary_key=True
#   * Make sure each ForeignKey has `on_delete` set to the desired behavior.
#   * Remove `managed = False` lines if you wish to allow Django to create, modify, and delete the table
# Feel free to rename the models, but don't rename db_table values or field names.
from __future__ import unicode_literals
 
from django.db import models
 
 
class Play(models.Model):
    original = models.CharField(max_length=30, blank=True, null=True)
    encrypted = models.CharField(max_length=200, blank=True, null=True)
    decrypted = models.CharField(max_length=30, blank=True, null=True)
 
    class Meta:
        managed = False
        db_table = 'play'
 
 
class Supermarket(models.Model):
    itemno = models.IntegerField(db_column='Itemno', blank=True, null=True)  # Field name made lowercase.
    category = models.CharField(db_column='Category', max_length=20, blank=True, null=True)  # Field name made lowercase.
    foodname = models.CharField(db_column='FoodName', max_length=30, blank=True, null=True)  # Field name made lowercase.
    company = models.CharField(db_column='Company', max_length=20, blank=True, null=True)  # Field name made lowercase.
    price = models.IntegerField(db_column='Price', blank=True, null=True)  # Field name made lowercase.
 
    class Meta:
        managed = False
        db_table = 'supermarket'
 
cs

 

 

[마이그레이션 - 일단 생략]

  굳이 여기서 쓸것도 아니고, 모델과 데이터베이스를 계속 싱크 시킬 것도 아니기 때문에 마이그레이션 명령을 돌리는건 생략 하지만, 저도 에러 땜에 이것저것 찾아보다 한번 돌려봤다. 마이그레이션 명령어를 실행하게 되면 아래와 같이 어드민 관련 테이블이나, 장고에서 지원되는 세션 같은 기능에서 사용하는 기본 테이블들이 데이터베이스안에 만들어지게 된다. 실제로 안돌려도 되니 참고만 하자(뭐 돌려도 상관은 없지만 말이다).

c:\Python\code\djangoweb>python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
  Applying admin.0001_initial... OK
....
  Applying auth.0008_alter_user_username_max_length... OK
  Applying sessions.0001_initial... OK

 

 

[inspectdb 를 통한 마이그레이션 성공 여부 확인 하기]
  실제 페이지를 모두 만들어서, 만들어진 모델의 Queryset 이 정상으로 돌아가는지 확인 하려면, 만드는 도중에 또 다른 실수를 할지 모르므로, 실제 사이트를 만들기 전에 아래의 장고걸 사이트에서 나온데로, 장고쉘 기능을 이용해서(파이썬 쉘과 비슷하다고 보면 된다), 모델이 잘 생성됬는지 확인해 본다.

https://tutorial.djangogirls.org/ko/django_orm/

 

 

일단 쉘을 실행 하자.

c:\Python\code\djangoweb>python manage.py shell

 

  잘 실행되서 '>>>' 프롬프트가 나오면 아래의 명령어를 넣어 슈퍼마켓 모델을 가져오고, 슈퍼마켓 모델에서 모든 내용들을 다 가져와 보자

>>> from supermarket.models import Supermarket
>>> Supermarket.objects.all()
  File "C:\python\lib\site-packages\sql_server\pyodbc\base.py", line 545, in execute
    return self.cursor.execute(sql, params)
django.db.utils.ProgrammingError: ('42S22', "[42S22] [Microsoft][ODBC Driver 13 for SQL Server][SQL Server]\ufffd\ufffd \ufffd\u0338\ufffd 'id'\ufffd\ufffd(\ufffd\ufffd) \ufffd\u07f8\ufffd\ufffd\u01fe\ufffd\ufffd\ufffd\ufffd\u03f4\ufffd. (207) (SQLExecDirectW)")

 

 

  흠 그런데 이상한 SQL 에러가 난다. 근데 에러 파일의 위치를 보면('pyodbc\base.py') 장고쪽은 아니고, pyodbc 쪽 에러같아서 mssql 모듈쪽은 이상없는것 같아 일단 안심은 된다. 어떤 에러인지 찾기 위해서 구글에서 'pyodbc 42S22 error' 라고 검색해서 아래 페이지를 찾아서 보니, 컬럼이 없는 경우의 에러인 것 같다. 그러고 보니 위의 에러에서 'id' 라는 컬럼이름 같은 항목이 보인다. 

https://stackoverflow.com/questions/36202976/pandas-with-pyodbc-nan-error-42s22-error-attribute-qnan-not-found-31

 

 

  그런데 supermarket 테이블에는 'id' 라는 컬럼 자체를 만든적이 없으니 이상하다고 생각하던 중, 예전에 장고 메뉴얼을 읽다가, 마이그레이션 설명 쪽에서 모델에 primary 키(겹치지 않는 유일한 값을 가진 필드 속성)가 없으면, 자동으로 테이블에 'id' 라는 이름으로 자동으로 숫자가 증가하는 primary 키를 만든다는 내용을 스쳐가듯 본 듯한 기억이 났다. 해당 테이블에는 당연히 primary 키 설정이 없었었고, 그래서 장고가 마이그레이션한 테이블처럼 id 필드가 무조건 있으리라 생각하고, 데이터베이스에 select 쿼리를 보낼 때 'select id, ...' 를 하여 에러가 났나보다.

https://docs.djangoproject.com/en/1.11/topics/db/models/

https://docs.djangoproject.com/en/1.11/topics/db/models/#automatic-primary-key-fields

 

 

  그럼 2가지 해결책이 있을 듯 하다. 하나는 현재의 supermarket 테이블에, 자동으로 숫자가 증가되는(auto increment) id 필드를 추가하는 방법이 있고, 다른 하나는 기존 supermarket 테이블을 삭제하고 다시 만들면서, 특정한 필드를 primary 키로 지정하여 생성하는 것이다(개인적으로 두 방식 모두 해봤는데 둘 다 잘 해결되긴 했다). 여기선 간단하게 가기위해서, supermarket 테이블에 id 컬럼을 하나 추가하자. 구글에서 'mssql add column autoincrement' 라고 검색하여 아래 페이지를 찾았다.

https://stackoverflow.com/questions/4862385/sql-server-add-auto-increment-primary-key-to-existing-table

 

     ALTER TABLE dbo.YourTable
     ADD ID INT IDENTITY

 

  아래의 명령어를 4교시에 설명한 MSSQL Managment Studio 를 실행해 쿼리 입력 창에서 mytest db 를 대상으로 실행한다(SSMS 사용법이 기억이 잘 안나면 4교시를 참고한다)

1
2
3
4
5
6
7
8
9
10
use mytest
go
ALTER TABLE dbo.supermarket
   ADD id INT IDENTITY
 
ALTER TABLE dbo.supermarket
   ADD CONSTRAINT PK_supermarket
   PRIMARY KEY(id)
 
select * from dbo.supermarket(nolock)
cs


  명령 실행후 supermarket 테이블을 셀렉트 한 내용을 보면 아래와 같이 숫자가 증가하는 id 값이 추가로 생겼다.

 

 

  이후 다시 장고 쉘에서 같은 명령어를 입력해 보면 정상적으로 결과를 가져온다. 값이 아니라 오브젝트 자체를 가져오기 때문에 안의 내용은 표시되진 않지만, 설치한 mssql 용 장고 모듈이 잘 동작되는 것을 확인 했으니 이제 실제 코드를 만들면 될듯 하다.
>>> from supermarket.models import Supermarket
>>> Supermarket.objects.all()
<QuerySet [<Supermarket: Supermarket object>, <Supermarket: Supermarket object>, <Supermarket: Supermarket object>, <Supermarket: Supermarket object>]>

 

 

  ※ 참고로 기존 테이블에 id 를 추가하는 게 싫어서 테이블을 재생성 하고 싶으면, 아래와 같은 itemno 에 primary 키 속성이 있는 테이블을 만들고, 데이터를 다시 채워 넣음 된다(물론 그전에 'drop table' 명령어로 기존 supermarket 테이블은 지워야 한다)

1
2
3
4
5
6
7
CREATE TABLE [dbo].[supermarket](
    [Itemno] [intNOT NULL PRIMARY KEY,
    [Category] [char](20NULL,
    [FoodName] [char](30NULL,
    [Company] [char](20NULL,
    [Price] [intNULL
)
cs


 

[supermarket 용 url 만들기]

  아까 샘플 url 을 만든것 처럼,  c:\Python\code\djangoweb\supermarket\urls.py 파일에 아래와 같이 supermk url 을 추가 한다.

1
2
3
4
5
6
7
from django.conf.urls import url
from supermarket import views
 
urlpatterns = [
    url(r'^$', views.HomePageView.as_view()),
    url(r'^supermk$', views.supermk),
]
cs

 

 

[supermarket 용 view 만들기]

  마찬가지로 c:\Python\code\djangoweb\supermarket\views.py 파일의 기존 내용의 마지막에, 아래의 supermk 뷰를 추가한다(따라오시다 헷깔리시는 경우는 나중에 전체 소스를 맨뒤의 부록 섹션에 첨부할테니 그걸 참고하시기 바란다). 예전 플라스크에서 구현한 것과 비슷한 구조로 Supermarket 모델에서 모든 값을 가져오고(supers = Supermarket.objects.all()), 이후에 가져온 데이터를(supers)을 super.html 과 같이 랜더링 한다.   

1
2
3
4
5
from .models import Supermarket
 
def supermk(request):
    supers = Supermarket.objects.all()
    return render(request, 'super.html', {'supers': supers})
cs

 

 

[supermarket 용 template 만들기]

  마찬가지로 c:\Python\code\djangoweb\supermarket\templates\super.html 파일을 메모장으로 utf-8 인코딩으로 저장해 만들면서 아래의 코드를 넣는다. 역시 플라스크와 비슷하게

'{{ }}' 와 '{% %}' 를 사용하여 루프를 돌리면서 <td> 태그 안에 각 컬럼 값을 넣게 된다(템플릿 엔진이 같은가도 싶다).

1
2
3
4
5
6
7
8
9
10
11
<table border="1" cellpadding="5" cellspacing="5">
{% for super in supers %}
   <tr>
      <td>{{ super.itemno }}</td>
      <td>{{ super.category }}</td>
      <td>{{ super.foodname }}</td>
      <td>{{ super.company }}</td>
      <td>{{ super.price }}</td>   
   </tr>
{% endfor %}
</table>
cs

 

 

[supermarket 페이지 결과 보기]

  아까 runserver로 웹서버를 실행한 상태로 두었다면 소스의 변경사항이 자동으로 반영되었을 테고(플라스크도 이랬다), 종료했다면 아래의 명령어를 다시 쳐서 실행한다) 

c:\Python\code\djangoweb>python manage.py runserver

 

  이제 브라우저를 띄워 http://127.0.0.1:8000/supermk 를 실행 하면, 아래와 같이 플라스크로 구현한 것과 비슷한 화면을 볼수 있다.

 

  현재까지의 트리는 아래와 같다. 추가된 파일은 색으로 표시했다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
djangoweb/
    manage.py
    djangoweb/
        __init__.py
        settings.py
        urls.py
        wsgi.py
    supermarket/
        __init__.py
        admin.py
        apps.py
        migrations/
            __init__.py
        models.py
        tests.py
        templates/
            index.html
            super.html
        urls.py
        views.py
cs

 

 

 

 

[D3.js 에서 Json 데이터 URL 를 호출해 웹으로 그래프 보여주기]
  이 예제를 만들기 위해 플라스크에 있는 예제를 전환하면서 또 한바탕 헤메긴 했었다. 헤멘 부분이 플라스크와 장고의 차이를 보여주기 때문에 일부는 말로 설명하고 일부는 해결 과정을 보여주려고 한다.

 

 

[d3.js 샘플 페이지 찾아보기]

  일단 플라스크 때 구현한 코드를 장고로 옮기려면 두 가지가 필요하다. 첫번째는 d3.js 를 사용하는 템플릿 코드의 일부를 장고에 맞추어 변경해야 할 것 같고, 또 d3.js 에서 호출했던 json 데이터를 반환하는 URL 을 장고로 구현해야 한다.

 

  구글에서 'django d3.js json' 을 찾아서 아래 페이지를 보니 2개의 힌트가 있다. 첫번째는 d3.js 에서 url 을 호출 하는 방식이다. urlconfs 파일에서 url 을 정의 할때 'name' 속성을 이용해 정의하고, d3.js 에서 'url' 문법을 이용해 경로를 호출 한다. 또 json 데이터를 만들어 내는 play_count_by_month 함수는 QuerySet 을 이용해 결과를 가져와서 JsonResponse 함수를 이용해 json 응답을 생성한다(JsonResponse(list(data), safe=False)). 'safe=False' 옵션이 있는 이유는, JsonResponse는 dictionary 형태의 데이터만 기본적으로 중계하고, 다른 데이터 형일 경우는 'safe=False' 옵션을 넣어야만 형변환 에러가 안난다. 

https://stackoverflow.com/questions/26453916/passing-data-from-django-to-d3

 

 

  좀더 자세히 json 을 반환하는 것을 살펴 보려고 추가적인 페이지도 찾아보았다.  JsonResponse 를 사용하는게 1.11 버전에서는 적절해 보인다.

https://simpleisbetterthancomplex.com/tutorial/2016/07/27/how-to-return-json-encoded-response.html
https://docs.djangoproject.com/en/dev/ref/request-response/#jsonresponse-objects

 

 

[urlconf 파일 수정]

  우선 c:\Python\code\djangoweb\supermarket\urls.py 파일에 아래와 같이 'data' 와 'd3sample' 경로를 추가한다. 'data' url에는 아까 샘플에서 봤듯이 name 속성이 추가됬다.

1
2
3
4
5
6
urlpatterns = [
    url(r'^$', views.HomePageView.as_view()),
    url(r'^supermk$', views.supermk),
    url(r'^data$', views.data, name='data'),
    url(r'^d3sample$', views.d3sample),
]
cs

 

 

[view 파일 수정]

  다음으로 c:\Python\code\djangoweb\supermarket\views.py 파일을 수정한다. data 함수 안의 내용은 거의 플라스크때 json 형태로 만든 샘플 데이터를 최종으로 JsonReponse 에 넘기는 변경만 했다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import json
import numpy as np
from django.http import JsonResponse
def data(request):     
    x = np.array(['2017-07-10''2017-07-11''2017-07-12''2017-07-13''2017-07-14'])
    y = np.array([58.1353.9867.0089.7099.00])
 
    myData = json.dumps([{"date": x[i], "close": y[i]} for i in range(5)])
    return JsonResponse(myData, safe=False)
 
 
 
def d3sample(request):
    return render(request, 'd3sample.html', context=None)
cs

 

 

[templete 파일 생성]

  메모장을 열어서 예전 플라스크 때의 코드를 복사해 json URL 호출 하는 부분만 샘플에서 참고한 내용을 기준으로 변경 후에, 인코딩을 utf-8 로 하여, c:\Python\code\djangoweb\supermarket\templates\d3sample.html 파일로 저장한다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
<!DOCTYPE html>
<meta charset="utf-8">
<style> <!-- 그래프 요소들의 스타일 지정 -->
body { font: 12px Arial;}
path { 
    stroke: steelblue;
    stroke-width: 2;
    fill: none;
}
.axis path,
.axis line {
    fill: none;
    stroke: grey;
    stroke-width: 1;
    shape-rendering: crispEdges;
}
 
</style>
<body>
 
 
<!-- 라이브러리 로딩. 내부에서 돌리려면 다운받아서 static 폴더에서 읽어와야 할듯 -->    
<script src="http://d3js.org/d3.v3.min.js"></script>
 
<script>
// 그래프 좌표 공간 설정
var margin = {top: 30, right: 20, bottom: 30, left: 50},
    width = 600 - margin.left - margin.right,
    height = 270 - margin.top - margin.bottom;
 
// 그래프 범위
var x = d3.time.scale().range([0, width]);
var y = d3.scale.linear().range([height, 0]);
 
// 축 정의
var xAxis = d3.svg.axis().scale(x)
    .orient("bottom").ticks(5);
var yAxis = d3.svg.axis().scale(y)
    .orient("left").ticks(5);
 
// 그래프 선 정의
var valueline = d3.svg.line()
    .x(function(d) { return x(d.date); })
    .y(function(d) { return y(d.close); });
    
// 캔버스 객체 생성
var svg = d3.select("body")
    .append("svg")
        .attr("width", width + margin.left + margin.right)
        .attr("height", height + margin.top + margin.bottom)
    .append("g")
        .attr("transform"
              "translate(" + margin.left + "," + margin.top + ")");
 
// 2017-07-01 식으로 데이터를 해석하게 지정함
var parseDate = d3.time.format("%Y-%m-%d").parse;
 
// 전달 받은 데이터를 이용해서 그래프를 그린다.
var callback = function (data) {
 
    data.forEach(function(d) {
        d.date = parseDate(d.date);
        d.close = +d.close;
    });
 
    // 실데이터에 맞춰 그래프 범위 지정
    x.domain(d3.extent(data, function(d) { return d.date; }));
    y.domain([0, d3.max(data, function(d) { return d.close; })]);
 
    // 선 그리기.
    svg.append("path")
        .attr("class""line")
        .attr("d", valueline(data));
 
    // x축 그리기?
    svg.append("g")
        .attr("class""x axis")
        .attr("transform""translate(0," + height + ")")
        .call(xAxis);
 
    // y 축 그리기?
    svg.append("g")
        .attr("class""y axis")
        .call(yAxis);
 
};
 
 
// django 에서 만든 http://127.0.0.1/data 를 호출하여 json 데이터를 가져와 callback 함수 호출
d3.json("{% url "data" %}",  callback);
 
</script>
</body>
cs

 

 

[에러를 만나다]

  그런데 브라우저를 열어 http://127.0.0.1:8000/d3sample 을 호출하여 보니 그래프가 표시되지 않는다. 피들러를 띄워 확인해 보니 d3sample url 이 호출되고, 이후 /data url 까지 잘 호출한다.

 

 

 

  그래서 json 형태로 넘어오는 데이터를 확인하기 위해 브라우저에서 http://127.0.0.1:8000/data url 을 호출해보니, 파일을 저장하라는 창이 뜬다. 뭔가 이상하긴 하다. 해당 json 파일을 c:\python\code 폴더에 저장해서 메모장으로 열어 보니, 아래와 같이 (") 문자앞에 역슬래시(\) 가 들어가 이스케이프 처리가 되어있다(아마 여기서는 안전하지 못한 코드의 경우 JsonResponse 메써드가 방어를 하는 차원인거 같긴하다). 

1
2
3
4
5
"[{\"close\": 58.13, \"date\": \"2017-07-10\"}, 
{\"close\": 53.98, \"date\": \"2017-07-11\"}, 
{\"close\": 67.0, \"date\": \"2017-07-12\"}, 
{\"close\": 89.7, \"date\": \"2017-07-13\"}, 
{\"close\": 99.0, \"date\": \"2017-07-14\"}]"
cs

 

 

  그래서 브라우저에서 F12키를 눌러서, 예전에 배운 IE 개발자 도구를 열은 후, 'F5'키를 눌러서 http://127.0.0.1:8000/data 페이지를 다시 로딩 한다.  개발자 도구창에서 '콘솔' 탭을 클릭한 후 동그란 !표 아이콘을 클릭해 보니, 아래와 같이 개체가 상이하여 루프 메서드를 실행할 수 없다는 자바스크립트 에러가 난다.

 

 

  에러 메시지 밑의 라인 링크('d3smaple (75,5)')를 클릭하면, 해당되는 소스 라인에 가게 되는데, 아무래도 json 데이터를 받아온 내용이 담겨있는 data 변수가 기존 플라스크 때와는 다르게 json 형식으로 잘 해석이 안 된것 같다(아마 위의 \" 이스케이프 처리 때문인 것 같긴하다)

 

 

  그래서 자바스크립트 안에 'document.write(data)' 구문을 넣어 data 변수를 뿌려보니 아래와 같이 파일로 저장했을때는 보였던 escape 문자(\) 는 d3.json 함수를 통과하면서 없어진듯 하고, 정상적으로 뿌려짐을 볼수 있다(해당 디버그 코드들은 최종 샘플에 주석처리해 놓았으니 참고만...). 문법적으로 json 형태는 맞는거 같아, 그렇다면 왠지 이스케이프 문자를 d3.json 이 처리는 해줬지만, json 데이터라고 생각을 안해 변환을 안해 준거 같다는 생각이 든다. 

1
2
// d3.json 으로 받아온 값을 담고 있는 data 변수의 값을 찍어봄
document.write(data)
cs

 

 

 

  그래서 data 변수의 타입을 확인해 보기로 했다. 구글에서 'javascript print typeof' 로 검색하니, 아래의 페이지에서 type 을 문자열로 반환하는 함수를 얻게 된다.

https://stackoverflow.com/questions/7390426/better-way-to-get-type-of-a-javascript-variable

1
2
3
4
5
6
7
8
// 데이터 타입을 string 형태로 반환하는 함수
function typeOf (obj) {
  return {}.toString.call(obj).split(' ')[1].slice(0-1).toLowerCase();
}
 
// 에러추적 2 : data 의 형태를 뿌려보니,string 이라고 나옴
var datatype = typeOf(data);
document.write(datatype);
cs

 

 

  데이터 타입을 확인하니 'string' 이라고 나온다.

 

 

   JsonResponse 에서 " 문자를 이스케이프 처리하는게 확실히 문제는 문제인거 같다. 처음에는 json object 를 dict 로 변환해 볼까를 이리저리 궁리했지만 샘플의 json object 는 2차원이고, dict 는 1차원이라서 뭔가 억지로 변환하여 문제를 풀면 d3sample.html 코드도 이것저것 바꿔줘야 할 것 같아서 망설이다가 data 변수의 타입이 string 이지만 실제 내용은 json 데이터 형태인건 맞으니, string 을 json 객체로 변환하면 결과대로 나오지 않을까 하는 생각이 들었다. 구글에 'javascript string to json' 이라고 찾아서, 아래의 페이지를 찾는다. 해당 코드를 이용하여 변환 후에, 데이터 타입을 화면에 뿌려본다.

http://jekalmin.tistory.com/entry/string%EC%9D%84-json%EC%9C%BC%EB%A1%9C-json%EC%9D%84-string%EC%9C%BC%EB%A1%9C-%EB%B3%80%ED%99%98

1
2
3
4
5
// string 을 json 형태로 변환하여 뿌려보니, list 라고 나옴
// 확인 후 변환 코드는 남겨두어, 넘어온 data 값을 변환하여 사용함.
data = JSON.parse(data);
var datatype = typeOf(data);
document.write(datatype);
cs

 

  위와 같이 변환한 후 데이터 형을 다시 확인해보니 array 라고 나온다. 지금 보니 이래서 구글에의 샘플 페이지의 view 함수의 JsonResponse 호출부분에서 데이터를  list 형태로 바꾼건가 싶기도 하다. 뭐 여튼 변환하는 코드만 주석 처리 하지 않고 남겨두어 사용해 본다.

 

 

   최종 코드는 아래와 같고, 디버깅 코드 등으로 변경한 내용은 다른 색으로 표시해 놓았다. 브라우저의 개발자 도구를 잘쓰면 이렇게 덕지덕지 덜 넣어도 될듯하긴 하다. 보시면 결국 넘어온 데이터를 'data = JSON.parse(data);' 를 이용해 json array 형태로 바꾸어줌 간단히 해결나는 상황이다(밑의 typeof 함수는 디버깅 용이다). 해당 내용으로 d3sample.html 내용을 수정해 준다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
<!DOCTYPE html>
<meta charset="utf-8">
<style> <!-- 그래프 요소들의 스타일 지정 -->
body { font: 12px Arial;}
path { 
    stroke: steelblue;
    stroke-width: 2;
    fill: none;
}
.axis path,
.axis line {
    fill: none;
    stroke: grey;
    stroke-width: 1;
    shape-rendering: crispEdges;
}
 
</style>
<body>
 
 
<!-- 라이브러리 로딩. 내부에서 돌리려면 다운받아서 static 폴더에서 읽어와야 할듯 -->    
<script src="http://d3js.org/d3.v3.min.js"></script>
 
<script>
// 그래프 좌표 공간 설정
var margin = {top: 30, right: 20, bottom: 30, left: 50},
    width = 600 - margin.left - margin.right,
    height = 270 - margin.top - margin.bottom;
 
// 그래프 범위
var x = d3.time.scale().range([0, width]);
var y = d3.scale.linear().range([height, 0]);
 
// 축 정의
var xAxis = d3.svg.axis().scale(x)
    .orient("bottom").ticks(5);
var yAxis = d3.svg.axis().scale(y)
    .orient("left").ticks(5);
 
// 그래프 선 정의
var valueline = d3.svg.line()
    .x(function(d) { return x(d.date); })
    .y(function(d) { return y(d.close); });
    
// 캔버스 객체 생성
var svg = d3.select("body")
    .append("svg")
        .attr("width", width + margin.left + margin.right)
        .attr("height", height + margin.top + margin.bottom)
    .append("g")
        .attr("transform"
              "translate(" + margin.left + "," + margin.top + ")");
 
// 2017-07-01 식으로 데이터를 해석하게 지정함
var parseDate = d3.time.format("%Y-%m-%d").parse;
 
// 전달 받은 데이터를 이용해서 그래프를 그린다.
var callback = function (data) {
 
    // 에러추적 1 : 데이터를 뿌려보니 \ 문자도 제거되고, 정상적인 json 문법인거 같음
    // document.write(data)
    
    // 에러추적 2 : data 의 형태를 뿌려보니,string 이라고 나옴
    // var datatype = typeOf(data);
    // document.write(datatype);
 
    // string 을 json 형태로 변환하여 뿌려보니, list 라고 나옴
    // 확인 후 변환 코드는 남겨 넘어온 data 값을 변환하여 사용함.
     data = JSON.parse(data);
    // var datatype = typeOf(data);
    // document.write(datatype);
 
    // 첨에 이런 에러가 남 (개체가 'forEach' 속성이나 메서드를 지원하지 않습니다.)
    data.forEach(function(d) {
        d.date = parseDate(d.date);
        d.close = +d.close;
    });
 
    // 실데이터에 맞춰 그래프 범위 지정
    x.domain(d3.extent(data, function(d) { return d.date; }));
    y.domain([0, d3.max(data, function(d) { return d.close; })]);
 
    // 선 그리기.
    svg.append("path")
        .attr("class""line")
        .attr("d", valueline(data));
 
    // x축 그리기?
    svg.append("g")
        .attr("class""x axis")
        .attr("transform""translate(0," + height + ")")
        .call(xAxis);
 
    // y 축 그리기?
    svg.append("g")
        .attr("class""y axis")
        .call(yAxis);
 
};
 
 
// flask 에서 만든 http://127.0.0.1/data 를 호출하여 json 데이터를 가져와 callback 함수 호출
d3.json("{% url "data" %}",  callback);
 
// 데이터 타입을 string 형태로 반환하는 함수
function typeOf (obj) {
  return {}.toString.call(obj).split(' ')[1].slice(0-1).toLowerCase();
}
 
</script>
</body>
cs

 

 

  이후 브라우저를 열어 http://127.0.0.1:8000/d3sample 를 호출하면 플라스크 때와 같이 그래프가 정상적으로 출력된다.

 

 

 참고로 앞의 코드에서 d3sample.html 이 아닌, 뷰 파일 쪽을 고치려면, 딕션너리 데이터가 리스트 안에 담긴 형태로 만들어서, JsonResponse 함수로 전달하면 됩니다.

 

 

[마무리 하면서]

  이렇게 해서 플라스크에서 구현했던 예제들을 장고에서 구현해 보면서, 장고란 프레임워크의 여러가지 면들에 대해 살펴보았다(덤으로 javascript 도 약간 배웠다). 생각보다는 장고는 DB에 마이그레이션도 해주고, 어드민도 자동 생성하는 듯, 마법사 모드에 가까운 부분도 있는것 같으며, 확실히 플라스크 보다는 체계적인 구조를 갖춘 듯 하다. 좀 뭐랄까 사용하는 규칙이 엄격하다고 할까... 장고를 사용하는게 괜찮아 보인다면 이제부터 메뉴얼을 찬찬히 훝어보거나, 관련 책을 하나 사서 보거나, 구글을 검색하면서 필요한 부분을 찾아 공부하면 될듯 싶다. 3섹션동안 웹 쪽을 다루긴 했지만 파이썬 기능에 초점을 두었기 때문에, 웹 프로그래밍 책들에서 많이 다루는 게시판이나, 로그인, 파일 업로드, CSS 등으로 화면 꾸미기와 같은 주제들은 다루지 않았기 때문에, 초보시라면 공부해야 될 부분들은 앞으로도 많을 것이다. 여기서는 legacy web, flask, django 세 가지가 각각 나름대로 비슷하면서도 다른 배경을 가지고 살아왔다는 것을 이해 한다면 성공일 것 같다. 이렇게 해서 legacy web 에서 시작하여 flask 와 django 를 살펴본 파이썬의 웹 프로그래밍에 대한 여정을 마치려 한다. 나름 처음 생각 했던것 보다는 잘 정리된 듯도 싶다 --;

 

 

 

[부록]

  전체 코드의 트리는 아래와 같고 소스들은 따라하시다 꼬일때를 대비해서, 참고하시라고 압축 파일로 첨부했다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
djangoweb/
    manage.py
    djangoweb/
        __init__.py
        settings.py
        urls.py
        wsgi.py
    supermarket/
        __init__.py
        admin.py
        apps.py
        migrations/
            __init__.py
        models.py
        tests.py
        templates/
            index.html
            super.html
            d3sample.html
        urls.py
        views.py
cs

 

 

[첨부 zip 파일]

djangoweb.zip

 

 

 

 

 

2017.8.7 by 자유로운설탕
cs

 

  

 

 

 

 

 

posted by 자유로운설탕
2017. 7. 20. 15:44 프로그래밍

  이번 시간에는 파이썬 웹 프레임워크로 많이 쓰이는 경량화 웹 프레임워크라고 불리우는 플라스크(flask) 를 살펴보는 시간을 가지려고 한다. 메뉴얼을 기반으로 전체적인 플라스크의 구조에 대해서 살펴보고, 지난 legacy web 시간에 구현했었던 DB의 테이블을 조회해 HTML 테이블로 표현하는 예제, Javascript 계의 matplotlib 이라 할수 있는 D3.js 와 결합하여 json 데이터를 가져다가 그래프를 그려주는 예제, 마지막으로 matplotlib 을 이용해서 파이썬 코드 기반의 그래프를 생성하여 HTML 문서에 포함 시키는 총 예제 3가지를 소개하려고 한다. 

 

 

 

[목차]

0. 왜 파이썬 공부에 구글을 이용하는게 좋은가?

1. 언어를 바라보는 방법. 파이썬을 어떻게 바라봐야 할까?

2. 파이썬 설치와 환경, 버전 선택 하기의 이유.

3. 만들고자 하는 기능을 모르는 조각으로 나눠 조사해 보기

4. 데이터 베이스에서 내용 가져와 출력하기

5. 암호화 모듈을 이용해 암복호화 해보기

6. 퍼즐 조각들을 합쳐보기

7. 엑셀 파일 사용해 보기 -> 부록 : fuction 을 이용해서, 코드 정리해 보기

8. 정규표현식을 왜 사용해야 할까? 언어속의 미니 언어 정규표현식 살펴보기

9. 입력과 결과를 GUI 화면과 연결해 보기

10. Whois API 이용해 보기

11. 웹페이지 호출해 내용 파싱 하기(BeautifulSoup 그리고 한계)

12. 자동화 - 웹 자동화(with Selenium)

13. 자동화 - 윈도우즈 GUI 자동화(with pywinauto)

14. 자동화 - 작업 자동화

15. 수학 라이브러리 살펴보기

16. 그래픽 라이브러리 살펴보기

17. 머신러닝에서의 파이썬의 역활

18. 웹 프로그래밍 - Legacy Web

19. 웹 프로그래밍 - Flask 살펴보기(feat. d3.js)

20. 웹 프로그래밍 - Django 살펴보기

21. 정리 - 이런저런 이야기

 

 

 

 

[들어가면서]

  우선 시작하기 전에 얘기하고 싶은 것은, 프레임워크란 부분에 너무 많은 기대는 하지는 말라고 하고 싶다. 프레임워크란 해당 분야의 좋은 관행(best practice)들과, 지원용 라이브러리들을 모아 놓은 범용적인 틀 같은 것이여서 개발에 대한 방법론, 자주 쓰는 기능 라이브러리, 안전한 설계 이슈 등 많은 부분에 도움을 줄 수 있겠지만, 그 범용적인 부분이 현재 자기가 만들고자 하는 특정한 프로그램에 적합 하다는 보장은 못해주며, 자신이 만드려는 프로그램에 맞게 커스터마이즈 하기 위해서는 많은 노력이 추가로 들어가게 될 것 이다. 마치 집을 짓는데, 기본 재료와 반듯한 땅, 숙련된 기술공을 제공해 준다 해도, 해당 지원 부분이 내가 원하는 집을 짓는데 필요한 디테일의 전부는 절대 못 된다는 것이다. 해당 프레임워크가 지원하는 언어를 충분한 깊이로 이해하고, 해당 분야(웹, 디비, 시스템, 빅데이터 등등)에 대해 충분히 이해한 상태에서 사용해야, 사용하려는 프레임워크에 대한 이해도도 깊어지고, 적절하게 스스로나 해당 프레임워크가 의도한 대로 사용이 가능할 것이다. 추가로 비슷하거나 상이한 다른 프레임워크들에 대해서도 핵심을 잘 이해하고 있다면, 선택한 프레임워크의 장, 단점에 대해서 객관적으로 바라보고 선택에 대한 트레이드오프를 잘 따져볼 수 있을 것 같다

 

 

[한글 문서 여부]

  Flask 에 대한 메뉴얼 문서는 구글을 찾다보면 한글문서가 있긴 한데, 현재 0.12 버전이 나온상태에서, 파이썬 3 지원이 안됬던 0.11 개발기간 당시의 버전 문서의 번역본이고, 번역이 완전히 다 되진 않은 상태라서, 처음 접할때 전체적인 맥락에 대한 살펴보기 용으로만 쓰라고 권하고 싶다. 또 구글에서 검색하다보면 나오는 간단한 예제들을 구현해 놓은 여러 한글 블로그들도 많으니, 본격적으로 영문 문서를 보기 전에 미리 사전 지식을 쌓아놓으면 좀 더 읽기 수월해 질 듯 하다.

http://flask-docs-kr.readthedocs.io/ko/latest/index.html

 

 

[Flask vs Django]

  그럼 우선 파이썬에서 보통 많이 얘기되는 두 개의 웹 프레임워크를 한번 비교해 보자. 구글에서 'flask vs django' 라고 넣고 검색해 보면 아래와 같은 여러 비교한 페이지들이 나온다.

 

[플라스크와 장고 비교 글 - 이 사람은 플라스크를 더 편하다고 생각한다.] 

https://www.codementor.io/garethdwyer/flask-vs-django-why-flask-might-be-better-4xs7mdf8v

[플라스크, 장고, 피라미드 비교 - 밑에 것은 번역 글]

https://www.airpair.com/python/posts/django-flask-pyramid

http://kmc5500.tistory.com/162

 

  대충 요약해 보면, flask 는 웹 프레임워크가 필요한 최소한의 기능만 제공 하고, 나머지는 외부 모듈이나, 개발자에게 구현을 하도록 유도하는 편이라고 하고, Django 는 중, 대규모의 사이트를 목적으로 만들어져 일반적인 웹사이트 개발에 필요한 풀 패키지를 지원하는 편이라고 한다. 글 들에서도 나오지만 판단은 각자 스스로 하는게 맞을 것 같고, 이번 시간에는 flask 가 표방하는 최소한의 웹 프레임워크가 어떤 의미인지 살펴보도록 하겠다.

 

 

[Flask Documentaton 보기]

  구글에 'flask documentation' 이라고 검색하면 아래의 최신 0.12 버전의 프레임워크에 대한 설명이 있는 링크를 얻을 수 있다.

http://flask.pocoo.org/docs/0.12/

 

  예전 수학 라이브러리 볼때 처럼, 쭉 목차를 살펴 보면, 먼저 'User Guide' 에는 Installation(설치) 부분이 있고(개인적으로 여러 버전을 운영할 일은 없을 듯 해서 vitualenv는 고려하지 않았다),  Quick Start 섹션에 기본적으로 기본적인 flask 웹 기동이나, url 을 파싱해 해당되는 파이썬 함수에 전달하는 라우팅, css 나 img 같은 정적인 파일에 접근하는 방법, 템플릿을 꾸며서 원하는 웹 화면을 보여주는 방법, request 된 데이터를 받아 처리하는 방법 등이 설명되어 있다. 그리고 Tutorial 에는 세팅 방법과, DB 를 조회해 화면에 보여주는 예제가 있는 것 같고, Templates 에는 여러가지 템플릿 제어 방법에 대한 설명, 한참 아래의 Patterns for Flask 에는 여러가지 플라스크로 웹을 구성하는 권장 기법들이 있다. 그 밑의 'API Reference''Additional Notes' 는 위의 사용자 가이드가 익숙해 졌을 때 추가적인 정보를 보기 위해 살펴보는게 맞을 거 같다(개인적으로 flask 를 둘러보면서 예제를 구현해 보려고 했을때 진행했던 흐름이다).

 

 

[Routing]

  실용적으로 보이는 프레임워크긴 하지만, 실제 웹코드 구현에 들어가기 전에 legacy 하고 차이가 나는 부분에 대해서 설명 후에 들어가려 한다(혹시 웹 프로그래밍에 대해서 익숙하지 않은 상태라면 앞 교시인 Legacy Web 파트를 꼭 읽어 보시고 오길 권한다). 먼저 routing 와 static files 에 대해서 생각해보자. 현재 가상현실, 증강현실이 주목받는 세상이지만, 컴퓨터 자체가 어느 정도는 해당 개념들의 표본이 아닌가 싶다. 컴퓨터 내의 파일이라는 개념에 대해 우리는 동영상, 그림파일, 문서파일, 음악파일 등을 자연스럽게 사용해와서 실체화된 것이라고 생각하지만, 실제로는 메모리나 디스크 상에 구분된 숫자에 불과하다는 사실은 맞을 것이다. 그 정보를 운영체제가 해석을 해서, 폴더나 파일로 구분하여 인식하고, 응용 프로그램이 해당 데이터를 전달 받아 우리가 볼 수 있도록 화면에 출력하거나, 소리로 출력하여 노래를 듣거나, 문서를 보거나 하는 것일 것이다. 일례로 오피스가 설치되 있지 않은 컴퓨터라면 doc, xls 파일은 아무 의미가 없는 파일일 것이다. 예컨데 특정한 처리를 해주는 로직을 만나야지만 우리가 파일이라고 믿는 것들이 의미가 생긴다는 것이다.

 

  비슷하게 우리가 legacy web 에서 'test.asp', 'hey.php' 같은 파일들이, 웹 프로그램 확장자를 가진 파일이여서, IIS, Apache 같은 웹 서버에서 해당 파일이 실행이 된다고 믿어왔던 것도 어떤 측면에서는 관념적인 것에 불과할지도 모른다. 역으로 얘기하면 어떤 이름과 확장자를 가진 URL 의 호출이 웹서버에 주어질 때, 그것을 어떻게 해석하냐는 폴더와 파일이라는 물리적인 요소에 달려있는게 아니라, 웹 서버가 요청을 어떻게 해석을 하냐는 논리적인 요소에 달려있다고 볼수 있다 

 

  이 부분이 최근의 웹 서버 모듈에서 볼수 있는 'routing' 이라는 개념이다. 클라이언트가 url 주소에 hey.php 를 요청하든 hey 를 요청하든, 웹 서버 모듈만 지원을 한다면, 해당 파일이라는 형태에 국한되지 않고 해석을 해,  파일 경로를 찾은 후 처리하는 대신에 특정한 로직(함수)으로 직접 전달할 수 있다. 즉 파일과 디렉토리 기반으로 움직이던 웹 서버의 동작을, URL 경로 규칙에 기반한 함수와의 연결 로직으로 추상화(혹은 일반화) 시켰다고 봐도 될 듯 싶다(그래서 개인적으로 확장자를 가진 웹에만 익숙하다가, 어느날 확장자가 없는 처음 웹을 만났을 때 웹서버 내에서 url 에 해당하는 실제 파일의 경로를 찾을 수가 없어서 당황했었던 기억이 난다). 결국 이렇게 되면 기존 웹에서 의미가 있었던 디렉토리와 파일명, 확장자는 모두 의미가 없는 껍데기가 된다. 이러한 URL 과 내부 기능과의 직접적인 연결을 하는 방식을, 네트워크에서 패킷을 적절한 경로로 안내하는 라우터의 역활을 차용해서 routing 이라고 명명한 것 같다.

 

 

[Static Files]

  같은 맥락에서 보면 이제 프로그램 확장자 파일이 아닌 css, jpeg 같은 정적인 파일에 대해서도 기존 웹과 같이 url 기반의 디렉토리와 파일이름 경로로 접근하기는 좀 힘들어 지게 됬다. 왜냐면 이젠 웹서버 모듈은 기존 웹과 같이 웹루트 폴더 기준의 트리 구조로 된게 아니라, 기본으로 라우팅이 되는 논리적 레벨의 매핑이기 때문이다. 그러한 논리적 레벨의 매핑을 특정 디렉토리를 기반으로한 물리적(이것도 앞에서 얘기했듯이 넓게보면 가상이긴 하지만) 매핑으로 잠시 변환하는 기능이, static files 이라고 보면 될것 같다. (flask 에서는 dynamic web, static web 이라는 표현으로 설명한다)

 

 

[Rendering Templates]

  예제를 구현하기 필요한 마지막 개념은 Rendering Templates 섹션이다. 사실 template 란건 특정 UI 의 재사용을 위한 개념으로 많이 사용되기는 하는데, flask 에서는 꼭 재사용이 아니더라도 UI 를 표현하기 위해선 하나의 템플릿(UI 를 표현한다는 측면과 재사용 적인 측면의 의미를 동시에 가졌다고 볼 수 있을 듯 싶다)을 사용해야 된다고 생각하면 된다. dynamic web 인 flask 의 어플리케이션 모듈 쪽에서 표현에 사용할 데이터를 준비한 후, 해당 데이터를 지정한 템플릿에 연관해서 처리를 한다. 이러한 구조가 HTML 과 어플리케이션 로직을 분리하기 위해서 라고 볼 수도 있겠지만, 사실 웹 자체의 베이스가 동적이여서 페이지 개념이 없어졌기 때문에, 최종적으로 페이지를 동적 프로그램 코드내에서 생성하게 되면, 이번엔 반대로 프로그램 코드에 UI코드가 섞이게 되어 의미가 없게 되니, 어쩔수 없이 최종으로 UI 를 표시하는 부분을 템플릿이라는 개념으로 다시 떼어낸 것도 같다. 마치 Javascript 와 HTML 을 event 속성이 연결하였듯이, 템플릿 또한 순수한 html 코드는 아니고, 앞의 ASP 의 <% %> 코드와 비슷하게, 전달된 데이터들을 템플릿 사이에 적절히 끼워주는 방식으로 구현된다(해당 작업을 렌더링이라고 표현하는 듯 하다).

 

 

[사전 준비 - Flask 설치]

파이썬 3를 지원하므로 pip 명령어로 설치하면 된다.

c:\Python\flaskweb>pip install flask

Collecting flask
....
Installing collected packages: flask
Successfully installed flask-0.12.2

 

 

  c:\python 폴더에 flaskweb 폴더(이 폴더 이름은 다른 아무 이름이나 된다)를 만든다. 다시 flaskweb 폴더내에 templates 폴더(이 폴더 이름은 약속된 이름이기 때문에 꼭 이 이름으로 만들어야 된다)를 만든다.

 

 

 

 

[DB 에서 데이터 불러와 HTML 테이블로 보여주기]

  첫번째 예제로 4교시에서 만들었던 예제를 응용해, MSSQL Server 에서 데이터를 불러 HTML 테이블로 출력하는 예제를 보이려고 한다. 여기에서 기본적인 routing, template 를 다루는 코드가 나오니 예제로 개념을 익히면 된다. 18교시와 마찬가지로 4교시에 만든 supermarket 테이블과 python 코드를 그대로 재사용 하려고 한다(4교시를 안해보셔서 환경이 없는 분들은 MSSQL Server 설치와 테이블 생성을 하시고 오셔야 한다).

 

 

[App Code 구현]

  해당 방식 구현을 위해 구글에서 'flask db to html table' 이라고 검색해서, 아래 3개의 페이지를 얻었다.

[DB 데이터를 템플릿에 넘기는 방식을 볼수 있음 - 템플릿 출력 부분 코드가 명확히 표현되 있진 않다.] 

https://stackoverflow.com/questions/29525758/data-from-sqlite-to-an-html-table-in-a-flask-page

[템플릿 출력 하는 코드가 명확히 나옴]
https://stackoverflow.com/questions/42040379/creating-an-html-table-with-database-values-in-flask

[메인 페이지에 SQL 초기화와 조회 관련 코드를 어떻게 배치할까에 대한 힌트]
https://stackoverflow.com/questions/38540256/flask-python-mysql-how-to-pass-selected-data-though-a-for-loop-and-return-it

 

 

  앞의 사용자 메뉴얼의 튜토리얼을 읽어 전체적인 분위기를 파악 후, 아래 3개의 코드들을 참조하고, 4교시에 만들어 놓았던 파이썬 코드를 결합시킨 최종 코드는 아래와 같다. 간단히 설명하면, flask 는 웹서버와 웹어플리케이션 기능을 같이 실행하는데(운영 단계에선 아파치 등과 연계하는게 맞을 듯은 싶다), 127.0.0.1 의 포트 5000번으로 서비스가 된다. 'sqltable' 경로가 호출되어 'showsql()' 함수가 시작이 되면, 지정된 SQL 문을 실행하여, templates 폴더에 있는(flask 의 몇 안되는 미리 약속되 있는 폴더이다) 'myweb.html' 파일과 DB 에서 가져온 전체 결과값(fetchall)을 지닌 'rows' 변수를 (아마 list)를 이용해서 렌더링 하여(rendering) 결과를 표시한다. 아래 코드는 결과를 표현해주는 템플릿 코드가 아직 만들어지지 않았기 때문에 아직은 반쪽의 코드라고 볼 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from flask import Flask
from flask import render_template
import pymssql
 
# MSSQL 연결 하기 
conn = pymssql.connect(server='localhost', user='pyuser', password='test1234', database='mytest')
cursor = conn.cursor()
 
# flask web 실행
app = Flask(__name__)
 
# 'sqltable' 이라는 URL 인자를 'showsql' 이라는 함수로 연결한다. 
@app.route("/sqltable")
def showsql():
    # SQL 문을 실행하여 supermarket 테이블에서 데이터를 가져온다.
    cursor.execute('SELECT Itemno, Category, FoodName, Company, Price FROM supermarket(nolock);')
    # 가져온 모든 데이터를 mytable.html 파일과 함께 랜더링 하여 표현한다.
    return render_template('myweb.html', rows = cursor.fetchall())
 
# 이 웹서버는 127.0.0.1 주소를 가지면 포트 5000번에 동작하며, 에러를 자세히 표시한다. 
if __name__ == "__main__":
    app.run(host='127.0.0.1',port=5000,debug=True)
cs

 

  위의 코드를 메모장에 넣고, 파일형식을 '모든파일', 인코딩을 'utf-8' 로 선택하여, c:\python\flaskweb 폴더에 myweb.py 로 저장한다.

 

 

[Templates Code 구현]

  다음은 myweb.html(이름은 호출하는 .py 파일의 이름과 달라도 무방하다) 템플릿 파일이다. 뭔가 전 시간의 ASP 코드 흐름과 비슷하지 않나 싶다. '<%' 대신 '{%' 로 파이썬 코드임을 표시하고, 안의 문법이 'vbscript' 대신 '파이썬' 문법인 차이같다.  사용자 메뉴얼을 보면 해당 템플릿을 표현하는 방식은 외부 모듈인 'Jinja2' 라는 템플릿 모듈을 차용했다라고 나온다. 대충 로직을 살펴 보면 테이블 외형을 뿌려주고('<table>태그') SQL 조회 결과를 한 줄씩 루프를 돌면서(for row in rows), '<tr>' 태그를 뿌려주고, 그 안에서 다시 해당 줄의 컬럼들을 선택하며 돌면서(for data in row), '<td>' 태그안에 그 값('{{data}}')을 넣어준다. 해당 부분은 ASP 코드랑도 비슷하고, 예전의 7교시 엑셀 시간에 배웠던 엑셀 파일로의 출력과도 비슷한 루프 구조를 가진다.

1
2
3
4
5
6
7
8
9
<table border="1" cellpadding="5" cellspacing="5">
{% for row in rows %}
    <tr>
    {% for data in row %}
        <td>{{ data }}</td>
    {% endfor %}
    </tr>
{% endfor %}
</table>
cs

 

  해당 코드를 역시 메모장에 복사하여, c:\python\flaskweb\templates 폴더에 myweb.html 로 저장한다(html 에 한글을 넣으려면 utf-8 인코딩으로 저장해야 에러가 안난다.)

 

 

  그럼 모든 코드가 구현되었고 c:\python\flaskweb\ 으로 이동하여, 아래와 같이 myweb.py 파일을 실행 한다.

c:\Python\flaskweb>python myweb.py
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 288-594-455
 * Running on
http://127.0.0.1:5000/ (Press CTRL+C to quit) 

 

  이후 브라우저를 열어 http://127.0.0.1:5000/sqltable 이라고 치면, myweb.py 상에서 sqltable 에 해당하는 showsql() 함수를 실행하여(일부러 독립된 요소라는 것을 보여주기 위해 이름들을 다 다르게 했다), 조회한 데이터를 templates 폴더에 있는 myweb.html 와 함께 랜더링 하여 아래와 같이 html 테이블을 보여준다(지금은 자연스럽게 보이지만 여기까지 올때까지의 몇 번의 시행착오 과정은 생략했다). 이 예제를 통해 기존 파이썬 로직들이 flask 라는 웹 서버 겸, 어플리케이션 모듈 프레임워크를 통해서 웹형식으로 표현되게된 흐름(flow)을 캐치하셨음 한다.

 

 

 

 

[D3.js 에서 Json 데이터 URL 를 호출해 웹으로 그래프 보여주기]

  D3.js 는 앞에서도 얘기했지만, 자바스크립트 쪽의 matplotlib 같은 시각화 라이브러리이다. 인기가 많은 편인 듯 해서, 디자인 강화에 중점을 둔 c3.js 등의 d3.js 기반의 라이브러리들도 있는 듯 하다. 해당 라이브러리의 컨셉은 목적상 matplotlib 과 역시 비슷하다(사실 모든 시각화 라이브러리가 비슷 한듯 하다). csv 등의 파일이나, 데이터를 반환하는 api 형태의 url 부터 json 데이터를 가져와서, 데이터 형을 잘 맞춰서, 원하는 그래프를 그려주는 라이브러리 함수에 공급한다. 그럼 보통 svg 형식으로 그래프를 그려(브라우저에서 HTML 문서안에 백터 그림을 나타내는 표준으로, svg와 canvas 두 가지 표준이 있다) 브라우저에 표시해 준다.

 

 

[App Code 구현]

  해당 페이지를 만들어 보기 위해 구글에서 'flask d3', 'd3 simple example', 'd3 simple date' 를 조회해서 아래의 3개의 페이지를 참고했다.

[전체적인 개념]
https://github.com/dfm/flask-d3-hello-world

[실제 동작 하는 코드]
http://bl.ocks.org/d3noob/b3ff6ae1c120eea654b5

[xxxx-xx-xx 형식의 날짜 데이터를 D3 에서 파싱하기 위해서]
https://stackoverflow.com/questions/13654609/draw-d3-simple-line-chart-with-an-array

 

 

  해당 페이지들의 예제들은 데이터 생성 로직이 조금 복잡하여, 주제에 집중하기 위해 17교시 머신러닝 예제 만들때 처럼 아래의 간단한 numpy 데이터를 임의로 만들었다.

1
2
3
   # 데이터 지정
    x = np.array(['2017-07-10''2017-07-11''2017-07-12''2017-07-13''2017-07-14'])
    y = np.array([58.1353.9867.0089.7099.00])
cs

 

 

  위의 예제들을 조합해서 정리한 프로그램쪽 코드는 아래와 같다. routing 경로가 2개로 늘어났다는 점만 제외하면, 나머진 다 앞에서 다루어 봤던 코드들이다. 조금 낯설은 코드는 json.dumps 명령어를 이용해서, json 데이터를 만들어 내는 부분이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import json
import flask
import numpy as np
 
app = flask.Flask(__name__)
 
# d3sample 을 호출했을때의 템플릿 설정
@app.route("/d3sample")
def showsample():
    return flask.render_template("d3sample.html")
 
# D3에서 가져갈 data url을 호출하면 반환할 json 데이터 만들어 내기 
@app.route("/data")
def data():
     
    # 데이터 지정
    x = np.array(['2017-07-10''2017-07-11''2017-07-12''2017-07-13''2017-07-14'])
    y = np.array([58.1353.9867.0089.7099.00])
 
    # 리스트를 json 데이터로 변환
    return json.dumps([{"date": x[i], "close": y[i]}
        for i in range(5)])
 
# 앞과 비슷한데 조금 틀려만 보임
if __name__ == "__main__":
    port = 5000
    app.debug = True
    app.run(port=port)
cs

 

  위의 코드를 메모장에 넣고, 파일형식은 '모든파일', 인코딩은 'utf-8' 로 선택하여, c:\python\flaskweb 폴더에 myweb_d3.py 로 저장한다(json.dumps 코드의 결과가 궁금 하시면 'http://127.0.0.1/data' 를 브라우저에서 호출해 본다).

 

 

[Templates Code 구현]

  그 다음은 랜더링에 사용할 d3samlpe.html 템플릿 파일이다. 안의 코드는 html 코드 보다는 d3.js 라이브러리를 사용하기 위한 자바스크립트 코드로 가득 차있다. 코드의 흐름을 보면 맨 아래 'd3.json("/data", callback)' 함수에서 'http://127.0.0.1/data' 경로를 호출하여 'json 형태의 데이터'를 얻어와서, 'callback' 함수에 넘겨준다. 'callback' 함수에서는 넘어온 데이터를 'd3.js 라이브러리' 함수에 입력하여 'svg 그래프'를 그린다. 상세한 코드들은 혹시 해당 라이브러리를 이용할 일이 있음(어차피 그래프 종류가 많아 각각 쓰임을 이해해야 한다) 이해하면 되고 여기선 flask 를 설명하는 목적이니 대충 주석과 흐름만 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
<!DOCTYPE html>
<meta charset="utf-8">
<style> <!-- 그래프 요소들의 스타일 지정 -->
body { font: 12px Arial;}
path { 
    stroke: steelblue;
    stroke-width: 2;
    fill: none;
}
.axis path,
.axis line {
    fill: none;
    stroke: grey;
    stroke-width: 1;
    shape-rendering: crispEdges;
}
 
</style>
<body>
<!-- 라이브러리 로딩. 내부에서 돌리려면 다운받아서 static 폴더에서 읽어와야 할듯 -->    
<script src="http://d3js.org/d3.v3.min.js"></script>
 
<script>
 
// 그래프 좌표 공간 설정
var margin = {top: 30, right: 20, bottom: 30, left: 50},
    width = 600 - margin.left - margin.right,
    height = 270 - margin.top - margin.bottom;
 
// 그래프 범위
var x = d3.time.scale().range([0, width]);
var y = d3.scale.linear().range([height, 0]);
 
// 축 정의
var xAxis = d3.svg.axis().scale(x)
    .orient("bottom").ticks(5);
var yAxis = d3.svg.axis().scale(y)
    .orient("left").ticks(5);
 
// 그래프 선 정의
var valueline = d3.svg.line()
    .x(function(d) { return x(d.date); })
    .y(function(d) { return y(d.close); });
    
// 캔버스 객체 생성
var svg = d3.select("body")
    .append("svg")
        .attr("width", width + margin.left + margin.right)
        .attr("height", height + margin.top + margin.bottom)
    .append("g")
        .attr("transform"
              "translate(" + margin.left + "," + margin.top + ")");
 
// 2017-07-01 식으로 데이터를 해석하게 지정함
var parseDate = d3.time.format("%Y-%m-%d").parse;
 
// 전달 받은 데이터를 이용해서 그래프를 그린다.
var callback = function (data) {
    data.forEach(function(d) {
        d.date = parseDate(d.date);
        d.close = +d.close;
    });
 
    // 실데이터에 맞춰 그래프 범위 지정
    x.domain(d3.extent(data, function(d) { return d.date; }));
    y.domain([0, d3.max(data, function(d) { return d.close; })]);
 
    // 선 그리기.
    svg.append("path")
        .attr("class""line")
        .attr("d", valueline(data));
 
    // x축 그리기?
    svg.append("g")
        .attr("class""x axis")
        .attr("transform""translate(0," + height + ")")
        .call(xAxis);
 
    // y 축 그리기?
    svg.append("g")
        .attr("class""y axis")
        .call(yAxis);
 
};
 
// flask 에서 만든 http://127.0.0.1/data 를 호출하여 json 데이터를 가져와 callback 함수 호출
d3.json("/data", callback);
 
</script>
</body>
cs

 

  해당 코드를 역시 메모장에 복사하여, c:\python\flaskweb\templates 폴더에 utf-8 인코딩으로 d3sample.html 로 저장한다(한글 주석이 있어 utf-8 인코딩이 아니면 rendering 과정에서 에러가 난다).

 

 

  그럼 모든 코드가 구현되었고 c:\python\flaskweb\ 으로 이동하여, 아래와 같이 myweb_d3.py 파일을 실행 한다.

c:\Python\flaskweb>python myweb_d3.py
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 288-594-455
 * Running on
http://127.0.0.1:5000/ (Press CTRL+C to quit)

 

  이후 브라우저를 열어 http://127.0.0.1:5000/d3sample 이라고 치면, myweb_d3.py 상에서 d3sample 에 해당하는 showsample() 함수를 실행하여, d3sample.html 템플릿을 호출하면, 해당 템플릿 내에서 http://127.0.0.1:5000/data URL 을 호출하여, json 데이터를 받아서 D3.js 라이브러리를 이용해서 브라우저 화면에 아래와 같이 그래프를 출력하게 해준다(뭐 HTML 페이지에서 자바스크립트 d3.js 라이브러리를 이용해서 그래프를 그리는 것도 rendering 이라고 표현해도 된다).

 

 

  앞에서 설명한 방식으로 실제 동작하는지 확인하기 위해 이전 시간에 배운 피들러를 띄워 페이지 호출을 관찰해 보면, 아래와 같이 /d3sample 과 /data 가 차례로 호출되는 것을 볼 수 있다. 앞의 /d3sample 은 브라우저 주소창에서, 뒤의 /data 는 템플릿 랜더링 과정에서 호출한 것이다.

 

 

 

 

[matplotlib 그래프를 웹 페이지에 보여주기]

  마지막 예제는 위와 비슷한 데이터를 matplotlib 으로 그리고, HTML 페이지안에 해당 그림을 <img> 태그 형태로 삽입하는 예제이다.

 

 

[App Code 구현]

  예제를 구현하기 위해 구글에서 몇가지 샘플을 실행해 봤는데, python 2.x 대의 예제라서 라이브러리가 안 맞아 안 돌아 가거나, 3.x 대 예제인데, 실제로 에러는 안 나는데 이미지는 안 나오는(엑박표시) 경우가 많았었다. 몇 개의 예제를 검토해 본 바로는, 보통 두 가지 방식으로 구현 되는데, 첫째는 그려진 이미지를 static 폴더에 img 파일로 실제 물리적으로 저장한 후, html 템플릿 페이지를 띄워, 해당 이미지를 html 템플릿 내에 static 형식으로 포함하는 방법이 있고, 둘째는 <img> 태그 경로에 이미지를 생성하는 flask url 을 지정하여, 이미지를 마임(MIME) 데이터로 전달받아 브라우저에서 표시해 주는 방식이 있다. 여튼 결과적으로 'python 3 flask matplotlib html' 라고 검색해서 아래 2번째 방식으로 구현하는 예제를 찾았다.

http://dataviztalk.blogspot.kr/2016/01/serving-matplotlib-plot-that-follows.html

 

 

  해당 코드를 기반으로 데이터 생성만 간략화한 버전이 아래와 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
from io import BytesIO
from flask import Flask, render_template, send_file, make_response
import flask
from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
import numpy as np
import matplotlib.pyplot as plt
 
app = flask.Flask(__name__)
 
# mypic 을 호출하면 mypic.html 로 렌더링 한다.
@app.route('/mypic')
def mypic():
    return flask.render_template("mypic.html")
 
# matplotlib 그래프 파일을 생성하여 소켓 통신으로 보내준다. 
@app.route('/plot')
def plot():
 
   # 그림판 준비
    fig, axis = plt.subplots(1)
    
   # 데이터 준비
    y = [1,2,3,4,5]
    x = [0,2,1,3,4]
 
    # 그리기
    axis.plot(x,y)
    canvas = FigureCanvas(fig)
    
    # 그려진 img 파일 내용을 html 랜더링 쪽에 전송한다.
    img = BytesIO()
    fig.savefig(img)
    img.seek(0)
    return send_file(img, mimetype='image/png')
 
 
if __name__ == '__main__':
    port = 5000
    app.debug = True
    app.run(port=port)
cs

 

  위의 코드를 메모장에 넣고, 파일형식은 '모든파일', 인코딩은 'utf-8' 로 선택하여, c:\python\flaskweb 폴더에 myweb_mat.py 로 저장한다.

 

 

[Templates Code 구현]

  그 다음은 mypic.html 템플릿 파일이다. 내부 코드는 엄청 간단해서 이미지 태그를 만들면서, 이미지 소스(src) 위치를 '/plot' 으로 지정한다. 그럼 http://127.0.0.1:5000/plot 을 읽어오며 실행되게 되어 해당 데이터가 MIME 데이터로 html 쪽에 전달되어 결합된다(사실 이런 방식은 처음 보는 거라서 좀 신기하긴 하다).

1
2
3
4
5
6
7
8
9
10
<html>
  <head>
    <title>image</title>
  </head>
  <body>
    matplotlib 으로부터 만들어진 이미지
    <p>
    <img src="/plot" alt="Image Placeholder">
  </body>
</html>
cs

 

  해당 코드를 역시 메모장에 복사하여, c:\python\flaskweb\templates 폴더에 utf-8 인코딩으로 mypic.html 로 저장한다.

 

 

  그럼 모든 코드가 구현되었고 c:\python\flaskweb\ 으로 이동하여, 아래와 같이 myweb_mat.py 파일을 실행 한다.

c:\Python\flaskweb>python myweb_mat.py
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 288-594-455
 * Running on
http://127.0.0.1:5000/ (Press CTRL+C to quit)

 

 

  이후 브라우저를 열어 'http://127.0.0.1:5000/mypic' 이라고 치면, myweb_mat.py 상에서 'mypic' url 에 해당하는 'mypic()' 함수를 실행하여, 'mypic.html' 템플릿을 호출하면, 해당 템플릿 내에서 'http://127.0.0.1:5000/plot' 경로를 호출하여, MIME 데이터로 이미지 스트림을 받아서 그림을 표시해 준다. 

 

 

 

 

 

[마무리 하면서]

  이렇게 해서 flask 프레임워크에 대해 간단히 살펴보는 시간이 끝났다. 'routing', 'dyanamic vs static web(static files)', 'rendering templetes' 가 주요 키워드인것 같다. 이전 시간에 legacy web 을 길게 설명하고, 이 시간에 flask 와 legacy web 을 비교하면서 얘기했던 방식이, 쉬운 이해에 도움이 되면 좋겠다. 여러 파이썬 웹 프레임워크에 대한 비교 글들의 끝에서 언급되지만 프레임워크의 선택은 개인의 일하는 스타일의 취향과, 구현하려는 목표에 해당 프레임워크가 추구하는 방향이 얼마나 적합한가에 따라 달라질 것이다. 실제 구현하려다 보면, 전체 구현하려는 대상에 비교해 프레임워크가 지원해 주는 기능은 정말 꼭 필요한 최소한의 기능 밖에 없는 것도 같긴 하다(물론 essential 한 부분이긴 하지만...). 

 

   추가로 d3.js 를 연계한 예제에서 봤듯이 웹의 많은 기능이 자바스크립트 기반에서 움직이기 때문에, 파이썬 로직으로 모든걸 해결하려는 것보다는, 파이썬 웹 어플리케이션 모듈 쪽에서는 데이터를 가공하여 제공하고, 실제 웹 쪽 UI 구현은 자바스크립트 라이브러리를 이용해 구현하면 효율이 좋은 경우도 많을 것 같다(웹의 1/3 쯤은 자바스크립트의 세상이고, d3.js 를 이해하기 위해서는 javascript 와 그래픽 라이브러리에 익숙하면 유리하기 때문에, 웹 전체에 연관되는 분야를 공부해 균형을 맞춰 놓는 것도 중요한 듯 하다). 다행인 점 하나는 'd3.js' 예제에서 봤듯이, 파이썬에서 배웠던 'matplotlib' 같은 목적이 비슷한 라이브러리를 사용해본 경험이, 많은 부분에서 비슷하게 적용 된다. 지금 이 시간이 기반이 되어 다음 시간인 Django 프레임워크에 대해서도 적절하게 설명할 수 있게 되길 바라며, Flask 살펴보기 시간을 마친다.  

 

 

 

 

2017.7.21 by 자유로운설탕
cs

 

  

 

 

 

 

 

 

 

 

posted by 자유로운설탕
prev 1 2 3 4 5 6 7 ··· 9 next