]> git.donarmstrong.com Git - bamtools.git/blob - src/api/internal/io/TcpSocket_p.cpp
Stablized HTTP access on all platforms. (issue #54, issue #11)
[bamtools.git] / src / api / internal / io / TcpSocket_p.cpp
1 // ***************************************************************************
2 // TcpSocket_p.cpp (c) 2011 Derek Barnett
3 // Marth Lab, Department of Biology, Boston College
4 // ---------------------------------------------------------------------------
5 // Last modified: 5 January 2012 (DB)
6 // ---------------------------------------------------------------------------
7 // Provides basic TCP I/O interface
8 // ***************************************************************************
9
10 #include "api/internal/io/ByteArray_p.h"
11 #include "api/internal/io/TcpSocket_p.h"
12 #include "api/internal/io/TcpSocketEngine_p.h"
13 using namespace BamTools;
14 using namespace BamTools::Internal;
15
16 #include <algorithm>
17 #include <climits>
18 #include <sstream>
19 #include <vector>
20 using namespace std;
21
22 // ------------------------------------
23 // static utility methods & constants
24 // ------------------------------------
25
26 namespace BamTools {
27 namespace Internal {
28
29 // constants
30 static const size_t DEFAULT_BUFFER_SIZE = 0x10000;
31
32 } // namespace Internal
33 } // namespace BamTools
34
35 // --------------------------
36 // TcpSocket implementation
37 // --------------------------
38
39 TcpSocket::TcpSocket(void)
40     : m_mode(IBamIODevice::NotOpen)
41 //    , m_localPort(0)
42     , m_remotePort(0)
43     , m_engine(0)
44     , m_cachedSocketDescriptor(-1)
45     , m_readBuffer(DEFAULT_BUFFER_SIZE)
46     , m_error(TcpSocket::NoError)
47     , m_state(TcpSocket::UnconnectedState)
48 { }
49
50 TcpSocket::~TcpSocket(void) {
51     if ( m_state == TcpSocket::ConnectedState )
52         DisconnectFromHost();
53 }
54
55 size_t TcpSocket::BufferBytesAvailable(void) const {
56     return m_readBuffer.Size();
57 }
58
59 bool TcpSocket::CanReadLine(void) const {
60     return m_readBuffer.CanReadLine();
61 }
62
63 void TcpSocket::ClearBuffer(void) {
64     m_readBuffer.Clear();
65 }
66
67 bool TcpSocket::ConnectImpl(const HostInfo& hostInfo,
68                             const std::string& port,
69                             IBamIODevice::OpenMode mode)
70 {
71     // skip if we're already connected
72     if ( m_state == TcpSocket::ConnectedState ) {
73         m_error = TcpSocket::SocketResourceError;
74         m_errorString = "socket already connected";
75         return false;
76     }
77
78     // reset socket state
79     m_hostName   = hostInfo.HostName();
80     m_mode       = mode;
81     m_state      = TcpSocket::UnconnectedState;
82     m_error      = TcpSocket::NoError;
83 //    m_localPort  = 0;
84     m_remotePort = 0;
85 //    m_localAddress.Clear();
86     m_remoteAddress.Clear();
87     m_readBuffer.Clear();
88
89     // fetch candidate addresses for requested host
90     vector<HostAddress> addresses = hostInfo.Addresses();
91     if ( addresses.empty() ) {
92         m_error = TcpSocket::HostNotFoundError;
93         m_errorString = "no IP addresses found for host";
94         return false;
95     }
96
97     // convert port string to integer
98     stringstream ss(port);
99     uint16_t portNumber(0);
100     ss >> portNumber;
101
102     // iterate through adddresses
103     vector<HostAddress>::const_iterator addrIter = addresses.begin();
104     vector<HostAddress>::const_iterator addrEnd  = addresses.end();
105     for ( ; addrIter != addrEnd; ++addrIter) {
106         const HostAddress& addr = (*addrIter);
107
108         // try to initialize socket engine with this address
109         if ( !InitializeSocketEngine(addr.GetProtocol()) ) {
110             // failure to initialize is OK here
111             // we'll just try the next available address
112             continue;
113         }
114
115         // attempt actual connection
116         if ( m_engine->Connect(addr, portNumber) ) {
117
118             // if connection successful, update our state & return true
119             m_mode = mode;
120 //            m_localAddress  = m_engine->GetLocalAddress();
121 //            m_localPort     = m_engine->GetLocalPort();
122             m_remoteAddress = m_engine->GetRemoteAddress();
123             m_remotePort    = m_engine->GetRemotePort();
124             m_cachedSocketDescriptor = m_engine->GetSocketDescriptor();
125             m_state = TcpSocket::ConnectedState;
126             return true;
127         }
128     }
129
130     // if we get here, no connection could be made
131     m_error = TcpSocket::HostNotFoundError;
132     m_errorString = "could not connect to any host addresses";
133     return false;
134 }
135
136 bool TcpSocket::ConnectToHost(const string& hostName,
137                               uint16_t port,
138                               IBamIODevice::OpenMode mode)
139 {
140     stringstream ss("");
141     ss << port;
142     return ConnectToHost(hostName, ss.str(), mode);
143
144 }
145
146 bool TcpSocket::ConnectToHost(const string& hostName,
147                               const string& port,
148                               IBamIODevice::OpenMode mode)
149 {
150     // create new address object with requested host name
151     HostAddress hostAddress;
152     hostAddress.SetAddress(hostName);
153
154     HostInfo info;
155     // if host name was IP address ("x.x.x.x" or IPv6 format)
156     // otherwise host name was 'plain-text' ("www.foo.bar")
157     // we need to look up IP address(es)
158     if ( hostAddress.HasIPAddress() )
159         info.SetAddresses( vector<HostAddress>(1, hostAddress) );
160     else
161         info = HostInfo::Lookup(hostName, port);
162
163     // attempt connection on requested port
164     return ConnectImpl(info, port, mode);
165 }
166
167 void TcpSocket::DisconnectFromHost(void) {
168
169     // close socket engine & delete
170     if ( m_state == TcpSocket::ConnectedState )
171         ResetSocketEngine();
172
173     // reset connection state
174 //    m_localPort = 0;
175     m_remotePort = 0;
176 //    m_localAddress.Clear();
177     m_remoteAddress.Clear();
178     m_hostName.clear();
179     m_cachedSocketDescriptor = -1;
180
181     // for future, make sure there's outgoing data that needs to be flushed
182     m_readBuffer.Clear();
183 }
184
185 TcpSocket::SocketError TcpSocket::GetError(void) const {
186     return m_error;
187 }
188
189 std::string TcpSocket::GetErrorString(void) const {
190     return m_errorString;
191 }
192
193 std::string TcpSocket::GetHostName(void) const {
194     return m_hostName;
195 }
196
197 //HostAddress TcpSocket::GetLocalAddress(void) const {
198 //    return m_localAddress;
199 //}
200
201 //uint16_t TcpSocket::GetLocalPort(void) const {
202 //    return m_localPort;
203 //}
204
205 HostAddress TcpSocket::GetRemoteAddress(void) const {
206     return m_remoteAddress;
207 }
208
209 uint16_t TcpSocket::GetRemotePort(void) const {
210     return m_remotePort;
211 }
212
213 TcpSocket::SocketState TcpSocket::GetState(void) const {
214     return m_state;
215 }
216
217 bool TcpSocket::InitializeSocketEngine(HostAddress::NetworkProtocol protocol) {
218     ResetSocketEngine();
219     m_engine = new TcpSocketEngine;
220     return m_engine->Initialize(protocol);
221 }
222
223 bool TcpSocket::IsConnected(void) const {
224     if ( m_engine == 0 )
225         return false;
226     return ( m_engine->IsValid() && (m_state == TcpSocket::ConnectedState) );
227 }
228
229 // may be read in a look until desired data amount has been read
230 // returns: number of bytes read, or -1 if error
231 int64_t TcpSocket::Read(char* data, const unsigned int numBytes) {
232
233     // if we have data in buffer, just return it
234     if ( !m_readBuffer.IsEmpty() ) {
235         const size_t bytesRead = m_readBuffer.Read(data, numBytes);
236         return static_cast<int64_t>(bytesRead);
237     }
238
239     // otherwise, we'll need to fetch data from socket
240     // first make sure we have a valid socket engine
241     if ( m_engine == 0 ) {
242         // TODO: set error string/state?
243         return -1;
244     }
245
246     // fetch data from socket, return 0 for success, -1 for failure
247     // since this should be called in a loop,
248     // we'll pull the actual bytes from the buffer on next iteration
249     const int64_t socketBytesRead = ReadFromSocket();
250     if ( socketBytesRead < 0 ) {
251         // TODO: set error string/state ?
252         return -1;
253     }
254
255     // we should have data now in buffer, try to fetch requested amount
256     // if nothing in buffer, we will return 0 bytes read (signals EOF reached)
257     const size_t numBytesRead = m_readBuffer.Read(data, numBytes);
258     return static_cast<int64_t>(numBytesRead);
259 }
260
261 int64_t TcpSocket::ReadFromSocket(void) {
262
263     // check for any socket engine errors
264     if ( !m_engine->IsValid() ) {
265         m_errorString = "TcpSocket::ReadFromSocket - socket disconnected";
266         ResetSocketEngine();
267         return -1;
268     }
269
270     // wait for ready read
271     bool timedOut;
272     const bool isReadyRead = m_engine->WaitForRead(5000, &timedOut);
273
274     // if not ready
275     if ( !isReadyRead ) {
276
277         // if we simply timed out
278         if ( timedOut ) {
279             // TODO: get add'l error info from engine ?
280             m_errorString = "TcpSocket::ReadFromSocket - timed out waiting for ready read";
281         }
282
283         // otherwise, there was some other error
284         else {
285             // TODO: get add'l error info from engine ?
286             m_errorString = "TcpSocket::ReadFromSocket - encountered error while waiting for ready read";
287         }
288
289         // return failure
290         return -1;
291     }
292
293     // get number of bytes available from socket
294     const int64_t bytesToRead = m_engine->NumBytesAvailable();
295     if ( bytesToRead < 0 ) {
296         // TODO: get add'l error info from engine ?
297         m_errorString = "TcpSocket::ReadFromSocket - encountered error while determining numBytesAvailable";
298         return -1;
299     }
300
301     // make space in buffer & read from socket
302     char* buffer = m_readBuffer.Reserve(bytesToRead);
303     const int64_t numBytesRead = m_engine->Read(buffer, bytesToRead);
304     if ( numBytesRead == -1 ) {
305         // TODO: get add'l error info from engine ?
306         m_errorString = "TcpSocket::ReadFromSocket - encountered error while reading bytes";
307     }
308
309     // return number of bytes actually read
310     return numBytesRead;
311 }
312
313 string TcpSocket::ReadLine(int64_t max) {
314
315     // prep result byte buffer
316     ByteArray result;
317     size_t bufferMax = ((max > static_cast<int64_t>(UINT_MAX))
318                         ? UINT_MAX : static_cast<size_t>(max));
319     result.Resize(bufferMax);
320
321     // read data
322     int64_t readBytes(0);
323     if ( result.Size() == 0 ) {
324
325         if ( bufferMax == 0 )
326             bufferMax = UINT_MAX;
327
328         result.Resize(1);
329
330         int64_t readResult;
331         do {
332             result.Resize( static_cast<size_t>(min(bufferMax, result.Size() + DEFAULT_BUFFER_SIZE)) );
333             readResult = ReadLine(result.Data()+readBytes, result.Size()-readBytes);
334             if ( readResult > 0 || readBytes == 0 )
335                 readBytes += readResult;
336         } while ( readResult == DEFAULT_BUFFER_SIZE && result[static_cast<size_t>(readBytes-1)] != '\n' );
337
338     } else
339         readBytes = ReadLine(result.Data(), result.Size());
340
341     // clean up byte buffer
342     if ( readBytes <= 0 )
343         result.Clear();
344     else
345         result.Resize(static_cast<size_t>(readBytes));
346
347     // return byte buffer as string
348     return string( result.ConstData(), result.Size() );
349 }
350
351 int64_t TcpSocket::ReadLine(char* dest, size_t max) {
352
353     // wait for buffer to contain line contents
354     if ( !WaitForReadLine() ) {
355         m_errorString = "TcpSocket::ReadLine - error waiting for read line";
356         return -1;
357     }
358
359     // leave room for null term
360     if ( max < 2 )
361         return -1;
362     --max;
363
364     // read from buffer, handle newlines
365     int64_t readSoFar = m_readBuffer.ReadLine(dest, max);
366     if ( readSoFar && dest[readSoFar-1] == '\n' ) {
367
368         // adjust for windows-style '\r\n'
369         if ( readSoFar > 1 && dest[readSoFar-2] == '\r') {
370             --readSoFar;
371             dest[readSoFar-1] = '\n';
372         }
373     }
374
375     // null terminate & return number of bytes read
376     dest[readSoFar] = '\0';
377     return readSoFar;
378 }
379
380 void TcpSocket::ResetSocketEngine(void) {
381
382     // shut down socket engine
383     if ( m_engine ) {
384         m_engine->Close();
385         delete m_engine;
386         m_engine = 0;
387     }
388
389     // reset our state & cached socket handle
390     m_state = TcpSocket::UnconnectedState;
391     m_cachedSocketDescriptor = -1;
392 }
393
394 bool TcpSocket::WaitForReadLine(void) {
395
396     // wait until we can read a line (will return immediately if already capable)
397     while ( !CanReadLine() ) {
398         if ( !ReadFromSocket() )
399             return false;
400     }
401
402     // if we get here, success
403     return true;
404 }
405
406 int64_t TcpSocket::Write(const char* data, const unsigned int numBytes) {
407
408     // single-shot attempt at write (not buffered, just try to shove the data through socket)
409     // this method purely exists to send 'small' HTTP requests/FTP commands from client to server
410
411     // wait for our socket to be write-able
412     bool timedOut;
413     const bool isReadyWrite = m_engine->WaitForWrite(3000, &timedOut);
414
415     // if ready, return number of bytes written
416     if ( isReadyWrite )
417         return m_engine->Write(data, numBytes);
418
419     // otherwise, socket not ready for writing
420     // set error string depending on reason & return failure
421     if ( !timedOut ) {
422         // TODO: get add'l error info from engine ??
423         m_errorString = "TcpSocket::Write - timed out waiting for ready-write";
424     }
425     else {
426         // TODO: get add'l error info from engine ??
427         m_errorString = "TcpSocket::Write - error encountered while waiting for ready-write";
428     }
429     return -1;
430 }