Changeset 2428
- Timestamp:
- 03/01/2009 08:59:47 (3 years ago)
- Location:
- box/trunk
- Files:
-
- 7 edited
-
lib/httpserver/HTTPException.txt (modified) (1 diff)
-
lib/httpserver/HTTPQueryDecoder.cpp (modified) (1 diff)
-
lib/httpserver/HTTPRequest.cpp (modified) (3 diffs)
-
lib/httpserver/HTTPRequest.h (modified) (6 diffs)
-
lib/httpserver/HTTPResponse.cpp (modified) (14 diffs)
-
lib/httpserver/HTTPResponse.h (modified) (4 diffs)
-
test/httpserver/testhttpserver.cpp (modified) (3 diffs)
Legend:
- Unmodified
- Added
- Removed
-
box/trunk/lib/httpserver/HTTPException.txt
r926 r2428 2 2 3 3 Internal 0 4 RequestReadFailed 15 RequestAlreadyBeenRead 24 RequestReadFailed 1 5 RequestAlreadyBeenRead 2 6 6 BadRequest 3 7 UnknownResponseCodeUsed 4 8 NoContentTypeSet 5 9 POSTContentTooLong 6 10 CannotSetRedirectIfReponseHasData 7 11 CannotSetNotFoundIfReponseHasData 8 12 NotImplemented 9 7 UnknownResponseCodeUsed 4 8 NoContentTypeSet 5 9 POSTContentTooLong 6 10 CannotSetRedirectIfReponseHasData 7 11 CannotSetNotFoundIfReponseHasData 8 12 NotImplemented 9 13 RequestNotInitialised 10 14 BadResponse 11 15 ResponseReadFailed 12 -
box/trunk/lib/httpserver/HTTPQueryDecoder.cpp
r926 r2428 20 20 // 21 21 // Function 22 // Name: HTTPQueryDecoder::HTTPQueryDecoder(HTTPRequest::Query_t &) 23 // Purpose: Constructor. Pass in the query contents you want to decode 24 // the query string into. 22 // Name: HTTPQueryDecoder::HTTPQueryDecoder( 23 // HTTPRequest::Query_t &) 24 // Purpose: Constructor. Pass in the query contents you want 25 // to decode the query string into. 25 26 // Created: 26/3/04 26 27 // -
box/trunk/lib/httpserver/HTTPRequest.cpp
r2423 r2428 53 53 // 54 54 // Function 55 // Name: HTTPRequest::HTTPRequest(enum Method, 56 // const std::string&) 57 // Purpose: Alternate constructor for hand-crafted requests 58 // Created: 03/01/09 59 // 60 // -------------------------------------------------------------------------- 61 HTTPRequest::HTTPRequest(enum Method method, const std::string& rURI) 62 : mMethod(method), 63 mRequestURI(rURI), 64 mHostPort(80), // default if not specified 65 mHTTPVersion(HTTPVersion_1_1), 66 mContentLength(-1), 67 mpCookies(0), 68 mClientKeepAliveRequested(false) 69 { 70 } 71 72 73 74 // -------------------------------------------------------------------------- 75 // 76 // Function 55 77 // Name: HTTPRequest::~HTTPRequest() 56 78 // Purpose: Destructor … … 73 95 // Function 74 96 // Name: HTTPRequest::Read(IOStreamGetLine &, int) 75 // Purpose: Read the request from an IOStreamGetLine (and attached stream) 76 // Returns false if there was no valid request, probably due to 77 // a kept-alive connection closing. 97 // Purpose: Read the request from an IOStreamGetLine (and 98 // attached stream). 99 // Returns false if there was no valid request, 100 // probably due to a kept-alive connection closing. 78 101 // Created: 26/3/04 79 102 // … … 284 307 } 285 308 309 return true; 310 } 311 312 // -------------------------------------------------------------------------- 313 // 314 // Function 315 // Name: HTTPRequest::Write(IOStream &, int) 316 // Purpose: Write the request to an IOStream using HTTP. 317 // Created: 03/01/09 318 // 319 // -------------------------------------------------------------------------- 320 bool HTTPRequest::Write(IOStream &rStream, int Timeout) 321 { 322 switch (mMethod) 323 { 324 case Method_UNINITIALISED: 325 THROW_EXCEPTION(HTTPException, RequestNotInitialised); break; 326 case Method_UNKNOWN: 327 THROW_EXCEPTION(HTTPException, BadRequest); break; 328 case Method_GET: 329 rStream.Write("GET"); break; 330 case Method_HEAD: 331 rStream.Write("HEAD"); break; 332 case Method_POST: 333 rStream.Write("POST"); break; 334 } 335 336 rStream.Write(" "); 337 rStream.Write(mRequestURI.c_str()); 338 rStream.Write(" "); 339 340 switch (mHTTPVersion) 341 { 342 case HTTPVersion_0_9: rStream.Write("HTTP/0.9"); break; 343 case HTTPVersion_1_0: rStream.Write("HTTP/1.0"); break; 344 case HTTPVersion_1_1: rStream.Write("HTTP/1.1"); break; 345 default: 346 THROW_EXCEPTION(HTTPException, NotImplemented); 347 } 348 349 rStream.Write("\n"); 350 std::ostringstream oss; 351 352 if (mContentLength != -1) 353 { 354 oss << "Content-Length: " << mContentLength << "\n"; 355 } 356 357 if (mContentType != "") 358 { 359 oss << "Content-Type: " << mContentType << "\n"; 360 } 361 362 if (mHostName != "") 363 { 364 if (mHostPort != 80) 365 { 366 oss << "Host: " << mHostName << ":" << mHostPort << 367 "\n"; 368 } 369 else 370 { 371 oss << "Host: " << mHostName << "\n"; 372 } 373 } 374 375 if (mpCookies) 376 { 377 THROW_EXCEPTION(HTTPException, NotImplemented); 378 } 379 380 if (mClientKeepAliveRequested) 381 { 382 oss << "Connection: keep-alive\n"; 383 } 384 else 385 { 386 oss << "Connection: close\n"; 387 } 388 389 rStream.Write(oss.str().c_str()); 390 rStream.Write("\n"); 391 286 392 return true; 287 393 } -
box/trunk/lib/httpserver/HTTPRequest.h
r926 r2428 28 28 { 29 29 public: 30 enum Method 31 { 32 Method_UNINITIALISED = -1, 33 Method_UNKNOWN = 0, 34 Method_GET = 1, 35 Method_HEAD = 2, 36 Method_POST = 3 37 }; 38 30 39 HTTPRequest(); 40 HTTPRequest(enum Method method, const std::string& rURI); 31 41 ~HTTPRequest(); 32 42 private: … … 41 51 enum 42 52 { 43 Method_UNINITIALISED = -1,44 Method_UNKNOWN = 0,45 Method_GET = 1,46 Method_HEAD = 2,47 Method_POST = 348 };49 50 enum51 {52 53 HTTPVersion__MajorMultiplier = 1000, 53 54 HTTPVersion_0_9 = 9, … … 57 58 58 59 bool Read(IOStreamGetLine &rGetLine, int Timeout); 60 bool Write(IOStream &rStream, int Timeout); 59 61 60 62 typedef std::map<std::string, std::string> CookieJar_t; … … 68 70 // 69 71 // -------------------------------------------------------------------------- 70 intGetMethod() const {return mMethod;}72 enum Method GetMethod() const {return mMethod;} 71 73 const std::string &GetRequestURI() const {return mRequestURI;} 72 74 const std::string &GetHostName() const {return mHostName;} // note: request does splitting of Host: header … … 92 94 // -------------------------------------------------------------------------- 93 95 bool GetClientKeepAliveRequested() const {return mClientKeepAliveRequested;} 96 void SetClientKeepAliveRequested(bool keepAlive) 97 { 98 mClientKeepAliveRequested = keepAlive; 99 } 94 100 95 101 private: … … 98 104 99 105 private: 100 intmMethod;106 enum Method mMethod; 101 107 std::string mRequestURI; 102 108 std::string mHostName; -
box/trunk/lib/httpserver/HTTPResponse.cpp
r926 r2428 14 14 15 15 #include "HTTPResponse.h" 16 #include "IOStreamGetLine.h" 16 17 #include "autogen_HTTPException.h" 17 18 … … 33 34 : mResponseCode(HTTPResponse::Code_NoContent), 34 35 mResponseIsDynamicContent(true), 35 mKeepAlive(false) 36 mKeepAlive(false), 37 mContentLength(-1) 36 38 { 37 39 } … … 55 57 // Function 56 58 // Name: HTTPResponse::ResponseCodeToString(int) 57 // Purpose: Return string equivalent of the response code, suitable for Status: headers 59 // Purpose: Return string equivalent of the response code, 60 // suitable for Status: headers 58 61 // Created: 26/3/04 59 62 // … … 115 118 // Function 116 119 // Name: HTTPResponse::Send(IOStream &, bool) 117 // Purpose: Build the response, and send via the stream. Optionally omitting118 // the content.120 // Purpose: Build the response, and send via the stream. 121 // Optionally omitting the content. 119 122 // Created: 26/3/04 120 123 // … … 140 143 } 141 144 // Extra headers... 142 for(std::vector<std:: string>::const_iterator i(mExtraHeaders.begin()); i != mExtraHeaders.end(); ++i)145 for(std::vector<std::pair<std::string, std::string> >::const_iterator i(mExtraHeaders.begin()); i != mExtraHeaders.end(); ++i) 143 146 { 144 147 header += "\r\n"; 145 header += *i;148 header += i->first + ": " + i->second; 146 149 } 147 150 // NOTE: a line ending must be included here in all cases … … 182 185 // 183 186 // Function 187 // Name: HTTPResponse::ParseHeaders(IOStreamGetLine &, int) 188 // Purpose: Private. Parse the headers of the response 189 // Created: 26/3/04 190 // 191 // -------------------------------------------------------------------------- 192 void HTTPResponse::ParseHeaders(IOStreamGetLine &rGetLine, int Timeout) 193 { 194 std::string header; 195 bool haveHeader = false; 196 while(true) 197 { 198 if(rGetLine.IsEOF()) 199 { 200 // Header terminates unexpectedly 201 THROW_EXCEPTION(HTTPException, BadRequest) 202 } 203 204 std::string currentLine; 205 if(!rGetLine.GetLine(currentLine, false /* no preprocess */, Timeout)) 206 { 207 // Timeout 208 THROW_EXCEPTION(HTTPException, RequestReadFailed) 209 } 210 211 // Is this a continuation of the previous line? 212 bool processHeader = haveHeader; 213 if(!currentLine.empty() && (currentLine[0] == ' ' || currentLine[0] == '\t')) 214 { 215 // A continuation, don't process anything yet 216 processHeader = false; 217 } 218 //TRACE3("%d:%d:%s\n", processHeader, haveHeader, currentLine.c_str()); 219 220 // Parse the header -- this will actually process the header 221 // from the previous run around the loop. 222 if(processHeader) 223 { 224 // Find where the : is in the line 225 const char *h = header.c_str(); 226 int p = 0; 227 while(h[p] != '\0' && h[p] != ':') 228 { 229 ++p; 230 } 231 // Skip white space 232 int dataStart = p + 1; 233 while(h[dataStart] == ' ' || h[dataStart] == '\t') 234 { 235 ++dataStart; 236 } 237 238 if(p == sizeof("Content-Length")-1 239 && ::strncasecmp(h, "Content-Length", sizeof("Content-Length")-1) == 0) 240 { 241 // Decode number 242 long len = ::strtol(h + dataStart, NULL, 10); // returns zero in error case, this is OK 243 if(len < 0) len = 0; 244 // Store 245 mContentLength = len; 246 } 247 else if(p == sizeof("Content-Type")-1 248 && ::strncasecmp(h, "Content-Type", sizeof("Content-Type")-1) == 0) 249 { 250 // Store rest of string as content type 251 mContentType = h + dataStart; 252 } 253 else if(p == sizeof("Cookie")-1 254 && ::strncasecmp(h, "Cookie", sizeof("Cookie")-1) == 0) 255 { 256 THROW_EXCEPTION(HTTPException, NotImplemented); 257 /* 258 // Parse cookies 259 ParseCookies(header, dataStart); 260 */ 261 } 262 else if(p == sizeof("Connection")-1 263 && ::strncasecmp(h, "Connection", sizeof("Connection")-1) == 0) 264 { 265 // Connection header, what is required? 266 const char *v = h + dataStart; 267 if(::strcasecmp(v, "close") == 0) 268 { 269 mKeepAlive = false; 270 } 271 else if(::strcasecmp(v, "keep-alive") == 0) 272 { 273 mKeepAlive = true; 274 } 275 // else don't understand, just assume default for protocol version 276 } 277 else 278 { 279 std::string headerName = header.substr(0, p); 280 AddHeader(headerName, h + dataStart); 281 } 282 283 // Unset have header flag, as it's now been processed 284 haveHeader = false; 285 } 286 287 // Store the chunk of header the for next time round 288 if(haveHeader) 289 { 290 header += currentLine; 291 } 292 else 293 { 294 header = currentLine; 295 haveHeader = true; 296 } 297 298 // End of headers? 299 if(currentLine.empty()) 300 { 301 // All done! 302 break; 303 } 304 } 305 } 306 307 void HTTPResponse::Receive(IOStream& rStream, int Timeout) 308 { 309 IOStreamGetLine rGetLine(rStream); 310 311 if(rGetLine.IsEOF()) 312 { 313 // Connection terminated unexpectedly 314 THROW_EXCEPTION(HTTPException, BadResponse) 315 } 316 317 std::string statusLine; 318 if(!rGetLine.GetLine(statusLine, false /* no preprocess */, Timeout)) 319 { 320 // Timeout 321 THROW_EXCEPTION(HTTPException, ResponseReadFailed) 322 } 323 324 if (statusLine.substr(0, 7) != "HTTP/1." || 325 statusLine[8] != ' ') 326 { 327 // Status line terminated unexpectedly 328 BOX_ERROR("Bad response status line: " << statusLine); 329 THROW_EXCEPTION(HTTPException, BadResponse) 330 } 331 332 if (statusLine[5] == '1' && statusLine[7] == '1') 333 { 334 // HTTP/1.1 default is to keep alive 335 mKeepAlive = true; 336 } 337 338 // Decode the status code 339 long status = ::strtol(statusLine.substr(9, 3).c_str(), NULL, 10); 340 // returns zero in error case, this is OK 341 if(status < 0) status = 0; 342 // Store 343 mResponseCode = status; 344 345 ParseHeaders(rGetLine, Timeout); 346 347 // push back whatever bytes we have left 348 // rGetLine.DetachFile(); 349 if (mContentLength > 0) 350 { 351 if (mContentLength < rGetLine.GetSizeOfBufferedData()) 352 { 353 // very small response, not good! 354 THROW_EXCEPTION(HTTPException, NotImplemented); 355 } 356 357 Write(rGetLine.GetBufferedData(), 358 rGetLine.GetSizeOfBufferedData()); 359 } 360 361 while (mContentLength != 0) // could be -1 as well 362 { 363 char buffer[4096]; 364 int readSize = sizeof(buffer); 365 if (mContentLength > 0 && mContentLength < readSize) 366 { 367 readSize = mContentLength; 368 } 369 readSize = rStream.Read(buffer, readSize, Timeout); 370 if (readSize == 0) 371 { 372 break; 373 } 374 mContentLength -= readSize; 375 Write(buffer, readSize); 376 } 377 378 SetForReading(); 379 } 380 381 // -------------------------------------------------------------------------- 382 // 383 // Function 184 384 // Name: HTTPResponse::AddHeader(const char *) 185 385 // Purpose: Add header, given entire line … … 187 387 // 188 388 // -------------------------------------------------------------------------- 389 /* 189 390 void HTTPResponse::AddHeader(const char *EntireHeaderLine) 190 391 { 191 392 mExtraHeaders.push_back(std::string(EntireHeaderLine)); 192 393 } 193 394 */ 194 395 195 396 // -------------------------------------------------------------------------- … … 201 402 // 202 403 // -------------------------------------------------------------------------- 404 /* 203 405 void HTTPResponse::AddHeader(const std::string &rEntireHeaderLine) 204 406 { 205 407 mExtraHeaders.push_back(rEntireHeaderLine); 206 408 } 207 409 */ 208 410 209 411 // -------------------------------------------------------------------------- … … 215 417 // 216 418 // -------------------------------------------------------------------------- 217 void HTTPResponse::AddHeader(const char *Header, const char *Value) 218 { 219 std::string h(Header); 220 h += ": "; 221 h += Value; 222 mExtraHeaders.push_back(h); 419 void HTTPResponse::AddHeader(const char *pHeader, const char *pValue) 420 { 421 mExtraHeaders.push_back(Header(pHeader, pValue)); 223 422 } 224 423 … … 232 431 // 233 432 // -------------------------------------------------------------------------- 234 void HTTPResponse::AddHeader(const char *Header, const std::string &rValue) 235 { 236 std::string h(Header); 237 h += ": "; 238 h += rValue; 239 mExtraHeaders.push_back(h); 433 void HTTPResponse::AddHeader(const char *pHeader, const std::string &rValue) 434 { 435 mExtraHeaders.push_back(Header(pHeader, rValue)); 240 436 } 241 437 … … 251 447 void HTTPResponse::AddHeader(const std::string &rHeader, const std::string &rValue) 252 448 { 253 mExtraHeaders.push_back( rHeader + ": " + rValue);449 mExtraHeaders.push_back(Header(rHeader, rValue)); 254 450 } 255 451 … … 280 476 h += "\""; 281 477 */ 282 std::string h ("Set-Cookie: ");478 std::string h; 283 479 h += Name; 284 480 h += "="; … … 287 483 h += Path; 288 484 289 mExtraHeaders.push_back( h);485 mExtraHeaders.push_back(Header("Set-Cookie", h)); 290 486 } 291 487 … … 313 509 314 510 // Set location to redirect to 315 std::string header ("Location: ");511 std::string header; 316 512 if(IsLocalURI) header += msDefaultURIPrefix; 317 513 header += RedirectTo; 318 mExtraHeaders.push_back( header);514 mExtraHeaders.push_back(Header("Location", header)); 319 515 320 516 // Set up some default content -
box/trunk/lib/httpserver/HTTPResponse.h
r926 r2428 15 15 16 16 #include "CollectInBufferStream.h" 17 18 class IOStreamGetLine; 17 19 18 20 // -------------------------------------------------------------------------- … … 36 38 37 39 void SetResponseCode(int Code); 40 int GetResponseCode() { return mResponseCode; } 38 41 void SetContentType(const char *ContentType); 42 const std::string& GetContentType() { return mContentType; } 39 43 40 44 void SetAsRedirect(const char *RedirectTo, bool IsLocalURI = true); … … 42 46 43 47 void Send(IOStream &rStream, bool OmitContent = false); 48 void Receive(IOStream& rStream, int Timeout = IOStream::TimeOutInfinite); 44 49 45 void AddHeader(const char *EntireHeaderLine);46 void AddHeader(const std::string &rEntireHeaderLine);50 // void AddHeader(const char *EntireHeaderLine); 51 // void AddHeader(const std::string &rEntireHeaderLine); 47 52 void AddHeader(const char *Header, const char *Value); 48 53 void AddHeader(const char *Header, const std::string &rValue); … … 107 112 bool mKeepAlive; 108 113 std::string mContentType; 109 std::vector<std::string> mExtraHeaders; 114 typedef std::pair<std::string, std::string> Header; 115 std::vector<Header> mExtraHeaders; 116 int mContentLength; // only used when reading response from stream 110 117 111 118 static std::string msDefaultURIPrefix; 119 120 void ParseHeaders(IOStreamGetLine &rGetLine, int Timeout); 112 121 }; 113 122 -
box/trunk/test/httpserver/testhttpserver.cpp
r2423 r2428 17 17 #include "HTTPRequest.h" 18 18 #include "HTTPResponse.h" 19 #include "IOStreamGetLine.h" 19 20 #include "ServerControl.h" 20 21 … … 72 73 case HTTPRequest::Method_HEAD: m = "HEAD"; break; 73 74 case HTTPRequest::Method_POST: m = "POST"; break; 75 default: m = "UNKNOWN"; 74 76 } 75 77 rResponse.Write(m, 4); … … 126 128 int pid = LaunchServer("./test server testfiles/httpserver.conf", "testfiles/httpserver.pid"); 127 129 TEST_THAT(pid != -1 && pid != 0); 128 if(pid > 0) 129 { 130 // Run the request script 131 TEST_THAT(::system("perl testfiles/testrequests.pl") == 0); 130 if(pid <= 0) 131 { 132 return 0; 133 } 134 135 // Run the request script 136 TEST_THAT(::system("perl testfiles/testrequests.pl") == 0); 137 138 signal(SIGPIPE, SIG_IGN); 139 140 SocketStream sock; 141 sock.Open(Socket::TypeINET, "localhost", 1080); 142 143 for (int i = 0; i < 4; i++) 144 { 145 HTTPRequest request(HTTPRequest::Method_GET, 146 "/test-one/34/341s/234?p1=vOne&p2=vTwo"); 147 148 if (i >= 2) 149 { 150 // first set of passes has keepalive off by default, 151 // so when i == 1 the socket has already been closed 152 // by the server, and we'll get -EPIPE when we try 153 // to send the request. 154 request.SetClientKeepAliveRequested(true); 155 } 156 157 if (i == 1) 158 { 159 TEST_CHECK_THROWS(request.Write(sock, 160 IOStream::TimeOutInfinite), 161 ConnectionException, SocketWriteError); 162 sock.Close(); 163 sock.Open(Socket::TypeINET, "localhost", 1080); 164 continue; 165 } 166 else 167 { 168 request.Write(sock, IOStream::TimeOutInfinite); 169 } 170 171 HTTPResponse response; 172 response.Receive(sock); 173 174 TEST_THAT(response.GetResponseCode() == HTTPResponse::Code_OK); 175 TEST_THAT(response.GetContentType() == "text/html"); 176 177 IOStreamGetLine getline(response); 178 std::string line; 179 180 TEST_THAT(getline.GetLine(line)); 181 TEST_EQUAL("<html>", line); 182 TEST_THAT(getline.GetLine(line)); 183 TEST_EQUAL("<head><title>TEST SERVER RESPONSE</title></head>", 184 line); 185 TEST_THAT(getline.GetLine(line)); 186 TEST_EQUAL("<body><h1>Test response</h1>", line); 187 TEST_THAT(getline.GetLine(line)); 188 TEST_EQUAL("<p><b>URI:</b> /test-one/34/341s/234</p>", line); 189 TEST_THAT(getline.GetLine(line)); 190 TEST_EQUAL("<p><b>Query string:</b> p1=vOne&p2=vTwo</p>", line); 191 TEST_THAT(getline.GetLine(line)); 192 TEST_EQUAL("<p><b>Method:</b> GET </p>", line); 193 TEST_THAT(getline.GetLine(line)); 194 TEST_EQUAL("<p><b>Decoded query:</b><br>", line); 195 TEST_THAT(getline.GetLine(line)); 196 TEST_EQUAL("PARAM:p1=vOne<br>", line); 197 TEST_THAT(getline.GetLine(line)); 198 TEST_EQUAL("PARAM:p2=vTwo<br></p>", line); 199 TEST_THAT(getline.GetLine(line)); 200 TEST_EQUAL("<p><b>Content type:</b> </p>", line); 201 TEST_THAT(getline.GetLine(line)); 202 TEST_EQUAL("<p><b>Content length:</b> -1</p>", line); 203 TEST_THAT(getline.GetLine(line)); 204 TEST_EQUAL("<p><b>Cookies:</b><br>", line); 205 TEST_THAT(getline.GetLine(line)); 206 TEST_EQUAL("</p>", line); 207 TEST_THAT(getline.GetLine(line)); 208 TEST_EQUAL("</body>", line); 209 TEST_THAT(getline.GetLine(line)); 210 TEST_EQUAL("</html>", line); 211 } 132 212 133 // Kill it 134 TEST_THAT(KillServer(pid)); 135 TestRemoteProcessMemLeaks("generic-httpserver.memleaks"); 136 } 213 // Kill it 214 TEST_THAT(KillServer(pid)); 215 TestRemoteProcessMemLeaks("generic-httpserver.memleaks"); 137 216 138 217 return 0;
Note: See TracChangeset
for help on using the changeset viewer.
