XMLCC: XML생성을 위한 C++ 라이브러리

어쩌다 이름이 XMLCC가 되었는지는 모릅니다. 그냥 손가락에서 저렇게 나왔어요. 신의 뜻이라 생각하고 넘어갑니다. 씨익-

사건의 발단은 회사에서 XML을 생성해서 서버에 전송해야 하는 일이 생겨서 std::stringstream을 이용해 XML을 생성하다 보니 코드가 너무 지저분해지는 겁니다. 후.. 이런저런 고민을 한 끝에 좀 깔끔하게 짤 수 있는 프로그램을 작성해보기로 했지요. 🙂

일단 만들 XML부터 봅시다.


<author>Crow, Lim</author>

저런 문서를 하나 만들기 위해, std::stringstream을 사용하게 되면 다음과 유사한 코드가 등장할겁니다.

std::stringstream ss;

void make_xml(std::stringstream&amp; ss, std::string const&amp; title, std::string const&amp; author, ...(생략)...)
{
ss << "<author>";
ss << author;
ss << "</author>";
}

보기만 해도 끔찍하군요. 엄청난 escaping문자들.. 보기만해도 어지럽습니다. 만약 다음과 같이 작성할수 있다면 행복할텐데요.

void make_xml(std::stringstream& ss, std::string const& title, std::string const& author, ...(생략)...)
{
// 들여쓰기는 보기 편하라고 한 것 입니다.
element_start(ss, "book");
    attribute(ss, "title", title);

element_start(ss, "author");
    text(ss, author);
element_end(ss, "author");

element_start(ss, "date");
    attribute(ss, "year", year);
    attribute(ss, "month", month);
    attribute(ss, "day", day);
element_end(ss, "date");

element_end(ss, "book");
}

아쉽게도 위와 같은 코드는 작동하지 못합니다. XML의 특성상 attribute는 element 시작태그 내에서 정의가 되어야 하기 때문이지요. 물론, 이를 해결하기 위해 element_start의 인수로 attribute의 list를 넘겨주는 방법도 있습니다만, 이 방법은 해당하는 리스트를 생성해야한다는 단점이 있습니다. 의미상 코드에서 정적으로 정의가 가능한 XML을 굳이 리스트를 생성하면서 만들 필요는 없지요.

이를 위해 element_start()의 리턴을 객체로 주는 방법을 사용했습니다. 🙂 그 객체에서 시작태그를 생성하고 attribute도 처리하는 거지요.

void make_xml(std::stringstream& ss, std::string const& title, std::string const& author, ...(생략)...)
{
    element_start(ss, "book")
        .attribute("title", title);

    element_start(ss, "author");
    text(ss, author);
    element_end(ss, "author");

    element_start(ss, "date")
        .attribute("year", year)
        .attribute("month", month)
        .attribute("day", day);
    element_end(ss, "date");

    element_end(ss, "book");
}

element_start()의 리턴객체에서 attribute를 호출합니다. 자세히 보시면 뒤에 세미콜론(;)이 없는 라인들이 있는데 사실 한 expression이란 겁니다. 일종의 호출 체인이랄까요?

xmlcc.h에 있는 주요 부분을 보면 다음과 같습니다.

	struct element_start_tag
	{
		std::ostream& out_;

		element_start_tag(std::ostream& out, std::string const& name)
			: out_(out)
		{
			out_ << "<" << name;
		};

		element_start_tag(element_start_tag const& tag)
			: out_(tag.out_)
		{
		};

		element_start_tag& attribute(bool use, std::string const& name, std::string const& value)
		{
			if(use == true)
			{
				return attribute(name, value);
			}

			return *this;
		}

		element_start_tag& attribute(std::string const& name, std::string const& value)
		{
			out_ << " ";
			text(out_, name); 
			out_ << "=\"";
			text(out_, value); 
			out_ << "\"";
			return *this;
		}

		template<class ValueT, class FilterFuncT>
			element_start_tag& attribute(std::string const& name, ValueT const& value, FilterFuncT const& f)
		{
			out_ << " ";
			text(out_, name); 
			out_ << "=\"";
			text(out_, f(value)); 
			out_ << "\"";

			return *this;
		}

		template<class Func>
			element_start_tag& attribute_with_generator(std::string const& name, Func const& f)
		{
			out_ << " " << name << "=" << "\"";
			f(out_);
			out_ << "\"";
			return *this;
		}

		element_start_tag& operator()(std::string const& name, std::string const& value)
		{
			return attribute(name, value);
		}

		element_start_tag& operator()(bool use, std::string const& name, std::string const& value)
		{
			return attribute(use, name, value);
		}

		void close_now()
		{
			out_ << "/";
		}

		~element_start_tag()
		{
			out_ << ">";
		};
	};


	inline element_start_tag element_start(std::ostream& out, std::string const& name)
	{
		return element_start_tag(out, name);
	}

보시면, 생성자에서는 시작태그의 요소이름까지만 출력을 하고, 소멸자에서 태그를 닫습니다. attribute를 사용하면 stream에 attribute를 출력해주지요. 또한, close_now()를 호출하면 “/”를 출력시켜줌으로써 스스로 닫는 element를 생성해줍니다. 그리고, element_start()는 이 element_start_tag를 생성해주는 도우미 역할을 하지요.

attribute메소드는 operator()를 이용해도 사용할 수 있습니다. 이 경우 코드가 훨씬 간단해지지요.

void make_xml(std::stringstream& ss, std::string const& title, std::string const& author, ...(생략)...)
{
element_start(ss, "book")
    ("title", title);

    element_start(ss, "author");
        text(ss, author);
    element_end(ss, "author");

    element_start(ss, "date")
        ("year", year)
        ("month", month)
        ("day", day);
    element_end(ss, "date");

element_end(ss, "book");
}

사실 XML생성에 있어서 다른 XML의 구성요소들은 크게 문제가 되지 않는데, 가장 머리 아픈 부분은 element의 시작태그와 그 안에 내포된 attribute인지라 이런 잔머리를 굴려봤습니다. 🙂 덕분에 소스코드는 escape문자 없이 깔끔해졌군요. 🙂 잇힝-

좀 더 삽질해서 DSL(Domain Specific Langauge)형태로 만들어 보려고 했는데, 시간도 없고 이정도면 쓸만한거 같아서 여기서 멈췄습니다. 🙂

xmlcc.h 다운로드

C++ Web Server: Concept #1

1차분 Concept

  1. J2EE에서 등장했던 Filter개념으로 모든 플러그인들을 해결한다.
  2. Filter들의 Chain이 기본이다. Chain의 끝에는 Static 파일 Service가 들어간다.
  3. 각각의 Filter들은 Request와 결합하여 하나의 Task를 생성한다
  4. 특정 Request에 연관된 Task들은 Task Chain이라고 부른다.
  5. Task Chain은 내포한 Task들이 종료될때마다 다른 쓰레드로 이동이 가능해야 한다.
  6. Network IO는 전담 Multiplexer쓰레드에서 처리한다
  7. 스크립트 확장은 특정 스크립트 파일을 Filter로 만들어주는 ScriptingEngine에서 처리하도록 한다.
  8. Request/Response의 Body부분은 std::iostream의 shared_ptr로 관리한다

2차분

  1. Filter는 Request Filtering과 Response Creation을 담당하는 process_request operation과 Response Filtering을 담당하는 filt_response operation 으로 이루어진다.
  2. response::shared_type process_request(request::shared_type)
  3. void filt_response(response::shared_type)
  4. process_request에서 response를 생성하고 나면, 그때부터 filt_response를 process_request를 실행한 역순으로 실행한다. 즉, Stack이란 이야기
  5. 웹서버 쪽에서는 Mime parsing(폼변수 분석)에 대한 책임을 지지 않는다. HTTP Header까지만 책임을 지고 그 뒤는 각 모듈에게 맡긴다. (이래야 대용량 전송시 커스터마이징이 쉬워진다.)
  6. Mime Parsing에 대한 책임을 지지 않는 대신에 Mime Parsing을 위한 라이브러리를 별도로 제공한다. ScriptingEngine에서 이를 활용할 것인지는 별도로 고려한다.

C++ Web Server: Concept#0

C++로 웹서버를 만들 생각을 왜했는지는 이미 기억이 가물가물. @_@

  1. J2EE에서 등장했던 Filter개념으로 모든 플러그인들을 해결한다.
  2. Filter들의 Chain이 기본이다. Chain의 끝에는 Static 파일 Service가 들어간다.
  3. 각각의 Filter들은 Request와 결합하여 하나의 Task를 생성한다
  4. 특정 Request에 연관된 Task들은 Task Chain이라고 부른다.
  5. Task Chain은 내포한 Task들이 종료될때마다 다른 쓰레드로 이동이 가능해야 한다.
  6. Network IO는 전담 Multiplexer쓰레드에서 처리한다
  7. 스크립트 확장은 특정 스크립트 파일을 Filter로 만들어주는 ScriptingEngine에서 처리하도록 한다.
  8. Request/Response의 Body부분은 std::iostream의 shared_ptr로 관리한다