// BamHttp_p.cpp (c) 2011 Derek Barnett
// Marth Lab, Department of Biology, Boston College
// ---------------------------------------------------------------------------
-// Last modified: 7 November 2011 (DB)
+// Last modified: 8 December 2011 (DB)
// ---------------------------------------------------------------------------
// Provides reading/writing of BAM files on HTTP server
// ***************************************************************************
static const string HTTP_PORT = "80";
static const string HTTP_PREFIX = "http://";
static const size_t HTTP_PREFIX_LENGTH = 7;
-static const char COLON_CHAR = ':';
-static const char SLASH_CHAR = '/';
+
+static const string DOUBLE_NEWLINE = "\n\n";
+
+static const string GET_METHOD = "GET";
+static const string HOST_HEADER = "Host";
+static const string RANGE_HEADER = "Range";
+static const string BYTES_PREFIX = "bytes=";
+
+static const char HOST_SEPARATOR = '/';
+static const char PROXY_SEPARATOR = ':';
// -----------------
// utility methods
// -----------------
+static inline
+bool endsWith(const string& source, const string& pattern) {
+ return ( source.find(pattern) == (source.length() - pattern.length()) );
+}
+
static inline
string toLower(const string& s) {
string out;
void BamHttp::ParseUrl(const string& url) {
+ // clear flag to start
+ m_isUrlParsed = false;
+
// make sure url starts with "http://", case-insensitive
string tempUrl(url);
toLower(tempUrl);
return;
// find end of host name portion (first '/' hit after the prefix)
- const size_t firstSlashFound = tempUrl.find(SLASH_CHAR, HTTP_PREFIX_LENGTH);
+ const size_t firstSlashFound = tempUrl.find(HOST_SEPARATOR, HTTP_PREFIX_LENGTH);
if ( firstSlashFound == string::npos ) {
; // no slash found... no filename given along with host?
}
// fetch hostname (check for proxy port)
string hostname = tempUrl.substr(HTTP_PREFIX_LENGTH, (firstSlashFound - HTTP_PREFIX_LENGTH));
- const size_t colonFound = hostname.find(COLON_CHAR);
+ const size_t colonFound = hostname.find(PROXY_SEPARATOR);
if ( colonFound != string::npos ) {
; // TODO: handle proxy port (later, just skip for now)
} else {
// if socket has access to entire file contents
// i.e. we received response with full data (status code == 200)
- if ( !m_endRangeFilePosition >= 0 ) {
+ if ( m_endRangeFilePosition < 0 ) {
// try to read 'remainingBytes' from socket
const int64_t socketBytesRead = ReadFromSocket(data+bytesReadSoFar, remainingBytes);
- if ( socketBytesRead < 0 )
+ if ( socketBytesRead < 0 ) // error
return -1;
+ else if ( socketBytesRead == 0 ) // EOF
+ return bytesReadSoFar;
bytesReadSoFar += socketBytesRead;
+ m_filePosition += socketBytesRead;
}
// socket has access to a range of data (might already be in buffer)
// there is data left from last request
if ( m_endRangeFilePosition > m_filePosition ) {
- // try to read either the total 'remainingBytes' or whatever we have remaining from last request range
+ // try to read either the total 'remainingBytes' or
+ // whatever we have remaining from last request range
const size_t rangeRemainingBytes = m_endRangeFilePosition - m_filePosition;
const size_t bytesToRead = std::min(remainingBytes, rangeRemainingBytes);
const int64_t socketBytesRead = ReadFromSocket(data+bytesReadSoFar, bytesToRead);
- if ( socketBytesRead < 0 )
+ if ( socketBytesRead < 0 ) // error
return -1;
+ else if ( socketBytesRead == 0 ) // EOF
+ return bytesReadSoFar;
bytesReadSoFar += socketBytesRead;
+ m_filePosition += socketBytesRead;
}
- // otherwise, this is a 1st-time read OR we already read everything from the last GET request
+ // otherwise, this is a 1st-time read or
+ // we already read everything from the last GET request
else {
// request for next range
const int64_t numBytesRead = m_socket->Read(data, maxNumBytes);
if ( numBytesRead < 0 )
return -1;
- m_filePosition += numBytesRead;
return numBytesRead;
}
if ( !EnsureSocketConnection() )
return false;
- // read response header from socket
- RaiiBuffer header(0x10000);
- size_t l = 0;
- while ( m_socket->Read(header.Buffer + l, 1) >= 0 ) {
- if ( header.Buffer[l] == '\n' && l >= 3 ) {
- if (strncmp(header.Buffer + l - 3, "\r\n\r\n", 4) == 0)
- break;
- }
- ++l;
- }
+ // fetch header, up until double new line
string responseHeader;
- responseHeader.resize(l+1);
- for ( size_t i = 0; i < l; ++i )
- responseHeader[i] = header.Buffer[i];
+ do {
+ // read line & append to full header
+ const string headerLine = m_socket->ReadLine();
+ responseHeader += headerLine;
+
+ } while ( !endsWith(responseHeader, DOUBLE_NEWLINE) );
+ // sanity check
if ( responseHeader.empty() ) {
// TODO: set error string
Close();
RaiiBuffer tmp(0x8000);
int64_t numBytesRead = 0;
while ( numBytesRead < m_filePosition ) {
- int64_t result = ReadFromSocket(tmp.Buffer, 0x8000);
- if ( result < 0 ) {
+
+ const int64_t remaining = m_filePosition - numBytesRead;
+ const size_t bytesToRead = static_cast<size_t>( (remaining > 0x8000) ? 0x8000 : remaining );
+ const int64_t socketBytesRead = ReadFromSocket(tmp.Buffer, bytesToRead);
+ if ( socketBytesRead < 0 ) { // error
Close();
return false;
}
- numBytesRead += result;
+ else if ( socketBytesRead == 0 ) // EOF
+ break;
+
+ numBytesRead += socketBytesRead;
}
// return success
- return true;
+ return ( numBytesRead == m_filePosition);
}
// on any other reponse status
return false;
}
-bool BamHttp::Seek(const int64_t& position) {
+bool BamHttp::Seek(const int64_t& position, const int origin) {
// if HTTP device not in a valid state
if ( !IsOpen() ) {
// discard socket's buffer contents, update positions, & return success
m_socket->ClearBuffer();
- m_filePosition = position;
- m_endRangeFilePosition = -1;
+
+ if ( origin == SEEK_CUR )
+ m_filePosition += position;
+ else if ( origin == SEEK_SET )
+ m_filePosition = position;
+ else {
+ // TODO: set error string
+ return false;
+ }
+ m_endRangeFilePosition = m_filePosition;
return true;
}
// create range string
m_endRangeFilePosition = m_filePosition + numBytes;
- stringstream range("bytes=");
- range << m_filePosition << "-" << m_endRangeFilePosition;
+ stringstream range("");
+ range << BYTES_PREFIX << m_filePosition << '-' << m_endRangeFilePosition;
// make sure we're connected
if ( !EnsureSocketConnection() )
return false;
// create request
- m_request = new HttpRequestHeader("GET", m_filename);
- m_request->SetField("Host", m_hostname);
- m_request->SetField("Range", range.str());
+ m_request = new HttpRequestHeader(GET_METHOD, m_filename);
+ m_request->SetField(HOST_HEADER, m_hostname);
+ m_request->SetField(RANGE_HEADER, range.str());
// write request to socket
const string requestHeader = m_request->ToString();
(void)data;
(void)numBytes;
BT_ASSERT_X(false, "BamHttp::Write : write-mode not supported on this device");
- return 0;
+ SetErrorString("BamHttp::Write", "write-mode not supported on this device");
+ return -1;
}
int64_t BamHttp::WriteToSocket(const char* data, const unsigned int numBytes) {
- if ( !EnsureSocketConnection() )
- return false;
+ if ( !m_socket->IsConnected() )
+ return -1;
m_socket->ClearBuffer();
return m_socket->Write(data, numBytes);
}