서버 관리 Part 1: RESTful APIs 추가

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

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 함수.

namespace http {
  // HTTP GET 메소드의 인자 목록.
  typedef std::map<string, string, impl::util::CaseInsensitiveLess> GetParameter;

  // 중복되는 key 를 허용하는 HTTP GET 메소드의 인자 목록.
  typedef std::map<string, std::vector<string>,
                  impl::util::CaseInsensitiveLess> GetParameter2;

  // HTTP GET 으로 넘어오는 인자 중에 중복되는 key 를 허용하지 않는 경우 요청 객체
  struct FUNAPI_DLL_VISIBILITY Request {
    Method method;              // HTTP 메소드
    string request_ip;          // Request IP
    string request_uri;         // Request URI
    Version version;            // HTTP 버전
    GetParameter get_parameter; // GET 메소드인 경우 인자 목록
    Header header;              // Header 필드
    string body;                // Request 바디
  };

  // HTTP GET 으로 넘어오는 인자 중에 중복되는 key 를 허용하는 경우 요청 객체
  struct FUNAPI_DLL_VISIBILITY Request2 {
    Method method;                // HTTP 메소드
    string request_ip;            // Request IP
    string request_uri;           // Request URI
    Version version;              // HTTP 버전
    GetParameter2 get_parameter;  // GET 메소드인 경우 인자 목록
    Header header;                // Header 필드
    string body;                  // Request 바디
  };

  struct FUNAPI_DLL_VISIBILITY Response {
    Version version;            // HTTP 버전
    uint32_t status_code;       // 응답 코드
    string status_message;      // 응답 코드에 해당하는 메시지
    Header header;              // Header 필드
    string body;                // Response 바디
  };
}
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;
namespace http
{
  public struct Request
  {
    public Method method; // HTTP 메소드
    public string path; // Request 경로
    public Version version; // HTTP 버전
    public NameValueCollection query; // GET 메소드인 경우 인자 목록
    public NameValueCollection header; // Header 필드
    public string body; // Request 바디
  }

  public struct Response
  {
    public Version version; // HTTP 버전
    public StatusCode status_code; // 응답 코드
    public string status_message; // 응답 코드에 해당하는 메시지
    public NameValueCollection header; // Header 필드
    public string body; // Response 바디
  }
}
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)

핸들러 작성

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);
    }
    

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);
  ...
}

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)