ID: 44393 Comment by: Richard dot Krehbiel at gmail dot com Reported By: richard dot krehbiel at gmail dot com Status: Open Bug Type: Feature/Change Request Operating System: Windows PHP Version: 5.2.5 New Comment:
This DOES work with compression (zlib.output_compression=true). It alleviates the "little chunks" issue too, because the compression does some buffering. Previous Comments: ------------------------------------------------------------------------ [2008-03-13 12:09:13] Richard dot Krehbiel at gmail dot com Bug: it doesn't properly serve HTTP 1.0 clients (wget), which don't support chunked transfers. This patch introduces a thread-local variable to indicate whether the transfer is chunked or not, and logic to detect an HTTP/1.0 transfer. --- /mnt/rich3/c/php-5.2.5/sapi/isapi/php5isapi.c 2007-02-23 17:08:30.000000000 -0500 +++ /mnt/rich3/c/buildphp/php-5.2.5/sapi/isapi/php5isapi.c 2008-03-12 14:28:34.000000000 -0400 @@ -143,6 +143,19 @@ NULL }; +typedef struct +{ + int chunked; +} PHP_STREAM_INFO; + +ts_rsrc_id tls_php_stream_info; + +static void php_stream_info_ctor(void *vsi, void ***foo) { + memset(vsi, 0, sizeof(PHP_STREAM_INFO)); +} + +static void php_stream_info_dtor(void *vsi, void ***foo) { +} static void php_info_isapi(ZEND_MODULE_INFO_FUNC_ARGS) { @@ -206,11 +219,36 @@ { DWORD num_bytes = str_length; LPEXTENSION_CONTROL_BLOCK ecb; - + // For chunked write + char chunksize[16]; + uint chunksizelen; + PHP_STREAM_INFO *phpinfo; + ecb = (LPEXTENSION_CONTROL_BLOCK) SG(server_context); - if (ecb->WriteClient(ecb->ConnID, (char *) str, &num_bytes, HSE_IO_SYNC) == FALSE) { - php_handle_aborted_connection(); + + phpinfo = (PHP_STREAM_INFO*)ts_resource(tls_php_stream_info); + + if(phpinfo->chunked) { + if(str_length > 0) { + uint two = 2; + _snprintf(chunksize, sizeof(chunksize), "%lX\r\n", str_length); + chunksizelen = strlen(chunksize); + if (ecb->WriteClient(ecb->ConnID, chunksize, &chunksizelen, HSE_IO_SYNC) == FALSE) { + php_handle_aborted_connection(); + } + if (ecb->WriteClient(ecb->ConnID, (char *) str, &num_bytes, HSE_IO_SYNC) == FALSE) { + php_handle_aborted_connection(); + } + if (ecb->WriteClient(ecb->ConnID, "\r\n", &two, HSE_IO_SYNC) == FALSE) { + php_handle_aborted_connection(); + } + } + } else { + if (ecb->WriteClient(ecb->ConnID, (char *) str, &num_bytes, HSE_IO_SYNC) == FALSE) { + php_handle_aborted_connection(); + } } + return num_bytes; } @@ -247,6 +285,7 @@ HSE_SEND_HEADER_EX_INFO header_info; sapi_header_struct default_content_type; char *status_buf = NULL; + PHP_STREAM_INFO *phpinfo = (PHP_STREAM_INFO*)ts_resource(tls_php_stream_info); /* Obtain headers length */ if (SG(sapi_headers).send_default_content_type) { @@ -256,16 +295,27 @@ zend_llist_apply_with_argument(&SG(sapi_headers).headers, (llist_apply_with_arg_func_t) accumulate_header_length, (void *) &total_length TSRMLS_CC); /* Generate headers */ - combined_headers = (char *) emalloc(total_length+1); + combined_headers = (char *) emalloc(total_length+64); combined_headers_ptr = combined_headers; if (SG(sapi_headers).send_default_content_type) { concat_header(&default_content_type, (void *) &combined_headers_ptr TSRMLS_CC); sapi_free_header(&default_content_type); /* we no longer need it */ } zend_llist_apply_with_argument(&SG(sapi_headers).headers, (llist_apply_with_arg_func_t) concat_header, (void *) &combined_headers_ptr TSRMLS_CC); - *combined_headers_ptr++ = '\r'; - *combined_headers_ptr++ = '\n'; - *combined_headers_ptr = 0; + + // HTTP/1.0 requestors don't get "chunked" replies + { + char protocol[64]; + DWORD protosize = sizeof(protocol); + + lpECB->GetServerVariable(lpECB->ConnID, "SERVER_PROTOCOL", protocol, &protosize); + phpinfo->chunked = (strcmp(protocol, "HTTP/1.0") != 0); + if(phpinfo->chunked) { + strcpy(combined_headers_ptr, "Transfer-Encoding: chunked\r\n\r\n"); + } else { + strcpy(combined_headers_ptr, "\r\n"); + } + } switch (SG(sapi_headers).http_response_code) { case 200: @@ -300,7 +350,7 @@ header_info.cchStatus = strlen(header_info.pszStatus); header_info.pszHeader = combined_headers; header_info.cchHeader = total_length; - header_info.fKeepConn = FALSE; + header_info.fKeepConn = phpinfo->chunked; lpECB->dwHttpStatusCode = SG(sapi_headers).http_response_code; lpECB->ServerSupportFunction(lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER_EX, &header_info, NULL, NULL); @@ -836,8 +886,11 @@ #ifdef PHP_ENABLE_SEH LPEXCEPTION_POINTERS e; #endif + PHP_STREAM_INFO *phpinfo; TSRMLS_FETCH(); + phpinfo = (PHP_STREAM_INFO*)ts_resource(tls_php_stream_info); + zend_first_try { #ifdef PHP_ENABLE_SEH __try { @@ -928,6 +981,15 @@ return HSE_STATUS_ERROR; } zend_end_try(); + // Finish a chunked transmission, send 0 length EOF chunk and trailing headers (none) + + if(phpinfo->chunked) { + uint five = 5; + if (lpECB->WriteClient(lpECB->ConnID, "0\r\n\r\n", &five, HSE_IO_SYNC) == FALSE) { + php_handle_aborted_connection(); + } + } + return HSE_STATUS_SUCCESS; } @@ -946,6 +1008,8 @@ if (isapi_sapi_module.startup) { isapi_sapi_module.startup(&sapi_module); } + ts_allocate_id(&tls_php_stream_info, sizeof(PHP_STREAM_INFO), + php_stream_info_ctor, php_stream_info_dtor); break; case DLL_THREAD_ATTACH: break; ------------------------------------------------------------------------ [2008-03-10 14:53:18] richard dot krehbiel at gmail dot com Description: ------------ The ISAPI module for PHP does not support "Keep-Alive" connections. I have a modified sapi/isapi/php5isapi.c that adds "Transfer-Encoding: chunked" support, which allows keep-alive to work. It works but needs polish*. Interested? *It needs to detect the presence of a "Content-Length" header and disable "chunked"; it needs buffering (every little 1-char echo becomes a chunk); I think it doesn't work with dynamic compression. --- /mnt/rich3/c/php-5.2.5/sapi/isapi/php5isapi.c 2007-02-23 17:08:30.000000000 -0500 +++ /mnt/rich3/c/buildphp/php-5.2.5/sapi/isapi/php5isapi.c 2008-03-10 10:46:17.317923500 -0400 @@ -206,10 +206,25 @@ { DWORD num_bytes = str_length; LPEXTENSION_CONTROL_BLOCK ecb; + // Chunked write... + char chunksize[16]; + uint chunksizelen; ecb = (LPEXTENSION_CONTROL_BLOCK) SG(server_context); - if (ecb->WriteClient(ecb->ConnID, (char *) str, &num_bytes, HSE_IO_SYNC) == FALSE) { - php_handle_aborted_connection(); + + if(str_length > 0) { + uint two = 2; + _snprintf(chunksize, sizeof(chunksize), "%lX\r\n", str_length); + chunksizelen = strlen(chunksize); + if (ecb->WriteClient(ecb->ConnID, chunksize, &chunksizelen, HSE_IO_SYNC) == FALSE) { + php_handle_aborted_connection(); + } + if (ecb->WriteClient(ecb->ConnID, (char *) str, &num_bytes, HSE_IO_SYNC) == FALSE) { + php_handle_aborted_connection(); + } + if (ecb->WriteClient(ecb->ConnID, "\r\n", &two, HSE_IO_SYNC) == FALSE) { + php_handle_aborted_connection(); + } } return num_bytes; } @@ -256,16 +271,14 @@ zend_llist_apply_with_argument(&SG(sapi_headers).headers, (llist_apply_with_arg_func_t) accumulate_header_length, (void *) &total_length TSRMLS_CC); /* Generate headers */ - combined_headers = (char *) emalloc(total_length+1); + combined_headers = (char *) emalloc(total_length+64); combined_headers_ptr = combined_headers; if (SG(sapi_headers).send_default_content_type) { concat_header(&default_content_type, (void *) &combined_headers_ptr TSRMLS_CC); sapi_free_header(&default_content_type); /* we no longer need it */ } zend_llist_apply_with_argument(&SG(sapi_headers).headers, (llist_apply_with_arg_func_t) concat_header, (void *) &combined_headers_ptr TSRMLS_CC); - *combined_headers_ptr++ = '\r'; - *combined_headers_ptr++ = '\n'; - *combined_headers_ptr = 0; + strcpy(combined_headers_ptr, "Transfer-Encoding: chunked\r\n\r\n"); switch (SG(sapi_headers).http_response_code) { case 200: @@ -300,7 +313,7 @@ header_info.cchStatus = strlen(header_info.pszStatus); header_info.pszHeader = combined_headers; header_info.cchHeader = total_length; - header_info.fKeepConn = FALSE; + header_info.fKeepConn = TRUE; lpECB->dwHttpStatusCode = SG(sapi_headers).http_response_code; lpECB->ServerSupportFunction(lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER_EX, &header_info, NULL, NULL); @@ -928,6 +941,15 @@ return HSE_STATUS_ERROR; } zend_end_try(); + // Finish a chunked transmission, send 0 length EOF chunk and trailing headers (none) + + { + uint five = 5; + if (lpECB->WriteClient(lpECB->ConnID, "0\r\n\r\n", &five, HSE_IO_SYNC) == FALSE) { + php_handle_aborted_connection(); + } + } + return HSE_STATUS_SUCCESS; } ------------------------------------------------------------------------ -- Edit this bug report at http://bugs.php.net/?id=44393&edit=1