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