summaryrefslogtreecommitdiffstats
path: root/rubbos/app/tomcat-connectors-1.2.32-src/native/common/jk_ajp_common.c
diff options
context:
space:
mode:
Diffstat (limited to 'rubbos/app/tomcat-connectors-1.2.32-src/native/common/jk_ajp_common.c')
-rw-r--r--rubbos/app/tomcat-connectors-1.2.32-src/native/common/jk_ajp_common.c3383
1 files changed, 3383 insertions, 0 deletions
diff --git a/rubbos/app/tomcat-connectors-1.2.32-src/native/common/jk_ajp_common.c b/rubbos/app/tomcat-connectors-1.2.32-src/native/common/jk_ajp_common.c
new file mode 100644
index 00000000..7b083264
--- /dev/null
+++ b/rubbos/app/tomcat-connectors-1.2.32-src/native/common/jk_ajp_common.c
@@ -0,0 +1,3383 @@
+/*
+ * 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: 1137200 $ *
+ ***************************************************************************/
+
+
+#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;
+}
+
+static const char *ajp_state_type[] = {
+ JK_AJP_STATE_TEXT_IDLE,
+ JK_AJP_STATE_TEXT_OK,
+ JK_AJP_STATE_TEXT_ERROR,
+ JK_AJP_STATE_TEXT_PROBE,
+ "unknown",
+ NULL
+};
+
+#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 header
+ * that is of interest.
+ */
+ if (len < 4 || len > 15)
+ return UNKNOWN_METHOD;
+
+ while (*p) {
+ header[i++] = toupper((unsigned char)*p);
+ p++;
+ }
+
+ header[i] = '\0';
+ p = &header[1];
+
+/* Always do memcmp including the final \0-termination character.
+ */
+ switch (header[0]) {
+ case 'A':
+ if (memcmp(p, "CCEPT", 6) == 0) {
+ if (!header[6])
+ return SC_ACCEPT;
+ else if (header[6] == '-') {
+ p += 6;
+ if (memcmp(p, "CHARSET", 8) == 0)
+ return SC_ACCEPT_CHARSET;
+ else if (memcmp(p, "ENCODING", 9) == 0)
+ return SC_ACCEPT_ENCODING;
+ else if (memcmp(p, "LANGUAGE", 9) == 0)
+ return SC_ACCEPT_LANGUAGE;
+ else
+ return UNKNOWN_METHOD;
+ }
+ else
+ return UNKNOWN_METHOD;
+ }
+ else if (memcmp(p, "UTHORIZATION", 13) == 0)
+ return SC_AUTHORIZATION;
+ else
+ return UNKNOWN_METHOD;
+ break;
+ case 'C':
+ if(memcmp(p, "OOKIE2", 7) == 0)
+ return SC_COOKIE2;
+ else if (memcmp(p, "OOKIE", 6) == 0)
+ return SC_COOKIE;
+ else if(memcmp(p, "ONNECTION", 10) == 0)
+ return SC_CONNECTION;
+ else if(memcmp(p, "ONTENT-TYPE", 12) == 0)
+ return SC_CONTENT_TYPE;
+ else if(memcmp(p, "ONTENT-LENGTH", 14) == 0)
+ return SC_CONTENT_LENGTH;
+ else
+ return UNKNOWN_METHOD;
+ break;
+ case 'H':
+ if(memcmp(p, "OST", 4) == 0)
+ return SC_HOST;
+ else
+ return UNKNOWN_METHOD;
+ break;
+ case 'P':
+ if(memcmp(p, "RAGMA", 6) == 0)
+ return SC_PRAGMA;
+ else
+ return UNKNOWN_METHOD;
+ break;
+ case 'R':
+ if(memcmp(p, "EFERER", 7) == 0)
+ return SC_REFERER;
+ else
+ return UNKNOWN_METHOD;
+ break;
+ case 'U':
+ if(memcmp(p, "SER-AGENT", 10) == 0)
+ return SC_USER_AGENT;
+ else
+ return UNKNOWN_METHOD;
+ break;
+ default:
+ return UNKNOWN_METHOD;
+ }
+ /* NOTREACHED */
+}
+
+/* Return the string representation of the worker state */
+const char *jk_ajp_get_state(ajp_worker_t *aw, jk_logger_t *l)
+{
+ return ajp_state_type[aw->s->state];
+}
+
+/* Return the int representation of the worker state */
+int jk_ajp_get_state_code(const char *v)
+{
+ if (!v)
+ return JK_AJP_STATE_DEF;
+ else if (*v == 'i' || *v == 'I' || *v == 'n' || *v == 'N' || *v == '0')
+ return JK_AJP_STATE_IDLE;
+ else if (*v == 'o' || *v == 'O' || *v == '1')
+ return JK_AJP_STATE_OK;
+ else if (*v == 'e' || *v == 'E' || *v == '4')
+ return JK_AJP_STATE_ERROR;
+ else if (*v == 'p' || *v == 'P' || *v == '6')
+ return JK_AJP_STATE_PROBE;
+ else
+ return JK_AJP_STATE_DEF;
+}
+
+int jk_ajp_get_cping_mode(const char *m, int def)
+{
+ int mv = def;
+ if (!m)
+ return mv;
+ while (*m != '\0') {
+ if (*m == 'C' || *m == 'c')
+ mv |= AJP_CPING_CONNECT;
+ else if (*m == 'P' || *m == 'p')
+ mv |= AJP_CPING_PREPOST;
+ else if (*m == 'I' || *m == 'i')
+ mv |= AJP_CPING_INTERVAL;
+ else if (*m == 'A' || *m == 'a') {
+ mv = AJP_CPING_CONNECT | AJP_CPING_PREPOST | AJP_CPING_INTERVAL;
+ break;
+ }
+ m++;
+ }
+ return mv;
+}
+
+/*
+ * 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;
+ }
+ }
+
+ /* Forward the remote port information, which was forgotten
+ * from the builtin data of the AJP 13 protocol.
+ * Since the servlet spec allows to retrieve it via getRemotePort(),
+ * we provide the port to the Tomcat connector as a request
+ * attribute. Modern Tomcat versions know how to retrieve
+ * the remote port from this attribute.
+ */
+ {
+ if (jk_b_append_byte(msg, SC_A_REQ_ATTRIBUTE) ||
+ jk_b_append_string(msg, SC_A_REQ_REMOTE_PORT) ||
+ jk_b_append_string(msg, s->remote_port)) {
+ jk_log(l, JK_LOG_ERROR,
+ "failed appending the remote port %s",
+ s->remote_port);
+ JK_TRACE_EXIT(l);
+ return JK_FALSE;
+ }
+ }
+
+ /* Forward activation information from the load balancer.
+ * It can be used by the backend to deny access by requests,
+ * which come with a session id but for an invalid session.
+ * Such requests get forwarded to backends even if they
+ * are disabled" in the load balancer, because the balancer
+ * does not know, which sessions are valid.
+ * If the backend can check, that is was "disabled" it can
+ * delete the session cookie and respond with a self-referential
+ * redirect. The new request will then be balanced to some
+ * other node that is not disabled.
+ */
+ {
+ if (jk_b_append_byte(msg, SC_A_REQ_ATTRIBUTE) ||
+ jk_b_append_string(msg, SC_A_JK_LB_ACTIVATION) ||
+ jk_b_append_string(msg, s->activation)) {
+ jk_log(l, JK_LOG_ERROR,
+ "failed appending the activation state %s",
+ s->activation);
+ 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;
+
+ JK_TRACE_ENTER(l);
+
+ d->status = jk_b_get_int(msg);
+ 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;
+}
+
+/*
+ * Abort endpoint use
+ */
+static void ajp_abort_endpoint(ajp_endpoint_t * ae, int shutdown, jk_logger_t *l)
+{
+ JK_TRACE_ENTER(l);
+ if (shutdown == JK_TRUE && IS_VALID_SOCKET(ae->sd)) {
+ if (ae->hard_close) {
+ /* Force unclean connection close to communicate client write errors
+ * back to Tomcat by aborting AJP response writes.
+ */
+ jk_close_socket(ae->sd, l);
+ }
+ else {
+ jk_shutdown_socket(ae->sd, l);
+ }
+ }
+ ae->worker->s->connected--;
+ ae->sd = JK_INVALID_SOCKET;
+ ae->last_op = JK_AJP13_END_RESPONSE;
+ JK_TRACE_EXIT(l);
+}
+
+/*
+ * Reset the endpoint (clean buf and close socket)
+ */
+static void ajp_reset_endpoint(ajp_endpoint_t * ae, jk_logger_t *l)
+{
+ JK_TRACE_ENTER(l);
+
+ if (JK_IS_DEBUG_LEVEL(l))
+ jk_log(l, JK_LOG_DEBUG,
+ "(%s) resetting endpoint with socket %d%s",
+ ae->worker->name, ae->sd, ae->reuse? "" : " (socket shutdown)");
+ if (!ae->reuse) {
+ ajp_abort_endpoint(ae, JK_TRUE, l);
+ }
+ jk_reset_pool(&(ae->pool));
+ JK_TRACE_EXIT(l);
+}
+
+/*
+ * 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 (JK_IS_DEBUG_LEVEL(l))
+ jk_log(l, JK_LOG_DEBUG,
+ "(%s) closing endpoint with socket %d%s",
+ ae->worker->name, ae->sd, ae->reuse ? "" : " (socket shutdown)");
+ if (IS_VALID_SOCKET(ae->sd)) {
+ jk_shutdown_socket(ae->sd, l);
+ }
+ ae->sd = JK_INVALID_SOCKET;
+ jk_close_pool(&(ae->pool));
+ free(ae);
+ JK_TRACE_EXIT(l);
+}
+
+
+/** Steal a connection from an idle cache endpoint
+ * @param ae endpoint that needs a new connection
+ * @param l logger
+ * @return JK_FALSE: failure
+ * JK_TRUE: success
+ * @remark Always closes old socket endpoint
+ */
+static int ajp_next_connection(ajp_endpoint_t *ae, jk_logger_t *l)
+{
+ int rc;
+ int ret = JK_FALSE;
+ ajp_worker_t *aw = ae->worker;
+
+ JK_TRACE_ENTER(l);
+
+ /* Close previous socket */
+ if (IS_VALID_SOCKET(ae->sd))
+ jk_shutdown_socket(ae->sd, l);
+ /* 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);
+ if (IS_VALID_SOCKET(ae->sd)) {
+ ret = JK_TRUE;
+ if (JK_IS_DEBUG_LEVEL(l))
+ jk_log(l, JK_LOG_DEBUG,
+ "(%s) Will try pooled connection socket %d from slot %d",
+ ae->worker->name, ae->sd, i);
+ }
+ }
+ JK_TRACE_EXIT(l);
+ return ret;
+}
+
+/** Handle the cping/cpong query
+ * @param ae endpoint
+ * @param timeout wait timeout in milliseconds
+ * @param l logger
+ * @return JK_FALSE: failure
+ * JK_TRUE: success
+ * @remark Always closes socket in case of
+ * a socket error
+ */
+static int ajp_handle_cping_cpong(ajp_endpoint_t * ae, int timeout, jk_logger_t *l)
+{
+ int i;
+ int cmd;
+ jk_msg_buf_t *msg;
+
+ JK_TRACE_ENTER(l);
+
+ ae->last_errno = 0;
+ msg = jk_b_new(&ae->pool);
+ if (!msg) {
+ jk_log(l, JK_LOG_ERROR,
+ "Failed allocating AJP message");
+ JK_TRACE_EXIT(l);
+ return JK_FALSE;
+ }
+ if (jk_b_set_buffer_size(msg, 16)) {
+ jk_log(l, JK_LOG_ERROR,
+ "Failed allocating AJP message buffer");
+ JK_TRACE_EXIT(l);
+ return JK_FALSE;
+ }
+ 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;
+ }
+
+ for (i = 0; i < 2; i++) {
+ /* wait for Pong reply for timeout milliseconds
+ */
+ if (jk_is_input_event(ae->sd, timeout, l) == JK_FALSE) {
+ ae->last_errno = errno;
+ jk_log(l, JK_LOG_INFO, "timeout in reply cpong");
+ /* We can't trust this connection any more. */
+ ajp_abort_endpoint(ae, JK_TRUE, l);
+ 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) {
+ /* If the respose was not CPONG it means that
+ * the previous response was not consumed by the
+ * client but the AJP messages was already in
+ * the network buffer.
+ * silently drop this single extra packet instead
+ * recycling the connection
+ */
+ if (i || ae->last_op == JK_AJP13_END_RESPONSE ||
+ cmd < JK_AJP13_SEND_BODY_CHUNK ||
+ cmd > AJP13_CPONG_REPLY) {
+ jk_log(l, JK_LOG_WARNING,
+ "awaited reply cpong, received %d instead. "
+ "Closing connection",
+ cmd);
+ /* We can't trust this connection any more. */
+ ajp_abort_endpoint(ae, JK_TRUE, l);
+ JK_TRACE_EXIT(l);
+ return JK_FALSE;
+ }
+ else {
+ jk_log(l, JK_LOG_INFO,
+ "awaited reply cpong, received %d instead. "
+ "Retrying next packet",
+ cmd);
+
+ }
+ }
+ else {
+ ae->last_op = AJP13_CPONG_REPLY;
+ /* We have received Pong reply */
+ break;
+ }
+ }
+ JK_TRACE_EXIT(l);
+ return JK_TRUE;
+}
+
+/** Connect an endpoint to a backend
+ * @param ae endpoint
+ * @param l logger
+ * @return JK_FALSE: failure
+ * JK_TRUE: success
+ * @remark Always closes socket in case of
+ * a socket error
+ * @remark Cares about ae->last_errno
+ */
+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->last_errno = 0;
+ ae->sd = jk_open_socket(&ae->worker->worker_inet_addr,
+ ae->worker->keepalive,
+ ae->worker->socket_timeout,
+ ae->worker->socket_connect_timeout,
+ ae->worker->socket_buf, l);
+
+ if (!IS_VALID_SOCKET(ae->sd)) {
+ 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;
+ }
+ ae->worker->s->connected++;
+ /* 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 */
+ /* XXX: Not sure, if we really should do logon before cping/cpong */
+ /* XXX: and if no cping/cpong is allowed before or after logon. */
+ if (ae->worker->logon != NULL) {
+ rc = ae->worker->logon(ae, l);
+ if (rc == JK_FALSE) {
+ jk_log(l, JK_LOG_ERROR,
+ "(%s) ajp14 worker logon to the backend server failed",
+ ae->worker->name);
+ /* Close the socket if unable to logon */
+ ajp_abort_endpoint(ae, JK_TRUE, l);
+ }
+ }
+ /* XXX: Should we send a cping also after logon to validate the connection? */
+ else if (ae->worker->connect_timeout > 0) {
+ rc = ajp_handle_cping_cpong(ae, ae->worker->connect_timeout, l);
+ if (rc == JK_FALSE)
+ jk_log(l, JK_LOG_ERROR,
+ "(%s) cping/cpong after connecting to the backend server failed (errno=%d)",
+ ae->worker->name, ae->last_errno);
+ }
+ JK_TRACE_EXIT(l);
+ return rc;
+}
+
+/* Syncing config values from shm */
+void jk_ajp_pull(ajp_worker_t * aw, int locked, jk_logger_t *l)
+{
+ int address_change = JK_FALSE;
+ int port = 0;
+ char host[JK_SHM_STR_SIZ+1];
+ struct sockaddr_in inet_addr;
+ JK_TRACE_ENTER(l);
+
+ if (JK_IS_DEBUG_LEVEL(l))
+ jk_log(l, JK_LOG_DEBUG,
+ "syncing mem for ajp worker '%s' from shm (%u -> %u) [%u->%u]",
+ aw->name, aw->sequence, aw->s->h.sequence, aw->addr_sequence, aw->s->addr_sequence);
+ if (locked == JK_FALSE)
+ jk_shm_lock();
+
+ aw->cache_timeout = aw->s->cache_timeout;
+ aw->connect_timeout = aw->s->connect_timeout;
+ aw->ping_timeout = aw->s->ping_timeout;
+ aw->reply_timeout = aw->s->reply_timeout;
+ aw->prepost_timeout = aw->s->prepost_timeout;
+ aw->recovery_opts = aw->s->recovery_opts;
+ aw->retries = aw->s->retries;
+ aw->retry_interval = aw->s->retry_interval;
+ aw->max_packet_size = aw->s->max_packet_size;
+ aw->sequence = aw->s->h.sequence;
+ if (aw->addr_sequence != aw->s->addr_sequence) {
+ address_change = JK_TRUE;
+ aw->addr_sequence = aw->s->addr_sequence;
+ strncpy(host, aw->s->host, JK_SHM_STR_SIZ);
+ port = aw->s->port;
+ }
+ if (locked == JK_FALSE)
+ jk_shm_unlock();
+
+ if (address_change == JK_TRUE) {
+ if (!jk_resolve(host, port, &inet_addr,
+ aw->worker.we->pool, l)) {
+ jk_log(l, JK_LOG_ERROR,
+ "Failed resolving address '%s:%d' for worker '%s'.",
+ host, port, aw->name);
+ }
+ else {
+ int rc;
+ JK_ENTER_CS(&aw->cs, rc);
+ if (rc) {
+ unsigned int i;
+ for (i = 0; i < aw->ep_cache_sz; i++) {
+ /* Close all connections in the cache */
+ if (aw->ep_cache[i] && IS_VALID_SOCKET(aw->ep_cache[i]->sd)) {
+ int sd = aw->ep_cache[i]->sd;
+ aw->ep_cache[i]->sd = JK_INVALID_SOCKET;
+ aw->ep_cache[i]->addr_sequence = aw->addr_sequence;
+ jk_shutdown_socket(sd, l);
+ aw->s->connected--;
+ }
+ }
+ }
+ aw->port = port;
+ strncpy(aw->host, host, JK_SHM_STR_SIZ);
+ memcpy(&(aw->worker_inet_addr), &inet_addr, sizeof(inet_addr));
+ if (rc) {
+ JK_LEAVE_CS(&aw->cs, rc);
+ } else {
+ jk_log(l, JK_LOG_ERROR,
+ "locking thread (errno=%d)", errno);
+ }
+ }
+ }
+
+ JK_TRACE_EXIT(l);
+}
+
+/* Syncing config values to shm */
+void jk_ajp_push(ajp_worker_t * aw, int locked, jk_logger_t *l)
+{
+ int address_change = JK_FALSE;
+
+ JK_TRACE_ENTER(l);
+
+ if (JK_IS_DEBUG_LEVEL(l))
+ jk_log(l, JK_LOG_DEBUG,
+ "syncing shm for ajp worker '%s' from mem (%u -> %u) [%u->%u]",
+ aw->name, aw->s->h.sequence, aw->sequence, aw->s->addr_sequence, aw->addr_sequence);
+ if (locked == JK_FALSE)
+ jk_shm_lock();
+
+ aw->s->cache_timeout = aw->cache_timeout;
+ aw->s->connect_timeout = aw->connect_timeout;
+ aw->s->ping_timeout = aw->ping_timeout;
+ aw->s->reply_timeout = aw->reply_timeout;
+ aw->s->prepost_timeout = aw->prepost_timeout;
+ aw->s->recovery_opts = aw->recovery_opts;
+ aw->s->retries = aw->retries;
+ aw->s->retry_interval = aw->retry_interval;
+ aw->s->max_packet_size = aw->max_packet_size;
+ aw->s->h.sequence = aw->sequence;
+ if (aw->s->addr_sequence != aw->addr_sequence) {
+ address_change = JK_TRUE;
+ strncpy(aw->s->host, aw->host, JK_SHM_STR_SIZ);
+ aw->s->port = aw->port;
+ aw->s->addr_sequence = aw->addr_sequence;
+ }
+ if (locked == JK_FALSE)
+ jk_shm_unlock();
+
+ if (address_change == JK_TRUE) {
+ int rc;
+ JK_ENTER_CS(&aw->cs, rc);
+ if (rc) {
+ unsigned int i;
+ for (i = 0; i < aw->ep_cache_sz; i++) {
+ /* Close all connections in the cache */
+ if (aw->ep_cache[i] && IS_VALID_SOCKET(aw->ep_cache[i]->sd)) {
+ int sd = aw->ep_cache[i]->sd;
+ aw->ep_cache[i]->sd = JK_INVALID_SOCKET;
+ aw->ep_cache[i]->addr_sequence = aw->addr_sequence;
+ jk_shutdown_socket(sd, l);
+ aw->s->connected--;
+ }
+ }
+ JK_LEAVE_CS(&aw->cs, rc);
+ } else {
+ jk_log(l, JK_LOG_ERROR,
+ "locking thread (errno=%d)", errno);
+ }
+ }
+ JK_TRACE_EXIT(l);
+}
+
+/** Send a message to an endpoint, using corresponding PROTO HEADER
+ * @param ae endpoint
+ * @param msg message to send
+ * @param l logger
+ * @return JK_FATAL_ERROR: endpoint contains unknown protocol
+ * JK_FALSE: other failure
+ * JK_TRUE: success
+ * @remark Always closes socket in case of
+ * a socket error, or JK_FATAL_ERROR
+ * @remark Cares about ae->last_errno
+ */
+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);
+
+ ae->last_errno = 0;
+ 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,
+ "(%s) unknown protocol %d, supported are AJP13/AJP14",
+ ae->worker->name, ae->proto);
+ /* We've got a protocol error. */
+ /* We can't trust this connection any more, */
+ /* because we might have send already parts of the request. */
+ ajp_abort_endpoint(ae, JK_TRUE, l);
+ JK_TRACE_EXIT(l);
+ return JK_FATAL_ERROR;
+ }
+
+ /* This is the only place in this function where we use the socket. */
+ /* If sendfull gets an error, it implicitely closes the socket. */
+ /* So any socket error inside ajp_connection_tcp_send_message */
+ /* results in a socket close and invalidated endpoint connection. */
+ if ((rc = jk_tcp_socket_sendfull(ae->sd, msg->buf,
+ msg->len, l)) > 0) {
+ ae->endpoint.wr += (jk_uint64_t)rc;
+ JK_TRACE_EXIT(l);
+ return JK_TRUE;
+ }
+ ae->last_errno = errno;
+ jk_log(l, JK_LOG_INFO,
+ "sendfull for socket %d returned %d (errno=%d)",
+ ae->sd, rc, ae->last_errno);
+ ajp_abort_endpoint(ae, JK_FALSE, l);
+ JK_TRACE_EXIT(l);
+ return JK_FALSE;
+}
+
+/** Receive a message from an endpoint, checking PROTO HEADER
+ * @param ae endpoint
+ * @param msg message to send
+ * @param l logger
+ * @return JK_TRUE: success
+ * JK_FALSE: could not read the AJP packet header
+ * JK_AJP_PROTOCOL_ERROR: failure after reading
+ * the AJP packet header
+ * @remark Always closes socket in case of
+ * a socket error
+ * @remark Cares about ae->last_errno
+ */
+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);
+
+ ae->last_errno = 0;
+ /* If recvfull gets an error, it implicitely closes the socket. */
+ /* We will invalidate the endpoint connection. */
+ rc = jk_tcp_socket_recvfull(ae->sd, head, AJP_HEADER_LEN, l);
+
+ /* If the return code is not negative */
+ /* then we always get back the correct number of bytes. */
+ if (rc < 0) {
+ if (rc == JK_SOCKET_EOF) {
+ ae->last_errno = EPIPE;
+ jk_log(l, JK_LOG_INFO,
+ "(%s) can't receive the response header 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);
+ }
+ else {
+ ae->last_errno = -rc;
+ jk_log(l, JK_LOG_INFO,
+ "(%s) can't receive the response header 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);
+ }
+ ajp_abort_endpoint(ae, JK_FALSE, l);
+ JK_TRACE_EXIT(l);
+ return JK_FALSE;
+ }
+ ae->endpoint.rd += (jk_uint64_t)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));
+ }
+ /* We've got a protocol error. */
+ /* We can't trust this connection any more. */
+ ajp_abort_endpoint(ae, JK_TRUE, l);
+ JK_TRACE_EXIT(l);
+ return JK_AJP_PROTOCOL_ERROR;
+ }
+ }
+ 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));
+ }
+ /* We've got a protocol error. */
+ /* We can't trust this connection any more. */
+ ajp_abort_endpoint(ae, JK_TRUE, l);
+ JK_TRACE_EXIT(l);
+ return JK_AJP_PROTOCOL_ERROR;
+ }
+ }
+
+ 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));
+ /* We've got a protocol error. */
+ /* We can't trust this connection any more. */
+ ajp_abort_endpoint(ae, JK_TRUE, l);
+ JK_TRACE_EXIT(l);
+ return JK_AJP_PROTOCOL_ERROR;
+ }
+
+ msg->len = msglen;
+ msg->pos = 0;
+
+ /* If recvfull gets an error, it implicitely closes the socket. */
+ /* We will invalidate the endpoint connection. */
+ rc = jk_tcp_socket_recvfull(ae->sd, msg->buf, msglen, l);
+ /* If the return code is not negative */
+ /* then we always get back the correct number of bytes. */
+ if (rc < 0) {
+ if (rc == JK_SOCKET_EOF) {
+ ae->last_errno = EPIPE;
+ jk_log(l, JK_LOG_ERROR,
+ "(%s) can't receive the response body 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);
+ }
+ else {
+ ae->last_errno = -rc;
+ jk_log(l, JK_LOG_ERROR,
+ "(%s) can't receive the response body 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);
+ }
+ ajp_abort_endpoint(ae, JK_FALSE, l);
+ JK_TRACE_EXIT(l);
+ /* Although we have a connection, this is effectively a protocol error.
+ * We received the AJP header packet, but not the packet payload
+ */
+ return JK_AJP_PROTOCOL_ERROR;
+ }
+ ae->endpoint.rd += (jk_uint64_t)rc;
+
+ if (JK_IS_DEBUG_LEVEL(l)) {
+ if (ae->proto == AJP13_PROTO)
+ jk_dump_buff(l, JK_LOG_DEBUG, "received from ajp13", msg);
+ else if (ae->proto == AJP14_PROTO)
+ 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;
+ }
+
+ JK_TRACE_EXIT(l);
+ 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 such available, try to connect
+ * - send request, but send must be seen as asynchronous,
+ * since send() call will return noerror about 95% of time
+ * Hopefully we'll get more information on next read.
+ *
+ * nb: op->request is the original request msg buffer
+ * op->reply is the reply msg buffer which could be scratched
+ *
+ * Return values of ajp_send_request() function:
+ * return value op->recoverable reason
+ * JK_FATAL_ERROR JK_FALSE ajp_connection_tcp_send_message() returns JK_FATAL_ERROR
+ * Endpoint belongs to unknown protocol.
+ * JK_FATAL_ERROR JK_TRUE ajp_connection_tcp_send_message() returns JK_FALSE
+ * Sending request or request body in jk_tcp_socket_sendfull() returns with error.
+ * JK_FATAL_ERROR JK_TRUE Could not connect to backend
+ * JK_CLIENT_RD_ERROR JK_FALSE Error during reading parts of POST body from client
+ * JK_TRUE JK_TRUE All other cases (OK)
+ */
+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_conn = 0;
+ int err_cping = 0;
+ int err_send = 0;
+ int rc;
+ int postlen;
+
+ JK_TRACE_ENTER(l);
+
+ ae->last_errno = 0;
+ /* Up to now, we can recover */
+ op->recoverable = JK_TRUE;
+
+ /* Check if the previous request really ended
+ */
+ if (ae->last_op != JK_AJP13_END_RESPONSE &&
+ ae->last_op != AJP13_CPONG_REPLY) {
+ jk_log(l, JK_LOG_INFO,
+ "(%s) did not receive END_RESPONSE, "
+ "closing socket %d",
+ ae->worker->name, ae->sd);
+ ajp_abort_endpoint(ae, JK_TRUE, l);
+ }
+ /*
+ * First try to check open connections...
+ */
+ while (IS_VALID_SOCKET(ae->sd)) {
+ int err = JK_FALSE;
+ if (jk_is_socket_connected(ae->sd, l) == JK_FALSE) {
+ ae->last_errno = errno;
+ jk_log(l, JK_LOG_DEBUG,
+ "(%s) failed sending request, "
+ "socket %d is not connected any more (errno=%d)",
+ ae->worker->name, ae->sd, ae->last_errno);
+ ajp_abort_endpoint(ae, JK_FALSE, l);
+ err = JK_TRUE;
+ err_conn++;
+ }
+ 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) {
+ jk_log(l, JK_LOG_INFO,
+ "(%s) failed sending request, "
+ "socket %d prepost cping/cpong failure (errno=%d)",
+ ae->worker->name, ae->sd, ae->last_errno);
+ /* 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 other connections would
+ * fail as well.
+ */
+ err = JK_TRUE;
+ err_cping++;
+ }
+ }
+
+ /* We've got a connected socket and the optional
+ * cping/cpong worked, so let's send the request now.
+ */
+ if (err == JK_FALSE) {
+ rc = ajp_connection_tcp_send_message(ae, op->request, l);
+ /* If this worked, we can break out of the loop
+ * and proceed with the request.
+ */
+ if (rc == JK_TRUE) {
+ ae->last_op = JK_AJP13_FORWARD_REQUEST;
+ break;
+ }
+ /* Error during sending the request.
+ */
+ err_send++;
+ if (rc == JK_FATAL_ERROR)
+ op->recoverable = JK_FALSE;
+ jk_log(l, JK_LOG_INFO,
+ "(%s) failed sending request (%srecoverable) "
+ "(errno=%d)",
+ ae->worker->name,
+ op->recoverable ? "" : "un",
+ ae->last_errno);
+ JK_TRACE_EXIT(l);
+ return JK_FATAL_ERROR;
+ }
+ /* If we got an error or can't send data, then try to steal another pooled
+ * connection and try again. If we are not successful, break out of this
+ * loop and try to open a new connection after the loop.
+ */
+ if (ajp_next_connection(ae, l) == JK_FALSE)
+ break;
+ }
+
+ /*
+ * If we failed to reuse a connection, try to reconnect.
+ */
+ if (!IS_VALID_SOCKET(ae->sd)) {
+ /* Could not steal any connection from an endpoint - backend is disconnected */
+ if (err_conn + err_cping + err_send > 0)
+ jk_log(l, JK_LOG_INFO,
+ "(%s) all endpoints are disconnected, "
+ "detected by connect check (%d), cping (%d), send (%d)",
+ ae->worker->name, err_conn, err_cping, err_send);
+ else if (JK_IS_DEBUG_LEVEL(l))
+ jk_log(l, JK_LOG_DEBUG,
+ "(%s) all endpoints are disconnected.",
+ ae->worker->name);
+ /* Connect to the backend.
+ */
+ if (ajp_connect_to_endpoint(ae, l) != JK_TRUE) {
+ jk_log(l, JK_LOG_ERROR,
+ "(%s) connecting to backend failed. Tomcat is probably not started "
+ "or is listening on the wrong port (errno=%d)",
+ ae->worker->name, ae->last_errno);
+ JK_TRACE_EXIT(l);
+ return JK_FATAL_ERROR;
+ }
+ if (ae->worker->connect_timeout <= 0 &&
+ ae->worker->prepost_timeout > 0) {
+ /* handle cping/cpong if prepost_timeout is set
+ * and we didn't already do a connect cping/cpong.
+ */
+ if (ajp_handle_cping_cpong(ae,
+ ae->worker->prepost_timeout, l) == JK_FALSE) {
+ jk_log(l, JK_LOG_INFO,
+ "(%s) failed sending request, "
+ "socket %d prepost cping/cpong failure (errno=%d)",
+ ae->worker->name, ae->sd, ae->last_errno);
+ JK_TRACE_EXIT(l);
+ return JK_FATAL_ERROR;
+ }
+ }
+
+ /* We've got a connected socket and the optional
+ * cping/cpong worked, so let's send the request now.
+ */
+ rc = ajp_connection_tcp_send_message(ae, op->request, l);
+ /* Error during sending the request.
+ */
+ if (rc != JK_TRUE) {
+ if (rc == JK_FATAL_ERROR)
+ op->recoverable = JK_FALSE;
+ jk_log(l, JK_LOG_ERROR,
+ "(%s) failed sending request on a fresh connection (%srecoverable), "
+ "socket %d (errno=%d)",
+ ae->worker->name, op->recoverable ? "" : "un",
+ ae->sd, ae->last_errno);
+ JK_TRACE_EXIT(l);
+ return JK_FATAL_ERROR;
+ }
+ ae->last_op = JK_AJP13_FORWARD_REQUEST;
+ }
+ else if (JK_IS_DEBUG_LEVEL(l))
+ jk_log(l, JK_LOG_DEBUG,
+ "(%s) Statistics about invalid connections: "
+ "connect check (%d), cping (%d), send (%d)",
+ ae->worker->name, err_conn, err_cping, err_send);
+
+ /*
+ * From now on an error means that we have an internal server error
+ * or Tomcat crashed.
+ */
+
+ if (JK_IS_DEBUG_LEVEL(l))
+ jk_log(l, JK_LOG_DEBUG,
+ "(%s) request body to send %" JK_UINT64_T_FMT " - 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) {
+ rc = ajp_connection_tcp_send_message(ae, op->post, l);
+ /* Error during sending the request body.
+ */
+ if (rc != JK_TRUE) {
+ if (rc == JK_FATAL_ERROR)
+ op->recoverable = JK_FALSE;
+ jk_log(l, JK_LOG_ERROR,
+ "(%s) failed sending request body of size %d (%srecoverable), "
+ "socket %d (errno=%d)",
+ ae->worker->name, postlen, op->recoverable ? "" : "un",
+ ae->sd, ae->last_errno);
+ JK_TRACE_EXIT(l);
+ return JK_FATAL_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) {
+ rc = ajp_connection_tcp_send_message(ae, s->reco_buf, l);
+ /* Error during sending the request body.
+ */
+ if (rc != JK_TRUE) {
+ if (rc == JK_FATAL_ERROR)
+ op->recoverable = JK_FALSE;
+ jk_log(l, JK_LOG_ERROR,
+ "(%s) failed sending request body of size %d (lb mode) (%srecoverable), "
+ "socket %d (errno=%d)",
+ ae->worker->name, postlen, op->recoverable ? "" : "un",
+ ae->sd, ae->last_errno);
+ JK_TRACE_EXIT(l);
+ return JK_FATAL_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 = AJP13_MAX_SEND_BODY_SZ;
+ if (ae->left_bytes_to_send < (jk_uint64_t)AJP13_MAX_SEND_BODY_SZ) {
+ len = (int)ae->left_bytes_to_send;
+ }
+ if ((len = ajp_read_into_msg_buff(ae, s, op->post, len, l)) <= 0) {
+ if (JK_IS_DEBUG_LEVEL(l))
+ jk_log(l, JK_LOG_DEBUG,
+ "(%s) browser stop sending data, no need to recover",
+ ae->worker->name);
+ op->recoverable = JK_FALSE;
+ /* Send an empty POST message since per AJP protocol
+ * spec whenever we have content length the message
+ * packet must be followed with initial POST packet.
+ * Size zero will be handled as error in container.
+ */
+ jk_b_reset(op->post);
+ jk_b_append_int(op->post, 0);
+ ajp_connection_tcp_send_message(ae, op->post, l);
+ 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;
+ }
+ if (JK_IS_DEBUG_LEVEL(l))
+ jk_log(l, JK_LOG_DEBUG,
+ "(%s) sending %d bytes of request body",
+ ae->worker->name, len);
+
+ s->content_read = (jk_uint64_t)len;
+ rc = ajp_connection_tcp_send_message(ae, op->post, l);
+ /* Error during sending the request body.
+ */
+ if (rc != JK_TRUE) {
+ if (rc == JK_FATAL_ERROR)
+ op->recoverable = JK_FALSE;
+ jk_log(l, JK_LOG_ERROR,
+ "(%s) failed sending request body of size %d (%srecoverable), "
+ "socket %d (errno=%d)",
+ ae->worker->name, len, op->recoverable ? "" : "un",
+ ae->sd, ae->last_errno);
+ JK_TRACE_EXIT(l);
+ return JK_FATAL_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:
+ {
+ int rc;
+ jk_res_data_t res;
+ if (ae->last_op == JK_AJP13_SEND_HEADERS) {
+ /* Do not send anything to the client.
+ * Backend already send us the headers.
+ */
+ if (JK_IS_DEBUG_LEVEL(l)) {
+ jk_log(l, JK_LOG_DEBUG,
+ "Already received AJP13_SEND HEADERS");
+ }
+ JK_TRACE_EXIT(l);
+ return JK_AJP13_ERROR;
+ }
+ 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->http_response_status = res.status;
+ if (r->extension.fail_on_status_size > 0)
+ rc = is_http_status_fail(r->extension.fail_on_status_size,
+ r->extension.fail_on_status, res.status);
+ else
+ rc = is_http_status_fail(ae->worker->http_status_fail_num,
+ ae->worker->http_status_fail, res.status);
+ if (rc > 0) {
+ JK_TRACE_EXIT(l);
+ return JK_STATUS_FATAL_ERROR;
+ }
+ else if (rc < 0) {
+ JK_TRACE_EXIT(l);
+ return JK_STATUS_ERROR;
+ }
+
+ if (r->extension.use_server_error_pages &&
+ r->http_response_status >= r->extension.use_server_error_pages)
+ r->response_blocked = JK_TRUE;
+
+ /*
+ * Call even if response is blocked, since it also handles
+ * forwarding some headers for special http status codes
+ * even if the server uses an own error page.
+ * Example: The WWW-Authenticate header in case of
+ * HTTP_UNAUTHORIZED (401).
+ */
+ 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->response_blocked) {
+ if (r->flush && r->flush_header)
+ r->flush(r);
+ }
+ }
+ return JK_AJP13_SEND_HEADERS;
+
+ case JK_AJP13_SEND_BODY_CHUNK:
+ if (ae->last_op == JK_AJP13_FORWARD_REQUEST) {
+ /* AJP13_SEND_BODY_CHUNK with length 0 is
+ * explicit flush packet message.
+ * Ignore those if they are left over from previous responses.
+ * Reportedly some versions of JBoss suffer from that problem.
+ */
+ if (jk_b_get_int(msg) == 0) {
+ jk_log(l, JK_LOG_DEBUG,
+ "Ignoring flush message received while sending the request");
+ return ae->last_op;
+ }
+ /* We have just send a request but received something
+ * that probably originates from buffered response.
+ */
+ if (JK_IS_DEBUG_LEVEL(l)) {
+ jk_log(l, JK_LOG_DEBUG,
+ "Unexpected AJP13_SEND_BODY_CHUNK");
+ }
+ JK_TRACE_EXIT(l);
+ return JK_AJP13_ERROR;
+ }
+ if (!r->response_blocked) {
+ 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->response_started) {
+ if (r->flush) {
+ r->flush(r);
+ }
+ }
+ else {
+ jk_log(l, JK_LOG_DEBUG,
+ "Ignoring flush message received before headers");
+ }
+ }
+ 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 ((jk_uint64_t)len > ae->left_bytes_to_send) {
+ len = (int)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 += (jk_uint64_t)len;
+ JK_TRACE_EXIT(l);
+ return JK_AJP13_HAS_RESPONSE;
+ }
+
+ jk_log(l, JK_LOG_INFO,
+ "Reading 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;
+ }
+ if (!r->response_blocked) {
+ if (r->done) {
+ /* Done with response */
+ r->done(r);
+ }
+ else if (r->flush && !r->flush_packets) {
+ /* Flush after the last write */
+ 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;
+}
+
+/*
+ * get replies from Tomcat via Ajp13/Ajp14
+ * 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.
+ *
+ * Return values of ajp_get_reply() function:
+ * return value op->recoverable reason
+ * JK_REPLY_TIMEOUT ?recovery_options Reply timeout while waiting for response packet
+ * JK_FALSE ?recovery_options Error during ajp_connection_tcp_get_message()
+ * Could not read the AJP packet header
+ * JK_AJP_PROTOCOL_ERROR: ?recovery_options Error during ajp_connection_tcp_get_message()
+ * Failure after reading the AJP packet header
+ * JK_STATUS_ERROR mostly JK_TRUE ajp_process_callback() returns JK_STATUS_ERROR
+ * Recoverable, if callback didn't return with a JK_HAS_RESPONSE before.
+ * JK_HAS_RESPONSE: parts of the post buffer are consumed.
+ * JK_STATUS_FATAL_ERROR mostly JK_TRUE ajp_process_callback() returns JK_STATUS_FATAL_ERROR
+ * Recoverable, if callback didn't return with a JK_HAS_RESPONSE before.
+ * JK_HAS_RESPONSE: parts of the post buffer are consumed.
+ * JK_FATAL_ERROR ? ajp_process_callback() returns JK_AJP13_ERROR
+ * JK_AJP13_ERROR: protocol error, or JK_INTERNAL_ERROR: chunk size to large
+ * JK_CLIENT_RD_ERROR ? ajp_process_callback() returns JK_CLIENT_RD_ERROR
+ * JK_CLIENT_RD_ERROR: could not read post from client.
+ * JK_CLIENT_WR_ERROR ? ajp_process_callback() returns JK_CLIENT_WR_ERROR
+ * JK_CLIENT_WR_ERROR: could not write back result to client
+ * JK_TRUE ? ajp_process_callback() returns JK_AJP13_END_RESPONSE
+ * JK_FALSE ? Other unhandled cases (unknown return codes)
+ */
+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);
+
+ p->last_errno = 0;
+ /* Start read all reply message */
+ while (1) {
+ int rc = 0;
+ /* Allow to overwrite reply_timeout on a per URL basis via service struct */
+ int reply_timeout = s->extension.reply_timeout;
+ if (reply_timeout < 0)
+ reply_timeout = p->worker->reply_timeout;
+ /* If we set a reply timeout, check if something is available */
+ if (reply_timeout > 0) {
+ if (jk_is_input_event(p->sd, reply_timeout, l) ==
+ JK_FALSE) {
+ p->last_errno = errno;
+ 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);
+ /* We can't trust this connection any more. */
+ ajp_abort_endpoint(p, JK_TRUE, l);
+ if (headeratclient == JK_FALSE) {
+ if (p->worker->recovery_opts & RECOVER_ABORT_IF_TCGETREQUEST)
+ op->recoverable = JK_FALSE;
+ /*
+ * We revert back to recoverable, if recovery_opts allow it for GET or HEAD
+ */
+ if (op->recoverable == JK_FALSE) {
+ if (p->worker->recovery_opts & RECOVER_ALWAYS_HTTP_HEAD) {
+ if (!strcmp(s->method, "HEAD"))
+ op->recoverable = JK_TRUE;
+ }
+ else if (p->worker->recovery_opts & RECOVER_ALWAYS_HTTP_GET) {
+ if (!strcmp(s->method, "GET"))
+ op->recoverable = JK_TRUE;
+ }
+ }
+ }
+ else {
+ if (p->worker->recovery_opts & RECOVER_ABORT_IF_TCSENDHEADER)
+ op->recoverable = JK_FALSE;
+ }
+
+ JK_TRACE_EXIT(l);
+ return JK_REPLY_TIMEOUT;
+ }
+ }
+
+ if ((rc = ajp_connection_tcp_get_message(p, op->reply, l)) != JK_TRUE) {
+ 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.
+ */
+
+ /*
+ * 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 revert back to recoverable, if recovery_opts allow it for GET or HEAD
+ */
+ if (op->recoverable == JK_FALSE) {
+ if (p->worker->recovery_opts & RECOVER_ALWAYS_HTTP_HEAD) {
+ if (!strcmp(s->method, "HEAD"))
+ op->recoverable = JK_TRUE;
+ }
+ else if (p->worker->recovery_opts & RECOVER_ALWAYS_HTTP_GET) {
+ if (!strcmp(s->method, "GET"))
+ op->recoverable = JK_TRUE;
+ }
+ }
+
+ }
+ 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!
+ */
+
+ /*
+ * 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 rc;
+ }
+
+ rc = ajp_process_callback(op->reply, op->post, p, s, l);
+ p->last_op = rc;
+ /* 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 (headeratclient == JK_FALSE)
+ headeratclient = JK_TRUE;
+ else {
+ /* Backend send headers twice?
+ * This is protocol violation
+ */
+ jk_log(l, JK_LOG_ERROR,
+ "(%s) Tomcat already send headers",
+ p->worker->name);
+ op->recoverable = JK_FALSE;
+ JK_TRACE_EXIT(l);
+ return JK_FALSE;
+ }
+ }
+ else if (JK_STATUS_ERROR == rc || JK_STATUS_FATAL_ERROR == rc) {
+ jk_log(l, JK_LOG_INFO,
+ "(%s) request failed%s, "
+ "because of response status %d, ",
+ p->worker->name,
+ rc == JK_STATUS_FATAL_ERROR ? "" : " (soft)",
+ s->http_response_status);
+ JK_TRACE_EXIT(l);
+ return rc;
+ }
+ else if (JK_AJP13_HAS_RESPONSE == rc) {
+ /*
+ * in upload-mode there is no second chance since
+ * we may have already 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_TRACE_EXIT(l);
+ return JK_FALSE;
+ }
+ }
+ else if (JK_AJP13_ERROR == rc) {
+ /*
+ * Tomcat has send invalid AJP message.
+ * Loadbalancer if present will decide if
+ * failover is possible.
+ */
+ JK_TRACE_EXIT(l);
+ return JK_FATAL_ERROR;
+ }
+ 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_INTERNAL_ERROR == rc) {
+ /*
+ * Internal error, like memory allocation or invalid packet lengths.
+ */
+ JK_TRACE_EXIT(l);
+ return JK_FATAL_ERROR;
+ }
+ else if (JK_AJP13_NO_RESPONSE == rc) {
+ /*
+ * This is fine, loop again, more data to send.
+ */
+ continue;
+ }
+ else if (rc < 0) {
+ op->recoverable = JK_FALSE;
+ jk_log(l, JK_LOG_ERROR,
+ "(%s) Callback returns with unknown value %d",
+ p->worker->name, rc);
+ JK_TRACE_EXIT(l);
+ return JK_FALSE;
+ }
+ }
+ /* XXX: Not reached? */
+ JK_TRACE_EXIT(l);
+ return JK_FALSE;
+}
+
+static void ajp_update_stats(jk_endpoint_t *e, ajp_worker_t *aw, int rc, jk_logger_t *l)
+{
+ aw->s->readed += e->rd;
+ aw->s->transferred += e->wr;
+ if (aw->s->busy)
+ aw->s->busy--;
+ if (rc == JK_TRUE) {
+ aw->s->state = JK_AJP_STATE_OK;
+ }
+ else if (rc == JK_CLIENT_ERROR) {
+ aw->s->state = JK_AJP_STATE_OK;
+ aw->s->client_errors++;
+ }
+ else {
+ aw->s->state = JK_AJP_STATE_ERROR;
+ aw->s->errors++;
+ aw->s->error_time = time(NULL);
+ }
+}
+
+/*
+ * 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)
+ *
+ * Return values of service() method for ajp13/ajp14 worker:
+ * return value is_error e->recoverable reason
+ * JK_FALSE JK_HTTP_SERVER_ERROR TRUE Invalid Parameters (null values)
+ * JK_SERVER_ERROR JK_HTTP_SERVER_ERROR TRUE Error during initializing empty request, response or post body objects
+ * JK_CLIENT_ERROR JK_HTTP_REQUEST_TOO_LARGE JK_TRUE Request doesn't fit into buffer (error during ajp_marshal_into_msgb())
+ * JK_CLIENT_ERROR JK_HTTP_BAD_REQUEST JK_FALSE Error during reading parts of POST body from client
+ * JK_FATAL_ERROR JK_HTTP_SERVER_ERROR JK_FALSE If ajp_send_request() returns JK_FATAL_ERROR and !op->recoverable.
+ * JK_FATAL_ERROR JK_HTTP_SERVER_ERROR JK_FALSE If ajp_send_request() returns JK_TRUE but !op->recoverable.
+ * This should never happen.
+ * JK_CLIENT_ERROR JK_HTTP_BAD_REQUEST ? ajp_get_reply() returns JK_CLIENT_RD_ERROR
+ * JK_CLIENT_ERROR JK_HTTP_OK ? ajp_get_reply() returns JK_CLIENT_WR_ERROR
+ * JK_FATAL_ERROR JK_HTTP_SERVER_ERROR JK_TRUE ajp_get_reply() returns JK_FATAL_ERROR
+ * JK_FATAL_ERROR: protocol error or internal error
+ * JK_FATAL_ERROR JK_HTTP_SERVER_ERROR JK_FALSE ajp_get_reply() returns JK_FATAL_ERROR
+ * JK_FATAL_ERROR: protocol error or internal error
+ * JK_STATUS_ERROR JK_HTTP_SERVER_BUSY JK_TRUE ajp_get_reply() returns JK_STATUS_ERROR
+ * Only if op->recoverable and no more ajp13/ajp14 direct retries
+ * JK_STATUS_ERROR JK_HTTP_SERVER_BUSY JK_FALSE ajp_get_reply() returns JK_STATUS_ERROR
+ * Only if !op->recoverable
+ * JK_STATUS_FATAL_ERROR JK_HTTP_SERVER_BUSY JK_TRUE ajp_get_reply() returns JK_STATUS_ERROR
+ * Only if op->recoverable and no more ajp13/ajp14 direct retries
+ * JK_STATUS_FATAL_ERROR JK_HTTP_SERVER_BUSY JK_FALSE ajp_get_reply() returns JK_STATUS_FATAL_ERROR
+ * Only if !op->recoverable
+ * JK_REPLY_TIMEOUT JK_HTTP_GATEWAY_TIME_OUT JK_TRUE ajp_get_reply() returns JK_REPLY_TIMEOUT
+ * JK_AJP_PROTOCOL_ERROR JK_HTTP_GATEWAY_TIME_OUT ? ajp_get_reply() returns JK_AJP_PROTOCOL_ERROR
+ * ??? JK_FATAL_ERROR JK_HTTP_GATEWAY_TIME_OUT JK_FALSE ajp_get_reply() returns something else
+ * Only if !op->recoverable
+ * ??? JK_FALSE JK_HTTP_SERVER_BUSY JK_TRUE ajp_get_reply() returns JK_FALSE
+ * Only if op->recoverable and no more ajp13/ajp14 direct retries
+ * JK_TRUE JK_HTTP_OK ? OK
+ */
+static int JK_METHOD ajp_service(jk_endpoint_t *e,
+ jk_ws_service_t *s,
+ jk_logger_t *l, int *is_error)
+{
+ int i;
+ int err = JK_TRUE;
+ ajp_operation_t oper;
+ ajp_operation_t *op = &oper;
+ ajp_endpoint_t *p;
+ ajp_worker_t *aw;
+ int log_error;
+ int rc = JK_UNSET;
+ char *msg = "";
+ int retry_interval;
+
+ JK_TRACE_ENTER(l);
+
+ if (!e || !e->endpoint_private || !s || !is_error) {
+ JK_LOG_NULL_PARAMS(l);
+ if (is_error)
+ *is_error = JK_HTTP_SERVER_ERROR;
+ JK_TRACE_EXIT(l);
+ return JK_FALSE;
+ }
+
+ p = e->endpoint_private;
+ aw = p->worker;
+
+ if (aw->sequence != aw->s->h.sequence)
+ jk_ajp_pull(aw, JK_FALSE, l);
+
+ aw->s->used++;
+
+ /* Set returned error to SERVER ERROR */
+ *is_error = JK_HTTP_SERVER_ERROR;
+
+ op->request = jk_b_new(&(p->pool));
+ if (!op->request) {
+ jk_log(l, JK_LOG_ERROR,
+ "Failed allocating AJP message");
+ JK_TRACE_EXIT(l);
+ return JK_SERVER_ERROR;
+ }
+ if (jk_b_set_buffer_size(op->request, aw->max_packet_size)) {
+ jk_log(l, JK_LOG_ERROR,
+ "Failed allocating AJP message buffer");
+ JK_TRACE_EXIT(l);
+ return JK_SERVER_ERROR;
+ }
+ jk_b_reset(op->request);
+
+ op->reply = jk_b_new(&(p->pool));
+ if (!op->reply) {
+ jk_log(l, JK_LOG_ERROR,
+ "Failed allocating AJP message");
+ JK_TRACE_EXIT(l);
+ return JK_SERVER_ERROR;
+ }
+ if (jk_b_set_buffer_size(op->reply, aw->max_packet_size)) {
+ jk_log(l, JK_LOG_ERROR,
+ "Failed allocating AJP message buffer");
+ JK_TRACE_EXIT(l);
+ return JK_SERVER_ERROR;
+ }
+
+ op->post = jk_b_new(&(p->pool));
+ if (!op->post) {
+ jk_log(l, JK_LOG_ERROR,
+ "Failed allocating AJP message");
+ JK_TRACE_EXIT(l);
+ return JK_SERVER_ERROR;
+ }
+ if (jk_b_set_buffer_size(op->post, aw->max_packet_size)) {
+ jk_log(l, JK_LOG_ERROR,
+ "Failed allocating AJP message buffer");
+ JK_TRACE_EXIT(l);
+ return JK_SERVER_ERROR;
+ }
+ jk_b_reset(op->post);
+
+ /* Set returned error to OK */
+ *is_error = JK_HTTP_OK;
+
+ op->recoverable = JK_TRUE;
+ op->uploadfd = -1; /* not yet used, later ;) */
+
+ p->left_bytes_to_send = s->content_length;
+ p->reuse = JK_FALSE;
+ p->hard_close = JK_FALSE;
+
+ s->secret = aw->secret;
+
+ /*
+ * We get here initial request (in op->request)
+ */
+ if (!ajp_marshal_into_msgb(op->request, s, l, p)) {
+ *is_error = JK_HTTP_REQUEST_TOO_LARGE;
+ jk_log(l, JK_LOG_INFO,
+ "Creating AJP message failed, "
+ "without recovery");
+ aw->s->client_errors++;
+ 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",
+ aw->name, aw->retries);
+ }
+ aw->s->busy++;
+ if (aw->s->state == JK_AJP_STATE_ERROR)
+ aw->s->state = JK_AJP_STATE_PROBE;
+ if (aw->s->busy > aw->s->max_busy)
+ aw->s->max_busy = aw->s->busy;
+ retry_interval = p->worker->retry_interval;
+ for (i = 0; i < aw->retries; i++) {
+ /* Reset reply message buffer for each retry */
+ jk_b_reset(op->reply);
+
+ /*
+ * ajp_send_request() already locally handles
+ * reconnecting and broken connection detection.
+ * So if we already failed in it, wait a bit before
+ * retrying the same backend.
+ */
+ if (i > 0 && retry_interval >= 0) {
+ if (JK_IS_DEBUG_LEVEL(l))
+ jk_log(l, JK_LOG_DEBUG,
+ "retry %d, sleeping for %d ms before retrying",
+ i, retry_interval);
+ jk_sleep(retry_interval);
+ /* Pull shared memory if something changed during sleep */
+ if (aw->sequence != aw->s->h.sequence)
+ jk_ajp_pull(aw, JK_FALSE, l);
+ }
+ /*
+ * We're using op->request which hold initial request
+ * if Tomcat is stopped or restarted, we will pass op->request
+ * to next valid tomcat.
+ */
+ log_error = JK_TRUE;
+ rc = JK_UNSET;
+ msg = "";
+ err = ajp_send_request(e, s, l, p, op);
+ e->recoverable = op->recoverable;
+ if (err == JK_CLIENT_RD_ERROR) {
+ *is_error = JK_HTTP_BAD_REQUEST;
+ msg = "because of client read error";
+ aw->s->client_errors++;
+ rc = JK_CLIENT_ERROR;
+ log_error = JK_FALSE;
+ e->recoverable = JK_FALSE;
+ /* Ajp message set reuse to TRUE in END_REQUEST message
+ * However due to client bad request if the recovery
+ * RECOVER_ABORT_IF_CLIENTERROR is set the physical connection
+ * will be closed and application in Tomcat can catch that
+ * generated exception, knowing the client aborted the
+ * connection. This AJP protocol limitation, where we
+ * should actually send some packet informing the backend
+ * that client broke the connection in a middle of
+ * request/response cycle.
+ */
+ if (aw->recovery_opts & RECOVER_ABORT_IF_CLIENTERROR) {
+ /* Mark the endpoint for shutdown */
+ p->reuse = JK_FALSE;
+ p->hard_close = JK_TRUE;
+ }
+ }
+ else if (err == JK_FATAL_ERROR) {
+ *is_error = JK_HTTP_SERVER_BUSY;
+ msg = "because of error during request sending";
+ rc = err;
+ if (!op->recoverable) {
+ *is_error = JK_HTTP_SERVER_ERROR;
+ msg = "because of protocol error during request sending";
+ }
+ }
+ else if (err == JK_TRUE && op->recoverable) {
+ /* Up to there we can recover */
+
+ err = ajp_get_reply(e, s, l, p, op);
+ e->recoverable = op->recoverable;
+ if (err == JK_TRUE) {
+ *is_error = JK_HTTP_OK;
+ /* Done with the request */
+ ajp_update_stats(e, aw, JK_TRUE, l);
+ JK_TRACE_EXIT(l);
+ return JK_TRUE;
+ }
+
+ if (err == JK_CLIENT_RD_ERROR) {
+ *is_error = JK_HTTP_BAD_REQUEST;
+ msg = "because of client read error";
+ aw->s->client_errors++;
+ rc = JK_CLIENT_ERROR;
+ log_error = JK_FALSE;
+ e->recoverable = JK_FALSE;
+ op->recoverable = JK_FALSE;
+ if (aw->recovery_opts & RECOVER_ABORT_IF_CLIENTERROR) {
+ /* Mark the endpoint for shutdown */
+ p->reuse = JK_FALSE;
+ p->hard_close = JK_TRUE;
+ }
+ }
+ else if (err == JK_CLIENT_WR_ERROR) {
+ /* XXX: Is this correct to log this as 200? */
+ *is_error = JK_HTTP_OK;
+ msg = "because of client write error";
+ aw->s->client_errors++;
+ rc = JK_CLIENT_ERROR;
+ log_error = JK_FALSE;
+ e->recoverable = JK_FALSE;
+ op->recoverable = JK_FALSE;
+ if (aw->recovery_opts & RECOVER_ABORT_IF_CLIENTERROR) {
+ /* Mark the endpoint for shutdown */
+ p->reuse = JK_FALSE;
+ p->hard_close = JK_TRUE;
+ }
+ }
+ else if (err == JK_FATAL_ERROR) {
+ *is_error = JK_HTTP_SERVER_ERROR;
+ msg = "because of server error";
+ rc = err;
+ }
+ else if (err == JK_REPLY_TIMEOUT) {
+ *is_error = JK_HTTP_GATEWAY_TIME_OUT;
+ msg = "because of reply timeout";
+ aw->s->reply_timeouts++;
+ rc = err;
+ }
+ else if (err == JK_STATUS_ERROR || err == JK_STATUS_FATAL_ERROR) {
+ *is_error = JK_HTTP_SERVER_BUSY;
+ msg = "because of response status";
+ rc = err;
+ }
+ else if (err == JK_AJP_PROTOCOL_ERROR) {
+ *is_error = JK_HTTP_BAD_GATEWAY;
+ msg = "because of protocol error";
+ rc = err;
+ }
+ /* This should only be the cases err == JK_FALSE */
+ else {
+ /* if we can't get reply, check if unrecoverable 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
+ */
+ *is_error = JK_HTTP_BAD_GATEWAY;
+ msg = "";
+ rc = JK_FALSE;
+ }
+ }
+ else {
+ /* XXX: this should never happen:
+ * ajp_send_request() never returns JK_TRUE if !op->recoverable.
+ * and all other return values have already been handled.
+ */
+ e->recoverable = JK_FALSE;
+ *is_error = JK_HTTP_SERVER_ERROR;
+ msg = "because of an unknown reason";
+ rc = JK_FATAL_ERROR;
+ jk_log(l, JK_LOG_ERROR,
+ "(%s) unexpected condition err=%d recoverable=%d",
+ aw->name, err, op->recoverable);
+ }
+ if (!op->recoverable && log_error == JK_TRUE) {
+ jk_log(l, JK_LOG_ERROR,
+ "(%s) sending request to tomcat failed (unrecoverable), "
+ "%s "
+ "(attempt=%d)",
+ aw->name, msg, i + 1);
+ }
+ else {
+ jk_log(l, JK_LOG_INFO,
+ "(%s) sending request to tomcat failed (%srecoverable), "
+ "%s "
+ "(attempt=%d)",
+ aw->name,
+ op->recoverable ? "" : "un",
+ msg, i + 1);
+ }
+ if (!op->recoverable) {
+ ajp_update_stats(e, aw, rc, l);
+ JK_TRACE_EXIT(l);
+ return rc;
+ }
+ /* Get another connection from the pool and try again.
+ * Note: All sockets are probably closed already.
+ */
+ ajp_next_connection(p, l);
+ }
+ /* Log the error only once per failed request. */
+ jk_log(l, JK_LOG_ERROR,
+ "(%s) connecting to tomcat failed.",
+ aw->name);
+
+ ajp_update_stats(e, aw, rc, l);
+ JK_TRACE_EXIT(l);
+ return rc;
+}
+
+/*
+ * 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);
+ if (!host) {
+ host = "undefined";
+ }
+ strncpy(p->host, jk_get_worker_host(props, p->name, host), JK_SHM_STR_SIZ);
+
+ if (JK_IS_DEBUG_LEVEL(l))
+ jk_log(l, JK_LOG_DEBUG,
+ "worker %s contact is '%s:%d'",
+ p->name, p->host, p->port);
+ /* Copy the contact to shm */
+ strncpy(p->s->host, p->host, JK_SHM_STR_SIZ);
+ p->s->port = p->port;
+ p->s->addr_sequence = p->addr_sequence = 0;
+ /* Resolve if port > 0.
+ */
+ if (p->port > 0) {
+ if (jk_resolve(p->host, p->port, &p->worker_inet_addr, we->pool, l)) {
+ JK_TRACE_EXIT(l);
+ return JK_TRUE;
+ }
+ jk_log(l, JK_LOG_ERROR,
+ "worker %s can't resolve tomcat address %s",
+ p->name, p->host);
+ p->s->port = p->port = 0;
+ if (JK_IS_DEBUG_LEVEL(l))
+ jk_log(l, JK_LOG_DEBUG,
+ "worker %s contact is disabled",
+ p->name);
+ JK_TRACE_EXIT(l);
+ return JK_TRUE;
+ }
+ else {
+ p->s->port = p->port = 0;
+ if (JK_IS_DEBUG_LEVEL(l))
+ jk_log(l, JK_LOG_DEBUG,
+ "worker %s contact is disabled",
+ p->name);
+ JK_TRACE_EXIT(l);
+ return JK_TRUE;
+ }
+ }
+ 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 and acquire timeout %d",
+ p->ep_cache_sz, p->ep_mincache_sz, p->cache_acquire_timeout);
+ 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]->hard_close = 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;
+ p->ep_cache[i]->last_op = JK_AJP13_END_RESPONSE;
+ p->ep_cache[i]->addr_sequence = 0;
+ }
+
+ 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->worker.we = we;
+ 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_connect_timeout =
+ jk_get_worker_socket_connect_timeout(props, p->name,
+ p->socket_timeout * 1000);
+
+ 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->ping_timeout =
+ jk_get_worker_ping_timeout(props, p->name,
+ AJP_DEF_PING_TIMEOUT);
+ p->ping_mode =
+ jk_get_worker_ping_mode(props, p->name,
+ AJP_CPING_NONE);
+
+ p->connect_timeout =
+ jk_get_worker_connect_timeout(props, p->name,
+ AJP_DEF_CONNECT_TIMEOUT);
+
+ p->prepost_timeout =
+ jk_get_worker_prepost_timeout(props, p->name,
+ AJP_DEF_PREPOST_TIMEOUT);
+
+ if ((p->ping_mode & AJP_CPING_CONNECT) &&
+ p->connect_timeout == AJP_DEF_CONNECT_TIMEOUT)
+ p->connect_timeout = p->ping_timeout;
+
+ if ((p->ping_mode & AJP_CPING_PREPOST) &&
+ p->prepost_timeout == AJP_DEF_PREPOST_TIMEOUT)
+ p->prepost_timeout = p->ping_timeout;
+
+ p->conn_ping_interval =
+ jk_get_worker_conn_ping_interval(props, p->name, 0);
+ if ((p->ping_mode & AJP_CPING_INTERVAL) &&
+ p->conn_ping_interval == 0) {
+ /* XXX: Ping timeout is in miliseconds
+ * and ping_interval is in seconds.
+ * Use 10 times larger value for ping interval
+ * (ping_timeout / 1000) * 10
+ */
+ p->conn_ping_interval = p->ping_timeout / 100;
+ }
+ p->reply_timeout =
+ jk_get_worker_reply_timeout(props, p->name,
+ AJP_DEF_REPLY_TIMEOUT);
+
+ p->recovery_opts =
+ jk_get_worker_recovery_opts(props, p->name,
+ AJP_DEF_RECOVERY_OPTS);
+
+ p->retries =
+ jk_get_worker_retries(props, p->name,
+ JK_RETRIES);
+
+ p->max_packet_size =
+ jk_get_max_packet_size(props, p->name);
+
+ p->socket_buf =
+ jk_get_worker_socket_buffer(props, p->name, p->max_packet_size);
+
+ p->retry_interval =
+ jk_get_worker_retry_interval(props, p->name,
+ JK_SLEEP_DEF);
+ p->cache_acquire_timeout = jk_get_worker_cache_acquire_timeout(props,
+ p->name, p->retries * p->retry_interval);
+ p->http_status_fail_num = jk_get_worker_fail_on_status(props, p->name,
+ &p->http_status_fail[0],
+ JK_MAX_HTTP_STATUS_FAILS);
+
+ if (p->retries < 1) {
+ jk_log(l, JK_LOG_INFO,
+ "number of retries must be greater then 1. Setting to default=%d",
+ JK_RETRIES);
+ p->retries = JK_RETRIES;
+ }
+
+ p->maintain_time = jk_get_worker_maintain_time(props);
+ if(p->maintain_time < 0)
+ p->maintain_time = 0;
+ p->s->last_maintain_time = time(NULL);
+ p->s->last_reset = p->s->last_maintain_time;
+
+ 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,
+ "socket timeout: %d",
+ p->socket_timeout);
+
+ jk_log(l, JK_LOG_DEBUG,
+ "socket connect timeout: %d",
+ p->socket_connect_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,
+ "ping timeout: %d",
+ p->ping_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",
+ p->retries);
+
+ jk_log(l, JK_LOG_DEBUG,
+ "max packet size: %d",
+ p->max_packet_size);
+
+ jk_log(l, JK_LOG_DEBUG,
+ "retry interval: %d",
+ p->retry_interval);
+ }
+ /*
+ * 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 JK_METHOD ajp_worker_factory(jk_worker_t **w,
+ const char *name, jk_logger_t *l)
+{
+ ajp_worker_t *aw;
+
+ JK_TRACE_ENTER(l);
+ if (name == NULL || w == NULL) {
+ JK_LOG_NULL_PARAMS(l);
+ JK_TRACE_EXIT(l);
+ return JK_FALSE;
+ }
+
+ aw = (ajp_worker_t *) calloc(1, sizeof(ajp_worker_t));
+ if (!aw) {
+ jk_log(l, JK_LOG_ERROR,
+ "malloc of private_data failed");
+ JK_TRACE_EXIT(l);
+ return JK_FALSE;
+ }
+
+ jk_open_pool(&aw->p,
+ aw->buf,
+ sizeof(jk_pool_atom_t) * TINY_POOL_SIZE);
+
+ strncpy(aw->name, name, JK_SHM_STR_SIZ);
+ aw->login = NULL;
+
+ aw->ep_cache_sz = 0;
+ aw->ep_cache = NULL;
+ aw->connect_retry_attempts = AJP_DEF_RETRY_ATTEMPTS;
+ aw->worker.worker_private = aw;
+
+ aw->worker.maintain = ajp_maintain;
+
+ aw->logon = NULL;
+
+ *w = &aw->worker;
+
+ aw->s = jk_shm_alloc_ajp_worker(&aw->p);
+ if (!aw->s) {
+ jk_close_pool(&aw->p);
+ free(aw);
+ jk_log(l, JK_LOG_ERROR,
+ "allocating ajp worker record from shared memory");
+ JK_TRACE_EXIT(l);
+ return JK_FALSE;
+ }
+ JK_TRACE_EXIT(l);
+ return JK_TRUE;
+}
+
+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;
+ }
+
+ jk_close_pool(&aw->p);
+ 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;
+
+ /* set last_access only if needed */
+ if (w->cache_timeout > 0)
+ p->last_access = time(NULL);
+ if (w->s->addr_sequence != p->addr_sequence) {
+ p->reuse = JK_FALSE;
+ p->addr_sequence = w->s->addr_sequence;
+ }
+ ajp_reset_endpoint(p, l);
+ *e = NULL;
+ JK_ENTER_CS(&w->cs, rc);
+ if (rc) {
+ int i;
+
+ for (i = w->ep_cache_sz - 1; i >= 0; i--) {
+ if (w->ep_cache[i] == NULL) {
+ w->ep_cache[i] = p;
+ break;
+ }
+ }
+ JK_LEAVE_CS(&w->cs, rc);
+
+ 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;
+ }
+ /* 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;
+ int rc;
+ int retry = 0;
+
+ *je = NULL;
+ /* Loop until cache_acquire_timeout interval elapses */
+ while ((retry * JK_SLEEP_DEF) < aw->cache_acquire_timeout) {
+
+ JK_ENTER_CS(&aw->cs, rc);
+ if (rc) {
+ unsigned int slot;
+ /* Try to find connected socket cache entry */
+ for (slot = 0; slot < aw->ep_cache_sz; slot++) {
+ if (aw->ep_cache[slot] &&
+ IS_VALID_SOCKET(aw->ep_cache[slot]->sd)) {
+ ae = aw->ep_cache[slot];
+ if (ae->reuse) {
+ aw->ep_cache[slot] = NULL;
+ break;
+ }
+ else {
+ /* XXX: We shouldn't have non reusable
+ * opened socket in the cache
+ */
+ ajp_reset_endpoint(ae, l);
+ ae = NULL;
+ jk_log(l, JK_LOG_WARNING,
+ "closing non reusable pool slot=%d", slot);
+ }
+ }
+ }
+ if (!ae) {
+ /* No connected cache entry found.
+ * Use the first free one.
+ */
+ 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;
+ }
+ }
+ }
+ JK_LEAVE_CS(&aw->cs, rc);
+ if (ae) {
+ if (aw->cache_timeout > 0)
+ ae->last_access = time(NULL);
+ *je = &ae->endpoint;
+ if (JK_IS_DEBUG_LEVEL(l))
+ jk_log(l, JK_LOG_DEBUG,
+ "acquired connection pool slot=%u after %d retries",
+ slot, retry);
+ JK_TRACE_EXIT(l);
+ return JK_TRUE;
+ }
+ else {
+ retry++;
+ if (JK_IS_DEBUG_LEVEL(l))
+ jk_log(l, JK_LOG_DEBUG,
+ "could not get free endpoint for worker %s"
+ " (retry %d, sleeping for %d ms)",
+ aw->name, retry, JK_SLEEP_DEF);
+ jk_sleep(JK_SLEEP_DEF);
+ }
+ }
+ else {
+ jk_log(l, JK_LOG_ERROR,
+ "locking thread (errno=%d)", errno);
+ JK_TRACE_EXIT(l);
+ return JK_FALSE;
+
+ }
+ }
+ jk_log(l, JK_LOG_WARNING,
+ "Unable to get the free endpoint for worker %s from %u slots",
+ aw->name, aw->ep_cache_sz);
+ }
+ else {
+ JK_LOG_NULL_PARAMS(l);
+ }
+
+ JK_TRACE_EXIT(l);
+ return JK_FALSE;
+}
+
+int JK_METHOD ajp_maintain(jk_worker_t *pThis, time_t mstarted, jk_logger_t *l)
+{
+ JK_TRACE_ENTER(l);
+
+ if (pThis && pThis->worker_private) {
+ ajp_worker_t *aw = pThis->worker_private;
+ time_t now = mstarted;
+ int rc;
+ long delta;
+
+ jk_shm_lock();
+
+ /* Now we check for global maintenance (once for all processes).
+ * Checking workers for idleness.
+ * Therefore we globally sync and we use a global timestamp.
+ * Since it's possible that we come here a few milliseconds
+ * before the interval has passed, we allow a little tolerance.
+ */
+ delta = (long)difftime(mstarted, aw->s->last_maintain_time) + JK_AJP_MAINTAIN_TOLERANCE;
+ if (delta >= aw->maintain_time) {
+ aw->s->last_maintain_time = mstarted;
+ if (aw->s->state == JK_AJP_STATE_OK &&
+ aw->s->used == aw->s->used_snapshot)
+ aw->s->state = JK_AJP_STATE_IDLE;
+ aw->s->used_snapshot = aw->s->used;
+ }
+
+ jk_shm_unlock();
+
+ /* Do connection pool maintenance only if timeouts or keepalives are set */
+ if (aw->cache_timeout <= 0 &&
+ aw->conn_ping_interval <= 0) {
+ /* Nothing to do. */
+ JK_TRACE_EXIT(l);
+ return JK_TRUE;
+ }
+
+ JK_ENTER_CS(&aw->cs, rc);
+ if (rc) {
+ unsigned int n = 0, k = 0, cnt = 0;
+ int i;
+ unsigned int m, m_count = 0;
+ jk_sock_t *m_sock;
+ /* Count open slots */
+ for (i = (int)aw->ep_cache_sz - 1; i >= 0; i--) {
+ if (aw->ep_cache[i] && IS_VALID_SOCKET(aw->ep_cache[i]->sd))
+ cnt++;
+ }
+ m_sock = (jk_sock_t *)malloc((cnt + 1) * sizeof(jk_sock_t));
+ /* Handle worker cache timeouts */
+ if (aw->cache_timeout > 0) {
+ for (i = (int)aw->ep_cache_sz - 1;
+ i >= 0; i--) {
+ /* Skip the closed sockets */
+ if (aw->ep_cache[i] && IS_VALID_SOCKET(aw->ep_cache[i]->sd)) {
+ int elapsed = (int)difftime(mstarted, 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;
+ m_sock[m_count++] = aw->ep_cache[i]->sd;
+ aw->ep_cache[i]->sd = JK_INVALID_SOCKET;
+ ajp_reset_endpoint(aw->ep_cache[i], l);
+ if (JK_IS_DEBUG_LEVEL(l))
+ jk_log(l, JK_LOG_DEBUG,
+ "cleaning pool slot=%d elapsed %d in %d",
+ i, elapsed, (int)(difftime(time(NULL), rt)));
+ }
+ }
+ if (cnt <= aw->ep_mincache_sz + n) {
+ 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;
+ }
+ }
+ }
+ /* Handle worker connection keepalive */
+ if (aw->conn_ping_interval > 0 && aw->ping_timeout > 0) {
+ for (i = (int)aw->ep_cache_sz - 1; i >= 0; 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->conn_ping_interval) {
+ k++;
+ /* handle cping/cpong.
+ */
+ if (ajp_handle_cping_cpong(aw->ep_cache[i],
+ aw->ping_timeout, l) == JK_FALSE) {
+ jk_log(l, JK_LOG_INFO,
+ "(%s) failed sending request, "
+ "socket %d keepalive cping/cpong "
+ "failure (errno=%d)",
+ aw->name,
+ aw->ep_cache[i]->sd,
+ aw->ep_cache[i]->last_errno);
+ aw->ep_cache[i]->reuse = JK_FALSE;
+ m_sock[m_count++] = aw->ep_cache[i]->sd;
+ aw->ep_cache[i]->sd = JK_INVALID_SOCKET;
+ ajp_reset_endpoint(aw->ep_cache[i], l);
+ }
+ else {
+ now = time(NULL);
+ aw->ep_cache[i]->last_access = now;
+ }
+ }
+ }
+ }
+ }
+ JK_LEAVE_CS(&aw->cs, rc);
+ /* Shutdown sockets outside of the lock.
+ * This has benefits only if maintain was
+ * called from the watchdog thread.
+ */
+ for (m = 0; m < m_count; m++) {
+ jk_shutdown_socket(m_sock[m], l);
+ }
+ free(m_sock);
+ if (n && 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), mstarted)),
+ aw->ep_cache_sz);
+ if (k && JK_IS_DEBUG_LEVEL(l))
+ jk_log(l, JK_LOG_DEBUG,
+ "pinged %u sockets in %d seconds from %u pool slots",
+ k, (int)(difftime(time(NULL), mstarted)),
+ aw->ep_cache_sz);
+ 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;
+}
+
+int ajp_has_endpoint(jk_worker_t *pThis,
+ jk_logger_t *l)
+{
+ JK_TRACE_ENTER(l);
+
+ if (pThis && pThis->worker_private) {
+ ajp_worker_t *aw = pThis->worker_private;
+ int rc;
+
+ JK_ENTER_CS(&aw->cs, rc);
+ if (rc) {
+ unsigned int slot;
+ /* Try to find connected socket cache entry */
+ for (slot = 0; slot < aw->ep_cache_sz; slot++) {
+ if (aw->ep_cache[slot]) {
+ JK_LEAVE_CS(&aw->cs, rc);
+ return JK_TRUE;
+ }
+ }
+ 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;
+ }
+ }
+ else {
+ JK_LOG_NULL_PARAMS(l);
+ }
+
+ JK_TRACE_EXIT(l);
+ return JK_FALSE;
+}