/*
**==============================================================================
**
** Open Management Infrastructure (OMI)
**
** Copyright (c) Microsoft Corporation
**
** Licensed under the Apache License, Version 2.0 (the "License"); you may not
** use this file except in compliance with the License. You may obtain a copy
** of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
** KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
** WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
** MERCHANTABLITY OR NON-INFRINGEMENT.
**
** See the Apache 2 License for the specific language governing permissions
** and limitations under the License.
**
**==============================================================================
*/
#include <vector>
#include <map>
#include <cstdlib>
#include <ut/ut.h>
#include <unittest/utils.h>
#include <protocol/httpclient.h>
#include <protocol/http.h>
#include <protocol/thread.h>
#include <base/result.h>
using namespace std;
/*********************************** http server ***************************/
/* local data */
static Http* s_http;
static bool s_stop;
static ThreadHandle s_t;
static MI_Uint16 PORT = ut::getUnittestPortNumber() + 10;
static string s_contentType;
static string s_charset;
static string s_content;
static string s_response;
static bool s_delayServerResponse = false;
// received data
static int s_httpCode;
static map< string, string > s_headers;
static string s_contentReceivedFromServer;
static int s_cxxError;
static bool s_httpResponseReceived;
#if defined(_MSC_VER)
#undef BEGIN_EXTERNC
#undef END_EXTERNC
#define BEGIN_EXTERNC
#define END_EXTERNC
#endif
static void setUp()
{
s_httpResponseReceived = false;
s_response = "";
s_contentType = "";
s_delayServerResponse = false;
s_httpCode = 0;
s_headers.clear();
s_contentReceivedFromServer.clear();
s_cxxError = -1;
}
static void cleanup()
{
}
/* helper functions */
BEGIN_EXTERNC
static void* MI_CALL _HTTPServerProc(void* )
{
Sock_Start();
// pump messages
for (; !s_stop; )
Http_Run( s_http, 1000 );
Sock_Stop();
return 0;
}
END_EXTERNC
BEGIN_EXTERNC
static void _StartHTTP_Server(
HttpCallbackOnNewConnection callbackOnNewConnection,
HttpCallbackOnCloseConnection callbackOnCloseConnection,
HttpCallbackOnRequest callbackOnRequest,
void* callbackData,
const HttpOptions* options = 0)
{
/* create a server */
UT_ASSERT( MI_RESULT_OK == Http_New_Server(
&s_http, 0, PORT, PORT + 1,
callbackOnNewConnection,
callbackOnCloseConnection,
callbackOnRequest, callbackData) );
if (options)
{
UT_ASSERT( MI_RESULT_OK == Http_SetOptions(
s_http, options) );
}
/* create a thread for message consumption */
s_stop = false;
UT_ASSERT(MI_RESULT_OK == Thread_Create(
_HTTPServerProc, 0, &s_t));
}
END_EXTERNC
BEGIN_EXTERNC
static void _StopHTTP_Server()
{
s_stop = true;
UT_ASSERT(MI_RESULT_OK == Thread_Destroy( s_t, MI_TRUE ));
UT_ASSERT( MI_RESULT_OK == Http_Delete(s_http) );
s_t = 0;
s_http = 0;
s_stop = false;
}
END_EXTERNC
BEGIN_EXTERNC
static void _HttpCallbackOnNewConnection(
Http* /*http*/,
void* /*callbackData*/,
void* httpConnectionHandle,
void** connectionData)
{
*connectionData = httpConnectionHandle;
}
END_EXTERNC
BEGIN_EXTERNC
static void _HttpCallbackOnCloseConnection(
Http* /*http*/,
void* /*callbackData*/,
void* /*connectionData*/)
{
}
END_EXTERNC
BEGIN_EXTERNC
static void _HttpCallbackOnRequest(
Http* http,
void* /*callbackData*/,
void* connectionData,
void* httpConnectionHandle,
const HttpHeaders* headers,
Page** page)
{
UT_ASSERT(httpConnectionHandle == connectionData);
// printf("cbt, %s\n", headers->authorization);
if (headers->contentType)
s_contentType = headers->contentType;
else
s_contentType = "";
if (headers->charset)
s_charset = headers->charset;
else
s_charset = "";
if (page && *page)
s_content = string( (char*) ((*page)+1), ((char*)((*page)+1)) + (*page)->u.s.size);
else
s_content.clear();
Page* rsp = (Page*)malloc(sizeof(Page) + s_response.size());
UT_ASSERT(rsp);
rsp->u.s.size = s_response.size();
memcpy(rsp+1, s_response.c_str(), s_response.size());
if (s_delayServerResponse)
ut::sleep_ms(175);
Http_SendResponse( http, httpConnectionHandle, HTTP_ERROR_CODE_OK, &rsp );
if (rsp )
free(rsp);
}
END_EXTERNC
/************************************* http client **********************/
BEGIN_EXTERNC
static void _HttpClientCallbackOnStatus(
HttpClient* http,
void* callbackData,
MI_Result result)
{
MI_UNUSED(http);
MI_UNUSED(callbackData);
MI_UNUSED(result);
s_httpResponseReceived = true;
}
static MI_Boolean _HttpClientCallbackOnResponse(
HttpClient* http,
void* callbackData,
const HttpClientResponseHeader* headers,
MI_Sint64 contentSize,
MI_Boolean lastChunk,
Page** data)
{
MI_UNUSED(http);
MI_UNUSED(callbackData);
MI_UNUSED(headers);
MI_UNUSED(contentSize);
MI_UNUSED(lastChunk);
if (headers)
{
s_httpCode = headers->httpError;
for (unsigned int i = 0; i < headers->sizeHeaders; i++)
{
s_headers[headers->headers[i].name] = headers->headers[i].value;
}
}
if (data && *data)
{
s_contentReceivedFromServer +=
string( (char*) ((*data)+1), ((char*)((*data)+1)) + (*data)->u.s.size);
}
return MI_TRUE;
}
END_EXTERNC
/* Simplified http client for chunked stuff testing */
struct ThreadSrvParam
{
string messageToSend;
size_t bytesToSendPerOperation;
bool started;
ThreadSrvParam():
bytesToSendPerOperation(0),
started(false)
{
}
};
/* simple http client */
static void* MI_CALL _http_server_proc(void* param)
{
ThreadSrvParam* p = (ThreadSrvParam*)param;
Addr addr;
Sock sock, listener;
MI_Result r;
Sock_Start();
// Initialize address (connect using loopback).
Addr_InitAny(&addr, PORT+2);
UT_ASSERT_EQUAL(Sock_CreateListener(&listener, &addr), MI_RESULT_OK);
UT_ASSERT_EQUAL(Sock_SetBlocking(listener, MI_FALSE), MI_RESULT_OK);
p->started = true;
// accept incoming request
for (int i = 0; ; i++)
{
// check if we waited too long
UT_ASSERT(i<1000);
r = Sock_Accept(listener, &sock, &addr);
if (MI_RESULT_WOULD_BLOCK == r)
{
ut::sleep_ms(1);
continue;
}
UT_ASSERT_EQUAL(r, MI_RESULT_OK);
break;
}
// close listener
Sock_Close(listener);
// read and ignore http request
char r_buf[1024];
size_t read = 0;
r = Sock_Read(sock, r_buf, sizeof(r_buf), &read);
if (r) printf("s,r: %d, err %d\n", (int)read, Sock_GetLastError());
// send pre-defined response
size_t sent = 0;
size_t size_left = p->messageToSend.size();
const char* buf = p->messageToSend.c_str();
while ( size_left )
{
ut::sleep_ms( 1 );
size_t wantToSend = min(p->bytesToSendPerOperation, size_left);
r = Sock_Write(sock, buf, wantToSend, &sent);
//printf("size_left %d, r %d, sent %d, want-send %d\n", size_left, r, sent, wantToSend);
if ( r != MI_RESULT_OK)
{
printf("size_left %d, r %d, want write %d, sent %d, le %d\n", (int)size_left, (int)r, (int)wantToSend, (int)sent, Sock_GetLastError());
ut::sleep_ms( 7000 );
}
UT_ASSERT(r == MI_RESULT_OK);
// printf("s: %d\n", (int)sent);
size_left -= sent;
buf += sent;
}
Sock_Close(sock);
Sock_Stop();
return 0;
}
BEGIN_EXTERNC
static void* MI_CALL http_server_proc(void* param)
{
try
{
return _http_server_proc(param);
}
catch (ut::UnittestException ex)
{
ex.m_testcase = "--http_server_proc"; testFailed(ex);
}
return 0;
}
END_EXTERNC
static void TestHttpClient_BasicOperations()
{
HttpClient* http = 0;
const char* header_strings[] = {
"Content-Type: text/html",
//"User-Agent: xplat http cleint" ,
"Host: host"
};
/* content to send to the client */
s_response = "Test";
HttpClientRequestHeaders headers = {
header_strings,
MI_COUNT(header_strings) };
UT_ASSERT_EQUAL(MI_RESULT_OK,
HttpClient_New_Connector(&http, 0, "127.0.0.1", PORT, MI_FALSE,
_HttpClientCallbackOnStatus,
_HttpClientCallbackOnResponse, 0));
UT_ASSERT_EQUAL(MI_RESULT_OK,
HttpClient_StartRequest(http, "GET", "/", &headers, 0));
for (int i = 0; i < 10000 && !s_httpResponseReceived; i++)
HttpClient_Run(http, 1000);
// verify results:
UT_ASSERT_EQUAL(s_httpCode, 200);
UT_ASSERT_EQUAL(s_headers["Content-Type"], "application/soap+xml;charset=UTF-8");
UT_ASSERT_EQUAL(s_contentReceivedFromServer, string("Test"));
UT_ASSERT_EQUAL(s_contentType, string("text/html"));
UT_ASSERT_EQUAL(MI_RESULT_OK,
HttpClient_Delete(http));
http = 0;
}
static void TestHttpClient_BasicOperations_https()
{
}
static void _runClientWithSimplifiedServer(ThreadSrvParam& param)
{
HttpClient* http = 0;
const char* header_strings[] = {
"Content-Type: text/html"
};
/* create a server */
ThreadHandle t;
UT_ASSERT(MI_RESULT_OK == Thread_Create(
http_server_proc, ¶m, &t));
for (int i = 0; !param.started; i++)
{
// check if we waited too long
UT_ASSERT(i<1000);
ut::sleep_ms(1);
}
HttpClientRequestHeaders headers = {
header_strings,
MI_COUNT(header_strings) };
UT_ASSERT_EQUAL(MI_RESULT_OK,
HttpClient_New_Connector(&http, 0, "127.0.0.1", PORT+2, MI_FALSE,
_HttpClientCallbackOnStatus,
_HttpClientCallbackOnResponse, 0));
UT_ASSERT_EQUAL(MI_RESULT_OK,
HttpClient_StartRequest(http, "GET", "/", &headers, 0));
for (int i = 0; i < 10000 && !s_httpResponseReceived; i++)
HttpClient_Run(http, 1000);
// wait for completion and check that
UT_ASSERT(MI_RESULT_OK == Thread_Destroy( t, MI_TRUE ));
// free client pointer
UT_ASSERT_EQUAL(MI_RESULT_OK,
HttpClient_Delete(http));
http = 0;
}
static void TestHttpClient_ChunkedResponse()
{
ThreadSrvParam param;
/* content to send to the client */
param.messageToSend =
/* header */
"HTTP/1.1 200 OK\r\n"
"transfer-encoding: chunked\r\n"
"Connection: Keep-Alive\r\n"
"Content-Type: application/soap+xml;charset=UTF-8\r\n"
"\r\n"
/* chunk 1 */
"1 \r\n"
"T\r\n"
/* chunk 2 */
"3 \r\n"
"est\r\n"
/* last chunk */
"0 \r\n"
"\r\n"
;
param.bytesToSendPerOperation = 10240;
_runClientWithSimplifiedServer(param);
// verify results:
UT_ASSERT_EQUAL(s_httpCode, 200);
UT_ASSERT_EQUAL(s_headers["Content-Type"], "application/soap+xml;charset=UTF-8");
UT_ASSERT_EQUAL(s_contentReceivedFromServer, string("Test"));
}
static void TestHttpClient_ChunkedResponseByOneByte()
{
ThreadSrvParam param;
/* content to send to the client */
param.messageToSend =
/* header */
"HTTP/1.1 200 OK\r\n"
"transfer-encoding: chunked\r\n"
"Connection: Keep-Alive\r\n"
"Content-Type: application/soap+xml;charset=UTF-8\r\n"
"\r\n"
/* chunk 1 - 17 bytes */
"11 \r\n"
"1234567890abcdefT\r\n"
/* chunk 2 - 13 bytes */
"d \r\n"
"est1234567890\r\n"
/* last chunk */
"0 \r\n"
"\r\n"
;
param.bytesToSendPerOperation = 1;
_runClientWithSimplifiedServer(param);
// verify results:
UT_ASSERT_EQUAL(s_httpCode, 200);
UT_ASSERT_EQUAL(s_headers["Content-Type"], "application/soap+xml;charset=UTF-8");
UT_ASSERT_EQUAL(s_contentReceivedFromServer, string("1234567890abcdefTest1234567890"));
}
static void RunTests()
{
Sock_Start();
_StartHTTP_Server(
_HttpCallbackOnNewConnection,
_HttpCallbackOnCloseConnection,
_HttpCallbackOnRequest,
0);
UT_TEST(TestHttpClient_BasicOperations);
UT_TEST(TestHttpClient_BasicOperations_https);
UT_TEST(TestHttpClient_ChunkedResponse);
UT_TEST(TestHttpClient_ChunkedResponseByOneByte);
_StopHTTP_Server();
Sock_Stop();
}
UT_ENTRY_POINT(RunTests);