/*** * ==++== * * Copyright (c) Microsoft Corporation. All rights reserved. * 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 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ==--== * =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ * * Defines a test server to handle requests and sending responses. * * =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- ****/ #include "stdafx.h" #ifdef _WIN32 #include #pragma warning ( push ) #pragma warning ( disable : 4457 ) #include #pragma warning ( pop ) #endif #include #include "cpprest/uri.h" #include "test_http_server.h" #include "cpprest/http_listener.h" #include using namespace web; using namespace utility; using namespace utility::conversions; namespace tests { namespace functional { namespace http { namespace utilities { #ifdef _WIN32 // Helper function to parse verb from Windows HTTP Server API. static utility::string_t parse_verb(const HTTP_REQUEST *p_http_request) { utility::string_t method; std::string temp; switch(p_http_request->Verb) { case HttpVerbGET: method = U("GET"); break; case HttpVerbPOST: method = U("POST"); break; case HttpVerbPUT: method = U("PUT"); break; case HttpVerbDELETE: method = U("DELETE"); break; case HttpVerbHEAD: method = U("HEAD"); break; case HttpVerbOPTIONS: method = U("OPTIONS"); break; case HttpVerbTRACE: method = U("TRACE"); break; case HttpVerbCONNECT: method = U("CONNECT"); break; case HttpVerbUnknown: temp = p_http_request->pUnknownVerb; method = utility::string_t(temp.begin(), temp.end()); default: break; } return method; } /// /// String values for all HTTP Server API known headers. /// NOTE: the order here is important it is from the _HTTP_HEADER_ID enum. /// static utility::string_t HttpServerAPIKnownHeaders[] = { U("Cache-Control"), U("Connection"), U("Data"), U("Keep-Alive"), U("Pragma"), U("Trailer"), U("Transfer-Encoding"), U("Upgrade"), U("Via"), U("Warning"), U("Allow"), U("Content-Length"), U("Content-Type"), U("Content-Encoding"), U("Content-Language"), U("Content-Location"), U("Content-Md5"), U("Content-Range"), U("Expires"), U("Last-Modified"), U("Accept"), U("Accept-Charset"), U("Accept-Encoding"), U("Accept-Language"), U("Authorization"), U("Cookie"), U("Expect"), U("From"), U("Host"), U("If-Match"), U("If-Modified-Since"), U("If-None-Match"), U("If-Range"), U("If-Unmodified-Since"), U("Max-Forwards"), U("Proxy-Authorization"), U("Referer"), U("Range"), U("Te"), U("Translate"), U("User-Agent"), U("Request-Maximum"), U("Accept-Ranges"), U("Age"), U("Etag"), U("Location"), U("Proxy-Authenticate"), U("Retry-After"), U("Server"), U("Set-Cookie"), U("Vary"), U("Www-Authenticate"), U("Response-Maximum") }; static utility::string_t char_to_wstring(const char * src) { if(src == nullptr) { return utility::string_t(); } std::string temp(src); return utility::string_t(temp.begin(), temp.end()); } static std::map parse_http_headers(const HTTP_REQUEST_HEADERS &headers) { std::map headers_map; for(USHORT i = 0; i < headers.UnknownHeaderCount; ++i) { headers_map[char_to_wstring(headers.pUnknownHeaders[i].pName)] = char_to_wstring(headers.pUnknownHeaders[i].pRawValue); } for(int i = 0; i < HttpHeaderMaximum; ++i) { if(headers.KnownHeaders[i].RawValueLength != 0) { headers_map[HttpServerAPIKnownHeaders[i]] = char_to_wstring(headers.KnownHeaders[i].pRawValue); } } return headers_map; } struct ConcRTOversubscribe { ConcRTOversubscribe() { #if _MSC_VER >= 1800 concurrency::Context::Oversubscribe(true); #endif } ~ConcRTOversubscribe() { #if _MSC_VER >= 1800 concurrency::Context::Oversubscribe(false); #endif } }; class _test_http_server { inline bool is_error_code(ULONG error_code) { return error_code == ERROR_OPERATION_ABORTED || error_code == ERROR_CONNECTION_INVALID || error_code == ERROR_NETNAME_DELETED || m_isClosing; } public: _test_http_server(const utility::string_t &uri) : m_uri(uri), m_session(0), m_url_group(0), m_request_queue(nullptr), m_isClosing(false) { HTTPAPI_VERSION httpApiVersion = HTTPAPI_VERSION_2; HttpInitialize(httpApiVersion, HTTP_INITIALIZE_SERVER, NULL); } ~_test_http_server() { HttpTerminate(HTTP_INITIALIZE_SERVER, NULL); } unsigned long open() { // Open server session. HTTPAPI_VERSION httpApiVersion = HTTPAPI_VERSION_2; ULONG error_code = HttpCreateServerSession(httpApiVersion, &m_session, 0); if(error_code) { return error_code; } // Create Url group. error_code = HttpCreateUrlGroup(m_session, &m_url_group, 0); if(error_code) { return error_code; } // Create request queue. error_code = HttpCreateRequestQueue(httpApiVersion, U("test_http_server"), NULL, NULL, &m_request_queue); if(error_code) { return error_code; } // Windows HTTP Server API will not accept a uri with an empty path, it must have a '/'. auto host_uri = m_uri.to_string(); if(m_uri.is_path_empty() && host_uri[host_uri.length() - 1] != '/' && m_uri.query().empty() && m_uri.fragment().empty()) { host_uri.append(U("/")); } // Add Url. error_code = HttpAddUrlToUrlGroup(m_url_group, host_uri.c_str(), (HTTP_URL_CONTEXT)this, 0); if(error_code) { return error_code; } // Associate Url group with request queue. HTTP_BINDING_INFO bindingInfo; bindingInfo.RequestQueueHandle = m_request_queue; bindingInfo.Flags.Present = 1; error_code = HttpSetUrlGroupProperty(m_url_group, HttpServerBindingProperty, &bindingInfo, sizeof(HTTP_BINDING_INFO)); if(error_code) { return error_code; } // Spawn a task to handle receiving incoming requests. m_request_task = pplx::create_task([this]() { ConcRTOversubscribe osubs; // Oversubscription for long running ConcRT tasks for(;;) { const ULONG buffer_length = 1024 * 4; char buffer[buffer_length]; ULONG bytes_received = 0; HTTP_REQUEST *p_http_request = (HTTP_REQUEST *)buffer; // Read in everything except the body. ULONG error_code2 = HttpReceiveHttpRequest( m_request_queue, HTTP_NULL_ID, 0, p_http_request, buffer_length, &bytes_received, 0); if (is_error_code(error_code2)) break; else VERIFY_ARE_EQUAL(0, error_code2); // Now create request structure. auto p_test_request = new test_request(); m_requests_memory.push_back(p_test_request); p_test_request->m_request_id = p_http_request->RequestId; p_test_request->m_p_server = this; p_test_request->m_path = utf8_to_utf16(p_http_request->pRawUrl); p_test_request->m_method = parse_verb(p_http_request); p_test_request->m_headers = parse_http_headers(p_http_request->Headers); // Read in request body. ULONG content_length; const bool has_content_length = p_test_request->match_header(U("Content-Length"), content_length); if(has_content_length && content_length > 0) { p_test_request->m_body.resize(content_length); auto result = HttpReceiveRequestEntityBody( m_request_queue, p_http_request->RequestId, HTTP_RECEIVE_REQUEST_ENTITY_BODY_FLAG_FILL_BUFFER, &p_test_request->m_body[0], content_length, &bytes_received, NULL); if (is_error_code(result)) break; else VERIFY_ARE_EQUAL(0, result); } utility::string_t transfer_encoding; const bool has_transfer_encoding = p_test_request->match_header(U("Transfer-Encoding"), transfer_encoding); if(has_transfer_encoding && transfer_encoding == U("chunked")) { content_length = 0; char buf[4096]; auto result = HttpReceiveRequestEntityBody( m_request_queue, p_http_request->RequestId, HTTP_RECEIVE_REQUEST_ENTITY_BODY_FLAG_FILL_BUFFER, (LPVOID)buf, 4096, &bytes_received, NULL); while ( result == NO_ERROR ) { content_length += bytes_received; p_test_request->m_body.resize(content_length); memcpy(&p_test_request->m_body[content_length-bytes_received], buf, bytes_received); result = HttpReceiveRequestEntityBody( m_request_queue, p_http_request->RequestId, HTTP_RECEIVE_REQUEST_ENTITY_BODY_FLAG_FILL_BUFFER, (LPVOID)buf, 4096, &bytes_received, NULL); } if (is_error_code(result)) break; else VERIFY_ARE_EQUAL(ERROR_HANDLE_EOF, result); } // Place request buffer. Concurrency::asend(m_requests, p_test_request); } }); return 0; } unsigned long close() { // Wait for all outstanding next_requests to be processed. A hang here means the test case // isn't making sure all the requests have been satisfied. We will only wait for 1 minute and then // allow the test case to continue anyway. This should be enough time for any outstanding requests to come // in and avoid causing an AV. // // If the requests haven't been fullfilled by now then there is a test bug so we still will mark failure. bool allRequestsSatisfied = true; std::for_each(m_all_next_request_tasks.begin(), m_all_next_request_tasks.end(), [&](pplx::task request_task) { if(!request_task.is_done()) { allRequestsSatisfied = false; } }); if(!allRequestsSatisfied) { // Just wait for either all the requests to finish or for the timer task, it doesn't matter which one. auto allRequestsTask = pplx::when_all(m_all_next_request_tasks.begin(), m_all_next_request_tasks.end()).then([](const std::vector &){}); auto timerTask = pplx::create_task([]() { ::tests::common::utilities::os_utilities::sleep(30000); }); (timerTask || allRequestsTask).wait(); VERIFY_IS_TRUE(false, "HTTP test case didn't properly wait for all requests to be satisfied."); } // Signal shutting down m_isClosing = true; // Windows HTTP Server API will not accept a uri with an empty path, it must have a '/'. utility::string_t host_uri = m_uri.to_string(); if(m_uri.is_path_empty() && host_uri[host_uri.length() - 1] != '/' && m_uri.query().empty() && m_uri.fragment().empty()) { host_uri.append(U("/")); } // Remove Url. ULONG error_code = HttpRemoveUrlFromUrlGroup(m_url_group, host_uri.c_str(), 0); if(error_code) { return error_code; } // Stop request queue. error_code = HttpShutdownRequestQueue(m_request_queue); m_request_task.wait(); if(error_code) { return error_code; } // Release memory for each request. std::for_each(m_requests_memory.begin(), m_requests_memory.end(), [](test_request *p_request) { delete p_request; }); // Close all resources. HttpCloseRequestQueue(m_request_queue); HttpCloseUrlGroup(m_url_group); HttpCloseServerSession(m_session); return 0; } test_request * wait_for_request() { return wait_for_requests(1)[0]; } pplx::task next_request() { auto next_request_task = pplx::create_task([this]() -> test_request * { return wait_for_request(); }); m_all_next_request_tasks.push_back(next_request_task); return next_request_task; } std::vector> next_requests(const size_t count) { std::vector> events; std::vector> requests; for(size_t i = 0; i < count; ++i) { events.push_back(pplx::task_completion_event()); auto next_request_task = pplx::create_task(events[i]); requests.push_back(next_request_task); m_all_next_request_tasks.push_back(next_request_task); } pplx::create_task([this, count, events]() { for(size_t i = 0; i < count; ++i) { events[i].set(wait_for_request()); } }); return requests; } std::vector wait_for_requests(const size_t count) { std::vector m_test_requests; for(size_t i = 0; i < count; ++i) { m_test_requests.push_back(Concurrency::receive(m_requests)); } return m_test_requests; } private: friend class test_request; ::http::uri m_uri; pplx::task m_request_task; Concurrency::unbounded_buffer m_requests; // Used to store all requests to simplify memory management. std::vector m_requests_memory; // Used to store all tasks created to wait on requests to catch any test cases // which fail to make sure any next_request tasks have completed before exiting. // Test cases rarely use more than a couple so it is ok to store them all. std::vector> m_all_next_request_tasks; HTTP_SERVER_SESSION_ID m_session; HTTP_URL_GROUP_ID m_url_group; HANDLE m_request_queue; volatile bool m_isClosing; }; #else class _test_http_server { private: const std::string m_uri; typename web::http::experimental::listener::http_listener m_listener; pplx::extensibility::critical_section_t m_lock; std::vector> m_requests; std::atomic m_last_request_id; std::unordered_map m_responding_requests; volatile std::atomic m_cancel; public: _test_http_server(const utility::string_t& uri) : m_uri(uri) , m_listener(uri) , m_last_request_id(0) , m_cancel(0) { m_listener.support([&](web::http::http_request result) -> void { auto tr = new test_request(); tr->m_method = result.method(); tr->m_path = result.request_uri().resource().to_string(); if (tr->m_path == "") tr->m_path = "/"; tr->m_p_server = this; tr->m_request_id = ++m_last_request_id; for (auto it = result.headers().begin(); it != result.headers().end(); ++it) tr->m_headers[it->first] = it->second; tr->m_body = result.extract_vector().get(); { pplx::extensibility::scoped_critical_section_t lock(m_lock); m_responding_requests[tr->m_request_id] = result; } while (!m_cancel) { pplx::extensibility::scoped_critical_section_t lock(m_lock); if (m_requests.size() > 0) { m_requests[0].set(tr); m_requests.erase(m_requests.begin()); return; } } }); } ~_test_http_server() { close(); } unsigned long open() { m_listener.open().wait(); return 0;} unsigned long close() { ++m_cancel; m_listener.close().wait(); return 0; } test_request * wait_for_request() { return next_request().get(); } unsigned long send_reply( unsigned long long request_id, const unsigned short status_code, const utility::string_t &reason_phrase, const std::map &headers, void * data, size_t data_length) { web::http::http_request request; { pplx::extensibility::scoped_critical_section_t lock(m_lock); auto it = m_responding_requests.find(request_id); if (it == m_responding_requests.end()) throw std::runtime_error("no such request awaiting response"); request = it->second; m_responding_requests.erase(it); } web::http::http_response response; response.set_status_code(status_code); response.set_reason_phrase(reason_phrase); for (auto it = headers.begin(); it != headers.end(); ++it) response.headers().add(it->first, it->second); unsigned char * data_bytes = reinterpret_cast(data); std::vector body_data(data_bytes, data_bytes + data_length); response.set_body(std::move(body_data)); request.reply(response); return 0; } pplx::extensibility::critical_section_t m_next_request_lock; pplx::task next_request() { pplx::task_completion_event tce; pplx::extensibility::scoped_critical_section_t lock(m_lock); m_requests.push_back(tce); return pplx::create_task(tce); } std::vector> next_requests(const size_t count) { std::vector> result; for (int i = 0; i < count; ++i) { result.push_back(next_request()); } return result; } std::vector wait_for_requests(const size_t count) { std::vector requests; for (int i = 0; i < count; ++i) { requests.push_back(wait_for_request()); } return requests; } }; #endif unsigned long test_request::reply(const unsigned short status_code) { return reply(status_code, U("")); } unsigned long test_request::reply(const unsigned short status_code, const utility::string_t &reason_phrase) { return reply(status_code, reason_phrase, std::map(), ""); } unsigned long test_request::reply( const unsigned short status_code, const utility::string_t &reason_phrase, const std::map &headers) { return reply(status_code, reason_phrase, headers, U("")); } unsigned long test_request::reply( const unsigned short status_code, const utility::string_t &reason_phrase, const std::map &headers, const utf8string &data) { return reply_impl(status_code, reason_phrase, headers, (void *)&data[0], data.size() * sizeof(utf8char)); } unsigned long test_request::reply( const unsigned short status_code, const utility::string_t &reason_phrase, const std::map &headers, const utf16string &data) { return reply_impl(status_code, reason_phrase, headers, (void *)&data[0], data.size() * sizeof(utf16char)); } #ifdef _WIN32 unsigned long test_request::reply_impl( const unsigned short status_code, const utility::string_t &reason_phrase, const std::map &headers, void * data, size_t data_length) { ConcRTOversubscribe osubs; // Oversubscription for long running ConcRT tasks HTTP_RESPONSE response; ZeroMemory(&response, sizeof(HTTP_RESPONSE)); response.StatusCode = status_code; std::string reason(reason_phrase.begin(), reason_phrase.end()); response.pReason = reason.c_str(); response.ReasonLength = (USHORT)reason.length(); // Add headers. std::vector headers_buffer; response.Headers.UnknownHeaderCount = (USHORT)headers.size() + 1; response.Headers.pUnknownHeaders = new HTTP_UNKNOWN_HEADER[headers.size() + 1]; headers_buffer.resize(headers.size() * 2 + 2); // Add the no cache header. headers_buffer[0] = "Cache-Control"; headers_buffer[1] = "no-cache"; response.Headers.pUnknownHeaders[0].NameLength = (USHORT)headers_buffer[0].size(); response.Headers.pUnknownHeaders[0].pName = headers_buffer[0].c_str(); response.Headers.pUnknownHeaders[0].RawValueLength = (USHORT)headers_buffer[1].size(); response.Headers.pUnknownHeaders[0].pRawValue = headers_buffer[1].c_str(); // Add all other headers. if(!headers.empty()) { int headerIndex = 1; for(auto iter = headers.begin(); iter != headers.end(); ++iter, ++headerIndex) { headers_buffer[headerIndex * 2] = utf16_to_utf8(iter->first); headers_buffer[headerIndex * 2 + 1] = utf16_to_utf8(iter->second); // TFS 624150 #pragma warning (push) #pragma warning (disable : 6386) response.Headers.pUnknownHeaders[headerIndex].NameLength = (USHORT)headers_buffer[headerIndex * 2].size(); #pragma warning (pop) response.Headers.pUnknownHeaders[headerIndex].pName = headers_buffer[headerIndex * 2].c_str(); response.Headers.pUnknownHeaders[headerIndex].RawValueLength = (USHORT)headers_buffer[headerIndex * 2 + 1].size(); response.Headers.pUnknownHeaders[headerIndex].pRawValue = headers_buffer[headerIndex * 2 + 1].c_str(); } } // Add body. response.EntityChunkCount = 0; HTTP_DATA_CHUNK dataChunk; if(data_length != 0) { response.EntityChunkCount = 1; dataChunk.DataChunkType = HttpDataChunkFromMemory; dataChunk.FromMemory.pBuffer = (void *)data; dataChunk.FromMemory.BufferLength = (ULONG)data_length; response.pEntityChunks = &dataChunk; } // Synchronously sending the request. unsigned long error_code = HttpSendHttpResponse( m_p_server->m_request_queue, m_request_id, HTTP_SEND_RESPONSE_FLAG_DISCONNECT, &response, NULL, NULL, NULL, NULL, NULL, NULL); // Free memory needed for headers. if(response.Headers.UnknownHeaderCount != 0) { delete [] response.Headers.pUnknownHeaders; } return error_code; } #else unsigned long test_request::reply_impl( const unsigned short status_code, const utility::string_t &reason_phrase, const std::map &headers, void * data, size_t data_length) { return m_p_server->send_reply(m_request_id, status_code, reason_phrase, headers, data, data_length); } #endif test_http_server::test_http_server(const web::http::uri &uri) { m_p_impl = new _test_http_server(uri.to_string()); } test_http_server::~test_http_server() { delete m_p_impl; } unsigned long test_http_server::open() { return m_p_impl->open(); } unsigned long test_http_server::close() { return m_p_impl->close(); } test_request * test_http_server::wait_for_request() { return m_p_impl->wait_for_request(); } pplx::task test_http_server::next_request() { return m_p_impl->next_request(); } std::vector test_http_server::wait_for_requests(const size_t count) { return m_p_impl->wait_for_requests(count); } std::vector> test_http_server::next_requests(const size_t count) { return m_p_impl->next_requests(count); } }}}}