태터데스크 관리자

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

태터데스크 메시지

저장하였습니다.

'2012/07'에 해당되는 글 3건

  1. 2012.07.26 MongoDB 읽고쓰기 (6)
  2. 2012.07.12 도깨비(tokebi) 더미 API (5)
  3. 2012.07.05 2012/07/05: Hello world in xcode (5)

MongoDB 읽고쓰기

Daily life/Hard study 2012.07.26 23:10

몽고DB의 C++ 클라이언트는 github 에서 구할 수 있다.

mongodb c++ client: github.com/mongodb/mongo
mongodb 서버는 빌드해서 설치할 수도 있지만 귀찮으니, apt-get 으로 설치하기로 한다.

a. debian: mongodb-server mongodb-dev mongodb-clients mongodb
b. ubuntu: mongodb
c. etc: http://docs.mongodb.org/manual/tutorial/install-mongodb-on-debian-or-ubuntu-linux/

세번째가 제일 나은 듯 하다. 그 와중에 upstart 라는걸 보게 되었다. 나중에 천천히 읽어볼 것: http://upstart.ubuntu.com/
사용한 client 라이브러리는 C++ 2.1.2 이다. API DOC: http://api.mongodb.org/cplusplus/2.1.0/


0. Install
  mongodb server:
  sudo apt-key adv --keyserver keyserver.ubuntu.com --recv 7F0CEB10
  echo "deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen" >> /etc/apt/sources.list
  sudo apt-get update
  sudo apt-get install mongodb-10gen


  mongodb client library:
  wget http://downloads.mongodb.org/cxx-driver/mongodb-linux-x86_64-2.1.2.tgz
  tar zxvf mongodb-linux-x86_64-2.1.2.tgz
  cd mongo-cxx-driver-nightly
  sudo scons install

이렇게 하면 /usr/local 밑에 몽고DB 클라이언트 라이브러리가 설치된다.
/usr/local/include/mongo 아래쪽 권한이 좀 이상하게 설치되어서 sudo chmod 755 /usr/local/include/mongo -R 을 해 주었다.
언제나 그런건지, scons 사용을 잘못한건지는 잘 모르겠다.

C++ 튜토리얼은 이걸 보면 된다. http://www.mongodb.org/pages/viewpage.action?pageId=133415
이 아래의 내용은 기본적으로 튜토리얼 내용을 그냥 따라간 로그이다.

1. Connect
#include <cstdio>
#include "client/dbclient.h"
using namespace std;

int main() {
    try {
        mongo::DBClientConnection c;
        c.connect("localhost");
    }
    catch (mongo::DBException& e) {
        printf("Error: %s\n", e.what());
    }
    return 0;
}


g++ -I/usr/local/include/mongo test.cpp -lboost_system -lboost_thread -lboost_filesystem -lmongoclient -o test

이렇게 빌드하면 ./test 를 실행했을때 아무일도 안 일어나게 된다. localhost 를 remotehost 같은걸로 바꾸고 해보면 에러가 나는걸 확인할 수 있다.


$ ./test
Thu Jul 26 21:36:19 getaddrinfo("remotehost") failed: No address associated with hostname
Error: can't connect couldn't connect to server remotehost:27017

2. Insert
접속은 했으니 이제 읽기/쓰기를 해보자. 아직 아무것도 들어있는게 없으므로, 먼저 쓰기를 하고 나서 쓴 값을 읽어보도록 하자. 몽고DB는 데이터를 BSON이라는 형식으로 저장하는데, JSON이랑 닮은 - 정확히는 json with custom extension - 포맷이다. 방법은 간단하다. BSON 객체를 생성 -> DB에 추가, 하면 된다.

a. BSON 객체 생성
BSON 객체는 BSONObjBuilder 를 사용해서 만든다. 사용법은 아래와 같다.

BSONObjBuilder builder;
builder.append("HERE_GOES_KEY", "HERE_GOES_VALUE");
builder.append("name", "dgoon");
BSONObj obj = builder.obj();

이걸 아래와 같이 체이닝 할 수도 있다

BSONObj obj = BSONObjBuilder().append("HERE_GOES_KEY", "HERE_GOES_VALUE").append("name", "dgoon").obj();

하지만 난 여러 줄에 풀어 쓰는걸 선호한다.
그 외에 stream 체이닝이나, Object ID 를 명시적으로 지정하는 방법 등 몇 가지 화두가 더 있지만 과감하게 생략.

b. DB 에 추가
이렇게 만든 BSONObj 객체를 DB에 넣기 위해서는,

conn.insert("database.collection", obj);

를 해주면 된다. database, collection 은 무엇인가! 대충 RDB 개념을 빌리자면 database=database, collection=table 정도로 생각하면 된다.

#include <cstdio>
#include "client/dbclient.h"
using namespace std;

int main() {
    try {
        mongo::DBClientConnection c;
        c.connect("localhost");

        mongo::BSONObjBuilder b;
        b.append("name","dgoon");
        b.append("addr", "nakseongdae");
        mongo::BSONObj p=b.obj();

        c.insert("mydb.character", p);
    }
    catch (mongo::DBException& e) {
        printf("Error: %s\n", e.what());
    }
    return 0;
}


소스코드를 이렇게 수정하고 나서 실행해보면, 역시 아무런 말도 없다. 잘 된거겠지… 확인을 해보자. 읽는 코드를 만들기 전에 잠깐 shell 에서 보도록 한다.

c. SHELL

몽고DB는 강력한지는 잘 모르겠고, 여튼 쉘 환경이 있다. 위의 c로 설치했다면 mongo 라는 커맨드가 있을 것이다.

dgoon@katy:~/works/mongo-cxx-driver-nightly$ mongo
MongoDB shell version: 2.0.6
connecting to: test
>

위에서 mydb.character 에 데이터를 넣었던 것이 기억나는가? db=mydb, collection=character 이다.

> show dbs
local     (empty)
mydb     0.078125GB
> use mydb
switched to db mydb
> show collections
character
coll
system.indexes
things
> db
mydb
> db.character
mydb.character
> db.character.find()
{ "_id" : ObjectId("50113dbb9aa68f690df58a39"), "name" : "dgoon", "addr" : "nakseongdae" }
{ "_id" : ObjectId("50113dbe9aa68f690df58a3a"), "name" : "dgoon", "addr" : "nakseongdae" }
{ "_id" : ObjectId("50113dbf9aa68f690df58a3b"), "name" : "dgoon", "addr" : "nakseongdae" }
>

이렇게 들어가 있다. … 왜 3개냐구? 내가 실행을 3번 했으니까. … 그렇다, 딱히 KEY 를 지정해 준 것이 아니기 때문에 여러번 추가해도 중복이 아니므로, 실행한 횟수만큼 여러개가 들어간다.


3. Retrieve

읽어오는건 코드가 조금 더 길다. 읽어올 조건과 함께 query를 날린다 -> 커서를 받는다 -> 커서를 순회하며 데이터를 꺼낸다, 순서로 작업하면 된다.

데이터를 꺼내오기 위해 명시해 주어야 하는 것은, 어디에서? 어떤 녀석을? 이다. connection.query 는 2개의 인자를 받는데, 하나는 어디에서? 또 하나는 어떤 녀석을? 에 대한 것이다. 여기서 조건 역시 BSONObj 타입으로 들어간다. 그냥 빈 객체를 넣으면 모두 다 꺼내오라는 뜻이다. BSONObj 객체의 각 필드가 AND로 조합되어 조건으로 들어간다고 한다. 우리는 모두다 꺼내와보자.

test.cpp 는 이제 아래와 같이 길어졌다.

#include <cstdio>
#include "client/dbclient.h"
using namespace std;

int main() {
    try {
        mongo::DBClientConnection c;
        c.connect("localhost");

        mongo::BSONObjBuilder b;
        b.append("name","dgoon");
        b.append("addr", "nakseongdae");
        mongo::BSONObj p=b.obj();

        c.insert("mydb.character", p);

        auto cursor=c.query("mydb.character",
                            mongo::BSONObj());
        while (cursor->more()) {
            printf("%s\n", cursor->next().toString().c_str());
        }
    }
    catch (mongo::DBException& e) {
        printf("Error: %s\n", e.what());
    }
    return 0;
}


auto는 타이핑을 줄이기 위해 사용했는데, 정확한 타입은 auto_ptr<mongo::DBClientCursor> 이다. auto_ptr을 사용한 이유는 소유권을 넘기면서 객체 소멸을 라이브러리 사용자(caller)에게 넘기고 난 귀찮으니 잊어버리겠어 -_- 라는 것이라 생각한다. auto 사용때문에 아래 컴파일 옵션에 -std=c++0x 가 추가된 것에 주의하자.

dgoon@katy:~/works/mongo-cxx-driver-nightly$ g++ -I/usr/local/include/mongo test.cpp -lboost_system -lboost_thread -lboost_filesystem -lmongoclient -std=c++0x -o test
goon@katy:~/works/mongo-cxx-driver-nightly$ ./test
{ _id: ObjectId('50113dbb9aa68f690df58a39'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('50113dbe9aa68f690df58a3a'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('50113dbf9aa68f690df58a3b'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('501140789aa68f690df58a3c'), name: "dgoon", addr: "nakseongdae" }
goon@katy:~/works/mongo-cxx-driver-nightly$ ./test
{ _id: ObjectId('50113dbb9aa68f690df58a39'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('50113dbe9aa68f690df58a3a'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('50113dbf9aa68f690df58a3b'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('501140789aa68f690df58a3c'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('501140799aa68f690df58a3d'), name: "dgoon", addr: "nakseongdae" }
goon@katy:~/works/mongo-cxx-driver-nightly$ ./test
{ _id: ObjectId('50113dbb9aa68f690df58a39'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('50113dbe9aa68f690df58a3a'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('50113dbf9aa68f690df58a3b'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('501140789aa68f690df58a3c'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('501140799aa68f690df58a3d'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('5011407a9aa68f690df58a3e'), name: "dgoon", addr: "nakseongdae" }
goon@katy:~/works/mongo-cxx-driver-nightly$ ./test
{ _id: ObjectId('50113dbb9aa68f690df58a39'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('50113dbe9aa68f690df58a3a'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('50113dbf9aa68f690df58a3b'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('501140789aa68f690df58a3c'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('501140799aa68f690df58a3d'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('5011407a9aa68f690df58a3e'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('5011407b9aa68f690df58a3f'), name: "dgoon", addr: "nakseongdae" }
goon@katy:~/works/mongo-cxx-driver-nightly$

실행할 때마다 엔트리가 하나씩 늘어나면서 가져오는 갯수도 늘어나고 있다. 참고로 b.append("age", 18); 처럼 value 에는 문자열 이외의 타입도 들어갈 수 있다.


4. Update

데이터를 가져오는 것과 별로 다르지 않다. 어디에 있는? 어떤 아이들에? 어떤 값을? 을 지정해주면 된다.


#include <cstdio>
#include "client/dbclient.h"
using namespace std;

int main() {
    try {
        mongo::DBClientConnection c;
        c.connect("localhost");

        c.update("mydb.character", // 어디에 있는?
                 BSON("age" << 18),  // 어떤 아이들에게?
                 BSON("$inc" << BSON("visits" << 1)));  // 어떤 값을??

        auto cursor=c.query("mydb.character",
                            mongo::BSONObj());
        while (cursor->more()) {
            printf("%s\n", cursor->next().toString().c_str());
        }
    }
    catch (mongo::DBException& e) {
        printf("Error: %s\n", e.what());
    }
    return 0;
}

이렇게 하면 mydb.character 에서 age=18 인 아이들을 찾아서(설명엔 없지만 위에서 몇개 추가해 두었다) visits 을 1 증가시킨다고 한다. $inc 가 뭔가 특별한 의미가 있나보다. 그래서 위의 프로그램을 여러번 실행하면,

dgoon@katy:~/works/mongo-cxx-driver-nightly$ ./test
{ _id: ObjectId('50113dbb9aa68f690df58a39'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('50113dbe9aa68f690df58a3a'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('50113dbf9aa68f690df58a3b'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('501140789aa68f690df58a3c'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('501140799aa68f690df58a3d'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('5011407a9aa68f690df58a3e'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('5011407b9aa68f690df58a3f'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('5011421d9aa68f690df58a40'), addr: "nakseongdae", age: 18, name: "dgoon", visits: 10 }
{ _id: ObjectId('5011421f9aa68f690df58a41'), addr: "nakseongdae", age: 18, name: "dgoon", visits: 1 }
dgoon@katy:~/works/mongo-cxx-driver-nightly$ ./test
{ _id: ObjectId('50113dbb9aa68f690df58a39'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('50113dbe9aa68f690df58a3a'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('50113dbf9aa68f690df58a3b'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('501140789aa68f690df58a3c'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('501140799aa68f690df58a3d'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('5011407a9aa68f690df58a3e'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('5011407b9aa68f690df58a3f'), name: "dgoon", addr: "nakseongdae" }
{ _id: ObjectId('5011421d9aa68f690df58a40'), addr: "nakseongdae", age: 18, name: "dgoon", visits: 11 }
{ _id: ObjectId('5011421f9aa68f690df58a41'), addr: "nakseongdae", age: 18, name: "dgoon", visits: 1 }

이런 결과가 나온다. 처음 한 번은 5011421f9aa68f690df58a41 를 고치더니 그 후로는 5011421d9aa68f690df58a40 를 고치고 있다. 어어?
… 요약하면 해당 조건에 맞는 데이터가 여러개 있는 경우에, 그 중 임의로 하나를 선택하여 갱신, 한다고 생각한다.
혹은 선택 기준은 있지만, 갱신할 때마다 선택되는 아이들이 달라지게 될 수도 있다. 여튼 곤란.

uniqueness 를 보장하기 위해서는 매번 업데이트 할 때마다 query를 보내어 확인을 한번 해 주어야 하는건가?

조건에 맞는 엔트리가 하나도 없는 경우는 아무일도 일어나지 않는다.



+
http://api.mongodb.org/cplusplus/2.1.0/dbclient_8h_source.html


ensureIndex의 함수 시그니쳐는 다음과 같다. unique=false가 기본값인데, true를 줄 수 있다.


virtual bool ensureIndex( const string &ns , BSONObj keys , bool unique = false, const string &name = "", bool cache = true, bool background = false, int v = -1 );


주의할 점은 특정 key에 unique=true가 걸리면 그 키가 동일한 경우는 물론 이고, 해당 키가 "없는 것도 하나의 값" 이 되어 버린다. 즉 ensureIndex("mydb.character", fromjson("{age:1}"), true); 로 인덱스를 생성했다면

mydb.character 에 age 가 없는 데이터는 단 하나밖에 존재할 수 없다

는 사실.


+ 도깨비의 백엔드 저장소로 몽고디비 사용할 예정.


Trackbacks 0 : Comments 6
  1. zzugg 2012.07.27 02:07 Modify/Delete Reply

    c++ client에는 sparse 옵션이 없나보네요?

  2. Favicon of http://blog.shurain.net 슈레인 2012.07.27 02:19 Modify/Delete Reply

    Mongo DB is Web Scale. http://youtu.be/b2F-DItXtZs

  3. Favicon of http://9901ccgenevois.com/clfrance.php christian louboutin 2013.07.14 09:05 Modify/Delete Reply

    당신은 내가사랑할 만한 사람이 아니예요,사랑하지 않으면 안될 사람이예요.

  4. Favicon of http://18028hiphopweekly.com/mag/thankhot.php nike shoes 2013.07.14 20:33 Modify/Delete Reply

    당신 매력있어, 자기가 얼마나 매력있는지 모르는게 당신매력이야

Write a comment


도깨비(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 : Django, REST, tokebi, web
Trackbacks 0 : Comments 5
  1. haru 2012.07.13 11:23 Modify/Delete Reply

    취미 프로젝트 하시는 건가요?!

  2. Favicon of http://applefarm.pe.kr etermory 2012.07.24 11:23 Modify/Delete Reply

    나중에 낼름 해가야지. ㅎㅎ

  3. Favicon of http://ad.longchampoutk.com/ longchamp 2013.04.01 19:56 Modify/Delete Reply

    아름다운 여자가 해바라기하는 걸 좋아해요

  4. Favicon of http://da.christianlouboutinfrx.com/ louboutin pas cher 2013.04.02 23:47 Modify/Delete Reply

    아름다운 여자가 해바라기하는 걸 좋아해요

Write a comment


2012/07/05: Hello world in xcode

Daily life/Hard study 2012.07.05 22:50

1. 일단 화면에 hello world을 출력하는 간단한 앱을 만들어보자.

2. 텍스트를 입력받고 버튼을 누르면 뒤집힌 텍스트를 출력하는 앱으로 업그레이드 해보자.

-----

Create new project를 누르면 application 타입을 물어본다. 뭐 이런 것들이 있다:
  Master-detail
  OpenGL
  Page-based
  Single view
  Tabbed
  Utility
  Empty

일단 헤딩이니까 Empty로 해보자
  project name: helloworld
  company name: dgoon
  class prefix: DGOON
  - 헐 자동으로 local git 저장소를 만드네?

프로젝트 생성하고 그냥 실행을 하면 (enable developer mode? 를 물어본다)
  run/stop 버튼 오른쪽에 target device를 설정할 수 있는 select box가 있다. ipad는 너무 크니까 iphone 으로 한다
  실행하면 아무것도 없는 하얀 화면이 나옴

프로그렘 진입은 main.m 파일이다. 하는 일은 UIApplicationMain 클래스를 만드는 것 뿐이다.

-> UIKIT_EXTERN int UIApplicationMain(int argc, char *argv[], NSString *principalClassName, NSString *delegateClassName);

DGOONAppDelegate 가 결국에는 모든 일을 다 하는 녀석인데, 이는 DGOONAppDelegate.h/m 파일에 있다.
결국 우리는 UIAppilcationDelegate를 상속받아 클래스를 만들어서 UIApplicationMain에게 던져주면 되는것이다!

DGOONAppDelegate.m 을 보면 몇 개의 함수들이 정의되어 있다.

  didFinishLaunchingWithOptions
 applicationWillResignActive
 applicationDidEnterBackground
 applicationWillEnterForeground
 applicationDidBecomeActive
 applicationWillTerminate

이름을 보면 하는 일들은 명확하다. 각 함수 안에 있는 주석을 읽으면 도움이 되겠지.
나는 didFinishLaunchingWithOptions 에서 화면에 helloworld만 찍어주면 된다.

didFinishLaunchingWithOptions 함수를 보면 self.window 를 만든 다음에 makeKeyAndVisible 을 불러준다.
검색해보니 key window 란 입력 이벤트를 받는 윈도우다. 즉, 화면에 보이게 하고 터치나 키 입력 등의 이벤트를 저 윈도우로 받게 세팅하는 것이다.

self.mytext 를 만들어서 저기에 "helloworld" 를 넣은 후에 보이게 하면 될것같다.

self.window 는 DGOONAppDelegate.h 에 선언되어 있는데,

@property (strong, nonatomic) UIWindow *window;

이런 모양이다. property 를 좀 검색해서 찾아보았다. http://maclove.pe.kr/28 이 문서에 나름 잘 설명되어 있는듯. …
그런데 strong 에 대한 설명은 없다! 검색해보니 최근에 추가된 것 같은데 retain 의 대체이며, reference counting 에 관련된 속성이라 한다.

헤더(.h)에는 이렇게:

@property (strong, nonatomic) UITextView *mytext;

구현(.m)에는 이렇게:

@synthesize mytext = _mytext;

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions

{

    self.window = [[UIWindowalloc] initWithFrame:[[UIScreenmainScreen] bounds]];

    // Override point for customization after application launch.

    

    self.mytext = [[UITextViewalloc] initWithFrame: CGRectMake(100, 100, 200, 200)];

    self.mytext.text = @"HelloWorld!";
    [self.window addSubview:self.mytext];

        

    self.window.backgroundColor = [UIColor whiteColor];

    [self.window makeKeyAndVisible];

    returnYES;

}


추가하였다. cmd+b 로 빌드는 성공. cmd+r 로 실행해보면 옆으로 누워있는 아이폰에 HelloWorld! 라고 쓰여있는게 보인다.
cmd+방향키로 오른쪽 왼쪽으로 돌릴 수 있다. textview는 기본적으로 수정 가능한가보다. 클릭을 하면 키보드가 뜨고
키입력을 하면 뭔가 써 넣을 수도 있고, select, copy 등도 가능하다. 수정 못하게 해보자.

    [self.mytext setEditable:false];

mytext생성 후에 이렇게 한줄 넣어주면 된다. xcode의 자동완성이 아주 도움이 많이 된다. 있을법한 이름을 때려 넣으니까 나온다.
수정은 불가능한데 여전히 select, copy 등은 가능하다. 이것도 막고 싶은데 setSelectable 은 없는 듯 하다. 나중에 찾아본다. …

helloworld 찍는 앱 완성.

-----

2번으로 넘어가기 전에, 테스트를 좀 해보자. self.mytext 에 스트링을 바꿔 넣으면 바로 반영이 되는가?
applicationDidEnterBackground 함수에 아래와 같이 한 줄을 넣으면,

    self.mytext.text = @"enter background!";

처음 실행할 때에는 HelloWorld! 가 보이고, 홈버튼을 누른 후에 다시 아이콘을 클릭하면 enter background! 로 텍스트가 바뀐다.
그런데, 다시 시작하니까 화면이 누워있다. 이거 기본 로테이션을 바꿀 수 없나?

왼쪽 project tree view의 루트를 클릭하고,  Target Helloworld -> Info -> Supported interface orientations 에서 item 0/1/2 순서를 바꿔주면 된다. Portrait를 0번으로 놓았다.

-----

2번을 위해서는

  a. UITextView 2개를 넣어서 하나는 입력(a), 하나는 출력(b)에 쓴다.
  b. UIButtonView (가 있나?) 를 만들어서 콜백함수를 작성 -> 자동완성을 보니 UIButton 이 나온다.
  c. 버튼의 콜백함수에서 (a)를 읽어 뒤집은 후 (b)에 넣는다.

를 하면 된다. 수정해보자.

위에서 추가했던 mytext 를 지우고, 대신 이런 아이들을 넣는다.

.h ->

@property (strong, nonatomic) UITextView *input_text;

@property (strong, nonatomic) UITextView *output_text;

@property (strong, nonatomic) UIButton *reverse;

.m ->

@synthesize input_text = _input_text;

@synthesize output_text = _output_text;

@synthesize reverse = _reverse;


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions

{

    self.window = [[UIWindowalloc] initWithFrame:[[UIScreenmainScreen] bounds]];

    // Override point for customization after application launch.

    

    self.input_text = [[UITextViewalloc] initWithFrame: CGRectMake(100, 100, 100, 20)];

    self.input_text.text = @"INPUT";

    [self.input_textsetScrollEnabled:false];


    self.output_text = [[UITextViewalloc] initWithFrame: CGRectMake(100, 200, 100, 20)];

    [self.output_textsetEditable:false];

    [self.output_textsetScrollEnabled:false];

    self.output_text.text = @"OUTPUT";

    

    self.reverse = [UIButtonbuttonWithType: UIButtonTypeRoundedRect];

    self.reverse.frame = CGRectMake(100, 150, 100, 20);

    [self.reversesetTitle:@"REVERSE!"forState:UIControlStateNormal];

    [self.reverseaddTarget:selfaction:@selector(reverse_output) forControlEvents:UIControlEventTouchUpInside];

    

    [self.windowaddSubview:self.input_text];

    [self.windowaddSubview:self.output_text];

    [self.windowaddSubview:self.reverse];

        

    self.window.backgroundColor = [UIColorwhiteColor];

    [self.windowmakeKeyAndVisible];

    returnYES;

}


- (void)reverse_output

{

    self.output_text.text = @"CLICKED!";

}

이렇게 하면 버튼을 눌렀을 때 output_text 가 CLICKED! 로 바뀌는 화면이 만들어진다.
여기서 reverse_output 을 수정해서 입력 텍스트를 뒤집게만 하면 된다. 일단 NSString 에 reverse 메소드가 있는지 확인을 해본다.
없다. … 검색을 해보니 … … ?? 뭐 이리 복잡해 으아앙 멘붕.

그냥 파일 이름을 DGOONAppDelegate.mm 으로 바꾸고 제일 위에

#include <algorithm>

#include <string>

using namespace std;

이걸 추가하고 나서 reverse_output 을 아래와 같이 고쳐버렸다. … C++이 최고다. orz

- (void)reverse_output

{

    std::string s([self.input_text.textUTF8String]);

    std::reverse(s.begin(), s.end());

    self.output_text.text = [NSStringstringWithUTF8String: s.c_str()];

}

이게 한글, 일본어같은 문자열에 대해서는 (당연히) 동작을 안할텐데 일단 이렇게 놓고 오늘은 끝.
어떻게 해야 쉽고 안전하고 이식성 있게 뒤집을 수 있는가!?


'Daily life > Hard study' 카테고리의 다른 글

MongoDB 읽고쓰기  (6) 2012.07.26
도깨비(tokebi) 더미 API  (5) 2012.07.12
2012/07/05: Hello world in xcode  (5) 2012.07.05
An introduction into SICP  (6) 2011.09.03
Reading: How To Design Programs(HTDP)  (0) 2011.07.13
D 프로그래밍 언어 - 오타(?)  (3) 2011.05.12
Trackbacks 0 : Comments 5
  1. Favicon of http://rein.kr rein 2012.07.05 23:56 Modify/Delete Reply

    utf-32 에서 뒤집으시면 (도망간다)

  2. haru 2012.07.06 13:42 Modify/Delete Reply

    이미 찾아보신 방법인지는 모르겠지만...

    기본적으로 NSString은 immutable 이기 때문에 in-place로 뒤집는 것은 안되고, 다른 스트링으로 뒤집힌 결과를 저장하되 루프를 사용하지 않는 방법을 찾아봤습니다.

    NSString *s = @"헬로우월드";
    NSMutableString *rs = [NSMutableString stringWithString:@""];
    NSRange fullRange = [s rangeOfString:s];
    NSStringEnumerationOptions enumerationOptions = (NSStringEnumerationReverse | NSStringEnumerationByComposedCharacterSequences);
    [s enumerateSubstringsInRange:fullRange
    options:enumerationOptions
    usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL * stop){
    [rs appendString:substring];
    }];

    http://stackoverflow.com/questions/4989562/print-reverse-of-a-string
    (이글의 마지막 답변이 제가 사용한 방법입니다.)

    참고로 lambda와 유사한 기능을 objective-c에서는 block이란 이름으로 제공합니다. 위 enumerateSubstringsInRange:options:usingBlock: 에서 마지막 인자로 사용한 것이 block 입니다.

    일하기 싫으니까 이런 거나 찾아보고 있고... ;;; 근데 어인일로 xcode를...

    (comment는 indent가 안 먹네요 ㅠㅠ)

    • dgoon 2012.07.06 11:08 Modify/Delete

      네, 저거 찾아보고 멘붕왔어요. 으흑으흑 ㅠㅠ
      xcode는 그냥 다시 손대봤어요.
      전에 배워둔게 쪼끔은 있어서 어떻게든 혼자 삽질할 만은 하네요. ㅎㅎㅎ

  3. Favicon of http://15481hiphopweekly.com/html/lv_p2.html louis vuitton outlet 2013.07.15 06:06 Modify/Delete Reply

    다른 남자 부르면서 울거면 나한테 이쁘지나 말던지

  4. Favicon of http://7451taxfreebicycle.com/lvus.php louis vuitton outlet 2013.07.16 09:24 Modify/Delete Reply

    좋으면 좋고 싫으면 싫은 거지, 뭐가 이렇게 어렵고 복잡하냐구

Write a comment