Passenger: Session.h Source File

- Main Page
- Modules
- Namespaces
- Classes
- Files

- File List

# Session.h

    00001 /*
    00002  *  Phusion Passenger - http://www.modrails.com/
    00003  *  Copyright (c) 2010 Phusion
    00004  *
    00005  *  "Phusion Passenger" is a trademark of Hongli Lai & Ninh Bui.
    00006  *
    00007  *  Permission is hereby granted, free of charge, to any person obtaining a copy
    00008  *  of this software and associated documentation files (the "Software"), to deal
    00009  *  in the Software without restriction, including without limitation the rights
    00010  *  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    00011  *  copies of the Software, and to permit persons to whom the Software is
    00012  *  furnished to do so, subject to the following conditions:
    00013  *
    00014  *  The above copyright notice and this permission notice shall be included in
    00015  *  all copies or substantial portions of the Software.
    00016  *
    00017  *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    00018  *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    00019  *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    00020  *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    00021  *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    00022  *  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    00023  *  THE SOFTWARE.
    00024  */
    00025 #ifndef _PASSENGER_SESSION_H_
    00026 #define _PASSENGER_SESSION_H_
    00027
    00028 #include <sys/types.h>
    00029 #include <sys/socket.h>
    00030 #include <sys/un.h>
    00031 #include <unistd.h>
    00032 #include <netdb.h>
    00033 #include <cerrno>
    00034 #include <cstring>
    00035
    00036 #include <string>
    00037 #include <vector>
    00038
    00039 #include <boost/shared_ptr.hpp>
    00040 #include <boost/function.hpp>
    00041 #include <oxt/backtrace.hpp>
    00042 #include <oxt/system_calls.hpp>
    00043
    00044 #include "MessageChannel.h"
    00045 #include "StaticString.h"
    00046 #include "Exceptions.h"
    00047 #include "Utils/StrIntUtils.h"
    00048 #include "Utils/IOUtils.h"
    00049
    00050 namespace Passenger {
    00051
    00052 using namespace boost;
    00053 using namespace oxt;
    00054 using namespace std;
    00055
    00056
    00057 /**
    00058  * Represents a single request/response pair of an application process.
    00059  *
    00060  * Session is used to forward a single HTTP request to an application
    00061  * process, and to read back the HTTP response. A Session object is to
    00062  * be used in the following manner:
    00063  *
    00064  *  -# Serialize the HTTP request headers into a format as expected by
    00065  *     sendHeaders(), then send that string by calling sendHeaders().
    00066  *  -# In case of a POST of PUT request, send the HTTP request body by
    00067  *     calling sendBodyBlock(), possibly multiple times.
    00068  *  -# Shutdown the writer end of the session channel (shutdownWriter())
    00069  *     since you're now done sending data.
    00070  *  -# The HTTP response can now be read through the session channel (getStream()).
    00071  *  -# When the HTTP response has been read, the session must be closed.
    00072  *     This is done by destroying the Session object.
    00073  *
    00074  * A usage example is shown in Process::newSession().
    00075  *
    00076  * Session is an abstract base class. Concrete implementations can be found in
    00077  * StandardSession and ApplicationPool::Client::RemoteSession.
    00078  *
    00079  * Session is not guaranteed to be thread-safe.
    00080  */
    00081 class Session {
    00082 protected:
    00083         string detachKey;
    00084         string connectPassword;
    00085         string gupid;
    00086
    00087 public:
    00088         /**
    00089          * Concrete classes might throw arbitrary exceptions.
    00090          */
    00091         virtual ~Session() {}
    00092
    00093         /**
    00094          * Initiate the session by connecting to the associated process.
    00095          * A Session is not usable until it's initiated.
    00096          *
    00097          * @throws SystemException Something went wrong.
    00098          * @throws IOException Something went wrong.
    00099          * @throws boost::thread_interrupted
    00100          */
    00101         virtual void initiate() = 0;
    00102
    00103         /**
    00104          * Returns whether this session has been initiated (that is, whether
    00105          * initiate() had been called in the past).
    00106          */
    00107         virtual bool initiated() const = 0;
    00108
    00109         /**
    00110          * Returns the type of the socket that this session is served from,
    00111          * e.g. "unix" indicating a Unix socket.
    00112          *
    00113          * @post !result.empty()
    00114          */
    00115         virtual string getSocketType() const = 0;
    00116
    00117         /**
    00118          * Returns the address of the socket that this session is served
    00119          * from. This can be a Unix socket filename or a TCP host:port string
    00120          * like "127.0.0.1:1234".
    00121          *
    00122          * @post !result.empty()
    00123          */
    00124         virtual string getSocketName() const = 0;
    00125
    00126         /**
    00127          * Send HTTP request headers to the application. The HTTP headers must be
    00128          * converted into CGI headers, and then encoded into a string that matches this grammar:
    00129          *
    00130            @verbatim
    00131            headers ::= header*
    00132            header ::= name NUL value NUL
    00133            name ::= notnull+
    00134            value ::= notnull+
    00135            notnull ::= "\x01" | "\x02" | "\x02" | ... | "\xFF"
    00136            NUL = "\x00"
    00137            @endverbatim
    00138          *
    00139          * There must be a header with the name "PASSWORD_CONNECT_PASSWORD", and it must
    00140          * have the same value as the string returned by getConnectPassword().
    00141          *
    00142          * This method should be the first one to be called during the lifetime of a Session
    00143          * object, otherwise strange things may happen.
    00144          *
    00145          * @param headers The HTTP request headers, converted into CGI headers and encoded as
    00146          *                a string, according to the description.
    00147          * @param size The size, in bytes, of <tt>headers</tt>.
    00148          * @pre headers != NULL
    00149          * @pre initiated()
    00150          * @throws IOException The I/O channel has already been closed or discarded.
    00151          * @throws SystemException Something went wrong during writing.
    00152          * @throws boost::thread_interrupted
    00153          */
    00154         virtual void sendHeaders(const char *headers, unsigned int size) {
    00155                 TRACE_POINT();
    00156                 int stream = getStream();
    00157                 if (stream == -1) {
    00158                         throw IOException("Cannot write headers to the request handler "
    00159                                 "because the I/O stream has already been closed or discarded.");
    00160                 }
    00161                 try {
    00162                         MessageChannel(stream).writeScalar(headers, size);
    00163                 } catch (SystemException &e) {
    00164                         e.setBriefMessage("An error occured while writing headers "
    00165                                 "to the request handler");
    00166                         throw;
    00167                 }
    00168         }
    00169
    00170         /**
    00171          * Convenience shortcut for sendHeaders(const char *, unsigned int)
    00172          * @param headers The headers
    00173          * @pre initiated()
    00174          * @throws IOException The I/O channel has already been closed or discarded.
    00175          * @throws SystemException Something went wrong during writing.
    00176          * @throws boost::thread_interrupted
    00177          */
    00178         virtual void sendHeaders(const StaticString &headers) {
    00179                 sendHeaders(headers.c_str(), headers.size());
    00180         }
    00181
    00182         /**
    00183          * Send a chunk of HTTP request body data to the application.
    00184          * You can call this method as many times as is required to transfer
    00185          * the entire HTTP request body.
    00186          *
    00187          * This method must only be called after a sendHeaders(), otherwise
    00188          * strange things may happen.
    00189          *
    00190          * @param block A block of HTTP request body data to send.
    00191          * @param size The size, in bytes, of <tt>block</tt>.
    00192          * @pre initiated()
    00193          * @throws IOException The I/O channel has already been closed or discarded.
    00194          * @throws SystemException Something went wrong during writing.
    00195          * @throws boost::thread_interrupted
    00196          */
    00197         virtual void sendBodyBlock(const char *block, unsigned int size) {
    00198                 TRACE_POINT();
    00199                 int stream = getStream();
    00200                 if (stream == -1) {
    00201                         throw IOException("Cannot write request body block to the "
    00202                                 "request handler because the I/O channel has "
    00203                                 "already been closed or discarded.");
    00204                 }
    00205                 try {
    00206                         MessageChannel(stream).writeRaw(block, size);
    00207                 } catch (SystemException &e) {
    00208                         e.setBriefMessage("An error occured while sending the "
    00209                                 "request body to the request handler");
    00210                         throw;
    00211                 }
    00212         }
    00213
    00214         /**
    00215          * Returns this session's channel's file descriptor. This stream is
    00216          * full-duplex, and will be automatically closed upon Session's
    00217          * destruction, unless discardStream() is called.
    00218          *
    00219          * @pre initiated()
    00220          * @returns The file descriptor, or -1 if the I/O channel has already
    00221          *          been closed or discarded.
    00222          */
    00223         virtual int getStream() const = 0;
    00224
    00225         /**
    00226          * Set the timeout value for reading data from the I/O channel.
    00227          * If no data can be read within the timeout period, then the
    00228          * read call will fail with error EAGAIN or EWOULDBLOCK.
    00229          *
    00230          * @pre The I/O channel hasn't been closed or discarded.
    00231          * @pre initiated()
    00232          * @param msec The timeout, in milliseconds. If 0 is given,
    00233          *             there will be no timeout.
    00234          * @throws SystemException Cannot set the timeout.
    00235          */
    00236         virtual void setReaderTimeout(unsigned int msec) = 0;
    00237
    00238         /**
    00239          * Set the timeout value for writing data from the I/O channel.
    00240          * If no data can be written within the timeout period, then the
    00241          * write call will fail with error EAGAIN or EWOULDBLOCK.
    00242          *
    00243          * @pre The I/O channel hasn't been closed or discarded.
    00244          * @pre initiated()
    00245          * @param msec The timeout, in milliseconds. If 0 is given,
    00246          *             there will be no timeout.
    00247          * @throws SystemException Cannot set the timeout.
    00248          */
    00249         virtual void setWriterTimeout(unsigned int msec) = 0;
    00250
    00251         /**
    00252          * Indicate that we don't want to read data anymore from the I/O channel.
    00253          * Calling this method after closeStream()/discardStream() is called will
    00254          * have no effect.
    00255          *
    00256          * @pre initiated()
    00257          * @throws SystemException Something went wrong.
    00258          * @throws boost::thread_interrupted
    00259          */
    00260         virtual void shutdownReader() = 0;
    00261
    00262         /**
    00263          * Indicate that we don't want to write data anymore to the I/O channel.
    00264          * Calling this method after closeStream()/discardStream() is called will
    00265          * have no effect.
    00266          *
    00267          * @pre initiated()
    00268          * @throws SystemException Something went wrong.
    00269          * @throws boost::thread_interrupted
    00270          */
    00271         virtual void shutdownWriter() = 0;
    00272
    00273         /**
    00274          * Close the I/O stream.
    00275          *
    00276          * @throws SystemException Something went wrong.
    00277          * @throws boost::thread_interrupted
    00278          * @pre initiated()
    00279          * @post getStream() == -1
    00280          */
    00281         virtual void closeStream() = 0;
    00282
    00283         /**
    00284          * Discard the I/O channel's file descriptor, so that the destructor
    00285          * won't automatically close it.
    00286          *
    00287          * @pre initiated()
    00288          * @post getStream() == -1
    00289          */
    00290         virtual void discardStream() = 0;
    00291
    00292         /**
    00293          * Get the process ID of the application process that this session
    00294          * belongs to.
    00295          */
    00296         virtual pid_t getPid() const = 0;
    00297
    00298         const string getDetachKey() const {
    00299                 return detachKey;
    00300         }
    00301
    00302         /**
    00303          * Returns this session's process's connect password. This password is
    00304          * guaranteed to be valid ASCII.
    00305          */
    00306         const string getConnectPassword() const {
    00307                 return connectPassword;
    00308         }
    00309
    00310         const string getGupid() const {
    00311                 return gupid;
    00312         }
    00313 };
    00314
    00315 typedef shared_ptr<Session> SessionPtr;
    00316
    00317
    00318 /**
    00319  * A "standard" implementation of Session.
    00320  */
    00321 class StandardSession: public Session {
    00322 public:
    00323         typedef function<void (const StandardSession *)> CloseCallback;
    00324
    00325 protected:
    00326         pid_t pid;
    00327         CloseCallback closeCallback;
    00328         string socketType;
    00329         string socketName;
    00330
    00331         /** The session connection file descriptor. */
    00332         int fd;
    00333         bool isInitiated;
    00334
    00335 public:
    00336         StandardSession(pid_t pid,
    00337                         const CloseCallback &closeCallback,
    00338                         const string &socketType,
    00339                         const string &socketName,
    00340                         const string &detachKey,
    00341                         const string &connectPassword,
    00342                         const string &gupid)
    00343         {
    00344                 TRACE_POINT();
    00345                 if (socketType != "unix" && socketType != "tcp") {
    00346                         throw IOException("Unsupported socket type '" + socketType + "'");
    00347                 }
    00348                 this->pid = pid;
    00349                 this->closeCallback = closeCallback;
    00350                 this->socketType    = socketType;
    00351                 this->socketName    = socketName;
    00352                 this->detachKey     = detachKey;
    00353                 this->connectPassword = connectPassword;
    00354                 this->gupid         = gupid;
    00355                 fd = -1;
    00356                 isInitiated = false;
    00357         }
    00358
    00359         virtual ~StandardSession() {
    00360                 TRACE_POINT();
    00361                 closeStream();
    00362                 if (closeCallback != NULL) {
    00363                         closeCallback(this);
    00364                 }
    00365         }
    00366
    00367         virtual void initiate() {
    00368                 TRACE_POINT();
    00369                 if (socketType == "unix") {
    00370                         fd = connectToUnixServer(socketName.c_str());
    00371                 } else {
    00372                         vector<string> args;
    00373
    00374                         split(socketName, ':', args);
    00375                         if (args.size() != 2 || atoi(args[1]) == 0) {
    00376                                 UPDATE_TRACE_POINT();
    00377                                 throw IOException("Invalid TCP/IP address '" + socketName + "'");
    00378                         }
    00379                         fd = connectToTcpServer(args[0].c_str(), atoi(args[1]));
    00380                 }
    00381                 isInitiated = true;
    00382         }
    00383
    00384         virtual bool initiated() const {
    00385                 return isInitiated;
    00386         }
    00387
    00388         virtual string getSocketType() const {
    00389                 return socketType;
    00390         }
    00391
    00392         virtual string getSocketName() const {
    00393                 return socketName;
    00394         }
    00395
    00396         virtual int getStream() const {
    00397                 return fd;
    00398         }
    00399
    00400         virtual void setReaderTimeout(unsigned int msec) {
    00401                 MessageChannel(fd).setReadTimeout(msec);
    00402         }
    00403
    00404         virtual void setWriterTimeout(unsigned int msec) {
    00405                 MessageChannel(fd).setWriteTimeout(msec);
    00406         }
    00407
    00408         virtual void shutdownReader() {
    00409                 TRACE_POINT();
    00410                 if (fd != -1) {
    00411                         int ret = syscalls::shutdown(fd, SHUT_RD);
    00412                         if (ret == -1) {
    00413                                 int e = errno;
    00414                                 // ENOTCONN is harmless here.
    00415                                 if (e != ENOTCONN) {
    00416                                         throw SystemException("Cannot shutdown the reader stream",
    00417                                                 e);
    00418                                 }
    00419                         }
    00420                 }
    00421         }
    00422
    00423         virtual void shutdownWriter() {
    00424                 TRACE_POINT();
    00425                 if (fd != -1) {
    00426                         int ret = syscalls::shutdown(fd, SHUT_WR);
    00427                         if (ret == -1) {
    00428                                 int e = errno;
    00429                                 // ENOTCONN is harmless here.
    00430                                 if (e != ENOTCONN) {
    00431                                         throw SystemException("Cannot shutdown the writer stream",
    00432                                                 e);
    00433                                 }
    00434                         }
    00435                 }
    00436         }
    00437
    00438         virtual void closeStream() {
    00439                 TRACE_POINT();
    00440                 if (fd != -1) {
    00441                         int ret = syscalls::close(fd);
    00442                         fd = -1;
    00443                         if (ret == -1) {
    00444                                 int e = errno;
    00445                                 if (errno == EIO) {
    00446                                         throw SystemException(
    00447                                                 "A write operation on the session stream failed",
    00448                                                 e);
    00449                                 } else {
    00450                                         throw SystemException(
    00451                                                 "Cannot close the session stream",
    00452                                                 e);
    00453                                 }
    00454                         }
    00455                 }
    00456         }
    00457
    00458         virtual void discardStream() {
    00459                 fd = -1;
    00460         }
    00461
    00462         virtual pid_t getPid() const {
    00463                 return pid;
    00464         }
    00465 };
    00466
    00467
    00468 } // namespace Passenger
    00469
    00470 #endif /* _PASSENGER_SESSION_H_ */