| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| /*************************************************************************** |
| * Description: common stuff for bi-directional protocols ajp13/ajp14. * |
| * Author: Gal Shachor <shachor@il.ibm.com> * |
| * Author: Henri Gomez <hgomez@apache.org> * |
| * Version: $Revision$ * |
| ***************************************************************************/ |
| |
| |
| #include "jk_global.h" |
| #include "jk_util.h" |
| #include "jk_ajp13.h" |
| #include "jk_ajp14.h" |
| #include "jk_ajp_common.h" |
| #include "jk_connect.h" |
| #if defined(AS400) && !defined(AS400_UTF8) |
| #include "util_ebcdic.h" |
| #endif |
| #if defined(NETWARE) && defined(__NOVELL_LIBC__) |
| #include "novsock2.h" |
| #endif |
| |
| const char *response_trans_headers[] = { |
| "Content-Type", |
| "Content-Language", |
| "Content-Length", |
| "Date", |
| "Last-Modified", |
| "Location", |
| "Set-Cookie", |
| "Set-Cookie2", |
| "Servlet-Engine", |
| "Status", |
| "WWW-Authenticate" |
| }; |
| |
| static const char *long_res_header_for_sc(int sc) |
| { |
| const char *rc = NULL; |
| sc = sc & 0X00FF; |
| if (sc <= SC_RES_HEADERS_NUM && sc > 0) { |
| rc = response_trans_headers[sc - 1]; |
| } |
| |
| return rc; |
| } |
| |
| #define UNKNOWN_METHOD (-1) |
| |
| static int sc_for_req_method(const char *method, size_t len) |
| { |
| /* Note: the following code was generated by the "shilka" tool from |
| the "cocom" parsing/compilation toolkit. It is an optimized lookup |
| based on analysis of the input keywords. Postprocessing was done |
| on the shilka output, but the basic structure and analysis is |
| from there. Should new HTTP methods be added, then manual insertion |
| into this code is fine, or simply re-running the shilka tool on |
| the appropriate input. */ |
| |
| /* Note: it is also quite reasonable to just use our method_registry, |
| but I'm assuming (probably incorrectly) we want more speed here |
| (based on the optimizations the previous code was doing). */ |
| |
| switch (len) |
| { |
| case 3: |
| switch (method[0]) |
| { |
| case 'A': |
| return (method[1] == 'C' |
| && method[2] == 'L' |
| ? SC_M_ACL : UNKNOWN_METHOD); |
| case 'P': |
| return (method[1] == 'U' |
| && method[2] == 'T' |
| ? SC_M_PUT : UNKNOWN_METHOD); |
| case 'G': |
| return (method[1] == 'E' |
| && method[2] == 'T' |
| ? SC_M_GET : UNKNOWN_METHOD); |
| default: |
| return UNKNOWN_METHOD; |
| } |
| |
| case 4: |
| switch (method[0]) |
| { |
| case 'H': |
| return (method[1] == 'E' |
| && method[2] == 'A' |
| && method[3] == 'D' |
| ? SC_M_HEAD : UNKNOWN_METHOD); |
| case 'P': |
| return (method[1] == 'O' |
| && method[2] == 'S' |
| && method[3] == 'T' |
| ? SC_M_POST : UNKNOWN_METHOD); |
| case 'M': |
| return (method[1] == 'O' |
| && method[2] == 'V' |
| && method[3] == 'E' |
| ? SC_M_MOVE : UNKNOWN_METHOD); |
| case 'L': |
| return (method[1] == 'O' |
| && method[2] == 'C' |
| && method[3] == 'K' |
| ? SC_M_LOCK : UNKNOWN_METHOD); |
| case 'C': |
| return (method[1] == 'O' |
| && method[2] == 'P' |
| && method[3] == 'Y' |
| ? SC_M_COPY : UNKNOWN_METHOD); |
| default: |
| return UNKNOWN_METHOD; |
| } |
| |
| case 5: |
| switch (method[2]) |
| { |
| case 'R': |
| return (memcmp(method, "MERGE", 5) == 0 |
| ? SC_M_MERGE : UNKNOWN_METHOD); |
| case 'C': |
| return (memcmp(method, "MKCOL", 5) == 0 |
| ? SC_M_MKCOL : UNKNOWN_METHOD); |
| case 'B': |
| return (memcmp(method, "LABEL", 5) == 0 |
| ? SC_M_LABEL : UNKNOWN_METHOD); |
| case 'A': |
| return (memcmp(method, "TRACE", 5) == 0 |
| ? SC_M_TRACE : UNKNOWN_METHOD); |
| default: |
| return UNKNOWN_METHOD; |
| } |
| |
| case 6: |
| switch (method[0]) |
| { |
| case 'U': |
| switch (method[5]) |
| { |
| case 'K': |
| return (memcmp(method, "UNLOCK", 6) == 0 |
| ? SC_M_UNLOCK : UNKNOWN_METHOD); |
| case 'E': |
| return (memcmp(method, "UPDATE", 6) == 0 |
| ? SC_M_UPDATE : UNKNOWN_METHOD); |
| default: |
| return UNKNOWN_METHOD; |
| } |
| case 'R': |
| return (memcmp(method, "REPORT", 6) == 0 |
| ? SC_M_REPORT : UNKNOWN_METHOD); |
| case 'S': |
| return (memcmp(method, "SEARCH", 6) == 0 |
| ? SC_M_SEARCH : UNKNOWN_METHOD); |
| case 'D': |
| return (memcmp(method, "DELETE", 6) == 0 |
| ? SC_M_DELETE : UNKNOWN_METHOD); |
| default: |
| return UNKNOWN_METHOD; |
| } |
| |
| case 7: |
| switch (method[1]) |
| { |
| case 'P': |
| return (memcmp(method, "OPTIONS", 7) == 0 |
| ? SC_M_OPTIONS : UNKNOWN_METHOD); |
| case 'H': |
| return (memcmp(method, "CHECKIN", 7) == 0 |
| ? SC_M_CHECKIN : UNKNOWN_METHOD); |
| default: |
| return UNKNOWN_METHOD; |
| } |
| |
| case 8: |
| switch (method[0]) |
| { |
| case 'P': |
| return (memcmp(method, "PROPFIND", 8) == 0 |
| ? SC_M_PROPFIND : UNKNOWN_METHOD); |
| case 'C': |
| return (memcmp(method, "CHECKOUT", 8) == 0 |
| ? SC_M_CHECKOUT : UNKNOWN_METHOD); |
| default: |
| return UNKNOWN_METHOD; |
| } |
| |
| case 9: |
| return (memcmp(method, "PROPPATCH", 9) == 0 |
| ? SC_M_PROPPATCH : UNKNOWN_METHOD); |
| |
| case 10: |
| switch (method[0]) |
| { |
| case 'U': |
| return (memcmp(method, "UNCHECKOUT", 10) == 0 |
| ? SC_M_UNCHECKOUT : UNKNOWN_METHOD); |
| case 'M': |
| return (memcmp(method, "MKACTIVITY", 10) == 0 |
| ? SC_M_MKACTIVITY : UNKNOWN_METHOD); |
| default: |
| return UNKNOWN_METHOD; |
| } |
| |
| case 11: |
| return (memcmp(method, "MKWORKSPACE", 11) == 0 |
| ? SC_M_MKWORKSPACE : UNKNOWN_METHOD); |
| |
| case 15: |
| return (memcmp(method, "VERSION-CONTROL", 15) == 0 |
| ? SC_M_VERSION_CONTROL : UNKNOWN_METHOD); |
| |
| case 16: |
| return (memcmp(method, "BASELINE-CONTROL", 16) == 0 |
| ? SC_M_BASELINE_CONTROL : UNKNOWN_METHOD); |
| |
| default: |
| return UNKNOWN_METHOD; |
| } |
| |
| /* NOTREACHED */ |
| } |
| |
| static int sc_for_req_header(const char *header_name) |
| { |
| char header[16]; |
| size_t len = strlen(header_name); |
| const char *p = header_name; |
| int i = 0; |
| |
| /* ACCEPT-LANGUAGE is the longest headeer |
| * that is of interest. |
| */ |
| if (len < 4 || len > 15) |
| return UNKNOWN_METHOD; |
| |
| while (*p) |
| header[i++] = toupper((unsigned char)*p++); |
| header[i] = '\0'; |
| p = &header[1]; |
| |
| switch (header[0]) { |
| case 'A': |
| if (memcmp(p, "CCEPT", 5) == 0) { |
| if (!header[6]) |
| return SC_ACCEPT; |
| else if (header[6] == '-') { |
| p += 6; |
| if (memcmp(p, "CHARSET", 7) == 0) |
| return SC_ACCEPT_CHARSET; |
| else if (memcmp(p, "ENCODING", 8) == 0) |
| return SC_ACCEPT_ENCODING; |
| else if (memcmp(p, "LANGUAGE", 8) == 0) |
| return SC_ACCEPT_LANGUAGE; |
| else |
| return UNKNOWN_METHOD; |
| } |
| else |
| return UNKNOWN_METHOD; |
| } |
| else if (memcmp(p, "UTHORIZATION", 12) == 0) |
| return SC_AUTHORIZATION; |
| else |
| return UNKNOWN_METHOD; |
| break; |
| case 'C': |
| if(memcmp(p, "OOKIE2", 6) == 0) |
| return SC_COOKIE2; |
| else if (memcmp(p, "OOKIE", 5) == 0) |
| return SC_COOKIE; |
| else if(memcmp(p, "ONNECTION", 9) == 0) |
| return SC_CONNECTION; |
| else if(memcmp(p, "ONTENT-TYPE", 11) == 0) |
| return SC_CONTENT_TYPE; |
| else if(memcmp(p, "ONTENT-LENGTH", 13) == 0) |
| return SC_CONTENT_LENGTH; |
| else |
| return UNKNOWN_METHOD; |
| break; |
| case 'H': |
| if(memcmp(p, "OST", 3) == 0) |
| return SC_HOST; |
| else |
| return UNKNOWN_METHOD; |
| break; |
| case 'P': |
| if(memcmp(p, "RAGMA", 5) == 0) |
| return SC_PRAGMA; |
| else |
| return UNKNOWN_METHOD; |
| break; |
| case 'R': |
| if(memcmp(p, "EFERER", 6) == 0) |
| return SC_REFERER; |
| else |
| return UNKNOWN_METHOD; |
| break; |
| case 'U': |
| if(memcmp(p, "SER-AGENT", 9) == 0) |
| return SC_USER_AGENT; |
| else |
| return UNKNOWN_METHOD; |
| break; |
| default: |
| return UNKNOWN_METHOD; |
| } |
| /* NOTREACHED */ |
| } |
| |
| |
| /* |
| * Message structure |
| * |
| * |
| AJPV13_REQUEST/AJPV14_REQUEST= |
| request_prefix (1) (byte) |
| method (byte) |
| protocol (string) |
| req_uri (string) |
| remote_addr (string) |
| remote_host (string) |
| server_name (string) |
| server_port (short) |
| is_ssl (boolean) |
| num_headers (short) |
| num_headers*(req_header_name header_value) |
| |
| ?context (byte)(string) |
| ?servlet_path (byte)(string) |
| ?remote_user (byte)(string) |
| ?auth_type (byte)(string) |
| ?query_string (byte)(string) |
| ?route (byte)(string) |
| ?ssl_cert (byte)(string) |
| ?ssl_cipher (byte)(string) |
| ?ssl_session (byte)(string) |
| ?ssl_key_size (byte)(int) via JkOptions +ForwardKeySize |
| request_terminator (byte) |
| ?body content_length*(var binary) |
| |
| */ |
| |
| static int ajp_marshal_into_msgb(jk_msg_buf_t *msg, |
| jk_ws_service_t *s, |
| jk_logger_t *l, ajp_endpoint_t * ae) |
| { |
| int method; |
| unsigned int i; |
| |
| JK_TRACE_ENTER(l); |
| |
| if ((method = sc_for_req_method(s->method, |
| strlen(s->method))) == UNKNOWN_METHOD) |
| method = SC_M_JK_STORED; |
| |
| if (jk_b_append_byte(msg, JK_AJP13_FORWARD_REQUEST) || |
| jk_b_append_byte(msg, (unsigned char)method) || |
| jk_b_append_string(msg, s->protocol) || |
| jk_b_append_string(msg, s->req_uri) || |
| jk_b_append_string(msg, s->remote_addr) || |
| jk_b_append_string(msg, s->remote_host) || |
| jk_b_append_string(msg, s->server_name) || |
| jk_b_append_int(msg, (unsigned short)s->server_port) || |
| jk_b_append_byte(msg, (unsigned char)(s->is_ssl)) || |
| jk_b_append_int(msg, (unsigned short)(s->num_headers))) { |
| |
| jk_log(l, JK_LOG_ERROR, |
| "failed appending the message begining"); |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |
| |
| for (i = 0; i < s->num_headers; i++) { |
| int sc; |
| |
| if ((sc = sc_for_req_header(s->headers_names[i])) != UNKNOWN_METHOD) { |
| if (jk_b_append_int(msg, (unsigned short)sc)) { |
| jk_log(l, JK_LOG_ERROR, |
| "failed appending the header name"); |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |
| } |
| else { |
| if (jk_b_append_string(msg, s->headers_names[i])) { |
| jk_log(l, JK_LOG_ERROR, |
| "failed appending the header name"); |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |
| } |
| |
| if (jk_b_append_string(msg, s->headers_values[i])) { |
| jk_log(l, JK_LOG_ERROR, |
| "failed appending the header value"); |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |
| } |
| |
| if (s->secret) { |
| if (jk_b_append_byte(msg, SC_A_SECRET) || |
| jk_b_append_string(msg, s->secret)) { |
| jk_log(l, JK_LOG_ERROR, |
| "failed appending secret"); |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |
| } |
| |
| if (s->remote_user) { |
| if (jk_b_append_byte(msg, SC_A_REMOTE_USER) || |
| jk_b_append_string(msg, s->remote_user)) { |
| jk_log(l, JK_LOG_ERROR, |
| "failed appending the remote user"); |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |
| } |
| if (s->auth_type) { |
| if (jk_b_append_byte(msg, SC_A_AUTH_TYPE) || |
| jk_b_append_string(msg, s->auth_type)) { |
| jk_log(l, JK_LOG_ERROR, |
| "failed appending the auth type"); |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |
| } |
| if (s->query_string) { |
| if (jk_b_append_byte(msg, SC_A_QUERY_STRING) || |
| #if defined(AS400) && !defined(AS400_UTF8) |
| jk_b_append_asciistring(msg, s->query_string)) { |
| #else |
| jk_b_append_string(msg, s->query_string)) { |
| #endif |
| jk_log(l, JK_LOG_ERROR, |
| "failed appending the query string"); |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |
| } |
| if (s->route) { |
| if (jk_b_append_byte(msg, SC_A_ROUTE) || |
| jk_b_append_string(msg, s->route)) { |
| jk_log(l, JK_LOG_ERROR, |
| "failed appending the route"); |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |
| } |
| if (s->ssl_cert_len) { |
| if (jk_b_append_byte(msg, SC_A_SSL_CERT) || |
| jk_b_append_string(msg, s->ssl_cert)) { |
| jk_log(l, JK_LOG_ERROR, |
| "failed appending the SSL certificates"); |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |
| } |
| |
| if (s->ssl_cipher) { |
| if (jk_b_append_byte(msg, SC_A_SSL_CIPHER) || |
| jk_b_append_string(msg, s->ssl_cipher)) { |
| jk_log(l, JK_LOG_ERROR, |
| "failed appending the SSL ciphers"); |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |
| } |
| if (s->ssl_session) { |
| if (jk_b_append_byte(msg, SC_A_SSL_SESSION) || |
| jk_b_append_string(msg, s->ssl_session)) { |
| jk_log(l, JK_LOG_ERROR, |
| "failed appending the SSL session"); |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |
| } |
| |
| /* |
| * ssl_key_size is required by Servlet 2.3 API |
| * added support only in ajp14 mode |
| * JFC removed: ae->proto == AJP14_PROTO |
| */ |
| if (s->ssl_key_size != -1) { |
| if (jk_b_append_byte(msg, SC_A_SSL_KEY_SIZE) || |
| jk_b_append_int(msg, (unsigned short)s->ssl_key_size)) { |
| jk_log(l, JK_LOG_ERROR, |
| "failed appending the SSL key size"); |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |
| } |
| |
| /* If the method was unrecognized, encode it as an attribute */ |
| if (method == SC_M_JK_STORED) { |
| if (JK_IS_DEBUG_LEVEL(l)) |
| jk_log(l, JK_LOG_DEBUG, "unknown method %s", s->method); |
| if (jk_b_append_byte(msg, SC_A_STORED_METHOD) || |
| jk_b_append_string(msg, s->method)) { |
| jk_log(l, JK_LOG_ERROR, |
| "failed appending the request method"); |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |
| } |
| |
| if (s->num_attributes > 0) { |
| for (i = 0; i < s->num_attributes; i++) { |
| if (jk_b_append_byte(msg, SC_A_REQ_ATTRIBUTE) || |
| jk_b_append_string(msg, s->attributes_names[i]) || |
| jk_b_append_string(msg, s->attributes_values[i])) { |
| jk_log(l, JK_LOG_ERROR, |
| "failed appending attribute %s=%s", |
| s->attributes_names[i], s->attributes_values[i]); |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |
| } |
| } |
| |
| if (jk_b_append_byte(msg, SC_A_ARE_DONE)) { |
| jk_log(l, JK_LOG_ERROR, |
| "failed appending the message end"); |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |
| |
| if (JK_IS_DEBUG_LEVEL(l)) |
| jk_log(l, JK_LOG_DEBUG, "ajp marshaling done"); |
| JK_TRACE_EXIT(l); |
| return JK_TRUE; |
| } |
| |
| /* |
| AJPV13_RESPONSE/AJPV14_RESPONSE:= |
| response_prefix (2) |
| status (short) |
| status_msg (short) |
| num_headers (short) |
| num_headers*(res_header_name header_value) |
| *body_chunk |
| terminator boolean <! -- recycle connection or not --> |
| |
| req_header_name := |
| sc_req_header_name | (string) |
| |
| res_header_name := |
| sc_res_header_name | (string) |
| |
| header_value := |
| (string) |
| |
| body_chunk := |
| length (short) |
| body length*(var binary) |
| |
| */ |
| |
| |
| static int ajp_unmarshal_response(jk_msg_buf_t *msg, |
| jk_res_data_t * d, |
| ajp_endpoint_t * ae, jk_logger_t *l) |
| { |
| jk_pool_t *p = &ae->pool; |
| |
| d->status = jk_b_get_int(msg); |
| JK_TRACE_ENTER(l); |
| |
| if (!d->status) { |
| jk_log(l, JK_LOG_ERROR, |
| "NULL status"); |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |
| |
| d->msg = (char *)jk_b_get_string(msg); |
| if (d->msg) { |
| #if (defined(AS400) && !defined(AS400_UTF8)) || defined(_OSD_POSIX) |
| jk_xlate_from_ascii(d->msg, strlen(d->msg)); |
| #endif |
| } |
| |
| if (JK_IS_DEBUG_LEVEL(l)) |
| jk_log(l, JK_LOG_DEBUG, |
| "status = %d", d->status); |
| |
| d->num_headers = jk_b_get_int(msg); |
| d->header_names = d->header_values = NULL; |
| |
| if (JK_IS_DEBUG_LEVEL(l)) |
| jk_log(l, JK_LOG_DEBUG, |
| "Number of headers is = %d", |
| d->num_headers); |
| |
| if (d->num_headers) { |
| d->header_names = jk_pool_alloc(p, sizeof(char *) * d->num_headers); |
| d->header_values = jk_pool_alloc(p, sizeof(char *) * d->num_headers); |
| |
| if (d->header_names && d->header_values) { |
| unsigned int i; |
| for (i = 0; i < d->num_headers; i++) { |
| unsigned short name = jk_b_pget_int(msg, msg->pos); |
| |
| if ((name & 0XFF00) == 0XA000) { |
| jk_b_get_int(msg); |
| name = name & 0X00FF; |
| if (name <= SC_RES_HEADERS_NUM) { |
| d->header_names[i] = |
| (char *)long_res_header_for_sc(name); |
| } |
| else { |
| jk_log(l, JK_LOG_ERROR, |
| "No such sc (%d)", name); |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |
| } |
| else { |
| d->header_names[i] = (char *)jk_b_get_string(msg); |
| if (!d->header_names[i]) { |
| jk_log(l, JK_LOG_ERROR, |
| "NULL header name"); |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |
| #if (defined(AS400) && !defined(AS400_UTF8)) || defined(_OSD_POSIX) |
| jk_xlate_from_ascii(d->header_names[i], |
| strlen(d->header_names[i])); |
| #endif |
| |
| } |
| |
| d->header_values[i] = (char *)jk_b_get_string(msg); |
| if (!d->header_values[i]) { |
| jk_log(l, JK_LOG_ERROR, |
| "NULL header value"); |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |
| |
| #if (defined(AS400) && !defined(AS400_UTF8)) || defined(_OSD_POSIX) |
| jk_xlate_from_ascii(d->header_values[i], |
| strlen(d->header_values[i])); |
| #endif |
| |
| if (JK_IS_DEBUG_LEVEL(l)) |
| jk_log(l, JK_LOG_DEBUG, |
| "Header[%d] [%s] = [%s]", |
| i, d->header_names[i], d->header_values[i]); |
| } |
| } |
| } |
| |
| JK_TRACE_EXIT(l); |
| return JK_TRUE; |
| } |
| |
| /* |
| * Reset the endpoint (clean buf and close socket) |
| */ |
| |
| static void ajp_reset_endpoint(ajp_endpoint_t * ae, jk_logger_t *l) |
| { |
| if (IS_VALID_SOCKET(ae->sd) && !ae->reuse) { |
| jk_close_socket(ae->sd); |
| if (JK_IS_DEBUG_LEVEL(l)) |
| jk_log(l, JK_LOG_DEBUG, |
| "reset socket with sd = %u", ae->sd ); |
| ae->sd = JK_INVALID_SOCKET; |
| } |
| jk_reset_pool(&(ae->pool)); |
| } |
| |
| /* |
| * Close the endpoint (close pool and close socket) |
| */ |
| |
| void ajp_close_endpoint(ajp_endpoint_t * ae, jk_logger_t *l) |
| { |
| JK_TRACE_ENTER(l); |
| |
| if (IS_VALID_SOCKET(ae->sd)) { |
| jk_shutdown_socket(ae->sd); |
| if (JK_IS_DEBUG_LEVEL(l)) |
| jk_log(l, JK_LOG_DEBUG, |
| "closed socket with sd = %d", ae->sd); |
| ae->sd = JK_INVALID_SOCKET; |
| } |
| |
| jk_close_pool(&(ae->pool)); |
| free(ae); |
| JK_TRACE_EXIT(l); |
| } |
| |
| |
| /* |
| * Try another connection from cache |
| */ |
| |
| static void ajp_next_connection(ajp_endpoint_t *ae, jk_logger_t *l) |
| { |
| int rc; |
| ajp_worker_t *aw = ae->worker; |
| jk_sock_t sock = ae->sd; |
| |
| /* Mark existing endpoint socket as closed */ |
| ae->sd = JK_INVALID_SOCKET; |
| JK_ENTER_CS(&aw->cs, rc); |
| if (rc) { |
| unsigned int i; |
| for (i = 0; i < aw->ep_cache_sz; i++) { |
| /* Find cache slot with usable socket */ |
| if (aw->ep_cache[i] && IS_VALID_SOCKET(aw->ep_cache[i]->sd)) { |
| ae->sd = aw->ep_cache[i]->sd; |
| aw->ep_cache[i]->sd = JK_INVALID_SOCKET; |
| break; |
| } |
| } |
| JK_LEAVE_CS(&aw->cs, rc); |
| } |
| /* Close previous socket */ |
| if (IS_VALID_SOCKET(sock)) |
| jk_close_socket(sock); |
| } |
| |
| /* |
| * Wait input event on ajp_endpoint for timeout ms |
| */ |
| static int ajp_is_input_event(ajp_endpoint_t * ae, int timeout, jk_logger_t *l) |
| { |
| fd_set rset; |
| struct timeval tv; |
| int rc; |
| |
| FD_ZERO(&rset); |
| FD_SET(ae->sd, &rset); |
| tv.tv_sec = timeout / 1000; |
| tv.tv_usec = (timeout % 1000) * 1000; |
| |
| do { |
| rc = select((int)ae->sd + 1, &rset, NULL, NULL, &tv); |
| } while (rc < 0 && errno == EINTR); |
| |
| ae->last_errno = 0; |
| if (rc == 0) { |
| /* Timeout. Set the errno to timeout */ |
| #if defined(WIN32) || (defined(NETWARE) && defined(__NOVELL_LIBC__)) |
| errno = WSAETIMEDOUT - WSABASEERR; |
| #else |
| errno = ETIMEDOUT; |
| #endif |
| ae->last_errno = errno; |
| return JK_FALSE; |
| } |
| else if (rc < 0) { |
| ae->last_errno = errno; |
| jk_log(l, JK_LOG_WARNING, |
| "error during select (errno=%d)", ae->last_errno); |
| return JK_FALSE; |
| } |
| else |
| return JK_TRUE; |
| } |
| |
| |
| /* |
| * Handle the CPING/CPONG initial query |
| */ |
| static int ajp_handle_cping_cpong(ajp_endpoint_t * ae, int timeout, jk_logger_t *l) |
| { |
| int cmd; |
| jk_msg_buf_t *msg; |
| |
| JK_TRACE_ENTER(l); |
| msg = jk_b_new(&ae->pool); |
| jk_b_set_buffer_size(msg, 16); /* 16 is way too large but I'm lazy :-) */ |
| jk_b_reset(msg); |
| jk_b_append_byte(msg, AJP13_CPING_REQUEST); |
| |
| /* Send CPing query */ |
| if (ajp_connection_tcp_send_message(ae, msg, l) != JK_TRUE) { |
| jk_log(l, JK_LOG_INFO, |
| "can't send cping query"); |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |
| |
| /* wait for Pong reply for timeout milliseconds |
| */ |
| if (ajp_is_input_event(ae, timeout, l) == JK_FALSE) { |
| jk_log(l, JK_LOG_INFO, "timeout in reply pong"); |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |
| |
| /* Read and check for Pong reply |
| */ |
| if (ajp_connection_tcp_get_message(ae, msg, l) != JK_TRUE) { |
| jk_log(l, JK_LOG_INFO, |
| "awaited reply cpong, not received"); |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |
| |
| if ((cmd = jk_b_get_byte(msg)) != AJP13_CPONG_REPLY) { |
| jk_log(l, JK_LOG_INFO, |
| "awaited reply cpong, received %d instead", |
| cmd); |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |
| |
| JK_TRACE_EXIT(l); |
| return JK_TRUE; |
| } |
| |
| int ajp_connect_to_endpoint(ajp_endpoint_t * ae, jk_logger_t *l) |
| { |
| char buf[32]; |
| int rc = JK_TRUE; |
| |
| JK_TRACE_ENTER(l); |
| |
| ae->sd = jk_open_socket(&ae->worker->worker_inet_addr, |
| ae->worker->keepalive, |
| ae->worker->socket_timeout, |
| ae->worker->socket_buf, l); |
| if (IS_VALID_SOCKET(ae->sd)) { |
| ae->last_errno = 0; |
| if (JK_IS_DEBUG_LEVEL(l)) { |
| jk_log(l, JK_LOG_DEBUG, |
| "Connected socket %d to (%s)", |
| ae->sd, |
| jk_dump_hinfo(&ae->worker->worker_inet_addr, buf)); |
| } |
| /* set last_access only if needed */ |
| if (ae->worker->cache_timeout > 0) |
| ae->last_access = time(NULL); |
| /* Check if we must execute a logon after the physical connect */ |
| if (ae->worker->logon != NULL) { |
| rc = ae->worker->logon(ae, l); |
| JK_TRACE_EXIT(l); |
| return rc; |
| } |
| /* should we send a CPING to validate connection ? */ |
| if (ae->worker->connect_timeout > 0) { |
| rc = ajp_handle_cping_cpong (ae, |
| ae->worker->connect_timeout, l); |
| JK_TRACE_EXIT(l); |
| return rc; |
| } |
| JK_TRACE_EXIT(l); |
| return JK_TRUE; |
| } |
| ae->last_errno = errno; |
| |
| jk_log(l, JK_LOG_INFO, |
| "Failed opening socket to (%s) (errno=%d)", |
| jk_dump_hinfo(&ae->worker->worker_inet_addr, buf), ae->last_errno); |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |
| |
| /* |
| * Send a message to endpoint, using corresponding PROTO HEADER |
| */ |
| |
| int ajp_connection_tcp_send_message(ajp_endpoint_t * ae, |
| jk_msg_buf_t *msg, jk_logger_t *l) |
| { |
| int rc; |
| |
| JK_TRACE_ENTER(l); |
| if (ae->proto == AJP13_PROTO) { |
| jk_b_end(msg, AJP13_WS_HEADER); |
| if (JK_IS_DEBUG_LEVEL(l)) |
| jk_dump_buff(l, JK_LOG_DEBUG, "sending to ajp13", msg); |
| } |
| else if (ae->proto == AJP14_PROTO) { |
| jk_b_end(msg, AJP14_WS_HEADER); |
| if (JK_IS_DEBUG_LEVEL(l)) |
| jk_dump_buff(l, JK_LOG_DEBUG, "sending to ajp14", msg); |
| } |
| else { |
| jk_log(l, JK_LOG_ERROR, |
| "unknown protocol %d, supported are AJP13/AJP14", ae->proto); |
| JK_TRACE_EXIT(l); |
| return JK_FATAL_ERROR; |
| } |
| |
| if ((rc = jk_tcp_socket_sendfull(ae->sd, msg->buf, |
| msg->len)) > 0) { |
| ae->endpoint.wr += msg->len; |
| JK_TRACE_EXIT(l); |
| ae->last_errno = 0; |
| return JK_TRUE; |
| } |
| ae->last_errno = errno; |
| jk_log(l, JK_LOG_ERROR, |
| "sendfull returned %d (errno=%d)", rc, ae->last_errno); |
| |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |
| |
| /* |
| * Receive a message from endpoint, checking PROTO HEADER |
| */ |
| |
| int ajp_connection_tcp_get_message(ajp_endpoint_t * ae, |
| jk_msg_buf_t *msg, jk_logger_t *l) |
| { |
| unsigned char head[AJP_HEADER_LEN]; |
| int rc; |
| int msglen; |
| unsigned int header; |
| char buf[32]; |
| |
| JK_TRACE_ENTER(l); |
| |
| rc = jk_tcp_socket_recvfull(ae->sd, head, AJP_HEADER_LEN); |
| |
| if (rc < 0) { |
| ae->last_errno = errno; |
| if (rc == JK_SOCKET_EOF) { |
| jk_log(l, JK_LOG_INFO, |
| "(%s) Tomcat has forced a connection close for socket %d", |
| ae->worker->name, ae->sd); |
| JK_TRACE_EXIT(l); |
| } |
| else { |
| jk_log(l, JK_LOG_ERROR, |
| "(%s) can't receive the response message from tomcat, " |
| "network problems or tomcat (%s) is down (errno=%d)", |
| ae->worker->name, jk_dump_hinfo(&ae->worker->worker_inet_addr, buf), |
| ae->last_errno); |
| JK_TRACE_EXIT(l); |
| } |
| return JK_FALSE; |
| } |
| ae->last_errno = 0; |
| ae->endpoint.rd += rc; |
| header = ((unsigned int)head[0] << 8) | head[1]; |
| |
| if (ae->proto == AJP13_PROTO) { |
| if (header != AJP13_SW_HEADER) { |
| |
| if (header == AJP14_SW_HEADER) { |
| jk_log(l, JK_LOG_ERROR, |
| "received AJP14 reply on an AJP13 connection from %s", |
| jk_dump_hinfo(&ae->worker->worker_inet_addr, buf)); |
| } |
| else { |
| jk_log(l, JK_LOG_ERROR, |
| "wrong message format 0x%04x from %s", |
| header, jk_dump_hinfo(&ae->worker->worker_inet_addr, |
| buf)); |
| } |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |
| } |
| else if (ae->proto == AJP14_PROTO) { |
| if (header != AJP14_SW_HEADER) { |
| |
| if (header == AJP13_SW_HEADER) { |
| jk_log(l, JK_LOG_ERROR, |
| "received AJP13 reply on an AJP14 connection from %s", |
| jk_dump_hinfo(&ae->worker->worker_inet_addr, buf)); |
| } |
| else { |
| jk_log(l, JK_LOG_ERROR, |
| "wrong message format 0x%04x from %s", |
| header, jk_dump_hinfo(&ae->worker->worker_inet_addr, |
| buf)); |
| } |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |
| } |
| |
| msglen = ((head[2] & 0xff) << 8); |
| msglen += (head[3] & 0xFF); |
| |
| if (msglen > msg->maxlen) { |
| jk_log(l, JK_LOG_ERROR, |
| "wrong message size %d %d from %s", |
| msglen, msg->maxlen, |
| jk_dump_hinfo(&ae->worker->worker_inet_addr, buf)); |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |
| |
| msg->len = msglen; |
| msg->pos = 0; |
| |
| rc = jk_tcp_socket_recvfull(ae->sd, msg->buf, msglen); |
| if (rc < 0) { |
| ae->last_errno = errno; |
| if (rc == JK_SOCKET_EOF) { |
| jk_log(l, JK_LOG_ERROR, |
| "(%s) can't receive the response message from tomcat, " |
| "tomcat (%s) has forced a connection close for socket %d", |
| ae->worker->name, jk_dump_hinfo(&ae->worker->worker_inet_addr, buf), |
| ae->sd); |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |
| else { |
| jk_log(l, JK_LOG_ERROR, |
| "(%s) can't receive the response message from tomcat, " |
| "network problems or tomcat (%s) is down (errno=%d)", |
| ae->worker->name, jk_dump_hinfo(&ae->worker->worker_inet_addr, buf), |
| ae->last_errno); |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |
| } |
| ae->last_errno = 0; |
| ae->endpoint.rd += rc; |
| |
| if (ae->proto == AJP13_PROTO) { |
| if (JK_IS_DEBUG_LEVEL(l)) |
| jk_dump_buff(l, JK_LOG_DEBUG, "received from ajp13", msg); |
| } |
| else if (ae->proto == AJP14_PROTO) { |
| if (JK_IS_DEBUG_LEVEL(l)) |
| jk_dump_buff(l, JK_LOG_DEBUG, "received from ajp14", msg); |
| } |
| JK_TRACE_EXIT(l); |
| return JK_TRUE; |
| } |
| |
| /* |
| * Read all the data from the socket. |
| * |
| * Socket API doesn't guaranty that all the data will be kept in a |
| * single read, so we must loop until all awaited data is received |
| */ |
| |
| static int ajp_read_fully_from_server(jk_ws_service_t *s, jk_logger_t *l, |
| unsigned char *buf, unsigned int len) |
| { |
| unsigned int rdlen = 0; |
| unsigned int padded_len = len; |
| |
| JK_TRACE_ENTER(l); |
| if (s->is_chunked && s->no_more_chunks) { |
| JK_TRACE_EXIT(l); |
| return 0; |
| } |
| if (s->is_chunked) { |
| /* Corner case: buf must be large enough to hold next |
| * chunk size (if we're on or near a chunk border). |
| * Pad the length to a reasonable value, otherwise the |
| * read fails and the remaining chunks are tossed. |
| */ |
| padded_len = (len < CHUNK_BUFFER_PAD) ? len : len - CHUNK_BUFFER_PAD; |
| } |
| |
| while (rdlen < padded_len) { |
| unsigned int this_time = 0; |
| if (!s->read(s, buf + rdlen, len - rdlen, &this_time)) { |
| /* Remote Client read failed. */ |
| JK_TRACE_EXIT(l); |
| return JK_CLIENT_RD_ERROR; |
| } |
| |
| if (0 == this_time) { |
| if (s->is_chunked) { |
| s->no_more_chunks = 1; /* read no more */ |
| } |
| break; |
| } |
| rdlen += this_time; |
| } |
| |
| return (int)rdlen; |
| } |
| |
| |
| /* |
| * Read data from AJP13/AJP14 protocol |
| * Returns -1 on error, else number of bytes read |
| */ |
| |
| static int ajp_read_into_msg_buff(ajp_endpoint_t * ae, |
| jk_ws_service_t *r, |
| jk_msg_buf_t *msg, int len, jk_logger_t *l) |
| { |
| unsigned char *read_buf = msg->buf; |
| |
| JK_TRACE_ENTER(l); |
| jk_b_reset(msg); |
| |
| read_buf += AJP_HEADER_LEN; /* leave some space for the buffer headers */ |
| read_buf += AJP_HEADER_SZ_LEN; /* leave some space for the read length */ |
| |
| /* Pick the max size since we don't know the content_length */ |
| if (r->is_chunked && len == 0) { |
| len = AJP13_MAX_SEND_BODY_SZ; |
| } |
| |
| if ((len = ajp_read_fully_from_server(r, l, read_buf, len)) < 0) { |
| jk_log(l, JK_LOG_INFO, |
| "(%s) receiving data from client failed. " |
| "Connection aborted or network problems", |
| ae->worker->name); |
| JK_TRACE_EXIT(l); |
| return JK_CLIENT_RD_ERROR; |
| } |
| |
| if (!r->is_chunked) { |
| ae->left_bytes_to_send -= len; |
| } |
| |
| if (len > 0) { |
| /* Recipient recognizes empty packet as end of stream, not |
| an empty body packet */ |
| if (0 != jk_b_append_int(msg, (unsigned short)len)) { |
| jk_log(l, JK_LOG_INFO, |
| "Failed appending message length"); |
| JK_TRACE_EXIT(l); |
| return JK_CLIENT_RD_ERROR; |
| } |
| } |
| |
| msg->len += len; |
| |
| JK_TRACE_EXIT(l); |
| return len; |
| } |
| |
| |
| /* |
| * send request to Tomcat via Ajp13 |
| * - first try to find reuseable socket |
| * - if no one available, try to connect |
| * - send request, but send must be see as asynchronous, |
| * since send() call will return noerror about 95% of time |
| * Hopefully we'll get more information on next read. |
| * |
| * nb: reqmsg is the original request msg buffer |
| * repmsg is the reply msg buffer which could be scratched |
| */ |
| static int ajp_send_request(jk_endpoint_t *e, |
| jk_ws_service_t *s, |
| jk_logger_t *l, |
| ajp_endpoint_t * ae, ajp_operation_t * op) |
| { |
| int err = 0; |
| int postlen; |
| |
| JK_TRACE_ENTER(l); |
| /* Up to now, we can recover */ |
| op->recoverable = JK_TRUE; |
| |
| /* |
| * First try to reuse open connections... |
| */ |
| while (IS_VALID_SOCKET(ae->sd)) { |
| int rc = 0; |
| err = 0; |
| if (!jk_is_socket_connected(ae->sd)) { |
| jk_log(l, JK_LOG_DEBUG, |
| "(%s) socket %d is not connected any more (errno=%d)", |
| ae->worker->name, ae->sd, errno); |
| jk_close_socket(ae->sd); |
| ae->sd = JK_INVALID_SOCKET; |
| err = 1; |
| } |
| if (ae->worker->prepost_timeout > 0 && !err) { |
| /* handle cping/cpong if prepost_timeout is set |
| * If the socket is disconnected no need to handle |
| * the cping/cpong |
| */ |
| if (ajp_handle_cping_cpong(ae, |
| ae->worker->prepost_timeout, l) == JK_FALSE) { |
| /* XXX: Is there any reason to try other |
| * connections to the node if one of them fails |
| * the cping/cpong heartbeat? |
| * Tomcat can be either too busy or simply dead, so |
| * there is a chance that all oter connections would |
| * fail as well. |
| */ |
| err = 2; |
| } |
| } |
| |
| /* If we got an error or can't send data, then try to get a pooled |
| * connection and try again. If we are succesful, break out of this |
| * loop. */ |
| if (err || |
| ((rc = ajp_connection_tcp_send_message(ae, op->request, l)) != JK_TRUE)) { |
| if (rc != JK_FATAL_ERROR) { |
| if (err == 1) { |
| jk_log(l, JK_LOG_DEBUG, |
| "(%s) failed sending request. " |
| "Will try another pooled connection", |
| ae->worker->name); |
| } |
| else { |
| jk_log(l, JK_LOG_INFO, |
| "(%s) error sending request. " |
| "Will try another pooled connection", |
| ae->worker->name); |
| } |
| ajp_next_connection(ae, l); |
| } |
| else { |
| op->recoverable = JK_FALSE; |
| jk_log(l, JK_LOG_ERROR, |
| "(%s) error sending request. Unrecoverable operation", |
| ae->worker->name); |
| jk_close_socket(ae->sd); |
| ae->sd = JK_INVALID_SOCKET; |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |
| } |
| else |
| break; |
| } |
| |
| /* |
| * If we failed to reuse a connection, try to reconnect. |
| */ |
| if (!IS_VALID_SOCKET(ae->sd)) { |
| if (err == 1) { |
| /* If err is set, the tomcat is disconnected */ |
| jk_log(l, JK_LOG_INFO, |
| "(%s) all endpoints are disconnected", ae->worker->name); |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |
| else if (err) { |
| /* If err is set, the tomcat is dead */ |
| jk_log(l, JK_LOG_INFO, |
| "(%s) all endpoints are dead", ae->worker->name); |
| /* TODO: What is the purpose of the following log message? |
| * IMO it is very confusing and does not reflect the |
| * real reason (CPING/CPONG failed) of the error. |
| * Further more user might deliberately set the |
| * connectionTimeout and this is normal operational |
| * message in that case. |
| */ |
| jk_log(l, JK_LOG_INFO, |
| "(%s) increase the backend idle connection " |
| "timeout or the connection_pool_minsize", |
| ae->worker->name); |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |
| /* Connect to the backend. |
| * This can be either uninitalized connection or a reconnect. |
| */ |
| if (ajp_connect_to_endpoint(ae, l) == JK_TRUE) { |
| /* |
| * After we are connected, each error that we are going to |
| * have is probably unrecoverable |
| */ |
| if (ajp_connection_tcp_send_message(ae, op->request, l) != JK_TRUE) { |
| /* Close the socket if unable to send request */ |
| jk_close_socket(ae->sd); |
| ae->sd = JK_INVALID_SOCKET; |
| jk_log(l, JK_LOG_INFO, |
| "(%s) error sending request on a fresh connection (errno=%d)", |
| ae->worker->name, ae->last_errno); |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |
| } |
| else { |
| /* Close the socket if unable to connect */ |
| jk_close_socket(ae->sd); |
| ae->sd = JK_INVALID_SOCKET; |
| jk_log(l, JK_LOG_INFO, |
| "(%s) error connecting to the backend server (errno=%d)", |
| ae->worker->name, ae->last_errno); |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |
| } |
| |
| /* |
| * From now on an error means that we have an internal server error |
| * or Tomcat crashed. In any case we cannot recover this. |
| */ |
| |
| if (JK_IS_DEBUG_LEVEL(l)) |
| jk_log(l, JK_LOG_DEBUG, |
| "(%s) request body to send %d - request body to resend %d", |
| ae->worker->name, ae->left_bytes_to_send, |
| op->reply->len - AJP_HEADER_LEN); |
| |
| /* |
| * POST recovery job is done here and will work when data to |
| * POST are less than 8k, since it's the maximum size of op-post buffer. |
| * We send here the first part of data which was sent previously to the |
| * remote Tomcat |
| */ |
| |
| /* Did we have something to resend (ie the op-post has been feeded previously */ |
| |
| postlen = op->post->len; |
| if (postlen > AJP_HEADER_LEN) { |
| if (ajp_connection_tcp_send_message(ae, op->post, l) != JK_TRUE) { |
| /* Close the socket if unable to send request */ |
| jk_close_socket(ae->sd); |
| ae->sd = JK_INVALID_SOCKET; |
| jk_log(l, JK_LOG_ERROR, "(%s) failed resending request body (%d)", |
| ae->worker->name, postlen); |
| JK_TRACE_EXIT(l); |
| return JK_SERVER_ERROR; |
| } |
| else { |
| if (JK_IS_DEBUG_LEVEL(l)) |
| jk_log(l, JK_LOG_DEBUG, "Resent the request body (%d)", |
| postlen); |
| } |
| } |
| else if (s->reco_status == RECO_FILLED) { |
| /* Recovery in LB MODE */ |
| postlen = s->reco_buf->len; |
| |
| if (postlen > AJP_HEADER_LEN) { |
| if (ajp_connection_tcp_send_message(ae, s->reco_buf, l) != JK_TRUE) { |
| /* Close the socket if unable to send request */ |
| jk_close_socket(ae->sd); |
| ae->sd = JK_INVALID_SOCKET; |
| jk_log(l, JK_LOG_ERROR, |
| "(%s) failed resending request body (lb mode) (%d)", |
| ae->worker->name, postlen); |
| JK_TRACE_EXIT(l); |
| return JK_SERVER_ERROR; |
| } |
| } |
| else { |
| if (JK_IS_DEBUG_LEVEL(l)) |
| jk_log(l, JK_LOG_DEBUG, |
| "Resent the request body (lb mode) (%d)", postlen); |
| } |
| } |
| else { |
| /* We never sent any POST data and we check if we have to send at |
| * least one block of data (max 8k). These data will be kept in reply |
| * for resend if the remote Tomcat is down, a fact we will learn only |
| * doing a read (not yet) |
| */ |
| /* || s->is_chunked - this can't be done here. The original protocol |
| sends the first chunk of post data ( based on Content-Length ), |
| and that's what the java side expects. |
| Sending this data for chunked would break other ajp13 servers. |
| |
| Note that chunking will continue to work - using the normal read. |
| */ |
| |
| if (ae->left_bytes_to_send > 0) { |
| int len = ae->left_bytes_to_send; |
| if (len > AJP13_MAX_SEND_BODY_SZ) { |
| len = AJP13_MAX_SEND_BODY_SZ; |
| } |
| if ((len = ajp_read_into_msg_buff(ae, s, op->post, len, l)) < 0) { |
| /* the browser stop sending data, no need to recover */ |
| op->recoverable = JK_FALSE; |
| JK_TRACE_EXIT(l); |
| return JK_CLIENT_RD_ERROR; |
| } |
| |
| /* If a RECOVERY buffer is available in LB mode, fill it */ |
| if (s->reco_status == RECO_INITED) { |
| jk_b_copy(op->post, s->reco_buf); |
| s->reco_status = RECO_FILLED; |
| } |
| |
| s->content_read = len; |
| if (ajp_connection_tcp_send_message(ae, op->post, l) != JK_TRUE) { |
| /* Close the socket if unable to send request */ |
| jk_close_socket(ae->sd); |
| ae->sd = JK_INVALID_SOCKET; |
| jk_log(l, JK_LOG_ERROR, "(%s) error sending request body", |
| ae->worker->name); |
| JK_TRACE_EXIT(l); |
| return JK_SERVER_ERROR; |
| } |
| } |
| } |
| JK_TRACE_EXIT(l); |
| return (JK_TRUE); |
| } |
| |
| /* |
| * What to do with incoming data (dispatcher) |
| */ |
| |
| static int ajp_process_callback(jk_msg_buf_t *msg, |
| jk_msg_buf_t *pmsg, |
| ajp_endpoint_t * ae, |
| jk_ws_service_t *r, jk_logger_t *l) |
| { |
| int code = (int)jk_b_get_byte(msg); |
| |
| JK_TRACE_ENTER(l); |
| switch (code) { |
| case JK_AJP13_SEND_HEADERS: |
| { |
| jk_res_data_t res; |
| if (!ajp_unmarshal_response(msg, &res, ae, l)) { |
| jk_log(l, JK_LOG_ERROR, |
| "ajp_unmarshal_response failed"); |
| JK_TRACE_EXIT(l); |
| return JK_AJP13_ERROR; |
| } |
| r->start_response(r, res.status, res.msg, |
| (const char *const *)res.header_names, |
| (const char *const *)res.header_values, |
| res.num_headers); |
| if (r->flush && r->flush_header) |
| r->flush(r); |
| r->http_response_status = res.status; |
| } |
| return JK_AJP13_SEND_HEADERS; |
| |
| case JK_AJP13_SEND_BODY_CHUNK: |
| { |
| unsigned int len = (unsigned int)jk_b_get_int(msg); |
| /* |
| * Do a sanity check on len to prevent write reading beyond buffer |
| * boundaries and thus revealing possible sensitive memory |
| * contents to the client. |
| * len cannot be larger than msg->len - 3 because the ajp message |
| * contains the magic byte for JK_AJP13_SEND_BODY_CHUNK (1 byte) |
| * and the length of the chunk (2 bytes). The remaining part of |
| * the message is the chunk. |
| */ |
| if (len > (unsigned int)(msg->len - 3)) { |
| jk_log(l, JK_LOG_ERROR, |
| "Chunk length too large. Length of AJP message is %i," |
| " chunk length is %i.", msg->len, len); |
| JK_TRACE_EXIT(l); |
| return JK_INTERNAL_ERROR; |
| } |
| if (len == 0) { |
| /* AJP13_SEND_BODY_CHUNK with length 0 is |
| * explicit flush packet message. |
| */ |
| if (r->flush) |
| r->flush(r); |
| } |
| else { |
| if (!r->write(r, msg->buf + msg->pos, len)) { |
| jk_log(l, JK_LOG_INFO, |
| "Writing to client aborted or client network problems"); |
| JK_TRACE_EXIT(l); |
| return JK_CLIENT_WR_ERROR; |
| } |
| if (r->flush && r->flush_packets) |
| r->flush(r); |
| } |
| } |
| break; |
| |
| case JK_AJP13_GET_BODY_CHUNK: |
| { |
| int len = (int)jk_b_get_int(msg); |
| |
| if (len < 0) { |
| len = 0; |
| } |
| if (len > AJP13_MAX_SEND_BODY_SZ) { |
| len = AJP13_MAX_SEND_BODY_SZ; |
| } |
| if ((unsigned int)len > ae->left_bytes_to_send) { |
| len = ae->left_bytes_to_send; |
| } |
| |
| /* the right place to add file storage for upload */ |
| if ((len = ajp_read_into_msg_buff(ae, r, pmsg, len, l)) >= 0) { |
| r->content_read += len; |
| JK_TRACE_EXIT(l); |
| return JK_AJP13_HAS_RESPONSE; |
| } |
| |
| jk_log(l, JK_LOG_INFO, |
| "Reding from client aborted or client network problems"); |
| |
| JK_TRACE_EXIT(l); |
| return JK_CLIENT_RD_ERROR; |
| } |
| break; |
| |
| case JK_AJP13_END_RESPONSE: |
| ae->reuse = (int)jk_b_get_byte(msg); |
| if (!ae->reuse) { |
| /* |
| * AJP13 protocol reuse flag set to false. |
| * Tomcat will close its side of the connection. |
| */ |
| jk_log(l, JK_LOG_WARNING, "AJP13 protocol: Reuse is set to false"); |
| } |
| else if (r->disable_reuse) { |
| if (JK_IS_DEBUG_LEVEL(l)) { |
| jk_log(l, JK_LOG_DEBUG, "AJP13 protocol: Reuse is disabled"); |
| } |
| ae->reuse = JK_FALSE; |
| } |
| else { |
| /* Reuse in all cases */ |
| if (JK_IS_DEBUG_LEVEL(l)) { |
| jk_log(l, JK_LOG_DEBUG, "AJP13 protocol: Reuse is OK"); |
| } |
| ae->reuse = JK_TRUE; |
| } |
| /* Flush after the last write */ |
| if (r->flush && !r->flush_packets) |
| r->flush(r); |
| |
| JK_TRACE_EXIT(l); |
| return JK_AJP13_END_RESPONSE; |
| break; |
| |
| default: |
| jk_log(l, JK_LOG_ERROR, |
| "Unknown AJP protocol code: %02X", code); |
| JK_TRACE_EXIT(l); |
| return JK_AJP13_ERROR; |
| } |
| |
| JK_TRACE_EXIT(l); |
| return JK_AJP13_NO_RESPONSE; |
| } |
| |
| static int is_http_status_fail(ajp_worker_t *w, int status) |
| { |
| unsigned int i; |
| for (i = 0; i < w->http_status_fail_num; i++) { |
| if (w->http_status_fail[i] == status) |
| return 1; |
| } |
| return 0; |
| } |
| |
| |
| /* |
| * get replies from Tomcat via Ajp13/Ajp14 |
| * We will know only at read time if the remote host closed |
| * the connection (half-closed state - FIN-WAIT2). In that case |
| * we must close our side of the socket and abort emission. |
| * We will need another connection to send the request |
| * There is need of refactoring here since we mix |
| * reply reception (tomcat -> apache) and request send (apache -> tomcat) |
| * and everything using the same buffer (repmsg) |
| * ajp13/ajp14 is async but handling read/send this way prevent nice recovery |
| * In fact if tomcat link is broken during upload (browser -> apache -> tomcat) |
| * we'll loose data and we'll have to abort the whole request. |
| */ |
| static int ajp_get_reply(jk_endpoint_t *e, |
| jk_ws_service_t *s, |
| jk_logger_t *l, |
| ajp_endpoint_t * p, ajp_operation_t * op) |
| { |
| /* Don't get header from tomcat yet */ |
| int headeratclient = JK_FALSE; |
| |
| JK_TRACE_ENTER(l); |
| |
| /* Start read all reply message */ |
| while (1) { |
| int rc = 0; |
| |
| /* If we set a reply timeout, check it something is available */ |
| if (p->worker->reply_timeout > 0) { |
| if (ajp_is_input_event(p, p->worker->reply_timeout, l) == |
| JK_FALSE) { |
| jk_log(l, JK_LOG_ERROR, |
| "(%s) Timeout with waiting reply from tomcat. " |
| "Tomcat is down, stopped or network problems (errno=%d)", |
| p->worker->name, p->last_errno); |
| if (headeratclient == JK_FALSE) { |
| if (p->worker->recovery_opts & RECOVER_ABORT_IF_TCGETREQUEST) |
| op->recoverable = JK_FALSE; |
| } |
| else { |
| if (p->worker->recovery_opts & RECOVER_ABORT_IF_TCSENDHEADER) |
| op->recoverable = JK_FALSE; |
| } |
| |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |
| } |
| |
| if (!ajp_connection_tcp_get_message(p, op->reply, l)) { |
| /* we just can't recover, unset recover flag */ |
| if (headeratclient == JK_FALSE) { |
| jk_log(l, JK_LOG_ERROR, |
| "(%s) Tomcat is down or refused connection. " |
| "No response has been sent to the client (yet)", |
| p->worker->name); |
| /* |
| * communication with tomcat has been interrupted BEFORE |
| * headers have been sent to the client. |
| * DISCUSSION: As we suppose that tomcat has already started |
| * to process the query we think it's unrecoverable (and we |
| * should not retry or switch to another tomcat in the |
| * cluster). |
| */ |
| |
| /* |
| * We mark it unrecoverable if recovery_opts set to RECOVER_ABORT_IF_TCGETREQUEST |
| */ |
| if (p->worker->recovery_opts & RECOVER_ABORT_IF_TCGETREQUEST) |
| op->recoverable = JK_FALSE; |
| /* |
| * we want to display the webservers error page, therefore |
| * we return JK_FALSE |
| */ |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |
| else { |
| jk_log(l, JK_LOG_ERROR, |
| "(%s) Tomcat is down or network problems. " |
| "Part of the response has already been sent to the client", |
| p->worker->name); |
| |
| /* communication with tomcat has been interrupted AFTER |
| * headers have been sent to the client. |
| * headers (and maybe parts of the body) have already been |
| * sent, therefore the response is "complete" in a sense |
| * that nobody should append any data, especially no 500 error |
| * page of the webserver! |
| * |
| * BUT if you retrun JK_TRUE you have a 200 (OK) code in your |
| * in your apache access.log instead of a 500 (Error). |
| * Therefore return FALSE/FALSE |
| * return JK_TRUE; |
| */ |
| |
| /* |
| * We mark it unrecoverable if recovery_opts set to RECOVER_ABORT_IF_TCSENDHEADER |
| */ |
| if (p->worker->recovery_opts & RECOVER_ABORT_IF_TCSENDHEADER) |
| op->recoverable = JK_FALSE; |
| |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |
| } |
| |
| rc = ajp_process_callback(op->reply, op->post, p, s, l); |
| |
| /* no more data to be sent, fine we have finish here */ |
| if (JK_AJP13_END_RESPONSE == rc) { |
| JK_TRACE_EXIT(l); |
| return JK_TRUE; |
| } |
| else if (JK_AJP13_SEND_HEADERS == rc) { |
| if (is_http_status_fail(p->worker, s->http_response_status)) { |
| JK_TRACE_EXIT(l); |
| return JK_STATUS_ERROR; |
| } |
| headeratclient = JK_TRUE; |
| } |
| else if (JK_AJP13_HAS_RESPONSE == rc) { |
| /* |
| * in upload-mode there is no second chance since |
| * we may have allready sent part of the uploaded data |
| * to Tomcat. |
| * In this case if Tomcat connection is broken we must |
| * abort request and indicate error. |
| * A possible work-around could be to store the uploaded |
| * data to file and replay for it |
| */ |
| op->recoverable = JK_FALSE; |
| rc = ajp_connection_tcp_send_message(p, op->post, l); |
| if (rc < 0) { |
| jk_log(l, JK_LOG_ERROR, |
| "(%s) Tomcat is down or network problems", |
| p->worker->name); |
| jk_close_socket(p->sd); |
| p->sd = JK_INVALID_SOCKET; |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |
| } |
| else if (JK_AJP13_ERROR == rc) { |
| /* |
| * Tomcat has send invalid AJP message. |
| * Locadbalancer if present will decide if |
| * failover is possible. |
| */ |
| JK_TRACE_EXIT(l); |
| return JK_SERVER_ERROR; |
| } |
| else if (JK_FATAL_ERROR == rc) { |
| /* |
| * we won't be able to gracefully recover from this so |
| * set recoverable to false and get out. |
| */ |
| op->recoverable = JK_FALSE; |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |
| else if (JK_CLIENT_RD_ERROR == rc) { |
| /* |
| * Client has stop sending to us, so get out. |
| * We assume this isn't our fault, so just a normal exit. |
| */ |
| JK_TRACE_EXIT(l); |
| return JK_CLIENT_RD_ERROR; |
| } |
| else if (JK_CLIENT_WR_ERROR == rc) { |
| /* |
| * Client has stop receiving to us, so get out. |
| * We assume this isn't our fault, so just a normal exit. |
| */ |
| JK_TRACE_EXIT(l); |
| return JK_CLIENT_WR_ERROR; |
| } |
| else if (JK_SERVER_ERROR == rc) { |
| /* |
| * Tomcat has stop talking to us, so get out. |
| * Locadbalancer if present will decide if |
| * failover is possible. |
| */ |
| JK_TRACE_EXIT(l); |
| return JK_SERVER_ERROR; |
| } |
| else if (rc < 0) { |
| JK_TRACE_EXIT(l); |
| return (JK_FALSE); /* XXX error */ |
| } |
| } |
| /* XXX: Not reached? */ |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |
| |
| /* |
| * service is now splitted in ajp_send_request and ajp_get_reply |
| * much more easier to do errors recovery |
| * |
| * We serve here the request, using AJP13/AJP14 (e->proto) |
| * |
| */ |
| static int JK_METHOD ajp_service(jk_endpoint_t *e, |
| jk_ws_service_t *s, |
| jk_logger_t *l, int *is_error) |
| { |
| int i, err; |
| ajp_operation_t oper; |
| ajp_operation_t *op = &oper; |
| ajp_endpoint_t *p; |
| |
| JK_TRACE_ENTER(l); |
| |
| if (is_error) |
| *is_error = JK_HTTP_SERVER_ERROR; |
| if (!e || !e->endpoint_private || !s || !is_error) { |
| JK_LOG_NULL_PARAMS(l); |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |
| |
| p = e->endpoint_private; |
| op->request = jk_b_new(&(p->pool)); |
| jk_b_set_buffer_size(op->request, p->worker->max_packet_size); |
| jk_b_reset(op->request); |
| |
| op->reply = jk_b_new(&(p->pool)); |
| jk_b_set_buffer_size(op->reply, p->worker->max_packet_size); |
| jk_b_reset(op->reply); |
| |
| op->post = jk_b_new(&(p->pool)); |
| jk_b_set_buffer_size(op->post, p->worker->max_packet_size); |
| jk_b_reset(op->post); |
| |
| op->recoverable = JK_TRUE; |
| op->uploadfd = -1; /* not yet used, later ;) */ |
| |
| p->left_bytes_to_send = s->content_length; |
| p->reuse = JK_FALSE; |
| |
| s->secret = p->worker->secret; |
| |
| /* |
| * We get here initial request (in reqmsg) |
| */ |
| if (!ajp_marshal_into_msgb(op->request, s, l, p)) { |
| *is_error = JK_REQUEST_TOO_LARGE; |
| jk_log(l, JK_LOG_INFO, |
| "Creating AJP message failed, " |
| "without recovery"); |
| JK_TRACE_EXIT(l); |
| return JK_CLIENT_ERROR; |
| } |
| |
| if (JK_IS_DEBUG_LEVEL(l)) { |
| jk_log(l, JK_LOG_DEBUG, "processing %s with %d retries", |
| p->worker->name, p->worker->worker.retries); |
| } |
| /* |
| * JK_RETRIES could be replaced by the number of workers in |
| * a load-balancing configuration |
| */ |
| for (i = 0; i < p->worker->worker.retries; i++) { |
| /* |
| * We're using reqmsg which hold initial request |
| * if Tomcat is stopped or restarted, we will pass reqmsg |
| * to next valid tomcat. |
| */ |
| err = ajp_send_request(e, s, l, p, op); |
| if (err == JK_TRUE) { |
| |
| /* If we have the no recoverable error, it's probably because |
| * the sender (browser) stopped sending data before the end |
| * (certainly in a big post) |
| */ |
| if (!op->recoverable) { |
| *is_error = JK_HTTP_SERVER_ERROR; |
| jk_log(l, JK_LOG_ERROR, |
| "(%s) sending request to tomcat failed " |
| "without recovery in send loop %d", |
| p->worker->name, i); |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |
| |
| /* Up to there we can recover */ |
| |
| err = ajp_get_reply(e, s, l, p, op); |
| if (err == JK_TRUE) { |
| *is_error = JK_HTTP_OK; |
| /* Done with the request */ |
| JK_TRACE_EXIT(l); |
| return JK_TRUE; |
| } |
| |
| if (err == JK_CLIENT_RD_ERROR) { |
| *is_error = JK_HTTP_BAD_REQUEST; |
| if (p->worker->recovery_opts & RECOVER_ABORT_IF_CLIENTERROR) { |
| /* Mark the endpoint for shutdown */ |
| p->reuse = JK_FALSE; |
| } |
| jk_log(l, JK_LOG_INFO, |
| "(%s) request failed, " |
| "because of client read error " |
| "without recovery in send loop attempt=%d", |
| p->worker->name, i); |
| JK_TRACE_EXIT(l); |
| return JK_CLIENT_ERROR; |
| } |
| else if (err == JK_CLIENT_WR_ERROR) { |
| /* XXX: Is this correct to log this as 200? */ |
| *is_error = JK_HTTP_OK; |
| if (p->worker->recovery_opts & RECOVER_ABORT_IF_CLIENTERROR) { |
| /* Mark the endpoint for shutdown */ |
| p->reuse = JK_FALSE; |
| } |
| jk_log(l, JK_LOG_INFO, |
| "(%s) request failed, " |
| "because of client write error " |
| "without recovery in send loop attempt=%d", |
| p->worker->name, i); |
| JK_TRACE_EXIT(l); |
| return JK_CLIENT_ERROR; |
| } |
| else if (err == JK_SERVER_ERROR) { |
| *is_error = JK_HTTP_SERVER_ERROR; |
| jk_log(l, JK_LOG_INFO, |
| "(%s) request failed, " |
| "because of server error " |
| "without recovery in send loop attempt=%d", |
| p->worker->name, i); |
| JK_TRACE_EXIT(l); |
| return JK_SERVER_ERROR; |
| } |
| else if (err == JK_STATUS_ERROR) { |
| jk_log(l, JK_LOG_INFO, |
| "(%s) request failed, " |
| "because of response status %d, " |
| "recoverable operation attempt=%d", |
| p->worker->name, |
| s->http_response_status, i); |
| JK_TRACE_EXIT(l); |
| if (i >= JK_RETRIES) { |
| jk_sleep(JK_SLEEP_DEF); |
| } |
| } |
| else { |
| /* if we can't get reply, check if no recover flag was set |
| * if is_recoverable_error is cleared, we have started |
| * receiving upload data and we must consider that |
| * operation is no more recoverable |
| */ |
| if (!op->recoverable) { |
| *is_error = JK_HTTP_BAD_GATEWAY; |
| jk_log(l, JK_LOG_ERROR, |
| "(%s) receiving reply from tomcat failed " |
| "without recovery in send loop attempt=%d", |
| p->worker->name, i); |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |
| jk_log(l, JK_LOG_INFO, |
| "(%s) receiving from tomcat failed, " |
| "recoverable operation attempt=%d", |
| p->worker->name, i); |
| /* Check for custom retries */ |
| if (i >= JK_RETRIES) { |
| jk_sleep(JK_SLEEP_DEF); |
| } |
| } |
| } |
| if (err == JK_CLIENT_RD_ERROR) { |
| *is_error = JK_HTTP_BAD_REQUEST; |
| if (p->worker->recovery_opts & RECOVER_ABORT_IF_CLIENTERROR) { |
| /* Mark the endpoint for shutdown */ |
| p->reuse = JK_FALSE; |
| } |
| jk_log(l, JK_LOG_INFO, |
| "(%s) sending request to tomcat failed, " |
| "because of client read error " |
| "without recovery in send loop attempt=%d", |
| p->worker->name, i); |
| JK_TRACE_EXIT(l); |
| return JK_CLIENT_ERROR; |
| } |
| else if (err == JK_CLIENT_WR_ERROR) { |
| *is_error = JK_HTTP_OK; |
| if (p->worker->recovery_opts & RECOVER_ABORT_IF_CLIENTERROR) { |
| /* Mark the endpoint for shutdown */ |
| p->reuse = JK_FALSE; |
| } |
| jk_log(l, JK_LOG_INFO, |
| "(%s) sending request to tomcat failed, " |
| "because of client write error " |
| "without recovery in send loop attempt=%d", |
| p->worker->name, i); |
| JK_TRACE_EXIT(l); |
| return JK_CLIENT_ERROR; |
| } |
| else { |
| jk_log(l, JK_LOG_INFO, |
| "(%s) sending request to tomcat failed, " |
| "recoverable operation attempt=%d", |
| p->worker->name, i + 1); |
| } |
| /* Get another connection from the pool and try again. |
| * Note: All sockets are probably closed already. |
| */ |
| ajp_next_connection(p, l); |
| } |
| *is_error = JK_HTTP_SERVER_BUSY; |
| /* Log the error only once per failed request. */ |
| jk_log(l, JK_LOG_ERROR, |
| "(%s) Connecting to tomcat failed. Tomcat is probably not started " |
| "or is listening on the wrong port", |
| p->worker->name); |
| |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |
| |
| /* |
| * Validate the worker (ajp13/ajp14) |
| */ |
| |
| int ajp_validate(jk_worker_t *pThis, |
| jk_map_t *props, |
| jk_worker_env_t *we, jk_logger_t *l, int proto) |
| { |
| int port; |
| const char *host; |
| |
| JK_TRACE_ENTER(l); |
| |
| if (proto == AJP13_PROTO) { |
| port = AJP13_DEF_PORT; |
| host = AJP13_DEF_HOST; |
| } |
| else if (proto == AJP14_PROTO) { |
| port = AJP14_DEF_PORT; |
| host = AJP14_DEF_HOST; |
| } |
| else { |
| jk_log(l, JK_LOG_ERROR, |
| "unknown protocol %d", proto); |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |
| |
| if (pThis && pThis->worker_private) { |
| ajp_worker_t *p = pThis->worker_private; |
| p->port = jk_get_worker_port(props, p->name, port); |
| p->host = jk_get_worker_host(props, p->name, host); |
| |
| if (JK_IS_DEBUG_LEVEL(l)) |
| jk_log(l, JK_LOG_DEBUG, |
| "worker %s contact is '%s:%d'", |
| p->name, p->host, p->port); |
| |
| if (p->port > 1024) { |
| if (jk_resolve(p->host, p->port, &p->worker_inet_addr)) { |
| JK_TRACE_EXIT(l); |
| return JK_TRUE; |
| } |
| jk_log(l, JK_LOG_ERROR, |
| "can't resolve tomcat address %s", p->host); |
| } |
| jk_log(l, JK_LOG_ERROR, |
| "invalid host and port %s %d", |
| ((p->host == NULL) ? "NULL" : p->host), p->port); |
| } |
| else { |
| JK_LOG_NULL_PARAMS(l); |
| } |
| |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |
| |
| static int ajp_create_endpoint_cache(ajp_worker_t *p, int proto, jk_logger_t *l) |
| { |
| unsigned int i; |
| time_t now = time(NULL); |
| |
| JK_TRACE_ENTER(l); |
| p->ep_cache = (ajp_endpoint_t **)calloc(1, sizeof(ajp_endpoint_t *) * |
| p->ep_cache_sz); |
| if (!p->ep_cache) { |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |
| if (JK_IS_DEBUG_LEVEL(l)) |
| jk_log(l, JK_LOG_DEBUG, |
| "setting connection pool size to %u with min %u", |
| p->ep_cache_sz, p->ep_mincache_sz); |
| for (i = 0; i < p->ep_cache_sz; i++) { |
| p->ep_cache[i] = (ajp_endpoint_t *)calloc(1, sizeof(ajp_endpoint_t)); |
| if (!p->ep_cache[i]) { |
| jk_log(l, JK_LOG_ERROR, |
| "allocating endpoint slot %d (errno=%d)", |
| i, errno); |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |
| p->ep_cache[i]->sd = JK_INVALID_SOCKET; |
| p->ep_cache[i]->reuse = JK_FALSE; |
| p->ep_cache[i]->last_access = now; |
| jk_open_pool(&(p->ep_cache[i]->pool), p->ep_cache[i]->buf, |
| sizeof(p->ep_cache[i]->buf)); |
| p->ep_cache[i]->worker = p; |
| p->ep_cache[i]->endpoint.endpoint_private = p->ep_cache[i]; |
| p->ep_cache[i]->proto = proto; |
| p->ep_cache[i]->endpoint.service = ajp_service; |
| p->ep_cache[i]->endpoint.done = ajp_done; |
| } |
| |
| JK_TRACE_EXIT(l); |
| return JK_TRUE; |
| } |
| |
| int ajp_init(jk_worker_t *pThis, |
| jk_map_t *props, jk_worker_env_t *we, jk_logger_t *l, int proto) |
| { |
| int rc = JK_FALSE; |
| int cache; |
| /* |
| * start the connection cache |
| */ |
| JK_TRACE_ENTER(l); |
| |
| cache = jk_get_worker_def_cache_size(proto); |
| |
| if (pThis && pThis->worker_private) { |
| ajp_worker_t *p = pThis->worker_private; |
| p->ep_cache_sz = jk_get_worker_cache_size(props, p->name, cache); |
| p->ep_mincache_sz = jk_get_worker_cache_size_min(props, p->name, |
| (p->ep_cache_sz+1) / 2); |
| p->socket_timeout = |
| jk_get_worker_socket_timeout(props, p->name, AJP_DEF_SOCKET_TIMEOUT); |
| |
| p->socket_buf = |
| jk_get_worker_socket_buffer(props, p->name, 8192); |
| |
| p->keepalive = |
| jk_get_worker_socket_keepalive(props, p->name, JK_FALSE); |
| |
| p->cache_timeout = |
| jk_get_worker_cache_timeout(props, p->name, |
| AJP_DEF_CACHE_TIMEOUT); |
| |
| p->connect_timeout = |
| jk_get_worker_connect_timeout(props, p->name, |
| AJP_DEF_CONNECT_TIMEOUT); |
| |
| p->reply_timeout = |
| jk_get_worker_reply_timeout(props, p->name, |
| AJP_DEF_REPLY_TIMEOUT); |
| |
| p->prepost_timeout = |
| jk_get_worker_prepost_timeout(props, p->name, |
| AJP_DEF_PREPOST_TIMEOUT); |
| |
| p->recovery_opts = |
| jk_get_worker_recovery_opts(props, p->name, |
| AJP_DEF_RECOVERY_OPTS); |
| p->max_packet_size = |
| jk_get_max_packet_size(props, p->name); |
| |
| p->http_status_fail_num = jk_get_worker_fail_on_status(props, p->name, |
| &p->http_status_fail[0], |
| JK_MAX_HTTP_STATUS_FAILS); |
| |
| |
| pThis->retries = |
| jk_get_worker_retries(props, p->name, |
| JK_RETRIES); |
| if (pThis->retries < 1) { |
| jk_log(l, JK_LOG_INFO, |
| "number of retries must be grater then 1. Setting to default=%d", |
| JK_RETRIES); |
| pThis->retries = JK_RETRIES; |
| } |
| |
| if (JK_IS_DEBUG_LEVEL(l)) { |
| |
| jk_log(l, JK_LOG_DEBUG, |
| "setting endpoint options:", |
| p->keepalive); |
| jk_log(l, JK_LOG_DEBUG, |
| "keepalive: %d", |
| p->keepalive); |
| |
| jk_log(l, JK_LOG_DEBUG, |
| "timeout: %d", |
| p->socket_timeout); |
| |
| jk_log(l, JK_LOG_DEBUG, |
| "buffer size: %d", |
| p->socket_buf); |
| |
| jk_log(l, JK_LOG_DEBUG, |
| "pool timeout: %d", |
| p->cache_timeout); |
| |
| jk_log(l, JK_LOG_DEBUG, |
| "connect timeout: %d", |
| p->connect_timeout); |
| |
| jk_log(l, JK_LOG_DEBUG, |
| "reply timeout: %d", |
| p->reply_timeout); |
| |
| jk_log(l, JK_LOG_DEBUG, |
| "prepost timeout: %d", |
| p->prepost_timeout); |
| |
| jk_log(l, JK_LOG_DEBUG, |
| "recovery options: %d", |
| p->recovery_opts); |
| |
| jk_log(l, JK_LOG_DEBUG, |
| "retries: %d", |
| pThis->retries); |
| |
| jk_log(l, JK_LOG_DEBUG, |
| "max packet size: %d", |
| p->max_packet_size); |
| } |
| /* |
| * Need to initialize secret here since we could return from inside |
| * of the following loop |
| */ |
| |
| p->secret = jk_get_worker_secret(props, p->name); |
| /* Initialize cache slots */ |
| JK_INIT_CS(&(p->cs), rc); |
| if (!rc) { |
| jk_log(l, JK_LOG_ERROR, |
| "creating thread lock (errno=%d)", |
| errno); |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |
| if (!ajp_create_endpoint_cache(p, proto, l)) { |
| jk_log(l, JK_LOG_ERROR, |
| "allocating connection pool of size %u", |
| p->ep_cache_sz); |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |
| rc = JK_TRUE; |
| } |
| else { |
| JK_LOG_NULL_PARAMS(l); |
| } |
| |
| JK_TRACE_EXIT(l); |
| return rc; |
| } |
| |
| int ajp_destroy(jk_worker_t **pThis, jk_logger_t *l, int proto) |
| { |
| JK_TRACE_ENTER(l); |
| |
| if (pThis && *pThis && (*pThis)->worker_private) { |
| unsigned int i; |
| ajp_worker_t *aw = (*pThis)->worker_private; |
| |
| if (JK_IS_DEBUG_LEVEL(l)) |
| jk_log(l, JK_LOG_DEBUG, |
| "up to %u endpoints to close", |
| aw->ep_cache_sz); |
| |
| for (i = 0; i < aw->ep_cache_sz; i++) { |
| if (aw->ep_cache[i]) |
| ajp_close_endpoint(aw->ep_cache[i], l); |
| } |
| free(aw->ep_cache); |
| JK_DELETE_CS(&(aw->cs), i); |
| |
| if (aw->login) { |
| /* take care of removing previously allocated data */ |
| if (aw->login->servlet_engine_name) |
| free(aw->login->servlet_engine_name); |
| |
| free(aw->login); |
| aw->login = NULL; |
| } |
| |
| free(aw); |
| JK_TRACE_EXIT(l); |
| return JK_TRUE; |
| } |
| |
| JK_LOG_NULL_PARAMS(l); |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |
| |
| int JK_METHOD ajp_done(jk_endpoint_t **e, jk_logger_t *l) |
| { |
| JK_TRACE_ENTER(l); |
| if (e && *e && (*e)->endpoint_private) { |
| ajp_endpoint_t *p = (*e)->endpoint_private; |
| int rc; |
| ajp_worker_t *w = p->worker; |
| |
| JK_ENTER_CS(&w->cs, rc); |
| if (rc) { |
| int i; |
| jk_sock_t sock = JK_INVALID_SOCKET; |
| |
| /* If we are going to close the connection, then park the socket so |
| we can shut it down nicely rather than letting ajp_reset_endpoint kill it */ |
| if (IS_VALID_SOCKET(p->sd) && !p->reuse) { |
| if (JK_IS_DEBUG_LEVEL(l)) |
| jk_log(l, JK_LOG_DEBUG, |
| "will be shutting down socket %u for worker %s", |
| p->sd, p->worker->name ); |
| sock = p->sd; |
| p->sd = JK_INVALID_SOCKET; |
| } |
| for(i = w->ep_cache_sz - 1; i >= 0; i--) { |
| if (w->ep_cache[i] == NULL) { |
| w->ep_cache[i] = p; |
| break; |
| } |
| } |
| ajp_reset_endpoint(p, l); |
| *e = NULL; |
| /* set last_access only if needed */ |
| if (w->cache_timeout > 0) |
| p->last_access = time(NULL); |
| JK_LEAVE_CS(&w->cs, rc); |
| |
| /* Drain and close the socket */ |
| if (IS_VALID_SOCKET(sock)) { |
| if (JK_IS_DEBUG_LEVEL(l)) |
| jk_log(l, JK_LOG_DEBUG, |
| "Shutting down held socket %u in worker %s", |
| sock, p->worker->name); |
| jk_shutdown_socket(sock); |
| } |
| if (i >= 0) { |
| if (JK_IS_DEBUG_LEVEL(l)) |
| jk_log(l, JK_LOG_DEBUG, |
| "recycling connection pool slot=%u for worker %s", |
| i, p->worker->name); |
| JK_TRACE_EXIT(l); |
| return JK_TRUE; |
| } |
| /* XXX: This should never hapen because |
| * there is always free empty cache slot |
| */ |
| jk_log(l, JK_LOG_ERROR, |
| "could not find empty connection pool slot from %u for worker %s", |
| w->ep_cache_sz, w->name); |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |
| |
| jk_log(l, JK_LOG_ERROR, |
| "locking thread (errno=%d)", errno); |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |
| |
| JK_LOG_NULL_PARAMS(l); |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |
| |
| int ajp_get_endpoint(jk_worker_t *pThis, |
| jk_endpoint_t **je, jk_logger_t *l, int proto) |
| { |
| JK_TRACE_ENTER(l); |
| |
| if (pThis && pThis->worker_private && je) { |
| ajp_worker_t *aw = pThis->worker_private; |
| ajp_endpoint_t *ae = NULL; |
| time_t now = 0; |
| int rc; |
| /* Obtain current time only if needed */ |
| if (aw->cache_timeout > 0) |
| now = time(NULL); |
| *je = NULL; |
| |
| JK_ENTER_CS(&aw->cs, rc); |
| if (rc) { |
| unsigned int slot; |
| for (slot = 0; slot < aw->ep_cache_sz; slot++) { |
| if (aw->ep_cache[slot]) { |
| ae = aw->ep_cache[slot]; |
| aw->ep_cache[slot] = NULL; |
| break; |
| } |
| } |
| if (ae) { |
| ae->last_access = now; |
| *je = &ae->endpoint; |
| JK_LEAVE_CS(&aw->cs, rc); |
| if (JK_IS_DEBUG_LEVEL(l)) |
| jk_log(l, JK_LOG_DEBUG, |
| "acquired connection pool slot=%u", |
| slot); |
| JK_TRACE_EXIT(l); |
| return JK_TRUE; |
| } |
| else { |
| jk_log(l, JK_LOG_WARNING, |
| "Unable to get the free endpoint for worker %s from %u slots", |
| aw->name, aw->ep_cache_sz); |
| } |
| JK_LEAVE_CS(&aw->cs, rc); |
| } |
| else { |
| jk_log(l, JK_LOG_ERROR, |
| "locking thread (errno=%d)", |
| errno); |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| |
| } |
| jk_log(l, JK_LOG_INFO, |
| "can't find free endpoint"); |
| } |
| else { |
| JK_LOG_NULL_PARAMS(l); |
| } |
| |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |
| |
| int JK_METHOD ajp_maintain(jk_worker_t *pThis, time_t now, jk_logger_t *l) |
| { |
| JK_TRACE_ENTER(l); |
| |
| if (pThis && pThis->worker_private) { |
| ajp_worker_t *aw = pThis->worker_private; |
| int rc; |
| /* Obtain current time only if needed */ |
| if (aw->cache_timeout <= 0) { |
| /* Nothing to do. */ |
| JK_TRACE_EXIT(l); |
| return JK_TRUE; |
| } |
| JK_ENTER_CS(&aw->cs, rc); |
| if (rc) { |
| unsigned int i, n = 0, cnt = 0; |
| /* Count open slots */ |
| for (i = 0; i < aw->ep_cache_sz; i++) { |
| if (aw->ep_cache[i] && IS_VALID_SOCKET(aw->ep_cache[i]->sd)) |
| cnt++; |
| } |
| /* Handle worker cache and recycle timeouts */ |
| for (i = 0; i < aw->ep_cache_sz; i++) { |
| /* Skip the closed sockets */ |
| if (aw->ep_cache[i] && IS_VALID_SOCKET(aw->ep_cache[i]->sd)) { |
| int elapsed = (int)difftime(now, aw->ep_cache[i]->last_access); |
| if (elapsed > aw->cache_timeout) { |
| time_t rt = 0; |
| n++; |
| if (JK_IS_DEBUG_LEVEL(l)) |
| rt = time(NULL); |
| aw->ep_cache[i]->reuse = JK_FALSE; |
| ajp_reset_endpoint(aw->ep_cache[i], l); |
| if (JK_IS_DEBUG_LEVEL(l)) |
| jk_log(l, JK_LOG_DEBUG, |
| "cleaning pool slot=%u elapsed %d in %d", |
| i, elapsed, (int)(difftime(time(NULL), rt))); |
| } |
| } |
| if ((cnt - n) <= aw->ep_mincache_sz) { |
| if (JK_IS_DEBUG_LEVEL(l)) { |
| jk_log(l, JK_LOG_DEBUG, |
| "reached pool min size %u from %u cache slots", |
| aw->ep_mincache_sz, aw->ep_cache_sz); |
| } |
| break; |
| } |
| } |
| if (JK_IS_DEBUG_LEVEL(l)) |
| jk_log(l, JK_LOG_DEBUG, |
| "recycled %u sockets in %d seconds from %u pool slots", |
| n, (int)(difftime(time(NULL), now)), |
| aw->ep_cache_sz); |
| JK_LEAVE_CS(&aw->cs, rc); |
| JK_TRACE_EXIT(l); |
| return JK_TRUE; |
| } |
| else { |
| jk_log(l, JK_LOG_ERROR, |
| "locking thread (errno=%d)", |
| errno); |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| |
| } |
| } |
| else { |
| JK_LOG_NULL_PARAMS(l); |
| } |
| |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |