| /* |
| * 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 |
| |
| /* Macro for checking the availability of the cache slot |
| */ |
| #define IS_SLOT_AVAIL(s) ((s) != NULL && (s)->avail) |
| |
| 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 |
| }; |
| |
| static char ajp_cping_mode[] = { |
| AJP_CPING_CONNECT_TEXT, |
| AJP_CPING_PREPOST_TEXT, |
| AJP_CPING_INTERVAL_TEXT, |
| }; |
| |
| #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; |
| if (header[6] == '-') { |
| p += 6; |
| if (memcmp(p, "CHARSET", 8) == 0) |
| return SC_ACCEPT_CHARSET; |
| if (memcmp(p, "ENCODING", 9) == 0) |
| return SC_ACCEPT_ENCODING; |
| if (memcmp(p, "LANGUAGE", 9) == 0) |
| return SC_ACCEPT_LANGUAGE; |
| } |
| return UNKNOWN_METHOD; |
| } |
| if (memcmp(p, "UTHORIZATION", 13) == 0) |
| return SC_AUTHORIZATION; |
| break; |
| case 'C': |
| if(memcmp(p, "OOKIE2", 7) == 0) |
| return SC_COOKIE2; |
| if (memcmp(p, "OOKIE", 6) == 0) |
| return SC_COOKIE; |
| if(memcmp(p, "ONNECTION", 10) == 0) |
| return SC_CONNECTION; |
| if(memcmp(p, "ONTENT-TYPE", 12) == 0) |
| return SC_CONTENT_TYPE; |
| if(memcmp(p, "ONTENT-LENGTH", 14) == 0) |
| return SC_CONTENT_LENGTH; |
| break; |
| case 'H': |
| if(memcmp(p, "OST", 4) == 0) |
| return SC_HOST; |
| break; |
| case 'P': |
| if(memcmp(p, "RAGMA", 6) == 0) |
| return SC_PRAGMA; |
| break; |
| case 'R': |
| if(memcmp(p, "EFERER", 7) == 0) |
| return SC_REFERER; |
| break; |
| case 'U': |
| if(memcmp(p, "SER-AGENT", 10) == 0) |
| return SC_USER_AGENT; |
| break; |
| default: |
| break;; |
| } |
| return UNKNOWN_METHOD; |
| } |
| |
| /* 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; |
| if (*v == 'i' || *v == 'I' || *v == 'n' || *v == 'N' || *v == '0') |
| return JK_AJP_STATE_IDLE; |
| if (*v == 'o' || *v == 'O' || *v == '1') |
| return JK_AJP_STATE_OK; |
| if (*v == 'e' || *v == 'E' || *v == '4') |
| return JK_AJP_STATE_ERROR; |
| if (*v == 'p' || *v == 'P' || *v == '6') |
| return JK_AJP_STATE_PROBE; |
| return JK_AJP_STATE_DEF; |
| } |
| |
| void jk_ajp_get_cping_text(int mode, char *buf) |
| { |
| int bit = 1; |
| int log2 = 0; |
| int pos = 0; |
| while (bit <= mode && bit <= AJP_CPING_MAX) { |
| if (mode & bit) { |
| buf[pos] = ajp_cping_mode[log2]; |
| pos +=1; |
| } |
| bit *= 2; |
| log2 += 1; |
| } |
| buf[pos] = '\0'; |
| } |
| |
| int jk_ajp_get_cping_mode(const char *m, int def) |
| { |
| int mv = 0; |
| if (!m) |
| return def; |
| while (*m != '\0') { |
| if (*m == AJP_CPING_CONNECT_TEXT || *m == tolower(AJP_CPING_CONNECT_TEXT)) |
| mv |= AJP_CPING_CONNECT; |
| if (*m == AJP_CPING_PREPOST_TEXT || *m == tolower(AJP_CPING_PREPOST_TEXT)) |
| mv |= AJP_CPING_PREPOST; |
| if (*m == AJP_CPING_INTERVAL_TEXT || *m == tolower(AJP_CPING_INTERVAL_TEXT)) |
| mv |= AJP_CPING_INTERVAL; |
| if (*m == AJP_CPING_ALL_TEXT || *m == tolower(AJP_CPING_ALL_TEXT)) { |
| mv = AJP_CPING_CONNECT | AJP_CPING_PREPOST | AJP_CPING_INTERVAL; |
| break; |
| } |
| m++; |
| } |
| if (mv) |
| return mv; |
| return def; |
| } |
| |
| /* |
| * 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, |
| "(%s) failed appending the message begining", ae->worker->name); |
| 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, |
| "(%s) failed appending the header code for '%s'", |
| ae->worker->name, s->headers_names[i]); |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |
| } |
| else { |
| if (jk_b_append_string(msg, s->headers_names[i])) { |
| jk_log(l, JK_LOG_ERROR, |
| "(%s) failed appending the header name '%s'", |
| ae->worker->name, s->headers_names[i]); |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |
| } |
| |
| if (jk_b_append_string(msg, s->headers_values[i])) { |
| jk_log(l, JK_LOG_ERROR, |
| "(%s) failed appending the header value for header '%s' of length %u", |
| ae->worker->name, s->headers_names[i], strlen(s->headers_names[i])); |
| 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, |
| "(%s) failed appending secret", ae->worker->name); |
| 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, |
| "(%s) failed appending the remote user", ae->worker->name); |
| 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, |
| "(%s) failed appending the auth type", ae->worker->name); |
| 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, |
| "(%s) failed appending the query string of length %u", |
| ae->worker->name, strlen(s->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, |
| "(%s) failed appending the route", ae->worker->name); |
| 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, |
| "(%s) failed appending the SSL certificates", ae->worker->name); |
| 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, |
| "(%s) failed appending the SSL ciphers", ae->worker->name); |
| 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, |
| "(%s) failed appending the SSL session", ae->worker->name); |
| 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, |
| "(%s) failed appending the SSL key size of length %d", |
| ae->worker->name, s->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, "(%s) unknown method %s", |
| ae->worker->name, 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, |
| "(%s) failed appending the request method", ae->worker->name); |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |
| } |
| |
| /* Forward the SSL protocol name. |
| * Modern Tomcat versions know how to retrieve |
| * the protocol name from this attribute. |
| */ |
| if (s->ssl_protocol && *s->ssl_protocol) { |
| if (jk_b_append_byte(msg, SC_A_REQ_ATTRIBUTE) || |
| jk_b_append_string(msg, SC_A_SSL_PROTOCOL) || |
| jk_b_append_string(msg, s->ssl_protocol)) { |
| jk_log(l, JK_LOG_ERROR, |
| "(%s) failed appending the ssl protocol name %s", |
| ae->worker->name, s->ssl_protocol); |
| 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, |
| "(%s) failed appending the remote port %s", |
| ae->worker->name, s->remote_port); |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |
| /* Forward the local ip address information, which was forgotten |
| * from the builtin data of the AJP 13 protocol. |
| * Since the servlet spec allows to retrieve it via getLocalAddr(), |
| * we provide the address to the Tomcat connector as a request |
| * attribute. Modern Tomcat versions know how to retrieve |
| * the local address from this attribute. |
| */ |
| if (jk_b_append_byte(msg, SC_A_REQ_ATTRIBUTE) || |
| jk_b_append_string(msg, SC_A_REQ_LOCAL_ADDR) || |
| jk_b_append_string(msg, s->local_addr)) { |
| jk_log(l, JK_LOG_ERROR, |
| "(%s) failed appending the local address %s", |
| ae->worker->name, s->local_addr); |
| 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, |
| "(%s) failed appending the activation state %s", |
| ae->worker->name, 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, |
| "(%s) failed appending attribute %s=%s", |
| ae->worker->name, 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, |
| "(%s) failed appending the message end", ae->worker->name); |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |
| |
| if (JK_IS_DEBUG_LEVEL(l)) |
| jk_log(l, JK_LOG_DEBUG, "(%s) ajp marshaling done", ae->worker->name); |
| 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, |
| "(%s) NULL status", ae->worker->name); |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |
| |
| d->msg = 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, |
| "(%s) status = %d", ae->worker->name, 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, |
| "(%s) No such sc (%d)", ae->worker->name, name); |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |
| } |
| else { |
| d->header_names[i] = jk_b_get_string(msg); |
| if (!d->header_names[i]) { |
| jk_log(l, JK_LOG_ERROR, |
| "(%s) NULL header name", ae->worker->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] = jk_b_get_string(msg); |
| if (!d->header_values[i]) { |
| jk_log(l, JK_LOG_ERROR, |
| "(%s) NULL header value", ae->worker->name); |
| 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, |
| "(%s) Header[%d] [%s] = [%s]", |
| ae->worker->name, 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 (JK_IS_DEBUG_LEVEL(l)) |
| jk_log(l, JK_LOG_DEBUG, |
| "(%s) aborting endpoint with socket %d", |
| ae->worker->name, ae->sd); |
| if (IS_VALID_SOCKET(ae->sd)) { |
| if (shutdown == JK_TRUE) { |
| 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); |
| } |
| } |
| JK_ATOMIC_DECREMENT(&(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); |
| JK_ATOMIC_DECREMENT(&(ae->worker->s->connected)); |
| 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) |
| { |
| unsigned int i; |
| 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); |
| JK_ATOMIC_DECREMENT(&(ae->worker->s->connected)); |
| ae->sd = JK_INVALID_SOCKET; |
| } |
| JK_ENTER_CS(&aw->cs); |
| for (i = 0; i < aw->ep_cache_sz; i++) { |
| /* Find cache slot with usable socket |
| */ |
| if (IS_SLOT_AVAIL(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); |
| 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, |
| "(%s) Failed allocating AJP message", |
| ae->worker->name); |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |
| if (jk_b_set_buffer_size(msg, 16)) { |
| jk_log(l, JK_LOG_ERROR, |
| "(%s) Failed allocating AJP message buffer", |
| ae->worker->name); |
| 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, |
| "(%s) can't send cping query", |
| ae->worker->name); |
| 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, "(%s) timeout in reply cpong after %d ms. " |
| "Socket = %d (event=%d)", |
| ae->worker->name, timeout, ae->sd, errno); |
| /* 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, |
| "(%s) awaited reply cpong, not received", |
| ae->worker->name); |
| 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, |
| "(%s) awaited reply cpong, received %d instead. " |
| "Closing connection", |
| ae->worker->name, 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, |
| "(%s) awaited reply cpong, received %d instead. " |
| "Retrying next packet", |
| ae->worker->name, 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[64]; |
| int rc = JK_TRUE; |
| int connected; |
| |
| JK_TRACE_ENTER(l); |
| |
| ae->last_errno = 0; |
| ae->sd = jk_open_socket(&ae->worker->worker_inet_addr, |
| ae->worker->worker_source_inet_addr.ipaddr_ptr != NULL ? |
| &ae->worker->worker_source_inet_addr : |
| NULL, |
| 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, |
| "(%s) Failed opening socket to (%s) (errno=%d)", |
| ae->worker->name, jk_dump_hinfo(&ae->worker->worker_inet_addr, buf, sizeof(buf)), |
| ae->last_errno); |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |
| connected = JK_ATOMIC_INCREMENT(&(ae->worker->s->connected)); |
| /* Update maximum number of connections |
| */ |
| if (connected > ae->worker->s->max_connected) |
| ae->worker->s->max_connected = 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 |
| * 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]; |
| jk_sockaddr_t 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 (%d->%d) [%d->%d]", |
| 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->busy_limit = aw->s->busy_limit; |
| 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 && port != 0) { |
| aw->port = port; |
| strncpy(aw->host, host, JK_SHM_STR_SIZ); |
| if (!jk_resolve(host, port, &inet_addr, |
| aw->worker.we->pool, aw->prefer_ipv6, l)) { |
| jk_log(l, JK_LOG_ERROR, |
| "Failed resolving address '%s:%d' for worker '%s'.", |
| host, port, aw->name); |
| /* Disable contact */ |
| aw->port = 0; |
| } |
| else { |
| unsigned int i; |
| JK_ENTER_CS(&aw->cs); |
| for (i = 0; i < aw->ep_cache_sz; i++) { |
| /* Close all avail connections in the cache |
| * Note that this won't change active connections. |
| */ |
| if (IS_SLOT_AVAIL(aw->ep_cache[i]) && IS_VALID_SOCKET(aw->ep_cache[i]->sd)) { |
| jk_sock_t 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); |
| JK_ATOMIC_DECREMENT(&(aw->s->connected)); |
| } |
| } |
| jk_clone_sockaddr(&(aw->worker_inet_addr), &inet_addr); |
| JK_LEAVE_CS(&aw->cs); |
| } |
| } |
| |
| 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 (%d->%d) [%d->%d]", |
| 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->busy_limit = aw->busy_limit; |
| aw->s->max_packet_size = aw->max_packet_size; |
| /* Force squence update on push |
| */ |
| ++aw->s->h.sequence; |
| aw->sequence = aw->s->h.sequence; |
| if (aw->s->addr_sequence != aw->addr_sequence) { |
| ++aw->s->addr_sequence; |
| address_change = JK_TRUE; |
| strncpy(aw->s->host, aw->host, JK_SHM_STR_SIZ); |
| aw->s->port = aw->port; |
| aw->addr_sequence = aw->s->addr_sequence; |
| } |
| if (locked == JK_FALSE) |
| jk_shm_unlock(); |
| |
| if (address_change == JK_TRUE) { |
| unsigned int i; |
| |
| JK_ENTER_CS(&aw->cs); |
| for (i = 0; i < aw->ep_cache_sz; i++) { |
| /* Close all connections in the cache |
| */ |
| if (IS_SLOT_AVAIL(aw->ep_cache[i]) && IS_VALID_SOCKET(aw->ep_cache[i]->sd)) { |
| jk_sock_t 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); |
| JK_ATOMIC_DECREMENT(&(aw->s->connected)); |
| } |
| } |
| JK_LEAVE_CS(&aw->cs); |
| } |
| 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, |
| "(%s) sendfull for socket %d returned %d (errno=%d)", |
| ae->worker->name, 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[64]; |
| |
| 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, sizeof(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, sizeof(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, |
| "(%s) received AJP14 reply on an AJP13 connection from %s", |
| ae->worker->name, jk_dump_hinfo(&ae->worker->worker_inet_addr, buf, sizeof(buf))); |
| } |
| else { |
| jk_log(l, JK_LOG_ERROR, |
| "(%s) wrong message format 0x%04x from %s", |
| ae->worker->name, header, jk_dump_hinfo(&ae->worker->worker_inet_addr, |
| buf, sizeof(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, |
| "(%s) received AJP13 reply on an AJP14 connection from %s", |
| ae->worker->name, jk_dump_hinfo(&ae->worker->worker_inet_addr, buf, sizeof(buf))); |
| } |
| else { |
| jk_log(l, JK_LOG_ERROR, |
| "(%s) wrong message format 0x%04x from %s", |
| ae->worker->name, header, jk_dump_hinfo(&ae->worker->worker_inet_addr, |
| buf, sizeof(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, |
| "(%s) wrong message size %d %d from %s", |
| ae->worker->name, msglen, msg->maxlen, |
| jk_dump_hinfo(&ae->worker->worker_inet_addr, buf, sizeof(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, sizeof(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, sizeof(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; |
| int maxlen; |
| |
| 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 */ |
| maxlen = ae->worker->max_packet_size - AJP_HEADER_LEN - AJP_HEADER_SZ_LEN; |
| |
| /* Pick the max size since we don't know the content_length |
| */ |
| if (r->is_chunked && ae->left_bytes_to_send == 0) { |
| len = maxlen; |
| } else { |
| if ((jk_uint64_t)maxlen > ae->left_bytes_to_send) { |
| maxlen = (int)ae->left_bytes_to_send; |
| } |
| if (len < 0 || len > maxlen) { |
| len = maxlen; |
| } |
| } |
| |
| 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, |
| "(%s) Failed appending message length", ae->worker->name); |
| 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 or all connections are in use |
| */ |
| if (err_conn + err_cping + err_send > 0) |
| if (err_cping + err_send > 0) |
| jk_log(l, JK_LOG_INFO, |
| "(%s) no usable connection found, will create a new one, " |
| "detected by connect check (%d), cping (%d), send (%d).", |
| ae->worker->name, err_conn, err_cping, err_send); |
| else |
| jk_log(l, JK_LOG_DEBUG, |
| "(%s) no usable connection found, will create a new one, " |
| "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) no usable connection found, will create a new one.", |
| 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->post->len > AJP_HEADER_LEN ? op->post->len - AJP_HEADER_LEN : 0); |
| |
| /* |
| * 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, "(%s) Resent the request body (%d)", |
| ae->worker->name, 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, |
| "(%s) Resent the request body (lb mode) (%d)", |
| ae->worker->name, 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; |
| if ((len = ajp_read_into_msg_buff(ae, s, op->post, -1, 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, |
| "(%s) Already received AJP13_SEND HEADERS", ae->worker->name); |
| } |
| JK_TRACE_EXIT(l); |
| return JK_AJP13_ERROR; |
| } |
| if (!ajp_unmarshal_response(msg, &res, ae, l)) { |
| jk_log(l, JK_LOG_ERROR, |
| "(%s) ajp_unmarshal_response failed", ae->worker->name); |
| JK_TRACE_EXIT(l); |
| return JK_AJP13_ERROR; |
| } |
| if (r->num_resp_headers > 0) { |
| char **old_names = res.header_names; |
| char **old_values = res.header_values; |
| if (JK_IS_DEBUG_LEVEL(l)) |
| jk_log(l, JK_LOG_DEBUG, "(%s) Adding %d response headers to %d " |
| "headers received from tomcat", |
| ae->worker->name, r->num_resp_headers, res.num_headers); |
| res.header_names = jk_pool_alloc(r->pool, |
| (r->num_resp_headers + res.num_headers) * |
| sizeof(char *)); |
| res.header_values = jk_pool_alloc(r->pool, |
| (r->num_resp_headers + res.num_headers) * |
| sizeof(char *)); |
| if (!res.header_names || !res.header_values) { |
| jk_log(l, JK_LOG_ERROR, |
| "(%s) Failed allocating one %d response headers.", |
| ae->worker->name, r->num_resp_headers + res.num_headers); |
| res.header_names = old_names; |
| res.header_values = old_values; |
| } else { |
| if (res.num_headers) { |
| memcpy(res.header_names, old_names, res.num_headers * sizeof(char *)); |
| memcpy(res.header_values, old_values, res.num_headers * sizeof(char *)); |
| } |
| if (r->num_resp_headers) { |
| memcpy(res.header_names + res.num_headers, r->resp_headers_names, |
| r->num_resp_headers * sizeof(char *)); |
| memcpy(res.header_values + res.num_headers, r->resp_headers_values, |
| r->num_resp_headers * sizeof(char *)); |
| } |
| res.num_headers = res.num_headers + r->num_resp_headers; |
| } |
| } |
| 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; |
| } |
| 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, |
| "(%s) Ignoring flush message received while sending the request", |
| ae->worker->name); |
| 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, |
| "(%s) Unexpected AJP13_SEND_BODY_CHUNK", |
| ae->worker->name); |
| } |
| 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, |
| "(%s) Chunk length too large. Length of AJP message is %d," |
| " chunk length is %d.", ae->worker->name, 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, |
| "(%s) Ignoring flush message received before headers", |
| ae->worker->name); |
| } |
| } |
| else { |
| if (!r->write(r, msg->buf + msg->pos, len)) { |
| jk_log(l, JK_LOG_INFO, |
| "(%s) Writing to client aborted or client network problems", |
| ae->worker->name); |
| 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; |
| } |
| |
| /* 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, |
| "(%s) Reading from client aborted or client network problems", |
| ae->worker->name); |
| |
| 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, "(%s) AJP13 protocol: Reuse is set to false", |
| ae->worker->name); |
| } |
| else if (r->disable_reuse) { |
| if (JK_IS_DEBUG_LEVEL(l)) { |
| jk_log(l, JK_LOG_DEBUG, "(%s) AJP13 protocol: Reuse is disabled", |
| ae->worker->name); |
| } |
| ae->reuse = JK_FALSE; |
| } |
| else { |
| /* Reuse in all cases |
| */ |
| if (JK_IS_DEBUG_LEVEL(l)) { |
| jk_log(l, JK_LOG_DEBUG, "(%s) AJP13 protocol: Reuse is OK", |
| ae->worker->name); |
| } |
| 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, |
| "(%s) Unknown AJP protocol code: %02X", ae->worker->name, 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; |
| } |
| if (JK_AJP13_SEND_HEADERS == rc) { |
| if (headeratclient == JK_FALSE) { |
| headeratclient = JK_TRUE; |
| continue; |
| } |
| 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; |
| } |
| } |
| 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; |
| } |
| 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 != JK_TRUE) { |
| jk_log(l, JK_LOG_ERROR, |
| "(%s) Tomcat is down or network problems", |
| p->worker->name); |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |
| } |
| 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; |
| } |
| 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; |
| } |
| 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; |
| } |
| if (JK_INTERNAL_ERROR == rc) { |
| /* |
| * Internal error, like memory allocation or invalid packet lengths. |
| */ |
| JK_TRACE_EXIT(l); |
| return JK_FATAL_ERROR; |
| } |
| if (JK_AJP13_NO_RESPONSE == rc) { |
| /* |
| * This is fine, loop again, more data to send. |
| */ |
| continue; |
| } |
| 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; |
| JK_ATOMIC_DECREMENT(&(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; |
| int busy; |
| |
| 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, |
| "(%s) Failed allocating AJP message", aw->name); |
| 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, |
| "(%s) Failed allocating AJP message buffer", aw->name); |
| 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, |
| "(%s) Failed allocating AJP message", aw->name); |
| 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, |
| "(%s) Failed allocating AJP message buffer", aw->name); |
| JK_TRACE_EXIT(l); |
| return JK_SERVER_ERROR; |
| } |
| |
| op->post = jk_b_new(&(p->pool)); |
| if (!op->post) { |
| jk_log(l, JK_LOG_ERROR, |
| "(%s) Failed allocating AJP message", aw->name); |
| 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, |
| "(%s) Failed allocating AJP message buffer", aw->name); |
| 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, |
| "(%s) Creating AJP message failed " |
| "without recovery - check max_packet_size", aw->name); |
| 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); |
| } |
| busy = JK_ATOMIC_INCREMENT(&(aw->s->busy)); |
| if (aw->busy_limit > 0 && busy > aw->busy_limit) { |
| JK_ATOMIC_DECREMENT(&(aw->s->busy)); |
| e->recoverable = JK_TRUE; |
| aw->s->errors++; |
| aw->s->error_time = time(NULL); |
| *is_error = JK_HTTP_SERVER_BUSY; |
| rc = JK_BUSY_ERROR; |
| jk_log(l, JK_LOG_ERROR, |
| "(%s) sending request to tomcat failed (recoverable), " |
| "busy limit %d reached (rc=%d, errors=%d, client_errors=%d).", |
| aw->name, aw->busy_limit, rc, aw->s->errors, aw->s->client_errors); |
| JK_TRACE_EXIT(l); |
| return rc; |
| } |
| if (aw->s->state == JK_AJP_STATE_ERROR) |
| aw->s->state = JK_AJP_STATE_PROBE; |
| if (busy > aw->s->max_busy) |
| aw->s->max_busy = 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, |
| "(%s) retry %d, sleeping for %d ms before retrying", |
| aw->name, 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 (%srecoverable)", |
| aw->name, err, op->recoverable ? "" : "un"); |
| } |
| 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); |
| } |
| ajp_update_stats(e, aw, rc, l); |
| /* Log the error only once per failed request. |
| */ |
| jk_log(l, JK_LOG_ERROR, |
| "(%s) connecting to tomcat failed (rc=%d, errors=%d, client_errors=%d).", |
| aw->name, rc, aw->s->errors, aw->s->client_errors); |
| |
| 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 { |
| if (pThis && pThis->worker_private) { |
| ajp_worker_t *p = pThis->worker_private; |
| jk_log(l, JK_LOG_ERROR, |
| "(%s) unknown protocol %d", p->name, proto); |
| } |
| else { |
| jk_log(l, JK_LOG_ERROR, |
| "(unset worker) unknown protocol %d", proto); |
| } |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |
| |
| if (pThis && pThis->worker_private) { |
| const char *tmp; |
| ajp_worker_t *p = pThis->worker_private; |
| p->worker.we = we; |
| p->port = jk_get_worker_port(props, p->name, port); |
| if (!host) { |
| host = "undefined"; |
| } |
| tmp = jk_get_worker_host(props, p->name, host); |
| if (jk_check_attribute_length("host name", tmp, l) == JK_FALSE) { |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |
| strncpy(p->host, tmp, JK_SHM_STR_SIZ); |
| p->prefer_ipv6 = jk_get_worker_prefer_ipv6(props, p->name, JK_FALSE); |
| tmp = jk_get_worker_source(props, p->name, ""); |
| if (jk_check_attribute_length("source address", tmp, l) == JK_FALSE) { |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |
| strncpy(p->source, tmp, JK_SHM_STR_SIZ); |
| if (p->s->h.sequence == 0) { |
| /* Initial setup. |
| */ |
| if (JK_IS_DEBUG_LEVEL(l)) |
| jk_log(l, JK_LOG_DEBUG, |
| "worker %s target is '%s:%d'", |
| p->name, p->host, p->port); |
| if (p->port > 0) { |
| if (!jk_resolve(p->host, p->port, &p->worker_inet_addr, |
| we->pool, p->prefer_ipv6, l)) { |
| 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); |
| } |
| } |
| if (p->source && *p->source) { |
| if (!jk_resolve(p->source, 0, &p->worker_source_inet_addr, |
| we->pool, p->prefer_ipv6, l)) { |
| p->worker_source_inet_addr.ipaddr_ptr = NULL; |
| jk_log(l, JK_LOG_WARNING, |
| "worker %s can't resolve source address '%s'", |
| p->name, p->source); |
| } |
| } |
| p->addr_sequence = 0; |
| p->s->addr_sequence = 0; |
| p->s->last_maintain_time = time(NULL); |
| p->s->last_reset = p->s->last_maintain_time; |
| p->s->port = p->port; |
| strncpy(p->s->host, p->host, JK_SHM_STR_SIZ); |
| jk_ajp_push(p, JK_TRUE, l); |
| } |
| else { |
| /* Somebody already setup this worker. |
| */ |
| if (JK_IS_DEBUG_LEVEL(l)) |
| jk_log(l, JK_LOG_DEBUG, |
| "worker %s contact '%s:%d' already configured type=%d (%d) [%d]", |
| p->name, p->host, p->port, p->s->h.type, |
| p->s->h.sequence, p->s->addr_sequence); |
| /* Force resolve */ |
| p->addr_sequence = -1; |
| jk_ajp_pull(p, JK_TRUE, l); |
| } |
| 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, |
| "(%s) setting connection pool size to %u with min %u " |
| "and acquire timeout %d", |
| p->name, 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, |
| "(%s) allocating endpoint slot %d (errno=%d)", |
| p->name, 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]->avail = JK_TRUE; |
| 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->busy_limit = |
| jk_get_worker_busy_limit(props, p->name, 0); |
| jk_get_worker_fail_on_status(props, p->name, |
| &(p->http_status_fail), |
| &(p->http_status_fail_num)); |
| |
| if (p->retries < 1) { |
| jk_log(l, JK_LOG_INFO, |
| "(%s) number of retries must be greater then 1. Setting to default=%d", |
| p->name, JK_RETRIES); |
| p->retries = JK_RETRIES; |
| } |
| |
| p->maintain_time = jk_get_worker_maintain_time(props); |
| if(p->maintain_time < 0) |
| p->maintain_time = 0; |
| if (JK_IS_DEBUG_LEVEL(l)) { |
| |
| jk_log(l, JK_LOG_DEBUG, |
| "(%s) setting endpoint options:", |
| p->name); |
| |
| 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); |
| |
| jk_log(l, JK_LOG_DEBUG, |
| "busy limit: %d", |
| p->busy_limit); |
| } |
| /* |
| * Need to initialize secret here since we could return from inside |
| * of the following loop |
| */ |
| p->secret = jk_get_worker_secret(props, p->name); |
| if (!ajp_create_endpoint_cache(p, proto, l)) { |
| jk_log(l, JK_LOG_ERROR, |
| "(%s) allocating connection pool of size %u", |
| p->name, 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) |
| { |
| int rc; |
| 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, |
| "(%s) malloc of private_data failed", name); |
| 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->worker.shutdown = ajp_shutdown; |
| |
| aw->logon = NULL; |
| |
| *w = &aw->worker; |
| |
| aw->s = jk_shm_alloc_ajp_worker(&aw->p, name, l); |
| if (!aw->s) { |
| jk_close_pool(&aw->p); |
| free(aw); |
| jk_log(l, JK_LOG_ERROR, |
| "(%s) allocating ajp worker record from shared memory", aw->name); |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |
| JK_INIT_CS(&aw->cs, rc); |
| if (!rc) { |
| jk_log(l, JK_LOG_ERROR, |
| "(%s) creating thread lock (errno=%d)", |
| aw->name, errno); |
| jk_close_pool(&aw->p); |
| free(aw); |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |
| if (JK_IS_DEBUG_LEVEL(l)) |
| jk_log(l, JK_LOG_DEBUG, |
| "ajp worker '%s' type=%d created", |
| aw->name, aw->s->h.type); |
| 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, |
| "(%s) up to %u endpoints to close", |
| aw->name, 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); |
| |
| 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; |
| 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); |
| p->avail = JK_TRUE; |
| JK_LEAVE_CS(&w->cs); |
| |
| if (JK_IS_DEBUG_LEVEL(l)) |
| jk_log(l, JK_LOG_DEBUG, |
| "recycling connection pool for worker %s and socket %d", |
| p->worker->name, (int)p->sd); |
| JK_TRACE_EXIT(l); |
| return JK_TRUE; |
| } |
| |
| 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 retry = 0; |
| |
| *je = NULL; |
| /* Loop until cache_acquire_timeout interval elapses |
| */ |
| while ((retry * JK_SLEEP_DEF) < aw->cache_acquire_timeout) { |
| unsigned int slot; |
| |
| JK_ENTER_CS(&aw->cs); |
| /* Try to find connected socket cache entry |
| */ |
| for (slot = 0; slot < aw->ep_cache_sz; slot++) { |
| if (IS_SLOT_AVAIL(aw->ep_cache[slot]) && |
| IS_VALID_SOCKET(aw->ep_cache[slot]->sd)) { |
| ae = aw->ep_cache[slot]; |
| if (ae->reuse) { |
| aw->ep_cache[slot]->avail = JK_FALSE; |
| break; |
| } |
| else { |
| /* XXX: We shouldn't have non reusable |
| * opened socket in the cache |
| */ |
| ajp_reset_endpoint(ae, l); |
| ae->avail = JK_TRUE; |
| ae = NULL; |
| jk_log(l, JK_LOG_WARNING, |
| "(%s) closing non reusable pool slot=%d", aw->name, slot); |
| } |
| } |
| } |
| if (!ae) { |
| /* No connected cache entry found. |
| * Use the first free one. |
| */ |
| for (slot = 0; slot < aw->ep_cache_sz; slot++) { |
| if (IS_SLOT_AVAIL(aw->ep_cache[slot])) { |
| ae = aw->ep_cache[slot]; |
| aw->ep_cache[slot]->avail = JK_FALSE; |
| break; |
| } |
| } |
| } |
| JK_LEAVE_CS(&aw->cs); |
| 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, |
| "(%s) acquired connection pool slot=%u after %d retries", |
| aw->name, 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); |
| } |
| } |
| 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, int global, jk_logger_t *l) |
| { |
| JK_TRACE_ENTER(l); |
| |
| if (pThis && pThis->worker_private) { |
| ajp_worker_t *aw = pThis->worker_private; |
| int i; |
| unsigned int n = 0, k = 0, cnt = 0; |
| unsigned int m, m_count = 0; |
| jk_sock_t *m_sock; |
| |
| /* 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); |
| /* 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 (IS_SLOT_AVAIL(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, |
| "(%s) cleaning pool slot=%d elapsed %d in %d", |
| aw->name, 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, |
| "(%s) reached pool min size %u from %u cache slots", |
| aw->name, 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 (IS_SLOT_AVAIL(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->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); |
| } |
| } |
| } |
| } |
| } |
| JK_LEAVE_CS(&aw->cs); |
| /* 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++) { |
| if (IS_VALID_SOCKET(m_sock[m])) { |
| jk_shutdown_socket(m_sock[m], l); |
| JK_ATOMIC_DECREMENT(&(aw->s->connected)); |
| } |
| } |
| free(m_sock); |
| if ((k + n) && JK_IS_DEBUG_LEVEL(l)) |
| jk_log(l, JK_LOG_DEBUG, |
| "(%s) pinged %u and recycled %u sockets in %d seconds from %u pool slots", |
| aw->name, k, n, (int)(difftime(time(NULL), mstarted)), |
| aw->ep_cache_sz); |
| JK_TRACE_EXIT(l); |
| return JK_TRUE; |
| } |
| else { |
| JK_LOG_NULL_PARAMS(l); |
| } |
| |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |
| |
| int JK_METHOD ajp_shutdown(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 i; |
| unsigned int n = 0; |
| |
| JK_ENTER_CS(&aw->cs); |
| for (i = (int)aw->ep_cache_sz - 1; |
| i >= 0; i--) { |
| /* Skip the closed sockets |
| */ |
| if (IS_SLOT_AVAIL(aw->ep_cache[i]) && |
| IS_VALID_SOCKET(aw->ep_cache[i]->sd)) { |
| n++; |
| aw->ep_cache[i]->reuse = JK_FALSE; |
| aw->ep_cache[i]->hard_close = JK_TRUE; |
| ajp_reset_endpoint(aw->ep_cache[i], l); |
| aw->ep_cache[i]->sd = JK_INVALID_SOCKET; |
| if (JK_IS_DEBUG_LEVEL(l)) |
| jk_log(l, JK_LOG_DEBUG, |
| "(%s) shut down pool slot=%d", |
| aw->name, i); |
| } |
| } |
| JK_LEAVE_CS(&aw->cs); |
| if (n && JK_IS_DEBUG_LEVEL(l)) |
| jk_log(l, JK_LOG_DEBUG, |
| "(%s) shut down %u sockets from %u pool slots", |
| aw->name, n, aw->ep_cache_sz); |
| JK_TRACE_EXIT(l); |
| return JK_TRUE; |
| } |
| 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; |
| unsigned int slot; |
| |
| JK_ENTER_CS(&aw->cs); |
| /* Try to find connected socket cache entry |
| */ |
| for (slot = 0; slot < aw->ep_cache_sz; slot++) { |
| if (IS_SLOT_AVAIL(aw->ep_cache[slot])) { |
| JK_LEAVE_CS(&aw->cs); |
| return JK_TRUE; |
| } |
| } |
| JK_LEAVE_CS(&aw->cs); |
| } |
| else { |
| JK_LOG_NULL_PARAMS(l); |
| } |
| |
| JK_TRACE_EXIT(l); |
| return JK_FALSE; |
| } |