diff options
author | James Cook <james@lindenlab.com> | 2007-01-02 08:33:20 +0000 |
---|---|---|
committer | James Cook <james@lindenlab.com> | 2007-01-02 08:33:20 +0000 |
commit | 420b91db29485df39fd6e724e782c449158811cb (patch) | |
tree | b471a94563af914d3ed3edd3e856d21cb1b69945 /indra/llmessage/lliohttpserver.cpp |
Print done when done.
Diffstat (limited to 'indra/llmessage/lliohttpserver.cpp')
-rw-r--r-- | indra/llmessage/lliohttpserver.cpp | 833 |
1 files changed, 833 insertions, 0 deletions
diff --git a/indra/llmessage/lliohttpserver.cpp b/indra/llmessage/lliohttpserver.cpp new file mode 100644 index 0000000000..c1a9bc442e --- /dev/null +++ b/indra/llmessage/lliohttpserver.cpp @@ -0,0 +1,833 @@ +/** + * @file lliohttpserver.cpp + * @author Phoenix + * @date 2005-10-05 + * @brief Implementation of the http server classes + * + * Copyright (c) 2005-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" +#include "lliohttpserver.h" + +#include "boost/tokenizer.hpp" + +#include "llapr.h" +#include "llbuffer.h" +#include "llbufferstream.h" +#include "llhttpnode.h" +#include "lliopipe.h" +#include "lliosocket.h" +#include "llioutil.h" +#include "llmemtype.h" +#include "llmemorystream.h" +#include "llpumpio.h" +#include "llsd.h" +#include "llsdserialize_xml.h" +#include "llstl.h" + +static const char HTTP_VERSION_STR[] = "HTTP/1.0"; +static const std::string CONTEXT_REQUEST("request"); +static const std::string HTTP_VERB_GET("GET"); +static const std::string HTTP_VERB_PUT("PUT"); +static const std::string HTTP_VERB_POST("POST"); +static const std::string HTTP_VERB_DELETE("DELETE"); + + +class LLHTTPPipe : public LLIOPipe +{ +public: + LLHTTPPipe(const LLHTTPNode& node) + : mNode(node), mResponse(NULL), mState(STATE_INVOKE), mChainLock(0), mStatusCode(0) + { } + virtual ~LLHTTPPipe() + { + if (mResponse.notNull()) + { + mResponse->nullPipe(); + } + } + +private: + // LLIOPipe API implementation. + virtual EStatus process_impl( + const LLChannelDescriptors& channels, + LLIOPipe::buffer_ptr_t& buffer, + bool& eos, + LLSD& context, + LLPumpIO* pump); + + const LLHTTPNode& mNode; + + class Response : public LLHTTPNode::Response + { + public: + + static LLPointer<Response> create(LLHTTPPipe* pipe); + virtual ~Response(); + + // from LLHTTPNode::Response + virtual void result(const LLSD&); + virtual void status(S32 code, const std::string& message); + + void nullPipe(); + + private: + Response() {;} // Must be accessed through LLPointer. + LLHTTPPipe* mPipe; + }; + friend class Response; + + LLPointer<Response> mResponse; + + enum State + { + STATE_INVOKE, + STATE_DELAYED, + STATE_LOCKED, + STATE_GOOD_RESULT, + STATE_STATUS_RESULT + }; + State mState; + + S32 mChainLock; + LLPumpIO* mLockedPump; + + void lockChain(LLPumpIO*); + void unlockChain(); + + LLSD mGoodResult; + S32 mStatusCode; + std::string mStatusMessage; +}; + +LLIOPipe::EStatus LLHTTPPipe::process_impl( + const LLChannelDescriptors& channels, + buffer_ptr_t& buffer, + bool& eos, + LLSD& context, + LLPumpIO* pump) +{ + PUMP_DEBUG; + lldebugs << "LLSDHTTPServer::process_impl" << llendl; + + // Once we have all the data, We need to read the sd on + // the the in channel, and respond on the out channel + + if(!eos) return STATUS_BREAK; + if(!pump || !buffer) return STATUS_PRECONDITION_NOT_MET; + + PUMP_DEBUG; + if (mState == STATE_INVOKE) + { + PUMP_DEBUG; + mState = STATE_DELAYED; + // assume deferred unless mResponse does otherwise + mResponse = Response::create(this); + + // TODO: Babbage: Parameterize parser? + LLBufferStream istr(channels, buffer.get()); + + std::string verb = context[CONTEXT_REQUEST]["verb"]; + if(verb == HTTP_VERB_GET) + { + mNode.get(LLHTTPNode::ResponsePtr(mResponse), context); + } + else if(verb == HTTP_VERB_PUT) + { + LLSD input; + LLSDSerialize::fromXML(input, istr); + + mNode.put(LLHTTPNode::ResponsePtr(mResponse), context, input); + } + else if(verb == HTTP_VERB_POST) + { + LLSD input; + LLSDSerialize::fromXML(input, istr); + + mNode.post(LLHTTPNode::ResponsePtr(mResponse), context, input); + } + else if(verb == HTTP_VERB_DELETE) + { + mNode.del(LLHTTPNode::ResponsePtr(mResponse), context); + } + else + { + mResponse->methodNotAllowed(); + } + + // Log Internal Server Errors + if(mStatusCode == 500) + { + llwarns << "LLHTTPPipe::process_impl:500:Internal Server Error" + << llendl; + } + } + + PUMP_DEBUG; + switch (mState) + { + case STATE_DELAYED: + lockChain(pump); + mState = STATE_LOCKED; + return STATUS_BREAK; + + case STATE_LOCKED: + // should never ever happen! + return STATUS_ERROR; + + case STATE_GOOD_RESULT: + { + context["response"]["contentType"] = "application/xml"; + LLBufferStream ostr(channels, buffer.get()); + LLSDSerialize::toXML(mGoodResult, ostr); + + return STATUS_DONE; + } + + case STATE_STATUS_RESULT: + { + context["response"]["contentType"] = "text/plain"; + context["response"]["statusCode"] = mStatusCode; + context["response"]["statusMessage"] = mStatusMessage; + LLBufferStream ostr(channels, buffer.get()); + ostr << mStatusMessage << std::ends; + + return STATUS_DONE; + } + default: + llwarns << "LLHTTPPipe::process_impl: unexpected state " + << mState << llendl; + + return STATUS_BREAK; + } +// PUMP_DEBUG; // unreachable +} + +LLPointer<LLHTTPPipe::Response> LLHTTPPipe::Response::create(LLHTTPPipe* pipe) +{ + LLPointer<Response> result = new Response(); + result->mPipe = pipe; + return result; +} + +// virtual +LLHTTPPipe::Response::~Response() +{ +} + +void LLHTTPPipe::Response::nullPipe() +{ + mPipe = NULL; +} + +// virtual +void LLHTTPPipe::Response::result(const LLSD& r) +{ + if(! mPipe) + { + llwarns << "LLHTTPPipe::Response::result: NULL pipe" << llendl; + return; + } + + mPipe->mStatusCode = 200; + mPipe->mStatusMessage = "OK"; + mPipe->mGoodResult = r; + mPipe->mState = STATE_GOOD_RESULT; + mPipe->unlockChain(); +} + +// virtual +void LLHTTPPipe::Response::status(S32 code, const std::string& message) +{ + if(! mPipe) + { + llwarns << "LLHTTPPipe::Response::status: NULL pipe" << llendl; + return; + } + + mPipe->mStatusCode = code; + mPipe->mStatusMessage = message; + mPipe->mState = STATE_STATUS_RESULT; + mPipe->unlockChain(); +} + +void LLHTTPPipe::lockChain(LLPumpIO* pump) +{ + if (mChainLock != 0) { return; } + + mLockedPump = pump; + mChainLock = pump->setLock(); +} + +void LLHTTPPipe::unlockChain() +{ + if (mChainLock == 0) { return; } + + mLockedPump->clearLock(mChainLock); + mLockedPump = NULL; + mChainLock = 0; +} + + + +/** + * @class LLHTTPResponseHeader + * @brief Class which correctly builds HTTP headers on a pipe + * @see LLIOPipe + * + * An instance of this class can be placed in a chain where it will + * wait for an end of stream. Once it gets that, it will count the + * bytes on CHANNEL_OUT (or the size of the buffer in io pipe versions + * prior to 2) prepend that data to the request in an HTTP format, and + * supply all normal HTTP response headers. + */ +class LLHTTPResponseHeader : public LLIOPipe +{ +public: + LLHTTPResponseHeader() {} + virtual ~LLHTTPResponseHeader() {} + +protected: + /* @name LLIOPipe virtual implementations + */ + //@{ + /** + * @brief Process the data in buffer + */ + EStatus process_impl( + const LLChannelDescriptors& channels, + buffer_ptr_t& buffer, + bool& eos, + LLSD& context, + LLPumpIO* pump); + //@} + +protected: + S32 mCode; +}; + + +/** + * LLHTTPResponseHeader + */ +// virtual +LLIOPipe::EStatus LLHTTPResponseHeader::process_impl( + const LLChannelDescriptors& channels, + buffer_ptr_t& buffer, + bool& eos, + LLSD& context, + LLPumpIO* pump) +{ + PUMP_DEBUG; + LLMemType m1(LLMemType::MTYPE_IO_HTTP_SERVER); + if(eos) + { + PUMP_DEBUG; + //mGotEOS = true; + std::ostringstream ostr; + std::string message = context["response"]["statusMessage"]; + + int code = context["response"]["statusCode"]; + if (code < 200) + { + code = 200; + message = "OK"; + } + + ostr << HTTP_VERSION_STR << " " << code << " " << message << "\r\n"; + + std::string type = context["response"]["contentType"].asString(); + if (!type.empty()) + { + ostr << "Content-Type: " << type << "\r\n"; + } + S32 content_length = buffer->countAfter(channels.in(), NULL); + if(0 < content_length) + { + ostr << "Content-Length: " << content_length << "\r\n"; + } + ostr << "\r\n"; + + LLChangeChannel change(channels.in(), channels.out()); + std::for_each(buffer->beginSegment(), buffer->endSegment(), change); + std::string header = ostr.str(); + buffer->prepend(channels.out(), (U8*)header.c_str(), header.size()); + PUMP_DEBUG; + return STATUS_DONE; + } + PUMP_DEBUG; + return STATUS_OK; +} + + + +/** + * @class LLHTTPResponder + * @brief This class + * @see LLIOPipe + * + * <b>NOTE:</b> You should not need to create or use one of these, the + * details are handled by the HTTPResponseFactory. + */ +class LLHTTPResponder : public LLIOPipe +{ +public: + LLHTTPResponder(const LLHTTPNode& tree); + ~LLHTTPResponder(); + +protected: + /** + * @brief Read data off of CHANNEL_IN keeping track of last read position. + * + * This is a quick little hack to read headers. It is not IO + * optimal, but it makes it easier for me to implement the header + * parsing. Plus, there should never be more than a few headers. + * This method will tend to read more than necessary, find the + * newline, make the front part of dest look like a c string, and + * move the read head back to where the newline was found. Thus, + * the next read will pick up on the next line. + * @param channel The channel to read in the buffer + * @param buffer The heap array of processed data + * @param dest Destination for the data to be read + * @param[in,out] len <b>in</b> The size of the buffer. <b>out</b> how + * much was read. This value is not useful for determining where to + * seek orfor string assignment. + * @returns Returns true if a line was found. + */ + bool readLine( + const LLChannelDescriptors& channels, + buffer_ptr_t buffer, + U8* dest, + S32& len); + + /** + * @brief Mark the request as bad, and handle appropriately + * + * @param channels The channels to use in the buffer. + * @param buffer The heap array of processed data. + */ + void markBad(const LLChannelDescriptors& channels, buffer_ptr_t buffer); + +protected: + /* @name LLIOPipe virtual implementations + */ + //@{ + /** + * @brief Process the data in buffer + */ + EStatus process_impl( + const LLChannelDescriptors& channels, + buffer_ptr_t& buffer, + bool& eos, + LLSD& context, + LLPumpIO* pump); + //@} + +protected: + enum EState + { + STATE_NOTHING, + STATE_READING_HEADERS, + STATE_LOOKING_FOR_EOS, + STATE_DONE, + STATE_SHORT_CIRCUIT + }; + + EState mState; + U8* mLastRead; + std::string mVerb; + std::string mAbsPathAndQuery; + std::string mPath; + std::string mQuery; + std::string mVersion; + S32 mContentLength; + + // handle the urls + const LLHTTPNode& mRootNode; +}; + +LLHTTPResponder::LLHTTPResponder(const LLHTTPNode& tree) : + mState(STATE_NOTHING), + mLastRead(NULL), + mContentLength(0), + mRootNode(tree) +{ + LLMemType m1(LLMemType::MTYPE_IO_HTTP_SERVER); +} + +// virtual +LLHTTPResponder::~LLHTTPResponder() +{ + LLMemType m1(LLMemType::MTYPE_IO_HTTP_SERVER); + //lldebugs << "destroying LLHTTPResponder" << llendl; +} + +bool LLHTTPResponder::readLine( + const LLChannelDescriptors& channels, + buffer_ptr_t buffer, + U8* dest, + S32& len) +{ + LLMemType m1(LLMemType::MTYPE_IO_HTTP_SERVER); + --len; + U8* last = buffer->readAfter(channels.in(), mLastRead, dest, len); + dest[len] = '\0'; + U8* newline = (U8*)strchr((char*)dest, '\n'); + if(!newline) + { + if(len) + { + lldebugs << "readLine failed - too long maybe?" << llendl; + markBad(channels, buffer); + } + return false; + } + S32 offset = -((len - 1) - (newline - dest)); + ++newline; + *newline = '\0'; + mLastRead = buffer->seek(channels.in(), last, offset); + return true; +} + +void LLHTTPResponder::markBad( + const LLChannelDescriptors& channels, + buffer_ptr_t buffer) +{ + LLMemType m1(LLMemType::MTYPE_IO_HTTP_SERVER); + mState = STATE_SHORT_CIRCUIT; + LLBufferStream out(channels, buffer.get()); + out << HTTP_VERSION_STR << " 400 Bad Request\r\n\r\n<html>\n" + << "<title>Bad Request</title>\n<body>\nBad Request.\n" + << "</body>\n</html>\n"; +} + +// virtual +LLIOPipe::EStatus LLHTTPResponder::process_impl( + const LLChannelDescriptors& channels, + buffer_ptr_t& buffer, + bool& eos, + LLSD& context, + LLPumpIO* pump) +{ + PUMP_DEBUG; + LLMemType m1(LLMemType::MTYPE_IO_HTTP_SERVER); + LLIOPipe::EStatus status = STATUS_OK; + + // parsing headers + if((STATE_NOTHING == mState) || (STATE_READING_HEADERS == mState)) + { + PUMP_DEBUG; + status = STATUS_BREAK; + mState = STATE_READING_HEADERS; + const S32 HEADER_BUFFER_SIZE = 1024; + char buf[HEADER_BUFFER_SIZE + 1]; /*Flawfinder: ignore*/ + S32 len = HEADER_BUFFER_SIZE; + +#if 0 + if(true) + { + LLBufferArray::segment_iterator_t seg_iter = buffer->beginSegment(); + char buf[1024]; /*Flawfinder: ignore*/ + while(seg_iter != buffer->endSegment()) + { + memcpy(buf, (*seg_iter).data(), (*seg_iter).size()); /*Flawfinder: ignore*/ + buf[(*seg_iter).size()] = '\0'; + llinfos << (*seg_iter).getChannel() << ": " << buf + << llendl; + ++seg_iter; + } + } +#endif + + PUMP_DEBUG; + if(readLine(channels, buffer, (U8*)buf, len)) + { + bool read_next_line = false; + bool parse_all = true; + if(mVerb.empty()) + { + read_next_line = true; + LLMemoryStream header((U8*)buf, len); + header >> mVerb; + + if((HTTP_VERB_GET == mVerb) + || (HTTP_VERB_POST == mVerb) + || (HTTP_VERB_PUT == mVerb) + || (HTTP_VERB_DELETE == mVerb)) + { + header >> mAbsPathAndQuery; + header >> mVersion; + + lldebugs << "http request: " + << mVerb + << " " << mAbsPathAndQuery + << " " << mVersion << llendl; + + std::string::size_type delimiter + = mAbsPathAndQuery.find('?'); + if (delimiter == std::string::npos) + { + mPath = mAbsPathAndQuery; + mQuery = ""; + } + else + { + mPath = mAbsPathAndQuery.substr(0, delimiter); + mQuery = mAbsPathAndQuery.substr(delimiter+1); + } + + if(!mAbsPathAndQuery.empty()) + { + if(mVersion.empty()) + { + // simple request. + parse_all = false; + mState = STATE_DONE; + mVersion.assign("HTTP/1.0"); + } + } + } + else + { + read_next_line = false; + parse_all = false; + lldebugs << "unknown http verb: " << mVerb << llendl; + markBad(channels, buffer); + } + } + if(parse_all) + { + bool keep_parsing = true; + while(keep_parsing) + { + if(read_next_line) + { + len = HEADER_BUFFER_SIZE; + readLine(channels, buffer, (U8*)buf, len); + } + if(0 == len) + { + return status; + } + if(buf[0] == '\r' && buf[1] == '\n') + { + // end-o-headers + keep_parsing = false; + mState = STATE_LOOKING_FOR_EOS; + break; + } + char* pos_colon = strchr(buf, ':'); + if(NULL == pos_colon) + { + keep_parsing = false; + lldebugs << "bad header: " << buf << llendl; + markBad(channels, buffer); + break; + } + // we've found a header + read_next_line = true; + std::string name(buf, pos_colon - buf); + std::string value(pos_colon + 2); + LLString::toLower(name); + if("content-length" == name) + { + lldebugs << "Content-Length: " << value << llendl; + mContentLength = atoi(value.c_str()); + } + } + } + } + } + + PUMP_DEBUG; + // look for the end of stream based on + if(STATE_LOOKING_FOR_EOS == mState) + { + if(0 == mContentLength) + { + mState = STATE_DONE; + } + else if(buffer->countAfter(channels.in(), mLastRead) >= mContentLength) + { + mState = STATE_DONE; + } + // else more bytes should be coming. + } + + PUMP_DEBUG; + if(STATE_DONE == mState) + { + // hey, hey, we should have everything now, so we pass it to + // a content handler. + context[CONTEXT_REQUEST]["verb"] = mVerb; + const LLHTTPNode* node = mRootNode.traverse(mPath, context); + if(node) + { + llinfos << "LLHTTPResponder::process_impl found node for " + << mAbsPathAndQuery << llendl; + + // Copy everything after mLast read to the out. + LLBufferArray::segment_iterator_t seg_iter; + seg_iter = buffer->splitAfter(mLastRead); + if(seg_iter != buffer->endSegment()) + { + LLChangeChannel change(channels.in(), channels.out()); + ++seg_iter; + std::for_each(seg_iter, buffer->endSegment(), change); + +#if 0 + seg_iter = buffer->beginSegment(); + char buf[1024]; /*Flawfinder: ignore*/ + while(seg_iter != buffer->endSegment()) + { + memcpy(buf, (*seg_iter).data(), (*seg_iter).size()); /*Flawfinder: ignore*/ + buf[(*seg_iter).size()] = '\0'; + llinfos << (*seg_iter).getChannel() << ": " << buf + << llendl; + ++seg_iter; + } +#endif + } + + // + // *FIX: get rid of extra bytes off the end + // + + // Set up a chain which will prepend a content length and + // HTTP headers. + LLPumpIO::chain_t chain; + chain.push_back(LLIOPipe::ptr_t(new LLIOFlush)); + context[CONTEXT_REQUEST]["path"] = mPath; + context[CONTEXT_REQUEST]["query-string"] = mQuery; + + const LLChainIOFactory* protocolHandler + = node->getProtocolHandler(); + if (protocolHandler) + { + protocolHandler->build(chain, context); + } + else + { + // this is a simple LLHTTPNode, so use LLHTTPPipe + chain.push_back(LLIOPipe::ptr_t(new LLHTTPPipe(*node))); + } + + // Add the header - which needs to have the same + // channel information as the link before it since it + // is part of the response. + LLIOPipe* header = new LLHTTPResponseHeader; + chain.push_back(LLIOPipe::ptr_t(header)); + + // We need to copy all of the pipes _after_ this so + // that the response goes out correctly. + LLPumpIO::links_t current_links; + pump->copyCurrentLinkInfo(current_links); + LLPumpIO::links_t::iterator link_iter = current_links.begin(); + LLPumpIO::links_t::iterator links_end = current_links.end(); + bool after_this = false; + for(; link_iter < links_end; ++link_iter) + { + if(after_this) + { + chain.push_back((*link_iter).mPipe); + } + else if(this == (*link_iter).mPipe.get()) + { + after_this = true; + } + } + + // Do the final build of the chain, and send it on + // it's way. + LLChannelDescriptors chnl = channels; + LLPumpIO::LLLinkInfo link; + LLPumpIO::links_t links; + LLPumpIO::chain_t::iterator it = chain.begin(); + LLPumpIO::chain_t::iterator end = chain.end(); + while(it != end) + { + link.mPipe = *it; + link.mChannels = chnl; + links.push_back(link); + chnl = LLBufferArray::makeChannelConsumer(chnl); + ++it; + } + pump->addChain( + links, + buffer, + context, + DEFAULT_CHAIN_EXPIRY_SECS); + + status = STATUS_STOP; + } + else + { + llinfos << "LLHTTPResponder::process_impl didn't find a node for " + << mAbsPathAndQuery << llendl; + LLBufferStream str(channels, buffer.get()); + mState = STATE_SHORT_CIRCUIT; + str << HTTP_VERSION_STR << " 404 Not Found\r\n\r\n<html>\n" + << "<title>Not Found</title>\n<body>\nNode '" << mAbsPathAndQuery + << "' not found.\n</body>\n</html>\n"; + } + } + + if(STATE_SHORT_CIRCUIT == mState) + { + //status = mNext->process(buffer, true, pump, context); + status = STATUS_DONE; + } + PUMP_DEBUG; + return status; +} + + + +void LLCreateHTTPPipe(LLPumpIO::chain_t& chain, const LLHTTPNode& root) +{ + chain.push_back(LLIOPipe::ptr_t(new LLHTTPResponder(root))); +} + + +class LLHTTPResponseFactory : public LLChainIOFactory +{ +public: + bool build(LLPumpIO::chain_t& chain, LLSD ctx) const + { + LLCreateHTTPPipe(chain, mTree); + return true; + } + + LLHTTPNode& getRootNode() { return mTree; } + +private: + LLHTTPNode mTree; +}; + + +LLHTTPNode& LLCreateHTTPServer( + apr_pool_t* pool, LLPumpIO& pump, U16 port) +{ + LLSocket::ptr_t socket = LLSocket::create( + pool, + LLSocket::STREAM_TCP, + port); + if(!socket) + { + llerrs << "Unable to initialize socket" << llendl; + } + + LLHTTPResponseFactory* factory = new LLHTTPResponseFactory; + boost::shared_ptr<LLChainIOFactory> factory_ptr(factory); + + LLIOServerSocket* server = new LLIOServerSocket(pool, socket, factory_ptr); + + LLPumpIO::chain_t chain; + chain.push_back(LLIOPipe::ptr_t(server)); + pump.addChain(chain, NEVER_CHAIN_EXPIRY_SECS); + + return factory->getRootNode(); +} + |