블로그 이미지
평범하게 살고 싶은 월급쟁이 기술적인 토론 환영합니다.같이 이야기 하고 싶으시면 부담 말고 연락주세요:이메일-bwcho75골뱅이지메일 닷컴. 조대협


Archive»


 
 

빠르게 훝어 보는 node.js - mongoose 스키마와 유용한 기능


조대협 (http://bcho.tistory.com)

지난번 mongoose 에 대한 간략한 소개 글 http://bcho.tistory.com/1094 에 이어서 오늘은 mongoose 스키마와, 기타 유용한 기능에 대해서 소개하고자 한다.

 쿼리

 

간단한 삽입,삭제,수정,조회 쿼리이외에 조금 향상된 쿼리를 살펴보자.

자세한 쿼리 사용 방법은 http://mongoosejs.com/docs/documents.html 참고하면 된다.

 

가지 쿼리들을 살펴보면

 

 

var mongoose = require('mongoose');

mongoose.connect('mongodb://localhost:27017/mydb');

var userSchema = mongoose.Schema({

      userid: String,

      sex : String,

      city : String,

      age : Number

});

 

var User = mongoose.model('users',userSchema);

 

// select city from users where userid='terry'

User.findOne({'userid':'terry'}).select('city').exec(function(err,user){

      console.log("q1");

      console.log(user+"\n");

      return;

});

 

 

// select * from users where city='seoul' order by userid limit 5

User.find({'city':'seoul'}).sort({'userid':1}).limit(5).exec(function(err,users){

      console.log("q2");

      console.log(users+"\n");

      return;

});

 

// using JSON doc query

// select userid,age from users where city='seoul' and age > 10 and age < 29

User.find({'city':'seoul', 'age':{$gt:10 , $lt:29}})

      .sort({'age':-1})

      .select('userid age')

      .exec(function(err,users){

           console.log("q3");

           console.log(users+"\n");

           return;

});

 

//using querybuilder

//select userid,age from users where city='seoul' and age > 10 and age < 29

User.find({})

      .where('city').equals('seoul')

      .where('age').gt(10).lt(29)

      .sort({'age':-1})

      .select('userid age')

      .exec(function(err,users){

           console.log("q4");

           console.log(users+"\n");

           return;

});

 

Figure 20 mongoose 쿼리 예제

 

코드 부분에서는

var mongoose = require('mongoose');

mongoose.connect('mongodb://localhost:27017/mydb');

var userSchema = mongoose.Schema({

      userid: String,

      sex : String,

      city : String,

      age : Number

});

 

var User = mongoose.model('users',userSchema);

 

mongoose 모듈을 로딩하고, mongodb 연결한다. 다음 스키마를 정의 한다. userid, sex, city 문자열로 가지고, age라는 필드를 숫자로 갖는다. 스키마 정의가 끝났으면 스키마를 이용해서 users 컬렉션에 대해서 User 모델 객체를 생성한다.

 

첫번째 쿼리를 살펴보자

// select city from users where userid='terry'

User.findOne({'userid':'terry'}).select('city').exec(function(err,user){

      console.log("q1");

      console.log(user+"\n");

      return;

});

 

첫번째 쿼리는 userid terry 도큐먼트를 쿼리해서 그중에서 city 라는 이름의 필드만 리턴하는 쿼리이다.

실행 결과는 다음과 같다.

 

q1

{ city: 'seoul', _id: 56e62f2c1a2762d26afa6053 }

 

두번째 쿼리를 살펴보자

// select * from users where city='seoul' order by userid limit 5

User.find({'city':'seoul'}).sort({'userid':1}).limit(5).exec(function(err,users){

      console.log("q2");

      console.log(users+"\n");

      return;

});

 

다음은 실행결과이다.

 

q2

{ age: 18,

  city: 'seoul',

  sex: 'female',

  userid: 'cath',

  _id: 56e62f351a2762d26afa6054 },{ age: 23,

  city: 'seoul',

  sex: 'female',

  userid: 'stella',

  _id: 56e62f3c1a2762d26afa6055 },{ age: 29,

  city: 'seoul',

  sex: 'male',

  userid: 'terry',

  _id: 56e62f2c1a2762d26afa6053 },{ age: 27,

  city: 'seoul',

  sex: 'female',

  userid: 'yuna',

  _id: 56e62f411a2762d26afa6056 }

 

users 컬렉션에서 city 필드가 seoul 도큐먼트를 조회한후 결과를 sort({‘userid’:1}) 이용하여 userid 따라 오름차순으로 정렬한 후에, limit(5) 이용하여 결과중 5개만 리턴한다.

 

세번째 쿼리를 보면

// using JSON doc query

// select userid,age from users where city='seoul' and age > 10 and age < 29

User.find({'city':'seoul', 'age':{$gt:10 , $lt:29}})

      .sort({'age':-1})

      .select('userid age')

      .exec(function(err,users){

           console.log("q3");

           console.log(users+"\n");

           return;

});                                 

 

users 컬렉션에서 city seoul이고, age 10보다 크고, 29보다 작은 도큐먼트를 조회한 sort 이용하여 내림 차순으로 정렬을 , select 이용하여 userid age 필드만 리턴한다

다음은 쿼리 실행결과이다.

 

q3

{ age: 23, userid: 'stella', _id: 56e62f3c1a2762d26afa6055 },{ age: 18, userid: 'cath', _id: 56e62f351a2762d26afa6054 }

 

마지막 네번째 쿼리를 보면

//using querybuilder

//select userid,age from users where city='seoul' and age > 10 and age < 29

User.find({})

      .where('city').equals('seoul')

      .where('age').gt(10).lt(29)

      .sort({'age':-1})

      .select('userid age')

      .exec(function(err,users){

           console.log("q4");

           console.log(users+"\n");

           return;

});

 

세번째 쿼리와 내용을 같으나, 검색 조건을 find() 안에서 json으로 정의하지 않고, 밖에 where문으로 빼서 정의했다. 이를 쿼리 빌더라고 하는데, mongoose where문에 따라서 자동으로 쿼리를 생성해준다.

.where('city').equals('seoul') city필드가 seoul 도큐먼트를 조회하고

.where('age').gt(10).lt(29) age 10보다 크고, 29보다 작은 도큐먼트를 조회하도록 한다.

다음은 쿼리 수행 결과이다.

 

q4

{ age: 23, userid: 'stella', _id: 56e62f3c1a2762d26afa6055 },{ age: 18, userid: 'cath', _id: 56e62f351a2762d26afa6054 }

 

예제 코드는 https://github.com/bwcho75/nodejs_tutorial/tree/master/mongoosequeryexample 저장되어 있다.

 

 데이타 유효성 검증

 

mongoose 가지고 있는 유용한 기능중의 하나가 validator라는 데이타 유효성 검증이다.

모델 객체에 들어갈 데이타 형뿐 아니라, 데이타의 규약등을 지정할 있는 기능인데, 예를 들어 문자열의 길이나, 특수문자 지원 여부등을 검증할 있다.

 

앞에서 만들었던 mongo.js에서 userSchema 부분을 다음과 같이 수정해보자

var mongoose = require('mongoose');

//define validator

function NameAlphabeticValidator(val){

    return val.match("^[a-zA-Z\(\)]+$");

}

function StringLengthValidator(val){

    if(val.length>10) return null;

    return val;

}

 

//define scheme

var userSchema = mongoose.Schema({

      userid: {type:String,validate:NameAlphabeticValidator},

      sex : String,

      city : {type:String,validate:[

                                    {validator:NameAlphabeticValidator,msg:'city should be alphabet only'},

                                    {validator:StringLengthValidator,msg:'city length should be less than 10'}

                                   ]

              }

      });

Figure 21 mongoose validator 예제

 

개의 validator 정의하였다. 하나는 알파벳만 허용하는 NameAlphabeticValidator이고, 다른 하나는 문자열의 길이가 10 이하인 것만 통과 시키는 StringLengthValidator이다.

Validator 정의는 간단하게 function(value)형태로 정의한다. 검증하고자 하는 값이 value라는 인자를 통해서 들어오고, 만약 검증을 통과하면 값을 리턴하면 되면, 실패하면 null 리턴하면 된다.

 

선언된 validator 스키마에 적용해보자.

validator 적용하는 방법은 스키마에서 필드의 데이타 타입을 지정하는 부분에서 위와 같이 데이타 타입을 지정한 , 뒷부분에 validate라는 키워드를 이용하여, 앞서 정의한 validator 명을 지정해주면 된다.

 

userid: {type:String,validate:NameAlphabeticValidator},

 

또는 다음과 같이 하나의 데이타 필드에 배열[] 이용하여 동시에 여러개의 validator 적용할 있다.

다음 코드는 city 필드에 NameAlphabeticValidatorStringLengthValidator 두개를 동시에 적용한 코드이다.

city : {type:String,validate:[

                                    {validator:NameAlphabeticValidator,msg:'city should be alphabet only'},

                                    {validator:StringLengthValidator,msg:'city length should be less than 10'}

                                   ]

 

validator 지정할때 위의 예제와 같이 msg 같이 정의하면, 데이타에 대한 유효성 검증이 실패했을때 나는 메세지를 정의할 있다.

 

다음은 예제에서 city이름에 10자이상의 문자열을 넣는 화면이다.

validator 의해서 유효성 검증이 실패하고, console.log 에러 메세지가 출력된 내용이다.



Figure 22 city 필드에 10자가 넘는 문자열을 입력하는 화면

 

다음은 validator 의해서 city 필드의 유효성 검사가 실패하고, console.log 에러 메세지가 출력된 화면이다.

 



Figure 23 validator에 의해서 city 필드 유효성 검증이 실패한 결과

 

이렇게 validator 만들어 사용하는 이외에도, mongoose에서는 데이타 타입별로 미리 정해놓은 validator 들이 있다.

예를 들어 Number 타입의 경우 min,max 예약어를 이용하여 타입 정의시 값의 유효 범위를 지정해놓을 있다.

age:     { type: Number, min: 18, max: 65 },

String 경우 RegularExpression 이용해서 문자열의 형태를 지정할 있고, maxlength 이용하여 전체 문자열 길이에 대한 제약을 있다.  데이타 타입별로 미리 정의되어 있는 validator http://mongoosejs.com/docs/schematypes.html 참고하기 바란다.

 

 SetterGetter, Default


mongoose에서는 스키마의 필드에 대해서 Setter Getter 저장할 있고, 데이타를 저장하지 않았을 경우에는 디폴트 값을 지정할 있다.

Setter 데이타 객체에 데이타를 저장할때, 실행되는 메서드로 데이타를 저장하기전 변환하는 역할을 수행할 있다.

아래 코드를 보자

 

var mongoose = require('mongoose');

 

// setter function

function upperCase (val) {

        return val.toUpperCase();

      }

 

var HelloSchema = new mongoose.Schema(

           { name : { type:String, default:'Hello Terry',set:upperCase} }

           );

 

// default test

var Hello = mongoose.model('hello',HelloSchema);

var hello = new Hello();

 

console.log(hello);

 

// setter test

hello.name="Upper case setter example";

console.log(hello);

 

Figure 24 mongoose setter 예제

 

{ name : { type:String, default:'Hello Terry',set:upperCase} } 코드 부분을 보면 “default”라는 키워드로 “Hello Terry” 라는 값을 지정하였다. name 필드는 별도의 값이 지정되지 않으면 “Hello Terry”라는 문자열을 디폴트 값으로 갖는다.

 

다음 set:upperCase , Setter 지정하는 부분으로, Setter “set:{Setter 함수}” 명으로 지정한다. 여기서 사용된 Setter 위에 코드에서 정의한 upperCase 라는 함수로, 값을 지정하면 문자열의 모든 알파벳을 대문자로 바꿔서 저장한다.

 

위의 예제 실행 결과를 보자

 

{ name: 'Hello Terry', _id: 56f94e5da92daa3a977d8525 }

{ name: 'UPPER CASE SETTER EXAMPLE',

  _id: 56f94e5da92daa3a977d8525 }

Figure 25 mongoose setter 예제 실행 결과

 

처음에는 아무 값도 지정하지 않았기 때문에 name 필드에 디폴트 값인 “Hello Terry” 저장된다.

다음으로, hello.name="Upper case setter example"; 저장을 했지만, 지정된 Setter 의해서, name 모든 알파벳이 대문자로 변환되어 { name: 'UPPER CASE SETTER EXAMPLE',_id: 56f94e5da92daa3a977d8525 } 저장된것을 확인할 있다.

 

Setter이외에 저장된 데이타를 조회할때, 변환하는 Getter 역시 지정이 가능하다.

다음 코드를 보자

 

var mongoose = require('mongoose');

 

// setter function

function lowercase (val) {

        return val.toLowerCase();

      }

 

var HelloSchema = new mongoose.Schema(

           { name : { type:String,get:lowercase} }

           );

 

// gettert test

var Hello = mongoose.model('hello',HelloSchema);

var hello = new Hello();

hello.name="LOWER case setter example";

console.log(hello);

console.log(hello.name);

 

Figure 26 mongoose getter 예제

 

Getter 지정은 스키마에서 타입 지정시 “get:{Getter 함수명}” 식으로 지정하면 된다. 위의 예제에서는

{ name : { type:String,get:lowercase} }

같이 lowercase 함수를 Getter 지정하였다.

 

예제에 대한 실행 결과를 보면 다음과 같다.

 

 

{ _id: 56f94f4314540b3d97fe17b3,

  name: 'LOWER case setter example' }

lower case setter example

 

Figure 27 mongoose getter 예제 실행 결과

 

실제로 데이타 객체내에 name 필드에 저장된 값은 name: 'LOWER case setter example' 이지만, hello.name으로 해당 필드의 내용을 조회했을 경우 getter 지정된 lowercase 함수를 통해서 모두 소문자로 변환된 lower case setter example

 문자열을 리턴하는 것을 확인할 있다.

 

이렇게 직접 getter setter 대한 함수를 정의할 있지만, mongoose에는 모든 문자열을 소문자로 변경하는 lowercase setter, 문자열 앞뒤의 공백을 없애주는 trim setter 등이 기본적으로 제공된다.

 

Lowercase setter 사용예

var s = new Schema({ email: { type: String, lowercase: true }})

 

trim setter 사용예

var s = new Schema({ name: { type: String, trim: true }})

 

데이타 타입별로 미리 제공되는 Setterd Getter http://mongoosejs.com/docs/schematypes.html 참고하기 바란다.

 

 스키마 타입

 

앞서서 mongoose 스키마에 대해서 설명하였는데, 조금 자세하게 살펴보자 스키마에서는 필드에 대한 데이타 타입을 정의할 있는데, 다음과 같다.

 

스키마 타입

설명

예제

String

문자열

‘Hello’

Number

숫자

135090

Date

날짜

ISODate("1970-06-09T15:00:00.000Z")

Buffer

바이너리 타입 (파일등을 저장할때 사용됨)

파일등의 바이너리 데이타

Mixed

특별한 형을 가지지 않고 아무 JSON 문서나 있음

‘any’:{ ‘data’:’this is any data….’}

Objectid

mongoDB objectid

ObjectId("56f8d0b63ef9d003961e5f3f")

Array

배열

[‘Hello’ , ‘Terry’ ]

Figure 28 mongoose 스키마 타입

설명을 돕기 위해서 예제를 보자.

다음과 같은 형태 데이타를 표현하기 위한 스키마를 저장할 것이다.

사용자의 정보를 저장하는 Profile이라는 형태의 스키마이다.

 

{

    "_id" : ObjectId("56f93d08253b92b296080587"),

    "meta" : {

        "book" : "architecture design",

        "company" : "cloud consulting"

    },

    "birthday" : ISODate("1970-06-09T15:00:00.000Z"),

    "address" : {

        "_id" : ObjectId("56f8d0b63ef9d003961e5f40"),

        "zipcode" : 135090,

        "city" : "youngin",

        "state" : "Kyungki"

    },

    "name" : "terry",

    "recommend" : [

        "I want to recommend terry",

        "He is good guy"

    ],

    "image" : {

        "data" : { "$binary" : "/9j/4AAQSkZJ (중략) Rg ", "$type" : "00" },

        "contentsType" : "image/png"

    },

    "__v" : 0

}

Figure 29 사용자 프로파일 JSON 도큐먼트 예제

 

이름, 생년월일, 주소, 그리고 사용자에 대한 추천글과, 사용자에 대한 이미지 파일을 저장하는 스키마이다.

이를 스키마로 지정하면 다음과 같다.

 

// define scheme

var addressSchema = new mongoose.Schema({

      zipcode : Number,

      city : String,

      state : String

});

 

 

var profileSchema = new mongoose.Schema({

      name : String,

      address : addressSchema,

      birthday : Date,

      meta : mongoose.Schema.Types.Mixed,

      image : {

           data : Buffer,

           contentsType : String

      },

      recommend : [String]

});

 

Figure 30 mongoose를 이용하여 schema.js 예제에서 사용자 프로파일 스키마를 정의한 부분

 

주소를 저장하기 위한 스키마는 addressSchema, 숫자로된 zipcode, 문자열로 city state 필드를 갖는다

·         name은 문자열로 이름을 저장한다.

·         address는 서브 도큐먼트 타입으로, 앞에서 정의한 addressSchema 형을 참조한다.

·         birthday는 날짜 타입이고, 

·         meta는 메타 정보를 저장하는 필드인데, Mixed 타입이다. Mixed 타입은 앞에서도 설명하였듯이, 아무 JSON 도큐먼트나 들어갈 수 있다.

·         다음으로 imageJSON 타입으로 안에, 사진 파일을 저장하기 위해서 Buffer 형으로 data  필드를 갖고, 사진 포맷 저장을 위해서 contentsType이라는 타입을 갖는다.

·         마지막으로 recommend 필드는 사용자에 대한 추천 문자열을 배열로 갖는다.

 

서브 도큐먼트 vs 임베디드 도큐먼트 vs Mixed 타입

 

스키마를 보면, 스키마 내에 JSON 도큐먼트를 갖는 필드가 address,meta,image 3 가지가 있다. 타입의 차이점은 무엇일까?

먼저 addresss 서브 도큐먼트 (sub document) 타입으로 mongodb 저장하면 도큐먼트 형으로 저장이 되고, _id 필드를 갖는다. 부모 도큐먼트 (여기서는 profileSchema) 종속 되는 도큐먼트 형태로, 단독으로는 업데이트가 불가능하고 반드시 부모 도큐먼트 업데이트시에만 업데이트가 가능하다. 이러한 서브 도큐먼트 타입은 같은 타입의 서브 도큐먼트가 반복적으로 사용될때 타입 객체를 사용할때 사용하면 좋다.

 

다음 image 필드와 같이 스키마내에 JSON 도큐먼트 포맷을 그대로 저장하는 방식을 embeded 방식이라고 하는데, 서브 도큐먼트와는 다르게 _id 필드가 붙지 않는다. 간단하게 JSON 도큐먼트를 내장할때 사용한다.

마지막으로 meta 필드의 경우 Mixed 타입을 사용했는데, 아무 포맷의 JSON 문서가 들어갈 있다. 컬렉션 내에서 해당 필드의 JSON 도큐먼트 포맷이 각기 다를때 사용할 있으며, 포맷을 정의하지 않기 때문에 유연하게 사용할 있다.

 

스키마를 정의했으면 이제 값을 넣어서 저장해보자

 

// create model

var Profile = mongoose.model('profiles',profileSchema);

var Address = mongoose.model('address',addressSchema);

var p = new Profile();

 

// populate model

p.name = "terry";

 

// address

var a = new Address();

a.zipcode = 135090;

a.city = "youngin";

a.state = "Kyungki";

p.address = a;

 

// birthday

p.birthday = new Date(1970,05,10);

 

// meta

p.meta = { company : 'cloud consulting', book : 'architecture design'};

 

// image

p.image.contentsType='image/png';

var buffer = fs.readFileSync('/Users/terry/nick.jpeg');

p.image.data = buffer;

 

// recommend

p.recommend.push("I want to recommend terry");

p.recommend.push("He is good guy");

 

p.save(function(err,silece){

      if(err){

           cosole.log(err);

           return;

      }

      console.log(p);

});

 

Figure 31 mongoose를 이용하여 schema.js 예제에서 데이타를 저장하는 부분

 

 

값을 저장하기 위해서 모델 객체를 생성한후 Profile 대한 데이타 객체 p Address 대한 데이타 객체 a 생성하였다.

값을 저장할때는 “{데이타 객체명}.필드=형태로 저장한다.

Address 저장을 위해서 데이타 객체인 a zipcode,city,state 값을 저장한후에, p.address = a 이용해서, address 필드의 값을 채워 넣는다.

p.birthday Date형이기 때문에, new Date() 메서드를 이용해서, Date 객체를 생성하여 데이타를 저장한다.

p.meta Mixed 타입으로 직접 JSON 도큐먼트를 지정하여 저장한다.

p.image 임베디드 도큐먼트 타입으로, p.image.data, p.image.contentsType 각각의 필드에 값을 저장한다. 이때 data 필드는 Buffer 타입으로, 예제에서는 /Users/terry/nick.jpeg 라는 파일을 저장 하였다. fs.readFileSync 이용하여 인코딩 지정없이 파일을 읽게 되면, 파일 데이타를 Buffer 객체로 반환해주는데, 값을 p.image.data 지정하여 저장하였다.

그리고 마지막으로, p.recommend String 배열로, push 메서드를 이용하여 데이타를 추가 하였다.

 

데이타 객체에 모든 값이 저장되었으면 이를 mongodb 저장하기 위해서 p.save 메서드를 이용하여 저장한다.

 

다음 데이타를 수정하는 방법을 알아보자. 앞의 예제에서 저장된 Profile 도큐먼트의 _id '56f93d08253b92b296080587'  라고 하자. 아래 예제는 Profile 컬렉션에서 _id '56f93d08253b92b296080587' 도큐먼트를 찾아서 birthday 2( Date.setMonth(1) 2월이다. 0부터 시작한다.) 바꿔서 save 메서드를 이용해서 저장하는 예제이다.

 

var mongoose = require('mongoose');

var fs = require('fs');

mongoose.connect('mongodb://localhost:27017/mydb');

 

// define scheme

var addressSchema = new mongoose.Schema({

      zipcode : Number,

      city : String,

      state : String

});

 

 

var profileSchema = new mongoose.Schema({

      name : String,

      address : addressSchema,

      birthday : Date,

      meta : mongoose.Schema.Types.Mixed,

      image : {

           data : Buffer,

           contentsType : String

      },

      recommend : [String]

});

 

// create model

var Profile = mongoose.model('profiles',profileSchema);

var Address = mongoose.model('address',addressSchema);

var p = new Profile();

 

Profile.findOne({_id:'56f93d08253b92b296080587'},function(err,p){

      console.log(p);

      p.birthday.setMonth(1);

      p.save(function(err,silece){

           if(err){

                 cosole.log(err);

                 return;

           }

           console.log(p);

      });

});

 

 

Figure 32 mongoose에서 데이타를 조회하여 Date 필드를 업데이트 하는 예제

 

저장된 데이타를 robomongo 이용해서 mongodb에서 확인해보면 다음과 같다.



Figure 33 예제 실행 결과, Date 필드 수정 내용이 반영되지 않은 결과

 

기대했던 결과와는 다르게, birthday 2월로 바뀌지 않고, 처음에 생성했던 6월로 되어 있는 것을 있다.

mongoose save 메서드는 필드의 값이 변환된 것만 자동으로 인식하여 save 저장하는데, 몇몇 타입의 경우 자동으로 변경된 값을 인식하지 못한다.

Date, Mixed 필드가 그러한데, 경우에는 mongoose 에게 해당 필드의 값이 변경되었음을 강제적으로 알려줘서 변경된 값을 인식하여 저장하게 해야 한다.

이때 markedModified(“필드명”) 메서드를 사용한다. 아래 코드는 markedModified 이용하여 birthday 필드가 변경되었음을 명시적으로 알려주고, 값을 저장하도록 변경한 코드이다.

 

Profile.findOne({_id:'56f93d08253b92b296080587'},function(err,p){

      console.log(p);

      p.birthday.setMonth(1);

      p.markModified('birthday');

      p.save(function(err,silece){

           if(err){

                 cosole.log(err);

                 return;

           }

           console.log(p);

      });

});

 

Figure 34 markedModified를 이용하여 Date 필드가 수정되었음을 명시적으로 알려주도록 코드를 수정한 내용

 

위의 코드를 수정한 다음 다시 mongodb 저장된 데이타를 보면 다음과 같다.



Figure 35 markedModified 반영후, Date 필드가 정상적으로 반영된 결과

 

성공적으로 birthday 월이 2월로 변경된것을 확인할 있다.

스키마 타입 관련 예제 코드는 https://github.com/bwcho75/nodejs_tutorial/tree/master/mongooseschemeexample 참고하기 바란다.

 

나중에 시간되면, population index 보강 예정

Thrift에 대해서 잘 설명해놓은 글 http://thrift-tutorial.readthedocs.org/en/latest/thrift-stack.html

Thrift

RPC 지원

프로그래밍 언어 커버리지가 넓음

TLS 기반의 Secure 프로토콜 지원

Map,List 지원


PB

IDL 정의가 조금 더 직관적

"Serialize/Deserialize 전용 (Message 정의용) :그래서 다른 Transport 프로토콜이 필요함"-다른 Protocol을 통해 Tunneling 함 (HTTP,AMQP,심지어 Thrifit까지,Netty의 경우 확장 플러그인이 있음)

Map,List등을 지원하지 않음


빠르게 훝어보는 node.js

#5 - Express 2/2

조대협 (http://bcho.tistory.com

File upload download 처리

Express는 파일 업로드 기능을 제공한다. Express 의 경우, 파일을 tmp directory에 업로드한후, 업로드가 끝나면 이벤트를 주는 형태이다. 그래서, 파일 업로드가 끝나면 파일 저장 디렉토리로 옮겨 줘야 한다. 그러면 간단하게 코드를 살펴보자.

express에서 업로드되는 file stream multipart 형태로 업로드가 된다. multi part request stream을 인식하려면, express세팅에 bodyParser 미들웨어를 사용함을 명시해줘야 한다.

var app = express();

app.use(express.bodyParser());

http://expressjs.com/3x/api.html#req.files

다른 미들웨어도 이 bodyParser() 미들웨어를 사용하기 때문에, 다른 미들웨어 선언전에 앞쪽에 선언을 해줘야 한다.

다음으로 file 업로드를 해줄 HTML 파일을 정의하자

<form action="/upload" method="post" enctype="multipart/form-data">

    <input type="file" name="myfile" />

    <input type="submit" name="upload" />

</form>

HTTP Post 형태로 multipart 형태로 데이터를 보내며, 파일은 “myfile”이라는 폼 이름으로 전송된다. 이를 받는 코드는 아래와 같다.

var fs = require('fs');

 

exports.upload = function(req, res){

    fs.readFile(req.files.myfile.path,function(error,data){

        var destination = __dirname + '\\..\\uploaded\\'+ req.files.myfile.name;

        fs.writeFile(destination,data,function(error){

            if(error){

                console.log(error);

                throw error;

            }else{

                res.redirect('back');

            }

        });

    });

 

};

파일은 req.files.{폼이름}.path에 업로드 된다. 위의 예제는 파일 컴포넌트의 폼명을 “myfile”로 했기 때문에 파일의 경로는 req.files.myfile.path가 되낟. 다음 fs.readFile을 이용해서 업로드된 파일이 tmp 디렉토리에 모두 업로드가 되면 destination 디렉토리로 복사해주는 예제이다.

tmp 디렉토리의 경우 bodyParser 미들웨어 적용시 적용할 수 있다.

조금 더 효율적인 코드를 구성해보면, 파일을 tmp 디렉토리에 써지지는 것을 바로 읽어서 write destination에 쓸 수 있도록 Stream을 이용할 수 있다. tmp에서 읽어서 destination 디렉토리에 쓰는 것은 위의 예제와 똑같지만, 나중에 설명하겠지만 Stream을 사용하면, 파일을 읽을 때, 쓸 수 있는 만큼만 (버퍼크기만큼만) 읽은 후 쓰기 때문에, 훨씬 효율적인 IO를 할 수 있다.

exports.uploadstream = function(req, res){

    var destination = __dirname + '\\..\\uploaded\\'+ req.files.myfile.name;

    var ws =  fs.createWriteStream(destination);

    fs.createReadStream(req.files.myfile.path).pipe(ws);

    res.redirect('back');

};

아쉬운 점은 Express의 특성상 바로 destination 디렉토리에 write하는 것은 안되고, tmp 디렉토리를 거쳐서 write해야 한다.


JSON REST API

Express JSON 기반의 REST API 구현도 지원하는데, Spring/Java를 알고 있는 개발자라면, 아주 짜증이 날(?) 정도로 express를 이용한 REST API 구현은 매우 간단하다. 설명은 생략하고 먼저 코드부터 보자

app.use(express.json());

app.post('/rest',function(request,response){

    request.accepts('application/json');

 

    // input message handling

    json = request.body;

    console.log('name is :'+json.name);

    console.log('address is :'+json.address);

 

    // output message

    response.json({result:'success'});

 

});

위의 코드는

{

   "name":"Terry",

   "address":"seoul"

}

와 같은 JSON 메시지를 받은 후에, 내용을 파싱하고, { ‘result’:’success’} 라는 리턴을 보내는 코드이다. 먼저 exress.json 미들웨어를 적용하고, request.accept application/json 타입으로 해서 JSON request를 받음을 명시한다.

다음으로는 request.body.{JSON 필드명} 을 사용하면 된다. 위의 예의 경우 JSON 필드명이 name address이기 때문에 그 값에 대한 경로는 body.name body.address가 된다.

Response를 보낼 때에는 위의 예제와 같이 response.json({key:value,…}) 형태로 지정하면 된다. 만약에 HTTP response code를 보내고 싶으면 response,json(HTTP_CODE,{key:value…}) 형태로 지정한다.

예를 들어 response.json(500,{error:’error message’});  형태로 지정할 수 있다.

또한 response.jsonp 메서드를 이용해서 JSONP 를 지원하는데, JSONP는 간단하게 말하면 Cross Site Scripting을 지원할 수 있는 방법이다. 자세한 설명은

http://beebole.com/blog/general/sandbox-your-cross-domain-jsonp-to-improve-mashup-security/

를 참고하기 바란다.


Connect Module pipe line

지금까지 Express의 기능에 대해서 간략하게 살펴보았는데, 에러 처리 방식에 앞서서 Express의 근간이 되는 Connect framework에 대해서 짚고 넘어가고자 한다.

Connect Framework Javascript 를 기반으로 한 웹/서버 개발용 프레임웍이다. Javascript 기반의 서버를 만들기 위해서, 개발되었으며, Ruby Rack Framework을 기반으로 하였다.

Connect에서는 Middleware라는 개념을 사용하는데, reusable한 컴포넌트를 middleware라고 한다. Request, response 파이프라인상에 middle ware를 넣어서 기능을 추가 및 처리 하는 개념인데, Java Servlet Filter Servlet Chaining과 같은 개념과 유사하다고 보면 된다. 아래 그림과 같이 request가 들어와서 서버에서 처리되고 reponse로 나가는 형태라고 할 때,



아래 그림과 같이 처리 과정에 middleware를 추가하여 기능을 처리하도록 할 수 있다.



우리가 지금까지 express를 사용하면서 app.use라고 했던것들이 middleware 모듈을 추가하는 기능이었다.

app.use(express.logger('dev'));

app.use(express.json());

app.use(express.urlencoded());

app.use(express.methodOverride());

app.use(express.cookieParser('your secret here'));

app.use(express.session());

app.use(app.router);

app.use(express.static(path.join(__dirname, 'public')));

 

app.use(function(req,res,next){

   console.log('custom log :'+req.path) ;

   next();

});

 

app.post('/upload', upload.fileupload);

위의 코드를 분석해 보면



와 같은 순서로 middleware가 적용된 것이다.

static 파일의 경우 위에서부터 순차적으로 logger 모듈부터 적용이 되다가 express.static 모듈에서 적용후에, static file response한 다음에 바로 리턴이 된다.

static 파일이 아닌 경우는 모두 아래 함수를 수행하게 되는데

app.use(function(req,res,next){

   console.log('custom log :'+req.path) ;

   next();

});

Middleware로 넘어오는 parameter HTTP request,response 뿐만 아니라 next라는 함수 포인터를 넘겨주는데, middleware를 수행한 다음에 다음 middleware를 실행하기 위한 포인터이다. 위의 예에서는 콘솔에서 로그를 출력한 후에, next()를 호출하여 다음 미들웨어를 호출하도록 하였다.

HTTP/POST /upload request의 경우에는 app.post('/upload', upload.fileupload); 미들웨어에 의해서 처리된다.

이렇게 middleware들은 순차에 의한 chaining 개념을 가지고 있기 때문에, middleware use를 이용해서 불러드릴 경우 순서가 매우 중요함을 알 수 있다.


Error Handling

다음으로 Express에서 에러처리에 대해서 알아보자 Express에서 별도의 에러처리를 하지 않으면, 404의 경우 한줄로 없는 페이지라는 메시지가 나오거나 500 에러의 경우 아래와 같이 에러스택이 바로 표시되어 버린다. (보안상의 이유라도 이런식으로 내부 스택이 나오는 것은 좋지 않다.)



그러면 어떻게 에러 처리를 하는지 알아보도록 하자. 먼저 코드를 보면

app.use(express.static(path.join(__dirname, 'public')));

 

app.get('/error',function(req,res,next){

   // this will make a error

    var err = new Error('custom error');

    err.type = 'my error';

    next(err);

 

});

app.use(function(err,req,res,next){

   console.log(err.type);

   console.log(err.stack);

 

   res.format({

       html: function(){

            res.send(500,'internal server error');

        },

       json:function(){

           res.send(500,{code:err.type,description:'error detail'});

       }

    });

 

});

app.use(function(req,res){

    res.send(404,"I cannot find the page");

} );

 

http.createServer(app).listen(app.get('port'), function(){

  console.log('Express server listening on port ' + app.get('port'));

});

 

먼저 404 에러의 경우 앞에 Connect에서 살펴본 middlewarechaining 개념을 이용하면 된다. 간단하게 다른 middleware에 의해서 처리되지 않은 URL 404로 처리하면 된다. 그래서 middleware를 불러드리는 맨 마지막에 404 에러 처리 로직을 구현하였다.

app.use(function(req,res){

    res.send(404,"I cannot find the page");

} );

 

다음은 500이나 503 같은 에러 처리 방식을 알아보자, 인위적으로 에러를 만들기 위해서 HTTP GET/error 시에 인위적으로 에러를 발생시키는 코드를 구현하였다.

app.get('/error',function(req,res,next){

   // this will make a error

    var err = new Error('custom error');

    err.type = 'my error';

    next(err);

 

});

 

여기서 주의 할점은 node.js의 일반적인 에러 처리 방식 처럼 throw를 통해서 에러를 던지는 것이 아니라 next()를 통해서 에러 메시지를 다음 middleware로 넘기는 형태를 사용한다.next 호출시 인자에 error가 있을 경우 미리 정의된 error handler를 부르게 된다.error handler는 다른 middleware와는 총 4개 인자를 받으며, 다르게 첫번째 인자가 err로 정의 된다.

app.use(function(err,req,res,next){

가 에러 핸들러를 구현한것이며, res.format을 이용하여, 브라우져가 선호하는 포맷 (content/accept에 정의된) 포맷으로 html이나 json으로 메시지를 보내주도록 구현하였다. 예제라서 간단하게 구현했지만, err.type에 에러가 발생할 때 타입을 정해놓으면 error handler에서 이 err.type에 따른 다양한 에러 핸들링 로직을 구현할 수 있고 (예를 들어 Nagios 기반의 모니터링 시스템에 이벤트를 날리거나, IT Admin에게 SMS 메시지를 보내는 것등), 500 error의 경우에는 template을 미리 만들어놓고 잘 디자인된 에러페이지를 출력할 수 있다.

이 밖에도 HTTP Basic Auth를 이용한 인증, 압축 모듈, CSRF (Cross Site Request Forgery) 등을 방어 하는 모듈등 다양한 기능을 지원하는 API 및 모듈이 있다. 자세한 내용은 http://expressjs.com/api.html 을 참고하기 바란다.



Fiddler는 간단하면서도 아주 강력한 웹 디버깅 툴이다. http://fiddler2.com

중간에서 웹서버 프록시 역할을 하면서, 오고 가는 트래픽을 모두 모니터링 및 디버깅할 수 도 있고, 기존에 보냈던 트래픽을 replay해서 다시 보낼 수 도 있다. (WireShark 보다 웹디버깅 측면에서는 오히려 더 편한듯).

여러가지 기능이 있지만,오늘은 Fiddler를 json 웹서버 처럼 사용하는 방법에 대해서 소개하고자 한다.

요즘 들어서, 특히 자바스크립트로 웹 클라이언트를 개발하는 일이 잦아지면서, 클라이언트 개발시 서버가 필요한데, 특히 JSON 서버의 경우에는 별도의 서버를 올리고, 간단하더라도 JSON 서비스를 만들어줘야 하기 때문에, JSON 클라이언트를 만드는 입장에서는 꽤나 번거롭다. 이를 Fiddler를 이용해서 간단하게 해결할 수 있다.


Fiddler의 기능중의 하나가 특정 URL 패턴으로 들어오는 request를 가로채서, 특정 Response를 보내게 만드는 기능이 있다.


바로 AutoResponder라는 기능인데, 위의 그림과 같이 설정할 수 있다.

RuleEditor에서 /jsonproxy 라는 URL로 설정해놓으면, 모든 사이트로 가는 Request에 대해 /jsonproxy라는 URI를 가지면 모두 이 Rule을 적용 받는다. 그리고, 아래에, Response를 선택할 수 있는데, 여기서는 특정 JSON 파일을 만들어서 선택하였다.

sample.json 파일의 내용은 아래와 같다.

HTTP/1.1 200 OK

Content-Type: application/json;charset=utf-8

Content-Length: 57


{

"data":{

"name":"Terry",

"city":"Seoul"

}

}

마지막으로 위의 AutoResponder메뉴에서 "Enable automatic response" 체크 박스를 선택해주면. /jsonproxy로 가는 모든 Request에 대해서, 위의 파일에 저장된 값을 리턴해준다.


django 에서 REST API 만들기

프로그래밍/Python | 2014.01.08 00:58 | Posted by 조대협

Dango에서 간단한 REST API 만들기 

조대협



Django에서 REST API를 만들려면 가장 널리 사용되는 프레임웍중 하나가 dango rest_framework이다.

http://django-rest-framework.org/tutorial/quickstart


설치는 다음과 같다.

pip install djangorestframework

pip install markdown       # Markdown support for the browsable API.

pip install django-filter  # Filtering support

 

1. quickstart라는 앱을 생성

 

C:\Users\terry\git\django_restframework_sample\django_restframework_sample>python manage.py startapp quickstart

또는 이클립스 사용시에는 project에서 오른쪽 버튼 누르고 django 메뉴에서 > Custom command (manage.py xxx)에서 startapp 명령어 실행

 



아래와 같이 프로젝트 아래에 앱이 생성된



 

2. DB sync

setting.py에 아래 내용을 추가

DATABASES = {

    'default': {

        'ENGINE': 'django.db.backends.sqlite3',

        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),

        'USER': '',

        'PASSWORD': '',

        'HOST': '',

        'PORT': ''

    }

}

다음 eclipse에서 마찬가지로 오른쪽 버튼을 눌러서, django 메뉴에서 manage.py syncdb 명령어를 수행



이 예제에서는 실제 테이블은 만들지 않고, API 인증을 위해서, django user 테이블만을 생성한다. Syncdb를 수행하면 아래와 같이 첫 user를 만드는데, 이때, 사용자 id,email,password를 입력하도록 한다.



 

3. 다음으로 코딩

1) quickstart/serializer.py 코드를 아래와 같이 작성

from django.contrib.auth.models import User, Group

from rest_framework import serializers

 

 

class UserSerializer(serializers.HyperlinkedModelSerializer):

    class Meta:

        model = User

        fields = ('url', 'username', 'email', 'groups')

 

 

class GroupSerializer(serializers.HyperlinkedModelSerializer):

    class Meta:

        model = Group

        fields = ('url', 'name')

 

2) quickstart/views.py를 다음과 같이 작성

from django.contrib.auth.models import User, Group

from rest_framework import viewsets

from quickstart.serializers import UserSerializer, GroupSerializer

 

 

class UserViewSet(viewsets.ModelViewSet):

    """

    API endpoint that allows users to be viewed or edited.

    """

    queryset = User.objects.all()

    serializer_class = UserSerializer

 

 

class GroupViewSet(viewsets.ModelViewSet):

    """

    API endpoint that allows groups to be viewed or edited.

    """

    queryset = Group.objects.all()

    serializer_class = GroupSerializer

 

위의 테스트 앱은 UserViewSet은 현재 Django에 등록된 사용자 리스트들을 읽어다가 JSON으로 리턴하는 클래스 이고, GroupViewSet은 마찬가지로 django에 등록된 사용자 그룹 리스트를 읽어다가 JSON으로 리턴하는 클래스이다.

 

Rather than write multiple views we're grouping together all the common behavior into classes called ViewSets.

We can easily break these down into individual views if we need to, but using viewsets keeps the view logic nicely organized as well as being very concise.

Notice that our viewset classes here are a little different from those in the frontpage example, as they include queryset andserializer_class attributes, instead of a model attribute.

For trivial cases you can simply set a model attribute on the ViewSet class and the serializer and queryset will be automatically generated for you. Setting the queryset and/or serializer_class attributes gives you more explicit control of the API behaviour, and is the recommended style for most applications.

4. 다음으로 세팅

1) root project(여기서는 django_restframework_sample) urls.py에 다음 코드를 추가함

from django.conf.urls import patterns, url, include

from rest_framework import routers

from quickstart import views

 

router = routers.DefaultRouter()

router.register(r'users', views.UserViewSet)

router.register(r'groups', views.GroupViewSet)

 

# Wire up our API using automatic URL routing.

# Additionally, we include login URLs for the browseable API.

urlpatterns = patterns('',

    url(r'^', include(router.urls)),

    url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework'))

)

일반적인 web application과 다르게 view가 아닌 viewset을 사용하기 때문에, URL rest_framework이 자동으로 설정하도록 세팅하였다.

l  /users à views.UserViewSet API로 맵핑 되고

l  /groups à views.GroupViewSet API로 맵핑 된다.

 

2) Root project setting.py에 다음을 추가

REST_FRAMEWORK = {

    'DEFAULT_PERMISSION_CLASSES': ('rest_framework.permissions.IsAdminUser',),

    'PAGINATE_BY': 10

}

이 부분이 흥미로운 부분인데, JSON 기반의 API 인증(Authentication)  인가(Authorization)을 프레임웍 자체에서 제공한다. 위의 경우에는 Admin 사용자에 대해서만 서비스를 제공하고, HTTP BASIC AUTH 인증을 사용한다.

http://django-rest-framework.org/api-guide/permissions 에 자세한 내용이 나와 았는데,  

  모든 사용자에게 access를 허가하는 Allow Any

  인증된 사용자에게만 access를 허가하는 isAuthenticated

  관리자에게만 인증을 허가하는 isAdminUser

  인증되지 않은 사용자에게는 Read,인증된 사용자에게는 Update를 제공하는 isAuthenticatedOrReadOnly

  기타 DjangoModel (django.contrib.auth에 의해서 관리되는 권한)이나 Object 단위 권한등에 따라서, 조정할 수 있다.

  그리고 TokenHasReadWriteScope의 경우에는 OAuth2Authentication등과 연계하여 Token 기반의 인증이 가능하게 된다.

※ 다양한 인증 메커니즘을 제공하기 때문에, 처음에는 어려울지 몰라도, 이해하고 나면 짜는 것 보다는 훨씬 쉬울듯.

위는 API접근 권한에 대한 것이었고, Authenticationhttp://django-rest-framework.org/api-guide/authentication 에 정의되어 있다. 위에서 사용한 것과 같이 HTTP Basic Auth를 사용할 수 도 있고, Token이나 Http Session 기반의 Authentication 그리고, OAuth 1,2 기반의 인증을 모두 지원한다. (Token 방식의 경우에는 userauthenticated된 다음, Authorization token을 서버에서 발급해준다. Token HTTP를 타고 내려가기 때문에, Token secure하게 전달하기 위해서 HTTPS를 필수로 사용해야 한다.)

 

 

3) 그리고, setting.py INSTALLED_APPS rest_framwwork을 추가하는 것도 있지 말자

INSTALLED_APPS = (

    'django.contrib.admin',

    'django.contrib.auth',

    'django.contrib.contenttypes',

    'django.contrib.sessions',

    'django.contrib.messages',

    'django.contrib.staticfiles',

    'rest_framework',

)

5. 테스팅

테스트는 Chrome plug-inadvanced rest client를 사용하였다.

여기서 주목할 것은 HTTP Basic Auth 헤더를 어떻게 입력하느냐인데,

Headers 메뉴에서 Form을 선택한후 Add new header에서 Key Authorization을 선택한다.



Authorization이 선택되면 value를 넣는 부분에 아래와 같이 Construct라는 버튼이 나타나는데,



이 버튼을 누르게 되면 아래와 같이 dialog box가 나와서 basic 며소에 대한 log in id password를 넣을 수 있게 된다.



이 과정이 끝나면 Header 부분에 HTTP Basic Auth 부분이 들어가게 되고, 아래와 같은 테스트 결과를 얻게 된다.

 




대부분의 내용은 http://django-rest-framework.org/tutorial/quickstart 를 참고하였음


XML2JSON Transform XSLT

프로그래밍/XML 관련 | 2008.12.10 14:15 | Posted by 조대협
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!--
  Copyright (c) 2006, Doeke Zanstra
  All rights reserved.

  Redistribution and use in source and binary forms, with or without modification,
  are permitted provided that the following conditions are met:

  Redistributions of source code must retain the above copyright notice, this
  list of conditions and the following disclaimer. Redistributions in binary
  form must reproduce the above copyright notice, this list of conditions and the
  following disclaimer in the documentation and/or other materials provided with
  the distribution.

  Neither the name of the dzLib nor the names of its contributors may be used to
  endorse or promote products derived from this software without specific prior
  written permission.

  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
  OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
  THE POSSIBILITY OF SUCH DAMAGE.
-->

  <xsl:output indent="no" omit-xml-declaration="yes" method="text" encoding="UTF-8" media-type="text/xml"/>
    <xsl:strip-space elements="*"/>
  <!--contant-->
  <xsl:variable name="d">0123456789</xsl:variable>

  <!-- ignore document text -->
  <xsl:template match="text()[preceding-sibling::node() or following-sibling::node()]"/>

  <!-- string -->
  <xsl:template match="text()">
    <xsl:call-template name="escape-string">
      <xsl:with-param name="s" select="."/>
    </xsl:call-template>
  </xsl:template>
 
  <!-- Main template for escaping strings; used by above template and for object-properties
       Responsibilities: placed quotes around string, and chain up to next filter, escape-bs-string -->
  <xsl:template name="escape-string">
    <xsl:param name="s"/>
    <xsl:text>"</xsl:text>
    <xsl:call-template name="escape-bs-string">
      <xsl:with-param name="s" select="$s"/>
    </xsl:call-template>
    <xsl:text>"</xsl:text>
  </xsl:template>
 
  <!-- Escape the backslash (\) before everything else. -->
  <xsl:template name="escape-bs-string">
    <xsl:param name="s"/>
    <xsl:choose>
      <xsl:when test="contains($s,'\')">
        <xsl:call-template name="escape-quot-string">
          <xsl:with-param name="s" select="concat(substring-before($s,'\'),'\\')"/>
        </xsl:call-template>
        <xsl:call-template name="escape-bs-string">
          <xsl:with-param name="s" select="substring-after($s,'\')"/>
        </xsl:call-template>
      </xsl:when>
      <xsl:otherwise>
        <xsl:call-template name="escape-quot-string">
          <xsl:with-param name="s" select="$s"/>
        </xsl:call-template>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>
 
  <!-- Escape the double quote ("). -->
  <xsl:template name="escape-quot-string">
    <xsl:param name="s"/>
    <xsl:choose>
      <xsl:when test="contains($s,'&quot;')">
        <xsl:call-template name="encode-string">
          <xsl:with-param name="s" select="concat(substring-before($s,'&quot;'),'\&quot;')"/>
        </xsl:call-template>
        <xsl:call-template name="escape-quot-string">
          <xsl:with-param name="s" select="substring-after($s,'&quot;')"/>
        </xsl:call-template>
      </xsl:when>
      <xsl:otherwise>
        <xsl:call-template name="encode-string">
          <xsl:with-param name="s" select="$s"/>
        </xsl:call-template>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>
 
  <!-- Replace tab, line feed and/or carriage return by its matching escape code. Can't escape backslash
       or double quote here, because they don't replace characters (&#x0; becomes \t), but they prefix
       characters (\ becomes \\). Besides, backslash should be seperate anyway, because it should be
       processed first. This function can't do that. -->
  <xsl:template name="encode-string">
    <xsl:param name="s"/>
    <xsl:choose>
      <!-- tab -->
      <xsl:when test="contains($s,'&#x9;')">
        <xsl:call-template name="encode-string">
          <xsl:with-param name="s" select="concat(substring-before($s,'&#x9;'),'\t',substring-after($s,'&#x9;'))"/>
        </xsl:call-template>
      </xsl:when>
      <!-- line feed -->
      <xsl:when test="contains($s,'&#xA;')">
        <xsl:call-template name="encode-string">
          <xsl:with-param name="s" select="concat(substring-before($s,'&#xA;'),'\n',substring-after($s,'&#xA;'))"/>
        </xsl:call-template>
      </xsl:when>
      <!-- carriage return -->
      <xsl:when test="contains($s,'&#xD;')">
        <xsl:call-template name="encode-string">
          <xsl:with-param name="s" select="concat(substring-before($s,'&#xD;'),'\r',substring-after($s,'&#xD;'))"/>
        </xsl:call-template>
      </xsl:when>
      <xsl:otherwise><xsl:value-of select="$s"/></xsl:otherwise>
    </xsl:choose>
  </xsl:template>

  <!-- number (no support for javascript mantise) -->
  <xsl:template match="text()[not(string(number())='NaN')]">
    <xsl:value-of select="."/>
  </xsl:template>

  <!-- boolean, case-insensitive -->
  <xsl:template match="text()[translate(.,'TRUE','true')='true']">true</xsl:template>
  <xsl:template match="text()[translate(.,'FALSE','false')='false']">false</xsl:template>

  <!-- item:null -->
  <xsl:template match="*[count(child::node())=0]">
    <xsl:call-template name="escape-string">
      <xsl:with-param name="s" select="local-name()"/>
    </xsl:call-template>
    <xsl:text>:null</xsl:text>
    <xsl:if test="following-sibling::*">,</xsl:if>
  </xsl:template>

  <!-- object -->
  <xsl:template match="*" name="base">
    <xsl:if test="not(preceding-sibling::*)">{</xsl:if>
    <xsl:call-template name="escape-string">
      <xsl:with-param name="s" select="local-name()"/>
    </xsl:call-template>
    <xsl:text>:</xsl:text>
    <xsl:apply-templates select="child::node()"/>
    <xsl:if test="following-sibling::*">,</xsl:if>
    <xsl:if test="not(following-sibling::*)">}</xsl:if>
  </xsl:template>

  <!-- array -->
  <xsl:template match="*[count(../*[name(../*)=name(.)])=count(../*) and count(../*)&gt;1]">
    <xsl:if test="not(preceding-sibling::*)">[</xsl:if>
    <xsl:choose>
      <xsl:when test="not(child::node())">
        <xsl:text>null</xsl:text>
      </xsl:when>
      <xsl:otherwise>
        <xsl:apply-templates select="child::node()"/>
      </xsl:otherwise>
    </xsl:choose>
    <xsl:if test="following-sibling::*">,</xsl:if>
    <xsl:if test="not(following-sibling::*)">]</xsl:if>
  </xsl:template>
 
  <!-- convert root element to an anonymous container -->
  <xsl:template match="/">
      <return type="json">
        <xsl:apply-templates select="node()"/>
    </return>
  </xsl:template>
   
</xsl:stylesheet>

Web Oriented Architecture (WOA)

분류없음 | 2008.10.13 16:52 | Posted by 조대협
오늘 WOA에 대해서 듣게 되었는데.
역시나 생각들은 비슷한가보다.

SOA로 대표되는 아키텍쳐는 기본적으로 엔터프라이즈 시스템을 위해 고안되었고 그로 인해서 기업의 업무를 충실히 지원하기 위해서 많은 기능들을 제공한다.
그로 인한 문제가 복잡도가 많이 올라갔다는 것인데...
WEB 2.0 사상이 나오면서 REST,JSON,POX(Plain Old XML)등과 같이 이른바 OPEN API로 지칭되는 서비스들에서 사용되기 위한 경량의 프로토콜과 메세지 포맷, 네트워크 아키텍쳐들이 많이 소개 되었다.
WOA는 SOA의 사상과 같이 서비스 중심의 아키텍쳐 이면서 서비스를 제공하지만
SOA와 같은 강력한 기능 (트렌젝션, 여러 메세징 방법과 프로토콜)을 지원하지 않지만 최소한의 기능을 최대한 쉽게 지원하는데 목적이 맞춰져 있다.
이른바 REST와 POX,JSON으로 대표되는 아키텍쳐이다.
이 아키텍쳐는 WEB의 환경을 그래도 상속 받아서 사용하기 때문에 WEB이 가지고 있는 특성을 그대로 사용할 수 있는 장점을 가지고 있다.
  • URI 기반의 리소스 정의 : RESTFul과 같이 URI자체가 Method의 의미를 가지기 때문에 이해하기가 쉽고 HTTP method (GET,CREATE,PUT,DELETE) 를 사용하기 때문에 매우 직관적이다.
  • WEB 관련 인프라 사용 : 기존의 웹 캐슁, Proxy, Load Balancer등의 인프라를 그대로 재활용할 수 있으며 80 포트를 사용하기 때문에 상대적으로 Firewall에 자유롭다
따로 고안된 아키텍쳐라 보기보다는 SOA가 나오면서 서비스 진영에서 시작된 OPEN API의 사상이 다시 기업에 적절한 아키텍쳐로 정리가 된 수준으로 생각하면 될 듯...

역시 먼저 숟가락 꼽고.. 잘 정리하면 그것이 아키텍쳐가 되는것 같다.