Well, thing is that Keep-Alive and Transfer-Encoding go hand-in-hand when you add compression to the HTTP response. With something like gzip compression the web server doesn't know the the Content-Length of the stream so the *only* way to achieve Keep-Alive without a Content-Length (AFAIK) is to do chunked Transfer-Encoding.
Our patch does just as I describe above, allowing for a Keep-Alive session to persist with compression by add a chunked Transfer-Encoding. This make a HUGE performance increase over highly latent lines (China/India -> United States). Attached and below. Let me know if this is considered large and I should move this to a bugzilla issue. Only took us about 30 minutes to apply it. Again, this is against 1.2.21! --- native/common/jk_util.c 2007/03/19 12:35:35 +++ native/common/jk_util.c 2007/03/20 14:44:58 @@ -1706,6 +1706,9 @@ s->route = NULL; s->retries = JK_RETRIES; s->add_log_items = NULL; + + s->flush = NULL; + s->flush_packets = JK_FALSE; } #ifdef _MT_CODE_PTHREAD --- native/iis/jk_isapi_plugin.c 2007/03/19 12:35:35 +++ native/iis/jk_isapi_plugin.c 2007/03/20 14:44:58 @@ -24,6 +24,9 @@ * Version: $Revision: 511888 $ * ************************************************************************ ***/ +// Updated with patch from +// http://mail-archives.apache.org/mod_mbox/tomcat-dev/200506.mbox/raw/%3C4 [EMAIL PROTECTED]/2 + // This define is needed to include wincrypt,h, needed to get client certificates #define _WIN32_WINNT 0x0400 @@ -89,6 +92,17 @@ #define URI_SELECT_PARSED_VERB ("parsed") #define URI_SELECT_UNPARSED_VERB ("unparsed") #define URI_SELECT_ESCAPED_VERB ("escaped") + +#define ENABLE_CHUNKED_ENCODING_TAG ("enable_chunked_encoding") + +/* Headers used in negotiating chunked encoding */ +#define TRANSFER_ENCODING_HEADER_COMPLETE ("Transfer-Encoding: chunked") +#define TRANSFER_ENCODING_HEADER_NAME ("Transfer-Encoding") +#define CONTENT_LENGTH_HEADER_NAME ("Content-Length") +#define CONNECTION_HEADER_NAME ("Connection") +#define CONNECTION_CLOSE_VALUE ("Close") + + #define URI_REWRITE_TAG ("rewrite_rule_file") #define SHM_SIZE_TAG ("shm_size") #define WORKER_MOUNT_RELOAD_TAG ("worker_mount_reload") @@ -146,6 +160,11 @@ (place) = def; \ } } while(0) +/* HTTP protocol CRLF */ +static char CRLF[3] = { (char)13, (char)10, '\0' }; +/* Whether chunked encoding has been enabled in the settings */ +static int chunked_encoding_enabled = JK_FALSE; + static char ini_file_name[MAX_PATH]; static int using_ini_file = JK_FALSE; static int is_inited = JK_FALSE; @@ -189,6 +208,8 @@ int request_started; unsigned int bytes_read_so_far; LPEXTENSION_CONTROL_BLOCK lpEcb; + + int chunk_content; /* Whether we're responding with Transfer-Encoding: chunked content */ }; typedef struct isapi_log_data_t isapi_log_data_t; @@ -209,6 +230,8 @@ static int JK_METHOD write(jk_ws_service_t *s, const void *b, unsigned int l); +static int JK_METHOD flush_response(jk_ws_service_t *s); + static int init_ws_service(isapi_private_data_t * private_data, jk_ws_service_t *s, char **worker_name); @@ -533,11 +556,15 @@ if (s && s->ws_private) { int rv = JK_TRUE; isapi_private_data_t *p = s->ws_private; + //int keep_alive = JK_FALSE; /* Whether the downstream or us can supply content length */ + if (!p->request_started) { HSE_SEND_HEADER_EX_INFO hi; + HSE_SEND_HEADER_EX_INFO send_header_ex_info; char *status_str; char *headers_str = NULL; - BOOL keep_alive = FALSE; + //BOOL keep_alive = FALSE; + int keep_alive = JK_FALSE; /* Whether the downstream or us can supply content length */ p->request_started = JK_TRUE; /* @@ -555,32 +582,113 @@ * Create response headers string */ if (num_of_headers) { + int chunked_ok = JK_FALSE; /* Whether the downstream response allows chunking */ + int http11_request = JK_FALSE; /* Whether the client is HTTP/1.1 */ + size_t i, len_of_headers; + + /* Check if we've got an HTTP/1.1 response */ + jk_log(logger, JK_LOG_DEBUG, + "jk_ws_service_t::start_response, Request uses protocol %s\n", s->protocol); + + if (strcasecmp(s->protocol, "HTTP/1.1")==0) { + http11_request = JK_TRUE; + chunked_ok = JK_TRUE; /* Chunking only on HTTP/1.1 */ + } + for (i = 0, len_of_headers = 0; i < num_of_headers; i++) { len_of_headers += strlen(header_names[i]); len_of_headers += strlen(header_values[i]); len_of_headers += 4; /* extra for colon, space and crlf */ } - len_of_headers += 3; /* crlf and terminating null char */ + /* Provide room in the buffer for the Transfer-Encoding header if we use it. */ + len_of_headers += strlen(TRANSFER_ENCODING_HEADER_COMPLETE) + 2; + + len_of_headers += 3; /* crlf and terminating null char */ headers_str = (char *)malloc(len_of_headers); headers_str[0] = '\0'; for (i = 0; i < num_of_headers; i++) { + /* Check the downstream response to see whether + it's appropriate the chunk the response content + and whether it supports keeping the connection open */ + if(strcasecmp(TRANSFER_ENCODING_HEADER_NAME, header_names[i])==0) { + keep_alive = http11_request; + chunked_ok = JK_FALSE; + } + else if(strcasecmp(CONTENT_LENGTH_HEADER_NAME, header_names[i])==0) { + keep_alive = http11_request; + chunked_ok = JK_FALSE; + } + else if((strcasecmp(CONNECTION_HEADER_NAME, header_names[i])==0) + && (strcasecmp(CONNECTION_CLOSE_VALUE, header_values[i])==0)) { + keep_alive = JK_FALSE; + chunked_ok = JK_FALSE; + } + StringCbCat(headers_str, len_of_headers, header_names[i]); StringCbCat(headers_str, len_of_headers, ": "); StringCbCat(headers_str, len_of_headers, header_values[i]); - StringCbCat(headers_str, len_of_headers, crlf); + StringCbCat(headers_str, len_of_headers, CRLF); + } + //StringCbCat(headers_str, len_of_headers, crlf); + + /* Check if we can send chunked content */ + if (chunked_encoding_enabled && chunked_ok) { + jk_log(logger, JK_LOG_DEBUG, + "jk_ws_service_t::start_response, using Transfer-Encoding: chunked\n"); + + /** We will supply the transfer-encoding to allow IIS to keep the connection open */ + keep_alive = JK_TRUE; + + p->chunk_content = JK_TRUE; + /* Indicate to the client that the content will be chunked + - We've already reserved space for this */ + StringCbCat(headers_str, len_of_headers, TRANSFER_ENCODING_HEADER_COMPLETE); + StringCbCat(headers_str, len_of_headers, CRLF); + } + else { + p->chunk_content = JK_FALSE; } - StringCbCat(headers_str, len_of_headers, crlf); - hi.pszHeader = headers_str; - hi.cchHeader = (DWORD)strlen(headers_str); + + StringCbCat(headers_str, len_of_headers, CRLF); + //strcat(headers_str, CRLF); + + //hi.pszHeader = headers_str; + //hi.cchHeader = (DWORD)strlen(headers_str); } else { - hi.pszHeader = crlf; + hi.pszHeader = CRLF; hi.cchHeader = 2; } - hi.fKeepConn = keep_alive; + + /** Fill in the response */ + send_header_ex_info.pszStatus = status_str; + send_header_ex_info.pszHeader = headers_str; + send_header_ex_info.cchStatus = strlen(status_str); + send_header_ex_info.cchHeader = strlen(headers_str); + + /* + * Using the extended form of the API means we have to get this right, + * i.e. IIS won't keep connections open if there's a Content-Length and close them if there isn't. + */ + jk_log(logger, JK_LOG_DEBUG, + "jk_ws_service_t::start_response, keep_alive = %d\n", keep_alive); + + send_header_ex_info.fKeepConn = keep_alive; + + if (!p->lpEcb->ServerSupportFunction(p->lpEcb->ConnID, + HSE_REQ_SEND_RESPONSE_HEADER_EX, + &send_header_ex_info, + NULL, + NULL)) { + jk_log(logger, JK_LOG_ERROR, + "HSE_REQ_SEND_RESPONSE_HEADER failed"); + rv = JK_FALSE; + } + + /*hi.fKeepConn = keep_alive; if (!p->lpEcb->ServerSupportFunction(p->lpEcb->ConnID, HSE_REQ_SEND_RESPONSE_HEADER_EX, &hi, @@ -588,7 +696,8 @@ jk_log(logger, JK_LOG_ERROR, "HSE_REQ_SEND_RESPONSE_HEADER_EX failed"); rv = JK_FALSE; - } + }*/ + if (headers_str) free(headers_str); if (status_str) @@ -658,6 +767,39 @@ return JK_FALSE; } +/* +* Writes a buffer to the ISAPI response. +*/ +static int isapi_write_client(isapi_private_data_t *p, const char *buf, unsigned write_length) +{ + unsigned written = 0; + DWORD try_to_write = 0; + + JK_TRACE_ENTER(logger); + + while (written < write_length) { + try_to_write = (write_length - written); + + if (!p->lpEcb->WriteClient(p->lpEcb->ConnID, + buf + written, &try_to_write, 0)) { + jk_log(logger, JK_LOG_ERROR, + "WriteClient failed with %08x", GetLastError()); + JK_TRACE_EXIT(logger); + return JK_FALSE; + } + written += try_to_write; + } + JK_TRACE_EXIT(logger); + return JK_TRUE; +} + +/* +* Write content to the response. +* If chunked encoding has been enabled and the client supports it +*(and it's appropriate for the response), then this will write a +* single "Transfer-Encoding: chunked" chunk +* +*/ static int JK_METHOD write(jk_ws_service_t *s, const void *b, unsigned int l) { JK_TRACE_ENTER(logger); @@ -666,23 +808,56 @@ isapi_private_data_t *p = s->ws_private; if (l) { - unsigned int written = 0; + //unsigned int written = 0; char *buf = (char *)b; if (!p->request_started) { start_response(s, 200, NULL, NULL, NULL, 0); } - - while (written < l) { + /*while (written < l) { DWORD try_to_write = l - written; if (!p->lpEcb->WriteClient(p->lpEcb->ConnID, buf + written, &try_to_write, 0)) { jk_log(logger, JK_LOG_ERROR, "WriteClient failed with %08x", GetLastError()); JK_TRACE_EXIT(logger); + return JK_FALSE;*/ + + /* Chunk header */ + if (p->chunk_content) { + char chunk_header[sizeof(unsigned)*2+3]; /* Hex of chunk length + CRLF + term. */ + jk_log(logger, JK_LOG_DEBUG, + "Using chunked encoding - writing chunk header\n"); + + /* Construct chunk header : HEX CRLF*/ + sprintf(chunk_header, "%X%s", l, CRLF); + + if (!isapi_write_client(p, chunk_header, strlen(chunk_header))) { + jk_log(logger, JK_LOG_ERROR, + "WriteClient for chunk header failed\n"); + JK_TRACE_EXIT(logger); return JK_FALSE; } - written += try_to_write; + } + + /* Write chunk body */ + if (!isapi_write_client(p, buf, l)) { + jk_log(logger, JK_LOG_ERROR, + "WriteClient for body chunk failed\n"); + JK_TRACE_EXIT(logger); + return JK_FALSE; + } + + /* Write chunk trailer */ + if (p->chunk_content) { + jk_log(logger, JK_LOG_DEBUG, + "Using chunked encoding - writing chunk trailer\n"); + + if (!isapi_write_client(p, CRLF, strlen(CRLF))) { + jk_log(logger, JK_LOG_ERROR, + "WriteClient for chunk trailer failed\n"); + } + // written += try_to_write; } } @@ -696,6 +871,37 @@ return JK_FALSE; } + +/** +* In the case of a Transfer-Encoding: chunked response, this will write the terminator chunk. +*/ +static int JK_METHOD flush_response(jk_ws_service_t *s) +{ + JK_TRACE_ENTER(logger); + + if (s && s->ws_private) { + isapi_private_data_t *p = s->ws_private; + + /* Write last chunk + terminator */ + if (p->chunk_content) { + static char CHUNKED_ENCODING_TRAILER[6] = { '0', (char)13, (char)10, (char)13, (char)10, '\0' }; + + jk_log(logger, JK_LOG_DEBUG, + "Terminating chunk encoded response.\n"); + + if (!isapi_write_client(p, CHUNKED_ENCODING_TRAILER, strlen(CHUNKED_ENCODING_TRAILER))) { + jk_log(logger, JK_LOG_ERROR, + "WriteClient for chunk response terminator failed\n"); + JK_TRACE_EXIT(logger); + return JK_FALSE; + } + } + } + + JK_TRACE_EXIT(logger); + return JK_TRUE; +} + BOOL WINAPI GetFilterVersion(PHTTP_FILTER_VERSION pVer) { BOOL rv = TRUE; @@ -1087,6 +1293,7 @@ private_data.request_started = JK_FALSE; private_data.bytes_read_so_far = 0; private_data.lpEcb = lpEcb; + private_data.chunk_content = JK_FALSE; s.ws_private = &private_data; s.pool = &private_data.p; @@ -1270,6 +1477,7 @@ jk_log(logger, JK_LOG_DEBUG, "Using rewrite rule file %s.", rewrite_rule_file); jk_log(logger, JK_LOG_DEBUG, "Using uri select %d.", uri_select_option); + jk_log(logger, JK_LOG_DEBUG, "Using chunked encoding? %d.\n", chunked_encoding_enabled); } if (rewrite_rule_file[0] && jk_map_alloc(&rewrite_map)) { @@ -1377,7 +1585,20 @@ else { src = &hkey; } + + chunked_encoding_enabled = jk_map_get_bool(map, ENABLE_CHUNKED_ENCODING_TAG, JK_FALSE); } + + ok = ok && get_config_parameter(src, ENABLE_CHUNKED_ENCODING_TAG, tmpbuf, sizeof(tmpbuf)); + + if (ok) { + if (strcasecmp(tmpbuf, "true") == 0 || + *tmpbuf == 'Y' || *tmpbuf == 'y' || *tmpbuf == '1') { + chunked_encoding_enabled = JK_TRUE; + } + + } + ok = ok && get_config_parameter(src, JK_LOG_FILE_TAG, log_file, sizeof(log_file)); if (get_config_parameter(src, JK_LOG_LEVEL_TAG, tmpbuf, sizeof(tmpbuf))) { log_level = jk_parse_log_level(tmpbuf); @@ -1395,6 +1616,8 @@ ok = JK_FALSE; } } + + shm_config_size = get_config_int(src, SHM_SIZE_TAG, JK_SHM_DEF_SIZE); worker_mount_reload = get_config_int(src, WORKER_MOUNT_RELOAD_TAG, JK_URIMAP_DEF_RELOAD); strip_session = get_config_bool(src, STRIP_SESSION_TAG, JK_FALSE); @@ -1508,9 +1731,11 @@ /* Yes we do want to reuse AJP connections */ s->disable_reuse = JK_FALSE; - s->flush = NULL; - s->flush_packets = JK_FALSE; + //s->flush = NULL; s->flush_header = JK_FALSE; + /* We want to flush at end of content to terminate chunked encoding */ + s->flush = flush_response; + s->flush_packets = JK_FALSE; /* Clear RECO status */ s->reco_status = RECO_NONE; -- -bk -----Original Message----- From: Mladen Turk [mailto:[EMAIL PROTECTED] Sent: Tuesday, March 20, 2007 10:20 PM To: Tomcat Users List Subject: Re: JK ISAPI Filter and Keep-Alive/Chunked Encoding (a.k.a. Is 1.2.21 broken?) Brandon Knitter wrote: > I tried this, but still there was no chunked encoding in version 1.2.22. You we speaking about Keep-Alive, not TE chunked. > > We have patched 1.2.21 to allow for enabling of chunked encoding in the > ISAPI filter if you configure it to do so (registry entry). Would this > be of any interest? It's a rather small patch if you think it could be > rolled into 1.2.22 or 1.2.23. > Sure, if it's not large, just post it here, and if it is, open a bugzilla issue. Regards, Mladen. --------------------------------------------------------------------- To start a new topic, e-mail: users@tomcat.apache.org To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED] ----------------------------------------------------------------------------------- This email message is for the sole use of the intended recipient(s) and may contain confidential information. Any unauthorized review, use, disclosure or distribution is prohibited. If you are not the intended recipient, please contact the sender by reply email and destroy all copies of the original message. -----------------------------------------------------------------------------------
--- native/common/jk_util.c 2007/03/19 12:35:35 +++ native/common/jk_util.c 2007/03/20 14:44:58 @@ -1706,6 +1706,9 @@ s->route = NULL; s->retries = JK_RETRIES; s->add_log_items = NULL; + + s->flush = NULL; + s->flush_packets = JK_FALSE; } #ifdef _MT_CODE_PTHREAD --- native/iis/jk_isapi_plugin.c 2007/03/19 12:35:35 +++ native/iis/jk_isapi_plugin.c 2007/03/20 14:44:58 @@ -24,6 +24,9 @@ * Version: $Revision: 511888 $ * ***************************************************************************/ +// Updated with patch from +// http://mail-archives.apache.org/mod_mbox/tomcat-dev/200506.mbox/raw/[EMAIL PROTECTED]/2 + // This define is needed to include wincrypt,h, needed to get client certificates #define _WIN32_WINNT 0x0400 @@ -89,6 +92,17 @@ #define URI_SELECT_PARSED_VERB ("parsed") #define URI_SELECT_UNPARSED_VERB ("unparsed") #define URI_SELECT_ESCAPED_VERB ("escaped") + +#define ENABLE_CHUNKED_ENCODING_TAG ("enable_chunked_encoding") + +/* Headers used in negotiating chunked encoding */ +#define TRANSFER_ENCODING_HEADER_COMPLETE ("Transfer-Encoding: chunked") +#define TRANSFER_ENCODING_HEADER_NAME ("Transfer-Encoding") +#define CONTENT_LENGTH_HEADER_NAME ("Content-Length") +#define CONNECTION_HEADER_NAME ("Connection") +#define CONNECTION_CLOSE_VALUE ("Close") + + #define URI_REWRITE_TAG ("rewrite_rule_file") #define SHM_SIZE_TAG ("shm_size") #define WORKER_MOUNT_RELOAD_TAG ("worker_mount_reload") @@ -146,6 +160,11 @@ (place) = def; \ } } while(0) +/* HTTP protocol CRLF */ +static char CRLF[3] = { (char)13, (char)10, '\0' }; +/* Whether chunked encoding has been enabled in the settings */ +static int chunked_encoding_enabled = JK_FALSE; + static char ini_file_name[MAX_PATH]; static int using_ini_file = JK_FALSE; static int is_inited = JK_FALSE; @@ -189,6 +208,8 @@ int request_started; unsigned int bytes_read_so_far; LPEXTENSION_CONTROL_BLOCK lpEcb; + + int chunk_content; /* Whether we're responding with Transfer-Encoding: chunked content */ }; typedef struct isapi_log_data_t isapi_log_data_t; @@ -209,6 +230,8 @@ static int JK_METHOD write(jk_ws_service_t *s, const void *b, unsigned int l); +static int JK_METHOD flush_response(jk_ws_service_t *s); + static int init_ws_service(isapi_private_data_t * private_data, jk_ws_service_t *s, char **worker_name); @@ -533,11 +556,15 @@ if (s && s->ws_private) { int rv = JK_TRUE; isapi_private_data_t *p = s->ws_private; + //int keep_alive = JK_FALSE; /* Whether the downstream or us can supply content length */ + if (!p->request_started) { HSE_SEND_HEADER_EX_INFO hi; + HSE_SEND_HEADER_EX_INFO send_header_ex_info; char *status_str; char *headers_str = NULL; - BOOL keep_alive = FALSE; + //BOOL keep_alive = FALSE; + int keep_alive = JK_FALSE; /* Whether the downstream or us can supply content length */ p->request_started = JK_TRUE; /* @@ -555,32 +582,113 @@ * Create response headers string */ if (num_of_headers) { + int chunked_ok = JK_FALSE; /* Whether the downstream response allows chunking */ + int http11_request = JK_FALSE; /* Whether the client is HTTP/1.1 */ + size_t i, len_of_headers; + + /* Check if we've got an HTTP/1.1 response */ + jk_log(logger, JK_LOG_DEBUG, + "jk_ws_service_t::start_response, Request uses protocol %s\n", s->protocol); + + if (strcasecmp(s->protocol, "HTTP/1.1")==0) { + http11_request = JK_TRUE; + chunked_ok = JK_TRUE; /* Chunking only on HTTP/1.1 */ + } + for (i = 0, len_of_headers = 0; i < num_of_headers; i++) { len_of_headers += strlen(header_names[i]); len_of_headers += strlen(header_values[i]); len_of_headers += 4; /* extra for colon, space and crlf */ } - len_of_headers += 3; /* crlf and terminating null char */ + /* Provide room in the buffer for the Transfer-Encoding header if we use it. */ + len_of_headers += strlen(TRANSFER_ENCODING_HEADER_COMPLETE) + 2; + + len_of_headers += 3; /* crlf and terminating null char */ headers_str = (char *)malloc(len_of_headers); headers_str[0] = '\0'; for (i = 0; i < num_of_headers; i++) { + /* Check the downstream response to see whether + it's appropriate the chunk the response content + and whether it supports keeping the connection open */ + if(strcasecmp(TRANSFER_ENCODING_HEADER_NAME, header_names[i])==0) { + keep_alive = http11_request; + chunked_ok = JK_FALSE; + } + else if(strcasecmp(CONTENT_LENGTH_HEADER_NAME, header_names[i])==0) { + keep_alive = http11_request; + chunked_ok = JK_FALSE; + } + else if((strcasecmp(CONNECTION_HEADER_NAME, header_names[i])==0) + && (strcasecmp(CONNECTION_CLOSE_VALUE, header_values[i])==0)) { + keep_alive = JK_FALSE; + chunked_ok = JK_FALSE; + } + StringCbCat(headers_str, len_of_headers, header_names[i]); StringCbCat(headers_str, len_of_headers, ": "); StringCbCat(headers_str, len_of_headers, header_values[i]); - StringCbCat(headers_str, len_of_headers, crlf); + StringCbCat(headers_str, len_of_headers, CRLF); + } + //StringCbCat(headers_str, len_of_headers, crlf); + + /* Check if we can send chunked content */ + if (chunked_encoding_enabled && chunked_ok) { + jk_log(logger, JK_LOG_DEBUG, + "jk_ws_service_t::start_response, using Transfer-Encoding: chunked\n"); + + /** We will supply the transfer-encoding to allow IIS to keep the connection open */ + keep_alive = JK_TRUE; + + p->chunk_content = JK_TRUE; + /* Indicate to the client that the content will be chunked + - We've already reserved space for this */ + StringCbCat(headers_str, len_of_headers, TRANSFER_ENCODING_HEADER_COMPLETE); + StringCbCat(headers_str, len_of_headers, CRLF); + } + else { + p->chunk_content = JK_FALSE; } - StringCbCat(headers_str, len_of_headers, crlf); - hi.pszHeader = headers_str; - hi.cchHeader = (DWORD)strlen(headers_str); + + StringCbCat(headers_str, len_of_headers, CRLF); + //strcat(headers_str, CRLF); + + //hi.pszHeader = headers_str; + //hi.cchHeader = (DWORD)strlen(headers_str); } else { - hi.pszHeader = crlf; + hi.pszHeader = CRLF; hi.cchHeader = 2; } - hi.fKeepConn = keep_alive; + + /** Fill in the response */ + send_header_ex_info.pszStatus = status_str; + send_header_ex_info.pszHeader = headers_str; + send_header_ex_info.cchStatus = strlen(status_str); + send_header_ex_info.cchHeader = strlen(headers_str); + + /* + * Using the extended form of the API means we have to get this right, + * i.e. IIS won't keep connections open if there's a Content-Length and close them if there isn't. + */ + jk_log(logger, JK_LOG_DEBUG, + "jk_ws_service_t::start_response, keep_alive = %d\n", keep_alive); + + send_header_ex_info.fKeepConn = keep_alive; + + if (!p->lpEcb->ServerSupportFunction(p->lpEcb->ConnID, + HSE_REQ_SEND_RESPONSE_HEADER_EX, + &send_header_ex_info, + NULL, + NULL)) { + jk_log(logger, JK_LOG_ERROR, + "HSE_REQ_SEND_RESPONSE_HEADER failed"); + rv = JK_FALSE; + } + + /*hi.fKeepConn = keep_alive; if (!p->lpEcb->ServerSupportFunction(p->lpEcb->ConnID, HSE_REQ_SEND_RESPONSE_HEADER_EX, &hi, @@ -588,7 +696,8 @@ jk_log(logger, JK_LOG_ERROR, "HSE_REQ_SEND_RESPONSE_HEADER_EX failed"); rv = JK_FALSE; - } + }*/ + if (headers_str) free(headers_str); if (status_str) @@ -658,6 +767,39 @@ return JK_FALSE; } +/* +* Writes a buffer to the ISAPI response. +*/ +static int isapi_write_client(isapi_private_data_t *p, const char *buf, unsigned write_length) +{ + unsigned written = 0; + DWORD try_to_write = 0; + + JK_TRACE_ENTER(logger); + + while (written < write_length) { + try_to_write = (write_length - written); + + if (!p->lpEcb->WriteClient(p->lpEcb->ConnID, + buf + written, &try_to_write, 0)) { + jk_log(logger, JK_LOG_ERROR, + "WriteClient failed with %08x", GetLastError()); + JK_TRACE_EXIT(logger); + return JK_FALSE; + } + written += try_to_write; + } + JK_TRACE_EXIT(logger); + return JK_TRUE; +} + +/* +* Write content to the response. +* If chunked encoding has been enabled and the client supports it +*(and it's appropriate for the response), then this will write a +* single "Transfer-Encoding: chunked" chunk +* +*/ static int JK_METHOD write(jk_ws_service_t *s, const void *b, unsigned int l) { JK_TRACE_ENTER(logger); @@ -666,23 +808,56 @@ isapi_private_data_t *p = s->ws_private; if (l) { - unsigned int written = 0; + //unsigned int written = 0; char *buf = (char *)b; if (!p->request_started) { start_response(s, 200, NULL, NULL, NULL, 0); } - - while (written < l) { + /*while (written < l) { DWORD try_to_write = l - written; if (!p->lpEcb->WriteClient(p->lpEcb->ConnID, buf + written, &try_to_write, 0)) { jk_log(logger, JK_LOG_ERROR, "WriteClient failed with %08x", GetLastError()); JK_TRACE_EXIT(logger); + return JK_FALSE;*/ + + /* Chunk header */ + if (p->chunk_content) { + char chunk_header[sizeof(unsigned)*2+3]; /* Hex of chunk length + CRLF + term. */ + jk_log(logger, JK_LOG_DEBUG, + "Using chunked encoding - writing chunk header\n"); + + /* Construct chunk header : HEX CRLF*/ + sprintf(chunk_header, "%X%s", l, CRLF); + + if (!isapi_write_client(p, chunk_header, strlen(chunk_header))) { + jk_log(logger, JK_LOG_ERROR, + "WriteClient for chunk header failed\n"); + JK_TRACE_EXIT(logger); return JK_FALSE; } - written += try_to_write; + } + + /* Write chunk body */ + if (!isapi_write_client(p, buf, l)) { + jk_log(logger, JK_LOG_ERROR, + "WriteClient for body chunk failed\n"); + JK_TRACE_EXIT(logger); + return JK_FALSE; + } + + /* Write chunk trailer */ + if (p->chunk_content) { + jk_log(logger, JK_LOG_DEBUG, + "Using chunked encoding - writing chunk trailer\n"); + + if (!isapi_write_client(p, CRLF, strlen(CRLF))) { + jk_log(logger, JK_LOG_ERROR, + "WriteClient for chunk trailer failed\n"); + } + // written += try_to_write; } } @@ -696,6 +871,37 @@ return JK_FALSE; } + +/** +* In the case of a Transfer-Encoding: chunked response, this will write the terminator chunk. +*/ +static int JK_METHOD flush_response(jk_ws_service_t *s) +{ + JK_TRACE_ENTER(logger); + + if (s && s->ws_private) { + isapi_private_data_t *p = s->ws_private; + + /* Write last chunk + terminator */ + if (p->chunk_content) { + static char CHUNKED_ENCODING_TRAILER[6] = { '0', (char)13, (char)10, (char)13, (char)10, '\0' }; + + jk_log(logger, JK_LOG_DEBUG, + "Terminating chunk encoded response.\n"); + + if (!isapi_write_client(p, CHUNKED_ENCODING_TRAILER, strlen(CHUNKED_ENCODING_TRAILER))) { + jk_log(logger, JK_LOG_ERROR, + "WriteClient for chunk response terminator failed\n"); + JK_TRACE_EXIT(logger); + return JK_FALSE; + } + } + } + + JK_TRACE_EXIT(logger); + return JK_TRUE; +} + BOOL WINAPI GetFilterVersion(PHTTP_FILTER_VERSION pVer) { BOOL rv = TRUE; @@ -1087,6 +1293,7 @@ private_data.request_started = JK_FALSE; private_data.bytes_read_so_far = 0; private_data.lpEcb = lpEcb; + private_data.chunk_content = JK_FALSE; s.ws_private = &private_data; s.pool = &private_data.p; @@ -1270,6 +1477,7 @@ jk_log(logger, JK_LOG_DEBUG, "Using rewrite rule file %s.", rewrite_rule_file); jk_log(logger, JK_LOG_DEBUG, "Using uri select %d.", uri_select_option); + jk_log(logger, JK_LOG_DEBUG, "Using chunked encoding? %d.\n", chunked_encoding_enabled); } if (rewrite_rule_file[0] && jk_map_alloc(&rewrite_map)) { @@ -1377,7 +1585,20 @@ else { src = &hkey; } + + chunked_encoding_enabled = jk_map_get_bool(map, ENABLE_CHUNKED_ENCODING_TAG, JK_FALSE); } + + ok = ok && get_config_parameter(src, ENABLE_CHUNKED_ENCODING_TAG, tmpbuf, sizeof(tmpbuf)); + + if (ok) { + if (strcasecmp(tmpbuf, "true") == 0 || + *tmpbuf == 'Y' || *tmpbuf == 'y' || *tmpbuf == '1') { + chunked_encoding_enabled = JK_TRUE; + } + + } + ok = ok && get_config_parameter(src, JK_LOG_FILE_TAG, log_file, sizeof(log_file)); if (get_config_parameter(src, JK_LOG_LEVEL_TAG, tmpbuf, sizeof(tmpbuf))) { log_level = jk_parse_log_level(tmpbuf); @@ -1395,6 +1616,8 @@ ok = JK_FALSE; } } + + shm_config_size = get_config_int(src, SHM_SIZE_TAG, JK_SHM_DEF_SIZE); worker_mount_reload = get_config_int(src, WORKER_MOUNT_RELOAD_TAG, JK_URIMAP_DEF_RELOAD); strip_session = get_config_bool(src, STRIP_SESSION_TAG, JK_FALSE); @@ -1508,9 +1731,11 @@ /* Yes we do want to reuse AJP connections */ s->disable_reuse = JK_FALSE; - s->flush = NULL; - s->flush_packets = JK_FALSE; + //s->flush = NULL; s->flush_header = JK_FALSE; + /* We want to flush at end of content to terminate chunked encoding */ + s->flush = flush_response; + s->flush_packets = JK_FALSE; /* Clear RECO status */ s->reco_status = RECO_NONE;
--------------------------------------------------------------------- To start a new topic, e-mail: users@tomcat.apache.org To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]