Arquivos
hhvm/hphp/test/ext/test_server.cpp
T
Drew Paroski 84b9d9a3a2 Separate resources from objects, part 1
In HHVM (and HPHPc before it) we've been piggybacking resources on the
KindOfObject machinery. At the language level, resource is considered to
be a different type than object, and there are a number of differences
in behavior between objects and resources (ex. resources don't allow for
dynamic properties, resources don't work with the clone operator, the
"(object)" cast behaves differently for resources vs. objects, etc).

Piggybacking resources on the KindOfObject machinery has some downsides.
Code that deals with KindOfObject values often needs to check if the value
is a resource and go down a different code path. This makes things harder
to maintain and harder to keep parity with Zend. Also, these extra branches
hurt performance a little, and they make it harder for the JIT to do a good
job in some cases when its generating machine code that operates on objects.

This diff prepares the code base for a new KindOfResource type by adding a
new "Resource" smart pointer type (currently a typedef for the Object smart
pointer type) and it updates the C++ code and the idl files appropriately.
This diff is essentially a cosmetic change and should not impact run time
behavior. In the next diff (part 2) we'll actually add a new KindOfResource
type, detach ResourceData from the ObjectData inheritence hierarchy, and
provide a real implementation for the Resource smart pointer type (instead
of just aliasing the Object smart pointer type).
2013-07-10 11:16:33 -07:00

796 linhas
23 KiB
C++

/*
+----------------------------------------------------------------------+
| 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 <boost/make_shared.hpp>
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<TestServer> 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<string>(port) + "/";
server += urls[url];
actual = "<No response from server>";
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<TestServer>(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<string>(s_server_port);
string adminConfig = "-vAdminServer.Port=" +
lexical_cast<string>(s_admin_port);
string rpcConfig = "-vSatellites.rpc.Port=" +
lexical_cast<string>(s_rpc_port);
string fd = lexical_cast<string>(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<string>(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<LibEventServer>(
"127.0.0.1", port, 50, -1);
server->setRequestHandlerFactory<TestServerRequestHandler>();
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("<?php print 'Hello, World!';",
"Hello, World!");
return true;
}
bool TestServer::TestServerVariables() {
VSR("<?php var_dump($_POST, $_GET);",
"array(0) {\n}\narray(0) {\n}\n");
VSR("<?php print $_SERVER['REQUEST_URI'];",
"/string");
VSGET("<?php "
"var_dump($_SERVER['PATH_INFO']);"
"var_dump(clean($_SERVER['PATH_TRANSLATED']));"
"var_dump($_SERVER['SCRIPT_NAME']);"
"var_dump($_SERVER['REQUEST_URI']);"
"var_dump(clean($_SERVER['SCRIPT_FILENAME']));"
"var_dump($_SERVER['QUERY_STRING']);"
"function clean($x) { return str_replace(getcwd(),'',$x); }",
"string(13) \"/path/subpath\"\n"
"string(13) \"/path/subpath\"\n"
"string(7) \"/string\"\n"
"string(28) \"/string/path/subpath?a=1&b=2\"\n"
"string(7) \"/string\"\n"
"string(7) \"a=1&b=2\"\n",
"string/path/subpath?a=1&b=2");
VSGET("<?php "
"var_dump($_SERVER['PATH_INFO']);"
"var_dump(clean($_SERVER['PATH_TRANSLATED']));"
"var_dump($_SERVER['SCRIPT_NAME']);"
"var_dump($_SERVER['REQUEST_URI']);"
"var_dump(clean($_SERVER['SCRIPT_FILENAME']));"
"var_dump($_SERVER['QUERY_STRING']);"
"var_dump(isset($_ENV['HPHP_RPC']));"
"function clean($x) { return str_replace(getcwd(),'',$x); }",
"NULL\n"
"string(7) \"/string\"\n"
"string(7) \"/string\"\n"
"string(15) \"/string?a=1&b=2\"\n"
"string(7) \"/string\"\n"
"string(7) \"a=1&b=2\"\n"
"bool(false)\n",
"string?a=1&b=2");
return true;
}
bool TestServer::TestInteraction() {
// run this twice to test lvalBlackHole
VSR2("<?php "
"$a[] = new stdclass;"
"var_dump(count(array_combine($a, $a)));",
"int(0)\n");
return true;
}
bool TestServer::TestGet() {
VSGET("<?php var_dump($_GET['name']);",
"string(0) \"\"\n", "string?name");
VSGET("<?php var_dump($_GET['name'], $_GET['id']);",
"string(0) \"\"\nstring(1) \"1\"\n", "string?name&id=1");
VSGET("<?php print $_GET['name'];",
"value", "string?name=value");
VSGET("<?php var_dump($_GET['names']);",
"array(2) {\n"
" [1]=>\n"
" string(3) \"foo\"\n"
" [2]=>\n"
" string(3) \"bar\"\n"
"}\n",
"string?names[1]=foo&names[2]=bar");
VSGET("<?php var_dump($_GET['names']);",
"array(2) {\n"
" [0]=>\n"
" string(3) \"foo\"\n"
" [1]=>\n"
" string(3) \"bar\"\n"
"}\n",
"string?names[]=foo&names[]=bar");
VSGET("<?php print $_REQUEST['name'];",
"value", "string?name=value");
return true;
}
bool TestServer::TestPost() {
const char *params = "name=value";
VSPOST("<?php print $_POST['name'];",
"value", "string", params);
VSPOST("<?php print $_REQUEST['name'];",
"value", "string", params);
VSPOST("<?php print $HTTP_RAW_POST_DATA;",
"name=value", "string", params);
return true;
}
bool TestServer::TestCookie() {
VSRX("<?php print $_COOKIE['name'];",
"value", "string", "GET", "Cookie: name=value;", nullptr);
VSRX("<?php print $_COOKIE['name2'];",
"value2", "string", "GET", "Cookie: n=v;name2=value2;n3=v3", nullptr);
return true;
}
bool TestServer::TestResponseHeader() {
VSR("<?php header('Set-Cookie: name=value'); var_dump(headers_list());",
"array(1) {\n"
" [0]=>\n"
" string(22) \"Set-Cookie: name=value\"\n"
"}\n");
VSRES("<?php header('Set-Cookie: name=value');",
"Set-Cookie: name=value");
VSRES("<?php header('Location: new/url');",
"302");
VSRES("<?php header(\"Test-Header: x\ry\"); echo 'done';",
"done");
return true;
}
bool TestServer::TestSetCookie() {
VSR("<?php setcookie('name', 'value'); var_dump(headers_list());",
"array(1) {\n"
" [0]=>\n"
" string(22) \"Set-Cookie: name=value\"\n"
"}\n");
return true;
VSRES("<?php setcookie('name', 'value');",
"Set-Cookie: name=value");
return true;
}
///////////////////////////////////////////////////////////////////////////////
class TestTransport : public Transport {
public:
TestTransport() : m_code(0) {}
int m_code;
std::string m_response;
/**
* Implementing HttpTransport...
*/
virtual const char *getUrl() { return "/string";}
virtual const char *getRemoteHost() { return "remote";}
virtual const void *getPostData(int &size) { size = 0; return nullptr;}
virtual uint16_t getRemotePort() { return 0; }
virtual Method getMethod() { return Transport::Method::GET;}
virtual std::string getHeader(const char *name) { return "";}
virtual void getHeaders(HeaderMap &headers) {}
virtual void addHeaderImpl(const char *name, const char *value) {}
virtual void removeHeaderImpl(const char *name) {}
virtual void sendImpl(const void *data, int size, int code, bool chunked) {
m_response.clear();
m_response.append((const char *)data, size);
m_code = code;
}
void process() {
HttpRequestHandler handler;
for (unsigned int i = 0; i < 100; i++) {
handler.handleRequest(this);
}
}
};
typedef boost::shared_ptr<TestTransport> TestTransportPtr;
typedef std::vector<TestTransportPtr> TestTransportPtrVec;
typedef AsyncFunc<TestTransport> TestTransportAsyncFunc;
typedef boost::shared_ptr<TestTransportAsyncFunc> TestTransportAsyncFuncPtr;
typedef std::vector<TestTransportAsyncFuncPtr> 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<string>(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("<?php print 'Hello, World!';",
"Hello, World!"));
return true;
}
///////////////////////////////////////////////////////////////////////////////
class EchoHandler : public RequestHandler {
public:
// implementing RequestHandler
virtual void handleRequest(Transport *transport) {
HeaderMap headers;
transport->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<string>(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<LibEventServer>(
"127.0.0.1", s_server_port, 50, -1);
server->setRequestHandlerFactory<EchoHandler>();
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<string>(s_server_port) +
"/echo?name=value";
for (int i = 0; i < 10; i++) {
HttpClient http;
StringBuffer response;
vector<String> 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<string>(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<String> 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<string>(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("<?php\n"
"function f() { return 100; }\n",
"100",
"f?auth=test",
s_rpc_port);
// array output
VSGETP("<?php\n"
"function f($a) { return array(1, 2, 3, $a); }\n",
"[1,2,3,\"hello\"]",
"f?auth=test&p=\"hello\"",
s_rpc_port);
// associate arrays
VSGETP("<?php\n"
"function f($a, $b) { return array_merge($a, $b); }\n",
"{\"a\":1,\"0\":2,\"1\":1,\"2\":2}",
"f?auth=test&p={\"a\":1,\"1\":2}&p=[1,2]",
s_rpc_port);
// builtin function and static method
VSGETP("<?php\n"
"class A { static function f($a) { return $a; } }\n",
"100",
"call_user_func?auth=test&p=\"A::f\"&p=100",
s_rpc_port);
// invoking a file, with NO json encoding
// "int(100)" is printed twice, one from warmup, and the other from include
VSGETP("<?php\n"
"var_dump(100);\n",
"int(100)\n"
"int(100)\n",
"?include=string&output=1&auth=test",
s_rpc_port);
VSGETP("<?php\n"
"var_dump(isset($_ENV['HPHP_RPC']));\n",
"bool(true)\n"
"bool(true)\n",
"?include=string&output=1&auth=test",
s_rpc_port);
return true;
}
bool TestServer::TestXboxServer() {
VSGET("<?php\n"
"if (array_key_exists('main', $_GET)) {\n"
" $t = xbox_task_start('1');\n"
" xbox_task_result($t, 0, $r);\n"
" var_dump($r);\n"
" $t = xbox_task_start('2');\n"
" xbox_task_result($t, 0, $r);\n"
" $t = xbox_task_start('1');\n"
" xbox_task_result($t, 0, $r);\n"
" var_dump($r);\n"
" sleep(7);\n"
" $t = xbox_task_start('3');\n"
" xbox_task_result($t, 0, $r);\n"
" var_dump($r);\n"
" sleep(2);\n"
" $t = xbox_task_start('4');\n"
" xbox_task_result($t, 0, $r);\n"
" $t = xbox_task_start('3');\n"
" xbox_task_result($t, 0, $r);\n"
" var_dump($r);\n"
" sleep(2);\n"
" $t = xbox_task_start('3');\n"
" xbox_task_result($t, 0, $r);\n"
" var_dump($r);\n"
"} else {\n"
" function xbox_process_message($msg) {\n"
" if ($msg == '1') return xbox_get_thread_timeout();\n"
" else if ($msg == '2') xbox_set_thread_timeout(5);\n"
" else if ($msg == '3') return xbox_get_thread_time();\n"
" else xbox_schedule_thread_reset();\n"
" }\n"
"}\n",
"int(10)\n"
"int(5)\n"
"int(0)\n"
"int(0)\n"
"int(0)\n",
"string?main=1");
return true;
}
bool TestServer::TestPageletServer() {
VSGET("<?php\n"
"if (array_key_exists('pagelet', $_GET)) {\n"
" echo 'Hello from the pagelet!';\n"
"} else {\n"
" $h = array('Host: ' . $_SERVER['HTTP_HOST']);\n"
" $t = pagelet_server_task_start('/string?pagelet=1', $h, '');\n"
" echo 'First! ';\n"
" $r = pagelet_server_task_result($t, $h, $c);\n"
" echo $r;\n"
"}\n",
"First! Hello from the pagelet!",
"string");
// POST vs GET
VSGET("<?php\n"
"if (array_key_exists('pagelet', $_GET)) {\n"
" echo $_SERVER['REQUEST_METHOD'];\n"
"} else {\n"
" $h = array('Host: ' . $_SERVER['HTTP_HOST']);\n"
" $t = pagelet_server_task_start('/string?pagelet=1', $h, '');\n"
" echo 'First! ';\n"
" $r = pagelet_server_task_result($t, $h, $c);\n"
" echo $r;\n"
"}\n",
"First! GET",
"string");
VSGET("<?php\n"
"if ($_SERVER['THREAD_TYPE'] == 'Pagelet Thread') {\n"
" echo 'hello';\n"
" pagelet_server_flush();\n"
" ob_start();\n"
" echo 'world';\n"
" pagelet_server_flush();\n"
" echo 'what';\n"
"} else {\n"
" $h = array('Host: ' . $_SERVER['HTTP_HOST']);\n"
" $t = pagelet_server_task_start('/string', $h, '');\n"
" for ($i = 0; ; $i++) {\n"
" while (($s = pagelet_server_task_status($t)) == \n"
" PAGELET_NOT_READY) { sleep(1); }\n"
" echo \"Step $i:\\n\";\n"
" $r = pagelet_server_task_result($t, $h, $c);\n"
" echo $r . \"\\n\";\n"
" if ($s == PAGELET_DONE) break;\n"
" }\n"
"}\n",
"Step 0:\n"
"hello\n"
"Step 1:\n"
"world\n"
"Step 2:\n"
"what\n",
"string");
return true;
}