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