/hphp/runtime/server/fastcgi/fastcgi-session.cpp
C++ | 717 lines | 495 code | 120 blank | 102 comment | 100 complexity | be74c794295ade6caa345411658fbfbf MD5 | raw file
- /*
- +----------------------------------------------------------------------+
- | HipHop for PHP |
- +----------------------------------------------------------------------+
- | Copyright (c) 2010-2015 Facebook, Inc. (http://www.facebook.com) |
- +----------------------------------------------------------------------+
- | This source file is subject to version 3.01 of the PHP license, |
- | that is bundled with this package in the file LICENSE, and is |
- | available through the world-wide-web at the following url: |
- | http://www.php.net/license/3_01.txt |
- | If you did not receive a copy of the PHP license and are unable to |
- | obtain it through the world-wide-web, please send a note to |
- | license@php.net so we can mail you a copy immediately. |
- +----------------------------------------------------------------------+
- */
- #include "hphp/runtime/server/fastcgi/fastcgi-session.h"
- #include "hphp/runtime/server/fastcgi/fastcgi-server.h"
- #include "hphp/util/logger.h"
- #include <folly/Memory.h>
- #include <folly/MoveWrapper.h>
- #include <folly/io/Cursor.h>
- #include <folly/io/IOBuf.h>
- #include <limits>
- namespace HPHP {
- using folly::IOBuf;
- using folly::IOBufQueue;
- using folly::io::Cursor;
- using folly::io::Appender;
- using folly::io::QueueAppender;
- ////////////////////////////////////////////////////////////////////////////////
- namespace fcgi {
- void KVParser::reset() {
- m_phase = Phase::READ_KEY_LENGTH;
- m_readBuf.clear();
- m_keyBuf.clear();
- m_valueBuf.clear();
- }
- std::tuple<
- std::unique_ptr<folly::IOBuf>,
- std::unique_ptr<folly::IOBuf>
- > KVParser::readNext() {
- assert(ready());
- m_phase = Phase::READ_KEY_LENGTH;
- auto key = m_keyBuf.move();
- auto val = m_valueBuf.move();
- process(); // check for more data
- return std::make_tuple(std::move(key), std::move(val));
- }
- bool KVParser::consume(std::unique_ptr<IOBuf> chain) {
- if (chain == nullptr || chain->computeChainDataLength() == 0) {
- if (m_readBuf.chainLength() != 0 || m_phase != Phase::READ_KEY_LENGTH) {
- // Malformed stream
- return false;
- }
- m_phase = Phase::COMPLETE;
- return true;
- }
- // The stream has been restarted
- if (m_phase == Phase::COMPLETE) m_phase = Phase::READ_KEY_LENGTH;
- m_readBuf.append(std::move(chain));
- // Can only process new data if we aren't currently waiting on a call to
- // readNext() to free up the key/value beffers
- if (m_phase != Phase::READY) {
- process();
- }
- return true;
- }
- void KVParser::process() {
- size_t available = m_readBuf.chainLength();
- size_t avail = available;
- Cursor cursor(m_readBuf.front());
- // if we can read a complete key/value then we are ready to be extracted
- if (parseKeyValue(cursor, avail)) {
- m_phase = Phase::READY;
- }
- m_readBuf.split(available - avail);
- }
- bool KVParser::parseKeyValue(Cursor& cursor, size_t& available) {
- if (m_phase == Phase::READ_KEY_LENGTH) {
- if (parseKeyValueLength(cursor, available, m_keyLength)) {
- m_keyLeft = m_keyLength;
- m_phase = Phase::READ_VALUE_LENGTH;
- } else {
- return false;
- }
- }
- if (m_phase == Phase::READ_VALUE_LENGTH) {
- if (parseKeyValueLength(cursor, available, m_valueLength)) {
- m_valueLeft = m_valueLength;
- m_phase = Phase::READ_KEY;
- } else {
- return false;
- }
- }
- if (m_phase == Phase::READ_KEY) {
- if (parseKeyValueContent(cursor, available, m_keyLeft, m_keyBuf)) {
- m_phase = Phase::READ_VALUE;
- } else {
- return false;
- }
- }
- if (m_phase == Phase::READ_VALUE) {
- if (parseKeyValueContent(cursor, available, m_valueLeft, m_valueBuf)) {
- m_phase = Phase::READ_KEY_LENGTH;
- return true;
- } else {
- return false;
- }
- }
- return false;
- }
- bool KVParser::parseKeyValueLength(Cursor& cursor,
- size_t& available,
- size_t& lengthReturn) {
- if (!available) {
- return false;
- }
- auto peeked = cursor.peek();
- if (*peeked.first & 0x80) { // highest bit is set
- if (available < sizeof(uint32_t)) {
- return false;
- }
- lengthReturn = cursor.readBE<uint32_t>();
- lengthReturn &= ~(0x80 << 24);
- available -= sizeof(uint32_t);
- } else {
- lengthReturn = cursor.readBE<uint8_t>();
- available--;
- }
- return true;
- }
- bool KVParser::parseKeyValueContent(Cursor& cursor,
- size_t& available,
- size_t& length,
- IOBufQueue& queue) {
- std::unique_ptr<IOBuf> buf;
- size_t len = cursor.cloneAtMost(buf, length);
- queue.append(std::move(buf));
- assert(length >= len);
- length -= len;
- assert(available >= len);
- available -= len;
- return (length == 0);
- }
- }
- ////////////////////////////////////////////////////////////////////////////////
- FastCGISession::FastCGISession(
- folly::EventBase* evBase,
- JobQueueDispatcher<FastCGIWorker>& dispatcher,
- folly::AsyncSocket::UniquePtr sock,
- const folly::SocketAddress& localAddr,
- const folly::SocketAddress& peerAddr)
- : m_eventBase(evBase)
- , m_dispatcher(dispatcher)
- , m_localAddr(localAddr)
- , m_peerAddr(peerAddr)
- , m_sock(std::move(sock))
- {
- ++m_eventCount; // pending readEOF
- m_sock->setReadCB(this);
- }
- void FastCGISession::timeoutExpired() noexcept {
- // Hard shutdown; socket timed out
- dropConnection();
- }
- void FastCGISession::describe(std::ostream& os) const {
- os << "[peerAddr: " << m_peerAddr
- << ", localAddr: " << m_localAddr
- << ", request_id: " << m_requestId
- << "]";
- }
- bool FastCGISession::isBusy() const {
- // We are busy whenever we are actively serving a request
- return m_requestId != 0;
- }
- void FastCGISession::notifyPendingShutdown() {
- closeWhenIdle();
- }
- void FastCGISession::closeWhenIdle() {
- if (!m_requestId) {
- m_sock->close(); // Flush any pending writes and close, calling close()
- // will immediately call readEOF and prevent any further
- // attempts to write data.
- // readEOF will call shutdown() which may free this out from under us, we
- // could add a DestructorGuard, but we'd only end up calling shutdown()
- // ourselves. Instead we return immediately.
- return;
- }
- m_keepConn = false; // will shutdown when request completes
- }
- void FastCGISession::dropConnection() {
- // Nothing else needs to be placed here. Calling closeWithReset() will cause
- // readEOF to be called immediately which will call shutdown().
- //
- // NB: If there are any pending writes they will all be failed. The last one
- // to fail will delete us.
- m_sock->closeWithReset();
- }
- void FastCGISession::dumpConnectionState(uint8_t loglevel) { /* nop */ }
- ////////////////////////////////////////////////////////////////////////////////
- // Borrowed from proxygen
- const uint32_t k_minReadSize = 1460;
- const uint32_t k_maxReadSize = 4000;
- void FastCGISession::getReadBuffer(void** bufReturn, size_t* lenReturn) {
- std::tie(*bufReturn, *lenReturn) = m_readBuf.preallocate(k_minReadSize,
- k_maxReadSize);
- }
- /*
- * readDataAvailable() - This is the primary entry point for FastCGI records.
- *
- * While processing FastCGI records it's possible that a socket error will
- * cause us to begin destructing, we construct a DestructorGuard and check
- * the value of m_shutdown periodically to guard against deadlock and use-
- * after-free bugs.
- */
- void FastCGISession::readDataAvailable(size_t len) noexcept {
- DestructorGuard dg(this);
- m_readBuf.postallocate(len);
- resetTimeout();
- // If we're shutting down don't process any further requests, we may be freed
- if (m_shutdown) {
- return;
- }
- auto origChain = m_readBuf.front();
- size_t avail = origChain ? origChain->computeChainDataLength() : 0;
- size_t total = avail;
- SCOPE_EXIT {
- m_readBuf.trimStart(total - avail);
- };
- if (!avail) return;
- auto chain = origChain->clone();
- while (!m_shutdown && avail >= sizeof(fcgi::record)) {
- chain->gather(sizeof(fcgi::record));
- auto const rec = reinterpret_cast<const fcgi::record*>(chain->data());
- if (avail < rec->size()) {
- return;
- }
- chain->gather(rec->size());
- avail -= rec->size();
- switch (rec->type) {
- case fcgi::BEGIN_REQUEST:
- onRecord(rec->getTyped<fcgi::begin_record>());
- break;
- case fcgi::ABORT_REQUEST:
- onRecord(rec->getTyped<fcgi::abort_record>());
- break;
- case fcgi::PARAMS:
- onStream(rec->getTyped<fcgi::params_record>(), chain.get());
- break;
- case fcgi::STDIN:
- onStream(rec->getTyped<fcgi::stdin_record>(), chain.get());
- break;
- case fcgi::GET_VALUES:
- onStream(rec->getTyped<fcgi::values_record>(), chain.get());
- break;
- case fcgi::GET_VALUES_RESULT:
- case fcgi::UNKNOWN_TYPE:
- case fcgi::END_REQUEST:
- case fcgi::STDOUT:
- case fcgi::STDERR:
- case fcgi::DATA:
- // Received malformed record data- bail out
- dropConnection();
- break;
- default:
- writeUnknownType(rec->type);
- }
- chain->trimStart(rec->size());
- }
- }
- void FastCGISession::readEOF() noexcept {
- ioStop();
- }
- ////////////////////////////////////////////////////////////////////////////////
- void FastCGISession::readErr(const folly::AsyncSocketException&) noexcept {
- ioStop();
- }
- void FastCGISession::writeErr(size_t,
- const folly::AsyncSocketException&) noexcept {
- ioStop();
- }
- void FastCGISession::writeSuccess() noexcept {
- if (--m_eventCount == 0 && m_shutdown) {
- // If we were terminating and this was the last pending event then trigger
- // the delete.
- destroy();
- }
- }
- ////////////////////////////////////////////////////////////////////////////////
- void FastCGISession::onStdOut(std::unique_ptr<IOBuf> chain) {
- // FastCGITransport doesn't run in the same event base. Calling into internal
- // functions here is unsafe from other threads so we enqueue the work for the
- // event base.
- folly::MoveWrapper<std::unique_ptr<IOBuf>> chain_wrapper(std::move(chain));
- m_eventBase->runInEventBaseThread([this, chain_wrapper]() mutable {
- writeStream(fcgi::STDOUT, std::move(*chain_wrapper));
- });
- }
- void FastCGISession::onComplete() {
- // FastCGITransport doesn't run in the same event base. Calling into internal
- // functions here is unsafe from other threads so we enqueue the work for the
- // event base.
- m_eventBase->runInEventBaseThread([&] {
- if (!m_aborting && !m_shutdown) {
- // If we're aborting we already wrote the end request. If we're shutting
- // down the socket is closed.
- writeEndRequest(m_requestId, 0, fcgi::REQUEST_COMPLETE);
- }
- // Reset state
- m_requestId = 0;
- m_paramsReader.reset();
- m_headersComplete = false;
- m_bodyComplete = false;
- m_transport.reset();
- --m_eventCount; // pending onComplete() received
- // Check if we were waiting to shutdown
- if (m_shutdown && !m_eventCount) {
- destroy();
- return; // not safe to continue after we delete ourselves
- }
- // Check if we were the last request on this channel
- if (!m_keepConn) {
- shutdown();
- return; // cannot continue execution after deleting self
- }
- // Clear the persistence flag
- m_keepConn = false;
- });
- }
- ////////////////////////////////////////////////////////////////////////////////
- void FastCGISession::ioStop() noexcept {
- if (m_transport) {
- // We set m_shutdown here because if the transport reenters and attempts
- // to write we will put the socket in a very bad state and fail all in
- // flight data.
- m_shutdown = true;
- // If the headers have not been fully received we never started the
- // transport and exiting without receiving an onComplete is safe.
- if (m_headersComplete) {
- m_bodyComplete = true;
- m_transport->onBodyComplete();
- }
- }
- // We may have read an EOF because someone deliberately called close() in
- // which case they may call shutdown() or we may already be inside shutdown()
- --m_eventCount;
- shutdown();
- }
- void FastCGISession::shutdown() noexcept {
- DestructorGuard dg(this); // close() may call destroy()
- m_shutdown = true;
- m_keepConn = false;
- // We may have gotten here via close(); if not perform the close ourselves.
- // close() may destroy() us, so we have the destructor guard
- if (m_sock->good()) {
- m_sock->close();
- }
- if (m_eventCount == 0) {
- destroy();
- }
- }
- void FastCGISession::enqueueWrite(std::unique_ptr<IOBuf> chain) {
- if (m_shutdown) {
- // If we're shutting down do not write any more data. Writing data to a
- // socket that has been closed will leave it in a very bad state.
- return;
- }
- ++m_eventCount;
- m_sock->writeChain(this, std::move(chain));
- }
- ////////////////////////////////////////////////////////////////////////////////
- void FastCGISession::onRecordImpl(const fcgi::begin_record* rec) {
- if (rec->requestId == 0) {
- // Garbage record
- dropConnection();
- return;
- }
- if (m_requestId) {
- if (m_requestId == rec->requestId) {
- // Malformed stream
- dropConnection();
- return;
- }
- // Already have an active connection
- writeEndRequest(rec->requestId, 0, fcgi::CANT_MULTIPLEX_CONN);
- return;
- }
- if (rec->role != fcgi::RESPONDER) {
- // Invalid role
- writeEndRequest(rec->requestId, 0, fcgi::UNKNOWN_ROLE);
- return;
- }
- // Until the job actually starts once we receive the headers we don't need
- // to register a pending onComplete()
- m_requestId = rec->requestId;
- m_transport = std::make_shared<FastCGITransport>(this);
- m_paramsReader.reset();
- // Determine if the server needs us to keep the channel open after the
- // request completes.
- m_keepConn = rec->flags & fcgi::KEEP_CONN;
- }
- void FastCGISession::onRecordImpl(const fcgi::abort_record* rec) {
- if (!m_requestId || rec->requestId != m_requestId) {
- // Garbage record
- dropConnection();
- return;
- }
- writeEndRequest(m_requestId, 1, fcgi::REQUEST_COMPLETE);
- m_aborting = true; // don't try to write REQUEST_COMPLETE again
- // There may still be a pending eventCount from an onComplete call from the
- // open tranport. We can't clear it here as there is no way to abort the
- // transport and we need to be around to receive any data it may try to send
- shutdown();
- }
- void FastCGISession::onStream(const fcgi::params_record* rec,
- const IOBuf* chain) {
- if (!m_requestId || rec->requestId != m_requestId || m_headersComplete) {
- // Garbage record
- dropConnection();
- return;
- }
- Cursor cur(chain);
- std::unique_ptr<IOBuf> segment;
- cur.skip(sizeof(fcgi::record));
- cur.cloneAtMost(segment, rec->contentLength);
- if (!m_paramsReader.consume(std::move(segment))) {
- // Malformed stream
- dropConnection();
- }
- while (m_paramsReader.ready()) {
- std::unique_ptr<IOBuf> key, val;
- std::tie(key, val) = m_paramsReader.readNext();
- m_transport->onHeader(std::move(key), std::move(val));
- }
- if (m_paramsReader.done()) {
- // If we've started shutting down then don't start the transport job.
- if (m_shutdown) {
- return;
- }
- m_headersComplete = true;
- m_transport->onHeadersComplete();
- // Now that the job is running we need to wait for a call to onComplete()
- ++m_eventCount;
- // This enqueue call would be safe from any thread because as the
- // JobQueueDispatcher is synchronized
- m_dispatcher.enqueue(std::make_shared<FastCGIJob>(m_transport));
- }
- }
- void FastCGISession::onStream(const fcgi::stdin_record* rec,
- const IOBuf* chain) {
- if (!m_requestId || rec->requestId != m_requestId || m_bodyComplete) {
- // Garbage record
- dropConnection();
- return;
- }
- if (!rec->contentLength) {
- m_bodyComplete = true;
- m_transport->onBodyComplete();
- return;
- }
- Cursor cur(chain);
- std::unique_ptr<IOBuf> segment;
- cur.skip(sizeof(fcgi::record));
- cur.cloneAtMost(segment, rec->contentLength);
- m_transport->onBody(std::move(segment));
- }
- void FastCGISession::onStream(const fcgi::values_record* rec,
- const IOBuf* chain) {
- if (m_requestId != 0) {
- // Garbage record
- dropConnection();
- return;
- }
- Cursor cur(chain);
- std::unique_ptr<IOBuf> segment;
- cur.skip(sizeof(fcgi::record));
- cur.cloneAtMost(segment, rec->contentLength);
- if (!m_capReader.consume(std::move(segment))) {
- // Malformed stream
- dropConnection();
- }
- while (m_capReader.ready()) {
- std::unique_ptr<IOBuf> key, val;
- std::tie(key, val) = m_capReader.readNext();
- size_t key_length = key ? key->computeChainDataLength() : 0;
- Cursor cursor(key.get());
- auto keyStr = cursor.readFixedString(key_length);
- writeCapability(keyStr);
- }
- }
- ////////////////////////////////////////////////////////////////////////////////
- const std::string k_getValueMaxConnKey = "FCGI_MAX_CONNS";
- const std::string k_getValueMaxRequestsKey = "FCGI_MAX_REQS";
- const std::string k_getValueMultiplexConnsKey = "FCGI_MPXS_CONNS";
- void FastCGISession::writeCapability(const std::string& key) {
- std::string value;
- if (key == k_getValueMaxConnKey) {
- value = std::to_string(m_dispatcher.getTargetNumWorkers());
- } else if (key == k_getValueMaxRequestsKey) {
- value = std::to_string(m_dispatcher.getTargetNumWorkers());
- } else if (key == k_getValueMultiplexConnsKey) {
- // multiplexed connections are not implemented
- value = "0";
- } else {
- // No-op we are supposed to ignore the keys that we
- // don't understand.
- return;
- }
- std::unique_ptr<IOBuf> chain;
- fcgi::values_result_record* rec;
- std::tie(chain, rec) = createRecord<fcgi::values_result_record>(
- fcgi::GET_VALUES_RESULT,
- 0, // management stream
- key.size() + value.size() + 2 * sizeof(uint32_t) // size hint
- );
- size_t len = 0;
- Appender cursor(chain.get(), 256);
- // Lengths can be sent as either bytes or double words.
- auto appendLength = [&] (const std::string& lenStr) {
- if (lenStr.size() > 255) {
- len += sizeof(uint32_t);
- cursor.writeBE<uint32_t>(lenStr.size() | (0x80 << 24));
- } else {
- len += sizeof(uint8_t);
- cursor.writeBE<uint8_t>(lenStr.size());
- }
- };
- auto appendData = [&] (const std::string& data) {
- len += data.size();
- cursor.push(reinterpret_cast<const uint8_t*>(data.c_str()), data.size());
- };
- appendLength(key);
- appendLength(value);
- appendData(key);
- appendData(value);
- rec->paddingLength = ((len + 7) & ~7) - len;
- rec->contentLength = len;
- cursor.ensure(rec->paddingLength);
- cursor.append(rec->paddingLength);
- enqueueWrite(std::move(chain));
- }
- void FastCGISession::writeEndRequest(uint16_t request_id,
- uint32_t app_status,
- fcgi::Status proto_status) {
- std::unique_ptr<IOBuf> chain;
- fcgi::end_record* rec;
- std::tie(chain, rec) = createRecord<fcgi::end_record>(
- fcgi::END_REQUEST,
- request_id
- );
- rec->appStatus = app_status;
- rec->protStatus = proto_status;
- enqueueWrite(std::move(chain));
- }
- void FastCGISession::writeUnknownType(fcgi::Type record_type) {
- std::unique_ptr<IOBuf> chain;
- fcgi::unknown_record* rec;
- std::tie(chain, rec) = createRecord<fcgi::unknown_record>(
- fcgi::UNKNOWN_TYPE,
- 0 // management record
- );
- rec->unknownType = record_type;
- enqueueWrite(std::move(chain));
- }
- void FastCGISession::writeStream(fcgi::Type type,
- std::unique_ptr<IOBuf> stream_chain) {
- assert(type == fcgi::STDOUT || type == fcgi::STDERR);
- if (stream_chain == nullptr) {
- return; // Nothing to do.
- }
- IOBufQueue queue(IOBufQueue::cacheChainLength());
- QueueAppender appender(&queue, 256);
- Cursor cursor(stream_chain.get());
- size_t maxChunk = std::numeric_limits<uint16_t>::max();
- size_t available = stream_chain->computeChainDataLength();
- while (available > 0) {
- size_t len = std::min(maxChunk, available);
- size_t pad = ((len + 7) & ~7) - len;
- appender.ensure(sizeof(fcgi::record));
- auto rec = reinterpret_cast<fcgi::record*>(appender.writableData());
- rec->version = fcgi::Version::Current;
- rec->type = type;
- rec->requestId = m_requestId;
- rec->contentLength = len;
- rec->paddingLength = pad;
- appender.append(sizeof(fcgi::record));
- std::unique_ptr<IOBuf> chunk;
- cursor.clone(chunk, len);
- available -= len;
- appender.insert(std::move(chunk));
- appender.ensure(pad);
- appender.append(pad);
- }
- enqueueWrite(queue.move());
- }
- ////////////////////////////////////////////////////////////////////////////////
- }