Added many helper functions to HTTP library, optimizing and fixing support for certain cases (among which chunked encoding).
This commit is contained in:
		
							parent
							
								
									7ea9dd9654
								
							
						
					
					
						commit
						f21ce291ae
					
				
					 2 changed files with 206 additions and 11 deletions
				
			
		|  | @ -2,6 +2,7 @@ | |||
| /// Holds all code for the HTTP namespace.
 | ||||
| 
 | ||||
| #include "http_parser.h" | ||||
| #include "timing.h" | ||||
| 
 | ||||
| /// This constructor creates an empty HTTP::Parser, ready for use for either reading or writing.
 | ||||
| /// All this constructor does is call HTTP::Parser::Clean().
 | ||||
|  | @ -45,6 +46,27 @@ std::string & HTTP::Parser::BuildRequest(){ | |||
|   return builder; | ||||
| } | ||||
| 
 | ||||
| /// Creates and sends a valid HTTP 1.0 or 1.1 request.
 | ||||
| /// The request is build from internal variables set before this call is made.
 | ||||
| /// To be precise, method, url, protocol, headers and body are used.
 | ||||
| void HTTP::Parser::SendRequest(Socket::Connection & conn){ | ||||
|   /// \todo Include GET/POST variable parsing?
 | ||||
|   std::map<std::string, std::string>::iterator it; | ||||
|   if (protocol.size() < 5 || protocol.substr(0, 4) != "HTTP"){ | ||||
|     protocol = "HTTP/1.0"; | ||||
|   } | ||||
|   builder = method + " " + url + " " + protocol + "\r\n"; | ||||
|   conn.SendNow(builder); | ||||
|   for (it = headers.begin(); it != headers.end(); it++){ | ||||
|     if (( *it).first != "" && ( *it).second != ""){ | ||||
|       builder = ( *it).first + ": " + ( *it).second + "\r\n"; | ||||
|       conn.SendNow(builder); | ||||
|     } | ||||
|   } | ||||
|   conn.SendNow("\r\n", 2); | ||||
|   conn.SendNow(body); | ||||
| } | ||||
| 
 | ||||
| /// Returns a string containing a valid HTTP 1.0 or 1.1 response, ready for sending.
 | ||||
| /// The response is partly build from internal variables set before this call is made.
 | ||||
| /// To be precise, protocol, headers and body are used.
 | ||||
|  | @ -70,6 +92,109 @@ std::string & HTTP::Parser::BuildResponse(std::string code, std::string message) | |||
|   return builder; | ||||
| } | ||||
| 
 | ||||
| /// Creates and sends a valid HTTP 1.0 or 1.1 response.
 | ||||
| /// The response is partly build from internal variables set before this call is made.
 | ||||
| /// To be precise, protocol, headers and body are used.
 | ||||
| /// \param code The HTTP response code. Usually you want 200.
 | ||||
| /// \param message The HTTP response message. Usually you want "OK".
 | ||||
| void HTTP::Parser::SendResponse(std::string code, std::string message, Socket::Connection & conn){ | ||||
|   /// \todo Include GET/POST variable parsing?
 | ||||
|   std::map<std::string, std::string>::iterator it; | ||||
|   if (protocol.size() < 5 || protocol.substr(0, 4) != "HTTP"){ | ||||
|     protocol = "HTTP/1.0"; | ||||
|   } | ||||
|   builder = protocol + " " + code + " " + message + "\r\n"; | ||||
|   conn.SendNow(builder); | ||||
|   for (it = headers.begin(); it != headers.end(); it++){ | ||||
|     if (( *it).first != "" && ( *it).second != ""){ | ||||
|       if (( *it).first != "Content-Length" || ( *it).second != "0"){ | ||||
|         builder = ( *it).first + ": " + ( *it).second + "\r\n"; | ||||
|         conn.SendNow(builder); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|   conn.SendNow("\r\n", 2); | ||||
| } | ||||
| 
 | ||||
| /// After receiving a header with this object, this function call will:
 | ||||
| /// - Forward the headers to the 'to' Socket::Connection.
 | ||||
| /// - Retrieve all the body from the 'from' Socket::Connection.
 | ||||
| /// - Forward those contents as-is to the 'to' Socket::Connection.
 | ||||
| /// It blocks until completed or either of the connections reaches an error state.
 | ||||
| void HTTP::Parser::Proxy(Socket::Connection & from, Socket::Connection & to){ | ||||
|   SendResponse("200", "OK", to); | ||||
|   if (getChunks){ | ||||
|     int doingChunk = 0; | ||||
|     while (to.connected() && from.connected()){ | ||||
|       if (from.Received().size() || from.spool()){ | ||||
|         if (doingChunk){ | ||||
|           unsigned int toappend = from.Received().get().size(); | ||||
|           if (toappend > doingChunk){ | ||||
|             toappend = doingChunk; | ||||
|             to.SendNow(from.Received().get().c_str(), toappend); | ||||
|             from.Received().get().erase(0, toappend); | ||||
|           }else{ | ||||
|             to.SendNow(from.Received().get()); | ||||
|             from.Received().get().clear(); | ||||
|           } | ||||
|           doingChunk -= toappend; | ||||
|         }else{ | ||||
|           //Make sure the received data ends in a newline (\n).
 | ||||
|           if ( *(from.Received().get().rbegin()) != '\n'){ | ||||
|             if (from.Received().size() > 1){ | ||||
|               std::string tmp = from.Received().get(); | ||||
|               from.Received().get().clear(); | ||||
|               from.Received().get().insert(0, tmp); | ||||
|             }else{ | ||||
|               Util::sleep(100); | ||||
|               continue; | ||||
|             } | ||||
|           } | ||||
|           //forward the size and any empty lines
 | ||||
|           to.SendNow(from.Received().get()); | ||||
|            | ||||
|           std::string tmpA = from.Received().get().substr(0, from.Received().get().size() - 1); | ||||
|           while (tmpA.find('\r') != std::string::npos){ | ||||
|             tmpA.erase(tmpA.find('\r')); | ||||
|           } | ||||
|           unsigned int chunkLen = 0; | ||||
|           if ( !tmpA.empty()){ | ||||
|             for (int i = 0; i < tmpA.size(); ++i){ | ||||
|               chunkLen = (chunkLen << 4) | unhex(tmpA[i]); | ||||
|             } | ||||
|             if (chunkLen == 0){ | ||||
|               getChunks = false; | ||||
|               to.SendNow("\r\n", 2); | ||||
|               return; | ||||
|             } | ||||
|             doingChunk = chunkLen; | ||||
|           } | ||||
|           from.Received().get().clear(); | ||||
|         } | ||||
|       }else{ | ||||
|         Util::sleep(100); | ||||
|       } | ||||
|     } | ||||
|   }else{ | ||||
|     unsigned int bodyLen = length; | ||||
|     while (bodyLen > 0 && to.connected() && from.connected()){ | ||||
|       if (from.Received().size() || from.spool()){ | ||||
|         if (from.Received().get().size() <= bodyLen){ | ||||
|           to.SendNow(from.Received().get()); | ||||
|           bodyLen -= from.Received().get().size(); | ||||
|           from.Received().get().clear(); | ||||
|         }else{ | ||||
|           to.SendNow(from.Received().get().c_str(), bodyLen); | ||||
|           from.Received().get().erase(0, bodyLen); | ||||
|           bodyLen = 0; | ||||
|         } | ||||
|       }else{ | ||||
|         Util::sleep(100); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /// Trims any whitespace at the front or back of the string.
 | ||||
| /// Used when getting/setting headers.
 | ||||
| /// \param s The string to trim. The string itself will be changed, not returned.
 | ||||
|  | @ -142,15 +267,34 @@ void HTTP::Parser::SetVar(std::string i, std::string v){ | |||
|   } | ||||
| } | ||||
| 
 | ||||
| /// Attempt to read a whole HTTP request or response from a Socket::Connection.
 | ||||
| /// If a whole request could be read, it is removed from the front of the socket buffer and true returned.
 | ||||
| /// If not, as much as can be interpreted is removed and false returned.
 | ||||
| /// \param conn The socket to read from.
 | ||||
| /// \return True if a whole request or response was read, false otherwise.
 | ||||
| bool HTTP::Parser::Read(Socket::Connection & conn){ | ||||
|   //Make sure the received data ends in a newline (\n).
 | ||||
|   if ( *(conn.Received().get().rbegin()) != '\n'){ | ||||
|     if (conn.Received().size() > 1){ | ||||
|       std::string tmp = conn.Received().get(); | ||||
|       conn.Received().get().clear(); | ||||
|       conn.Received().get().insert(0, tmp); | ||||
|     }else{ | ||||
|       return false; | ||||
|     } | ||||
|   } | ||||
|   return parse(conn.Received().get()); | ||||
| } //HTTPReader::Read
 | ||||
| 
 | ||||
| /// Attempt to read a whole HTTP request or response from a std::string buffer.
 | ||||
| /// If a whole request could be read, it is removed from the front of the given buffer.
 | ||||
| /// If a whole request could be read, it is removed from the front of the given buffer and true returned.
 | ||||
| /// If not, as much as can be interpreted is removed and false returned.
 | ||||
| /// \param strbuf The buffer to read from.
 | ||||
| /// \return True if a whole request or response was read, false otherwise.
 | ||||
| bool HTTP::Parser::Read(std::string & strbuf){ | ||||
|   return parse(strbuf); | ||||
| } //HTTPReader::Read
 | ||||
| 
 | ||||
| #include <iostream> | ||||
| /// Attempt to read a whole HTTP response or request from a data buffer.
 | ||||
| /// If succesful, fills its own fields with the proper data and removes the response/request
 | ||||
| /// from the data buffer.
 | ||||
|  | @ -177,6 +321,21 @@ bool HTTP::Parser::parse(std::string & HTTPbuffer){ | |||
|         seenReq = true; | ||||
|         f = tmpA.find(' '); | ||||
|         if (f != std::string::npos){ | ||||
|           if (tmpA.substr(0, 4) == "HTTP"){ | ||||
|             protocol = tmpA.substr(0, f); | ||||
|             tmpA.erase(0, f + 1); | ||||
|             f = tmpA.find(' '); | ||||
|             if (f != std::string::npos){ | ||||
|               url = tmpA.substr(0, f); | ||||
|               tmpA.erase(0, f + 1); | ||||
|               method = tmpA; | ||||
|               if (url.find('?') != std::string::npos){ | ||||
|                 parseVars(url.substr(url.find('?') + 1)); //parse GET variables
 | ||||
|               } | ||||
|             }else{ | ||||
|               seenReq = false; | ||||
|             } | ||||
|           }else{ | ||||
|             method = tmpA.substr(0, f); | ||||
|             tmpA.erase(0, f + 1); | ||||
|             f = tmpA.find(' '); | ||||
|  | @ -190,6 +349,7 @@ bool HTTP::Parser::parse(std::string & HTTPbuffer){ | |||
|             }else{ | ||||
|               seenReq = false; | ||||
|             } | ||||
|           } | ||||
|         }else{ | ||||
|           seenReq = false; | ||||
|         } | ||||
|  | @ -234,6 +394,9 @@ bool HTTP::Parser::parse(std::string & HTTPbuffer){ | |||
|         } | ||||
|       }else{ | ||||
|         if (getChunks){ | ||||
|           if (headerOnly){ | ||||
|             return true; | ||||
|           } | ||||
|           if (doingChunk){ | ||||
|             unsigned int toappend = HTTPbuffer.size(); | ||||
|             if (toappend > doingChunk){ | ||||
|  | @ -321,6 +484,31 @@ void HTTP::Parser::Chunkify(std::string & bodypart){ | |||
|   } | ||||
| } | ||||
| 
 | ||||
| /// Converts a string to chunked format if protocol is HTTP/1.1 - does nothing otherwise.
 | ||||
| /// \param bodypart The data to convert - will be converted in-place.
 | ||||
| void HTTP::Parser::Chunkify(std::string & bodypart, Socket::Connection & conn){ | ||||
|   Chunkify(bodypart.c_str(), bodypart.size(), conn); | ||||
| } | ||||
| 
 | ||||
| /// Converts a string to chunked format if protocol is HTTP/1.1 - does nothing otherwise.
 | ||||
| /// \param bodypart The data to convert - will be converted in-place.
 | ||||
| void HTTP::Parser::Chunkify(const char * data, unsigned int size, Socket::Connection & conn){ | ||||
|   if (protocol == "HTTP/1.1"){ | ||||
|     static char len[10]; | ||||
|     int sizelen = snprintf(len, 10, "%x\r\n", size); | ||||
|     //prepend the chunk size and \r\n
 | ||||
|     conn.SendNow(len, sizelen); | ||||
|     //send the chunk itself
 | ||||
|     conn.SendNow(data, size); | ||||
|     //append \r\n
 | ||||
|     conn.SendNow("\r\n", 2); | ||||
|     if ( !size){ | ||||
|       //append \r\n again!
 | ||||
|       conn.SendNow("\r\n", 2); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /// Unescapes URLencoded std::string data.
 | ||||
| std::string HTTP::Parser::urlunescape(const std::string & in){ | ||||
|   std::string out; | ||||
|  |  | |||
|  | @ -6,6 +6,7 @@ | |||
| #include <string> | ||||
| #include <stdlib.h> | ||||
| #include <stdio.h> | ||||
| #include "socket.h" | ||||
| 
 | ||||
| /// Holds all HTTP processing related code.
 | ||||
| namespace HTTP { | ||||
|  | @ -13,6 +14,7 @@ namespace HTTP { | |||
|   class Parser{ | ||||
|     public: | ||||
|       Parser(); | ||||
|       bool Read(Socket::Connection & conn); | ||||
|       bool Read(std::string & strbuf); | ||||
|       std::string GetHeader(std::string i); | ||||
|       std::string GetVar(std::string i); | ||||
|  | @ -24,7 +26,12 @@ namespace HTTP { | |||
|       void SetBody(char * buffer, int len); | ||||
|       std::string & BuildRequest(); | ||||
|       std::string & BuildResponse(std::string code, std::string message); | ||||
|       void SendRequest(Socket::Connection & conn); | ||||
|       void SendResponse(std::string code, std::string message, Socket::Connection & conn); | ||||
|       void Chunkify(std::string & bodypart); | ||||
|       void Chunkify(std::string & bodypart, Socket::Connection & conn); | ||||
|       void Chunkify(const char * data, unsigned int size, Socket::Connection & conn); | ||||
|       void Proxy(Socket::Connection & from, Socket::Connection & to); | ||||
|       void Clean(); | ||||
|       static std::string urlunescape(const std::string & in); | ||||
|       static std::string urlencode(const std::string & in); | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Thulinma
						Thulinma