http://github.com/ipkn/crow
Crow 프로젝트에서 사용한 C++11 기법들을 실제 구현에 대한 설명을 포함하여 자세히 설명한 발표자료입니다.
C++11 features used in Crow
video:
http://youtu.be/MixS9c3mE6U
https://vimeo.com/119627253
Crow
C++ Framework for writing Web server
http://github.com/ipkn/crow
GitHub Star 1063개
Hacker News vote 194
https://news.ycombinator.com/item?id=8002604
Web server 개념
브라우저로 접속해서 요청을 보내면
해당 요청에 맞는 응답을 보내줌
GET / HTTP/1.1
Host: www.example.com
HTTP/1.1 200 OK
Content-Length: 12
...
Hello World!
Routing - CROW_ROUTE
어떤 요청에 어떤 응답을 줄지 지정
CROW_ROUTE(app, “/”)
CROW_ROUTE(app, “/post/all”)
CROW_ROUTE(app, “/post/<int>”)
CROW_ROUTE(app,
“/near/<double>/<double>”)
호출부 구현
template <typename CP,
int NInt, int NUint, int NDouble, int NString,
typename S1, typename S2>
struct call;
S1 - URL로 부터 정의된 핸들러 인자 타입의 리스트
S2 - 호출을 위한 타입+인덱스 정보의 리스트
N?? - 인자 처리를 위한 인덱스 값들, CP - 호출 파라미터
Middleware of express.js (Node.js)
app.use(function (req, res, next) {
req.cookies = parse_cookies(req);
next();
});
req에 임의로 변수추가 가능
Middleware 구현의 제약들
근데 우린 C++이잖아… 변수는 다 선언되어야
unordered_map<string, any> ???
+ express의 문제점: Dependency
세션은 사용하는데 쿠키파서가 없는 경우
이 문제를 어떻게 해결하면 좋을까?
각 MW별로 저장해야할 값들이 존재
어떤 MW는 다른 MW의 값을 참조해야 한다
(C++이니까)
필요한 MW가 빠진 경우 컴파일 에러가 발생해
야함
struct MW::context
MW가 필요한 값을 저장하는 struct
내용에 특별한 제한이 없음
App<A, B, C> app;
A, B, C 세 MW를 사용하는 app
SimpleApp == App<>
Crow에서의 해결 방식
ctx_A : public A::context
ctx_AB : public ctx_A, public B::context
ctx_ABC : public ctx_AB, public C::context
context : public ctx_ABC
실제로 ctx_AB === partial_context<A, B>
context 체인 상속 - App<A, B, C>
Crow에서의 해결 방식
template <typename AllContext>
before_handle(req, res, context& ctx,
AllContext& all_ctx)
from MW - all_ctx.get<A>()
from handler - app.get_context<A>(req)
Crow에서의 해결 방식 - 전체 호출 과정
A::before_handle(req, res, ctx_A)
B::before_handle(req, res, ctx_AB)
C::before_handle(req, res, ctx_ABC)
app.handle(req, res)
C::after_handle(req, res, ctx_ABC)
B::after_handle(req, res, ctx_AB)
A::after_handle(req, res, ctx_A)
다른 MW과 연관이 없는 MW라면?
template <typename AllContext>
before_handle(req, res,
context& ctx, AllContext& all_ctx)
대부분의 경우
before_handle(req, res, context& ctx)
SFINAE
Substitution failure is not an error.
템플릿 인자를 대입하는 과정에서 발생하는 에
러는 에러로 간주하지 않는다. (모두 실패하는
경우에는 에러)
또는 이를 이용한 프로그래밍 기법을 지칭함
template <typename A, typename B>
auto add(A a, B b) -> decltype(a+b)
{ return a+b; }
template <typename A, typename B>
decltype(declval<A>() + declval<B>())
add(A a, B b)
C++14: return type deduction
std::enable_if
SFINAE를 이용하기 편하게 만들어둔 도구
조건부로 구현을 선택
template <typename T>
enable_if<is_integral<T>::value> func(T x) …
handler 형태를 고를 때 사용 (생략)
http://en.cppreference.com/w/cpp/types/enable_if
CookieParser - Example Middleware
CookieParser::context
uo_map<string, string> jar,
cookies_to_add;
get_cookie(key)
set_cookie(key, value)
before - 파싱 + jar에 저장
after - Set-Cookie 헤더 추가
고려해야 할 점
멀티 쓰레드 작업 배분
Connection 객체의 생성, 소멸 관리
HTTP 타임 아웃 (Keep-Alive)
초기 버전
boost::asio + joyent/http-parser
asio::io_service 하나
concurrency 개수 만큼 io_service.run()
shared_ptr<Connection> 으로 수명 관리
asio::deadline_timer로 타임아웃 처리
초당 처리 횟수가 100,000 이상..
shared_ptr의 atomic 연산이 성능 부하
deadline_timer 내부 자료구조 업데이트 부하
여러 개의 싱글 쓰레드 서버의 결합
N 개의 작업용 쓰레드 (asio::io_service ✕ N)
요청이 들어올 때마다
어느 쓰레드가 처리할지 RR로 분배
해당 요청에 대한 모든 처리는
담당한 쓰레드 안에서만 이루어짐