From 86f9fbefa58d91e151190c969216c751573bc664 Mon Sep 17 00:00:00 2001 From: Mike Buland Date: Thu, 4 Oct 2007 10:46:46 +0000 Subject: Discovered that the Bu::Client::disconnect() function didn't do anything. That has been fixed, it now safely disconnects after emptying the Client's outgoing buffer. Added some more helpers to Bu::FString. Added the beginings of ProtocolHttp using a new method for processing protocols that's based more strongly on an NFA state machine, this makes sense, but I never had the desire to actually try implementing it before. It's working pretty well. --- src/client.cpp | 13 +++- src/client.h | 6 +- src/fstring.h | 24 ++++++-- src/protocolhttp.cpp | 163 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/protocolhttp.h | 63 ++++++++++++++++++++ src/server.cpp | 14 +++++ 6 files changed, 275 insertions(+), 8 deletions(-) create mode 100644 src/protocolhttp.cpp create mode 100644 src/protocolhttp.h diff --git a/src/client.cpp b/src/client.cpp index d416700..19d2778 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -11,7 +11,8 @@ Bu::Client::Client( Bu::Socket *pSocket ) : pSocket( pSocket ), pProto( NULL ), - nRBOffset( 0 ) + nRBOffset( 0 ), + bWantsDisconnect( false ) { } @@ -205,5 +206,15 @@ const Bu::Socket *Bu::Client::getSocket() const void Bu::Client::disconnect() { + bWantsDisconnect = true; } +bool Bu::Client::wantsDisconnect() +{ + return bWantsDisconnect; +} + +void Bu::Client::close() +{ + pSocket->close(); +} diff --git a/src/client.h b/src/client.h index 4188a49..0b670e2 100644 --- a/src/client.h +++ b/src/client.h @@ -44,13 +44,12 @@ namespace Bu void clearProtocol(); bool isOpen(); + void close(); const Bu::Socket *getSocket() const; - /** - *@todo Make this not suck. - */ void disconnect(); + bool wantsDisconnect(); private: Bu::Socket *pSocket; @@ -58,6 +57,7 @@ namespace Bu Bu::FString sReadBuf; int nRBOffset; Bu::FString sWriteBuf; + bool bWantsDisconnect; }; } diff --git a/src/fstring.h b/src/fstring.h index 9de6a56..0306aa1 100644 --- a/src/fstring.h +++ b/src/fstring.h @@ -605,10 +605,26 @@ namespace Bu /** * Find the index of the first occurrance of (sText) - *@param sText (const char *) The string to search for. + *@param sText (const chr *) The string to search for. *@returns (long) The index of the first occurrance. -1 for not found. */ - long find( const char *sText ) + long find( const chr cChar ) + { + flatten(); + for( long j = 0; j < pFirst->nLength; j++ ) + { + if( pFirst->pData[j] == cChar ) + return j; + } + return -1; + } + + /** + * Find the index of the first occurrance of cChar + *@param cChar (const chr) The character to search for. + *@returns (long) The index of the first occurrance. -1 for not found. + */ + long find( const chr *sText ) { long nTLen = strlen( sText ); flatten(); @@ -622,10 +638,10 @@ namespace Bu /** * Do a reverse search for (sText) - *@param sText (const char *) The string to search for. + *@param sText (const chr *) The string to search for. *@returns (long) The index of the last occurrance. -1 for not found. */ - long rfind( const char *sText ) + long rfind( const chr *sText ) { long nTLen = strlen( sText ); flatten(); diff --git a/src/protocolhttp.cpp b/src/protocolhttp.cpp new file mode 100644 index 0000000..6f2ae68 --- /dev/null +++ b/src/protocolhttp.cpp @@ -0,0 +1,163 @@ +#include +#include +#include +#include "protocolhttp.h" + +#define CRLF "\x0D\x0A" +#define CR '\x0D' +#define LF '\x0A' + +using namespace Bu; + +Bu::ProtocolHttp::ProtocolHttp() +{ +} + +Bu::ProtocolHttp::~ProtocolHttp() +{ +} + +void Bu::ProtocolHttp::onNewConnection( Bu::Client *pClient ) +{ + this->pClient = pClient; + + iState = 0; +} + +#define SDB( i ) printf("state %d: %d, \"%s\"\n", i, tt, sToken.getStr() ) + +void Bu::ProtocolHttp::onNewData( Bu::Client *pClient ) +{ + for(;;) + { + Bu::FString sToken; + TokenType tt = getToken( sToken ); + + if( tt == ttOutOfData ) + return; + + switch( iState ) + { + case 0: // Initial header + SDB( 0 ); + sMethod = sToken; + iState = 1; + break; + + case 1: // Misc headers + SDB( 1 ); + sPath = sToken; + iState = 2; + break; + + case 2: // Content + SDB( 2 ); + if( strncmp( sToken.getStr(), "HTTP/", 5 ) ) + { + printf("not http, disconnect.\n"); + pClient->disconnect(); + return; + } + else + { + char *s, *s2; + s = sToken.getStr()+5; + iMajor = strtol( s, &s2, 10 ); + iMinor = strtol( s2+1, NULL, 10 ); + printf("HTTP: %d.%d\n", iMajor, iMinor ); + iState = 3; + } + break; + + case 3: + SDB( 3 ); + pClient->disconnect(); + return; + break; + } + } +} + +Bu::ProtocolHttp::TokenType Bu::ProtocolHttp::getToken( Bu::FString &line ) +{ + char s; + int jmax = pClient->getInputSize(); + bool bNonWS = false; + + for( int j = 0; j < jmax; j++ ) + { + pClient->peek( &s, 1, j ); + if( iState > 2 && isSeperator( s ) ) + { + if( j == 0 ) + { + line += s; + pClient->seek( 1 ); + return ttSeperator; + } + else + { + pClient->seek( j ); + return ttString; + } + } + else if( isWS( s ) ) + { + if( bNonWS ) + { + pClient->seek( j ); + return ttString; + } + } + else if( s == CR ) + { + if( pClient->getInputSize() < 3 ) + return ttOutOfData; + + char ss[2]; + pClient->peek( ss, 2, j+1 ); + if( ss[0] == LF && ss[1] != ' ' && ss[1] != '\t' ) + { + if( bNonWS ) + { + pClient->seek( j ); + return ttString; + } + else + { + pClient->seek( 2 ); + return ttNewline; + } + } + + j += 2; + if( bNonWS ) + { + pClient->seek( j ); + return ttString; + } + } + else + { + line += s; + bNonWS = true; + } + } + + return ttOutOfData; +} + +bool Bu::ProtocolHttp::isWS( char buf ) +{ + return (buf == ' ' || buf == '\t'); +} + +bool Bu::ProtocolHttp::isSeperator( char buf ) +{ + return (buf == '(' || buf == ')' || buf == '<' || buf == '>' || + buf == '@' || buf == ',' || buf == ';' || buf == ':' || + buf == '\\' || buf == '\"' || buf == '/' || buf == '[' || + buf == ']' || buf == '?' || buf == '=' || buf == '{' || + buf == '}' ); +} + diff --git a/src/protocolhttp.h b/src/protocolhttp.h new file mode 100644 index 0000000..e10cb23 --- /dev/null +++ b/src/protocolhttp.h @@ -0,0 +1,63 @@ +#ifndef BU_PROTOCOL_HTTP_H +#define BU_PROTOCOL_HTTP_H + +#include +#include + +#include "bu/protocol.h" +#include "bu/client.h" +#include "bu/fstring.h" + +namespace Bu +{ + /** + * + */ + class ProtocolHttp : public Protocol + { + public: /* Types */ + typedef Bu::List TokenList; + + public: /* Interface */ + ProtocolHttp(); + virtual ~ProtocolHttp(); + + virtual void onNewConnection( Bu::Client *pClient ); + virtual void onNewData( Bu::Client *pClient ); + + private: + enum TokenType + { + ttOutOfData, + ttString, + ttNewline, + ttDoubleNewline, + ttSeperator + }; + /** + * Read an HTTP line, this is up to the first CRLF that isn't followed + * by a continuation character, converting it to one line as it reads. + *@param line All data read will be appended to line, even if no + * end-of-line is read. + *@returns True if an end-of-line is read and the line should be + * processed, false if the end-of-line has not been reached, and more + * data needs to be read before this operation can continue. + */ + TokenType getToken( Bu::FString &line ); + bool isWS( char buf ); + bool isSeperator( char buf ); + + private: /* state */ + Bu::Client *pClient; + TokenList lTokens; + + int iState; + + Bu::FString sMethod; + Bu::FString sPath; + int iMajor; + int iMinor; + }; +} + +#endif diff --git a/src/server.cpp b/src/server.cpp index 53b4301..29d4822 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -74,12 +74,26 @@ void Bu::Server::scan() } } + Bu::List lDelete; // Now we just try to write all the pending data on all the sockets. // this could be done better eventually, if we care about the socket // wanting to accept writes (using a select). for( ClientHash::iterator i = hClients.begin(); i != hClients.end(); i++ ) { (*i)->processOutput(); + if( (*i)->wantsDisconnect() ) + { + lDelete.append( i.getKey() ); + } + } + + for( Bu::List::iterator i = lDelete.begin(); i != lDelete.end(); i++ ) + { + Client *pClient = hClients.get( *i ); + onClosedConnection( pClient ); + pClient->close(); + hClients.erase( *i ); + FD_CLR( *i, &fdActive ); } } -- cgit v1.2.3