/* +----------------------------------------------------------------------+ | HipHop for PHP | +----------------------------------------------------------------------+ | Copyright (c) 2010-2013 Facebook, Inc. (http://www.facebook.com) | +----------------------------------------------------------------------+ | This source file is subject to version 3.01 of the PHP license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.php.net/license/3_01.txt | | If you did not receive a copy of the PHP license and are unable to | | obtain it through the world-wide-web, please send a note to | | license@php.net so we can mail you a copy immediately. | +----------------------------------------------------------------------+ */ #include "hphp/test/ext/test_server.h" #include "hphp/compiler/parser/parser.h" #include "hphp/compiler/builtin_symbols.h" #include "hphp/compiler/code_generator.h" #include "hphp/compiler/analysis/analysis_result.h" #include "hphp/util/util.h" #include "hphp/util/process.h" #include "hphp/compiler/option.h" #include "hphp/util/async_func.h" #include "hphp/runtime/ext/ext_curl.h" #include "hphp/runtime/ext/ext_options.h" #include "hphp/runtime/base/server/http_request_handler.h" #include "hphp/runtime/base/util/http_client.h" #include "hphp/runtime/base/runtime_option.h" #include "hphp/runtime/base/server/libevent_server.h" #include using namespace HPHP; #define PORT_MIN 7300 #define PORT_MAX 7400 /////////////////////////////////////////////////////////////////////////////// TestServer::TestServer() { } static int s_server_port = 0; static int s_admin_port = 0; static int s_rpc_port = 0; static int inherit_fd = -1; bool TestServer::VerifyServerResponse(const char *input, const char **outputs, const char **urls, int nUrls, const char *method, const char *header, const char *postdata, bool responseHeader, const char *file /* = "" */, int line /* = 0 */, int port /* = 0 */) { assert(input); if (port == 0) port = s_server_port; if (!CleanUp()) return false; string fullPath = "runtime/tmp/string"; std::ofstream f(fullPath.c_str()); if (!f) { printf("Unable to open %s for write. Run this test from hphp/.\n", fullPath.c_str()); return false; } f << input; f.close(); AsyncFunc func(this, &TestServer::RunServer); func.start(); bool passed = true; string actual; int url = 0; for (url = 0; url < nUrls; url++) { String server = "http://"; server += f_php_uname("n"); server += ":" + lexical_cast(port) + "/"; server += urls[url]; actual = ""; string err; for (int i = 0; i < 10; i++) { Variant c = f_curl_init(); f_curl_setopt(c.toResource(), k_CURLOPT_URL, server); f_curl_setopt(c.toResource(), k_CURLOPT_RETURNTRANSFER, true); if (postdata) { f_curl_setopt(c.toResource(), k_CURLOPT_POSTFIELDS, postdata); f_curl_setopt(c.toResource(), k_CURLOPT_POST, true); } if (header) { f_curl_setopt(c.toResource(), k_CURLOPT_HTTPHEADER, CREATE_VECTOR1(header)); } if (responseHeader) { f_curl_setopt(c.toResource(), k_CURLOPT_HEADER, 1); } Variant res = f_curl_exec(c.toResource()); if (!same(res, false)) { actual = (std::string) res.toString(); break; } sleep(1); // wait until HTTP server is up and running } if (actual != outputs[url]) { if (!responseHeader || actual.find(outputs[url]) == string::npos) { passed = false; break; } } } AsyncFunc(this, &TestServer::StopServer).run(); func.waitForEnd(); if (!passed) { printf("%s:%d\nParsing: [%s] (req %d)\nBet %d:\n" "--------------------------------------\n" "%s" "--------------------------------------\n" "Got %d:\n" "--------------------------------------\n" "%s" "--------------------------------------\n", file, line, input, url + 1, (int)strlen(outputs[url]), outputs[url], (int)actual.length(), actual.c_str()); return false; } return true; } void TestServer::RunServer() { string out, err; string portConfig = "-vServer.Port=" + lexical_cast(s_server_port); string adminConfig = "-vAdminServer.Port=" + lexical_cast(s_admin_port); string rpcConfig = "-vSatellites.rpc.Port=" + lexical_cast(s_rpc_port); string fd = lexical_cast(inherit_fd); const char *argv[] = { "", "--mode=server", "--config=test/ext/config-server.hdf", portConfig.c_str(), adminConfig.c_str(), rpcConfig.c_str(), "--port-fd", fd.c_str(), NULL }; if (Option::EnableEval < Option::FullEval) { argv[0] = "runtime/tmp/TestServer/test"; } else { argv[0] = HHVM_PATH; } Process::Exec(argv[0], argv, NULL, out, &err); } void TestServer::StopServer() { for (int i = 0; i < 10; i++) { string out, err; Variant c = f_curl_init(); String url = "http://"; url += f_php_uname("n"); url += ":" + lexical_cast(s_admin_port) + "/stop"; f_curl_setopt(c.toResource(), k_CURLOPT_URL, url); f_curl_setopt(c.toResource(), k_CURLOPT_RETURNTRANSFER, true); Variant res = f_curl_exec(c.toResource()); if (!same(res, false)) { break; } sleep(1); // wait until HTTP server is up and running } } /////////////////////////////////////////////////////////////////////////////// class TestServerRequestHandler : public RequestHandler { public: // implementing RequestHandler virtual void handleRequest(Transport *transport) { // do nothing } }; static int find_server_port(int port_min, int port_max) { for (int port = port_min; ; port++) { try { ServerPtr server = boost::make_shared( "127.0.0.1", port, 50, -1); server->setRequestHandlerFactory(); server->start(); server->stop(); server->waitForEnd(); return port; } catch (const FailedToListenException& e) { if (port >= port_max) throw; } } } bool TestServer::RunTests(const std::string &which) { bool ret = true; { // TestLibeventServer finds a good port to listen on, so it must // always run. std::string which = "TestLibeventServer"; RUN_TEST(TestLibeventServer); } s_admin_port = find_server_port(s_server_port + 1, PORT_MAX); s_rpc_port = find_server_port(s_admin_port + 1, PORT_MAX); RUN_TEST(TestInheritFdServer); RUN_TEST(TestSanity); RUN_TEST(TestServerVariables); RUN_TEST(TestInteraction); RUN_TEST(TestGet); RUN_TEST(TestPost); RUN_TEST(TestCookie); RUN_TEST(TestResponseHeader); RUN_TEST(TestSetCookie); //RUN_TEST(TestRequestHandling); RUN_TEST(TestHttpClient); RUN_TEST(TestRPCServer); RUN_TEST(TestXboxServer); RUN_TEST(TestPageletServer); return ret; } /////////////////////////////////////////////////////////////////////////////// bool TestServer::TestSanity() { VSR("\n" " string(3) \"foo\"\n" " [2]=>\n" " string(3) \"bar\"\n" "}\n", "string?names[1]=foo&names[2]=bar"); VSGET("\n" " string(3) \"foo\"\n" " [1]=>\n" " string(3) \"bar\"\n" "}\n", "string?names[]=foo&names[]=bar"); VSGET("\n" " string(22) \"Set-Cookie: name=value\"\n" "}\n"); VSRES("\n" " string(22) \"Set-Cookie: name=value\"\n" "}\n"); return true; VSRES(" TestTransportPtr; typedef std::vector TestTransportPtrVec; typedef AsyncFunc TestTransportAsyncFunc; typedef boost::shared_ptr TestTransportAsyncFuncPtr; typedef std::vector TestTransportAsyncFuncPtrVec; #define TEST_SIZE 100 /** * Start processing TEST_SIZE number of requests at the same time with * that many threads. This is mainly testing global variables to make sure * all handling are thread-safe. */ bool TestServer::TestRequestHandling() { RuntimeOption::AllowedFiles.insert("/string"); TestTransportPtrVec transports(TEST_SIZE); TestTransportAsyncFuncPtrVec funcs(TEST_SIZE); for (unsigned int i = 0; i < TEST_SIZE; i++) { TestTransport *transport = new TestTransport(); transports[i] = TestTransportPtr(transport); funcs[i] = TestTransportAsyncFuncPtr (new TestTransportAsyncFunc(transport, &TestTransport::process)); } for (unsigned int i = 0; i < TEST_SIZE; i++) { funcs[i]->start(); } for (unsigned int i = 0; i < TEST_SIZE; i++) { funcs[i]->waitForEnd(); } for (unsigned int i = 0; i < TEST_SIZE; i++) { VS(transports[i]->m_code, 200); VS(String(transports[i]->m_response), "Hello, world!"); } return Count(true); } bool TestServer::TestLibeventServer() { s_server_port = find_server_port(PORT_MIN, PORT_MAX); return Count(true); } static bool PreBindSocketHelper(struct addrinfo *info) { if (info->ai_family != AF_INET && info->ai_family != AF_INET6) { printf("No IPV4/6 interface found.\n"); return false; } int fd = socket(info->ai_family, SOCK_STREAM, IPPROTO_TCP); if (fd < 0) { printf("Error creating socket: %s\n", strerror(errno)); return false; } int ret = ::bind(fd, info->ai_addr, info->ai_addrlen); if (ret < 0) { printf("Error binding socket to port %d: %s\n", s_server_port, strerror(errno)); return false; } inherit_fd = fd; return true; } bool TestServer::PreBindSocket() { struct addrinfo hints, *res, *res0; std::memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG; if (getaddrinfo(nullptr, lexical_cast(s_server_port).c_str(), &hints, &res0) < 0) { printf("Error in getaddrinfo(): %s\n", strerror(errno)); return false; } for (res = res0; res; res = res->ai_next) { if (res->ai_family == AF_INET6 || res->ai_next == nullptr) { break; } } bool ret = PreBindSocketHelper(res); freeaddrinfo(res0); return ret; } void TestServer::CleanupPreBoundSocket() { close(inherit_fd); inherit_fd = -1; } bool TestServer::TestInheritFdServer() { WITH_PREBOUND_SOCKET(VSR("getHeaders(headers); string response; response = "\nGET param: name = "; response += transport->getParam("name"); if (transport->getMethod() == Transport::Method::POST) { int size = 0; const char *data = (const char *)transport->getPostData(size); response += "\nPOST data: "; response += string(data, size); } for (HeaderMap::const_iterator iter = headers.begin(); iter != headers.end(); ++iter) { response += "\nHeader: "; response += iter->first; for (unsigned int i = 0; i < iter->second.size(); i++) { response += "\n"; response += lexical_cast(i); response += ": "; response += iter->second[i]; } } transport->addHeader("Custom", "blah"); transport->sendString(response); } }; bool TestServer::TestHttpClient() { ServerPtr server; for (s_server_port = PORT_MIN; s_server_port <= PORT_MAX; s_server_port++) { try { server = boost::make_shared( "127.0.0.1", s_server_port, 50, -1); server->setRequestHandlerFactory(); server->start(); break; } catch (const FailedToListenException& e) { if (s_server_port == PORT_MAX) throw; } } HeaderMap headers; headers["Cookie"].push_back("c1=v1;c2=v2;"); headers["Cookie"].push_back("c3=v3;c4=v4;"); string url = "http://127.0.0.1:" + lexical_cast(s_server_port) + "/echo?name=value"; for (int i = 0; i < 10; i++) { HttpClient http; StringBuffer response; vector responseHeaders; int code = http.get(url.c_str(), response, &headers, &responseHeaders); VS(code, 200); VS(response.data(), ("\nGET param: name = value" "\nHeader: Accept" "\n0: */*" "\nHeader: Cookie" "\n0: c1=v1;c2=v2;" "\n1: c3=v3;c4=v4;" "\nHeader: Host" "\n0: 127.0.0.1:" + lexical_cast(s_server_port)).c_str()); bool found = false; for (unsigned int i = 0; i < responseHeaders.size(); i++) { if (responseHeaders[i] == "Custom: blah") { found = true; } } VERIFY(found); } for (int i = 0; i < 10; i++) { HttpClient http; StringBuffer response; vector responseHeaders; int code = http.post(url.c_str(), "postdata", 8, response, &headers, &responseHeaders); VS(code, 200); VS(response.data(), ("\nGET param: name = value" "\nPOST data: postdata" "\nHeader: Accept" "\n0: */*" "\nHeader: Content-Length" "\n0: 8" "\nHeader: Content-Type" "\n0: application/x-www-form-urlencoded" "\nHeader: Cookie" "\n0: c1=v1;c2=v2;" "\n1: c3=v3;c4=v4;" "\nHeader: Host" "\n0: 127.0.0.1:" + lexical_cast(s_server_port)).c_str()); bool found = false; for (unsigned int i = 0; i < responseHeaders.size(); i++) { if (responseHeaders[i] == "Custom: blah") { found = true; } } VERIFY(found); } server->stop(); server->waitForEnd(); return Count(true); } bool TestServer::TestRPCServer() { // the simplest case VSGETP("