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.
|
/// Holds all code for the HTTP namespace.
|
||||||
|
|
||||||
#include "http_parser.h"
|
#include "http_parser.h"
|
||||||
|
#include "timing.h"
|
||||||
|
|
||||||
/// This constructor creates an empty HTTP::Parser, ready for use for either reading or writing.
|
/// This constructor creates an empty HTTP::Parser, ready for use for either reading or writing.
|
||||||
/// All this constructor does is call HTTP::Parser::Clean().
|
/// All this constructor does is call HTTP::Parser::Clean().
|
||||||
|
@ -45,6 +46,27 @@ std::string & HTTP::Parser::BuildRequest(){
|
||||||
return builder;
|
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.
|
/// 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.
|
/// The response is partly build from internal variables set before this call is made.
|
||||||
/// To be precise, protocol, headers and body are used.
|
/// 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;
|
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.
|
/// Trims any whitespace at the front or back of the string.
|
||||||
/// Used when getting/setting headers.
|
/// Used when getting/setting headers.
|
||||||
/// \param s The string to trim. The string itself will be changed, not returned.
|
/// \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.
|
/// 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.
|
/// \param strbuf The buffer to read from.
|
||||||
/// \return True if a whole request or response was read, false otherwise.
|
/// \return True if a whole request or response was read, false otherwise.
|
||||||
bool HTTP::Parser::Read(std::string & strbuf){
|
bool HTTP::Parser::Read(std::string & strbuf){
|
||||||
return parse(strbuf);
|
return parse(strbuf);
|
||||||
} //HTTPReader::Read
|
} //HTTPReader::Read
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
/// Attempt to read a whole HTTP response or request from a data buffer.
|
/// 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
|
/// If succesful, fills its own fields with the proper data and removes the response/request
|
||||||
/// from the data buffer.
|
/// from the data buffer.
|
||||||
|
@ -177,6 +321,21 @@ bool HTTP::Parser::parse(std::string & HTTPbuffer){
|
||||||
seenReq = true;
|
seenReq = true;
|
||||||
f = tmpA.find(' ');
|
f = tmpA.find(' ');
|
||||||
if (f != std::string::npos){
|
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);
|
method = tmpA.substr(0, f);
|
||||||
tmpA.erase(0, f + 1);
|
tmpA.erase(0, f + 1);
|
||||||
f = tmpA.find(' ');
|
f = tmpA.find(' ');
|
||||||
|
@ -190,6 +349,7 @@ bool HTTP::Parser::parse(std::string & HTTPbuffer){
|
||||||
}else{
|
}else{
|
||||||
seenReq = false;
|
seenReq = false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}else{
|
}else{
|
||||||
seenReq = false;
|
seenReq = false;
|
||||||
}
|
}
|
||||||
|
@ -234,6 +394,9 @@ bool HTTP::Parser::parse(std::string & HTTPbuffer){
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
if (getChunks){
|
if (getChunks){
|
||||||
|
if (headerOnly){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
if (doingChunk){
|
if (doingChunk){
|
||||||
unsigned int toappend = HTTPbuffer.size();
|
unsigned int toappend = HTTPbuffer.size();
|
||||||
if (toappend > doingChunk){
|
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.
|
/// Unescapes URLencoded std::string data.
|
||||||
std::string HTTP::Parser::urlunescape(const std::string & in){
|
std::string HTTP::Parser::urlunescape(const std::string & in){
|
||||||
std::string out;
|
std::string out;
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include "socket.h"
|
||||||
|
|
||||||
/// Holds all HTTP processing related code.
|
/// Holds all HTTP processing related code.
|
||||||
namespace HTTP {
|
namespace HTTP {
|
||||||
|
@ -13,6 +14,7 @@ namespace HTTP {
|
||||||
class Parser{
|
class Parser{
|
||||||
public:
|
public:
|
||||||
Parser();
|
Parser();
|
||||||
|
bool Read(Socket::Connection & conn);
|
||||||
bool Read(std::string & strbuf);
|
bool Read(std::string & strbuf);
|
||||||
std::string GetHeader(std::string i);
|
std::string GetHeader(std::string i);
|
||||||
std::string GetVar(std::string i);
|
std::string GetVar(std::string i);
|
||||||
|
@ -24,7 +26,12 @@ namespace HTTP {
|
||||||
void SetBody(char * buffer, int len);
|
void SetBody(char * buffer, int len);
|
||||||
std::string & BuildRequest();
|
std::string & BuildRequest();
|
||||||
std::string & BuildResponse(std::string code, std::string message);
|
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);
|
||||||
|
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();
|
void Clean();
|
||||||
static std::string urlunescape(const std::string & in);
|
static std::string urlunescape(const std::string & in);
|
||||||
static std::string urlencode(const std::string & in);
|
static std::string urlencode(const std::string & in);
|
||||||
|
|
Loading…
Add table
Reference in a new issue