blob: 9c7c1a350f8e53951653e0abcca487fd716febbd [file] [log] [blame]
/*
* Copyright 2003-2004 The Apache Software Foundation.
// (c) Copyright IBM Corp. 2004, 2005 All Rights Reserved
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* @author Lilantha Darshana (lilantha@virtusa.com)
* @author Damitha Kumarage (damitha@jkcsworld.com, damitha@opensource.lk)
* @author Samisa Abeysinghe (sabeysinghe@virtusa.com)
*/
#include "Platform.h"
#include "Channel.h"
#include "../../platforms/PlatformAutoSense.hpp"
#include <iostream>
#include <stdio.h>
using namespace std;
/**
* Create a Channel & initialize
*
*/
/** Channel::Channel() Constructor
*/
Channel::Channel ():m_Sock (INVALID_SOCKET), m_iMsgLength( 0), m_sMsg(NULL), m_lTimeoutSeconds(0), m_bUseProxy(false)
{
#ifdef WIN32
m_lTimeoutSeconds = 10;
#endif
}
/** Channel::~Channel() Destructor
*/
Channel::~Channel ()
{
// If the socket value is not invalid, then close the socket before
// deleting the Channel object.
if( m_Sock != INVALID_SOCKET)
{
closeChannel();
}
if (m_sMsg)
delete [] m_sMsg;
}
/** Channel::setURL( URL) sets the URL for the channel.
*
* @param const char* URL pointer to a NULL terminated character string that
* contains the new URL for the channel.
*/
void Channel::setURL(const char* cpURL)
{
// Set the Channel URL to the new value.
// NB: This will cause problems if the new URL is diferent in type from the
// old URL. e.g. if the new URL is https and the old URL was http, then
// problems will occur because each type operates differently.
m_URL.setURL( cpURL);
}
/** Channel::getURL() gets the URL for the channel.
*
* @return const char* URL pointer to a NULL terminated character string that
* contains the URL for the channel.
*/
const char* Channel::getURL()
{
// Return the URL currently assicated with the channel object.
return m_URL.getURL();
}
/**
* This channel open INET channel for the time being using primitive sockets
* Do we need any other type of channel; like shared memory, pipes etc. ????
*
* @param p_RemoteNode End point address as hostname/IP
* @param p_RemoteEnd Port #
*
* @return true if successfuly open a soket to the endpoint. o/w exception is
* thrown
*/
bool
Channel::open () //std::string & p_RemoteNode, unsigned short p_RemoteEnd)
throw (AxisTransportException&)
{
// if there is an open socket already, close it first
if (m_Sock != INVALID_SOCKET)
closeChannel();
// If the underlying socket transport has not been initialised properly,
// then thrown an exeption.
if( !Init())
{
throw AxisTransportException( SERVER_TRANSPORT_CHANNEL_INIT_ERROR);
}
// If the transport was initilised, then create client and server sockets.
sockaddr_in clAddr;
// Create the Client (Rx) side first.
if( (m_Sock = socket( PF_INET, SOCK_STREAM, 0)) != INVALID_SOCKET)
{
clAddr.sin_family = AF_INET; // AF_INET (address family Internet).
clAddr.sin_port = 0; // No Specify Port required.
clAddr.sin_addr.s_addr = INADDR_ANY;
// Attempt to bind the client to the client socket.
if( bind( m_Sock, (struct sockaddr *) &clAddr, sizeof( clAddr)) == SOCKET_ERROR)
{
// Error whilst binding. Cannot open a channel to the remote end,
// shutting down the channel and then throw an exception.
closeChannel();
throw AxisTransportException( SERVER_TRANSPORT_SOCKET_CONNECT_ERROR);
}
// Although the above fragment makes use of the bind() API, it would be
// just as effective to skip over this call as there are no specific
// local port ID requirements for this client. The only advantage that
// bind() offers is the accessibility of the port which the system
// chose via the .sin_port member of the cli_addr structure which will
// be set upon success of the bind() call.
// Create the Server (Tx) side.
sockaddr_in svAddr;
struct hostent * pHostEntry = NULL;
const char* host = m_URL.getHostName();
unsigned int port = m_URL.getPort();
if (m_bUseProxy)
{
port = m_uiProxyPort;
host = m_strProxyHost.c_str();
}
svAddr.sin_family = AF_INET;
svAddr.sin_port = htons (port);
// Probably this is the host-name of the server we are connecting to...
if( (pHostEntry = gethostbyname(host)))
{
svAddr.sin_addr.s_addr = ((struct in_addr *) pHostEntry->h_addr)->s_addr;
}
else
{
// No this is the IP address
svAddr.sin_addr.s_addr = inet_addr (host);
}
// Attempt to connect to the remote server.
if( connect( m_Sock, (struct sockaddr *) &svAddr, sizeof (svAddr)) == SOCKET_ERROR)
{
// Cannot open a channel to the remote end, shutting down the
// channel and then throw an exception.
// Before we do anything else get the last error message;
long dw = GETLASTERROR
closeChannel();
string* message = PLATFORM_GET_ERROR_MESSAGE(dw);
char fullMessage[600];
sprintf(fullMessage,
"Failed to open connection to server: \n \
hostname='%s'\n\
port='%d'\n\
Error Message='%s'\
Error Code='%d'\n",
m_URL.getHostName(), m_URL.getPort(), message->c_str(), dw);
delete(message);
throw AxisTransportException( CLIENT_TRANSPORT_OPEN_CONNECTION_FAILED, fullMessage);
}
}
else
{
// Sockets error Couldn't create socket. Close the channel and throw
// an exception.
closeChannel();
throw AxisTransportException( SERVER_TRANSPORT_SOCKET_CREATE_ERROR);
}
/* Turn off the Nagle algorithm - Patch by Steve Hardy */
/* This is needed, because our TCP stack would otherwise wait at most
* 200 ms before actually sending data to the server (while waiting for
* a full packet). This limits performance to around 5 requests per
* second, which is not acceptable. Turning off the Nagle algorithm
* allows for much faster transmission of small packets, but may
* degrade high-bandwidth transmissions.
*/
int one = 1;
setsockopt(m_Sock, IPPROTO_TCP, TCP_NODELAY, (char *)&one, sizeof(int));
return true;
}
/**
* Channel::Init() Operating System specific initialization for sockets
*
* @return bool True if successfully initialised the OS specifics.
* False if unsuccessful
*/
bool Channel::Init ()
{
#ifdef WIN32
WSADATA wsaData; // Contains vendor-specific information, such as the
// maximum number of sockets available and the maximum
// datagram size.
// wsaData filled by Windows Sockets DLLs.
if( WSAStartup( WS_VERSION_REQD, &wsaData))
{
// Error - Could not setup underlying Windows socket transport
// mechanism.
m_LastErr = "WinSock DLL not responding.";
return false;
}
else
{
// Query to see whether the available version matches what is required
if ((LOBYTE( wsaData.wVersion) < WS_VERSION_MAJOR()) ||
(LOBYTE( wsaData.wVersion) == WS_VERSION_MAJOR() &&
HIBYTE( wsaData.wVersion) < WS_VERSION_MINOR()))
{
// Error - Underlying Windows socket transport version is not
// compatible with what is required.
char buf[100];
sprintf( buf,
"Windows Sockets version %d.%d not supported by winsock2.dll",
LOBYTE (wsaData.wVersion), HIBYTE (wsaData.wVersion));
Error( buf);
closeChannel();
return false;
}
}
#else
/* cout << "no need for linux" << endl; */
/* other OS specific Intitialization goes here */
#endif
return true;
}
/**
* Channel::operator << (Msg) Write (send) a message to the remote server.
* Sending a message will block the application (i.e. synchronous communication).
* NB: We may need to do this asynchronizely; preferably either non-blocking
* send or pthread.
*
* @param const char * Msg NULL terminated character string containing the
* message to be written to the open channel.
* NB: For GSKit SSL, the message has been encoded and may contain embedded
* null characters. To overcome this problem, the m_iMsgLength value must be
* set to the message length before calling this method. If m_iMsgLength is
* not 0, this method will assume the the value is the message length.
* m_iMsgLength is set to 0 in the constructor, so unless it is intentionally
* changed, it will not have any effect on this method.
*
* @return const Channel & is the address of the Channel object (the 'this'
* pointer).
*/
const Channel &Channel::operator << (const char *msg)
{
// Check that the Tx/Rx sockets are valid (this will have been done if the
// application has called the open method first.
if( INVALID_SOCKET == m_Sock)
{
// Error - Writing cannot be done without having a open socket to
// remote end. Throw an exception.
throw AxisTransportException( SERVER_TRANSPORT_INVALID_SOCKET);
}
int size = strlen( msg);
int nByteSent;
if( m_iMsgLength)
{
size = m_iMsgLength;
}
if( (nByteSent = send( m_Sock, msg, size, MSG_DONTROUTE)) == SOCKET_ERROR)
{
// Output streaming error while writing data. Close the channel and
// throw an exception.
closeChannel();
throw AxisTransportException(SERVER_TRANSPORT_OUTPUT_STREAMING_ERROR);
}
return *this;
}
/**
* Channel::operator >> (Msg) Read (receive) a message from the remote server;
* Reading may be done in 'chunks'.
*
* @param std::string & Msg is string that will receive the message.
* NB: For GSKit SSL, the message has been encoded and may contain embedded
* null characters. To overcome this problem, the m_iMsgLength value must be
* set to the negative of the message length before calling this method. If
* m_iMsgLength is not negative, this method will assume the BUF_SIZE value is
* the message length. m_iMsgLength is set to 0 in the constructor, so unless
* it is intentionally changed, it will not have any effect on this method.
*
* @return const Channel & is the address of the Channel object (the 'this'
* pointer).
*/
const Channel &Channel::operator >> (std::string & msg)
{
msg = "";
if (INVALID_SOCKET == m_Sock)
{
// Error - Reading cannot be done without having a open socket Input
// streaming error on undefined channel; please open the
// channel first
throw AxisTransportException( SERVER_TRANSPORT_INVALID_SOCKET);
}
int nByteRecv = 0;
char buf[BUF_SIZE];
int iBufSize = BUF_SIZE;
if( m_iMsgLength < 0)
{
iBufSize = -m_iMsgLength;
}
else
{
iBufSize--;
}
//assume timeout not set; set default tatus to OK
int iTimeoutStatus = 1;
//check if timeout set
if(m_lTimeoutSeconds)
{
iTimeoutStatus = applyTimeout();
}
// Handle timeout outcome
if( iTimeoutStatus < 0)
{
// Error
// Select SOCKET_ERROR. Channel error while waiting for timeout
throw AxisTransportException( SERVER_TRANSPORT_TIMEOUT_EXCEPTION,
"Channel error while waiting for timeout");
}
if( iTimeoutStatus == 0)
{
// Timeout expired - select timeout expired.
// Channel error connection timeout before receving
throw AxisTransportException( SERVER_TRANSPORT_TIMEOUT_EXPIRED,
"Channel error: connection timed out before receving");
}
// Either timeout was not set or data available before timeout; so read
if( (nByteRecv = recv( m_Sock, (char *) &buf, iBufSize, 0)) == SOCKET_ERROR)
{
// Recv SOCKET_ERROR, Channel error while getting data
/* closeChannel(); */
throw AxisTransportException( SERVER_TRANSPORT_INPUT_STREAMING_ERROR,
"Channel error while getting data");
}
if( nByteRecv)
{
/* printf("nByteRecv:%d\n", nByteRecv); */
buf[nByteRecv] = '\0';
/* got a part of the message, so add " \ "to form */
msg = buf;
if( m_iMsgLength < 0)
{
m_iMsgLength = nByteRecv;
if (m_sMsg)
{
delete [] m_sMsg;
m_sMsg = NULL;
}
m_sMsg = new char[nByteRecv];
memcpy( m_sMsg, buf, nByteRecv);
}
}
else
{
if( m_iMsgLength < 0)
{
m_iMsgLength = nByteRecv;
}
;//printf ("execution break\n");
}
return *this;
}
/**
* Channel::readNonBlocking( Msg, BlockingRequired) Read (receive) a message
* from the remote server; Reading may be done in 'chunks'?
*
* @param string & Msg
* @param bool BlockingRequired
*
* @return const Channel & is the address of the Channel object (the 'this'
* pointer).
*/
const Channel &Channel::readNonBlocking( std::string & msg, bool bBlockingRequired)
{
msg = "";
if (INVALID_SOCKET == m_Sock)
{
/*Reading cannot be done without having a open socket*/
throw AxisTransportException(SERVER_TRANSPORT_INVALID_SOCKET);
}
int nByteRecv = 0;
char buf[BUF_SIZE];
//Samisa: I want to set the socket to non blocking mode here
//for Winsock there is no simple way to do this but for Linux I used MSG_DONTWAIT
//there is no MSG_DONTWAIT defined in Winsock
int flags = 0;
#if defined WIN32
// Set the socket I/O mode; iMode = 0 for blocking; iMode != 0 for non-blocking
int iMode = 1;
if( bBlockingRequired)
{
iMode = 0;
}
ioctlsocket( m_Sock, FIONBIO, (u_long FAR*) &iMode);
flags = 0;
#elif defined AIX
flags=MSG_WAITALL;
if (!bBlockingRequired)
{
flags=MSG_NONBLOCK;
}
#elif defined( __OS400__ )
fcntl(m_Sock, F_SETFL, (int)O_NONBLOCK);
flags = 0;
#else
//for linux
if( !bBlockingRequired)
{
flags = MSG_DONTWAIT;
}
//TODO: define flags (or other means) to enable non blocking for other operating systems
#endif
if ((nByteRecv = recv (m_Sock, (char *) &buf, BUF_SIZE - 1, flags))
== SOCKET_ERROR)
{
/*Channel error while getting data*/
return *this;
}
if (nByteRecv)
{
buf[nByteRecv] = '\0';
msg = buf;
}
else
Error( "Channel::readNonBlocking: execution break");
return *this;
}
/**
* Channel::closeChannel() Close and clean-up using specific Operating System
* calls.
*/
void Channel::closeChannel ()
{
if( INVALID_SOCKET == m_Sock) // Check if socket already closed : AXISCPP-185
{
return;
}
#ifdef WIN32
closesocket( m_Sock);
// Check for any possible error conditions from WSACleanup() and report
// them before exiting, as this information might indicate a network
// layer problem in the system.
WSACleanup ();
#else
::close( m_Sock);
#endif
m_Sock = INVALID_SOCKET; // fix for AXISCPP-185
}
/**
* Channel::Error( Error) Log any errors that cause on channel usage or
* initilaization.
*/
void Channel::Error( const char *err)
{
#ifdef _DEBUG
std::cerr << err << std::endl;
#endif
}
/**
* Channel::setTimeout( Seconds)
*
* @param const long Seconds
*/
void Channel::setTimeout(const long lSeconds)
{
m_lTimeoutSeconds = lSeconds;
}
/**
* Channel::applyTimeout()
*
* @return int
*/
int Channel::applyTimeout()
{
fd_set set;
struct timeval timeout;
// Initialize the file descriptor set.
FD_ZERO( &set);
FD_SET( m_Sock, &set);
/* Initialize the timeout data structure. */
timeout.tv_sec = m_lTimeoutSeconds;
timeout.tv_usec = 0;
/* select returns 0 if timeout, 1 if input available, -1 if error. */
return select( FD_SETSIZE, &set, NULL, NULL, &timeout);
}
// This is used by SimpleAxisServer
void Channel::setSocket(unsigned int uiNewSocket)
{
m_Sock = uiNewSocket;
}
/**
* Debug routine will be deleted.
*/
void Channel::hexOutput( char * psData, int iDataLength)
{
unsigned char * pByte = (unsigned char *) psData;
string sLineHex;
string sLineChar;
int iByteCount = 0;
int iLineCount;
char szDigits[6];
while( iByteCount < iDataLength)
{
sprintf( szDigits, "%04X ", iByteCount);
sLineHex = szDigits;
sLineChar = "";
iLineCount = 0;
while( iLineCount < 16 && iByteCount < iDataLength)
{
unsigned char Char = *pByte;
if( Char < 32 || Char > 127)
{
Char = '.';
}
sprintf( szDigits, "%02X ", *pByte);
sLineHex = sLineHex + szDigits;
sLineChar = sLineChar + (char) Char;
pByte++;
iByteCount++;
iLineCount++;
}
for( int iCount = iLineCount; iCount < 16; iCount++)
{
sLineHex = sLineHex + " ";
}
printf( "%s %s\n", sLineHex.c_str(), sLineChar.c_str());
}
}
void Channel::setProxy (const char *pcProxyHost, unsigned int uiProxyPort)
{
m_strProxyHost = pcProxyHost;
m_uiProxyPort = uiProxyPort;
m_bUseProxy = true;
}