/* 
 * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
 */

#include "test.h"

#if INCLUDE_HTTP_CLIENT_TEST

#define THIS_FILE           "test_http"
//#define VERBOSE
#define STR_PREC(s) (int)s.slen, s.ptr
#define USE_LOCAL_SERVER

#include <pjlib.h>
#include <pjlib-util.h>

#define ACTION_REPLY    0
#define ACTION_IGNORE   -1

static struct server_t
{
    pj_sock_t        sock;
    pj_uint16_t      port;
    pj_thread_t     *thread;

    /* Action:
     *  0:    reply with the response in resp.
     * -1:    ignore query (to simulate timeout).
     * other: reply with that error
     */
    int             action;
    pj_bool_t       send_content_length;
    unsigned        data_size;
    unsigned        buf_size;
} g_server;

static pj_bool_t thread_quit;
static pj_timer_heap_t *timer_heap;
static pj_ioqueue_t *ioqueue;
static pj_pool_t *pool;
static pj_http_req *http_req;
static pj_bool_t test_cancel = PJ_FALSE;
static pj_size_t total_size;
static pj_size_t send_size = 0;
static pj_status_t sstatus;
static pj_sockaddr_in addr;
static int counter = 0;

static int server_thread(void *p)
{
    struct server_t *srv = (struct server_t*)p;
    char *pkt = (char*)pj_pool_alloc(pool, srv->buf_size);
    pj_sock_t newsock = PJ_INVALID_SOCKET;

    while (!thread_quit) {
        pj_ssize_t pkt_len;
        int rc;
        pj_fd_set_t rset;
        pj_time_val timeout = {0, 500};

        while (!thread_quit) {
            PJ_FD_ZERO(&rset);
            PJ_FD_SET(srv->sock, &rset);
            rc = pj_sock_select((int)srv->sock+1, &rset, NULL, NULL, &timeout);
            if (rc != 1) {
                continue;
            }

            rc = pj_sock_accept(srv->sock, &newsock, NULL, NULL);
            if (rc == PJ_SUCCESS) {
                break;
            }
        }

        if (thread_quit)
            break;

        while (!thread_quit) {
            PJ_FD_ZERO(&rset);
            PJ_FD_SET(newsock, &rset);
            rc = pj_sock_select((int)newsock+1, &rset, NULL, NULL, &timeout);
            if (rc != 1) {
                PJ_LOG(3,("http test", "client timeout"));
                continue;
            }

            pkt_len = srv->buf_size;
            rc = pj_sock_recv(newsock, pkt, &pkt_len, 0);
            if (rc == PJ_SUCCESS) {
                break;
            }
        }

        if (thread_quit)
            break;

        /* Simulate network RTT */
        pj_thread_sleep(50);

        if (srv->action == ACTION_IGNORE) {
            continue;
        } else if (srv->action == ACTION_REPLY) {
            pj_size_t send_len = 0;
            unsigned ctr = 0;
            pkt_len = pj_ansi_snprintf(pkt, srv->buf_size, 
                                       "HTTP/1.0 200 OK\r\n");
            PJ_ASSERT_ON_FAIL(pkt_len>0, {
                PJ_LOG(2, (THIS_FILE, "Error creating response, pkt_len=%ld",
                           pkt_len));
                pj_sock_close(newsock);
                continue;
            })
            if (srv->send_content_length) {
                pj_ansi_snprintf(pkt + pkt_len, srv->buf_size - pkt_len,
                                "Content-Length: %d\r\n",
                                srv->data_size);
            }
            pkt_len = pj_ansi_strxcat(pkt, "\r\n", srv->buf_size);
            if (pkt_len < 0) {
                PJ_LOG(2, (THIS_FILE, "Error creating response 2, pkt_len=%ld",
                           pkt_len));
                pj_sock_close(newsock);
                continue;
            }
            rc = pj_sock_send(newsock, pkt, &pkt_len, 0);
            if (rc != PJ_SUCCESS) {
                pj_sock_close(newsock);
                continue;
            }
            while (send_len < srv->data_size) {
                pkt_len = srv->data_size - send_len;
                if (pkt_len > (signed)srv->buf_size)
                    pkt_len = srv->buf_size;
                send_len += pkt_len;
                pj_create_random_string(pkt, pkt_len);
                pj_ansi_snprintf(pkt, srv->buf_size, "\nPacket: %d", ++ctr);
                pkt[pj_ansi_strlen(pkt)] = '\n';
                rc = pj_sock_send(newsock, pkt, &pkt_len, 0);
                if (rc != PJ_SUCCESS)
                    break;
            }
            pj_sock_close(newsock);
        }
    }

    return 0;
}

static void on_data_read(pj_http_req *hreq, void *data, pj_size_t size)
{
    PJ_UNUSED_ARG(hreq);
    PJ_UNUSED_ARG(data);

    PJ_LOG(5, (THIS_FILE, "\nData received: %lu bytes", (unsigned long)size));
    if (size > 0) {
#ifdef VERBOSE
        printf("%.*s\n", (int)size, (char *)data);
#endif
    }
}

static void on_send_data(pj_http_req *hreq,
                         void **data, pj_size_t *size)
{
    char *sdata;
    pj_size_t sendsz = 8397;

    PJ_UNUSED_ARG(hreq);

    if (send_size + sendsz > total_size) {
        sendsz = total_size - send_size;
    }
    send_size += sendsz;

    sdata = (char*)pj_pool_alloc(pool, sendsz);
    pj_create_random_string(sdata, sendsz);
    pj_ansi_snprintf(sdata, sendsz, "\nSegment #%d\n", ++counter);
    *data = sdata;
    *size = sendsz;

    PJ_LOG(5, (THIS_FILE, "\nSending data progress: %lu out of %lu bytes",
           (unsigned long)send_size, (unsigned long)total_size));
}


static void on_complete(pj_http_req *hreq, pj_status_t status,
                        const pj_http_resp *resp)
{
    PJ_UNUSED_ARG(hreq);

    if (status == PJ_ECANCELLED) {
        PJ_LOG(5, (THIS_FILE, "Request cancelled"));
        return;
    } else if (status == PJ_ETIMEDOUT) {
        PJ_LOG(5, (THIS_FILE, "Request timed out!"));
        return;
    } else if (status != PJ_SUCCESS) {
        PJ_LOG(3, (THIS_FILE, "Error %d", status));
        return;
    }
    PJ_LOG(5, (THIS_FILE, "\nData completed: %lu bytes",
               (unsigned long)resp->size));
    if (resp->size > 0 && resp->data) {
#ifdef VERBOSE
        printf("%.*s\n", (int)resp->size, (char *)resp->data);
#endif
    }
}

static void on_response(pj_http_req *hreq, const pj_http_resp *resp)
{
    pj_size_t i;

    PJ_UNUSED_ARG(hreq);
    PJ_UNUSED_ARG(resp);
    PJ_UNUSED_ARG(i);

#ifdef VERBOSE
    printf("%.*s, %d, %.*s\n", STR_PREC(resp->version),
           resp->status_code, STR_PREC(resp->reason));
    for (i = 0; i < resp->headers.count; i++) {
        printf("%.*s : %.*s\n", 
               STR_PREC(resp->headers.header[i].name),
               STR_PREC(resp->headers.header[i].value));
    }
#endif

    if (test_cancel) {
        /* Need to delay closing the client socket here, otherwise the
         * server will get SIGPIPE when sending response.
         */
        pj_thread_sleep(100);
        pj_http_req_cancel(hreq, PJ_TRUE);
        test_cancel = PJ_FALSE;
    }
}


pj_status_t parse_url(const char *url, pj_http_url *hurl)
{
    pj_str_t surl;
    pj_status_t status;

    pj_cstr(&surl, url);
    status = pj_http_req_parse_url(&surl, hurl);
#ifdef VERBOSE
    if (!status) {
        printf("URL: %s\nProtocol: %.*s\nHost: %.*s\nPort: %d\nPath: %.*s\n\n",
               url, STR_PREC(hurl->protocol), STR_PREC(hurl->host),
               hurl->port, STR_PREC(hurl->path));
    } else {
    }
#endif
    return status;
}

static int parse_url_test()
{
    struct test_data
    {
        char *url;
        pj_status_t result;
        const char *username;
        const char *passwd;
        const char *host;
        int port;
        const char *path;
    } test_data[] =
    {
        /* Simple URL without '/' in the end */
        {"http://www.pjsip.org", PJ_SUCCESS, "", "", "www.pjsip.org", 80, "/"},

        /* Simple URL with port number but without '/' in the end */
        {"http://pjsip.org:8080", PJ_SUCCESS, "", "", "pjsip.org", 8080, "/"},

        /* URL with path */
        {"http://127.0.0.1:280/Joomla/index.php?option=com_content&task=view&id=5&Itemid=6",
                PJ_SUCCESS, "", "", "127.0.0.1", 280,
                "/Joomla/index.php?option=com_content&task=view&id=5&Itemid=6"},

        /* URL with port and path */
        {"http://pjsip.org:81/about-us/", PJ_SUCCESS, "", "", "pjsip.org", 81, "/about-us/"},

        /* unsupported protocol */
        {"ftp://www.pjsip.org", PJ_ENOTSUP, "", "", "", 80, ""},

        /* invalid format */
        {"http:/pjsip.org/about-us/", PJLIB_UTIL_EHTTPINURL, "", "", "", 80, ""},

        /* invalid port number */
        {"http://pjsip.org:xyz/", PJLIB_UTIL_EHTTPINPORT, "", "", "", 80, ""},

        /* with username and password */
        {"http://user:pass@pjsip.org", PJ_SUCCESS, "user", "pass", "pjsip.org", 80, "/"},

        /* password only*/
        {"http://:pass@pjsip.org", PJ_SUCCESS, "", "pass", "pjsip.org", 80, "/"},

        /* user only*/
        {"http://user:@pjsip.org", PJ_SUCCESS, "user", "", "pjsip.org", 80, "/"},

        /* empty username and passwd*/
        {"http://:@pjsip.org", PJ_SUCCESS, "", "", "pjsip.org", 80, "/"},

        /* '@' character in username and path */
        {"http://user@pjsip.org/@", PJ_SUCCESS, "user", "", "pjsip.org", 80, "/@"},

        /* '@' character in path */
        {"http://pjsip.org/@", PJ_SUCCESS, "", "", "pjsip.org", 80, "/@"},

        /* '@' character in path */
        {"http://pjsip.org/one@", PJ_SUCCESS, "", "", "pjsip.org", 80, "/one@"},

        /* Invalid URL */
        {"http://:", PJ_EINVAL, "", "", "", 0, ""},

        /* Invalid URL */
        {"http://@", PJ_EINVAL, "", "", "", 0, ""},

        /* Invalid URL */
        {"http", PJ_EINVAL, "", "", "", 0, ""},

        /* Invalid URL */
        {"http:/", PJ_EINVAL, "", "", "", 0, ""},

        /* Invalid URL */
        {"http://", PJ_EINVAL, "", "", "", 0, ""},

        /* Invalid URL */
        {"http:///", PJ_EINVAL, "", "", "", 0, ""},

        /* Invalid URL */
        {"http://@/", PJ_EINVAL, "", "", "", 0, ""},

        /* Invalid URL */
        {"http:///@", PJ_EINVAL, "", "", "", 0, ""},

        /* Invalid URL */
        {"http://:::", PJ_EINVAL, "", "", "", 0, ""},
    };
    unsigned i;

    for (i=0; i<PJ_ARRAY_SIZE(test_data); ++i) {
        struct test_data *ptd;
        pj_http_url hurl;
        pj_status_t status;

        ptd = &test_data[i];

        PJ_LOG(3, (THIS_FILE, ".. %s", ptd->url));
        status = parse_url(ptd->url, &hurl);

        if (status != ptd->result) {
            PJ_LOG(3,(THIS_FILE, "%d", status));
            return -11;
        }
        if (status != PJ_SUCCESS)
            continue;
        if (pj_strcmp2(&hurl.username, ptd->username))
            return -12;
        if (pj_strcmp2(&hurl.passwd, ptd->passwd))
            return -13;
        if (pj_strcmp2(&hurl.host, ptd->host))
            return -14;
        if (hurl.port != ptd->port)
            return -15;
        if (pj_strcmp2(&hurl.path, ptd->path))
            return -16;
    }

    return 0;
}

/* 
 * GET request scenario 1: using on_response() and on_data_read()
 * Server replies with content-length. Application cancels the 
 * request upon receiving the response, then start it again.
 */
int http_client_test1()
{
    pj_str_t url;
    pj_http_req_callback hcb;
    pj_http_req_param param;
    char urlbuf[80];

    pj_bzero(&hcb, sizeof(hcb));
    hcb.on_complete = &on_complete;
    hcb.on_data_read = &on_data_read;
    hcb.on_response = &on_response;
    pj_http_req_param_default(&param);

    /* Create pool, timer, and ioqueue */
    pool = pj_pool_create(mem, NULL, 8192, 4096, NULL);
    if (pj_timer_heap_create(pool, 16, &timer_heap))
        return -31;
    if (pj_ioqueue_create(pool, 16, &ioqueue))
        return -32;

#ifdef USE_LOCAL_SERVER

    thread_quit = PJ_FALSE;
    g_server.action = ACTION_REPLY;
    g_server.send_content_length = PJ_TRUE;
    g_server.data_size = 2970;
    g_server.buf_size = 1024;

    sstatus = pj_sock_socket(pj_AF_INET(), pj_SOCK_STREAM(), 0, 
                             &g_server.sock);
    if (sstatus != PJ_SUCCESS)
        return -41;

    pj_sockaddr_in_init(&addr, NULL, 0);

    sstatus = pj_sock_bind(g_server.sock, &addr, sizeof(addr));
    if (sstatus != PJ_SUCCESS)
        return -43;

    {
        pj_sockaddr_in addr2;
        int addr_len = sizeof(addr2);
        sstatus = pj_sock_getsockname(g_server.sock, &addr2, &addr_len);
        if (sstatus != PJ_SUCCESS)
            return -44;
        g_server.port = pj_sockaddr_in_get_port(&addr2);
        pj_ansi_snprintf(urlbuf, sizeof(urlbuf),
                         "http://127.0.0.1:%d/about-us/",
                         g_server.port);
        url = pj_str(urlbuf);
    }

    sstatus = pj_sock_listen(g_server.sock, 8);
    if (sstatus != PJ_SUCCESS)
        return -45;

    sstatus = pj_thread_create(pool, NULL, &server_thread, &g_server,
                               0, 0, &g_server.thread);
    if (sstatus != PJ_SUCCESS)
        return -47;

#else
    pj_cstr(&url, "http://www.teluu.com/about-us/");
#endif

    if (pj_http_req_create(pool, &url, timer_heap, ioqueue, 
                           &param, &hcb, &http_req))
        return -33;

    test_cancel = PJ_TRUE;
    if (pj_http_req_start(http_req))
        return -35;

    while (pj_http_req_is_running(http_req)) {
        pj_time_val delay = {0, 50};
        pj_ioqueue_poll(ioqueue, &delay);
        pj_timer_heap_poll(timer_heap, NULL);
    }

    if (pj_http_req_start(http_req))
        return -37;

    while (pj_http_req_is_running(http_req)) {
        pj_time_val delay = {0, 50};
        pj_ioqueue_poll(ioqueue, &delay);
        pj_timer_heap_poll(timer_heap, NULL);
    }

#ifdef USE_LOCAL_SERVER
    thread_quit = PJ_TRUE;
    pj_thread_join(g_server.thread);
    pj_sock_close(g_server.sock);
#endif

    pj_http_req_destroy(http_req);
    pj_ioqueue_destroy(ioqueue);
    pj_timer_heap_destroy(timer_heap);
    pj_pool_release(pool);

    return PJ_SUCCESS;
}

/* 
 * GET request scenario 2: using on_complete() to get the 
 * complete data. Server does not reply with content-length.
 * Request timed out, application sets a longer timeout, then
 * then restart the request.
 */
int http_client_test2()
{
    pj_str_t url;
    pj_http_req_callback hcb;
    pj_http_req_param param;
    pj_time_val timeout;
    char urlbuf[80];

    pj_bzero(&hcb, sizeof(hcb));
    hcb.on_complete = &on_complete;
    hcb.on_response = &on_response;
    pj_http_req_param_default(&param);

    /* Create pool, timer, and ioqueue */
    pool = pj_pool_create(mem, NULL, 8192, 4096, NULL);
    if (pj_timer_heap_create(pool, 16, &timer_heap))
        return -41;
    if (pj_ioqueue_create(pool, 16, &ioqueue))
        return -42;

#ifdef USE_LOCAL_SERVER

    pj_cstr(&url, "http://127.0.0.1:380");
    param.timeout.sec = 0;
    param.timeout.msec = 2000;

    thread_quit = PJ_FALSE;
    g_server.action = ACTION_IGNORE;
    g_server.send_content_length = PJ_FALSE;
    g_server.data_size = 4173;
    g_server.buf_size = 1024;

    sstatus = pj_sock_socket(pj_AF_INET(), pj_SOCK_STREAM(), 0, 
                             &g_server.sock);
    if (sstatus != PJ_SUCCESS)
        return -41;

    pj_sockaddr_in_init(&addr, NULL, 0);

    sstatus = pj_sock_bind(g_server.sock, &addr, sizeof(addr));
    if (sstatus != PJ_SUCCESS)
        return -43;

    {
        pj_sockaddr_in addr2;
        int addr_len = sizeof(addr2);
        sstatus = pj_sock_getsockname(g_server.sock, &addr2, &addr_len);
        if (sstatus != PJ_SUCCESS)
            return -44;
        g_server.port = pj_sockaddr_in_get_port(&addr2);
        pj_ansi_snprintf(urlbuf, sizeof(urlbuf),
                         "http://127.0.0.1:%d",
                         g_server.port);
        url = pj_str(urlbuf);
    }

    sstatus = pj_sock_listen(g_server.sock, 8);
    if (sstatus != PJ_SUCCESS)
        return -45;

    sstatus = pj_thread_create(pool, NULL, &server_thread, &g_server,
                               0, 0, &g_server.thread);
    if (sstatus != PJ_SUCCESS)
        return -47;

#else
    pj_cstr(&url, "http://www.google.com.sg");
    param.timeout.sec = 0;
    param.timeout.msec = 50;
#endif

    pj_http_headers_add_elmt2(&param.headers, (char*)"Accept",
                             (char*)"image/gif, image/x-xbitmap, image/jpeg, "
                                    "image/pjpeg, application/x-ms-application,"
                                    " application/vnd.ms-xpsdocument, "
                                    "application/xaml+xml, "
                                    "application/x-ms-xbap, "
                                    "application/x-shockwave-flash, "
                                    "application/vnd.ms-excel, "
                                    "application/vnd.ms-powerpoint, "
                                    "application/msword, */*");
    pj_http_headers_add_elmt2(&param.headers, (char*)"Accept-Language",
                              (char*)"en-sg");
    pj_http_headers_add_elmt2(&param.headers, (char*)"User-Agent",
                              (char*)"Mozilla/4.0 (compatible; MSIE 7.0; "
                                     "Windows NT 6.0; SLCC1; "
                                     ".NET CLR 2.0.50727; "
                                     ".NET CLR 3.0.04506)");
    if (pj_http_req_create(pool, &url, timer_heap, ioqueue, 
                           &param, &hcb, &http_req))
        return -43;

    if (pj_http_req_start(http_req))
        return -45;

    while (pj_http_req_is_running(http_req)) {
        pj_time_val delay = {0, 50};
        pj_ioqueue_poll(ioqueue, &delay);
        pj_timer_heap_poll(timer_heap, NULL);
    }

#ifdef USE_LOCAL_SERVER
    g_server.action = ACTION_REPLY;
#endif

    timeout.sec = 0; timeout.msec = 10000;
    pj_http_req_set_timeout(http_req, &timeout);
    if (pj_http_req_start(http_req))
        return -47;

    while (pj_http_req_is_running(http_req)) {
        pj_time_val delay = {0, 50};
        pj_ioqueue_poll(ioqueue, &delay);
        pj_timer_heap_poll(timer_heap, NULL);
    }

#ifdef USE_LOCAL_SERVER
    thread_quit = PJ_TRUE;
    pj_thread_join(g_server.thread);
    pj_sock_close(g_server.sock);
#endif

    pj_http_req_destroy(http_req);
    pj_ioqueue_destroy(ioqueue);
    pj_timer_heap_destroy(timer_heap);
    pj_pool_release(pool);

    return PJ_SUCCESS;
}

/*
 * PUT request scenario 1: sending the whole data at once
 */
int http_client_test_put1()
{
    pj_str_t url;
    pj_http_req_callback hcb;
    pj_http_req_param param;
    char *data;
    int length = 3875;
    char urlbuf[80];

    pj_bzero(&hcb, sizeof(hcb));
    hcb.on_complete = &on_complete;
    hcb.on_data_read = &on_data_read;
    hcb.on_response = &on_response;

    /* Create pool, timer, and ioqueue */
    pool = pj_pool_create(mem, NULL, 8192, 4096, NULL);
    if (pj_timer_heap_create(pool, 16, &timer_heap))
        return -51;
    if (pj_ioqueue_create(pool, 16, &ioqueue))
        return -52;

#ifdef USE_LOCAL_SERVER
    thread_quit = PJ_FALSE;
    g_server.action = ACTION_REPLY;
    g_server.send_content_length = PJ_TRUE;
    g_server.data_size = 0;
    g_server.buf_size = 4096;

    sstatus = pj_sock_socket(pj_AF_INET(), pj_SOCK_STREAM(), 0, 
                             &g_server.sock);
    if (sstatus != PJ_SUCCESS)
        return -41;

    pj_sockaddr_in_init(&addr, NULL, 0);

    sstatus = pj_sock_bind(g_server.sock, &addr, sizeof(addr));
    if (sstatus != PJ_SUCCESS)
        return -43;

    {
        pj_sockaddr_in addr2;
        int addr_len = sizeof(addr2);
        sstatus = pj_sock_getsockname(g_server.sock, &addr2, &addr_len);
        if (sstatus != PJ_SUCCESS)
            return -44;
        g_server.port = pj_sockaddr_in_get_port(&addr2);
        pj_ansi_snprintf(urlbuf, sizeof(urlbuf),
                         "http://127.0.0.1:%d/test/test.txt",
                         g_server.port);
        url = pj_str(urlbuf);
    }

    sstatus = pj_sock_listen(g_server.sock, 8);
    if (sstatus != PJ_SUCCESS)
        return -45;

    sstatus = pj_thread_create(pool, NULL, &server_thread, &g_server,
                               0, 0, &g_server.thread);
    if (sstatus != PJ_SUCCESS)
        return -47;

#else
    pj_cstr(&url, "http://127.0.0.1:280/test/test.txt");

#endif

    pj_http_req_param_default(&param);
    pj_strset2(&param.method, (char*)"PUT");
    data = (char*)pj_pool_alloc(pool, length);
    pj_create_random_string(data, length);
    pj_ansi_snprintf(data, length, "PUT test\n");
    param.reqdata.data = data;
    param.reqdata.size = length;
    if (pj_http_req_create(pool, &url, timer_heap, ioqueue, 
                           &param, &hcb, &http_req))
        return -53;

    if (pj_http_req_start(http_req))
        return -55;

    while (pj_http_req_is_running(http_req)) {
        pj_time_val delay = {0, 50};
        pj_ioqueue_poll(ioqueue, &delay);
        pj_timer_heap_poll(timer_heap, NULL);
    }

#ifdef USE_LOCAL_SERVER
    thread_quit = PJ_TRUE;
    pj_thread_join(g_server.thread);
    pj_sock_close(g_server.sock);
#endif

    pj_http_req_destroy(http_req);
    pj_ioqueue_destroy(ioqueue);
    pj_timer_heap_destroy(timer_heap);
    pj_pool_release(pool);

    return PJ_SUCCESS;
}

/*
 * PUT request scenario 2: using on_send_data() callback to
 * sending the data in chunks
 */
int http_client_test_put2()
{
    pj_str_t url;
    pj_http_req_callback hcb;
    pj_http_req_param param;
    char urlbuf[80];

    pj_bzero(&hcb, sizeof(hcb));
    hcb.on_complete = &on_complete;
    hcb.on_send_data = &on_send_data;
    hcb.on_data_read = &on_data_read;
    hcb.on_response = &on_response;

    /* Create pool, timer, and ioqueue */
    pool = pj_pool_create(mem, NULL, 8192, 4096, NULL);
    if (pj_timer_heap_create(pool, 16, &timer_heap))
        return -51;
    if (pj_ioqueue_create(pool, 16, &ioqueue))
        return -52;

#ifdef USE_LOCAL_SERVER
    thread_quit = PJ_FALSE;
    g_server.action = ACTION_REPLY;
    g_server.send_content_length = PJ_TRUE;
    g_server.data_size = 0;
    g_server.buf_size = 16384;

    sstatus = pj_sock_socket(pj_AF_INET(), pj_SOCK_STREAM(), 0, 
                             &g_server.sock);
    if (sstatus != PJ_SUCCESS)
        return -41;

    pj_sockaddr_in_init(&addr, NULL, 0);

    sstatus = pj_sock_bind(g_server.sock, &addr, sizeof(addr));
    if (sstatus != PJ_SUCCESS)
        return -43;

    {
        pj_sockaddr_in addr2;
        int addr_len = sizeof(addr2);
        sstatus = pj_sock_getsockname(g_server.sock, &addr2, &addr_len);
        if (sstatus != PJ_SUCCESS)
            return -44;
        g_server.port = pj_sockaddr_in_get_port(&addr2);
        pj_ansi_snprintf(urlbuf, sizeof(urlbuf),
                         "http://127.0.0.1:%d/test/test2.txt",
                         g_server.port);
        url = pj_str(urlbuf);
    }

    sstatus = pj_sock_listen(g_server.sock, 8);
    if (sstatus != PJ_SUCCESS)
        return -45;

    sstatus = pj_thread_create(pool, NULL, &server_thread, &g_server,
                               0, 0, &g_server.thread);
    if (sstatus != PJ_SUCCESS)
        return -47;

#else
    pj_cstr(&url, "http://127.0.0.1:280/test/test2.txt");

#endif

    pj_http_req_param_default(&param);
    pj_strset2(&param.method, (char*)"PUT");
    total_size = 15383;
    send_size = 0;
    param.reqdata.total_size = total_size;
    if (pj_http_req_create(pool, &url, timer_heap, ioqueue, 
                           &param, &hcb, &http_req))
        return -53;

    if (pj_http_req_start(http_req))
        return -55;

    while (pj_http_req_is_running(http_req)) {
        pj_time_val delay = {0, 50};
        pj_ioqueue_poll(ioqueue, &delay);
        pj_timer_heap_poll(timer_heap, NULL);
    }

#ifdef USE_LOCAL_SERVER
    thread_quit = PJ_TRUE;
    pj_thread_join(g_server.thread);
    pj_sock_close(g_server.sock);
#endif

    pj_http_req_destroy(http_req);
    pj_ioqueue_destroy(ioqueue);
    pj_timer_heap_destroy(timer_heap);
    pj_pool_release(pool);

    return PJ_SUCCESS;
}

int http_client_test_delete()
{
    pj_str_t url;
    pj_http_req_callback hcb;
    pj_http_req_param param;
    char urlbuf[80];

    pj_bzero(&hcb, sizeof(hcb));
    hcb.on_complete = &on_complete;
    hcb.on_response = &on_response;

    /* Create pool, timer, and ioqueue */
    pool = pj_pool_create(mem, NULL, 8192, 4096, NULL);
    if (pj_timer_heap_create(pool, 16, &timer_heap))
        return -61;
    if (pj_ioqueue_create(pool, 16, &ioqueue))
        return -62;

#ifdef USE_LOCAL_SERVER
    thread_quit = PJ_FALSE;
    g_server.action = ACTION_REPLY;
    g_server.send_content_length = PJ_TRUE;
    g_server.data_size = 0;
    g_server.buf_size = 1024;

    sstatus = pj_sock_socket(pj_AF_INET(), pj_SOCK_STREAM(), 0, 
                             &g_server.sock);
    if (sstatus != PJ_SUCCESS)
        return -41;

    pj_sockaddr_in_init(&addr, NULL, 0);

    sstatus = pj_sock_bind(g_server.sock, &addr, sizeof(addr));
    if (sstatus != PJ_SUCCESS)
        return -43;

    {
        pj_sockaddr_in addr2;
        int addr_len = sizeof(addr2);
        sstatus = pj_sock_getsockname(g_server.sock, &addr2, &addr_len);
        if (sstatus != PJ_SUCCESS)
            return -44;
        g_server.port = pj_sockaddr_in_get_port(&addr2);
        pj_ansi_snprintf(urlbuf, sizeof(urlbuf),
                         "http://127.0.0.1:%d/test/test2.txt",
                         g_server.port);
        url = pj_str(urlbuf);
    }

    sstatus = pj_sock_listen(g_server.sock, 8);
    if (sstatus != PJ_SUCCESS)
        return -45;

    sstatus = pj_thread_create(pool, NULL, &server_thread, &g_server,
                               0, 0, &g_server.thread);
    if (sstatus != PJ_SUCCESS)
        return -47;

#else
    pj_cstr(&url, "http://127.0.0.1:280/test/test2.txt");
#endif

    pj_http_req_param_default(&param);
    pj_strset2(&param.method, (char*)"DELETE");
    if (pj_http_req_create(pool, &url, timer_heap, ioqueue, 
                           &param, &hcb, &http_req))
        return -63;

    if (pj_http_req_start(http_req))
        return -65;

    while (pj_http_req_is_running(http_req)) {
        pj_time_val delay = {0, 50};
        pj_ioqueue_poll(ioqueue, &delay);
        pj_timer_heap_poll(timer_heap, NULL);
    }

#ifdef USE_LOCAL_SERVER
    thread_quit = PJ_TRUE;
    pj_thread_join(g_server.thread);
    pj_sock_close(g_server.sock);
#endif

    pj_http_req_destroy(http_req);
    pj_ioqueue_destroy(ioqueue);
    pj_timer_heap_destroy(timer_heap);
    pj_pool_release(pool);

    return PJ_SUCCESS;
}

int http_client_test()
{
    int rc;
    
    PJ_LOG(3, (THIS_FILE, "..Testing URL parsing"));
    rc = parse_url_test();
    if (rc)
        return rc;

    PJ_LOG(3, (THIS_FILE, "..Testing GET request scenario 1"));
    rc = http_client_test1();
    if (rc)
        return rc;

    PJ_LOG(3, (THIS_FILE, "..Testing GET request scenario 2"));
    rc = http_client_test2();
    if (rc)
        return rc;

    PJ_LOG(3, (THIS_FILE, "..Testing PUT request scenario 1"));
    rc = http_client_test_put1();
    if (rc)
        return rc;

    PJ_LOG(3, (THIS_FILE, "..Testing PUT request scenario 2"));
    rc = http_client_test_put2();
    if (rc)
        return rc;

    PJ_LOG(3, (THIS_FILE, "..Testing DELETE request"));
    rc = http_client_test_delete();
    if (rc)
        return rc;

    return PJ_SUCCESS;
}

#else
/* To prevent warning about "translation unit is empty"
 * when this test is disabled. 
 */
int dummy_http_client_test;
#endif  /* INCLUDE_HTTP_CLIENT_TEST */
