Index: jk/native/common/jk_util.c =================================================================== RCS file: /home/cvspublic/jakarta-tomcat-connectors/jk/native/common/jk_util.c,v retrieving revision 1.69 diff -u -r1.69 jk_util.c --- jk/native/common/jk_util.c 15 May 2005 15:22:05 -0000 1.69 +++ jk/native/common/jk_util.c 9 Jun 2005 03:13:30 -0000 @@ -1230,6 +1230,8 @@ s->num_attributes = 0; s->jvm_route = NULL; s->retries = JK_RETRIES; + s->flush = NULL; + s->flush_packets = JK_FALSE; } #ifdef _MT_CODE_PTHREAD Index: jk/native/iis/jk_isapi_plugin.c =================================================================== RCS file: /home/cvspublic/jakarta-tomcat-connectors/jk/native/iis/jk_isapi_plugin.c,v retrieving revision 1.49 diff -u -r1.49 jk_isapi_plugin.c --- jk/native/iis/jk_isapi_plugin.c 18 May 2005 18:04:53 -0000 1.49 +++ jk/native/iis/jk_isapi_plugin.c 9 Jun 2005 04:38:46 -0000 @@ -20,6 +20,7 @@ * Author: Larry Isaacs * * Author: Ignacio J. Ortega * * Author: Mladen Turk * + * Author: Tim Whittington 1000) { jk_log(logger, JK_LOG_ERROR, @@ -482,10 +498,13 @@ if (s && s->ws_private) { 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) { - size_t len_of_status; + HSE_SEND_HEADER_EX_INFO send_header_ex_info; char *status_str; char *headers_str; + int keep_alive = JK_FALSE; /* Whether the downstream or us can supply content length */ p->request_started = JK_TRUE; @@ -497,46 +516,113 @@ } status_str = (char *)_alloca((6 + strlen(reason)) * sizeof(char)); sprintf(status_str, "%d %s", status, reason); - len_of_status = strlen(status_str); /* * 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 */ } + /* 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 *)_alloca(len_of_headers * sizeof(char)); 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; + } + strcat(headers_str, header_names[i]); strcat(headers_str, ": "); strcat(headers_str, header_values[i]); - strcat(headers_str, crlf); + strcat(headers_str, CRLF); } - strcat(headers_str, 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 */ + strcat(headers_str, TRANSFER_ENCODING_HEADER_COMPLETE); + strcat(headers_str, CRLF); + } + else { + p->chunk_content = JK_FALSE; + } + + strcat(headers_str, CRLF); } else { - headers_str = crlf; + headers_str = CRLF; } - if (!p->lpEcb->ServerSupportFunction(p->lpEcb->ConnID, - HSE_REQ_SEND_RESPONSE_HEADER, - status_str, - (LPDWORD) &len_of_status, - (LPDWORD) headers_str)) { - jk_log(logger, JK_LOG_ERROR, - "HSE_REQ_SEND_RESPONSE_HEADER failed"); + /** 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"); JK_TRACE_EXIT(logger); return JK_FALSE; - } + } } + JK_TRACE_EXIT(logger); return JK_TRUE; @@ -602,6 +688,38 @@ 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); @@ -610,29 +728,87 @@ isapi_private_data_t *p = s->ws_private; if (l) { - unsigned int written = 0; char *buf = (char *)b; if (!p->request_started) { start_response(s, 200, NULL, NULL, NULL, 0); } - 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()); + /* 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; + } + } + + /* 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"); JK_TRACE_EXIT(logger); return JK_FALSE; } - written += try_to_write; } } JK_TRACE_EXIT(logger); return JK_TRUE; + } + + JK_LOG_NULL_PARAMS(logger); + JK_TRACE_EXIT(logger); + 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; } JK_LOG_NULL_PARAMS(logger); @@ -992,6 +1168,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; @@ -1139,6 +1316,7 @@ jk_log(logger, JK_LOG_DEBUG, "Using worker mount file %s.", worker_mount_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 (uri_worker_map_alloc(&uw_map, NULL, logger)) { rc = JK_FALSE; @@ -1256,6 +1434,7 @@ ok = JK_FALSE; } } + chunked_encoding_enabled = jk_map_get_bool(map, ENABLE_CHUNKED_ENCODING_TAG, JK_FALSE); } else { @@ -1323,6 +1502,15 @@ } } + if (get_registry_config_parameter(hkey, + ENABLE_CHUNKED_ENCODING_TAG, + tmpbuf, sizeof(tmpbuf))) { + if (strcasecmp(tmpbuf, "true") == 0 || + *tmpbuf == 'Y' || *tmpbuf == 'y' || *tmpbuf == '1') { + chunked_encoding_enabled = JK_TRUE; + } + } + RegCloseKey(hkey); } return ok; @@ -1356,7 +1544,10 @@ s->start_response = start_response; s->read = read; s->write = write; - s->flush = NULL; + + /* 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;