33. 서버 관리 Part 1: RESTful APIs 추가

아이펀 엔진은 game server 내에 손쉽게 RESTful API 를 구현할 수 있는 기능을 제공합니다. 이 기능을 이용하면 HTTP 프로토콜을 이용하여 쉽게 다양한 외부 시스템과 연동할 수 있습니다.

33.1. REST API URL 과 핸들러 등록

아래의 함수를 이용하여 특정 URL 또는 URL Pattern 과 이를 처리하는 핸들러를 등록할 수 있습니다.

static void ApiService::RegisterHandler(const http::Method &method,
                                        const boost::regex &path_pattern,
                                        const Handler &handler)

static void ApiService::RegisterHandler2(const http::Method &method,
                                         const boost::regex &path_pattern,
                                         const Handler2 &handler)

static void ApiService::RegisterHandler3 (const http::Method &method,
                                          const boost::regex &path_pattern,
                                          const Handler3 &handler)
public static void ApiService.RegisterHandler (http.Method method,
                                               string path,
                                               Handler handler)
public static void ApiService.RegisterHandler (http.Method method,
                                               string path,
                                               AsyncHandler handler)

public static void ApiService.RegisterHandler (http.Method method,
                                               Regex path_pattern,
                                               HandlerForRegexPath handler)
public static void ApiService.RegisterHandler (http.Method method,
                                               Regex path_pattern,
                                               AsyncHandlerForRegexPath handler)
  • method: http::kGet, http::kPut, http::kPost, http::kDelete, http::kHead.
  • path: 처리하려는 URL 문자열. 특정 URL 하나만을 지칭할 때 사용됨.
  • path_pattern: 처리하려는 URL pattern. (?<NAME>pattern) 형태로 URL 에 parameter 를 포함하고 코드에서 NAME 으로 읽을 수 있습니다. (아래 예제 참고)
  • handler: 해당 URL 을 처리하는 handler 함수.
typedef boost::function<
    void(const Ptr<http::Response> &)>
         ResponseWriter ASSERT_NO_ROLLBACK;

typedef boost::function<
    void(Ptr<http::Response> ret,
         const http::Request &request,
         const MatchResult &params)> Handler;

typedef boost::function<
    void(Ptr<http::Response> ret,
         const http::Request2 &request,
         const MatchResult &params)> Handler2;

typedef boost::function<
    void(const http::Request2 &request,
         const MatchResult &params,
         const ResponseWriter &finisher)> Handler3;
public delegate void ResponseWriter(http.Response response);

public delegate http.Response Handler (http.Request request);
public delegate void AsyncHandler (http.Request request,
                                   ResponseWriter response_writer);

public delegate http.Response HandlerForRegexPath (http.Request request,
                                                   MatchCollection matches);
public delegate void AsyncHandlerForRegexPath (http.Request request,
                                               MatchCollection matches,
                                               ResponseWriter response_writer);

Important

만약 HTTP 응답을 바로 보내지 않고 비동기적으로 처리해야 한다면 (다른 처리를 비동기로 진행한 후에 HTTP 응답을 보내고 싶다면) 아래 함수로 핸들러를 등록하시면 비동기로 처리 할 수 있습니다.

static void ApiService::RegisterHandler3 (const http::Method &method,
                                          const boost::regex &path_pattern,
                                          const Handler3 &handler)
public static void ApiService.RegisterHandler (http.Method method,
                                               string path,
                                               AsyncHandler handler)

public static void ApiService.RegisterHandler (http.Method method,
                                               Regex path_pattern,
                                               AsyncHandlerForRegexPath handler)

33.2. 핸들러 작성

handler 함수는 아래와 같이 작성할 수 있습니다.

  • HTTP GET 으로 넘어오는 인자 중에 중복되는 key 를 허용하지 않는 경우

    void MyHandler(
      Ptr<http::Response> response,
      const http::Request &request,
      const ApiService::MatchResult &params) {
    
      response->status_code = http::kOk;
      response->body = ...
    }
    
  • HTTP GET 으로 넘어오는 인자 중에 중복되는 key 를 허용하는 경우

    void MyHandler2(
      Ptr<http::Response> response,
      const http::Request2 &request,
      const ApiService::MatchResult &params) {
    
      response->status_code = http::kOk;
      response->body = ...
    }
    
  • Response body 를 비동기로 생성하는 경우 (HTTP GET 의 인자는 중복 key 를 허용)

    void MyHandler3(
      const http::Request2 &request,
      const ApiService::MatchResult &params,
      const ApiService::ResponseWriter &finisher) {
    
      some_async_func(finisher);
    }
    
    void some_async_func(const ApiService::ResponseWriter &finisher) {
      Ptr<http::Response> response(new http::Response);
      finisher(response);
    }
    
  • 단일 URL 을 동기식으로 처리

    funapi.http.Response MySyncHandler1(funapi.http.Request request)
    {
      funapi.http.Response response = new funapi.http.Response ();
      response.status_code = funapi.http.StatusCode.kOk;
      response.body = ...
      return response;
    }
    
  • URL 이 pattern 으로 주어졌을 때 동기식으로 처리

    funapi.http.Response MySyncHandler2(funapi.http.Request request, MatchCollection matches)
    {
      funapi.http.Response response = new funapi.http.Response ();
      response.status_code = funapi.http.StatusCode.kOk;
      response.body = ...
      return response;
    }
    
  • 단일 URL 을 비동기식으로 처리

    void MyAsyncHandler1(funapi.http.Request request, ResponseWriter finisher)
    {
      some_async_func(finisher)
    }
    
    void some_async_func(ResponseWriter finisher)
    {
      funapi.http.Response response = new funapi.http.Response ();
      response.status_code = funapi.http.StatusCode.kOk;
      response.body = ...
      finisher(response);
    }
    
  • URL 이 pattern 으로 주어졌을 때 비동기식으로 처리

    void MyAsyncHandler2(funapi.http.Request request, MatchCollection matches, ResponseWriter finisher)
    {
      some_async_func(finisher)
    }
    
    void some_async_func(ResponseWriter finisher)
    {
      funapi.http.Response response = new funapi.http.Response ();
      response.status_code = funapi.http.StatusCode.kOk;
      response.body = ...
      finisher(response);
    }
    

33.3. API Service 등록 예제

아래는 다음과 같은 두 개의 API 를 등록하는 예제입니다.

GET /example/hello
GET /example/hello2/*/*
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// request: http://localhost:8014/example/hello
// response: hello!
void OnHello(Ptr<http::Response> response,
    const http::Request &request,
    const ApiService::MatchResult &params) {
  response->status_code = http::kOk;
  response->header.insert(std::make_pair("Content-Type", "text/plain"));
  response->body = "hello!";
}


// request: http://localhost:8014/example/hello2/bob/24
// response:
// {
//   "name": "bob",
//   "age": "24"
// }
void OnHello2(Ptr<http::Response> response,
    const http::Request &request,
    const ApiService::MatchResult &params) {
  Json msg;
  msg["name"] = params["name"];
  msg["age"] = params["age"];
  response->status_code = http::kOk;
  response->header.insert(std::make_pair("Content-Type", "application/json"));
  response->body = msg.ToString();
}


static bool Install(const ArgumentMap &arguments) {
  ...
  ApiService::RegisterHandler(
      http::kGet, boost::regex("/example/hello"), OnHello);

  ApiService::RegisterHandler(http::kGet,
      boost::regex("/example/hello2/(?<name>\\w+)/(?<age>\\w+)/"), OnHello2);
  ...
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
using System.Text.RegularExpressions;
using System.Collections.Specialized;
using funapi;

// request: http://localhost:8014/example/hello
// response: hello!
public static funapi.http.Response OnHello(funapi.http.Request request)
{
  funapi.http.Response response = new funapi.http.Response ();
  response.status_code = funapi.http.StatusCode.kOk;
  response.body = "hello"; // response body
  return response;
}


// request: http://localhost:8014/example/hello2/bob/24
// response:
// {
//   "name": "bob",
//   "age": "24"
// }
public static funapi.http.Response OnHello2(
    funapi.http.Request request,
    MatchCollection collection)
{
  funapi.http.Response response = new funapi.http.Response ();
  response.header = new NameValueCollection ();
  response.header.Add ("Content-Type", "application/json");
  response.status_code = funapi.http.StatusCode.kOk;

  JObject msg = new JObject ();
  GroupCollection groups = collection [0]. Groups;
  msg ["name"] = groups ["name"].Value;
  msg ["age"] = groups ["age"].Value;
  response.body = msg.ToString ();
  return response;
}


public static void Install(ArgumentMap arguments)
{
  ...
  ApiService.RegisterHandler (
      funapi.http.Method.kGet, "/example/hello", OnHello);

  ApiService.RegisterHandler (
      funapi.http.Method.kGet,
      new Regex("/example/hello2/(?<name>\\w+)/(?<age>\\w+)/"),
      OnHello2);
  ...
}

33.4. ApiService 기능 설정 파라미터

  • api_service_port: 관리용 RESTful API 서비스를 돌리기 위한 로컬 TCP 포트 번호. (type=uint64, default=8015)
  • api_service_logging_level: 관리용 RESTful API 메시지들의 로그 레벨. 0 은 로그를 남기지 않음. 1은 에러인 경우만 남김. 2는 모든 요청에 대해 로그를 남김 (type=uint64, default=2)

직접 설정을 바꿀 일이 거의 없는 설정들

  • api_service_event_tags_size: 관리용 RESTful API 서비스를 동시에 돌리는 갯수 (type=uint64, default=1)