Source

Magnum / source / libs / magnum_net_http / source / magnum / net / http / HttpClientConnection.cpp

/* 
  Magnum 
  Copyright (C) 2002-2012 Kaya Kupferschmidt. All Rights Reserved.

  This software is provided 'as-is', without any express or implied
  warranty.  In no event will the authors be held liable for any damages
  arising from the use of this software.

  Permission is granted to anyone to use this software for any purpose,
  including commercial applications, and to alter it and redistribute it
  freely, under the terms in LICENSE.TXT.

  Kaya Kupferschmidt  (k.kupferschmidt@dimajix.de)
*/

#include "magnum/io/Content.h"
#include "magnum/io/StreamUtils.h"
#include "magnum/net/Exception.h"
#include "magnum/net/URL.h"
#include "magnum/net/http/HttpClient.h"
#include "magnum/net/http/HttpRequest.h"
#include "magnum/net/http/HttpResponseParser.h"
#include "magnum/net/http/HttpInputStream.h"
#include "magnum/net/http/HttpOutputStream.h"
#include "magnum/net/http/HttpClientConnection.h"


namespace magnum { namespace net { namespace http {
using magnum::object::Nil;
using magnum::types::Character;
using magnum::types::index_t;
using magnum::io::Content;
using magnum::io::StreamUtils;


/*--------------------------------------------------------------------------*/
/**
 */
HttpClientConnection::HttpClientConnection(Connection* con, const Proxy* proxy)
: HttpConnection(con) 
{
    if (proxy && proxy->getType() == Proxy::CachingHttp)
        m_Proxy = proxy;
}
	

/*--------------------------------------------------------------------------*/
/**
 */
HttpClientConnection::~HttpClientConnection() 
{
}


/*--------------------------------------------------------------------------*/
/**
 */
void HttpClientConnection::writeRequest(const HttpRequest& request)
{
    writeRequest(request, ByteArray());
}


/*--------------------------------------------------------------------------*/
/**
 */
void HttpClientConnection::writeRequest(const HttpRequest& request, InputStream* input)
{
    if (input == 0) {
        writeRequestImpl(request, ByteArray(), 0);
    }
    else if (request.getVersion() >= HttpVersion::Http_1_1) {
        writeRequestImpl(request, ByteArray(), input);
    }
    else {
        Content content(input);
        writeRequestImpl(request, content.getBuffer(), 0);
    }
}


/*--------------------------------------------------------------------------*/
/**
 */
void HttpClientConnection::writeRequest(const HttpRequest& request, const ByteArray& data) 
{
    writeRequestImpl(request, data, 0);
}


/*--------------------------------------------------------------------------*/
/**
 */
void HttpClientConnection::writeRequestImpl(const HttpRequest& request, const ByteArray& bdata, InputStream* sdata) 
{
    // Check state of connection
    HttpConnection::State initialState = getState();
    if (initialState != Open && initialState != Persistent)
        throw HttpRequestException("Connection is closed.");

    // Set state to error, just in case. Will be changed at the end of this method
    setState(Error);

	// Send request to HTTP server
	HttpOutputStream* output = static_cast<HttpOutputStream*>(getOutputStream());
    output->setEncoding(HttpOutputStream::Unencoded);

    //request.writeTo(output);
    const URL& url = request.getURL();
    CString file = getProxy() ? url.getEncodedUrl() : url.getEncodedFile();
    if (file.isEmpty())
        file = '/';

    // Build request string
    if (request.getVersion() == HttpVersion::Http_1_0) {
        if (url.isHostNumeric())
            *output << request.getMethod() << ' ' << file << " HTTP/1.0\r\n";
        else
            *output << request.getMethod() << ' ' << file << " HTTP/1.0\r\nHost: " << url.getEncodedHost() + "\r\n";
    }
    else if (request.getVersion() == HttpVersion::Http_1_1) {
        *output << request.getMethod() << ' ' << file << " HTTP/1.1\r\nHost: " << url.getEncodedHost()
            << "\r\nAccept-Encoding: gzip, deflate, identity\r\n";
    }
    else {
        if (url.isHostNumeric())
            *output << request.getMethod() << ' ' << file << " HTTP/1.0\r\n";
        else
            *output << request.getMethod() << ' ' << file << " HTTP/1.0\r\nHost: " << url.getEncodedHost() << "\r\n";
    }

    // Enable keep-alive (for http 1.0)
    if (getProxy())
        *output << "Proxy-Connection: Keep-Alive\r\n";
    else
        *output << "Connection: Keep-Alive\r\n";

    // Write headers
    const MimeHeaders& headers = request.getHeaders();
    headers.writeTo(output);

	// Check for any additional data
	if (!bdata.isEmpty()) {
	    CString headers;
	    headers << "Content-Length: " << (int)bdata.getLength() << "\r\n";
	    output->write(headers.getData(), headers.getLength());
	}
    else if (sdata != 0) {
        static const char header[] = "Transfer-Encoding: chunked\r\n";
	    output->write(header, sizeof(header)-1);
    }
	
	// Write empty line to denote end of headers
	output->write("\r\n",2);
	
	// Write any additional data
	if (!bdata.isEmpty()) {
	    output->write(bdata.getData(), bdata.getLength());
    }
    else if (sdata != 0) {
        output->setEncoding(HttpOutputStream::Chunked);
        StreamUtils::copy(sdata, output);
        output->setEncoding(HttpOutputStream::Unencoded);
    }
    
    // Flush output stream
	output->flush();

    // Set state to previous state, such that we can read the response
    setState(initialState);
}
	
	
/*--------------------------------------------------------------------------*/
/**
 */
HttpResponse HttpClientConnection::readResponse(const HttpRequest& request)
{
    // Check state of connection
    HttpConnection::State initialState = getState();
    if (initialState != Open && initialState != Persistent)
        throw HttpRequestException("Connection is closed.");

    // Set state to error, just in case. Will be changed at the end of this method
    setState(Error);

    HttpInputStream* input = static_cast<HttpInputStream*>(getInputStream());

	// Prepare input parser
	input->setContent((size_t)-1, HttpInputStream::Unencoded, HttpInputStream::Uncompressed);
	
	HttpResponseParser parser;
	HttpResponse response = parser.readResponse(input, request);
	HttpHeaders& headers = response.getHeaders();
		
	// Scan mime-headers
	size_t contentLength = (size_t)-1;
    HttpConnection::State finalState = (response.getVersion() == HttpVersion::Http_1_1) ? Persistent : Closed;
	HttpInputStream::ContentEncoding contentEncoding = HttpInputStream::Uncompressed;
	HttpInputStream::TransferEncoding transferEncoding = HttpInputStream::Unencoded;

	// HEAD requests may *never* carry any load
	if (response.getMethod() == "HEAD") {
	    contentLength = 0;
	    contentEncoding = HttpInputStream::Uncompressed;
	    transferEncoding = HttpInputStream::Unencoded;
	}
    else {
        // Get content length
        const CString& contentLengthStr = headers.find(HttpHeaders::ContentLength);
        if (!contentLengthStr.isNull())
		    contentLength = contentLengthStr.toInteger();

        // Get content encoding
        const CString& contentEncodingStr = headers.find(HttpHeaders::ContentEncoding);
        if (!contentEncodingStr.isNull()) {
            if (contentEncodingStr.compare("gzip", CString::CaseInsensitive) == 0)
	            contentEncoding = HttpInputStream::GZip;
            else if (contentEncodingStr.compare("deflate", CString::CaseInsensitive) == 0)
	            contentEncoding = HttpInputStream::Deflate;
        }
        
        // Get transfer encoding
        const CString& transferEncodingStr = headers.find(HttpHeaders::TransferEncoding);
        if (!transferEncodingStr.isNull()) {
            if (transferEncodingStr.compare("chunked", CString::CaseInsensitive) == 0)
	            transferEncoding = HttpInputStream::Chunked;
        }
    }
    
    // Get connection state
    if (getProxy()) {
        const CString& connectionStr = headers.find(HttpHeaders::ProxyConnection);
        if (!connectionStr.isNull()) {
            if (connectionStr.compare("keep-alive", CString::CaseInsensitive) == 0) {
                finalState = Persistent;
            }
            else {
                finalState = Closed;
            }
        }
    }
    else {
        const CString& connectionStr = headers.find(HttpHeaders::Connection);
        if (!connectionStr.isNull()) {
            if (connectionStr.compare("keep-alive", CString::CaseInsensitive) == 0) {
                finalState = Persistent;
            }
            else {
                finalState = Closed;
            }
        }
    }
		
	// Attach input stream to response
	input->setContent(contentLength, transferEncoding, contentEncoding);
	response.setInputStream(input);

    // Check if we can maintain a persistent connection
    if (finalState == Persistent) {
        // Persistent connections need either content length or transfer encoding
        if (contentLength == -1 && transferEncoding == HttpInputStream::Unencoded)
            finalState = Closed;
    }

    // Set State to requested final state (closed or persistent)
    setState(finalState);
	
	return response;
}

} } }