태터데스크 관리자

도움말
닫기
적용하기   첫페이지 만들기

태터데스크 메시지

저장하였습니다.

도깨비(tokebi) 더미 API

Daily life/Hard study 2012.07.12 22:19


목표: 아이폰에서 읽고 쓸 수 있는 임의의 저장소 인터페이스를(만) 만드는 것


1. Initial commit
  - put: (app_key, content, tag_list, meta_info)
  - get: (app_key, condition)

  일단 일반적인 모양만 정의하고 세세한 사용은 나중에 정의하기 위하여 메타정보, 조건 등을 dictionary 로 받는걸로 한다. POST 요청만 다루며 GET은 예외를 던져버리도록 하자. 일단 인터페이스를 만드는게 목표이므로 실제로 데이터를 저장하는 부분은 고려하지 않는다.


성능은 고려사항이 아니다. 아이폰 앱을 만들 때 붙여놓을 더미가 필요한 것이므로 django로 후다닥 작성한다. django 1.4 기준이다. 혹시 나중에 필요하게 되면 비동기로 다시 작성한다. 개인적으로 eventlet 에 관심이 ...

  app_key: fixed-length binary
  content: binary
  tag_list: list of strings
  meta_info: dictionary
  condition: dictionary

  URL:
    - /api/v1/put
    - /api/v1/get

  이 저장소는 만들어질 아이폰 앱과는 완전히 별개의 코드베이스이므로 독립적인 git 저장소를 생성한다. dgoon's key value storage -> dkv -> 도깨비 -> tokebi 로 한다. 난 부끄러움이 많으니까 github에 공개 저장소를 만들진 않는다. 하지만 비공개 저장소도 싫으니까 개인서버에 조용히 판다. git.dgoon.net

django 프로젝트(tokebi)를 생성하고 기본 세팅. RDB는 사용하지 않을 예정이므로 DATABASES는 깔끔하게 날린다.
나중에 관리 페이지라던가 다른게 생길 수 있으니 api 라는 애플리케이션을 생성해서 거기에 코드를 넣는다.
위에서 정의한대로 필요한 콜은 put, get 두개이다. 이 각각을 받아줄 endpoint 를 설정한다. 정의되지 않은 URL에 대해서는 일단 404를 알아서 만들도록 둔다. secret key 따위 알게뭐야 흥.

----- api/views.py -----
from django.http import HttpResponse

def api_get(request):
    return HttpResponse('OK', status=200)

def api_put(request):
    return HttpResponse('OK', status=200)


----- tokebi/urls.py -----
...
from api.views import api_get, api_put

urlpatterns = patterns('',
    url(r'^api/v1/get/', api_get),
    url(r'^api/v1/put/', api_put),

…)


이렇게 하고 테스트 서버를 띄워서 접속하면 /api/v1/get, /api/v2/put 에 대해서 OK 를 얻을 수 있다. 일단 여기서 Initial commit 을 해 둔다.


2. Test request

필요한 정보가 모두 있는 경우 OK를, 부족한 경우 500 에러를 내도록 한다. 이게 되어야 개발에 사용할 더미로써 가치가 있다.
그러기 위해선 쿼리를 보낼 수 있어야 한다.

put.py, get.py 를 만든다.

  - put.py: 명령행에서 HOST PORT CONTENT TAG_LIST 를 입력받는다. 메타정보는 일단 {} 를 넣는다.
  - get.py: 명령행에서 HOST PORT 를 입력받는다. 조건에 {} 를 넣는다.

두 경우 모두 app_key 는 'deadbeef' 를 하드코딩한다. 요청은 파이썬으로 작성한다. httplib.HTTPConnection(http://docs.python.org/library/httplib.html#httpconnection-objects) 을 사용하면 되겠지.

----- put.py -----
import sys, httplib, urllib

APP_KEY = 'deadbeef'

if '__main__'==__name__:
    if len(sys.argv)<5:
        print('Usage: %s host port content tag_list' % sys.argv[0])
        sys.exit(0)

    host = sys.argv[1]
    port = int(sys.argv[2])
    content = sys.argv[3]
    tag_list = sys.argv[4:]

    conn = httplib.HTTPConnection(host, port)
    body = {
        'app_key': APP_KEY,
        'content': content,
        'tag_list': tag_list,
        'meta': {},
    }
    header = {
        'Content-type': 'application/x-www-form-urlencoded',
        'Accept': 'text/plain',
    }
    conn.request("POST", "/api/v1/put/", urllib.urlencode(body), header)

    res = conn.getresponse()
    res_body = res.read()
    assert(res.status==200 and res_body=='OK')


그런데 python put.py localhost 2223 HAHAHA tag1 tag2 tag3 를 실행해더니 assertion error가 떳다.

[12/Jul/2012 21:06:55] "POST /api/v1/put/ HTTP/1.1" 403 2282
[12/Jul/2012 21:07:07] "GET /api/v1/put/ HTTP/1.1" 200 2

403이 put.py 로 날린 요청, 200 이 브라우저에서 날린 요청이다. 500도 아니고 403 ?? put.py 에서 res_body 를 찍게 해 봤더니,
csrf관련된 내용이 보인다. 말인 즉슨, cross-site request forgery를 방지하기 위한 미들웨어가 있는데 그녀석을 위한 토큰이 포함되어야 한다나 어쩐다나.
나는 템플릿을 쓸 생각이 없고, 요청에 그런걸 만들어 보내는것도 귀찮으므로 csrf 관련 옵션을 비활성화 해버리겠다. settings.py 에서 csrf 로 검색하면 바로 나온다. MIDDLEWARE_CLASSES 에서 django.middleware.csrf.CsrfViewMiddleware 를 주석처리하고 나니 잘 된다.

api_put 을 고쳐서 들어온 body 를 확인하도록 하자. 콘솔에 그냥 print 를 해보면 볼 수 있다.

----- api_put @ views.py -----
def api_put(request):    method = request.META['REQUEST_METHOD']
    print method
    print request.GET
    print request.POST
    return HttpResponse('OK', status=200)

이렇게 하고 put.py 로 한번, 브라우저로 리프레시 한번 하면 콘솔에 아래와 같이 찍힌다.

POST
<QueryDict: {}>
<QueryDict: {u'content': [u'THIS IS CONTENT'], u'tag_list': [u"['tag1', 'tag2', 'tag3']"], u'meta': [u'{}'], u'app_key': [u'deadbeef']}>
[12/Jul/2012 21:27:37] "POST /api/v1/put/ HTTP/1.1" 200 2
GET
<QueryDict: {}>
<QueryDict: {}>
[12/Jul/2012 21:27:40] "GET /api/v1/put/ HTTP/1.1" 200 2

귀찮으니

  a. content, tag_list, meta, app_key 가 모두 있지 않은 경우
  b. method=POST 가 아닌 경우

에 모두 예외를 던져버리자. … 코드를 만들고 나서 생각해보니 method=POST 강제하는건 왠지 데코레이터 있을 것 같다. 검색 ㄱㄱ

https://docs.djangoproject.com/en/dev/topics/http/decorators/

빙고. 그러면 최종적으로 api_put 의 모양은 아래와 같게 된다.

----- api_put @ views.py -----
from django.views.decorators.http import require_http_methods
class MissingRequiredField(BaseException): pass                                    

PUT_REQUIRED_FIELDS = ['app_key', 'content', 'tag_list', 'meta']

@require_http_methods(["POST"])
def api_put(request):                                                             
    try:                                                                          
        params = request.POST
            if (set(PUT_REQUIRED_FIELDS) - set(params.keys())):
                raise MissingRequiredField('MissingRequiredField')                                                                                                        
        content = params['content']                                               
        app_key = params['app_key']                                               
        tag_list = params['tag_list']                                             
        meta = params['meta']                                                     
                                                                                  
        print(app_key, content, tag_list, meta)                                   
                                                                                  
        return HttpResponse('OK', status=200)                                     
    except BaseException, msg:                                                    
        print('Exception raised: %s' % str(msg))                                  
        return HttpResponse(str(msg), status=500)          

그런데, 콘솔 출력을 보면

(u'deadbeef', u'THIS IS CONTENT', u"['tag1', 'tag2', 'tag3']", u'{}')
                       
이렇게 보이는게, tag_list, meta 가 직렬화된 상태로 넘어온 것 같다. … 음 이거 귀찮은데 …
그냥 요청을 json 으로 받을까. 일단 get.py 도 만들고 나서 고민하자. get.py 는 put.py 의 copy&paste로 뚝딱.
api_get 도 api_put 을 복사붙이기로 작성한다.

views.py: http://git.dgoon.net/?p=tokebi.git;a=blob;f=tokebi/api/views.py;h=09dd4ab585b3be18654fd51356f1ccd1d7f50fdc;hb=c4a018b479157c9e27a6a67c912079e3ebe1be6d
get.py: http://git.dgoon.net/?p=tokebi.git;a=blob;f=sample_query/get.py;h=4d10dabf8d1097464fad6d15bed582366480a2ca;hb=c4a018b479157c9e27a6a67c912079e3ebe1be6d
put.py: http://git.dgoon.net/?p=tokebi.git;a=blob;f=sample_query/put.py;h=1f2f5ef3aef9be70e6758094b3fcdd238ec33619;hb=c4a018b479157c9e27a6a67c912079e3ebe1be6d

오늘은 일단 여기까지. 추후에 요청을 json 으로 받도록 해보자. … 는 나중에 하고, 간단한 persistent layer를 붙여서 아이폰에서 put/get 동작시키는걸 먼저 해야겠다.


tags : , , ,
Trackbacks 0 : Comments 5