1 // ***************************************************************************
2 // BamHttp_p.cpp (c) 2011 Derek Barnett
3 // Marth Lab, Department of Biology, Boston College
4 // ---------------------------------------------------------------------------
5 // Last modified: 7 November 2011 (DB)
6 // ---------------------------------------------------------------------------
7 // Provides reading/writing of BAM files on HTTP server
8 // ***************************************************************************
10 #include "api/BamAux.h"
11 #include "api/internal/io/BamHttp_p.h"
12 #include "api/internal/io/HttpHeader_p.h"
13 #include "api/internal/io/TcpSocket_p.h"
14 using namespace BamTools;
15 using namespace BamTools::Internal;
30 static const string HTTP_PORT = "80";
31 static const string HTTP_PREFIX = "http://";
32 static const size_t HTTP_PREFIX_LENGTH = 7;
33 static const char COLON_CHAR = ':';
34 static const char SLASH_CHAR = '/';
41 string toLower(const string& s) {
43 const size_t sSize = s.size();
45 for ( size_t i = 0; i < sSize; ++i )
46 out[i] = tolower(s[i]);
50 } // namespace Internal
51 } // namespace BamTools
53 // ------------------------
54 // BamHttp implementation
55 // ------------------------
57 BamHttp::BamHttp(const string& url)
59 , m_socket(new TcpSocket)
63 , m_isUrlParsed(false)
65 , m_endRangeFilePosition(-1)
70 BamHttp::~BamHttp(void) {
72 // close connection & clean up
78 void BamHttp::Close(void) {
81 m_socket->DisconnectFromHost();
83 // clean up request & response
93 // reset state - necessary??
94 m_isUrlParsed = false;
96 m_endRangeFilePosition = -1;
99 bool BamHttp::ConnectSocket(void) {
101 BT_ASSERT_X(m_socket, "null socket?");
103 // any state checks, etc?
104 if ( !m_socket->ConnectToHost(m_hostname, m_port, m_mode) ) {
105 // TODO: set error string
109 // attempt initial request
111 m_endRangeFilePosition = -1;
112 if ( !SendRequest() ) {
113 // TODO: set error string
118 // wait for response from server
119 if ( !ReceiveResponse() ) {
120 // TODO: set error string
129 bool BamHttp::EnsureSocketConnection(void) {
130 if ( m_socket->IsConnected() )
132 else return ConnectSocket();
135 bool BamHttp::IsOpen(void) const {
136 return IBamIODevice::IsOpen() && m_isUrlParsed;
139 bool BamHttp::IsRandomAccess(void) const {
143 bool BamHttp::Open(const IBamIODevice::OpenMode mode) {
145 // BamHttp only supports read-only access
146 if ( mode != IBamIODevice::ReadOnly ) {
147 SetErrorString("BamHttp::Open", "writing on this device is not supported");
152 // attempt connection to socket
153 if ( !ConnectSocket() ) {
154 SetErrorString("BamHttp::Open", m_socket->GetErrorString());
162 void BamHttp::ParseUrl(const string& url) {
164 // make sure url starts with "http://", case-insensitive
167 const size_t prefixFound = tempUrl.find(HTTP_PREFIX);
168 if ( prefixFound == string::npos )
171 // find end of host name portion (first '/' hit after the prefix)
172 const size_t firstSlashFound = tempUrl.find(SLASH_CHAR, HTTP_PREFIX_LENGTH);
173 if ( firstSlashFound == string::npos ) {
174 ; // no slash found... no filename given along with host?
177 // fetch hostname (check for proxy port)
178 string hostname = tempUrl.substr(HTTP_PREFIX_LENGTH, (firstSlashFound - HTTP_PREFIX_LENGTH));
179 const size_t colonFound = hostname.find(COLON_CHAR);
180 if ( colonFound != string::npos ) {
181 ; // TODO: handle proxy port (later, just skip for now)
183 m_hostname = hostname;
187 // store remainder of URL as filename (must be non-empty)
188 string filename = tempUrl.substr(firstSlashFound);
189 if ( filename.empty() )
191 m_filename = filename;
193 // set parsed OK flag
194 m_isUrlParsed = true;
197 int64_t BamHttp::Read(char* data, const unsigned int numBytes) {
199 // if BamHttp not in a valid state
203 // read until hit desired @numBytes
204 int64_t bytesReadSoFar = 0;
205 while ( bytesReadSoFar < numBytes ) {
207 // calculate number of bytes we're going to try to read this iteration
208 const size_t remainingBytes = ( numBytes - bytesReadSoFar );
210 // if socket has access to entire file contents
211 // i.e. we received response with full data (status code == 200)
212 if ( !m_endRangeFilePosition >= 0 ) {
214 // try to read 'remainingBytes' from socket
215 const int64_t socketBytesRead = ReadFromSocket(data+bytesReadSoFar, remainingBytes);
216 if ( socketBytesRead < 0 )
218 bytesReadSoFar += socketBytesRead;
221 // socket has access to a range of data (might already be in buffer)
222 // i.e. we received response with partial data (status code == 206)
225 // there is data left from last request
226 if ( m_endRangeFilePosition > m_filePosition ) {
228 // try to read either the total 'remainingBytes' or whatever we have remaining from last request range
229 const size_t rangeRemainingBytes = m_endRangeFilePosition - m_filePosition;
230 const size_t bytesToRead = std::min(remainingBytes, rangeRemainingBytes);
231 const int64_t socketBytesRead = ReadFromSocket(data+bytesReadSoFar, bytesToRead);
232 if ( socketBytesRead < 0 )
234 bytesReadSoFar += socketBytesRead;
237 // otherwise, this is a 1st-time read OR we already read everything from the last GET request
240 // request for next range
241 if ( !SendRequest(remainingBytes) || !ReceiveResponse() ) {
249 // return actual number bytes successfully read
250 return bytesReadSoFar;
253 int64_t BamHttp::ReadFromSocket(char* data, const unsigned int maxNumBytes) {
255 // try to read 'remainingBytes' from socket
256 const int64_t numBytesRead = m_socket->Read(data, maxNumBytes);
257 if ( numBytesRead < 0 )
259 m_filePosition += numBytesRead;
263 bool BamHttp::ReceiveResponse(void) {
265 // clear any prior response
269 // make sure we're connected
270 if ( !EnsureSocketConnection() )
273 // read response header from socket
274 RaiiBuffer header(0x10000);
276 while ( m_socket->Read(header.Buffer + l, 1) >= 0 ) {
277 if ( header.Buffer[l] == '\n' && l >= 3 ) {
278 if (strncmp(header.Buffer + l - 3, "\r\n\r\n", 4) == 0)
283 string responseHeader;
284 responseHeader.resize(l+1);
285 for ( size_t i = 0; i < l; ++i )
286 responseHeader[i] = header.Buffer[i];
288 if ( responseHeader.empty() ) {
289 // TODO: set error string
294 // create response from header text
295 m_response = new HttpResponseHeader(responseHeader);
296 if ( !m_response->IsValid() ) {
297 // TODO: set error string
302 // if we got range response as requested
303 if ( m_response->GetStatusCode() == 206 )
306 // if we got the full file contents instead of range
307 else if ( m_response->GetStatusCode() == 200 ) {
309 // skip up to current file position
310 RaiiBuffer tmp(0x8000);
311 int64_t numBytesRead = 0;
312 while ( numBytesRead < m_filePosition ) {
313 int64_t result = ReadFromSocket(tmp.Buffer, 0x8000);
318 numBytesRead += result;
325 // on any other reponse status
326 // TODO: set error string
331 bool BamHttp::Seek(const int64_t& position) {
333 // if HTTP device not in a valid state
335 // TODO: set error string
339 // discard socket's buffer contents, update positions, & return success
340 m_socket->ClearBuffer();
341 m_filePosition = position;
342 m_endRangeFilePosition = -1;
346 bool BamHttp::SendRequest(const size_t numBytes) {
348 // remove any currently active request
352 // create range string
353 m_endRangeFilePosition = m_filePosition + numBytes;
354 stringstream range("bytes=");
355 range << m_filePosition << "-" << m_endRangeFilePosition;
357 // make sure we're connected
358 if ( !EnsureSocketConnection() )
362 m_request = new HttpRequestHeader("GET", m_filename);
363 m_request->SetField("Host", m_hostname);
364 m_request->SetField("Range", range.str());
366 // write request to socket
367 const string requestHeader = m_request->ToString();
368 const size_t headerSize = requestHeader.size();
369 return ( WriteToSocket(requestHeader.c_str(), headerSize) == headerSize );
372 int64_t BamHttp::Tell(void) const {
373 return ( IsOpen() ? m_filePosition : -1 );
376 int64_t BamHttp::Write(const char* data, const unsigned int numBytes) {
379 BT_ASSERT_X(false, "BamHttp::Write : write-mode not supported on this device");
383 int64_t BamHttp::WriteToSocket(const char* data, const unsigned int numBytes) {
384 if ( !EnsureSocketConnection() )
386 m_socket->ClearBuffer();
387 return m_socket->Write(data, numBytes);