Added many helper functions to HTTP library, optimizing and fixing support for certain cases (among which chunked encoding).

This commit is contained in:
Thulinma 2013-08-21 11:59:43 +02:00
parent 7ea9dd9654
commit f21ce291ae
2 changed files with 206 additions and 11 deletions

View file

@ -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,18 +321,34 @@ bool HTTP::Parser::parse(std::string & HTTPbuffer){
seenReq = true;
f = tmpA.find(' ');
if (f != std::string::npos){
method = tmpA.substr(0, f);
tmpA.erase(0, f + 1);
f = tmpA.find(' ');
if (f != std::string::npos){
url = tmpA.substr(0, f);
if (tmpA.substr(0, 4) == "HTTP"){
protocol = tmpA.substr(0, f);
tmpA.erase(0, f + 1);
protocol = tmpA;
if (url.find('?') != std::string::npos){
parseVars(url.substr(url.find('?') + 1)); //parse GET variables
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{
seenReq = false;
method = 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);
protocol = tmpA;
if (url.find('?') != std::string::npos){
parseVars(url.substr(url.find('?') + 1)); //parse GET variables
}
}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;

View file

@ -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);