view deps/Platinum/Source/Core/PltHttpServerTask.cpp @ 0:3425707ddbf6

Initial import (hopefully this mercurial stuff works...)
author fraserofthenight
date Mon, 06 Jul 2009 08:06:28 -0700
parents
children
line wrap: on
line source

/*****************************************************************
|
|   Platinum - HTTP Server Tasks
|
| Copyright (c) 2004-2008, Plutinosoft, LLC.
| All rights reserved.
| http://www.plutinosoft.com
|
| This program is free software; you can redistribute it and/or
| modify it under the terms of the GNU General Public License
| as published by the Free Software Foundation; either version 2
| of the License, or (at your option) any later version.
|
| OEMs, ISVs, VARs and other distributors that combine and 
| distribute commercially licensed software with Platinum software
| and do not wish to distribute the source code for the commercially
| licensed software under version 2, or (at your option) any later
| version, of the GNU General Public License (the "GPL") must enter
| into a commercial license agreement with Plutinosoft, LLC.
| 
| This program is distributed in the hope that it will be useful,
| but WITHOUT ANY WARRANTY; without even the implied warranty of
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
| GNU General Public License for more details.
|
| You should have received a copy of the GNU General Public License
| along with this program; see the file LICENSE.txt. If not, write to
| the Free Software Foundation, Inc., 
| 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
| http://www.gnu.org/licenses/gpl-2.0.html
|
****************************************************************/

/*----------------------------------------------------------------------
|   includes
+---------------------------------------------------------------------*/
#include "PltHttpServerTask.h"
#include "PltHttp.h"
#include "PltVersion.h"

NPT_SET_LOCAL_LOGGER("platinum.core.http.servertask")

/*----------------------------------------------------------------------
|   external references
+---------------------------------------------------------------------*/
extern NPT_String HttpServerHeader;

/*----------------------------------------------------------------------
|   PLT_HttpServerSocketTask::PLT_HttpServerSocketTask
+---------------------------------------------------------------------*/
PLT_HttpServerSocketTask::PLT_HttpServerSocketTask(NPT_Socket* socket, 
                                                   bool        stay_alive_forever) :
    m_Socket(socket),
    m_StayAliveForever(stay_alive_forever)
{
    socket->SetReadTimeout(30000);
    socket->SetWriteTimeout(30000);
}

/*----------------------------------------------------------------------
|   PLT_HttpServerSocketTask::~PLT_HttpServerSocketTask
+---------------------------------------------------------------------*/
PLT_HttpServerSocketTask::~PLT_HttpServerSocketTask() 
{
    delete m_Socket;
}

/*----------------------------------------------------------------------
|   PLT_HttpServerSocketTask::DoRun
+---------------------------------------------------------------------*/
void
PLT_HttpServerSocketTask::DoRun()
{
    NPT_BufferedInputStreamReference buffered_input_stream;
    NPT_HttpRequestContext           context;
    NPT_Result                       res = NPT_SUCCESS;
    bool                             headers_only;
    bool                             keep_alive = false;

    // create a buffered input stream to parse http request
    // as it comes
    NPT_InputStreamReference input_stream;
    NPT_CHECK_LABEL_SEVERE(GetInputStream(input_stream), done);
    buffered_input_stream = new NPT_BufferedInputStream(input_stream);

    while (!IsAborting(0)) {
        NPT_HttpRequest*  request = NULL;
        NPT_HttpResponse* response = NULL;

        // reset keep-alive in case of failure
        keep_alive = false;

        // wait for a request
        res = Read(buffered_input_stream, request, &context);
        if (NPT_FAILED(res) || (request == NULL)) goto cleanup;

        // callback to process request and get back a response
        headers_only = false;
        res = ProcessRequest(*request, context, response, headers_only);
        if (NPT_FAILED(res) || (response == NULL)) goto cleanup;

        // send back response
        keep_alive = PLT_HttpHelper::IsConnectionKeepAlive(*request);
        res = Write(response, keep_alive, headers_only);

cleanup:
        // cleanup
        delete request;
        delete response;

        if (!keep_alive && !m_StayAliveForever) {
            // if we were to support persistent connections 
            // we would stop only if res would be a failure
            // (like a timeout or a read/write error)
            goto done;
        }
    }

done:
    return;
}

/*----------------------------------------------------------------------
|   PLT_HttpServerSocketTask::GetInputStream
+---------------------------------------------------------------------*/
NPT_Result
PLT_HttpServerSocketTask::GetInputStream(NPT_InputStreamReference& stream)
{
    return m_Socket->GetInputStream(stream);
}

/*----------------------------------------------------------------------
|   PLT_HttpServerSocketTask::GetInfo
+---------------------------------------------------------------------*/
NPT_Result
PLT_HttpServerSocketTask::GetInfo(NPT_SocketInfo& info)
{
    return m_Socket->GetInfo(info);
}

/*----------------------------------------------------------------------
|   PLT_HttpServerSocketTask::Read
+---------------------------------------------------------------------*/
NPT_Result
PLT_HttpServerSocketTask::Read(NPT_BufferedInputStreamReference& buffered_input_stream, 
                               NPT_HttpRequest*&                 request,
                               NPT_HttpRequestContext*           context) 
{
    NPT_SocketInfo info;
    GetInfo(info);

    if (context) {
        // extract socket info
        context->SetLocalAddress(info.local_address);
        context->SetRemoteAddress(info.remote_address);
    }

    // parse request
    NPT_Result res = NPT_HttpRequest::Parse(*buffered_input_stream, &info.local_address, request);
    if (NPT_FAILED(res) || !request) {
        // only log if not timeout
        res = NPT_FAILED(res)?res:NPT_FAILURE;
        if (res != NPT_ERROR_TIMEOUT && res != NPT_ERROR_EOS) NPT_CHECK_WARNING(res);
        return res;
    }

    // read socket info again to refresh the remote address in case it was a udp socket
    GetInfo(info);
    if (context) {
        context->SetLocalAddress(info.local_address);
        context->SetRemoteAddress(info.remote_address);
    }

    // return right away if no body is expected
    if (request->GetMethod() == NPT_HTTP_METHOD_GET || 
        request->GetMethod() == NPT_HTTP_METHOD_HEAD) 
        return NPT_SUCCESS;

    // create an entity
    NPT_HttpEntity* request_entity = new NPT_HttpEntity(request->GetHeaders());
    request->SetEntity(request_entity);

    // buffer body now if any
    if (request_entity->GetContentLength() > 0) {
        // unbuffer the stream
        buffered_input_stream->SetBufferSize(0);

        NPT_MemoryStream* body_stream = new NPT_MemoryStream();
        NPT_CHECK_SEVERE(NPT_StreamToStreamCopy(
            *buffered_input_stream.AsPointer(), 
            *body_stream, 
            0, 
            request_entity->GetContentLength()));

        // set entity body
        request_entity->SetInputStream((NPT_InputStreamReference)body_stream);

        // rebuffer the stream
        buffered_input_stream->SetBufferSize(NPT_BUFFERED_BYTE_STREAM_DEFAULT_SIZE);
    }

    return NPT_SUCCESS;
}

/*----------------------------------------------------------------------
|   PLT_HttpServerSocketTask::Write
+---------------------------------------------------------------------*/
NPT_Result
PLT_HttpServerSocketTask::Write(NPT_HttpResponse* response, 
                                bool&             keep_alive, 
                                bool              headers_only /* = false */) 
{
    // add any headers that may be missing
    NPT_HttpHeaders& headers = response->GetHeaders();
    const NPT_String* value = headers.GetHeaderValue(NPT_HTTP_HEADER_CONNECTION);
    if (value) {
        // only replace Connection header if already set and keep_alive passed not allowed
        if (!keep_alive) {
            headers.SetHeader(NPT_HTTP_HEADER_CONNECTION,  "close"); // override
        } else {
            keep_alive =  value->Compare("keep-alive") == 0; // keep-alive ok but use whatever was set
        }
    } else {
        headers.SetHeader(NPT_HTTP_HEADER_CONNECTION, keep_alive?"keep-alive":"close");
    }

    // set user agent
    headers.SetHeader(NPT_HTTP_HEADER_SERVER, 
                      NPT_HttpServer::m_ServerHeader, false); // set but don't replace
                      
    // get the response entity to set additional headers
    NPT_HttpEntity* entity = response->GetEntity();
    if (entity) {
        if (entity->HasContentLength()) {
            // content length
            headers.SetHeader(NPT_HTTP_HEADER_CONTENT_LENGTH, 
                NPT_String::FromIntegerU(entity->GetContentLength()));
        }

        // content type
        NPT_String content_type = entity->GetContentType();
        if (!content_type.IsEmpty()) {
            headers.SetHeader(NPT_HTTP_HEADER_CONTENT_TYPE, content_type);
        }

        // content encoding
        NPT_String content_encoding = entity->GetContentEncoding();
        if (!content_encoding.IsEmpty()) {
            headers.SetHeader(NPT_HTTP_HEADER_CONTENT_ENCODING, content_encoding);
        }
    } else {
        // force content length to 0 is there is no message body
        headers.SetHeader(NPT_HTTP_HEADER_CONTENT_LENGTH, "0");
    }

    //headers.SetHeader("DATE", "Wed, 13 Feb 2008 22:32:57 GMT");
    //headers.SetHeader("Accept-Ranges", "bytes");

    NPT_LOG_FINE("PLT_HttpServerTask Sending response:");
    PLT_LOG_HTTP_MESSAGE(NPT_LOG_LEVEL_FINE, response);

    // get the socket stream to send the request
    NPT_OutputStreamReference output_stream;
    NPT_CHECK_SEVERE(m_Socket->GetOutputStream(output_stream));

    // create a memory stream to buffer the headers
    NPT_MemoryStream header_stream;
    
    // emit the response headers into the header buffer
    response->Emit(header_stream);

    // send the headers
    NPT_CHECK_SEVERE(output_stream->WriteFully(header_stream.GetData(), header_stream.GetDataSize()));

    // send response body if any
    if (!headers_only && entity) {
        NPT_InputStreamReference body_stream;
        entity->GetInputStream(body_stream);

        if (!body_stream.IsNull()) {
            NPT_CHECK_SEVERE(NPT_StreamToStreamCopy(
                *body_stream.AsPointer(), 
                *output_stream.AsPointer(),
                0,
                entity->GetContentLength()));
        }
    }

    // flush the output stream so that everything is sent to the server
    output_stream->Flush();

    return NPT_SUCCESS;
}