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 @@
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:
server_http_1()
class http_parse_error : public error
{
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 { return max_content_length; }
const int http_error_code;
};
void set_max_content_length (
unsigned long max_length
)
{
max_content_length = max_length;
}
// ----------------------------------------------------------------------------------------
template <typename Key, typename Value>
class constmap : public std::map<Key, Value>
......@@ -78,10 +62,20 @@ namespace dlib
typedef constmap< std::string, std::string > key_value_map;
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 request_type;
......@@ -101,7 +95,7 @@ namespace dlib
struct outgoing_things
{
outgoing_things() : http_return(200) { }
outgoing_things() : http_return(200), http_return_status("OK") { }
key_value_map cookies;
key_value_map headers;
......@@ -109,19 +103,16 @@ namespace dlib
std::string http_return_status;
};
// ----------------------------------------------------------------------------------------
private:
virtual const std::string on_request (
const incoming_things& incoming,
outgoing_things& outgoing
) = 0;
unsigned char to_hex( unsigned char x ) const
namespace http_impl
{
inline unsigned char to_hex( unsigned char x )
{
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;
......@@ -146,9 +137,9 @@ namespace dlib
return os.str();
}
unsigned char from_hex (
inline unsigned char from_hex (
unsigned char ch
) const
)
{
if (ch <= '9' && ch >= '0')
ch -= '0';
......@@ -161,9 +152,9 @@ namespace dlib
return ch;
}
const std::string urldecode (
inline const std::string urldecode (
const std::string& str
) const
)
{
using namespace std;
string result;
......@@ -190,7 +181,10 @@ namespace dlib
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
after the ? in the query URL.
......@@ -220,11 +214,11 @@ namespace dlib
}
}
void read_with_limit(
inline void read_with_limit(
std::istream& in,
std::string& buffer,
int delim = '\n'
) const
)
{
using namespace std;
const size_t max = 16*1024;
......@@ -236,6 +230,10 @@ namespace dlib
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.
if (in.get() != delim)
{
......@@ -252,31 +250,17 @@ namespace dlib
}
}
}
}
void on_connect (
inline unsigned long parse_http_request (
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
incoming_things& incoming,
unsigned long max_content_length
)
{
bool my_fault = true;
using namespace std;
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;
using namespace http_impl;
read_with_limit(in, incoming.request_type, ' ');
// get the path
......@@ -320,7 +304,16 @@ namespace dlib
istringstream sin(line.substr(16));
sin >> content_length;
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
else if (line.size() > 6 && strings_equal_ignore_case(line, "Cookie:", 7))
......@@ -375,18 +368,6 @@ namespace dlib
read_with_limit(in, line);
} // 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
// pick out the queries using parse_url.
......@@ -394,6 +375,11 @@ namespace dlib
strings_equal_ignore_case(incoming.request_type, "PUT")) &&
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);
}
......@@ -404,32 +390,43 @@ namespace dlib
}
my_fault = false;
key_value_map& new_cookies = outgoing.cookies;
key_value_map& response_headers = outgoing.headers;
if (!in)
throw http_parse_error("Error parsing HTTP request", 500);
// Set some defaults
outgoing.http_return = 200;
outgoing.http_return_status = "OK";
return content_length;
}
// if there wasn't a problem with the input stream at some point
// then lets trigger this request callback.
std::string result;
if (in)
inline void read_body (
std::istream& in,
incoming_things& incoming
)
{
result = on_request(incoming, outgoing);
}
else
// if the body hasn't already been loaded and there is data to load
if (incoming.body.size() == 0 &&
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";
outgoing.http_return = 413;
outgoing.http_return_status = "Request Entity Too Large";
in.read(&incoming.body[0],content_length);
}
}
}
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
bool has_content_type(false),
has_location(false);
bool has_content_type = false, has_location = false;
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") )
......@@ -452,12 +449,7 @@ namespace dlib
response_headers["Content-Type"] = "text/html";
}
{
ostringstream os;
os << result.size();
response_headers["Content-Length"] = os.str();
}
response_headers["Content-Length"] = cast_to_string(result.size());
out << "HTTP/1.0 " << outgoing.http_return << " " << outgoing.http_return_status << "\r\n";
......@@ -474,16 +466,98 @@ namespace dlib
}
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()";
// If this is an escaped exception from on_request then let it fly!
// Seriously though, this way it is obvious to the user that something bad happened
// since they probably won't have the dlib logger enabled.
if (!my_fault)
throw;
outgoing_things outgoing;
outgoing.http_return = 500;
outgoing.http_return_status = e.what();
write_http_response(out, outgoing, std::string("Error processing request: ") + e.what());
}
// ----------------------------------------------------------------------------------------
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;
......@@ -498,6 +572,3 @@ namespace dlib
#endif // DLIB_SERVER_HTTp_1_
......@@ -193,7 +193,8 @@ namespace dlib
- outgoing.http_return and outgoing.http_return_status may be set to override the
default HTTP return code of 200 OK
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