Author: mturk Date: Thu Oct 2 01:21:39 2008 New Revision: 701029 URL: http://svn.apache.org/viewvc?rev=701029&view=rev Log: Merge from jk_isapi_plugin_chunked branch
Modified: tomcat/connectors/trunk/jk/native/iis/jk_isapi_plugin.c Modified: tomcat/connectors/trunk/jk/native/iis/jk_isapi_plugin.c URL: http://svn.apache.org/viewvc/tomcat/connectors/trunk/jk/native/iis/jk_isapi_plugin.c?rev=701029&r1=701028&r2=701029&view=diff ============================================================================== --- tomcat/connectors/trunk/jk/native/iis/jk_isapi_plugin.c (original) +++ tomcat/connectors/trunk/jk/native/iis/jk_isapi_plugin.c Thu Oct 2 01:21:39 2008 @@ -51,15 +51,35 @@ #include <strsafe.h> -#define VERSION_STRING "Jakarta/ISAPI/" JK_EXPOSED_VERSION +#ifdef ALLOW_CHUNKING +#define HAS_CHUNKING "-CHUNKING" +#else +#define HAS_CHUNKING "-NO_CHUNKING" +#endif + +#ifdef AUTOMATIC_POOL_SIZE +#define HAS_AUTO_POOL "-AUTO_POOL" +#else +#define HAS_AUTO_POOL "-NO_AUTO_POOL" +#endif + +#ifdef CONFIGURABLE_ERROR_PAGE +#define HAS_ERROR_PAGE "-ERROR_PAGE" +#else +#define HAS_ERROR_PAGE "-NO_ERROR_PAGE" +#endif + +#define VERSION_STRING "Jakarta/ISAPI/" JK_EXPOSED_VERSION HAS_CHUNKING HAS_AUTO_POOL HAS_ERROR_PAGE #define SHM_DEF_NAME "JKISAPISHMEM" #define DEFAULT_WORKER_NAME ("ajp13") +#ifndef AUTOMATIC_POOL_SIZE /* * This is default value found inside httpd.conf * for MaxClients */ #define DEFAULT_WORKER_THREADS 250 +#endif /* * We use special headers to pass values from the filter to the @@ -75,17 +95,25 @@ #define WORKER_HEADER_NAME_BASE ("TOMCATWORKER") #define TOMCAT_TRANSLATE_HEADER_NAME_BASE ("TOMCATTRANSLATE") #define CONTENT_LENGTH ("CONTENT_LENGTH:") + +/* The HTTP_ form of the header for use in ExtensionProc */ +#define HTTP_HEADER_PREFIX "HTTP_" +#define HTTP_HEADER_PREFIX_LEN 5 + /* The template used to construct our unique headers * from the base name and module instance */ -#define HEADER_TEMPLATE ("%s%p:") -#define HTTP_HEADER_TEMPLATE ("HTTP_%s%p") +#define HEADER_TEMPLATE "%s%p:" +#define HTTP_HEADER_TEMPLATE HTTP_HEADER_PREFIX "%s%p" static char URI_HEADER_NAME[MAX_PATH]; static char QUERY_HEADER_NAME[MAX_PATH]; static char WORKER_HEADER_NAME[MAX_PATH]; static char TOMCAT_TRANSLATE_HEADER_NAME[MAX_PATH]; +/* The variants of the special headers after IIS adds + * "HTTP_" to the front of them + */ static char HTTP_URI_HEADER_NAME[MAX_PATH]; static char HTTP_QUERY_HEADER_NAME[MAX_PATH]; static char HTTP_WORKER_HEADER_NAME[MAX_PATH]; @@ -103,15 +131,46 @@ #define SHM_SIZE_TAG ("shm_size") #define WORKER_MOUNT_RELOAD_TAG ("worker_mount_reload") #define STRIP_SESSION_TAG ("strip_session") +#ifndef AUTOMATIC_AUTH_NOTIFICATION #define AUTH_COMPLETE_TAG ("auth_complete") +#endif #define REJECT_UNSAFE_TAG ("reject_unsafe") #define WATCHDOG_INTERVAL_TAG ("watchdog_interval") +#define ENABLE_CHUNKED_ENCODING_TAG ("enable_chunked_encoding") +#ifdef CONFIGURABLE_ERROR_PAGE +#define ERROR_PAGE_TAG ("error_page") +#endif +/* HTTP standard headers */ +#define TRANSFER_ENCODING_CHUNKED_HEADER_COMPLETE ("Transfer-Encoding: chunked") +#define TRANSFER_ENCODING_CHUNKED_HEADER_COMPLETE_LEN (26) +#define TRANSFER_ENCODING_HEADER_NAME ("Transfer-Encoding") +#define TRANSFER_ENCODING_HEADER_NAME_LEN (17) +#define TRANSFER_ENCODING_IDENTITY_VALUE ("identity") +#define TRANSFER_ENCODING_CHUNKED_VALUE ("chunked") +#define TRANSFER_ENCODING_CHUNKED_VALUE_LEN (7) + +#define CONTENT_LENGTH_HEADER_NAME ("Content-Length") +#define CONTENT_LENGTH_HEADER_NAME_LEN (14) + +#define CONNECTION_HEADER_NAME ("Connection") +#define CONNECTION_CLOSE_VALUE ("Close") #define TRANSLATE_HEADER ("Translate:") #define TRANSLATE_HEADER_NAME ("Translate") #define TRANSLATE_HEADER_NAME_LC ("translate") +/* HTTP protocol CRLF */ +#define CRLF ("\r\n") +#define CRLF_LEN (2) + +/* Transfer-Encoding: chunked content trailer */ +#define CHUNKED_ENCODING_TRAILER ("0\r\n\r\n") +#define CHUNKED_ENCODING_TRAILER_LEN (5) + +/* Hex of chunk length (one char per byte) + CRLF + terminator. */ +#define CHUNK_HEADER_BUFFER_SIZE (sizeof(unsigned int)*2+CRLF_LEN+1) + #define BAD_REQUEST -1 #define BAD_PATH -2 #define MAX_SERVERNAME 128 @@ -193,18 +252,24 @@ static char extension_uri[INTERNET_MAX_URL_LENGTH] = "/jakarta/isapi_redirect.dll"; static char log_file[MAX_PATH * 2]; -static int log_level = JK_LOG_DEF_LEVEL; +static int log_level = JK_LOG_DEF_LEVEL; static char worker_file[MAX_PATH * 2]; static char worker_mount_file[MAX_PATH * 2] = {0}; static int worker_mount_reload = JK_URIMAP_DEF_RELOAD; static char rewrite_rule_file[MAX_PATH * 2] = {0}; static size_t shm_config_size = 0; -static int strip_session = 0; -static DWORD auth_notification_flags = 0; -static int use_auth_notification_flags = 1; -static int reject_unsafe = 0; -static int watchdog_interval = 0; +static int strip_session = 0; +#ifndef AUTOMATIC_AUTH_NOTIFICATION +static int use_auth_notification_flags = 1; +#endif +static int chunked_encoding_enabled = JK_FALSE; +static int reject_unsafe = 0; +static int watchdog_interval = 0; static HANDLE watchdog_handle = NULL; +#ifdef CONFIGURABLE_ERROR_PAGE +static char error_page_buf[INTERNET_MAX_URL_LENGTH] = {0}; +static char *error_page = NULL; +#endif #define URI_SELECT_OPT_PARSED 0 #define URI_SELECT_OPT_UNPARSED 1 @@ -221,6 +286,7 @@ jk_pool_t p; unsigned int bytes_read_so_far; + int chunk_content; /* Whether we're responding with Transfer-Encoding: chunked content */ LPEXTENSION_CONTROL_BLOCK lpEcb; }; @@ -230,6 +296,15 @@ char query[INTERNET_MAX_URL_LENGTH]; }; +typedef struct iis_info_t iis_info_t; +struct iis_info_t { + int major; /* The major version */ + int minor; /* The minor version */ + DWORD filter_notify_event; /* The primary filter SF_NOTIFY_* event */ +}; + +static iis_info_t iis_info; + static int JK_METHOD start_response(jk_ws_service_t *s, int status, const char *reason, @@ -242,6 +317,8 @@ static int JK_METHOD iis_write(jk_ws_service_t *s, const void *b, unsigned int l); +static int JK_METHOD iis_done(jk_ws_service_t *s); + static int init_ws_service(isapi_private_data_t * private_data, jk_ws_service_t *s, char **worker_name); @@ -251,6 +328,12 @@ static int read_registry_init_data(void); +#ifdef AUTOMATIC_POOL_SIZE +static int read_registry_pool_thread_limit(size_t *pool_threads); + +static int determine_iis_thread_count(); + +#endif static int get_config_parameter(LPVOID src, const char *tag, char *val, DWORD sz); @@ -262,7 +345,7 @@ const char *tag, char *b, DWORD sz); static int get_registry_config_number(HKEY hkey, const char *tag, - int *val); + int *val); static int get_server_value(LPEXTENSION_CONTROL_BLOCK lpEcb, @@ -274,7 +357,9 @@ static int base64_encode_cert(char *encoded, const char *string, int len); -static int get_auth_flags(); +static int get_iis_info(iis_info_t *info); + +static int isapi_write_client(isapi_private_data_t *p, const char *buf, unsigned int write_length); static char x2c(const char *what) { @@ -541,7 +626,7 @@ pfc->ServerSupportFunction(pfc, SF_REQ_SEND_RESPONSE_HEADER, status, 0, 0); - pfc->WriteClient(pfc, msg, &len, 0); + pfc->WriteClient(pfc, msg, &len, HSE_IO_SYNC); } static void write_error_message(LPEXTENSION_CONTROL_BLOCK lpEcb, int err) @@ -555,7 +640,7 @@ (LPDWORD)CONTENT_TYPE); len = (DWORD)(sizeof(HTML_ERROR_500) - 1); lpEcb->WriteClient(lpEcb->ConnID, - HTML_ERROR_500, &len, 0); + HTML_ERROR_500, &len, HSE_IO_SYNC); } else if (err == 503) { lpEcb->ServerSupportFunction(lpEcb->ConnID, @@ -565,7 +650,7 @@ (LPDWORD)CONTENT_TYPE); len = (DWORD)(sizeof(HTML_ERROR_503) - 1); lpEcb->WriteClient(lpEcb->ConnID, - HTML_ERROR_503, &len, 0); + HTML_ERROR_503, &len, HSE_IO_SYNC); } else { return; @@ -580,8 +665,6 @@ const char *const *header_values, unsigned int num_of_headers) { - static char crlf[3] = { (char)13, (char)10, '\0' }; - JK_TRACE_ENTER(logger); if (status < 100 || status > 1000) { jk_log(logger, JK_LOG_ERROR, @@ -595,12 +678,19 @@ int rv = JK_TRUE; isapi_private_data_t *p = s->ws_private; if (!s->response_started) { - char *status_str; - DWORD status_str_len; + char *status_str = NULL; char *headers_str = NULL; - BOOL keep_alive = FALSE; + BOOL keep_alive = FALSE; /* Whether the downstream or us can supply content length */ + BOOL rc; + size_t i, len_of_headers = 0; + s->response_started = JK_TRUE; + if (JK_IS_DEBUG_LEVEL(logger)) { + jk_log(logger, JK_LOG_DEBUG, "Starting response for URI '%s' (protocol %s)", + s->req_uri, s->protocol); + } + /* * Create the status line */ @@ -609,44 +699,132 @@ } status_str = (char *)malloc((6 + strlen(reason))); StringCbPrintf(status_str, 6 + strlen(reason), "%d %s", status, reason); - status_str_len = (DWORD)strlen(status_str); + + if (chunked_encoding_enabled) { + /* Check if we've got an HTTP/1.1 response */ + if (!strcasecmp(s->protocol, "HTTP/1.1")) { + keep_alive = TRUE; + /* Chunking only when HTTP/1.1 client and enabled */ + p->chunk_content = JK_TRUE; + } + } /* * Create response headers string */ - if (num_of_headers) { - size_t i, len_of_headers = 0; - 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 */ - headers_str = (char *)malloc(len_of_headers); - headers_str[0] = '\0'; + /* Calculate length of headers block */ + for (i = 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 */ + } + if (p->chunk_content) { for (i = 0; i < num_of_headers; i++) { - 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); + /* Check the downstream response to see whether + * it's appropriate the chunk the response content + * and whether it supports keeping the connection open. + + * This implements the rules for HTTP/1.1 message length determination + * with the exception of multipart/byteranges media types. + * http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.4 + */ + if (!strcasecmp(CONTENT_LENGTH_HEADER_NAME, header_names[i])) { + p->chunk_content = JK_FALSE; + if (JK_IS_DEBUG_LEVEL(logger)) + jk_log(logger, JK_LOG_DEBUG, "Response specifies Content-Length" ); + } + else if (!strcasecmp(CONNECTION_HEADER_NAME, header_names[i]) + && !strcasecmp(CONNECTION_CLOSE_VALUE, header_values[i])) { + keep_alive = FALSE; + p->chunk_content = JK_FALSE; + if (JK_IS_DEBUG_LEVEL(logger)) + jk_log(logger, JK_LOG_DEBUG, "Response specifies Connection: Close" ); + } + else if (!strcasecmp(TRANSFER_ENCODING_HEADER_NAME, header_names[i]) + && !strcasecmp(TRANSFER_ENCODING_IDENTITY_VALUE, header_values[i])) { + /* HTTP states that this must include 'chunked' as the last value. + * 'identity' is the same as absence of the header */ + p->chunk_content = JK_FALSE; + if (JK_IS_DEBUG_LEVEL(logger)) + jk_log(logger, JK_LOG_DEBUG, "Response specifies Transfer-Encoding" ); + } } - StringCbCat(headers_str, len_of_headers, crlf); + + /* Provide room in the buffer for the Transfer-Encoding header if we use it. */ + len_of_headers += TRANSFER_ENCODING_CHUNKED_HEADER_COMPLETE_LEN + 2; } - else { - headers_str = crlf; + + /* Allocate and init the headers string */ + len_of_headers += 3; /* crlf and terminating null char */ + headers_str = (char *)malloc(len_of_headers); + headers_str[0] = '\0'; + + /* Copy headers into headers block for sending */ + for (i = 0; i < num_of_headers; i++) { + 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); } - if (!p->lpEcb->ServerSupportFunction(p->lpEcb->ConnID, - HSE_REQ_SEND_RESPONSE_HEADER, - status_str, - &status_str_len, - (LPDWORD)headers_str)) { + if (p->chunk_content) { + /* Configure the response if chunked encoding is used */ + if (JK_IS_DEBUG_LEVEL(logger)) + jk_log(logger, JK_LOG_DEBUG, "Using Transfer-Encoding: chunked"); + + /** We will supply the transfer-encoding to allow IIS to keep the connection open */ + keep_alive = 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_CHUNKED_HEADER_COMPLETE); + StringCbCat(headers_str, len_of_headers, CRLF); + } + + /* Terminate the headers */ + StringCbCat(headers_str, len_of_headers, CRLF); + + if (JK_IS_DEBUG_LEVEL(logger)) + jk_log(logger, JK_LOG_DEBUG, "%ssing Keep-Alive", (keep_alive ? "U" : "Not u")); + + if (keep_alive) { + HSE_SEND_HEADER_EX_INFO hi; + + /* Fill in the response */ + hi.pszStatus = status_str; + hi.pszHeader = headers_str; + hi.cchStatus = (DWORD)strlen(status_str); + hi.cchHeader = (DWORD)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. + */ + hi.fKeepConn = keep_alive; + + /* Send the response to the client */ + rc = p->lpEcb->ServerSupportFunction(p->lpEcb->ConnID, + HSE_REQ_SEND_RESPONSE_HEADER_EX, + &hi, + NULL, NULL); + } + else { + DWORD status_str_len = strlen(status_str); + /* Old style response - forces Connection: close if Tomcat response doesn't + specify necessary details to allow keep alive */ + rc = p->lpEcb->ServerSupportFunction(p->lpEcb->ConnID, + HSE_REQ_SEND_RESPONSE_HEADER, + status_str, + &status_str_len, + (LPDWORD)headers_str); + } + if (!rc) { jk_log(logger, JK_LOG_ERROR, - "HSE_REQ_SEND_RESPONSE_HEADER failed with error=%08x", - GetLastError()); + "HSE_REQ_SEND_RESPONSE_HEADER%s failed with error=%d (0x%08x)", + (keep_alive ? "_EX" : ""), GetLastError(), GetLastError()); rv = JK_FALSE; } if (headers_str) @@ -724,7 +902,7 @@ } else { jk_log(logger, JK_LOG_ERROR, - "ReadClient failed with %08x", GetLastError()); + "ReadClient failed with %d (0x%08x)", GetLastError(), GetLastError()); JK_TRACE_EXIT(logger); return JK_FALSE; } @@ -739,31 +917,148 @@ return JK_FALSE; } +/* + * Writes a buffer to the ISAPI response. + */ +static int isapi_write_client(isapi_private_data_t *p, const char *buf, unsigned int write_length) +{ + unsigned int written = 0; + DWORD try_to_write = 0; + + JK_TRACE_ENTER(logger); + + if (JK_IS_DEBUG_LEVEL(logger)) + jk_log(logger, JK_LOG_DEBUG, "Writing %d bytes of data to client", write_length); + + while (written < write_length) { + try_to_write = write_length - written; + if (!p->lpEcb->WriteClient(p->lpEcb->ConnID, + (LPVOID)(buf + written), &try_to_write, HSE_IO_SYNC)) { + jk_log(logger, JK_LOG_ERROR, + "WriteClient failed with %d (0x%08x)", GetLastError(), GetLastError()); + JK_TRACE_EXIT(logger); + return JK_FALSE; + } + written += try_to_write; + if (JK_IS_DEBUG_LEVEL(logger)) + jk_log(logger, JK_LOG_DEBUG, "Wrote %d bytes of data successfully", 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 iis_write(jk_ws_service_t *s, const void *b, unsigned int l) { JK_TRACE_ENTER(logger); + if (!l) { + JK_TRACE_EXIT(logger); + return JK_TRUE; + } + if (s && s->ws_private && b) { isapi_private_data_t *p = s->ws_private; + const char *buf = (const char *)b; - if (l) { - unsigned int written = 0; - char *buf = (char *)b; + if (!p) { + JK_TRACE_EXIT(logger); + return JK_FALSE; + } + + if (!s->response_started) { + start_response(s, 200, NULL, NULL, NULL, 0); + } - if (!s->response_started) { - start_response(s, 200, NULL, NULL, NULL, 0); + if (p->chunk_content == JK_FALSE) { + if (isapi_write_client(p, buf, l) == JK_FALSE) { + JK_TRACE_EXIT(logger); + return JK_FALSE; } + } + else { + char chunk_header[CHUNK_HEADER_BUFFER_SIZE]; - 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()); + /* Construct chunk header : HEX CRLF*/ + StringCbPrintf(chunk_header, CHUNK_HEADER_BUFFER_SIZE, "%X%s", l, CRLF); + + if (iis_info.major >= 6) { + HSE_RESPONSE_VECTOR response_vector; + HSE_VECTOR_ELEMENT response_elements[3]; + + response_elements[0].ElementType = HSE_VECTOR_ELEMENT_TYPE_MEMORY_BUFFER; + response_elements[0].pvContext = chunk_header; + response_elements[0].cbOffset = 0; + response_elements[0].cbSize = strlen(chunk_header); + + response_elements[1].ElementType = HSE_VECTOR_ELEMENT_TYPE_MEMORY_BUFFER; + response_elements[1].pvContext = (PVOID)buf; + response_elements[1].cbOffset = 0; + response_elements[1].cbSize = l; + + response_elements[2].ElementType = HSE_VECTOR_ELEMENT_TYPE_MEMORY_BUFFER; + response_elements[2].pvContext = CRLF; + response_elements[2].cbOffset = 0; + response_elements[2].cbSize = CRLF_LEN; + + response_vector.dwFlags = HSE_IO_SYNC; + response_vector.pszStatus = NULL; + response_vector.pszHeaders = NULL; + response_vector.nElementCount = 3; + response_vector.lpElementArray = response_elements; + + if (JK_IS_DEBUG_LEVEL(logger)) + jk_log(logger, JK_LOG_DEBUG, + "Using vector write for chunk encoded %d byte chunk", l); + + if (!p->lpEcb->ServerSupportFunction(p->lpEcb->ConnID, + HSE_REQ_VECTOR_SEND, + &response_vector, + (LPDWORD)NULL, + (LPDWORD)NULL)) { + jk_log(logger, JK_LOG_ERROR, + "Vector write of chunk encoded response failed with %d (0x%08x)", + GetLastError(), GetLastError()); + JK_TRACE_EXIT(logger); + return JK_FALSE; + } + } else { + /* Write chunk header */ + if (JK_IS_DEBUG_LEVEL(logger)) + jk_log(logger, JK_LOG_DEBUG, + "Using chunked encoding - writing chunk header for %d byte chunk", l); + + if (!isapi_write_client(p, chunk_header, (unsigned int)strlen(chunk_header))) { + jk_log(logger, JK_LOG_ERROR, "WriteClient for chunk header failed"); + JK_TRACE_EXIT(logger); + return JK_FALSE; + } + + /* Write chunk body (or simple body block) */ + if (JK_IS_DEBUG_LEVEL(logger)) { + jk_log(logger, JK_LOG_DEBUG, "Writing %s of size %d", + (p->chunk_content ? "chunk body" : "simple response"), l); + } + if (!isapi_write_client(p, buf, l)) { + jk_log(logger, JK_LOG_ERROR, "WriteClient for response body chunk failed"); + JK_TRACE_EXIT(logger); + return JK_FALSE; + } + /* Write chunk trailer */ + if (JK_IS_DEBUG_LEVEL(logger)) { + jk_log(logger, JK_LOG_DEBUG, "Using chunked encoding - writing chunk trailer"); + } + + if (!isapi_write_client(p, CRLF, CRLF_LEN)) { + jk_log(logger, JK_LOG_ERROR, "WriteClient for chunk trailer failed"); JK_TRACE_EXIT(logger); return JK_FALSE; } - written += try_to_write; } } @@ -777,6 +1072,76 @@ return JK_FALSE; } +/** + * In the case of a Transfer-Encoding: chunked response, this will write the terminator chunk. + */ +static int JK_METHOD iis_done(jk_ws_service_t *s) +{ + JK_TRACE_ENTER(logger); + + if (s && s->ws_private) { + isapi_private_data_t *p = s->ws_private; + + if (p->chunk_content == JK_FALSE) { + JK_TRACE_EXIT(logger); + return JK_TRUE; + } + + /* Write last chunk + terminator */ + if (iis_info.major >= 6) { + HSE_RESPONSE_VECTOR response_vector; + HSE_VECTOR_ELEMENT response_elements[1]; + + response_elements[0].ElementType = HSE_VECTOR_ELEMENT_TYPE_MEMORY_BUFFER; + response_elements[0].pvContext = CHUNKED_ENCODING_TRAILER; + response_elements[0].cbOffset = 0; + response_elements[0].cbSize = CHUNKED_ENCODING_TRAILER_LEN; + + /* HSE_IO_FINAL_SEND lets IIS process the response to the client before we return */ + response_vector.dwFlags = HSE_IO_SYNC | HSE_IO_FINAL_SEND; + response_vector.pszStatus = NULL; + response_vector.pszHeaders = NULL; + response_vector.nElementCount = 1; + response_vector.lpElementArray = response_elements; + + if (JK_IS_DEBUG_LEVEL(logger)) + jk_log(logger, JK_LOG_DEBUG, + "Using vector write to terminate chunk encoded response."); + + if (!p->lpEcb->ServerSupportFunction(p->lpEcb->ConnID, + HSE_REQ_VECTOR_SEND, + &response_vector, + (LPDWORD)NULL, + (LPDWORD)NULL)) { + jk_log(logger, JK_LOG_ERROR, + "Vector termination of chunk encoded response failed with %d (0x%08x)", + GetLastError(), GetLastError()); + JK_TRACE_EXIT(logger); + return JK_FALSE; + } + } + else { + if (JK_IS_DEBUG_LEVEL(logger)) + jk_log(logger, JK_LOG_DEBUG, "Terminating chunk encoded response"); + + if (!isapi_write_client(p, CHUNKED_ENCODING_TRAILER, CHUNKED_ENCODING_TRAILER_LEN)) { + jk_log(logger, JK_LOG_ERROR, + "WriteClient for chunked response terminator failed with %d (0x%08x)", + GetLastError(), GetLastError()); + JK_TRACE_EXIT(logger); + return JK_FALSE; + } + } + + JK_TRACE_EXIT(logger); + return JK_TRUE; + } + + JK_LOG_NULL_PARAMS(logger); + JK_TRACE_EXIT(logger); + return JK_FALSE; +} + BOOL WINAPI GetFilterVersion(PHTTP_FILTER_VERSION pVer) { BOOL rv = TRUE; @@ -790,22 +1155,13 @@ if (!is_inited) { rv = initialize_extension(); } - if (auth_notification_flags == SF_NOTIFY_AUTH_COMPLETE) { - pVer->dwFlags = SF_NOTIFY_ORDER_HIGH | - SF_NOTIFY_SECURE_PORT | - SF_NOTIFY_NONSECURE_PORT | - SF_NOTIFY_PREPROC_HEADERS | - SF_NOTIFY_LOG | - SF_NOTIFY_AUTH_COMPLETE; - } - else { - pVer->dwFlags = SF_NOTIFY_ORDER_HIGH | - SF_NOTIFY_SECURE_PORT | - SF_NOTIFY_NONSECURE_PORT | - SF_NOTIFY_PREPROC_HEADERS; - } + pVer->dwFlags = SF_NOTIFY_ORDER_HIGH | + SF_NOTIFY_SECURE_PORT | + SF_NOTIFY_NONSECURE_PORT | + SF_NOTIFY_LOG | + iis_info.filter_notify_event; - StringCbCopy(pVer->lpszFilterDesc, SF_MAX_FILTER_DESC_LEN, VERSION_STRING); + StringCbCopy(pVer->lpszFilterDesc, SF_MAX_FILTER_DESC_LEN, (VERSION_STRING)); return rv; } @@ -1165,7 +1521,7 @@ if (!is_mapread) is_inited = JK_FALSE; } - if (!is_inited && !is_mapread) { + if (!is_inited) { /* In case the initialization failed * return error. This will make entire IIS * unusable like with Apache servers @@ -1173,7 +1529,7 @@ SetLastError(ERROR_INVALID_FUNCTION); return SF_STATUS_REQ_ERROR; } - if (auth_notification_flags == dwNotificationType) { + if (iis_info.filter_notify_event == dwNotificationType) { char uri[INTERNET_MAX_URL_LENGTH]; char snuri[INTERNET_MAX_URL_LENGTH] = "/"; char Host[INTERNET_MAX_URL_LENGTH] = ""; @@ -1195,7 +1551,7 @@ DWORD szPort = sizeof(Port); DWORD szTranslate = sizeof(Translate); - if (auth_notification_flags == SF_NOTIFY_AUTH_COMPLETE) { + if (iis_info.filter_notify_event == SF_NOTIFY_AUTH_COMPLETE) { GetHeader = ((PHTTP_FILTER_AUTH_COMPLETE_INFO) pvNotification)->GetHeader; SetHeader = @@ -1447,7 +1803,7 @@ } } } - else if (is_inited && (dwNotificationType == SF_NOTIFY_LOG)) { + else if (dwNotificationType == SF_NOTIFY_LOG) { if (pfc->pFilterContext) { isapi_log_data_t *ld = (isapi_log_data_t *)pfc->pFilterContext; HTTP_FILTER_LOG *pl = (HTTP_FILTER_LOG *)pvNotification; @@ -1463,7 +1819,7 @@ { pVer->dwExtensionVersion = MAKELONG(HSE_VERSION_MINOR, HSE_VERSION_MAJOR); - StringCbCopy(pVer->lpszExtensionDesc, HSE_MAX_EXT_DLL_NAME_LEN, VERSION_STRING); + StringCbCopy(pVer->lpszExtensionDesc, HSE_MAX_EXT_DLL_NAME_LEN, (VERSION_STRING)); if (!is_inited) { @@ -1510,6 +1866,7 @@ 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; @@ -1543,8 +1900,28 @@ jk_log(logger, JK_LOG_ERROR, "service() failed with http error %d", is_error); } - lpEcb->dwHttpStatusCode = is_error; - write_error_message(lpEcb, is_error); +#ifdef CONFIGURABLE_ERROR_PAGE + /** Try to redirect the client to a page explaining the ISAPI redirector is down */ + if (error_page) { + int len_of_error_page = (int)strlen(error_page); + if (!lpEcb->ServerSupportFunction(lpEcb->ConnID, + HSE_REQ_SEND_URL_REDIRECT_RESP, + error_page, + (LPDWORD)&len_of_error_page, + (LPDWORD)NULL)) { + jk_log(logger, JK_LOG_ERROR, + "HttpExtensionProc error, Error page redirect failed with %d (0x%08x)", + GetLastError(), GetLastError()); + lpEcb->dwHttpStatusCode = is_error; + } + } + else { +#endif + lpEcb->dwHttpStatusCode = is_error; + write_error_message(lpEcb, is_error); +#ifdef CONFIGURABLE_ERROR_PAGE + } +#endif } e->done(&e, logger); } @@ -1652,6 +2029,7 @@ StringCbPrintf(WORKER_HEADER_NAME, MAX_PATH, HEADER_TEMPLATE, WORKER_HEADER_NAME_BASE, hInst); StringCbPrintf(TOMCAT_TRANSLATE_HEADER_NAME, MAX_PATH, HEADER_TEMPLATE, TOMCAT_TRANSLATE_HEADER_NAME_BASE, hInst); + /* Construct the HTTP_ headers that will be seen in ExtensionProc */ StringCbPrintf(HTTP_URI_HEADER_NAME, MAX_PATH, HTTP_HEADER_TEMPLATE, URI_HEADER_NAME_BASE, hInst); StringCbPrintf(HTTP_QUERY_HEADER_NAME, MAX_PATH, HTTP_HEADER_TEMPLATE, QUERY_HEADER_NAME_BASE, hInst); StringCbPrintf(HTTP_WORKER_HEADER_NAME, MAX_PATH, HTTP_HEADER_TEMPLATE, WORKER_HEADER_NAME_BASE, hInst); @@ -1701,17 +2079,120 @@ return 0; } +#ifdef AUTOMATIC_POOL_SIZE +static int read_registry_pool_thread_limit(size_t *pool_threads) +{ +#define IIS_PARAMETERS_LOCATION ("SYSTEM\\CurrentControlSet\\Services\\InetInfo\\Parameters") +#define IIS_MAX_POOL_THREADS_KEY ("PoolThreadLimit") + + HKEY hkey; + int rc = JK_FALSE; + DWORD regPtl; + + JK_TRACE_ENTER(logger); + if (JK_IS_DEBUG_LEVEL(logger)) { + jk_log(logger, JK_LOG_DEBUG, "Checking registry for PoolThreadLimit override." ); + } + rc = RegOpenKeyEx(HKEY_LOCAL_MACHINE, + IIS_PARAMETERS_LOCATION, (DWORD) 0, KEY_READ, &hkey); + + if (ERROR_SUCCESS != rc) { + if (JK_IS_DEBUG_LEVEL(logger)) { + jk_log(logger, JK_LOG_DEBUG, "Unable to open registry for reading." ); + } + JK_TRACE_EXIT(logger); + return JK_FALSE; + } + + if (get_registry_config_number(hkey, IIS_MAX_POOL_THREADS_KEY, ®Ptl )) { + (*pool_threads) = (unsigned)regPtl; + if (JK_IS_DEBUG_LEVEL(logger)) { + jk_log(logger, JK_LOG_DEBUG, "PoolThreadLimit override of %d located in registry.", + (*pool_threads) ); + } + rc = JK_TRUE; + } else { + if (JK_IS_DEBUG_LEVEL(logger)) { + jk_log(logger, JK_LOG_DEBUG, "PoolThreadLimit setting not found in registry." ); + } + rc = JK_FALSE; + } + + RegCloseKey( hkey ); + + JK_TRACE_EXIT(logger); + return rc; +} + +static int determine_iis_thread_count() +{ +#define CACHE_SIZE_WORKSTATION 10 + OSVERSIONINFOEX verinfo; + size_t cache_size; + + JK_TRACE_ENTER(logger); + if (JK_IS_DEBUG_LEVEL(logger)) { + jk_log(logger, JK_LOG_DEBUG, "Attempting to set connection_pool_size to suit current OS settings." ); + } + + /* Check the type of OS we're using */ + ZeroMemory(&verinfo, sizeof(OSVERSIONINFOEX)); + verinfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); + if (GetVersionEx((LPOSVERSIONINFO)(&verinfo))) { + /* For Server OSes, determine PoolThreadLimit */ + if (verinfo.wProductType == VER_NT_DOMAIN_CONTROLLER || verinfo.wProductType == VER_NT_SERVER) { + if (JK_IS_DEBUG_LEVEL(logger)) { + jk_log(logger, JK_LOG_DEBUG, "Current OS detected as Server OS - will try to detect PoolThreadLimit" ); + } + + /* Check in the registry first for an override */ + if (!read_registry_pool_thread_limit( &cache_size )) { + /* Otherwise, Calculate default cache size as 2*MB RAM, capped at 256 */ + MEMORYSTATUS memstat; + GlobalMemoryStatus(&memstat); + cache_size = 2 * memstat.dwTotalPhys / (1024*1024); + if (cache_size > 256 ) + cache_size = 256; + if (JK_IS_DEBUG_LEVEL(logger)) { + jk_log(logger, JK_LOG_DEBUG, "PoolThreadLimit not in registry - " + "calculating default connection_pool_size of MIN(2*MB,256) as %u", + cache_size ); + } + } + } else { + /* We have a Workstation/Pro version running PWS */ + if (JK_IS_DEBUG_LEVEL(logger)) { + jk_log(logger, JK_LOG_DEBUG, "Current OS is a Workstation/Pro - " + "using PWS connection_pool_size of %d", + CACHE_SIZE_WORKSTATION ); + } + cache_size = CACHE_SIZE_WORKSTATION; + } + } else { + /* Assume Workstation - something is probably wrong here */ + jk_log(logger, JK_LOG_WARNING, "Unable to detect current OS - assuming Workstation/Pro - " + "using PWS connection_pool_size of %d", CACHE_SIZE_WORKSTATION ); + cache_size = CACHE_SIZE_WORKSTATION; + } + JK_TRACE_EXIT(logger); + return (int)cache_size; +} +#endif + static int init_jk(char *serverName) { char shm_name[MAX_PATH]; int rc = JK_FALSE; +#ifdef AUTOMATIC_POOL_SIZE + int def_cache_size; +#endif if (!jk_open_file_logger(&logger, log_file, log_level)) { logger = NULL; } StringCbCopy(shm_name, MAX_PATH, SHM_DEF_NAME); - jk_log(logger, JK_LOG_INFO, "Starting %s", VERSION_STRING ); + jk_log(logger, JK_LOG_INFO, "Starting %s", (VERSION_STRING)); if (*serverName) { size_t i; @@ -1724,11 +2205,23 @@ } } +#ifdef AUTOMATIC_POOL_SIZE + def_cache_size = determine_iis_thread_count(); + if (JK_IS_DEBUG_LEVEL(logger)) { + jk_log(logger, JK_LOG_DEBUG, "Setting default connection_pool_size to %d", + def_cache_size ); + } + jk_set_worker_def_cache_size(def_cache_size); + jk_log(logger, JK_LOG_INFO, "Using a default of %d connections per pool", + def_cache_size); +#else jk_set_worker_def_cache_size(DEFAULT_WORKER_THREADS); +#endif /* Logging the initialization type: registry or properties file in virtual dir */ if (JK_IS_DEBUG_LEVEL(logger)) { + jk_log(logger, JK_LOG_DEBUG, "Detected IIS version %d.%d", iis_info.major, iis_info.minor); if (using_ini_file) { jk_log(logger, JK_LOG_DEBUG, "Using ini file %s.", ini_file_name); } @@ -1745,7 +2238,20 @@ 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%s chunked encoding.", (chunked_encoding_enabled ? "" : " no")); + jk_log(logger, JK_LOG_DEBUG, "Using notification event %s (0x%08x)", + (iis_info.filter_notify_event == SF_NOTIFY_AUTH_COMPLETE) ? + "SF_NOTIFY_AUTH_COMPLETE" : + ((iis_info.filter_notify_event == SF_NOTIFY_PREPROC_HEADERS) ? + "SF_NOTIFY_PREPROC_HEADERS" : "UNKNOWN"), + iis_info.filter_notify_event); + +#ifdef CONFIGURABLE_ERROR_PAGE + if (error_page) { + jk_log(logger, JK_LOG_DEBUG, "Using error page '%s'.", error_page); + } +#endif jk_log(logger, JK_LOG_DEBUG, "Using uri header %s.", URI_HEADER_NAME); jk_log(logger, JK_LOG_DEBUG, "Using query header %s.", QUERY_HEADER_NAME); jk_log(logger, JK_LOG_DEBUG, "Using worker header %s.", WORKER_HEADER_NAME); @@ -1855,12 +2361,11 @@ watchdog_handle = CreateThread(NULL, 0, watchdog_thread, NULL, 0, &wi); if (!watchdog_handle) { - rc = GetLastError(); - jk_log(logger, JK_LOG_ERROR, "Error creating Watchdog thread"); - return rc; + jk_log(logger, JK_LOG_EMERG, "Error %d (0x%08x) creating Watchdog thread", + GetLastError(), GetLastError()); } } - jk_log(logger, JK_LOG_INFO, "%s initialized", (VERSION_STRING) ); + jk_log(logger, JK_LOG_INFO, "%s initialized", (VERSION_STRING)); } return rc; } @@ -1869,7 +2374,9 @@ { if (read_registry_init_data()) { - auth_notification_flags = get_auth_flags(); + if (get_iis_info(&iis_info) != JK_TRUE) { + jk_log(logger, JK_LOG_ERROR, "Could not retrieve IIS version from registry"); + } is_inited = JK_TRUE; } return is_inited; @@ -1944,9 +2451,20 @@ shm_config_size = (size_t) get_config_int(src, SHM_SIZE_TAG, 0); 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); +#ifndef AUTOMATIC_AUTH_NOTIFICATION use_auth_notification_flags = get_config_int(src, AUTH_COMPLETE_TAG, 1); +#endif reject_unsafe = get_config_bool(src, REJECT_UNSAFE_TAG, JK_FALSE); watchdog_interval = get_config_int(src, WATCHDOG_INTERVAL_TAG, 0); +#ifdef ALLOW_CHUNKING + chunked_encoding_enabled = get_config_bool(src, ENABLE_CHUNKED_ENCODING_TAG, JK_FALSE); +#endif +#ifdef CONFIGURABLE_ERROR_PAGE + if (get_config_parameter(src, ERROR_PAGE_TAG, error_page_buf, sizeof(error_page_buf))) { + error_page = error_page_buf; + } +#endif + if (using_ini_file) { jk_map_free(&map); } @@ -2035,7 +2553,7 @@ return JK_FALSE; } - *val = data; + *val = (int)data; return JK_TRUE; } @@ -2052,6 +2570,7 @@ s->start_response = start_response; s->read = iis_read; s->write = iis_write; + s->done = iis_done; if (!(huge_buf = jk_pool_alloc(&private_data->p, MAX_PACKET_SIZE))) { JK_TRACE_EXIT(logger); @@ -2195,7 +2714,6 @@ if (cnt) { char *headers_buf = huge_buf; unsigned int i; - size_t len_of_http_prefix = strlen("HTTP_"); BOOL need_content_length_header = (s->content_length == 0); cnt -= 2; /* For our two special headers: @@ -2216,12 +2734,13 @@ for (i = 0, tmp = headers_buf; *tmp && i < cnt;) { int real_header = JK_TRUE; - /* Skipp the HTTP_ prefix to the beginning of th header name */ - tmp += len_of_http_prefix; + /* Skip the HTTP_ prefix to the beginning of the header name */ + tmp += HTTP_HEADER_PREFIX_LEN; if (!strnicmp(tmp, URI_HEADER_NAME, strlen(URI_HEADER_NAME)) || !strnicmp(tmp, WORKER_HEADER_NAME, strlen(WORKER_HEADER_NAME))) { + /* Skip redirector headers */ real_header = JK_FALSE; } else if (!strnicmp(tmp, QUERY_HEADER_NAME, @@ -2258,7 +2777,7 @@ *tmp = '\0'; tmp++; - /* Skip all the WS chars after the ':' to the beginning of th header value */ + /* Skip all the WS chars after the ':' to the beginning of the header value */ while (' ' == *tmp || '\t' == *tmp || '\v' == *tmp) { tmp++; } @@ -2273,7 +2792,7 @@ *tmp = '\0'; tmp++; - /* skipp CR LF */ + /* skip CR LF */ while (*tmp == '\n' || *tmp == '\r') { tmp++; } @@ -2409,29 +2928,45 @@ return (int)(p - encoded); } -static int get_auth_flags() +/** +* Determine version info and the primary notification event +*/ +static int get_iis_info(iis_info_t* iis_info) { HKEY hkey; long rc; - int maj, sz; - int rv = SF_NOTIFY_PREPROC_HEADERS; - int use_auth = JK_FALSE; - /* Retreive the IIS version Major */ + int rv = JK_FALSE; + + iis_info->major = 0; + iis_info->minor = 0; + iis_info->filter_notify_event = SF_NOTIFY_PREPROC_HEADERS; + + /* Retrieve the IIS version Major/Minor */ rc = RegOpenKeyEx(HKEY_LOCAL_MACHINE, W3SVC_REGISTRY_KEY, (DWORD) 0, KEY_READ, &hkey); - if (ERROR_SUCCESS != rc) { - return rv; - } - sz = sizeof(int); - rc = RegQueryValueEx(hkey, "MajorVersion", NULL, NULL, - (LPBYTE) & maj, &sz); - if (ERROR_SUCCESS != rc) { - CloseHandle(hkey); - return rv; + if (ERROR_SUCCESS == rc) { + if (get_registry_config_number(hkey, "MajorVersion", &iis_info->major) == JK_TRUE) { +#ifdef AUTOMATIC_AUTH_NOTIFICATION + if (iis_info->major > 4) +#else + if (use_auth_notification_flags && iis_info->major > 4) +#endif + iis_info->filter_notify_event = SF_NOTIFY_AUTH_COMPLETE; + if (get_registry_config_number(hkey, "MinorVersion", &iis_info->minor) == JK_TRUE) { + +#ifdef AUTOMATIC_AUTH_NOTIFICATION + /* SF_NOTIFY_AUTH_COMPLETE causes redirect failures + * (ERROR_INVALID_PARAMETER) on IIS 5.1 with OPTIONS/PUT + * and is only available from IIS 5+ + */ + if (iis_info->major == 5 && iis_info->minor == 1) { + iis_info->filter_notify_event = SF_NOTIFY_PREPROC_HEADERS; + } +#endif + rv = JK_TRUE; + } + } } CloseHandle(hkey); - if (use_auth_notification_flags && maj > 4) - rv = SF_NOTIFY_AUTH_COMPLETE; - return rv; } --------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]