• Main Page
  • Related Pages
  • Modules
  • Namespaces
  • Classes
  • Files
  • File List
  • File Members

src/web/HttpRequest.cpp

00001 /*
00002  *   This file is part of the Standard Portable Library (SPL).
00003  *
00004  *   SPL is free software: you can redistribute it and/or modify
00005  *   it under the terms of the GNU General Public License as published by
00006  *   the Free Software Foundation, either version 3 of the License, or
00007  *   (at your option) any later version.
00008  *
00009  *   SPL is distributed in the hope that it will be useful,
00010  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
00011  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00012  *   GNU General Public License for more details.
00013  *
00014  *   You should have received a copy of the GNU General Public License
00015  *   along with SPL.  If not, see <http://www.gnu.org/licenses/>.
00016  */
00017 
00018 // RFC 2616 http://www.w3.org/Protocols/rfc2616/rfc2616.html
00019 
00020 #include <spl/Int32.h>
00021 #include <spl/web/HttpRequest.h>
00022 #include <spl/net/TcpSocket.h>
00023 
00024 using namespace spl;
00025 
00026 HttpRequest::HttpRequest()
00027 :       m_method("GET"),
00028         m_uri(),
00029         m_httpVersion("HTTP/1.0"),
00030         m_header(),
00031         m_body(NULL),
00032         m_state(HTTPREQ_STATE_METHOD),
00033         m_accum(128)
00034 {
00035 }
00036 
00037 HttpRequest::HttpRequest(const String& uri)
00038 :       m_method("GET"),
00039         m_uri(uri),
00040         m_httpVersion("HTTP/1.0"),
00041         m_header(),
00042         m_body(NULL),
00043         m_state(HTTPREQ_STATE_METHOD),
00044         m_accum(128)
00045 {
00046 }
00047 
00048 HttpRequest::HttpRequest(const HttpRequest& req)
00049 :       m_method(req.m_method),
00050         m_uri(req.m_uri),
00051         m_httpVersion(req.m_httpVersion),
00052         m_header(req.m_header),
00053         m_body(NULL),
00054         m_state(req.m_state),
00055         m_accum(req.m_accum)
00056 {
00057         if ( NULL != req.m_body )
00058         {
00059                 m_body = req.m_body->Clone();
00060         }
00061 }
00062 
00063 HttpRequest::~HttpRequest()
00064 {
00065         if ( NULL != m_body )
00066         {
00067                 delete m_body;
00068         }
00069 }
00070 
00071 HttpRequest& HttpRequest::operator =(const HttpRequest& req)
00072 {
00073         m_method = req.m_method;
00074         m_uri = req.m_uri;
00075         m_httpVersion = req.m_httpVersion;
00076         m_header = req.m_header;
00077         m_state = req.m_state;
00078         m_accum = req.m_accum;
00079 
00080         if ( NULL != m_body )
00081         {
00082                 delete m_body;
00083         }
00084         if ( NULL == req.m_body )
00085         {
00086                 m_body = NULL;
00087         }
00088         else
00089         {
00090                 m_body = req.m_body->Clone();
00091         }
00092 
00093         return *this;
00094 }
00095 
00096 bool HttpRequest::ParseLine(const byte *data, int len, int *pos)
00097 {
00098         while ( *pos < len )
00099         {
00100                 char ch = (char)data[(*pos)++];
00101                 if ( ch == '\r' )
00102                 {
00103                         continue;
00104                 }
00105                 if ( ch == '\n' )
00106                 {
00107                         return true;
00108                 }
00109                 m_accum.Append( ch );
00110         }
00111         return false;
00112 }
00113 
00114 void HttpRequest::Parse(const Array<byte>& data, int len)
00115 {
00116         int pos = 0;
00117 
00118         while ( pos < len )
00119         {
00120                 switch ( m_state )
00121                 {
00122                 case HTTPREQ_STATE_METHOD:
00123                         while ( pos < len )
00124                         {
00125                                 char ch = (char)data[pos++];
00126                                 if ( ch == ' ' ) 
00127                                 {
00128                                         m_method = *m_accum.ToString();
00129                                         m_accum.SetLength(0);
00130                                         m_state = HTTPREQ_STATE_URI;
00131                                         break;
00132                                 }
00133                                 m_accum.Append( ch );
00134                                 if ( m_accum.Length() > 6 )
00135                                 {
00136                                         throw new Exception(String::Format("%s %s", "Invalid HTTP method", m_accum.ToString()->GetChars())->GetChars());
00137                                 }
00138                         }
00139                         break;
00140 
00141                 case HTTPREQ_STATE_URI:
00142                         while ( pos < len )
00143                         {
00144                                 char ch = (char)data[pos++];
00145                                 if ( ch == ' ' ) 
00146                                 {
00147                                         StringPtr str = m_accum.ToString();
00148                                         m_uri.Parse(*str);
00149                                         m_uri.ValidateMem();
00150                                         m_accum.SetLength(0);
00151                                         m_state = HTTPREQ_STATE_VERSION;
00152                                         break;
00153                                 }
00154                                 m_accum.Append( ch );
00155                         }
00156                         break;
00157 
00158                 case HTTPREQ_STATE_VERSION:
00159                         if ( ParseLine(data, len, &pos) )
00160                         {
00161                                 m_httpVersion = *m_accum.ToString();
00162                                 m_accum.SetLength(0);
00163                                 m_state = HTTPREQ_STATE_HEADERS;
00164                         }
00165                         break;
00166 
00167                 case HTTPREQ_STATE_HEADERS:
00168                         if ( ParseLine(data, len, &pos) )
00169                         {
00170                                 if ( m_accum.Length() == 0 )
00171                                 {
00172                                         m_state = HTTPREQ_STATE_BODY;
00173                                         break;
00174                                 }
00175                                 StringPtr header = m_accum.ToString();
00176                                 m_accum.SetLength(0);
00177 
00178                                 int colonPos = header->IndexOf(':');
00179                                 if ( colonPos < 0 )
00180                                 {
00181                                         throw new Exception(String::Format("%s %s", "Invalid HTTP header format", header->GetChars())->GetChars());
00182                                 }
00183                                 StringPtr key = header->Substring(0, colonPos);
00184                                 key = key->Trim()->ToLower();
00185                                 StringPtr val = header->Substring(colonPos + 1);
00186                                 m_header.Header(*key) = *val->Trim();
00187                         }
00188                         break;
00189 
00190                 case HTTPREQ_STATE_BODY:
00191                         if ( NULL == m_body )
00192                         {
00193                                 if ( m_header.HasHeader("Content-Type") )
00194                                 {
00195                                         String& contentType = m_header.Header("Content-Type");
00196                                         if ( contentType.Equals("application/x-www-form-urlencoded") )
00197                                         {
00198                                                 m_body = new HttpRequestBodyFormData();
00199                                         }
00200                                         else
00201                                         {
00202                                                 m_body = new HttpRequestBodyFormData();
00203                                         }
00204                                 }
00205                                 else
00206                                 {
00207                                         m_body = new HttpRequestBodyFormData();
00208                                 }
00209                         }
00210                         int contentLen;
00211                         if ( m_header.HasHeader("Content-Length") )
00212                         {
00213                                 String& scl = m_header.Header("Content-Length");
00214                                 ASSERT( Int32::IsInt(scl) );
00215                                 contentLen = Int32::Parse(scl);
00216                         }
00217                         else
00218                         {
00219                                 contentLen = 0;
00220                         }
00221 
00222                         m_body->Parse( data, pos, len - pos, contentLen );
00223                         
00224                         break;
00225 
00226                 default:
00227                         throw new Exception("HttpRequest::Parse: corrupted state.");
00228                 }
00229         }
00230 }
00231 
00232 bool HttpRequest::IsComplete()
00233 {
00234         if ( m_header.Count() == 0 )
00235         {
00236                 return false;
00237         }
00238 
00239         int contentLen;
00240         if ( m_header.HasHeader("Content-Length") )
00241         {
00242                 String& scl = m_header.Header("Content-Length");
00243                 ASSERT( Int32::IsInt(scl) );
00244                 contentLen = Int32::Parse(scl);
00245         }
00246         else
00247         {
00248                 contentLen = 0;
00249         }
00250 
00251         if ( NULL != m_body && contentLen > 0 )
00252         {
00253                 return m_body->ByteCount() >= contentLen;
00254         }
00255         else
00256         {
00257                 // request does not have a content lenth, should check Transfer-Encoding and media type "multipart/byteranges".
00258                 return m_state == HTTPREQ_STATE_BODY;
00259         }
00260 }
00261 
00262 HttpResponse *HttpRequest::Send()
00263 {
00264         HttpResponse *resp = new HttpResponse();
00265         
00266         try
00267         {
00268                 TcpSocket sock(m_uri.Host(), m_uri.Port());
00269                 sock.Connect();
00270                 sock.SetRecvTimeout( 30 * 1000 );
00271                 sock.SetBlocking();
00272 
00273                 if ( NULL == m_body )
00274                 {
00275                         m_header.ContentLength() = "0";
00276                 }
00277                 else
00278                 {
00279                         m_header.ContentLength() = *Int32::ToString(m_body->ByteCount());
00280                 }
00281 
00282                 if ( m_header.ContentType() == "" )
00283                 {
00284                         m_header.ContentType() = "Content-Type", "application/x-www-form-urlencoded";
00285                 }
00286 
00287                 StringPtr httpReq = ToString();
00288                 sock.GetStream()->Write( httpReq->ToByteArray() );
00289 
00290                 IStreamPtr reader = sock.GetStream();
00291                 resp->Parse(reader);
00292                 sock.Close();
00293         }
00294         catch ( Exception *ex )
00295         {
00296                 delete resp;
00297                 throw ex;
00298         }
00299 
00300         return resp;
00301 }
00302 
00303 StringPtr HttpRequest::ToString() const
00304 {
00305         StringBuffer buf;
00306 
00307         buf.Append( m_method );
00308         buf.Append( ' ' );
00309         buf.Append( m_uri.AbsolutePath() );
00310         buf.Append( ' ' );
00311         buf.Append( m_httpVersion );
00312         buf.Append( "\r\n" );
00313         buf.Append( m_header.ToString() );
00314 
00315         if ( NULL != m_body )
00316         {
00317                 buf.Append( m_body->ToString() );
00318         }
00319 
00320         return buf.ToString();
00321 }
00322 
00323 #ifdef DEBUG
00324 void HttpRequest::ValidateMem() const
00325 {
00326         m_method.ValidateMem();
00327         m_httpVersion.ValidateMem();
00328         m_accum.ValidateMem();
00329 
00330         if ( NULL != m_body )
00331         {
00332                 ASSERT_PTR(m_body);
00333                 m_body->ValidateMem();
00334         }
00335 }
00336 
00337 void HttpRequest::CheckMem() const
00338 {
00339         m_method.CheckMem();
00340         m_httpVersion.CheckMem();
00341         m_accum.CheckMem();
00342 
00343         if ( NULL != m_body )
00344         {
00345                 DEBUG_NOTE_MEM( m_body );
00346                 m_body->CheckMem();
00347         }
00348 }
00349 #endif