Commit 1db057d6 authored by Davis King's avatar Davis King
Browse files

Refactored the code in the http server so that it will be more reusable

by other tools.
parent 05b1ba8b
...@@ -25,34 +25,18 @@ ...@@ -25,34 +25,18 @@
namespace dlib namespace dlib
{ {
template < // ----------------------------------------------------------------------------------------
typename server_base
>
class server_http_1 : public server_base
{
/*!
CONVENTION
this extension doesn't add any new state to this object.
!*/
public: class http_parse_error : public error
server_http_1()
{ {
max_content_length = 10*1024*1024; // 10MB public:
} http_parse_error(const std::string& str, int http_error_code_):
error(str),http_error_code(http_error_code_) {}
unsigned long get_max_content_length ( const int http_error_code;
) const { return max_content_length; } };
void set_max_content_length ( // ----------------------------------------------------------------------------------------
unsigned long max_length
)
{
max_content_length = max_length;
}
template <typename Key, typename Value> template <typename Key, typename Value>
class constmap : public std::map<Key, Value> class constmap : public std::map<Key, Value>
...@@ -78,10 +62,20 @@ namespace dlib ...@@ -78,10 +62,20 @@ namespace dlib
typedef constmap< std::string, std::string > key_value_map; typedef constmap< std::string, std::string > key_value_map;
struct incoming_things struct incoming_things
{ {
incoming_things() : foreign_port(0), local_port(0) {} incoming_things (
const std::string& foreign_ip_,
const std::string& local_ip_,
unsigned short foreign_port_,
unsigned short local_port_
):
foreign_ip(foreign_ip_),
foreign_port(foreign_port_),
local_ip(local_ip_),
local_port(local_port_)
{}
std::string path; std::string path;
std::string request_type; std::string request_type;
...@@ -101,7 +95,7 @@ namespace dlib ...@@ -101,7 +95,7 @@ namespace dlib
struct outgoing_things struct outgoing_things
{ {
outgoing_things() : http_return(200) { } outgoing_things() : http_return(200), http_return_status("OK") { }
key_value_map cookies; key_value_map cookies;
key_value_map headers; key_value_map headers;
...@@ -109,19 +103,16 @@ namespace dlib ...@@ -109,19 +103,16 @@ namespace dlib
std::string http_return_status; std::string http_return_status;
}; };
// ----------------------------------------------------------------------------------------
private: namespace http_impl
virtual const std::string on_request ( {
const incoming_things& incoming, inline unsigned char to_hex( unsigned char x )
outgoing_things& outgoing
) = 0;
unsigned char to_hex( unsigned char x ) const
{ {
return x + (x > 9 ? ('A'-10) : '0'); return x + (x > 9 ? ('A'-10) : '0');
} }
const std::string urlencode( const std::string& s ) const inline const std::string urlencode( const std::string& s )
{ {
std::ostringstream os; std::ostringstream os;
...@@ -146,9 +137,9 @@ namespace dlib ...@@ -146,9 +137,9 @@ namespace dlib
return os.str(); return os.str();
} }
unsigned char from_hex ( inline unsigned char from_hex (
unsigned char ch unsigned char ch
) const )
{ {
if (ch <= '9' && ch >= '0') if (ch <= '9' && ch >= '0')
ch -= '0'; ch -= '0';
...@@ -161,9 +152,9 @@ namespace dlib ...@@ -161,9 +152,9 @@ namespace dlib
return ch; return ch;
} }
const std::string urldecode ( inline const std::string urldecode (
const std::string& str const std::string& str
) const )
{ {
using namespace std; using namespace std;
string result; string result;
...@@ -190,7 +181,10 @@ namespace dlib ...@@ -190,7 +181,10 @@ namespace dlib
return result; return result;
} }
void parse_url(std::string word, key_value_map& queries) inline void parse_url(
std::string word,
key_value_map& queries
)
/*! /*!
Parses the query string of a URL. word should be the stuff that comes Parses the query string of a URL. word should be the stuff that comes
after the ? in the query URL. after the ? in the query URL.
...@@ -220,11 +214,11 @@ namespace dlib ...@@ -220,11 +214,11 @@ namespace dlib
} }
} }
void read_with_limit( inline void read_with_limit(
std::istream& in, std::istream& in,
std::string& buffer, std::string& buffer,
int delim = '\n' int delim = '\n'
) const )
{ {
using namespace std; using namespace std;
const size_t max = 16*1024; const size_t max = 16*1024;
...@@ -236,6 +230,10 @@ namespace dlib ...@@ -236,6 +230,10 @@ namespace dlib
buffer += (char)in.get(); buffer += (char)in.get();
} }
// if we quit the loop because the data is longer than expected or we hit EOF
if (in.peek() == EOF || buffer.size() == max)
throw http_parse_error("HTTP field from client is too long", 414);
// Make sure the last char is the delim. // Make sure the last char is the delim.
if (in.get() != delim) if (in.get() != delim)
{ {
...@@ -252,31 +250,17 @@ namespace dlib ...@@ -252,31 +250,17 @@ namespace dlib
} }
} }
} }
}
void on_connect (
inline unsigned long parse_http_request (
std::istream& in, std::istream& in,
std::ostream& out, incoming_things& incoming,
const std::string& foreign_ip, unsigned long max_content_length
const std::string& local_ip,
unsigned short foreign_port,
unsigned short local_port,
uint64
) )
{ {
bool my_fault = true;
using namespace std; using namespace std;
using namespace http_impl;
try
{
incoming_things incoming;
outgoing_things outgoing;
incoming.foreign_ip = foreign_ip;
incoming.foreign_port = foreign_port;
incoming.local_ip = local_ip;
incoming.local_port = local_port;
read_with_limit(in, incoming.request_type, ' '); read_with_limit(in, incoming.request_type, ' ');
// get the path // get the path
...@@ -320,7 +304,16 @@ namespace dlib ...@@ -320,7 +304,16 @@ namespace dlib
istringstream sin(line.substr(16)); istringstream sin(line.substr(16));
sin >> content_length; sin >> content_length;
if (!sin) if (!sin)
content_length = 0; {
throw http_parse_error("Invalid Content-Length of '" + line.substr(16) + "'", 411);
}
if (content_length > max_content_length)
{
std::ostringstream sout;
sout << "Content-Length of post back is too large. It must be less than " << max_content_length;
throw http_parse_error(sout.str(), 413);
}
} }
// look for any cookies // look for any cookies
else if (line.size() > 6 && strings_equal_ignore_case(line, "Cookie:", 7)) else if (line.size() > 6 && strings_equal_ignore_case(line, "Cookie:", 7))
...@@ -375,18 +368,6 @@ namespace dlib ...@@ -375,18 +368,6 @@ namespace dlib
read_with_limit(in, line); read_with_limit(in, line);
} // while (line.size() > 2 ) } // while (line.size() > 2 )
// If there is data being posted back to us then load it into the incoming.body
// string.
if (content_length > max_content_length)
{
dlog << LERROR << "Request from: " << foreign_ip << " - body content length " << content_length << " exceeded max content length of " << max_content_length;
in.setstate(ios::badbit);
}
else if ( content_length > 0)
{
incoming.body.resize(content_length);
in.read(&incoming.body[0],content_length);
}
// If there is data being posted back to us as a query string then // If there is data being posted back to us as a query string then
// pick out the queries using parse_url. // pick out the queries using parse_url.
...@@ -394,6 +375,11 @@ namespace dlib ...@@ -394,6 +375,11 @@ namespace dlib
strings_equal_ignore_case(incoming.request_type, "PUT")) && strings_equal_ignore_case(incoming.request_type, "PUT")) &&
strings_equal_ignore_case(left_substr(content_type,";"), "application/x-www-form-urlencoded")) strings_equal_ignore_case(left_substr(content_type,";"), "application/x-www-form-urlencoded"))
{ {
if (content_length > 0)
{
incoming.body.resize(content_length);
in.read(&incoming.body[0],content_length);
}
parse_url(incoming.body, incoming.queries); parse_url(incoming.body, incoming.queries);
} }
...@@ -404,32 +390,43 @@ namespace dlib ...@@ -404,32 +390,43 @@ namespace dlib
} }
my_fault = false; if (!in)
key_value_map& new_cookies = outgoing.cookies; throw http_parse_error("Error parsing HTTP request", 500);
key_value_map& response_headers = outgoing.headers;
// Set some defaults return content_length;
outgoing.http_return = 200; }
outgoing.http_return_status = "OK";
// if there wasn't a problem with the input stream at some point inline void read_body (
// then lets trigger this request callback. std::istream& in,
std::string result; incoming_things& incoming
if (in) )
{ {
result = on_request(incoming, outgoing); // if the body hasn't already been loaded and there is data to load
} if (incoming.body.size() == 0 &&
else incoming.headers.count("Content-Length") != 0)
{
const unsigned long content_length = string_cast<unsigned long>(incoming.headers["Content-Length"]);
incoming.body.resize(content_length);
if (content_length > 0)
{ {
dlog << LERROR << "Request from: " << foreign_ip << " - Invalid request - Request Entity Too Large"; in.read(&incoming.body[0],content_length);
outgoing.http_return = 413; }
outgoing.http_return_status = "Request Entity Too Large"; }
} }
my_fault = true;
inline void write_http_response (
std::ostream& out,
outgoing_things outgoing,
const std::string& result
)
{
using namespace http_impl;
key_value_map& new_cookies = outgoing.cookies;
key_value_map& response_headers = outgoing.headers;
// only send this header if the user hasn't told us to send another kind // only send this header if the user hasn't told us to send another kind
bool has_content_type(false), bool has_content_type = false, has_location = false;
has_location(false);
for( typename key_value_map::const_iterator ci = response_headers.begin(); ci != response_headers.end(); ++ci ) for( typename key_value_map::const_iterator ci = response_headers.begin(); ci != response_headers.end(); ++ci )
{ {
if ( !has_content_type && strings_equal_ignore_case(ci->first , "content-type") ) if ( !has_content_type && strings_equal_ignore_case(ci->first , "content-type") )
...@@ -452,12 +449,7 @@ namespace dlib ...@@ -452,12 +449,7 @@ namespace dlib
response_headers["Content-Type"] = "text/html"; response_headers["Content-Type"] = "text/html";
} }
{ response_headers["Content-Length"] = cast_to_string(result.size());
ostringstream os;
os << result.size();
response_headers["Content-Length"] = os.str();
}
out << "HTTP/1.0 " << outgoing.http_return << " " << outgoing.http_return_status << "\r\n"; out << "HTTP/1.0 " << outgoing.http_return << " " << outgoing.http_return_status << "\r\n";
...@@ -474,16 +466,98 @@ namespace dlib ...@@ -474,16 +466,98 @@ namespace dlib
} }
out << "\r\n" << result; out << "\r\n" << result;
} }
catch (std::bad_alloc&)
inline void write_http_response (
std::ostream& out,
const http_parse_error& e
)
{
outgoing_things outgoing;
outgoing.http_return = e.http_error_code;
outgoing.http_return_status = e.what();
write_http_response(out, outgoing, std::string("Error processing request: ") + e.what());
}
inline void write_http_response (
std::ostream& out,
const std::exception& e
)
{ {
dlog << LERROR << "We ran out of memory in server_http::on_connect()"; outgoing_things outgoing;
// If this is an escaped exception from on_request then let it fly! outgoing.http_return = 500;
// Seriously though, this way it is obvious to the user that something bad happened outgoing.http_return_status = e.what();
// since they probably won't have the dlib logger enabled. write_http_response(out, outgoing, std::string("Error processing request: ") + e.what());
if (!my_fault)
throw;
} }
// ----------------------------------------------------------------------------------------
template <
typename server_base
>
class server_http_1 : public server_base
{
/*!
CONVENTION
this extension doesn't add any new state to this object.
!*/
public:
server_http_1()
{
max_content_length = 10*1024*1024; // 10MB
}
unsigned long get_max_content_length (
) const { return max_content_length; }
void set_max_content_length (
unsigned long max_length
)
{
max_content_length = max_length;
}
private:
virtual const std::string on_request (
const incoming_things& incoming,
outgoing_things& outgoing
) = 0;
void on_connect (
std::istream& in,
std::ostream& out,
const std::string& foreign_ip,
const std::string& local_ip,
unsigned short foreign_port,
unsigned short local_port,
uint64
)
{
try
{
incoming_things incoming(foreign_ip, local_ip, foreign_port, local_port);
outgoing_things outgoing;
parse_http_request(in, incoming, max_content_length);
read_body(in, incoming);
const std::string& result = on_request(incoming, outgoing);
write_http_response(out, outgoing, result);
}
catch (http_parse_error& e)
{
dlog << LERROR << "Error processing request from: " << foreign_ip << " - " << e.what();
write_http_response(out, e);
}
catch (std::exception& e)
{
dlog << LERROR << "Error processing request from: " << foreign_ip << " - " << e.what();
write_http_response(out, e);
}
} }
unsigned long max_content_length; unsigned long max_content_length;
...@@ -498,6 +572,3 @@ namespace dlib ...@@ -498,6 +572,3 @@ namespace dlib
#endif // DLIB_SERVER_HTTp_1_ #endif // DLIB_SERVER_HTTp_1_
...@@ -193,7 +193,8 @@ namespace dlib ...@@ -193,7 +193,8 @@ namespace dlib
- outgoing.http_return and outgoing.http_return_status may be set to override the - outgoing.http_return and outgoing.http_return_status may be set to override the
default HTTP return code of 200 OK default HTTP return code of 200 OK
throws throws
- does not throw any exceptions - throws only exceptions derived from std::exception. If an exception is thrown
then the error string from the exception is returned to the web browser.
!*/ !*/
}; };
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment