Web API

Oasis는 HTTP, HTTPS 서버와 클라이언트를 지원합니다.

헤더 파일

OasisWeb.h

HttpUpStreamDelegate 인터페이스

Oasis Web 서버와 클라이언트는 HttpUpStreamDelegate 인터페이스로 부터 유도된 사용자 정의 객체를 통하여 HTTP 요청과 응답을 처리합니다.

class HttpUpStreamDelegate : public std::enable_shared_from_this<HttpUpStreamDelegate>
{
public:
    HttpUpStreamDelegate();
    virtual ~HttpUpStreamDelegate();

public: 
    virtual void onRequest(const NetMessageRef &msg);
    virtual void onEarlyResponse(const NetMessageRef &msg); //when the response header is ready
    virtual void onResponse(const NetMessageRef &msg); //when the response header and contents ready
    virtual void onGatewayResponse(const struct sockaddr_in &local_addr, const struct sockaddr_in &remote_addr, int32_t status_code, const char *status_reason, const char *content_type, const char *content, size_t content_length, key_value_map_t &parameters);
};
void onRequest ( const NetMessageRef & msg )
OasisWeb.h
클라이언트로 부터 요청 NetMessage를 수신한 경우 호출됩니다.
매개변수
msg  요청 NetMessage 객체입니다.
void onEarlyResponse ( const NetMessageRef & msg )
OasisWeb.h
웹 서버로 부터 응답 헤더 NetMessage를 수신한 경우 호출됩니다. 아직 컨텐츠는 수신하지 않는 상태입니다.
매개변수
msg  응답 NetMessage 객체입니다.
void onResponse ( const NetMessageRef & msg )
OasisWeb.h
웹 서버로 부터 완전한 응답 NetMessage를 수신한 경우 호출됩니다.
매개변수
msg  응답 NetMessage 객체입니다.
void onGatewayResponse ( const struct sockaddr_in & local_addr , const struct sockaddr_in & remote_addr , int32_t status_code , const char * status_reason , const char * content_type , const char * content , size_t content_length , key_value_map_t & parameters )
OasisWeb.h
DDS 게이트웨이 웹 서버에서 사용되는 중계용 콜백 함수입니다. 일반 웹 서버에서는 사용하지 않습니다.
매개변수
local_addr  로컬 웹 서버가 원격 클라이언트와 연결시에 binding된 로컬 소켓 주소입니다.
remote_addr  원격 클라이언트의 소켓 주소입니다.
status_code  응답 코드입니다.
status_reason  응답 설명입니다.
content_type  응답 메세지의 Content-Type입니다. 컨텐츠가 없으면 nullptr을 지정합니다.
content  응답 메세지의 컨텐츠입니다. 컨텐츠가 없으면 nullptr을 지정합니다.
content_length  응답 메세지 컨텐츠 크기입니다. 바이트 단위입니다. 컨텐츠가 없으면 0을 지정합니다.
parameters  사용하지 않습니다.

웹 서버 함수

HttpServerRef createHttpServer ( key_value_map_t & parameters , const std::shared_ptr<HttpUpStreamDelegate> & delegate )
OasisWeb.h
HTTP 서버 객체를 생성합니다.
매개변수
parameters  HTTP 서버 객체 생성에 필요한 key-value map 입니다.
delegate  HTTP 요청 처리를 위한 사용자 정의 upstream delegate 객체입니다.
리턴값
성공하면 HTTP 서버 객체를 리턴합니다. 실패하면 nullptr을 리턴합니다.
기본값
필수
설명
port
80
 
웹서버 포트번호입니다.
root-dir
웹서버 문서가 위치한 루트 디렉토리 경로입니다.
remote-pool-size
10
 
최대 접속 허용 수 입니다. "0"이면 무제한입니다.
scheduler-size
10
 
서버 스케쥴러 태스크 개수입니다.
upstreamer-size
10
 
Delegate에 전달할 업스트림 태스크 개수입니다.
transmitter-size
10
 
전송 태스크 개수입니다.
timer-size
3
 
타이머 태스크 개수입니다.
enable-stun
0
 
STUN을 활성화합니다. 사용하지 않습니다
stun-server-addr
 
STUN 서버 주소입니다. 사용하지 않습니다
stun-server-port
 
STUN 서버 포트입니다. 사용하지 않습니다
content-length-max
1048576
 
HTTP 컨텐츠 최대 허용 길이입니다. 바이트 단위입니다.
linger-time-max
1000
 
연결 종료후 최대 유지 시간입니다. 밀리초 단위입니다.
local-connection-only
0
 
"1"이면 로컬 네트워크에 있는 클라이언트만 접속 허용합니다.
cache-dir
 
캐쉬 디렉토리입니다.
cached-content-types
 
캐쉬할 Content-Type 목록입니다. 콤마(,)로 구분되어지는 MIME 타입의 문자열 리스트입니다.

HTTPS를 활성화하려면 아래 key-value map이 필요합니다.

기본값
필수
설명
tls-enabled
0
TLS 활성화 여부를 지정합니다. "1"이면 활성화되며, "0"이면 TLS를 사용하지 않습니다. 사용할 경우 인증서 경로등 키값이 유효해야 합니다.
tls-version
1.2
TLS 버전입니다. "1.2"를 입력합니다.
tls-client-verify
0
 
접속 클라이언트의 인증서를 검증할 지 여부를 지정합니다. "1" 이면 tls-certificate-request-authority-names 키에 등록된 이름만 연결 허용합니다.
tls-certificate-request-authority-names
 
\0xa 로 구분된 기관명(CN) 목록입니다. 일례로, "MediaStek-Root-CA\x0aMediaStek Inc." 와 같은 형식입니다.
ca-certs-file-path
 
\0xa 로 구분된 PEM 양식의 ROOT CA 인증서 파일 경로입니다. 일례로, "/data/certs/cacerts.pem\x0a/data/certs/RootCA.pem" 와 같은 형식입니다.
cert-file-path
RSA, ECDSA, DSS 등의 PEM 양식의 공개 인증서 파일 경로입니다.
pkey-file-path
PEM 양식의 개인키 파일 경로입니다.
int32_t destroyHttpServer ( HttpServerRef & server )
OasisWeb.h
HttpServer를 해제합니다.
매개변수
server  createHttpServer로 생성한 HttpServer 객체입니다.
리턴값
  • 0: 성공
  • -1: 실패
int32_t startHttpServer ( const HttpServerRef & server )
OasisWeb.h
HttpServer를 시작합니다.
매개변수
server  createHttpServer로 생성한 HttpServer 객체입니다.
리턴값
  • 0: 성공
  • -1: 실패
int32_t stopHttpServer ( const HttpServerRef & server )
OasisWeb.h
HttpServer를 중지합니다.
매개변수
server  createHttpServer로 생성한 HttpServer 객체입니다.
리턴값
  • 0: 성공
  • -1: 실패
int32_t createHttpWsService ( const HttpServerRef & http_server , const char * url , key_value_map_t & parameters , const std::shared_ptr<WsEventDelegate> & delegate )
OasisWeb.h
HttpServer의 지정한 URL에 웹소켓 서비스를 활성화합니다.
매개변수
http_server  createHttpServer로 생성한 HttpServer 객체입니다.
url  웹소켓 서비스 URL 경로입니다.
parameters  웹소켓 서비스 생성에 필요한 key-value map 입니다.
delegate  웹소켓 이벤트 처리를 위한 WsEventDelegate로 부터 유도된 사용자 정의 객체입니다.
리턴값
  • 0: 성공
  • -1: 실패

아래는 웹소켓 서비스 생성에 필요한 key-value map 목록입니다.

기본값
필수
설명
websocket-protocols
 
사용자가 정의한 프로토콜 이름입니다. 웹소켓 연결이 이 필드가 비어지 않으면 이 필드 값과 매치가 되어야 연결이 이루어집니다.
websocket-fragment-size-max
65535
 
최대 fragment 크기입니다.
int32_t httpInterpretServerPage ( NetMessageRef & response , const char * url , const char * server_page_path , http_interpret_callback_t interpret_callback , void * callback_data , key_value_map_t * callback_arguments )
OasisWeb.h
웹 서버 의 요청 메세지 처리 핸들러인 HttpUpStreamDelegate::onRequest API에서 서버 스크립트 콜백을 수행할 때 호출합니다. 웹 페이지 파일 내에 <?=<key_name>?> 이러한 서버 스크립트 태깅이 있을 경우, <key_name> 값을 얻기 위해 http_interpret_callback_t로 정의된 사용자 함수가 호출됩니다. server_page_path 파일이 없으면 "400 Bad Request" 응답을 전송합니다. 성공할 경우, "200 OK" 응답 코드와 함께 파일의 내용을 전송합니다.
매개변수
response  응답 NetMessage 객체입니다.
url  이 웹 문서의 URL 경로입니다.
server_page_path  이 웹 문서의 서버 파일 시스템 내 절대경로입니다.
interpret_callback  웹 서버 스크립트 콜백 함수입니다.
callback_data  웹 서버 스크립트 콜백 함수에 전달될 사용자 정의 데이터입니다.
callback_arguments  웹 서버 스크립트 콜백 함수에 전달될 key-value map 포인터입니다.
리턴값
  • 0: 성공
  • -1: 실패

아래 코드는 웹 문서 확장자가 osp인 경우, 웹 서버 스크립트 수행하는 예입니다.

static int32_t httpInterpretCallback(const char *url, void *callback_data, key_value_map_t *callback_arguments, const char *key_name, std::string &output)
{
    // key_name에 해당하는 값을 구하여 output에 리턴합니다.
    return 0;
}

void MyHttpConfigUpStreamDelegate::onRequest(const NetMessageRef &msg)
{
    bool reply_now = true;
    std::string url;
    netMessageUriGetPath(msg, url);

    ASSERT(netMessageIsRequest(msg));
    if(!netMessageIsRequest(msg)) {
        return;
    }

    NetMessageRef res = netMessageCreate(msg, false);
    if(res == nullptr) {
        netMessageAbortConnection(msg);
        return;
    }

    netMessageSetStatusCodeAndReasonString(res, 200, "OK");
    netMessageSetHeaderField(res, "Connection", "close");

    if(url == "/") {

        std::string index_path = app().httpRootPath();
        index_path += "/index.osp";
        err = httpInterpretServerPage(res, url.c_str(), index_path.c_str(), &httpInterpretCallback, (void*)this, nullptr);
        if(err < 0) {
            //do nothing
            netMessageAbortConnection(msg);
            return;
        }
    } else {

        std::string path = app().httpRootPath();
        path += url;
        if(access(path.c_str(), F_OK) == 0) {
            //get mime type based on extension
            const char *mime_type = get_file_mime_type(path.c_str());   
            if(mime_type == nullptr) {
                //not supported
                netMessageSetStatusCodeAndReasonString(res, 403, "Forbidden");
            } else {
                std::string ext = get_file_ext(path);
                if(ext == "osp") {
                    err = httpInterpretServerPage(res, url.c_str(), path.c_str(), &httpInterpretCallback, (void*)this, nullptr);
                    if(err < 0) {
                        //do nothing
                        netMessageAbortConnection(msg);
                        return;
                    }
                } else {
                    ssize_t ret = netMessageSendFile(res, path.c_str(), mime_type);
                    reply_now = false;
                }
            }
        } else {
            netMessageSetStatusCodeAndReasonString(res, 400, "Bad Request");
        }
    }

done:
    if(reply_now) {
        netMessagePost(res);    
    }
}

Script 처리 콜백

Oasis 웹 서버는 웹 문서 내에 <?=<key_name>?> 양식의 서버 스크립트용 요소를 정의할 수 있습니다.

httpInterpretServerPage 함수는 웹 문서에서 이 요소를 분류하여, <key_name>에 해당하는 실제 값을 http_interpret_callback_t 콜백 함수를 호출하여 얻습니다. 이렇게 동적으로 변경한 웹 문서 내용을 클라이언트에게 전달합니다.

typedef  int32_t  (*http_interpret_callback_t) ( const char * url , void * callback_data , key_value_map_t * callback_arguments , const char * key_name , std::string & output )
OasisWeb.h
매개변수
url  웹 문서의 URL입니다.
callback_data  httpInterpretCallback 호출시 매개변수로 전달한 사용자 정의 데이터입니다.
callback_arguments  httpInterpretCallback 호출시 매개변수로 전달한 key-value map 포인터입니다.
key_name  키 이름입니다.
output  OUT 키 값을 리턴합니다.
리턴값
  • 0: 성공
  • -1: 실패

아래는 http_interpret_callback_t 구현 예입니다.

<p>
    제품명은 <?=printProductName?> 입니다.
</p>  
<p>
    접속한 서버의 IP 주소는 <?=printIPAddress?> 입니다.
</p>
static int32_t httpInterpretCallback(const char *url, void *callback_data, key_value_map_t *callback_arguments, const char *key_name, std::string &output)
{
    std::string  _url(url);
    char value[256];
    int32_t err;

    output.clear();

    if(strcmp(key_name, "printProductName") == 0) {
        output = "Oasis";
    } else if(strcmp(key_name, "printIPAddress") == 0) {
        output = "127.0.0.1";
    }

    return 0;
}

웹 클라이언트 함수

HttpConnectionRef httpOpenConnection ( const char * url , int32_t timeout , const char * cache_directory = nullptr )
OasisWeb.h
HttpConnection 객체를 생성하고 웹 서버에 연결합니다.
매개변수
url  연결할 웹서버 URL입니다.
timeout  연결될 때까지 기다리는 최대 시간입니다. 밀리초 단위입니다.
cache_directory  임시 파일을 저장할 캐쉬 디렉토리를 경로입니다.
리턴값
성공하면 HttpConnection 객체를 리턴하고 실패하면 nullptr을 리턴합니다.
HttpConnectionRef httpOpenConnectionWithParameters ( const char * url , int32_t timeout , key_value_map_t & parameters , const char * cache_directory = nullptr )
OasisWeb.h
key-value map을 이용하여 HttpConnection 객체를 생성하고 웹 서버에 연결합니다.
매개변수
url  연결할 웹서버 URL입니다.
timeout  연결될 때까지 기다리는 최대 시간입니다. 밀리초 단위입니다.
parameters  연결에 필요한 key-value map 입니다.
cache_directory  임시 파일을 저장할 캐쉬 디렉토리를 경로입니다.
리턴값
성공하면 HttpConnection 객체를 리턴하고 실패하면 nullptr을 리턴합니다.

연결에 필요한 key-value map은 아래와 같습니다.

기본값
필수
설명
ca-certs-file-path
 
\0xa 로 구분된 PEM 양식의 ROOT CA 인증서 파일 경로입니다. 일례로, "/data/certs/cacerts.pem\x0a/data/certs/RootCA.pem" 와 같은 형식입니다.
cert-file-path
 
RSA, ECDSA, DSS 등의 PEM 양식의 공개 인증서 파일 경로입니다.
pkey-file-path
 
PEM 양식의 개인키 파일 경로입니다.
tls-disable-certificate-validation
0
 
웹 서버의 인증서를 검증하지 않습니다.
tls-use-sni
0
 
서버 연결시 서버의 FQDN을 Hello Handshake에 포함시킵니다.
int32_t httpOpenConnection ( const HttpConnectionRef & connection , const char * url , int32_t timeout )
OasisWeb.h
웹 서버에 연결합니다. httpConnectionIsClosed로 연결 상태를 확인하고, 이미 연결된 상태면 httpCloseConnection을 호출한 다음, httpOpenConnection를 사용합니다.
매개변수
connection  HttpConnection 객체입니다.
url  연결할 웹서버 URL입니다.
timeout  연결될 때까지 기다리는 최대 시간입니다. 밀리초 단위입니다.
리턴값
  • 0: 성공
  • -1: 실패
int32_t httpCloseConnection ( const HttpConnectionRef & connection , bool is_shutdown = false )
OasisWeb.h
연결을 닫습니다.
매개변수
connection  HttpConnection 객체입니다.
is_shutdown  true이면 소켓을 완전히 닫습니다.
리턴값
  • 0: 성공
  • -1: 실패
int32_t httpConnectionIsClosed ( const HttpConnectionRef & connection )
OasisWeb.h
현재 연결이 닫혀 있는지 확인합니다.
매개변수
connection  HttpConnection 객체입니다.
리턴값
  • 1: 연결이 닫혀 있습니다.
  • 0: 연결 중입니다.
NetMessageRef httpConnectionGetRequestMessage ( const HttpConnectionRef & connection )
OasisWeb.h
연결 성공시 생성한 요청 타입의 NetMessage 객체를 얻습니다.
매개변수
connection  HttpConnection 객체입니다.
리턴값
요청 타입의 NetMessage 객체를 리턴합니다. 유효하지 않으면 nullptr을 리턴합니다.
NetMessageRef httpConnectionGetResponseHeaderMessage ( const HttpConnectionRef & connection , int32_t timeout )
OasisWeb.h
응답 헤더 NetMessage를 얻습니다. 완전한 응답 메세지를 받는데 오래 걸릴 경우, 우선 헤더를 참조하기 위해 사용됩니다.
매개변수
connection  HttpConnection 객체입니다.
timeout  응답 헤더를 받을 때까지 기다리는 시간입니다. 밀리초 단위입니다.
리턴값
응답 헤더 NetMessage를 리턴합니다. 오류가 있거나 아직 웹 서버로 부터 응답 메세지를 받지 못 하거나 타임아웃이 발생하면 nullptr을 리턴합니다.
NetMessageRef httpConnectionGetResponseMessage ( const HttpConnectionRef & connection , int32_t timeout )
OasisWeb.h
완전한 응답 NetMessage를 얻습니다.
매개변수
connection  HttpConnection 객체입니다.
timeout  완전한 응답 메세지를 받을 때까지 기다리는 시간입니다. 밀리초 단위입니다.
리턴값
완전한 응답 NetMessage를 리턴합니다. 완전히 응답을 받지 못 한 경우나 타임아웃이 발생한 경우 nullptr을 리턴합니다.

예제

서버

아래 예제의 서버는 홈 페이지에 접속하면 index.osp 파일을 읽어서 Script 콜백 함수를 호출하여 index.osp 내에 있는 변수값을 실제 적용 값으로 치환하여 클라이언트에게 전송합니다. 그외 파일에 접근할 경우, 허용된 경우에 파일 내용을 전송합니다.

또한 /ws/echo 에 웹소켓 서버를 연결하여, 웹소켓 클라이언트가 /ws/echo URL에 접속할 경우, 웹소켓 통신을 하게 됩니다.

응용 프로그램 이름은 webserver 이고 사용법은 아래와 같습니다.

USAGE: ./webserver  [-p <port number>] [-s] <documnet root dir>
$ ./webserver -p 8080 -s /mnt/sd
  • p 옵션은 포트번호를 지정합니다.
  • s 옵션은 보안 서버 (HTTPS)를 실행합니다. 인증서와 개인키는 각 /mnt/sd/webserver-cert.pem 와 /mnt/sd/webserver-key.pem 에 있다고 가정합니다.
  • documnet root dir 필수 인자로서 HTTP 문서 파일들의 루트 디렉토리 경로입니다.

코드를 설명합니다.

서버에서 스크립트 콜백 함수를 정의합니다.

static int32_t httpInterpretCallback(const char *url, void *callback_data, key_value_map_t *callback_arguments, const char *key_name, std::string &output)
{
    std::string  _url(url);
    char value[256];
    int32_t err;

    output.clear();

    if(strcmp(key_name, "printModelName") == 0) {
        output = "OASIS";
    }  

    return 0;
}

HTTP 서버용 HttpUpStreamDelegate 에서 유도된 사용자 정의 Delegate 클래스를 정의합니다.


class MyHttpUpStreamDelegate : public HttpUpStreamDelegate 
{
public:
    MyHttpUpStreamDelegate() { }
    virtual ~MyHttpUpStreamDelegate() { }

public:
    virtual void onRequest(const NetMessageRef &msg) {

        std::string url;
        netMessageUriGetPath(msg, url);

        ASSERT(netMessageIsRequest(msg));
        if(!netMessageIsRequest(msg)) {
            return;
        }

        NetMessageRef res = netMessageCreate(msg, false);
        if(res == nullptr) {
            netMessageAbortConnection(msg);
            return;
        }    

        fprintf(stdout, "onRequest: %s\n", url.c_str());

        netMessageSetStatusCodeAndReasonString(res, 200, "OK");
        //netMessageSetHeaderField(res, "Connection", "close");

        if(url == "/") {
            // HOME url이면, HTTP 문서의 루트 디렉토리에 index.osp 파일을 읽어와 스크립트를 실행 후 전송합니다.
            std::string index_path = http_root_dir_path + "/index.osp";
            int32_t err = httpInterpretServerPage(res, url.c_str(), index_path.c_str(), &httpInterpretCallback, (void*)this, nullptr);
            if(err < 0) {
                netMessageAbortConnection(msg);
                return;
            } else {
                netMessagePost(res);    
            }

        } else {
            std::string path = http_root_dir_path + url;
            if(access(path.c_str(), F_OK) == 0) {
                //get mime type based on extension
                const char *mime_type = get_file_mime_type(path.c_str());   
                if(mime_type == nullptr) {
                    //not supported
                    netMessageSetStatusCodeAndReasonString(res, 403, "Forbidden");
                    netMessagePost(res);    
                } else {
                    std::string ext = get_file_ext(path);
                    if(ext == "osp") {
                        //oasis server script file extension
                        int32_t err = httpInterpretServerPage(res, url.c_str(), path.c_str(), &httpInterpretCallback, (void*)this, nullptr);
                        if(err < 0) {
                            netMessageAbortConnection(msg);
                            return;
                        }
                    } else {
                        netMessageSendFile(res, path.c_str(), mime_type);
                    }
                }
            } else {
                netMessageSetStatusCodeAndReasonString(res, 400, "Bad Request");
                netMessagePost(res);    
            }
        }
    }

    virtual void onResponse(const NetMessageRef &msg) {

    }
};

index.osp 문서는 아래와 같습니다.

<html>
<head>
<meta charset="utf-8">
<title>Oasis Web Server</title>
</head>
<body>
<h1> Hello, World!</h1>
<h2> My model is <?=printModelName?>.</h2>
</body>
</html>

웹소켓 서버용 WsEventDelegate 에서 유도된 사용자 정의 Delegate 클래스를 정의합니다. 단순 받은 메세지를 그대로 응답하는 기능입니다.


class EchoWsEventDelegate : public WsEventDelegate
{
public:
    EchoWsEventDelegate() {}
    virtual ~EchoWsEventDelegate() {}

public: 
    virtual void onOpen(const WsEventRef &evt) {
        fprintf(stdout, "Echo: Open\n");
    }

    virtual void onMessage(const WsEventRef &evt)  {
        fprintf(stdout, "Echo: Message\n");

        if(wsEventMessageType(evt) == kWsMessageText) {
            std::vector<uint8_t> data;
            if(wsEventData(evt, data) > 0) {
                std::string str = "Echo: ";
                str += std::string(data.data(), data.data()+data.size());
                wsSendText(evt, str.c_str());
            }
        }
    }

    virtual void onError(const WsEventRef &evt) {
        fprintf(stdout, "Echo: Error\n");
    }

    virtual void onClose(const WsEventRef &evt) {
        fprintf(stdout, "Echo: Close\n");
    }
};

Oasis를 초기화 합니다. Oasis 파일 시스템을 사용하지 않습니다.

    key_value_map_t parameters;

    parameters["offs-disable"] = "1";
    if(oasis::initialize(parameters) < 0) {
        fprintf(stderr,  "Oasis init failed\n");
        return -1;
    }

HTTP 서버 객체를 생성하고 시작합니다.


    parameters.clear();

    parameters["port"] = std::to_string(http_port);
    parameters["root-dir"] = http_root_dir_path.c_str();
    parameters["content-length-max"] = std::to_string(3*1024*1024);

    parameters["tls-disable-certificate-validation"] = "1";

    if(is_secure) {
        parameters["tls-enabled"] = "1";
        parameters["tls-version"] = "1.2";

        parameters["cert-file-path"] = "/mnt/sd/webserver-cert.pem";
        parameters["pkey-file-path"] = "/mnt/sd/webserver-key.pem";
    }

    http_delegate = std::make_shared<MyHttpUpStreamDelegate>();

    http_server = oasis::createHttpServer(parameters, http_delegate);
    if (http_server) {
        err = oasis::startHttpServer(http_server);
        ASSERT(err == 0);
        if(err < 0) {
            fprintf(stderr,  "HTTPS server failed\n");
            goto done;
        }
        fprintf(stdout, "HTTP%s running at %d...\n", is_secure?"S":"", http_port);

        // 웹소켓 서비스를 /ws/echo URL을 통해 접근하도록 합니다.
        parameters.clear();
        std::shared_ptr<EchoWsEventDelegate> echo_delegate = std::make_shared<EchoWsEventDelegate>();
        if(createHttpWsService(http_server, "/ws/echo", parameters, echo_delegate) >= 0) {
        }
    }

HTTP 서버를 해제합니다.

    if(http_server) {
        oasis::destroyHttpServer(http_server);
    }

전체 코드는 아래와 같습니다.

#include "OasisAPI.h"
#include "OasisWeb.h"
#include "OasisLog.h"
#include "OasisUtil.h"

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <dirent.h>

#include <signal.h>

#include <thread>
#include <mutex>
#include <memory>
#include <condition_variable>

#define DLOG_THIS   0x00080000

#undef DLOG_FLAGS
#define  DLOG_FLAGS (DLOG_FLAGS_DEFAULT|DLOG_THIS/**/)

#undef DLOG_TAG
#define DLOG_TAG "HTTP-SERVER"


using namespace oasis;

static bool isDirectory(const char *path)
{
    struct stat path_stat;
    stat(path, &path_stat);
    return S_ISDIR(path_stat.st_mode);
}

static std::string http_root_dir_path;


static int32_t httpInterpretCallback(const char *url, void *callback_data, key_value_map_t *callback_arguments, const char *key_name, std::string &output)
{
    std::string  _url(url);
    char value[256];
    int32_t err;

    output.clear();

    if(strcmp(key_name, "printModelName") == 0) {
        output = "OASIS";
    }  

    return 0;
}


class MyHttpUpStreamDelegate : public HttpUpStreamDelegate 
{
public:
    MyHttpUpStreamDelegate() { }
    virtual ~MyHttpUpStreamDelegate() { }

public:
    virtual void onRequest(const NetMessageRef &msg) {

        std::string url;
        netMessageUriGetPath(msg, url);

        ASSERT(netMessageIsRequest(msg));
        if(!netMessageIsRequest(msg)) {
            return;
        }

        NetMessageRef res = netMessageCreate(msg, false);
        if(res == nullptr) {
            netMessageAbortConnection(msg);
            return;
        }    

        fprintf(stdout, "onRequest: %s\n", url.c_str());

        netMessageSetStatusCodeAndReasonString(res, 200, "OK");
        //netMessageSetHeaderField(res, "Connection", "close");

        if(url == "/") {

            std::string index_path = http_root_dir_path + "/index.osp";
            int32_t err = httpInterpretServerPage(res, url.c_str(), index_path.c_str(), &httpInterpretCallback, (void*)this, nullptr);
            if(err < 0) {
                netMessageAbortConnection(msg);
                return;
            } else {
                netMessagePost(res);    
            }

        } else {
            std::string path = http_root_dir_path + url;
            if(access(path.c_str(), F_OK) == 0) {
                //get mime type based on extension
                const char *mime_type = get_file_mime_type(path.c_str());   
                if(mime_type == nullptr) {
                    //not supported
                    netMessageSetStatusCodeAndReasonString(res, 403, "Forbidden");
                    netMessagePost(res);    
                } else {
                    std::string ext = get_file_ext(path);
                    if(ext == "osp") {
                        //oasis server script file extension
                        int32_t err = httpInterpretServerPage(res, url.c_str(), path.c_str(), &httpInterpretCallback, (void*)this, nullptr);
                        if(err < 0) {
                            netMessageAbortConnection(msg);
                            return;
                        }
                    } else {
                        netMessageSendFile(res, path.c_str(), mime_type);
                    }
                }
            } else {
                netMessageSetStatusCodeAndReasonString(res, 400, "Bad Request");
                netMessagePost(res);    
            }
        }
    }

    virtual void onResponse(const NetMessageRef &msg) {

    }
};

class EchoWsEventDelegate : public WsEventDelegate
{
public:
    EchoWsEventDelegate() {}
    virtual ~EchoWsEventDelegate() {}

public: 
    virtual void onOpen(const WsEventRef &evt) {
        fprintf(stdout, "Echo: Open\n");
    }

    virtual void onMessage(const WsEventRef &evt)  {
        fprintf(stdout, "Echo: Message\n");

        if(wsEventMessageType(evt) == kWsMessageText) {
            std::vector<uint8_t> data;
            if(wsEventData(evt, data) > 0) {
                std::string str = "Echo: ";
                str += std::string(data.data(), data.data()+data.size());
                wsSendText(evt, str.c_str());
            }
        }
    }

    virtual void onError(const WsEventRef &evt) {
        fprintf(stdout, "Echo: Error\n");
    }

    virtual void onClose(const WsEventRef &evt) {
        fprintf(stdout, "Echo: Close\n");
    }
};


static bool continue_running = true;

void cancel_handler(int signum)
{
    continue_running = false;
}


void printUsage(const char *pgname)
{
    fprintf(stderr, "USAGE: %s [-p <port number>] [-s] <documnet root dir>\n", pgname);
}

using namespace oasis;

int main(int argc, char* argv[])
{
    int32_t err;
    int c;
    int32_t http_port = 80;
    bool is_secure = false;

    std::shared_ptr<MyHttpUpStreamDelegate> http_delegate;
    HttpServerRef http_server;

    opterr = 0;
    while ((c = getopt(argc, argv, "p:sh")) != -1) {
        switch (c) {
        case 'p':
            if(optarg == nullptr) {
                fprintf(stderr, "error: bad option argument '%c'\n", optopt);
                return -1;
            }
            http_port = atoi(optarg);
            if(http_port <= 0 || http_port > 65535) {
                fprintf(stderr, "bad port number: %d\n", http_port);
                return -1;
            }
            break;
        case 's':
            is_secure = true;
            break;
        case 'h':
        default:
            printUsage(argv[0]);
            return -1;
        }
    }

    if(argc-optind < 1) {
        fprintf(stderr, "error: invalid or insufficient arguments (%d, %d)\n", argc, optind);
        printUsage(argv[0]);
        return -1;
    }

    http_root_dir_path = argv[optind];
    if(!isDirectory(http_root_dir_path.c_str())) {
        fprintf(stderr, "error: invalid root directory: %s\n", http_root_dir_path.c_str());
        printUsage(argv[0]);
        return -1;
    }

    key_value_map_t parameters;

    parameters["offs-disable"] = "1";
    if(oasis::initialize(parameters) < 0) {
        fprintf(stderr,  "Oasis init failed\n");
        return -1;
    }

    parameters.clear();

    parameters["port"] = std::to_string(http_port);
    parameters["root-dir"] = http_root_dir_path.c_str();
    parameters["content-length-max"] = std::to_string(3*1024*1024);

    parameters["tls-disable-certificate-validation"] = "1";

    if(is_secure) {
        parameters["tls-enabled"] = "1";
        parameters["tls-version"] = "1.2";

        parameters["cert-file-path"] = "/mnt/sd/webserver-cert.pem";
        parameters["pkey-file-path"] = "/mnt/sd/webserver-key.pem";
    }

    http_delegate = std::make_shared<MyHttpUpStreamDelegate>();

    http_server = oasis::createHttpServer(parameters, http_delegate);
    if (http_server) {
        err = oasis::startHttpServer(http_server);
        ASSERT(err == 0);
        if(err < 0) {
            fprintf(stderr,  "HTTPS server failed\n");
            goto done;
        }
        fprintf(stdout, "HTTP%s running at %d...\n", is_secure?"S":"", http_port);

        //serverless websocket handler
        parameters.clear();
        std::shared_ptr<EchoWsEventDelegate> echo_delegate = std::make_shared<EchoWsEventDelegate>();
        if(createHttpWsService(http_server, "/ws/echo", parameters, echo_delegate) >= 0) {
        }
    }

    do {
        usleep(100000);
    } while(continue_running);

done:
    if(http_server) {
        oasis::destroyHttpServer(http_server);
    }

    oasis::finalize();

    return 0;
}

클라이언트

아래는 웹 사이트에 접속해서 홈페이지 HTML 문서를 다운받는 클라이언트는 예입니다.

#include "OasisAPI.h"
#include "OasisWeb.h"

#include "OasisLog.h"

#include <thread>
#include <mutex>
#include <memory>
#include <condition_variable>

#define DLOG_THIS   0x00080000

#undef DLOG_FLAGS
#define  DLOG_FLAGS (DLOG_FLAGS_DEFAULT|DLOG_THIS/**/)

#undef DLOG_TAG
#define DLOG_TAG "HTTPCLI"

using namespace oasis;

int main(int argc, char* argv[])
{
    int32_t err;

    int32_t status_code;
    std::string reason_string;
    std::string content_type; 
    std::vector<char> res_content;
    size_t content_length;

    const char *url = "https://google.com/";

    HttpConnectionRef conn;
    NetMessageRef msg, res_msg;
    int32_t wait_count = 0;

    key_value_map_t parameters, fields;


    parameters["offs-disable"] = "1";
    parameters["media-cache-size"] = "4096";
    if(oasis::initialize(parameters) < 0) {
        DLOG(DLOG_ERROR, "Oasis init failed\n");
        return -1;
    }

    parameters.clear();

    // 클라이언트의 인증서가 필요할 경우 설정합니다.
    // parameters["ca-certs-file-path"] = "/data/certs/cacerts.pem\x0a/data/certs/RootCA.pem";
    // parameters["cert-file-path"] = "/data/certs/client_fullchain.pem";
    // parameters["pkey-file-path"] = "/data/certs/client_privkey.pem";

    parameters["tls-disable-certificate-validation"] = "1";

    conn = httpOpenConnectionWithParameters(url, 30000, parameters);
    if(!conn) {
        DLOG(DLOG_THIS, "connection failed!\n");
        goto done;
    }

    msg = httpConnectionGetRequestMessage(conn);
    ASSERT(msg);
    if(!msg) {
        return -1;
    }

    netMessageSetMethod(msg, "GET");

    if(netMessagePost(msg) < 0) {
        DLOG(DLOG_ERROR, "post error\n");
        goto done;
    }

    wait_count = 0;
    do {
        res_msg = httpConnectionGetResponseMessage(conn, 1000);
        wait_count++;
    } while(res_msg == nullptr && wait_count*1000 < 30000); //30seconds

    if(res_msg == nullptr) {
        //either timeout or aborted
        DLOG(DLOG_ERROR, "res timeout\n");
        goto done;
    }


    status_code = netMessageGetStatusCodeAndReasonString(res_msg, reason_string);
    DLOG(DLOG_THIS, "Response: %d %s\n", status_code, reason_string.c_str());

    parameters.clear();
    netMessageUriGetQueryParameters(res_msg, parameters);
    if(parameters.empty() == false) {
        DLOG(DLOG_THIS, "Parameters:\n");
        for(auto p : parameters) {
            DLOG(DLOG_THIS, "    [%s] = %s\n", p.first.c_str(), p.second.c_str());
        }
    }

    DLOG(DLOG_THIS, "Headers:\n");
    fields.clear();
    netMessageGetHeaderFields(res_msg, fields);
    for(auto p : fields) {
        DLOG(DLOG_THIS, "    [%s] = %s\n", p.first.c_str(), p.second.c_str());
    }


    content_length = netMessageGetContentLength(res_msg);
    DLOG(DLOG_THIS, "Content length: %d\n", content_length);
    netMessageGetContentType(res_msg, content_type);
    DLOG(DLOG_THIS, "Content type: %s\n", content_type.c_str());

    netMessageGetContent(res_msg, res_content);
    //DUMP("REG_RES", res_content.data(), res_content.size());

    if(content_length > 0) {
        char path[PATH_MAX];
        sprintf(path, "/tmp/%u.html", (uint32_t)time(NULL));
        FILE *fp = fopen(path, "w");
        if(fp) {
            fwrite(res_content.data(), 1, res_content.size(), fp);
            fclose(fp);
            DLOG(DLOG_THIS, "saved to %s, %d bytes\n", path, content_length);
        }       
    }

    httpCloseConnection(conn);
    conn = nullptr;

done:

    oasis::finalize();

    return 0;
}