From 3b19e196faab180102fe85089b20df94f6344ff4 Mon Sep 17 00:00:00 2001 From: Dmitry Stogov Date: Mon, 9 Feb 2004 07:51:07 +0000 Subject: [PATCH] Allowing to pass request to SoapServer::handle direct (not through $HTTP_RAW_POST_DATA). --- ext/soap/readme.html | 30 ++- ext/soap/soap.c | 360 +++++++++++++++++----------------- ext/soap/tests/server015.phpt | 33 ++++ 3 files changed, 235 insertions(+), 188 deletions(-) create mode 100644 ext/soap/tests/server015.phpt diff --git a/ext/soap/readme.html b/ext/soap/readme.html index 4675337ec9..cbb38ab776 100644 --- a/ext/soap/readme.html +++ b/ext/soap/readme.html @@ -169,7 +169,7 @@ It is just a data holder and it has not any special method except constructor. -->

Table of Contents

- + @@ -189,7 +189,7 @@ It is just a data holder and it has not any special method except constructor.
is_sopa_fault -- checks if SOAP call was failed
is_soap_fault -- checks if SOAP call was failed
SoapClient::SoapClient -- SoapClient constructor
SoapClient::__call -- calls a SOAP function
SoapClient::__getLastRequest -- returns last SOAP request
-

is_sopa_fault

+

is_soap_fault

(PHP 5)

checks if SOAP call was failed

Description

@@ -266,7 +266,10 @@ This is a low level API function to make a SOAP call. Usually in WSDL mode you can simple call SOAP functions as SoapClient methods. It is useful for nonWSDL mode when 'soapaction' is unknown, 'uri' is differ form default or when ypu like to send and/or receive SOAP Headers. To check if function call -is failed check the result with is_soap_fault() function. +is failed check the result with is_soap_fault() function.
+SOAP function may return one or several values. In the first case __call will +return just the value of output parameter, in the second it will return +array with named output parameters.

Examples

@@ -388,12 +391,22 @@ allow setting a default SOAP version (soap_version) and actor URI
 Exports one or more functions for remote clients. To export one function pass
 function name into functions parameter as string. To export several
 functions pass an array of function names and to export all functions pass
-a special constant SOAP_FUNCTIONS_ALL.
+a special constant SOAP_FUNCTIONS_ALL.
+Functions must receive all input arguments in the same order as defined +in WSDL file (They should not receive any output parameters as arguments) and +return one or more values. To return several values they must return array with +named output parameters.

Examples

-    $server->addFunction("func");
+    function func($inputString) {
+        return $inputString;
+    }
+    $server->addFunction("echoString");
 
-    $server->addFunction(array("func1","func2"));
+    function echoTwoStrings($inputString1, $inputString2) {
+        return array("outputString1"=>$inputString1,"outputString2"=>$inputString2);
+    }
+    $server->addFunction(array("echoString","echoTwoStrings"));
 
     $server->addFunction(SOAP_FUNCTIONS_ALL);
 
@@ -443,9 +456,10 @@ with server that exports functions form class (see

(PHP 5)

handles a SOAP request

Description

-

void handle()

+

void handle([string soap_envelope])

It processes a SOAP request, call necessary functions, and send response back. -It assumes request in global $HTTP_RAW_POST_DATA PHP variable. +It assumes request in input parameter or in global $HTTP_RAW_POST_DATA PHP variable +if the argument is omitted.

Example

 <?php
diff --git a/ext/soap/soap.c b/ext/soap/soap.c
index 0febecad4d..ddda94746d 100644
--- a/ext/soap/soap.c
+++ b/ext/soap/soap.c
@@ -1051,12 +1051,17 @@ PHP_METHOD(soapserver, handle)
 	xmlChar *buf;
 	HashTable *function_table;
 	soapHeader *soap_headers;
+	sdlFunctionPtr function;
+	char *arg = NULL;
+	int arg_len;
 
 	SOAP_SERVER_BEGIN_CODE();
 
 	FETCH_THIS_SERVICE(service);
 	SOAP_GLOBAL(soap_version) = service->version;
-	ZERO_PARAM();
+	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|s", &arg, &arg_len) == FAILURE) {
+		php_error(E_ERROR, "Invalid parameters passed to soapserver:handle");
+	}
 	INIT_ZVAL(retval);
 
 	if (zend_hash_find(&EG(symbol_table), "_SERVER", sizeof("_SERVER"), (void **)&server_vars) == SUCCESS) {
@@ -1111,234 +1116,229 @@ PHP_METHOD(soapserver, handle)
 		}
 	}
 
-/* Turn on output buffering... we don't want people print in their methods
- #if PHP_API_VERSION <= 20010901
-	if (php_start_ob_buffer(NULL, 0 TSRMLS_CC) != SUCCESS)
- #else
-*/
 	if (php_start_ob_buffer(NULL, 0, 0 TSRMLS_CC) != SUCCESS) {
-/* #endif */
 		php_error(E_ERROR,"ob_start failed");
 	}
 
-	if (zend_hash_find(&EG(symbol_table), HTTP_RAW_POST_DATA, sizeof(HTTP_RAW_POST_DATA), (void **) &raw_post)!=FAILURE
-		&& ((*raw_post)->type==IS_STRING)) {
-		sdlFunctionPtr function;
-
-		doc_request = soap_xmlParseMemory(Z_STRVAL_PP(raw_post),Z_STRLEN_PP(raw_post));
-
-		if (doc_request == NULL) {
-			php_error(E_ERROR, "Bad Request");
-		}
-		if (xmlGetIntSubset(doc_request) != NULL) {
-			xmlNodePtr env = get_node(doc_request->children,"Envelope");
-			if (env && env->ns) {
-				if (strcmp(env->ns->href,SOAP_1_1_ENV_NAMESPACE) == 0) {
-					SOAP_GLOBAL(soap_version) = SOAP_1_1;
-				} else if (strcmp(env->ns->href,SOAP_1_2_ENV_NAMESPACE) == 0) {
-					SOAP_GLOBAL(soap_version) = SOAP_1_2;
-				}
+	if (ZEND_NUM_ARGS() == 0) {
+		if (zend_hash_find(&EG(symbol_table), HTTP_RAW_POST_DATA, sizeof(HTTP_RAW_POST_DATA), (void **) &raw_post)!=FAILURE
+			&& ((*raw_post)->type==IS_STRING)) {
+			doc_request = soap_xmlParseMemory(Z_STRVAL_PP(raw_post),Z_STRLEN_PP(raw_post));
+		} else {
+			if (!zend_ini_long("always_populate_raw_post_data", sizeof("always_populate_raw_post_data"), 0)) {
+				php_error(E_ERROR, "PHP-SOAP requires 'always_populate_raw_post_data' to be on please check your php.ini file");
 			}
-			xmlFreeDoc(doc_request);
-			php_error(E_ERROR,"DTD are not supported by SOAP");
+			php_error(E_ERROR, "Can't find HTTP_RAW_POST_DATA");
 		}
+	} else {
+		doc_request = soap_xmlParseMemory(arg,arg_len);
+	}
 
-		old_sdl = SOAP_GLOBAL(sdl);
-		SOAP_GLOBAL(sdl) = service->sdl;
-		old_soap_version = SOAP_GLOBAL(soap_version);
-		function = deseralize_function_call(service->sdl, doc_request, service->actor, &function_name, &num_params, ¶ms, &soap_version, &soap_headers TSRMLS_CC);
+	if (doc_request == NULL) {
+		php_error(E_ERROR, "Bad Request");
+	}
+	if (xmlGetIntSubset(doc_request) != NULL) {
+		xmlNodePtr env = get_node(doc_request->children,"Envelope");
+		if (env && env->ns) {
+			if (strcmp(env->ns->href,SOAP_1_1_ENV_NAMESPACE) == 0) {
+				SOAP_GLOBAL(soap_version) = SOAP_1_1;
+			} else if (strcmp(env->ns->href,SOAP_1_2_ENV_NAMESPACE) == 0) {
+				SOAP_GLOBAL(soap_version) = SOAP_1_2;
+			}
+		}
 		xmlFreeDoc(doc_request);
+		php_error(E_ERROR,"DTD are not supported by SOAP");
+	}
 
-		if (service->type == SOAP_CLASS) {
-			soap_obj = NULL;
+	old_sdl = SOAP_GLOBAL(sdl);
+	SOAP_GLOBAL(sdl) = service->sdl;
+	old_soap_version = SOAP_GLOBAL(soap_version);
+	function = deseralize_function_call(service->sdl, doc_request, service->actor, &function_name, &num_params, ¶ms, &soap_version, &soap_headers TSRMLS_CC);
+	xmlFreeDoc(doc_request);
+
+	if (service->type == SOAP_CLASS) {
+		soap_obj = NULL;
 #if HAVE_PHP_SESSION
-			/* If persistent then set soap_obj from from the previous created session (if available) */
-			if (service->soap_class.persistance == SOAP_PERSISTENCE_SESSION) {
-				zval **tmp_soap;
+		/* If persistent then set soap_obj from from the previous created session (if available) */
+		if (service->soap_class.persistance == SOAP_PERSISTENCE_SESSION) {
+			zval **tmp_soap;
 
-				if (PS(session_status) != php_session_active &&
-				    PS(session_status) != php_session_disabled) {
-					php_session_start(TSRMLS_C);
-				}
+			if (PS(session_status) != php_session_active &&
+			    PS(session_status) != php_session_disabled) {
+				php_session_start(TSRMLS_C);
+			}
 
-				/* Find the soap object and assign */
-				if (zend_hash_find(Z_ARRVAL_P(PS(http_session_vars)), "_bogus_session_name", sizeof("_bogus_session_name"), (void **) &tmp_soap) == SUCCESS) {
-					soap_obj = *tmp_soap;
-				}
+			/* Find the soap object and assign */
+			if (zend_hash_find(Z_ARRVAL_P(PS(http_session_vars)), "_bogus_session_name", sizeof("_bogus_session_name"), (void **) &tmp_soap) == SUCCESS) {
+				soap_obj = *tmp_soap;
 			}
+		}
 #endif
-			/* If new session or something wierd happned */
-			if (soap_obj == NULL) {
-				zval *tmp_soap;
-				char *class_name;
-				int class_name_len;
-
-				MAKE_STD_ZVAL(tmp_soap);
-				object_init_ex(tmp_soap, service->soap_class.ce);
-
-				/* Call constructor */
-				class_name_len = strlen(service->soap_class.ce->name);
-				class_name = emalloc(class_name_len+1);
-				memcpy(class_name, service->soap_class.ce->name,class_name_len+1);
-				if (zend_hash_exists(&Z_OBJCE_P(tmp_soap)->function_table, php_strtolower(class_name, class_name_len), class_name_len+1)) {
-					zval c_ret, constructor;
-
-					INIT_ZVAL(c_ret);
-					INIT_ZVAL(constructor);
-
-					ZVAL_STRING(&constructor, service->soap_class.ce->name, 1);
-					if (call_user_function(NULL, &tmp_soap, &constructor, &c_ret, service->soap_class.argc, service->soap_class.argv TSRMLS_CC) == FAILURE) {
-						php_error(E_ERROR, "Error calling constructor");
-					}
-					zval_dtor(&constructor);
-					zval_dtor(&c_ret);
+		/* If new session or something wierd happned */
+		if (soap_obj == NULL) {
+			zval *tmp_soap;
+			char *class_name;
+			int class_name_len;
+
+			MAKE_STD_ZVAL(tmp_soap);
+			object_init_ex(tmp_soap, service->soap_class.ce);
+
+			/* Call constructor */
+			class_name_len = strlen(service->soap_class.ce->name);
+			class_name = emalloc(class_name_len+1);
+			memcpy(class_name, service->soap_class.ce->name,class_name_len+1);
+			if (zend_hash_exists(&Z_OBJCE_P(tmp_soap)->function_table, php_strtolower(class_name, class_name_len), class_name_len+1)) {
+				zval c_ret, constructor;
+
+				INIT_ZVAL(c_ret);
+				INIT_ZVAL(constructor);
+
+				ZVAL_STRING(&constructor, service->soap_class.ce->name, 1);
+				if (call_user_function(NULL, &tmp_soap, &constructor, &c_ret, service->soap_class.argc, service->soap_class.argv TSRMLS_CC) == FAILURE) {
+					php_error(E_ERROR, "Error calling constructor");
 				}
-				efree(class_name);
+				zval_dtor(&constructor);
+				zval_dtor(&c_ret);
+			}
+			efree(class_name);
 #if HAVE_PHP_SESSION
-				/* If session then update session hash with new object */
-				if (service->soap_class.persistance == SOAP_PERSISTENCE_SESSION) {
-					zval **tmp_soap_pp;
-					if (zend_hash_update(Z_ARRVAL_P(PS(http_session_vars)), "_bogus_session_name", sizeof("_bogus_session_name"), &tmp_soap, sizeof(zval *), (void **)&tmp_soap_pp) == SUCCESS) {
-						soap_obj = *tmp_soap_pp;
-					}
-				} else {
-					soap_obj = tmp_soap;
+			/* If session then update session hash with new object */
+			if (service->soap_class.persistance == SOAP_PERSISTENCE_SESSION) {
+				zval **tmp_soap_pp;
+				if (zend_hash_update(Z_ARRVAL_P(PS(http_session_vars)), "_bogus_session_name", sizeof("_bogus_session_name"), &tmp_soap, sizeof(zval *), (void **)&tmp_soap_pp) == SUCCESS) {
+					soap_obj = *tmp_soap_pp;
 				}
-#else
+			} else {
 				soap_obj = tmp_soap;
+			}
+#else
+			soap_obj = tmp_soap;
 #endif
 
-			}
+		}
 /* 			function_table = &(soap_obj->value.obj.ce->function_table);*/
- 			function_table = &((Z_OBJCE_P(soap_obj))->function_table);
+		function_table = &((Z_OBJCE_P(soap_obj))->function_table);
+	} else {
+		if (service->soap_functions.functions_all == TRUE) {
+			function_table = EG(function_table);
 		} else {
-			if (service->soap_functions.functions_all == TRUE) {
-				function_table = EG(function_table);
-			} else {
-				function_table = service->soap_functions.ft;
-			}
+			function_table = service->soap_functions.ft;
 		}
+	}
 
-		doc_return = NULL;
+	doc_return = NULL;
 
-		/* Process soap headers */
-		if (soap_headers != NULL) {
-			soapHeader *header = soap_headers;
-			while (header != NULL) {
-				soapHeader *h = header;
+	/* Process soap headers */
+	if (soap_headers != NULL) {
+		soapHeader *header = soap_headers;
+		while (header != NULL) {
+			soapHeader *h = header;
 
-				header = header->next;
-				if (h->mustUnderstand && service->sdl && !h->function && !h->hdr) {
-					soap_server_fault("MustUnderstand","Header not understood", NULL, NULL TSRMLS_CC);
-				}
+			header = header->next;
+			if (h->mustUnderstand && service->sdl && !h->function && !h->hdr) {
+				soap_server_fault("MustUnderstand","Header not understood", NULL, NULL TSRMLS_CC);
+			}
 
-				fn_name = estrndup(Z_STRVAL(h->function_name),Z_STRLEN(h->function_name));
-				if (zend_hash_exists(function_table, php_strtolower(fn_name, Z_STRLEN(h->function_name)), Z_STRLEN(h->function_name) + 1)) {
- 					if (service->type == SOAP_CLASS) {
-						call_status = call_user_function(NULL, &soap_obj, &h->function_name, &h->retval, h->num_params, h->parameters TSRMLS_CC);
-					} else {
-						call_status = call_user_function(EG(function_table), NULL, &h->function_name, &h->retval, h->num_params, h->parameters TSRMLS_CC);
-					}
-					if (call_status != SUCCESS) {
-						php_error(E_ERROR, "Function '%s' call failed", Z_STRVAL(function_name));
-					}
-				} else if (h->mustUnderstand) {
-					soap_server_fault("MustUnderstand","Header not understood", NULL, NULL TSRMLS_CC);
+			fn_name = estrndup(Z_STRVAL(h->function_name),Z_STRLEN(h->function_name));
+			if (zend_hash_exists(function_table, php_strtolower(fn_name, Z_STRLEN(h->function_name)), Z_STRLEN(h->function_name) + 1)) {
+				if (service->type == SOAP_CLASS) {
+					call_status = call_user_function(NULL, &soap_obj, &h->function_name, &h->retval, h->num_params, h->parameters TSRMLS_CC);
+				} else {
+					call_status = call_user_function(EG(function_table), NULL, &h->function_name, &h->retval, h->num_params, h->parameters TSRMLS_CC);
+				}
+				if (call_status != SUCCESS) {
+					php_error(E_ERROR, "Function '%s' call failed", Z_STRVAL(function_name));
 				}
-				efree(fn_name);
+			} else if (h->mustUnderstand) {
+				soap_server_fault("MustUnderstand","Header not understood", NULL, NULL TSRMLS_CC);
 			}
+			efree(fn_name);
 		}
+	}
 
-		fn_name = estrndup(Z_STRVAL(function_name),Z_STRLEN(function_name));
-		if (zend_hash_exists(function_table, php_strtolower(fn_name, Z_STRLEN(function_name)), Z_STRLEN(function_name) + 1)) {
- 			if (service->type == SOAP_CLASS) {
-				call_status = call_user_function(NULL, &soap_obj, &function_name, &retval, num_params, params TSRMLS_CC);
-				if (service->soap_class.persistance != SOAP_PERSISTENCE_SESSION) {
-					zval_ptr_dtor(&soap_obj);
-				}
-			} else {
-				call_status = call_user_function(EG(function_table), NULL, &function_name, &retval, num_params, params TSRMLS_CC);
+	fn_name = estrndup(Z_STRVAL(function_name),Z_STRLEN(function_name));
+	if (zend_hash_exists(function_table, php_strtolower(fn_name, Z_STRLEN(function_name)), Z_STRLEN(function_name) + 1)) {
+		if (service->type == SOAP_CLASS) {
+			call_status = call_user_function(NULL, &soap_obj, &function_name, &retval, num_params, params TSRMLS_CC);
+			if (service->soap_class.persistance != SOAP_PERSISTENCE_SESSION) {
+				zval_ptr_dtor(&soap_obj);
 			}
 		} else {
-			php_error(E_ERROR, "Function '%s' doesn't exist", Z_STRVAL(function_name));
+			call_status = call_user_function(EG(function_table), NULL, &function_name, &retval, num_params, params TSRMLS_CC);
 		}
-		efree(fn_name);
+	} else {
+		php_error(E_ERROR, "Function '%s' doesn't exist", Z_STRVAL(function_name));
+	}
+	efree(fn_name);
 
-		if (call_status == SUCCESS) {
-			char *response_name;
+	if (call_status == SUCCESS) {
+		char *response_name;
 
-			if (function && function->responseName) {
-				response_name = estrdup(function->responseName);
-			} else {
-				response_name = emalloc(Z_STRLEN(function_name) + sizeof("Response"));
-				memcpy(response_name,Z_STRVAL(function_name),Z_STRLEN(function_name));
-				memcpy(response_name+Z_STRLEN(function_name),"Response",sizeof("Response"));
-			}
-			SOAP_GLOBAL(overrides) = service->mapping;
-			doc_return = seralize_response_call(function, response_name, service->uri, &retval, soap_headers, soap_version TSRMLS_CC);
-			SOAP_GLOBAL(overrides) = NULL;
-			efree(response_name);
+		if (function && function->responseName) {
+			response_name = estrdup(function->responseName);
 		} else {
-			php_error(E_ERROR, "Function '%s' call failed", Z_STRVAL(function_name));
-		}
+			response_name = emalloc(Z_STRLEN(function_name) + sizeof("Response"));
+			memcpy(response_name,Z_STRVAL(function_name),Z_STRLEN(function_name));
+			memcpy(response_name+Z_STRLEN(function_name),"Response",sizeof("Response"));
+		}
+		SOAP_GLOBAL(overrides) = service->mapping;
+		doc_return = seralize_response_call(function, response_name, service->uri, &retval, soap_headers, soap_version TSRMLS_CC);
+		SOAP_GLOBAL(overrides) = NULL;
+		efree(response_name);
+	} else {
+		php_error(E_ERROR, "Function '%s' call failed", Z_STRVAL(function_name));
+	}
 
-		/* Free soap headers */
-		while (soap_headers != NULL) {
-			soapHeader *h = soap_headers;
-			int i;
+	/* Free soap headers */
+	while (soap_headers != NULL) {
+		soapHeader *h = soap_headers;
+		int i;
 
-			soap_headers = soap_headers->next;
-			i = h->num_params;
-			while (i > 0) {
-				zval_ptr_dtor(&h->parameters[--i]);
-			}
-			efree(h->parameters);
-			zval_dtor(&h->function_name);
-			zval_dtor(&h->retval);
-			efree(h);
+		soap_headers = soap_headers->next;
+		i = h->num_params;
+		while (i > 0) {
+			zval_ptr_dtor(&h->parameters[--i]);
 		}
+		efree(h->parameters);
+		zval_dtor(&h->function_name);
+		zval_dtor(&h->retval);
+		efree(h);
+	}
 
-		SOAP_GLOBAL(soap_version) = old_soap_version;
-		SOAP_GLOBAL(sdl) = old_sdl;
+	SOAP_GLOBAL(soap_version) = old_soap_version;
+	SOAP_GLOBAL(sdl) = old_sdl;
 
-		/* Flush buffer */
-		php_end_ob_buffer(0, 0 TSRMLS_CC);
+	/* Flush buffer */
+	php_end_ob_buffer(0, 0 TSRMLS_CC);
 
-		/* xmlDocDumpMemoryEnc(doc_return, &buf, &size, XML_CHAR_ENCODING_UTF8); */
-		xmlDocDumpMemory(doc_return, &buf, &size);
+	/* xmlDocDumpMemoryEnc(doc_return, &buf, &size, XML_CHAR_ENCODING_UTF8); */
+	xmlDocDumpMemory(doc_return, &buf, &size);
 
-		if (size == 0) {
-			php_error(E_ERROR, "Dump memory failed");
-		}
+	if (size == 0) {
+		php_error(E_ERROR, "Dump memory failed");
+	}
 
-		sprintf(cont_len, "Content-Length: %d", size);
-		sapi_add_header(cont_len, strlen(cont_len) + 1, 1);
-		if (soap_version == SOAP_1_2) {
-			sapi_add_header("Content-Type: application/soap+xml; charset=\"utf-8\"", sizeof("Content-Type: application/soap+xml; charset=\"utf-8\""), 1);
-		} else {
-			sapi_add_header("Content-Type: text/xml; charset=\"utf-8\"", sizeof("Content-Type: text/xml; charset=\"utf-8\""), 1);
-		}
+	sprintf(cont_len, "Content-Length: %d", size);
+	sapi_add_header(cont_len, strlen(cont_len) + 1, 1);
+	if (soap_version == SOAP_1_2) {
+		sapi_add_header("Content-Type: application/soap+xml; charset=\"utf-8\"", sizeof("Content-Type: application/soap+xml; charset=\"utf-8\""), 1);
+	} else {
+		sapi_add_header("Content-Type: text/xml; charset=\"utf-8\"", sizeof("Content-Type: text/xml; charset=\"utf-8\""), 1);
+	}
 
-		/* Free Memory */
-		if (num_params > 0) {
-			for (i = 0; i < num_params;i++) {
-				zval_ptr_dtor(¶ms[i]);
-			}
-			efree(params);
+	/* Free Memory */
+	if (num_params > 0) {
+		for (i = 0; i < num_params;i++) {
+			zval_ptr_dtor(¶ms[i]);
 		}
+		efree(params);
+	}
 
-		zval_dtor(&function_name);
-		xmlFreeDoc(doc_return);
-
-		php_write(buf, size TSRMLS_CC);
-		xmlFree(buf);
-	} else {
-		if (!zend_ini_long("always_populate_raw_post_data", sizeof("always_populate_raw_post_data"), 0)) {
-			php_error(E_ERROR, "PHP-SOAP requires 'always_populate_raw_post_data' to be on please check your php.ini file");
-		}
+	zval_dtor(&function_name);
+	xmlFreeDoc(doc_return);
 
-		php_error(E_ERROR, "Can't find HTTP_RAW_POST_DATA");
-	}
+	php_write(buf, size TSRMLS_CC);
+	xmlFree(buf);
 
 	zval_dtor(&retval);
 
diff --git a/ext/soap/tests/server015.phpt b/ext/soap/tests/server015.phpt
new file mode 100644
index 0000000000..ea538e84eb
--- /dev/null
+++ b/ext/soap/tests/server015.phpt
@@ -0,0 +1,33 @@
+--TEST--
+SOAP Server 15: passing request to handle()
+--SKIPIF--
+
+--FILE--
+"http://testuri.org"));
+$server->addfunction("test");
+
+$envelope = <<
+
+  
+    
+  
+
+EOF;
+$server->handle($envelope);
+echo "ok\n";
+?>
+--EXPECT--
+
+Hello World
+ok
-- 
2.40.0