2010년 3월 25일 목요일

Google Docs를 사용해서 쉽게 설문조사 하기 (Easy email survey using Google Docs)

Google Docs에는 많이 알려지지 않았지만 매우 편리한 기능이 숨겨져있다.
그 중 유용한 것이 이메일을 통한 설문조사 기능이다.


Google Docs의 좌측 상단에 있는 'Create New'를 눌러 그 중 'Form'을 선택한다.


위와 같이 질문을 입력하는 화면이 나오게 된다. 여기서 각 질문을 만들어주면 된다.


각 항목에 대한 설명은 위와 같다.


답변 타입은 위와 같이 7개가 있다.


질문을 추가하려면 좌측 상단의 Add item 버튼을 눌러주면 된다.


설문지가 어떻게 보이는지 미리보기를 하고 싶으면 화면 아래쪽의 검은색 부분에 있는 주소를 눌러주면 된다.


그러면 브라우져의 새 창이나 탭에 위와같이 설문지를 미리보기 할 수 있다.


설문지 디자인이 심심(?)하다고 생각되면 Theme 버튼을 눌러 테마를 선택할 수 있다. 모든 확인이 끝났으면 Email this form 버튼을 눌러 설문지를 보내면 된다.


현재 약 70개 정도의 테마가 있다.


Email this form 버튼을 누르면 위와 같은 화면이 열려 누구에게 메일로 보낼지 지정해 줄 수 있다. 여러 사람의 주소를 쓰는 경우 각 주소는 ','로 구분해주면 된다.


설문지를 받은 사람의 브라우져 화면이다.


설문에 답한 다음 Submit버튼을 눌러주면 된다.


Submit 버튼을 누르면 데이터를 보내기 때문에 진짜로 보낼건지 확인한다. 'OK'를 눌러주면 된다.


데이터가 전달되고 나면 위와 같이 Thank you 화면이 나온다.


이렇게 메일로 수집된 데이터는 자동으로 설문을 보낸 사람의 스프레드쉬트 파일에 입력된다. Google Docs에 가 보면 자신이 만든 설문에 대한 파일이 생겨져 있다.


파일을 열어보면 위와 같이 설문 응답 내용이 자동으로 스프레드 쉬트에 입력되어 있다.


또한 설문을 보내 사람은 메일함을 열어보면 설문결과를 확인할 수 있는 링크와 설문지를 추가로 발송할 수 있는 링크가 들어있는 메일이 google docs로부터 와 있다.

이 기능을 사용하면 설문지를 돌리고 수집한 다음 결과를 정리하기 위해 입력하는 수고 없이 매우 쉽게 설문조사를 할 수 있다.

2010년 3월 8일 월요일

DIAMEX DXM - OBD모듈




그 동안 가장 일반적으로 사용되던 OBD-II 모듈은 대부분 ELM-3xx 칩 기반으로 만들어졌다. 하지만 좀 더 좋은 모듈이 나왔다. 독일에서 만든 DIAMEX DXM 모듈로 매우 작은 크기(21mm*36mm)지만 ARM CORTEX 코어를 사용해 매우 빠르고 보호용 다이오드, 콘덴서 하나만 있으면 OBD포트에 바로 연결해 간단하게 사용할 수 있다. PWM(SAE J1850), VPWM(SAE J1850), CAN(ISO15765-4), KWP2000(ISO14230-4), ISO9141-2를 모두 지원하고 자동으로 사용되는 통신방식을 선택해 준다.
또한 AT 명령어 셋을 사용하기 때문에 이미 나와있는 대다수의 OBD-II 소프트웨어와 호환이 된다. 명령어 셋은 여기에서 확인할 수 있다.


위의 회로도는 컴퓨터의 RS-232 포트에 연결하는 경우의 회로이다. 마이크로 프로세서의 시리얼 포트에 연결할 때는 MAX3232부분도 필요없이 프로세서의 Rx, Tx에 직접 연결해주면 된다.

모듈의 pinout은 다음과 같다.
 






위의 사진에서 보듯이 이미 이 모듈을 사용한 시리얼, USB, Bluetooth로 연결할 수 있는 제품들도 나와있다.

2010년 3월 7일 일요일

DIAMEX DXM 명령어 목록

DIAMEX DXM module은 시리얼 포트를 통해 PC와 통신을 하고 AT명령어 셋을 사용하기 때문에 이미 나와있는 대다수의 OBD-II 소프트웨어와 호환된다. 지원하는 AT명령어 목록은 다음과 같다.
--

DIAMEX DXM command list

COMMAND

Explanation

ATZ

Reboot the controller

ATWS

Same as ATZ but faster

ATI

Identification text is reported

ATD

All parameters are stored in the ground state as after a cold or warm state

ATE0/1

Toggle the serial echo on/off

ATL0/1

Toggle the transmission of the linefeed(L/F)

ATM0/1

Toggle the memory function of the final protocol

ATH0/1

Toggle if the OBD2 header answers and checksum byte shall be displayed

ATBD

Causes the display of the OBD2 receive buffer

ATB

Display of the OBD2 reception buffer

ATSRxx

Input of the RX ECU OBD2 filter address for OBD2 responses

ATN

Display of the current protocol used as HEX value F0..F9

  • F0 – no active protocol  

  • F1 – PWM protocl 

  • F2 – VPWM protocol 

  • F3 – ISO9141 protocol 

  • F4 – KWP2000 protocol 

  • F5 – KWP2000 protocol 

  • F6 – CAN 11-bit ID, 500 kBaud 

  • F7 – CAN 29-bit ID, 500 kBaud 

  • F8 – CAN 11-bit ID, 250 kBaud 

  • F9 – CAN 29-bit ID, 250 kBaud 

ATDP

Display of the currently used protocol in plaintext

  • NOT CONNECTED 

  • SAE J1850/PWM 

  • SAE J1850/VPWM 

  • ISO9141-2 

  • ISO14230-4, KWP2000 (5 BAUD Init) 

  • ISO14230-4, KWP2000 (Fast Init) 

  • ISO15765-4, CAN(11/500) 

  • ISO15765-4, CAN(29/500) 

  • ISO15765-4, CAN(11/250) 

  • ISO15765-4, CAN(29/250) 

ATK

Display the current keywords on ISO9141 and KWP2000

ATKW0/1,

For a slow, init keywords are distinguished between protocol 3 (ISO9141) and protocol 4 (KWP2000). This automatic detection can be disabled with ATKW0

ATP[A]x

Manual setting of the current protocol or the automatic protocol search

  • ATP1 – PWM 

  • ATP2 – VPWM 

  • APT3 – ISO9141-2 

  • ATP4 – KWP2000 5 BAUD Init 

  • ATP5 – KWP2000 Fast Init 

  • ATP6 – CAN 11/500 

  • ATP7 – CAN 29/500 

  • ATP8 – CAN 11/250 

  • ATP9 – CAN 29/250 

ATV

Overview of all the modifiable parameters of the DXM1 are displayed

ATSBx

Set baud rate of serial interface

  • ATSB0 – 9600 Baud (Standard) 

  • ATSB1 – 19200 Baud 

  • ATSB2 – 38400 Baud 

  • ATSB3 – 57600 Baud 

  • ATSB4 – 115200 Baud 

  • ATSB5 – 125000 Baud 

  • ATSB6 – 250000 Baud 

ATSHxxyyzz

Manual setting of the header bytes for ISO9141, KWP2000, PWM and VPWM protocol

  • xx = priority/type-byte 

  • yy = target-address 

  • zz = source-address 

ATWMxxyyzzaa[bb][cc]

Manual setting of the wakeup message bytes for ISO9141 and KWP2000

  • xx = priority/type-byte (with length indication in KWP) 

  • yy = target-address 

  • zz = source-address 

  • aa, bb, cc = command bytes 1~3 

ATSWxx

The length of time between automatic wakeup commands in existing ISO9141 and KWP2000 can be set

ATCA0/1

The automatic formatting of the transmitted and received CAN data can be switched on/off

ATCC0/1

Multi-frame answers have to be sent flow control messages from the tester, which indicate the controller, that following packages have to be accepted

ATCDxx

Flow control data packages which has to be sent in multi frame responses, contain beside the status byte (FS), a block size byte (BS) and also a byte for the duration (ST), which have to be added between the following response packages

ATCIxxx/xxxxxxxx

CAN-ID which has to be sent, is set with this command

  • 11-bit ID – 7DF (default) 

  • 29-bit ID – 18 DD 33 F1 (default) 

ATCFxxx/xxxxxxxx

Set CAN RX-filter

  • 11-bit ID – 7E8 (default) 

  • 29-bit ID – 18 DA F1 00 (default) 

ATCMxxx/xxxxxxxx

Set CAN RX-mask

  • 11-bit ID – 7F8 (default) 

  • 29-bit ID – 1F FF FF 00 (default) 

AT!00

Output of the serial number of the DXM controller

AT!01

Output of the controller type and the BIOS version number

AT!10

Mesaurement of 12-volt vehicle power



2010년 3월 5일 금요일

초음파 소나를 사용한 전원관리 - Sonar Power Manager

현재 컴퓨터 전원절약을 위해 스크린세이버에서 가장 많이 사용되는 방법은 일정시간 이상 사용자의 입력이나 이벤트가 없으면 사용자가 컴퓨터 앞에 앉아 있다고 해도 컴퓨터를 사용하지 않는다고 판단해서 모니터를 끄고 다음 단계로 하드 정지 등의 전원을 절약하기 위한 절차를 수행한다.
하지만 이 방법으로는 사용자가 컴퓨터 앞에 앉아있어 원하지 않는 경우에도 화면이 꺼질수 있고, 반대로 자리를 비워도 일정시간동안은 화면이 그대로 유지될 수 밖에 없다.

그에 대한 대안으로 휴대폰은 보통 사용자가 항상 몸에 지니고 다닌다는 점에 착안해 블루투스를 지원하는 휴대폰을 등록해 놓으면 휴대폰의 블루투스 신호가 잡히면 컴퓨터를 사용할 수 있게 하고 신호가 사라지면 사용자가 컴퓨터에서 멀리 떨어졌다고 생각해 슬립모드 또는 스크린세이버를 실행하게 하기도 한다.

블루투스 휴대폰의 활용법 - 컴퓨터의 RFID키로 사용하기 (Use bluetooth-enabled cellphone as a RFID key for PC)

하지만 더 재미있는 방법을 생각해 낸 사람이 있다. 요새 컴퓨터...특히 노트북의 경우 거의 다 스피커와 마이크를 가지고 있기 때문에 그걸 이용해서 박쥐가 비행할 때 앞쪽의 장애물을 감지하는 방식과 같은 식으로 사용자가 컴퓨터 앞에 있는가를 감지해 내는 것이다.


이 프로그램을 만든 사람에 의하면 대부분의 컴퓨터는 사람 귀의 가청주파수(~20KHz)보다 높은 초음파를 발생하고 마이크로 받아들일 수 있다고 한다. (물론 모든 기종이 다 가능하지는 않고 그런 경우 불행하게도 이 프로그램을 사용할 수 없다.) 오픈소스 소프트웨어로 윈도우와 리눅스에서 사용할 수 있고 리눅스의 경우는 소스를 받아 컴파일 해 줘야 한다.


이 프로그램의 스크린샷이다. 저 그래프에서 파란색 선은 매 초마다 읽은 소나값이고 검은색 선은 소나값을 최근 10초동안 평균한 값(잡음등의 영향을 최소화), 빨간색 선은 presence threshold로 검은색 선이 빨간선 아래로 내려가면 사용자가 컴퓨터 앞에서 멀어졌다고 판단하는 기준이 된다.

프로그램은 여기에서 다운받을 수 있다.

- 관련 논문
S. P. Tarzia, R. P. Dick, P. A. Dinda, G. Memik. Sonar-based Measurement of User Presence and Attention. In Proc. 11th Intl. Conf. on Ubiquitous Computing (UbiComp'09). September 2009. pages 89-92.

2010년 3월 3일 수요일

Tornado - 파이선 웹 프레임웍



Tornado는 python으로 만들어 진 오픈소스 웹 프레임웍과 관련 툴들로 구글의 webapp과 유사하다.
하지만 가장 큰 차이점은 non-blocking이고 매우 빠르다는 것이다. epoll을 사용해서 non-blocking 서버를 구현하기 때문에 동시에 몇천개의 접속을 처리해 줄 수 있기 때문에 리얼타임 웹 서비스에 적합하다.

현재 tornado는 python 2.5, 2.6에서 테스트 되었다. 설치하려면 먼저 PycURL과 simplejson이 설치되어 있어야 한다.

우분투에서는 다음과 같이 하면 prerequisite가 해결된다.

% sudo apt-get install python-dev python-pycurl python-simplejson

Mac OS X에서는 다음과 같이 하면 prerequisite가 해결된다.

% sudo easy_install setuptools pycurl==7.16.2.1 simplejson

현재 tornado의 버젼은 0.2이다. 여기를 눌러 다운받으면 된다.
다운받은 다음 같은 디렉토리에서 아래의 명령어를 입력해주면 설치가 끝난다.

% tar xvzf tornado-0.2.tar.gz
% cd tornado-0.2
% python setup.py build
% sudo python setup.py install

설치가 끝나고 나면 제대로 설치되었는가 확인도 할 겸 프로그래밍을 배울 때 가장 처음에 해 보게되는 hello world 예제를 실행해 보겠다.

import tornado.httpserver
import tornado.ioloop
import tornado.web

class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("Hello, world")

application = tornado.web.Application([
    (r"/", MainHandler),
])

if __name__ == "__main__":
    http_server = tornado.httpserver.HTTPServer(application)
    http_server.listen(8888)
    tornado.ioloop.IOLoop.instance().start()

위의 코드를 저장하고(여기서는 helloworld.py란 이름으로 저장하였다고 하겠다.) 터미널의 쉘 프롬프트에서 python helloworld.py 를 입력해서 실행해 준 다음 웹 브라우져의 주소창에 http://localhost:8888 을 입력해주면 된다.

그러면 위와 같은 화면이 나오게 된다.

Request handler and request argument

토네이도 웹 어플리케이션은 URL 또는 URL패턴을 tornado.web.RequestHandler의 서브클래스로 매핑한다. 이 클래스들은 해당 URL로의 HTTP GET 또는 POST request를 처리하기 위한 get() 또는 post() 메소드를 정의한다.

아래 코드는 루트 URL인 '/'는 MainHandler에, URL패턴인 '/story/([0-9]+)'는 StoryHandler로 매핑하고 있다. 정규식(regular expression) 그룹은 RequestHandler 메소드에게 argument로 전달된다.

import tornado.httpserver
import tornado.ioloop
import tornado.webclass

MainHandler(tornado.web.RequestHandler):    
     def get(self):        
          self.write("You requested the main page")

class StoryHandler(tornado.web.RequestHandler):    
     def get(self, story_id):        
          self.write("You requested the story " + story_id)

application = tornado.web.Application([    
     (r"/", MainHandler),    
     (r"/story/([0-9]+)",
     StoryHandler),
])

if __name__ == "__main__":    
     http_server = tornado.httpserver.HTTPServer(application)          
     http_server.listen(8888)   
     tornado.ioloop.IOLoop.instance().start()



또한 get_argument() 메소드를 사용하면 query 스트링을 가져와서 POST 바디의 내용을 파싱할 수 있다.

import tornado.httpserver
import tornado.ioloop
import tornado.web

class MainHandler(tornado.web.RequestHandler):    
     def get(self):        
          self.write('<html><body><form action="/" method="post">'                   
                    '<input type="text" name="message">'                   
                    '<input type="submit" value="Submit">'
                    '</form></body></html>')   

     def post(self):        
          self.set_header("Content-Type", "text/plain")        
          self.write("You wrote " + self.get_argument("message"))

application = tornado.web.Application([    
     (r"/", MainHandler),  
])

if __name__ == "__main__":    
     http_server = tornado.httpserver.HTTPServer(application)
     http_server.listen(8888)    
     tornado.ioloop.IOLoop.instance().start()





클라이언트에 에러 response(예를들면 403 Unauthorized 같은)를 보내려면 tornado.web.HTTPError exception을 raise해 주면 된다.

if not self.user_is_logged_in():    
     raise tornado.web.HTTPError(403)

request handler에서 현재 request 오브젝트를 self.request 로 억세스 할 수 있다. HTTPRequest 오브젝트는 여러가지 유용한 attribute를 포함하고 있다.

    * arguments - GET 과 POST의 전체 argument
    * files - (multipart/form-data POST request를 통한) 모든 업로드 된 파일들
    * path - request 패스 (URL에서 '?' 앞쪽 전체)
    * headers - request 헤더

모든 attribute 목록은 httpserver의 HTTPRequest 클래스 정의를 보면 된다.


템플릿

Tornado에서는 python에서 사용할 수 있는 어떤 템플릿 언어도 사용할 수 있지만 tornado는 다른 템플릿 언어들에 비해 매우 빠르고 유연한 템플릿 언어를 기본적으로 포함하고 있다. 상세한 내용은 template 모듈 문서를 참조하면 된다.

Tornado 템플릿은 마크업 내에 python 제어문(control statement)와 표현식(expression)을 포함하고 있는 HTML이다. 아래 예제를 보면 쉽게 이해가 될 것이다.

<html>  
 <head>      
  <title>{{ title }}</title>   
 </head>  
 <body>    
  <ul>       
   {% for item in items %}         
    <li>{{ escape(item) }}</li>       
   {% end %}     
  </ul>   
 </body>
</html>

이 템플릿을 template.html 이라는 이름으로 저장하고 python 파일과 같은 디렉토리에 넣어주었다면 아래의 코드를 사용해서 이 템플릿을 렌더링 할 수 있다.

import tornado.httpserver
import tornado.ioloop
import tornado.web

class MainHandler(tornado.web.RequestHandler):    
     def get(self):        
          items = ["Item 1", "Item 2", "Item 3"]                 
          self.render("template.html", title="My title", items=items)

application = tornado.web.Application([    
     (r"/", MainHandler),  
])

if __name__ == "__main__":    
     http_server = tornado.httpserver.HTTPServer(application)    
     http_server.listen(8888)    
     tornado.ioloop.IOLoop.instance().start()

렌더링 된 결과이다.

<html>  
 <head>      
  <title>My title</title>   
 </head>  
 <body>    
  <ul>         
   <li>Item 1</li>        
   <li>Item 2</li>        
   <li>Item 3</li>
  </ul>   
 </body>
</html>

Tornado 템플릿은 제어문과 표현식을 지원한다. 제어문은 {%%}로 둘러쌓아주면 된다. 예제 {% if len(items) > 2 %}
표현식은 {{}}로 둘러쌓아준다. 예제 {{ items[0] }}

제어문은 python의 제어문과 동일하다. Tornado에서는 if, for, while, try를 지원하고 모두 {% end %}로 끝나야 한다. 또한 extends와 block 문을 사용해서 템플릿 상속(template inheritance)을 지원한다. 자세한 내용은 template module 문서를 참조하면 된다.

표현식은 함수 호출을 포함한 어떤 python 표현식이 될 수 있다. escape, url_escape, json_encode 함수는 디폴트로 제공한다. 또한 다른 함수를 템플릿에 넘겨주는것도 템플릿 렌더 함수에게 함수 이름을 keyword argument로 넘겨주기만 하면 된다.

class MainHandler(tornado.web.RequestHandler):    
     def get(self):        
          self.render("template.html", add=self.add)    

     def add(self, x, y):        
          return x + y

실제 어플리케이션을 만들 때 tornado 템플릿의 모든 기능, 특히 템플릿 상속 기능을 사용하길 원하게 될 것이다. templae module 섹션에서 이 기능에 대한 모든 내용을 읽어두는게 좋다.내부에서 tornado 템플릿은 python으로 직접 번역된다. 템플릿에 포함된 표현식은 템플릿을 나타내는 파이선 함수로 글자 그대로 복사된다. tornado 템플릿은 유연성을 최대한 보장하기 위해 다른 엄격한 템플릿 시스템에서는 금지하고 있는것들도 다 허용하기 위해 템플릿 언어에서 어떤것을 특별히 금지시키려고 하지 않았다. 결과적으로 템플릿 표현식에서 이상한 내용을 써 놓았으면 그 템플릿을 실행할 때 이상한 python에러를 접하게 될 것이다.

Cookies and secure cookies

사용자의 브라우져에서 set_cookie 메소드를 사용해서 쿠키를 설정할 수 있다.

import tornado.httpserver
import tornado.ioloop
import tornado.web

class MainHandler(tornado.web.RequestHandler):    
     def get(self):        
          if not self.get_cookie("mycookie"):
              self.set_cookie("mycookie", "myvalue")
              self.write("Your cookie was not set yet!")
          else:
              self.write("Your cookie was set!")

application = tornado.web.Application([    
     (r"/", MainHandler),    
])

if __name__ == "__main__":    
     http_server = tornado.httpserver.HTTPServer(application)    
     http_server.listen(8888)    
     tornado.ioloop.IOLoop.instance().start()



쿠키는 악의적인 해커가 매우 쉽게 위조할 수 있다. 현재 로그인 된 사용자의 ID를 저장하기 위해 쿠키를 사용해야 하면 쿠키 위조를 방지하기 위해 사인을 해 줄 필요가 있다. Tornado는 기본적으로 set_secure_cookie와 get_secure_cookie 메소드를 제공해준다. 이 메소드를 사용하려면 어플리케이션을 만들 때 cookie_secret 메소드를 사용해서 secret key를 지정해 줘야 한다. 어플리케이션 설정을 키워드 argument로 어플리케이션에 넘겨줄 수 있다.

application = tornado.web.Application([    
     (r"/", MainHandler),    
], cookie_secret="6fjslkdfjef24=d2t4jkldsjlv/sdfajl")

사인된 쿠키는 쿠키의 인코딩 된 값에 추가로 타임스탬프와 HMAC사인이 추가되어 있다. 만일 쿠키가 오래된 것이거나 사인이 일치하지 않으면 get_secure_cookie는 쿠키가 설정되지 않았을때와 마찮가지로 None을 리턴한다. 위의 예제의 secure 버젼은 다음과 같다.

import tornado.httpserver
import tornado.ioloop
import tornado.web

class MainHandler(tornado.web.RequestHandler):    
     def get(self):        
          if not self.get_secure_cookie("mycookie"):
              self.set_secure_cookie("mycookie", "myvalue")
              self.write("Your cookie was not set yet!")
          else:
              self.write("Your cookie was set!")

application = tornado.web.Application([    
     (r"/", MainHandler),    
], cookie_secret="6fjslkdfjef24=d2t4jkldsjlv/sdfajl")

if __name__ == "__main__":    
     http_server = tornado.httpserver.HTTPServer(application)    
     http_server.listen(8888)    
     tornado.ioloop.IOLoop.instance().start()


User authentication

현재 인증된 사용자는 모든 request handler에서는 self.current_user로, 템플릿에서는 current_user에 들어있다. 기본적으로 current_user에는 None이 들어있다.

어플리케이션에서 사용자 인증 기능을 넣어주려면 쿠키 값에 따라 현재 사용자를 판단하기 위해 request handler의 get_current_user 메소드를 override해 줘야 한다. 아래 코드는 사용자가 단순히 닉네임을 지정하면 그게 쿠키에 저장되는 간단한 예제이다.

import tornado.httpserver
import tornado.ioloop
import tornado.web

class BaseHandler(tornado.web.RequestHandler):
     def get_current_user(self):
          return self.get_secure_cookie("user")

class MainHandler(BaseHandler):
     def get(self):
          if not self.current_user:
              self.redirect("/login")
              return
          name = tornado.escape.xhtml_escape(self.current_user)
          self.write("Hello, " + name)

class LoginHandler(BaseHandler):
     def get(self):
          self.write('<html><body><form action="/login" method="post">'
                    'Name: <input type="text" name="name">'
                    '<input type="submit" value="Sign in">'
                    '</form></body></html>')

     def post(self):
          self.set_secure_cookie("user", self.get_argument("name"))
          self.redirect("/")

application = tornado.web.Application([
     (r"/", MainHandler),
     (r"/login", LoginHandler),
], cookie_secret="&k4jtlwjflkdjsdlkj24t243t==afgfg3lgjelrgds")

if __name__ == "__main__":    
     http_server = tornado.httpserver.HTTPServer(application)    
     http_server.listen(8888)    
     tornado.ioloop.IOLoop.instance().start()




이제는 로그인이 되고 쿠키가 설정되었기 때문에 http://localhost:8888 을 입력하면 http://localhost:8888/login 으로 리다이렉트 되지 않고 바로 위와 같은 화면이 나온다.

사용자가 파이선 데코레이터인 tornado.web.authenticated를 사용해서 로그인하도록 요구할수도 있다. Request가 이 데코레이터와 함께 메소드로 가고 사용자가 로그인되어 있지 않으면 login_url로 리다이렉트 된다. 위의 예제는 아래와 같이 바뀌게 된다.

import tornado.httpserver
import tornado.ioloop
import tornado.web


class MainHandler(BaseHandler):
     @tornado.web.authenticated
     def get(self):
          name = tornado.escape.xhtml_escape(self.current_user)
          self.write("Hello, " + name)

settings = {
     "cookie_secret": "&k4jtlwjflkdjsdlkj24t243t==afgfg3lgjelrgds",
     "login_url": "/login",
}

application = tornado.web.Application([
     (r"/", MainHandler),
     (r"/login", LoginHandler),
], **settings)

if __name__ == "__main__":    
     http_server = tornado.httpserver.HTTPServer(application)    
     http_server.listen(8888)    
     tornado.ioloop.IOLoop.instance().start()


Post메소드를 authenticated 데코레이터로 장식하고 사용자가 로그인 되어있지 않으면 서버는 403 응답을 보낸다.

Tornado는 구글의 OAuth같은 3rd party 인증 기법을 기본적으로 지원한다. 상세한 내용은 auth module 문서를 참조하면 된다. 이 방식의 사용자 인증과 사용자 정보를 MySQL DB에 저장하는 완전한 예제는 Tornado 블로그를 보면 된다.


Cross-site request forgery protection

XSRF는 개인화 된 웹 어플리케이션에서 매우 흔한 문제이다. XSRF가 어떻게 동작하는지에 대한 상세한 설명은 위키피디아를 보면 된다.

XSRF를 방지하기 위해 가장 일반적으로 사용되는 방법은 모든 사용자에게 쿠키로 예측할 수 없는 값을 주고 모든 form submit에 대해 그 값을 추가 argument로 포함하는 것이다. 쿠키와 form submission의 값이 일치하지 않으면 request는 위조되었을 가능성이 크다.

Tornado는 기본적으로 XSRF 방지 기능을 지원한다. 이 기능을 추가하고 싶으면 어플리케이션 설정에 xsrf_cookies를 포함시켜 주면 된다.

settings = {
     "cookie_secret": "&k4jtlwjflkdjsdlkj24t243t==afgfg3lgjelrgds",
     "login_url": "/login",
     "xsrf_cookies": True,
}

application = tornado.web.Application([
     (r"/", MainHandler),
     (r"/login", LoginHandler),
], **settings)

xsrf_cookies가 설정되어 있으면 tornado 웹 어플리케이션은 모든 사용자에 대해 _xsrf 쿠키를 설정하고 정확한 _xsrf 값을 포함하고 있지 않은 POST request는 모두 거부한다. 이 설정을 활성화 시켜 놨으면 POST로 값을 submit하는 모든 폼들에 대해 이 필드를 포함하도록 해 줘야만 한다. 모든 템플릿에서 사용할 수 있는 xsrf_form_html() 이라는 특수 함수를 지정해주면 된다.

<form action="/login" method="post">
     {{ xsrf_form_html() }}
     <div>Username: <input type="text" name="username"/></div>
     <div>Password: <input type="password" name="password"/></div>
     <div><input type="submi" value="Sign in"/></div>
</form>

AJAX POST request를 submit하는 경우 각 request에 대해 _xsrf 값을 포함하도록 JavaScript에게 알려줘야 한다. FriendFeed에서는 AJAX POST request가 자동으로 request에 _xsrf 값을 추가하도록 하기 위해 jQuery의 함수를 사용했다.

function getCookie(name) {
     var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
     return r ? r[1] : undefined;
}

jQuery.postJSON = function(url, args, callback) {
     args._xsrf = getCookie("_xsrf");
     $.ajax({url: url, data: $.param(args), dataType: "text", type: "POST",
          success: function(response) {
          callback(eval("(" + response + ")"));
     }});
};


Static files and aggressive file caching

어플리케이션에서 static_path 설정을 지정해주면 Tornado에서 static file을 사용할 수 있다.

settings = {
    "static_path": os.path.join(os.path.dirname(__file__), "static"),
    "cookie_secret": "61oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=",
    "login_url": "/login",
    "xsrf_cookies": True,
}

application = tornado.web.Application([
    (r"/", MainHandler),
    (r"/login", LoginHandler),
], **settings)

이 설정을 해 주면 /static/으 로 시작하는 모든 request는 자동으로 지정된 static directory를 검색하게 해 준다. 즉 http://localhost:8888/static/foo.png 를 입력하면 지정된 static directory에 있는 foo.png파일을 보여준다. 또한 /robots.txt와 /favicon.ico 의 경우는 설사 /static으로 시작하지 않더라도 자동으로 static directory에 있는 파일을 사용한다.

브라우져가 static resource를 미리 캐슁해 놓으면 페이지 렌더링을 지연시킬 수 있는 불필요한 If-Modified-Since 나 Etag request를 보내는걸 막을 수 있기 때문에 성능 향상에 도움이 된다. Tornado는 이를 위해 기본적으로 static content versioning 이라는 기능을 지원한다.

이 기능을 사용하려면 템플릿에서 HTML파일내에서 static file의 URL을 직접 지정해주는 대신 static_url() 메소드를 사용해야 한다. 

<html>
   <head>
      <title>FriendFeed - {{ _("Home") }}</title>
   </head>
   <body>
     <div><img src="{{ static_url("images/logo.png") }}"/></div>
   </body>
 </html>

static_url() 함수는 상대패스를 /static/images/logo.png?v=aae54 같은 URI로 번역된다. v argument는 logo.png 파일 내용에 대한 해쉬값으로 이게 있으면 Tornado 서버가 사용자의 브라우져로 캐쉬 헤더를 보내 브라우져 캐쉬 내용을 영구적으로 사용할 수 있게 해 준다.

v argument는 파일 내용에 의해 계산되기 때문에 파일을 업데이트 하고 서버를 재시동시키면 새로운 v 값을 보내기 시작하기 때문에 사용자의 브라우져는 자동적으로 새 파일을 가져올 수 있게 된다. 파일의 내용이 바뀌지 않으면 브라우져는 서버에 있는 파일이 업데이트 되었는지 따로 확인하지 않고도 로컬에 캐쉬되어 있는 파일을 계속 사용할 수 있기 때문에 엄청난 렌더링 성능 향상을 가져온다.