- Main Page
- Modules
- Classes
- Files

- File List

# Application.h

    00001 /*
    00002  *  Phusion Passenger - http://www.modrails.com/
    00003  *  Copyright (C) 2008  Phusion
    00004  *
    00005  *  Phusion Passenger is a trademark of Hongli Lai & Ninh Bui.
    00006  *
    00007  *  This program is free software; you can redistribute it and/or modify
    00008  *  it under the terms of the GNU General Public License as published by
    00009  *  the Free Software Foundation; version 2 of the License.
    00010  *
    00011  *  This program is distributed in the hope that it will be useful,
    00012  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
    00013  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    00014  *  GNU General Public License for more details.
    00015  *
    00016  *  You should have received a copy of the GNU General Public License along
    00017  *  with this program; if not, write to the Free Software Foundation, Inc.,
    00018  *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
    00019  */
    00020 #ifndef _PASSENGER_APPLICATION_H_
    00021 #define _PASSENGER_APPLICATION_H_
    00022
    00023 #include <boost/shared_ptr.hpp>
    00024 #include <boost/function.hpp>
    00025 #include <oxt/system_calls.hpp>
    00026 #include <oxt/backtrace.hpp>
    00027 #include <string>
    00028 #include <vector>
    00029
    00030 #include <sys/types.h>
    00031 #include <sys/socket.h>
    00032 #include <sys/un.h>
    00033 #include <netdb.h>
    00034 #include <unistd.h>
    00035 #include <errno.h>
    00036 #include <ctime>
    00037 #include <cstring>
    00038
    00039 #include "MessageChannel.h"
    00040 #include "Exceptions.h"
    00041 #include "Logging.h"
    00042 #include "Utils.h"
    00043
    00044 namespace Passenger {
    00045
    00046 using namespace std;
    00047 using namespace boost;
    00048
    00049 /**
    00050  * Represents a single Ruby on Rails or Rack application instance.
    00051  *
    00052  * @ingroup Support
    00053  */
    00054 class Application {
    00055 public:
    00056         class Session;
    00057         /** Convenient alias for Session smart pointer. */
    00058         typedef shared_ptr<Session> SessionPtr;
    00059
    00060         /**
    00061          * Represents the life time of a single request/response pair of a
    00062          * Ruby on Rails or Rack application.
    00063          *
    00064          * Session is used to forward a single HTTP request to a Ruby on Rails/Rack
    00065          * application. A Session has two communication channels: one for reading data
    00066          * from the application, and one for writing data to the application.
    00067          *
    00068          * In general, a session object is to be used in the following manner:
    00069          *
    00070          *  -# Convert the HTTP request headers into a string, as expected by sendHeaders().
    00071          *     Then send that string by calling sendHeaders().
    00072          *  -# In case of a POST of PUT request, send the HTTP request body by calling
    00073          *     sendBodyBlock(), possibly multiple times.
    00074          *  -# Shutdown the writer channel since you're now done sending data.
    00075          *  -# The HTTP response can now be read through the reader channel (getStream()).
    00076          *  -# When the HTTP response has been read, the session must be closed.
    00077          *     This is done by destroying the Session object.
    00078          *
    00079          * A usage example is shown in Application::connect().
    00080          */
    00081         class Session {
    00082         public:
    00083                 /**
    00084                  * Implementing classes might throw arbitrary exceptions.
    00085                  */
    00086                 virtual ~Session() {}
    00087
    00088                 /**
    00089                  * Send HTTP request headers to the application. The HTTP headers must be
    00090                  * converted into CGI headers, and then encoded into a string that matches this grammar:
    00091                  *
    00092                    @verbatim
    00093                    headers ::= header*
    00094                    header ::= name NUL value NUL
    00095                    name ::= notnull+
    00096                    value ::= notnull+
    00097                    notnull ::= "\x01" | "\x02" | "\x02" | ... | "\xFF"
    00098                    NUL = "\x00"
    00099                    @endverbatim
    00100                  *
    00101                  * This method should be the first one to be called during the lifetime of a Session
    00102                  * object. Otherwise strange things may happen.
    00103                  *
    00104                  * @param headers The HTTP request headers, converted into CGI headers and encoded as
    00105                  *                a string, according to the description.
    00106                  * @param size The size, in bytes, of <tt>headers</tt>.
    00107                  * @pre headers != NULL
    00108                  * @throws IOException The writer channel has already been closed.
    00109                  * @throws SystemException Something went wrong during writing.
    00110                  * @throws boost::thread_interrupted
    00111                  */
    00112                 virtual void sendHeaders(const char *headers, unsigned int size) {
    00113                         TRACE_POINT();
    00114                         int stream = getStream();
    00115                         if (stream == -1) {
    00116                                 throw IOException("Cannot write headers to the request handler "
    00117                                         "because the writer stream has already been closed.");
    00118                         }
    00119                         try {
    00120                                 MessageChannel(stream).writeScalar(headers, size);
    00121                         } catch (SystemException &e) {
    00122                                 e.setBriefMessage("An error occured while writing headers "
    00123                                         "to the request handler");
    00124                                 throw;
    00125                         }
    00126                 }
    00127
    00128                 /**
    00129                  * Convenience shortcut for sendHeaders(const char *, unsigned int)
    00130                  * @param headers
    00131                  * @throws IOException The writer channel has already been closed.
    00132                  * @throws SystemException Something went wrong during writing.
    00133                  * @throws boost::thread_interrupted
    00134                  */
    00135                 virtual void sendHeaders(const string &headers) {
    00136                         sendHeaders(headers.c_str(), headers.size());
    00137                 }
    00138
    00139                 /**
    00140                  * Send a chunk of HTTP request body data to the application.
    00141                  * You can call this method as many times as is required to transfer
    00142                  * the entire HTTP request body.
    00143                  *
    00144                  * This method should only be called after a sendHeaders(). Otherwise
    00145                  * strange things may happen.
    00146                  *
    00147                  * @param block A block of HTTP request body data to send.
    00148                  * @param size The size, in bytes, of <tt>block</tt>.
    00149                  * @throws IOException The writer channel has already been closed.
    00150                  * @throws SystemException Something went wrong during writing.
    00151                  * @throws boost::thread_interrupted
    00152                  */
    00153                 virtual void sendBodyBlock(const char *block, unsigned int size) {
    00154                         TRACE_POINT();
    00155                         int stream = getStream();
    00156                         if (stream == -1) {
    00157                                 throw IOException("Cannot write request body block to the "
    00158                                         "request handler because the writer stream has "
    00159                                         "already been closed.");
    00160                         }
    00161                         try {
    00162                                 MessageChannel(stream).writeRaw(block, size);
    00163                         } catch (SystemException &e) {
    00164                                 e.setBriefMessage("An error occured while sending the "
    00165                                         "request body to the request handler");
    00166                                 throw;
    00167                         }
    00168                 }
    00169
    00170                 /**
    00171                  * Get the I/O stream's file descriptor. This steam is full-duplex,
    00172                  * and will be automatically closed upon Session's destruction,
    00173                  * unless discardStream() is called.
    00174                  *
    00175                  * @pre The stream has not been fully closed.
    00176                  */
    00177                 virtual int getStream() const = 0;
    00178
    00179                 /**
    00180                  * Set the timeout value for reading data from the I/O stream.
    00181                  * If no data can be read within the timeout period, then the
    00182                  * read call will fail with error EAGAIN or EWOULDBLOCK.
    00183                  *
    00184                  * @param msec The timeout, in milliseconds. If 0 is given,
    00185                  *             there will be no timeout.
    00186                  * @throws SystemException Cannot set the timeout.
    00187                  */
    00188                 virtual void setReaderTimeout(unsigned int msec) = 0;
    00189
    00190                 /**
    00191                  * Set the timeout value for writing data from the I/O stream.
    00192                  * If no data can be written within the timeout period, then the
    00193                  * write call will fail with error EAGAIN or EWOULDBLOCK.
    00194                  *
    00195                  * @param msec The timeout, in milliseconds. If 0 is given,
    00196                  *             there will be no timeout.
    00197                  * @throws SystemException Cannot set the timeout.
    00198                  */
    00199                 virtual void setWriterTimeout(unsigned int msec) = 0;
    00200
    00201                 /**
    00202                  * Indicate that we don't want to read data anymore from the I/O stream.
    00203                  * Calling this method after closeStream() is called will have no effect.
    00204                  *
    00205                  * @throws SystemException Something went wrong.
    00206                  * @throws boost::thread_interrupted
    00207                  */
    00208                 virtual void shutdownReader() = 0;
    00209
    00210                 /**
    00211                  * Indicate that we don't want to write data anymore to the I/O stream.
    00212                  * Calling this method after closeStream() is called will have no effect.
    00213                  *
    00214                  * @throws SystemException Something went wrong.
    00215                  * @throws boost::thread_interrupted
    00216                  */
    00217                 virtual void shutdownWriter() = 0;
    00218
    00219                 /**
    00220                  * Close the I/O stream.
    00221                  *
    00222                  * @throws SystemException Something went wrong.
    00223                  * @throws boost::thread_interrupted
    00224                  */
    00225                 virtual void closeStream() = 0;
    00226
    00227                 /**
    00228                  * Discard the I/O stream's file descriptor, so that Session won't automatically
    00229                  * closed it upon Session's destruction.
    00230                  */
    00231                 virtual void discardStream() = 0;
    00232
    00233                 /**
    00234                  * Get the process ID of the application instance that belongs to this session.
    00235                  */
    00236                 virtual pid_t getPid() const = 0;
    00237         };
    00238
    00239 private:
    00240         /**
    00241          * A "standard" implementation of Session.
    00242          */
    00243         class StandardSession: public Session {
    00244         protected:
    00245                 function<void()> closeCallback;
    00246                 int fd;
    00247                 pid_t pid;
    00248
    00249         public:
    00250                 StandardSession(pid_t pid,
    00251                                 const function<void()> &closeCallback,
    00252                                 int fd) {
    00253                         this->pid = pid;
    00254                         this->closeCallback = closeCallback;
    00255                         this->fd = fd;
    00256                 }
    00257
    00258                 virtual ~StandardSession() {
    00259                         TRACE_POINT();
    00260                         closeStream();
    00261                         closeCallback();
    00262                 }
    00263
    00264                 virtual int getStream() const {
    00265                         return fd;
    00266                 }
    00267
    00268                 virtual void setReaderTimeout(unsigned int msec) {
    00269                         MessageChannel(fd).setReadTimeout(msec);
    00270                 }
    00271
    00272                 virtual void setWriterTimeout(unsigned int msec) {
    00273                         MessageChannel(fd).setWriteTimeout(msec);
    00274                 }
    00275
    00276                 virtual void shutdownReader() {
    00277                         TRACE_POINT();
    00278                         if (fd != -1) {
    00279                                 int ret = syscalls::shutdown(fd, SHUT_RD);
    00280                                 if (ret == -1) {
    00281                                         throw SystemException("Cannot shutdown the writer stream",
    00282                                                 errno);
    00283                                 }
    00284                         }
    00285                 }
    00286
    00287                 virtual void shutdownWriter() {
    00288                         TRACE_POINT();
    00289                         if (fd != -1) {
    00290                                 int ret = syscalls::shutdown(fd, SHUT_WR);
    00291                                 if (ret == -1) {
    00292                                         throw SystemException("Cannot shutdown the writer stream",
    00293                                                 errno);
    00294                                 }
    00295                         }
    00296                 }
    00297
    00298                 virtual void closeStream() {
    00299                         TRACE_POINT();
    00300                         if (fd != -1) {
    00301                                 int ret = syscalls::close(fd);
    00302                                 if (ret == -1) {
    00303                                         throw SystemException("Cannot close the session stream",
    00304                                                 errno);
    00305                                 }
    00306                                 fd = -1;
    00307                         }
    00308                 }
    00309
    00310                 virtual void discardStream() {
    00311                         fd = -1;
    00312                 }
    00313
    00314                 virtual pid_t getPid() const {
    00315                         return pid;
    00316                 }
    00317         };
    00318
    00319         string appRoot;
    00320         pid_t pid;
    00321         string listenSocketName;
    00322         string listenSocketType;
    00323         int ownerPipe;
    00324
    00325         SessionPtr connectToUnixServer(const function<void()> &closeCallback) const {
    00326                 TRACE_POINT();
    00327                 int fd, ret;
    00328
    00329                 do {
    00330                         fd = socket(PF_UNIX, SOCK_STREAM, 0);
    00331                 } while (fd == -1 && errno == EINTR);
    00332                 if (fd == -1) {
    00333                         throw SystemException("Cannot create a new unconnected Unix socket", errno);
    00334                 }
    00335
    00336                 struct sockaddr_un addr;
    00337                 addr.sun_family = AF_UNIX;
    00338                 strncpy(addr.sun_path, listenSocketName.c_str(), sizeof(addr.sun_path));
    00339                 addr.sun_path[sizeof(addr.sun_path) - 1] = '\0';
    00340                 do {
    00341                         ret = ::connect(fd, (const sockaddr *) &addr, sizeof(addr));
    00342                 } while (ret == -1 && errno == EINTR);
    00343                 if (ret == -1) {
    00344                         int e = errno;
    00345                         string message("Cannot connect to Unix socket '");
    00346                         message.append(listenSocketName);
    00347                         message.append("'");
    00348                         do {
    00349                                 ret = close(fd);
    00350                         } while (ret == -1 && errno == EINTR);
    00351                         throw SystemException(message, e);
    00352                 }
    00353
    00354                 return ptr(new StandardSession(pid, closeCallback, fd));
    00355         }
    00356
    00357         SessionPtr connectToTcpServer(const function<void()> &closeCallback) const {
    00358                 TRACE_POINT();
    00359                 int fd, ret;
    00360                 vector<string> args;
    00361
    00362                 split(listenSocketName, ':', args);
    00363                 if (args.size() != 2 || atoi(args[1]) == 0) {
    00364                         throw IOException("Invalid TCP/IP address '" + listenSocketName + "'");
    00365                 }
    00366
    00367                 struct addrinfo hints, *res;
    00368
    00369                 memset(&hints, 0, sizeof(hints));
    00370                 hints.ai_family = PF_INET;
    00371                 hints.ai_socktype = SOCK_STREAM;
    00372                 ret = getaddrinfo(args[0].c_str(), args[1].c_str(), &hints, &res);
    00373                 if (ret != 0) {
    00374                         int e = errno;
    00375                         throw IOException("Cannot resolve address '" + listenSocketName +
    00376                                 "': " + gai_strerror(e));
    00377                 }
    00378
    00379                 do {
    00380                         fd = socket(PF_INET, SOCK_STREAM, 0);
    00381                 } while (fd == -1 && errno == EINTR);
    00382                 if (fd == -1) {
    00383                         freeaddrinfo(res);
    00384                         throw SystemException("Cannot create a new unconnected TCP socket", errno);
    00385                 }
    00386
    00387                 do {
    00388                         ret = ::connect(fd, res->ai_addr, res->ai_addrlen);
    00389                 } while (ret == -1 && errno == EINTR);
    00390                 freeaddrinfo(res);
    00391                 if (ret == -1) {
    00392                         int e = errno;
    00393                         string message("Cannot connect to TCP server '");
    00394                         message.append(listenSocketName);
    00395                         message.append("'");
    00396                         do {
    00397                                 ret = close(fd);
    00398                         } while (ret == -1 && errno == EINTR);
    00399                         throw SystemException(message, e);
    00400                 }
    00401
    00402                 return ptr(new StandardSession(pid, closeCallback, fd));
    00403         }
    00404
    00405 public:
    00406         /**
    00407          * Construct a new Application object.
    00408          *
    00409          * @param theAppRoot The application root of an application. In case of a Rails application,
    00410          *             this is the folder that contains 'app/', 'public/', 'config/', etc.
    00411          *             This must be a valid directory, but the path does not have to be absolute.
    00412          * @param pid The process ID of this application instance.
    00413          * @param listenSocketName The name of the listener socket of this application instance.
    00414          * @param listenSocketType The type of the listener socket, e.g. "unix" for Unix
    00415          *                         domain sockets.
    00416          * @param ownerPipe The owner pipe of this application instance.
    00417          * @post getAppRoot() == theAppRoot && getPid() == pid
    00418          */
    00419         Application(const string &theAppRoot, pid_t pid, const string &listenSocketName,
    00420                     const string &listenSocketType, int ownerPipe) {
    00421                 appRoot = theAppRoot;
    00422                 this->pid = pid;
    00423                 this->listenSocketName = listenSocketName;
    00424                 this->listenSocketType = listenSocketType;
    00425                 this->ownerPipe = ownerPipe;
    00426                 P_TRACE(3, "Application " << this << ": created.");
    00427         }
    00428
    00429         virtual ~Application() {
    00430                 TRACE_POINT();
    00431                 int ret;
    00432
    00433                 if (ownerPipe != -1) {
    00434                         do {
    00435                                 ret = close(ownerPipe);
    00436                         } while (ret == -1 && errno == EINTR);
    00437                 }
    00438                 if (listenSocketType == "unix") {
    00439                         do {
    00440                                 ret = unlink(listenSocketName.c_str());
    00441                         } while (ret == -1 && errno == EINTR);
    00442                 }
    00443                 P_TRACE(3, "Application " << this << ": destroyed.");
    00444         }
    00445
    00446         /**
    00447          * Returns the application root for this application. See the constructor
    00448          * for information about the application root.
    00449          */
    00450         string getAppRoot() const {
    00451                 return appRoot;
    00452         }
    00453
    00454         /**
    00455          * Returns the process ID of this application instance.
    00456          */
    00457         pid_t getPid() const {
    00458                 return pid;
    00459         }
    00460
    00461         /**
    00462          * Connect to this application instance with the purpose of sending
    00463          * a request to the application. Once connected, a new session will
    00464          * be opened. This session represents the life time of a single
    00465          * request/response pair, and can be used to send the request
    00466          * data to the application instance, as well as receiving the response
    00467          * data.
    00468          *
    00469          * The use of connect() is demonstrated in the following example.
    00470          * @code
    00471          *   // Connect to the application and get the newly opened session.
    00472          *   Application::SessionPtr session(app->connect("/home/webapps/foo"));
    00473          *
    00474          *   // Send the request headers and request body data.
    00475          *   session->sendHeaders(...);
    00476          *   session->sendBodyBlock(...);
    00477          *   // Done sending data, so we close the writer channel.
    00478          *   session->shutdownWriter();
    00479          *
    00480          *   // Now read the HTTP response.
    00481          *   string responseData = readAllDataFromSocket(session->getReader());
    00482          *   // Done reading data, so we close the reader channel.
    00483          *   session->shutdownReader();
    00484          *
    00485          *   // This session has now finished, so we close the session by resetting
    00486          *   // the smart pointer to NULL (thereby destroying the Session object).
    00487          *   session.reset();
    00488          *
    00489          *   // We can connect to an Application multiple times. Just make sure
    00490          *   // the previous session is closed.
    00491          *   session = app->connect("/home/webapps/bar")
    00492          * @endcode
    00493          *
    00494          * Note that a RoR application instance can only process one
    00495          * request at the same time, and thus only one session at the same time.
    00496          * It's unspecified whether Rack applications can handle multiple simultanous sessions.
    00497          *
    00498          * You <b>must</b> close a session when you no longer need if. If you
    00499          * call connect() without having properly closed a previous session,
    00500          * you might cause a deadlock because the application instance may be
    00501          * waiting for you to close the previous session.
    00502          *
    00503          * @return A smart pointer to a Session object, which represents the created session.
    00504          * @param closeCallback A function which will be called when the session has been closed.
    00505          * @post this->getSessions() == old->getSessions() + 1
    00506          * @throws SystemException Something went wrong during the connection process.
    00507          * @throws IOException Something went wrong during the connection process.
    00508          */
    00509         SessionPtr connect(const function<void()> &closeCallback) const {
    00510                 TRACE_POINT();
    00511                 if (listenSocketType == "unix") {
    00512                         return connectToUnixServer(closeCallback);
    00513                 } else if (listenSocketType == "tcp") {
    00514                         return connectToTcpServer(closeCallback);
    00515                 } else {
    00516                         throw IOException("Unsupported socket type '" + listenSocketType + "'");
    00517                 }
    00518         }
    00519 };
    00520
    00521 /** Convenient alias for Application smart pointer. */
    00522 typedef shared_ptr<Application> ApplicationPtr;
    00523
    00524 } // namespace Passenger
    00525
    00526 #endif /* _PASSENGER_APPLICATION_H_ */