const char *req_HeaderForKey(HTTPRequest *req, const char *key) { if (req->headers) return st_valueFor(req->headers, key); else return NULL; }
/* * run the request */ static HTTPResponse *_runRequest(WOAppReq *app, WOAppHandle woappHandle, WOInstanceHandle instHandle, HTTPRequest *req) { WOConnection *c; HTTPResponse *resp = NULL; int send_status, keepConnection, retryRequest = 0; const char *idString = NULL; WOLog(WO_INFO,"Trying to contact %s:%s on %s(%d)", app->name,app->instance,app->host,app->port); /* Tag the request with a unique identifier (if it hasn't been already) */ /* (It might already have been tagged if we are retrying the request due to refusing new sessions, for example) */ idString = req_HeaderForKey(req, REQUEST_ID_HEADER); if (idString == NULL) { String *requestID = tr_uniqueID(); if (requestID) { req_addHeader(req, REQUEST_ID_HEADER, requestID->text, STR_COPYVALUE); idString = req_HeaderForKey(req, REQUEST_ID_HEADER); str_free(requestID); } } do { c = tr_open(instHandle); /* connect */ if (c == NULL) { WOLog(WO_INFO,"%s:%s NOT LISTENING on %s(%d)", app->name,app->instance,app->host,app->port); app->error = err_connect; return NULL; } /* * app found and is listening on port */ if (app->scheduler->beginTransaction) app->scheduler->beginTransaction(app, instHandle); /* Make sure that we're the only connection header, and we're explicit about the setting */ req_removeHeader(req,CONNECTION); if (c->isPooled) { req_addHeader(req,CONNECTION,HTTP_KEEP_ALIVE,0); } else { req_addHeader(req,CONNECTION,HTTP_CLOSE,0); } WOLog(WO_INFO,"%s:%s on %s(%d) connected [pooled: %s]", app->name, app->instance, app->host, app->port, c->isPooled ? "Yes" : "No"); /* * send the request.... */ send_status = req_sendRequest(req, c->fd); if (send_status != 0) { if ((send_status == TR_RESET) && (retryRequest == 0) && !req->haveReadStreamedData) { /* If we get here the connection was reset. This means the instance has either quit or crashed. */ /* Bump the generation number so all pooled connections to this instance will be invalidated. */ /* Then retry the request with a new connection. If the instance is not running the retry will */ /* fail with a different error and the instance will be marked dead. */ _WOInstance *inst = ac_lockInstance(instHandle); /* note: if we get here, keepConnection == 0 --> this connection will be closed */ if (inst) { ac_cycleInstance(inst, c->generation); ac_unlockInstance(instHandle); } retryRequest++; WOLog(WO_INFO, "retrying request due to connection reset"); /* Must close connection before continuing */ tr_close(c, instHandle, 0); continue; } else { WOLog(WO_ERR,"Failed to send request"); tr_close(c, instHandle, 0); /* close app connection */ if (send_status == -1) app->error = err_read; else app->error = err_send; return NULL; } } /* Note that we have a request queued */ WOLog(WO_INFO,"Request %s sent, awaiting response", req->request_str); /* While the app is processing the request, take the opportunity to check/update the config. */ ac_readConfiguration(); /* * now wait for the response... */ resp = resp_getResponseHeaders(c, instHandle); /* go ahead and read the first chunk of response data */ if (resp && req->method != HTTP_HEAD_METHOD) { if (resp_getResponseContent(resp, 1) == -1) { resp_free(resp); resp = NULL; } } /* Validate the ID */ if (idString && resp) { const char *respID = st_valueFor(resp->headers, REQUEST_ID_HEADER); if (respID != NULL) { if (strcmp(respID, idString) != 0) { WOLog(WO_ERR, "Got response with wrong ID! Dumping response. request ID = %s, response ID = %s", idString, respID); /* note this will cause the connection to be closed below */ resp_free(resp); resp = NULL; } else st_removeKey(resp->headers, REQUEST_ID_HEADER); } else WOLog(WO_WARN, "Got response with no ID."); } app->response = resp; /* * check if this connection can be kept open */ keepConnection = 0; #ifndef CGI /* doesn't make sense to keep the connection for CGI */ if (resp && resp->headers) { const char *keepAlive; keepAlive = st_valueFor(resp->headers, CONNECTION); if (keepAlive) { /* if the keep alive header is set, honor the value */ if (strcasecmp(keepAlive, HTTP_KEEP_ALIVE) == 0) keepConnection = 1; } else { /* no keep alive header - keep alive by default for HTTP/1.1 only */ if (resp->flags & RESP_HTTP11) keepConnection = 1; } } #endif if (resp != NULL) { if (app->scheduler->finalizeTransaction) if (app->scheduler->finalizeTransaction(app, instHandle)) keepConnection = 0; st_removeKey(resp->headers, REFUSING_SESSIONS_HEADER); st_removeKey(resp->headers, LOAD_AVERAGE_HEADER); st_removeKey(resp->headers, CONNECTION); WOLog(WO_INFO,"received ->%d %s",resp->status,resp->statusMsg); retryRequest = 0; } else { if (c != NULL && tr_connectionWasReset(c)) { /* If we get here the connection was reset. This means the instance has either quit or crashed. */ /* Bump the generation number so all pooled connections to this instance will be invalidated. */ /* Then retry the request with a new connection. If the instance is not running the retry will */ /* fail with a different error and the instance will be marked dead. */ /* Note that only one retry due to a connection reset error is allowed. This is to prevent an */ /* infinite loop if the instance dies while processing the request and restarts quickly enough */ /* to process the retry. */ _WOInstance *inst = ac_lockInstance(instHandle); /* note: if we get here, keepConnection == 0 --> this connection will be closed */ if (inst) { ac_cycleInstance(inst, c->generation); ac_unlockInstance(instHandle); } retryRequest++; if (retryRequest == 1) WOLog(WO_INFO, "retrying request due to connection reset"); } else app->error = err_response; } if (resp && resp->content_read < resp->content_length) { resp->keepConnection = keepConnection; resp->instHandle = instHandle; resp->flags |= RESP_CLOSE_CONNECTION; } else { tr_close(c, instHandle, keepConnection); } } while (retryRequest == 1); return resp; }
/* * Handle the meat of the request: * - check for a load balanced instance * - (or) autostart an (or find an autostarted) instance * - open a socket to the application * - message the application with the request using HTTP * - wait for and receive the response * - free the request * - return the response * * If we can't message the application, retry the request up to * RETRIES times (see config.h). * */ HTTPResponse *tr_handleRequest(HTTPRequest *req, const char *url, WOURLComponents *wc, const char *server_protocol, const char *docroot) { WOAppReq app; int connectionAttempts = 0, searchAttempts = 0; HTTPResponse *resp = NULL; WOAppHandle appHandle = AC_INVALID_HANDLE; _WOApp *woapp = NULL; WOInstanceHandle instHandle = AC_INVALID_HANDLE, retryInstHandle; _WOInstance *inst; int urlVersion, retries = RETRIES; memset(&app, 0, sizeof(WOAppReq)); app.urlVersion = CURRENT_WOF_VERSION_MAJOR; app.docroot = docroot; app.request = req; /* valid for request forwarding phase */ /* * copy the application name */ if (wc->applicationName.length != 0 && wc->applicationName.length < WA_MAX_APP_NAME_LENGTH) { strncpy(app.name, wc->applicationName.start, wc->applicationName.length); app.name[wc->applicationName.length] = '\0'; } /* * find the application */ for (searchAttempts = 0; searchAttempts < 2 && instHandle == AC_INVALID_HANDLE; searchAttempts++) { /* If the find app succeeds, have to be very careful about the possibility */ /* that someone else can delete it out from under us. */ appHandle = ac_findApplication(app.name); if (appHandle != AC_INVALID_HANDLE) { /* cache some info so we don't have to keep the app locked */ woapp = ac_lockedApp(appHandle); urlVersion = woapp->urlVersion; app.scheduler = lb_schedulerByName(woapp->loadbalance); strncpy(app.redirect_url, woapp->redirect_url, WA_MAX_URL_LENGTH); retries = woapp->retries; /* extract the request & application information from the URI */ resp = _collectRequestInformation(&app, wc, url, urlVersion, req); if (resp != NULL) { ac_unlockApp(appHandle); return resp; /* some kind of error in URI */ } /* note: if we found the app, it is still locked at this point */ /* * select an app, the request may be requesting a specific instance * or it may be indifferent. an instance neutral request may be a new * request or a returning one for which the state is not stored in the * app. * if specified instance does not respond, we'll failover to another * instance */ if (app.instance[0] == 0) { instHandle = tr_selectInstance(&app, woapp); if (instHandle != AC_INVALID_HANDLE) { WOLog(WO_INFO,"Selected new app instance at index %d", instHandle); tr_prepareToUseInstance(&app, instHandle); ac_unlockInstance(instHandle); } } else { WOLog(WO_INFO,"Selecting specific app instance %s.", app.instance); instHandle = ac_findInstance(woapp, app.instance); if (instHandle == AC_INVALID_HANDLE) { WOLog(WO_WARN, "Unable to find instance %s. Attempting to select another.", app.instance); instHandle = tr_selectInstance(&app, woapp); } if (instHandle != AC_INVALID_HANDLE) { tr_prepareToUseInstance(&app, instHandle); ac_unlockInstance(instHandle); } } /* At this point, pendingResponses has been incremented in the instance. */ /* An instance will not be deleted with a nonzero pendingResponses coung, and */ /* an app will not be deleted if it has any instances. Now it is ok to unlock the app. */ ac_unlockApp(appHandle); } /* If we didn't find the app or if we didn't find a specific requested instance, reload the config */ if (searchAttempts == 0 && (appHandle == AC_INVALID_HANDLE || (instHandle == AC_INVALID_HANDLE && app.instance[0] == '-'))) { if (app.instance[0] != 0) WOLog(WO_INFO, "Requested application '%s' not found. Reloading config.", app.name); else WOLog(WO_INFO, "Specific instance %s:%s not found. Reloading config.", app.name, app.instance); ac_resetConfigTimers(); ac_readConfiguration(); } else { /* didn't reload the config, so no point in retrying */ break; } } /* * run the request... */ resp = NULL; if (instHandle != AC_INVALID_HANDLE) { /* Attempt to send request and read response */ do { /* Fix up URL so app knows it's being load balanced ... */ /* We need to do this in the loop to get correct instance number into the request URL */ req_reformatRequest(req, &app, wc, server_protocol); WOLog(WO_INFO,"Sending request to instance number %s, port %d", app.instance, app.port); resp = _runRequest(&app, appHandle, instHandle, req); /* Cannot retry if we have read some streamed content data because we cannot unwind the byte stream. */ if (req->haveReadStreamedData) retries = 0; if (resp) { if (resp->status == 302) /* redirected */ { if (!req->haveReadStreamedData && st_valueFor(resp->headers, "x-webobjects-refusing-redirection")) { /* redirected because instance is refusing new sessions */ resp_free(resp); resp = NULL; WOLog(WO_INFO, "Request redirected because instance refusing new sessions."); } } } else { if (app.error != err_read) { connectionAttempts++; /* Mark this instance as unresponsive */ WOLog(WO_INFO,"Marking instance %s dead", app.instance); if (app.scheduler->instanceDidNotRespond) { inst = ac_lockInstance(instHandle); if (inst) { app.scheduler->instanceDidNotRespond(inst); ac_unlockInstance(instHandle); } } WOLog(WO_DBG, "connectionAttempts = %d, retries = %d", connectionAttempts, retries); ac_readConfiguration(); } } if (resp == NULL && connectionAttempts <= retries) { /* appHandle is still valid because we have not decremented pendingResponses on the instance */ woapp = ac_lockApp(appHandle); if (woapp) { retryInstHandle = tr_selectInstance(&app, woapp); if (retryInstHandle != AC_INVALID_HANDLE) { tr_prepareToUseInstance(&app, retryInstHandle); ac_unlockInstance(retryInstHandle); } ac_unlockApp(appHandle); } else retryInstHandle = AC_INVALID_HANDLE; /* Decrement pendingResponses on the original instance. It is safe to do now. */ /* The retry instance has had pendingResponses incremented, which will prevent the */ /* app from being removed. (Or, there was not other instance to retry, and we are done.) */ inst = ac_lockInstance(instHandle); if (inst) { if (inst->pendingResponses > 0) inst->pendingResponses--; ac_unlockInstance(instHandle); instHandle = retryInstHandle; } if (retryInstHandle != AC_INVALID_HANDLE) { WOLog(WO_INFO,"Attempting failover to new instance at index %d", retryInstHandle); } else { WOLog(WO_INFO,"No new instance located for failover"); } instHandle = retryInstHandle; } } while (resp == NULL && instHandle != AC_INVALID_HANDLE && connectionAttempts < retries); if (instHandle != AC_INVALID_HANDLE) { /* still have to decrement pendingResponses */ inst = ac_lockInstance(instHandle); if (inst) { if (inst->pendingResponses > 0) inst->pendingResponses--; ac_unlockInstance(instHandle); /* At this point, the app could be removed from the config at any time. */ instHandle = retryInstHandle; } } } else { if (ac_authorizeAppListing(wc)) resp = WOAdaptorInfo(req, wc); else app.error = err_notFound; } /* * how'd we do? */ if (resp == NULL) { /* * we may be able to send a palatable response... */ resp = _errorResponse(&app, wc, req); } return resp; }