Commit 75b4c4fc authored by Davis King's avatar Davis King

Merged in Steven Van Ingelgem's patch to cleanup the HTTP server and

add new functionality.  This breaks backwards compatibility with the
previous on_request() interface but it is easy to update old code and
it is now much cleaner.

--HG--
extra : convert_revision : svn%3Afdd8eb12-d10e-0410-9acb-85c331704f74/trunk%402931
parent e5dbf8ff
...@@ -30,9 +30,6 @@ namespace dlib ...@@ -30,9 +30,6 @@ namespace dlib
typedef sockstreambuf::kernel_1a ssbuf1a; typedef sockstreambuf::kernel_1a ssbuf1a;
typedef sockstreambuf::kernel_2a ssbuf2a; typedef sockstreambuf::kernel_2a ssbuf2a;
typedef map<std::string,std::string>::kernel_1a_c map_ss_type;
typedef queue<std::string>::kernel_1a_c queue_type;
typedef map<uint64,connection*,memory_manager<char>::kernel_2a>::kernel_1b id_map; typedef map<uint64,connection*,memory_manager<char>::kernel_2a>::kernel_1b id_map;
public: public:
...@@ -52,9 +49,9 @@ namespace dlib ...@@ -52,9 +49,9 @@ namespace dlib
iostream_1a_c; iostream_1a_c;
// http_1a // http_1a
typedef server_http_1<iostream_1a,map_ss_type,queue_type> typedef server_http_1<iostream_1a>
http_1a; http_1a;
typedef server_http_1<iostream_1a_c,map_ss_type,queue_type> typedef server_http_1<iostream_1a_c>
http_1a_c; http_1a_c;
}; };
......
// Copyright (C) 2006 Davis E. King (davisking@users.sourceforge.net) // Copyright (C) 2006 Davis E. King (davisking@users.sourceforge.net), Steven Van Ingelgem
// License: Boost Software License See LICENSE.txt for the full license. // License: Boost Software License See LICENSE.txt for the full license.
#ifndef DLIB_SERVER_HTTp_1_ #ifndef DLIB_SERVER_HTTp_1_
#define DLIB_SERVER_HTTp_1_ #define DLIB_SERVER_HTTp_1_
...@@ -17,13 +17,16 @@ ...@@ -17,13 +17,16 @@
#pragma warning (disable: 1125) #pragma warning (disable: 1125)
#endif #endif
#if _MSC_VER
# pragma warning( disable: 4503 )
#endif // _MSC_VER
namespace dlib namespace dlib
{ {
template < template <
typename server_base, typename server_base
typename map_ss_type,
typename queue_string_type
> >
class server_http_1 : public server_base class server_http_1 : public server_base
{ {
...@@ -35,26 +38,100 @@ namespace dlib ...@@ -35,26 +38,100 @@ namespace dlib
public: public:
typedef map_ss_type map_type;
typedef queue_string_type queue_type;
private: template <typename Key, typename Value>
class constmap : public std::map<Key, Value>
{
public:
const Value& operator[](const Key& k) const
{
static const Value dummy = Value();
virtual void on_request ( typename std::map<Key, Value>::const_iterator ci = std::map<Key, Value>::find(k);
const std::string& path,
std::string& result, if ( ci == this->end() )
const map_type& queries, return dummy;
const map_type& cookies, else
queue_type& new_cookies, return ci->second;
const map_type& incoming_headers, }
map_type& response_headers,
const std::string& foreign_ip, Value& operator[](const Key& k)
const std::string& local_ip, {
unsigned short foreign_port, return std::map<Key, Value>::operator [](k);
unsigned short local_port }
};
typedef constmap< std::string, std::string > key_value_map;
struct incoming_things
{
incoming_things() : foreign_port(0), local_port(0), content_length(0) {}
std::string path;
std::string request_type;
std::string content_type;
key_value_map queries;
key_value_map cookies;
key_value_map headers;
std::string foreign_ip;
unsigned short foreign_port;
std::string local_ip;
unsigned short local_port;
unsigned long content_length;
};
struct outgoing_things
{
outgoing_things() : http_return(200) { }
key_value_map cookies;
key_value_map headers;
unsigned short http_return;
};
private:
virtual const std::string on_request (
const incoming_things& incoming,
outgoing_things& outgoing
) = 0; ) = 0;
unsigned char to_hex ( unsigned char to_hex( unsigned char x ) const
{
return x + (x > 9 ? 'A' : '0');
}
const std::string urlencode( const std::string& s ) const
{
std::ostringstream os;
for ( std::string::const_iterator ci = s.begin(); ci != s.end(); ++ci )
{
if ( (*ci >= 'a' && *ci <= 'z') ||
(*ci >= 'A' && *ci <= 'Z') ||
(*ci >= '0' && *ci <= '9') )
{ // allowed
os << *ci;
}
else if ( *ci == ' ')
{
os << '+';
}
else
{
os << '%' << to_hex(*ci >> 4) << to_hex(*ci % 16);
}
}
return os.str();
}
unsigned char from_hex (
unsigned char ch unsigned char ch
) const ) const
{ {
...@@ -69,7 +146,7 @@ namespace dlib ...@@ -69,7 +146,7 @@ namespace dlib
return ch; return ch;
} }
const std::string decode_query_string ( const std::string urldecode (
const std::string& str const std::string& str
) const ) const
{ {
...@@ -84,8 +161,8 @@ namespace dlib ...@@ -84,8 +161,8 @@ namespace dlib
} }
else if (str[i] == '%' && str.size() > i+2) else if (str[i] == '%' && str.size() > i+2)
{ {
const unsigned char ch1 = to_hex(str[i+1]); const unsigned char ch1 = from_hex(str[i+1]);
const unsigned char ch2 = to_hex(str[i+2]); const unsigned char ch2 = from_hex(str[i+2]);
const unsigned char ch = (ch1 << 4) | ch2; const unsigned char ch = (ch1 << 4) | ch2;
result += ch; result += ch;
i += 2; i += 2;
...@@ -109,95 +186,56 @@ namespace dlib ...@@ -109,95 +186,56 @@ namespace dlib
) )
{ {
bool my_fault = true; bool my_fault = true;
using namespace std;
try try
{ {
enum req_type {get, post} rtype; incoming_things incoming;
outgoing_things outgoing;
using namespace std;
map_type cookies; incoming.foreign_ip = foreign_ip;
string word; incoming.foreign_port = foreign_port;
string path; incoming.local_ip = local_ip;
in >> word; incoming.local_port = local_port;
if (word == "GET" || word == "get")
{ in >> incoming.request_type;
rtype = get;
}
else if ( word == "POST" || word == "post")
{
rtype = post;
}
else
{
// this isn't a GET or POST request so just drop the connection
return;
}
// get the path // get the path
in >> path; in >> incoming.path;
key_value_map& incoming_headers = incoming.headers;
key_value_map& cookies = incoming.cookies;
key_value_map& queries = incoming.queries;
std::string& path = incoming.path;
std::string& content_type = incoming.content_type;
unsigned long& content_length = incoming.content_length;
// now loop over all the incoming_headers
string line; string line;
getline(in,line); getline(in,line);
unsigned long content_length = 0;
string content_type;
map_type incoming_headers;
string first_part_of_header; string first_part_of_header;
string::size_type position_of_double_point; string::size_type position_of_double_point;
// now loop over all the incoming_headers
while (line.size() > 2) while (line.size() > 2)
{ {
position_of_double_point = line.find_first_of(':'); position_of_double_point = line.find_first_of(':');
if ( position_of_double_point != string::npos ) if ( position_of_double_point != string::npos )
{ {
first_part_of_header = line.substr(0, position_of_double_point); first_part_of_header = line.substr(0, position_of_double_point);
if ( incoming_headers.is_in_domain(first_part_of_header) )
{ if ( !incoming_headers[first_part_of_header].empty() )
incoming_headers[ first_part_of_header ] += " " + line.substr(position_of_double_point+1); incoming_headers[ first_part_of_header ] += " ";
} incoming_headers[first_part_of_header] += line.substr(position_of_double_point+1);
else
{
string second_part_of_header(line.substr(position_of_double_point+1));
incoming_headers.add( first_part_of_header, second_part_of_header );
}
// look for Content-Type: // look for Content-Type:
if (line.size() > 14 && if (line.size() > 14 && strings_equal_ignore_case(line, "Content-Type:", 13))
line[0] == 'C' &&
line[1] == 'o' &&
line[2] == 'n' &&
line[3] == 't' &&
line[4] == 'e' &&
line[5] == 'n' &&
line[6] == 't' &&
line[7] == '-' &&
(line[8] == 'T' || line[8] == 't') &&
line[9] == 'y' &&
line[10] == 'p' &&
line[11] == 'e' &&
line[12] == ':'
)
{ {
content_type = line.substr(14); content_type = line.substr(14);
if (content_type[content_type.size()-1] == '\r') if (content_type[content_type.size()-1] == '\r')
content_type.erase(content_type.size()-1); content_type.erase(content_type.size()-1);
} }
// look for Content-Length: // look for Content-Length:
else if (line.size() > 16 && else if (line.size() > 16 && strings_equal_ignore_case(line, "Content-Length:", 15))
line[0] == 'C' &&
line[1] == 'o' &&
line[2] == 'n' &&
line[3] == 't' &&
line[4] == 'e' &&
line[5] == 'n' &&
line[6] == 't' &&
line[7] == '-' &&
(line[8] == 'L' || line[8] == 'l') &&
line[9] == 'e' &&
line[10] == 'n' &&
line[11] == 'g' &&
line[12] == 't' &&
line[13] == 'h' &&
line[14] == ':'
)
{ {
istringstream sin(line.substr(16)); istringstream sin(line.substr(16));
sin >> content_length; sin >> content_length;
...@@ -205,15 +243,7 @@ namespace dlib ...@@ -205,15 +243,7 @@ namespace dlib
content_length = 0; content_length = 0;
} }
// look for any cookies // look for any cookies
else if (line.size() > 6 && else if (line.size() > 6 && strings_equal_ignore_case(line, "Cookie:", 7))
line[0] == 'C' &&
line[1] == 'o' &&
line[2] == 'o' &&
line[3] == 'k' &&
line[4] == 'i' &&
line[5] == 'e' &&
line[6] == ':'
)
{ {
string::size_type pos = 6; string::size_type pos = 6;
string key, value; string key, value;
...@@ -242,10 +272,11 @@ namespace dlib ...@@ -242,10 +272,11 @@ namespace dlib
{ {
if (line[pos] == ';') if (line[pos] == ';')
{ {
if (cookies.is_in_domain(key) == false) cookies[urldecode(key)] = urldecode(value);
cookies.add(key,value);
seen_equal_sign = false; seen_equal_sign = false;
seen_key_start = false; seen_key_start = false;
key.clear();
value.clear();
} }
else else
{ {
...@@ -253,8 +284,12 @@ namespace dlib ...@@ -253,8 +284,12 @@ namespace dlib
} }
} }
} }
if (key.size() > 0 && cookies.is_in_domain(key) == false) if (key.size() > 0)
cookies.add(key,value); {
cookies[urldecode(key)] = urldecode(value);
key.clear();
value.clear();
}
} }
} // no ':' in it! } // no ':' in it!
getline(in,line); getline(in,line);
...@@ -263,20 +298,21 @@ namespace dlib ...@@ -263,20 +298,21 @@ namespace dlib
// 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
// just stick it onto the end of the path so the following code can // just stick it onto the end of the path so the following code can
// then just pick it out like we do for GET requests. // then just pick it out like we do for GET requests.
if (rtype == post && tolower(left_substr(content_type,";")) == "application/x-www-form-urlencoded" if (strings_equal_ignore_case(incoming.request_type, "POST") &&
strings_equal_ignore_case(left_substr(content_type,";"), "application/x-www-form-urlencoded")
&& content_length > 0) && content_length > 0)
{ {
line.resize(content_length); line.resize(content_length);
in.read(&line[0],content_length); in.read(&line[0],content_length);
path += "?" + line;
path += (path.find('?') == std::string::npos) ? '?' : '&';
path += line;
} }
string result;
map_type queries;
string::size_type pos = path.find_first_of("?"); string::size_type pos = path.find_first_of("?");
if (pos != string::npos) if (pos != string::npos)
{ {
word = path.substr(pos+1); std::string word = path.substr(pos+1);
path = path.substr(0,pos); path = path.substr(0,pos);
for (pos = 0; pos < word.size(); ++pos) for (pos = 0; pos < word.size(); ++pos)
{ {
...@@ -291,10 +327,10 @@ namespace dlib ...@@ -291,10 +327,10 @@ namespace dlib
pos = word.find_first_of("="); pos = word.find_first_of("=");
if (pos != string::npos) if (pos != string::npos)
{ {
string key = decode_query_string(word.substr(0,pos)); string key = urldecode(word.substr(0,pos));
string value = decode_query_string(word.substr(pos+1)); string value = urldecode(word.substr(pos+1));
if (queries.is_in_domain(key) == false)
queries.add(key,value); queries[key] = value;
} }
sin >> word; sin >> word;
} }
...@@ -302,33 +338,59 @@ namespace dlib ...@@ -302,33 +338,59 @@ namespace dlib
my_fault = false; my_fault = false;
queue_type new_cookies; key_value_map& new_cookies = outgoing.cookies;
map_type response_headers; key_value_map& response_headers = outgoing.headers;
// if there wasn't a problem with the input stream at some point // if there wasn't a problem with the input stream at some point
// then lets trigger this request callback. // then lets trigger this request callback.
std::string result;
if (in) if (in)
on_request(path,result,queries,cookies,new_cookies,incoming_headers, response_headers, foreign_ip,local_ip,foreign_port,local_port); result = on_request(incoming, outgoing);
my_fault = true; my_fault = true;
out << "HTTP/1.0 200 OK\r\n";
// 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
if (response_headers.is_in_domain("Content-Type") == false && bool has_content_type(false),
response_headers.is_in_domain("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") )
{
has_content_type = true;
}
else if ( !has_location && strings_equal_ignore_case(ci->first , "location") )
{
has_location = true;
}
}
if ( has_location )
{ {
out << "Content-Type: text/html\r\n"; outgoing.http_return = 302;
} }
out << "Content-Length: " << result.size() << "\r\n";
if ( !has_content_type )
{
response_headers["Content-Type"] = "text/html";
}
{
ostringstream os;
os << result.size();
response_headers["Content-Length"] = os.str();
}
out << "HTTP/1.0 " << outgoing.http_return << " OK\r\n";
// Set any new headers // Set any new headers
response_headers.reset(); for( typename key_value_map::const_iterator ci = response_headers.begin(); ci != response_headers.end(); ++ci )
while (response_headers.move_next()) {
out << response_headers.element().key() << ':' << response_headers.element().value() << "\r\n"; out << ci->first << ':' << ci->second << "\r\n";
}
// set any cookies // set any cookies
new_cookies.reset(); for( typename key_value_map::const_iterator ci = new_cookies.begin(); ci != new_cookies.end(); ++ci )
while (new_cookies.move_next())
{ {
out << "Set-Cookie: " << new_cookies.element() << "\r\n"; out << "Set-Cookie: " << urlencode(ci->first) << '=' << urlencode(ci->second) << "\r\n";
} }
out << "\r\n" << result; out << "\r\n" << result;
} }
...@@ -348,11 +410,9 @@ namespace dlib ...@@ -348,11 +410,9 @@ namespace dlib
}; };
template < template <
typename server_base, typename server_base
typename map_ss_type,
typename queue_string_type
> >
const logger server_http_1<server_base,map_ss_type,queue_string_type>::dlog("dlib.server"); const logger server_http_1<server_base>::dlog("dlib.server");
} }
#endif // DLIB_SERVER_HTTp_1_ #endif // DLIB_SERVER_HTTp_1_
......
// Copyright (C) 2006 Davis E. King (davisking@users.sourceforge.net) // Copyright (C) 2006 Davis E. King (davisking@users.sourceforge.net), Steven Van Ingelgem
// License: Boost Software License See LICENSE.txt for the full license. // License: Boost Software License See LICENSE.txt for the full license.
#undef DLIB_SERVER_HTTp_ABSTRACT_ #undef DLIB_SERVER_HTTp_ABSTRACT_
#ifdef DLIB_SERVER_HTTp_ABSTRACT_ #ifdef DLIB_SERVER_HTTp_ABSTRACT_
...@@ -13,9 +13,7 @@ namespace dlib ...@@ -13,9 +13,7 @@ namespace dlib
{ {
template < template <
typename server_base, typename server_base
typename map_ss_type,
typename queue_string_type
> >
class server_http : public server_base class server_http : public server_base
{ {
...@@ -24,31 +22,24 @@ namespace dlib ...@@ -24,31 +22,24 @@ namespace dlib
REQUIREMENTS ON server_base REQUIREMENTS ON server_base
is an implementation of server/server_iostream_abstract.h is an implementation of server/server_iostream_abstract.h
REQUIREMENTS ON map_ss_type
is an implementation of map/map_kernel_abstract.h with
domain set to std::string and range set to std::string
REQUIREMENTS ON queue_string_type
is an implementation of queue/queue_kernel_abstract.h with
T set to std::string
WHAT THIS EXTENSION DOES FOR SERVER IOSTREAM WHAT THIS EXTENSION DOES FOR SERVER IOSTREAM
This extension turns the server object into a simple HTTP server. This extension turns the server object into a simple HTTP server.
It only handles HTTP GET and POST requests and each incoming request triggers the It only handles HTTP GET and POST requests and each incoming request triggers the
on_request() callback. on_request() callback.
COOKIE STRINGS COOKIE STRINGS
The strings returned in the new_cookies queue should be of the following form: The strings returned in the cookies key_value_map should be of the following form:
cookie_name=cookie contents; expires=Fri, 31-Dec-2010 23:59:59 GMT; path=/; domain=.example.net key: cookie_name
value: cookie contents; expires=Fri, 31-Dec-2010 23:59:59 GMT; path=/; domain=.example.net
You don't have to supply all the extra cookie arguments. So if you just want to You don't have to supply all the extra cookie arguments. So if you just want to
set a cookie that will expire when the client's browser is closed you can set a cookie that will expire when the client's browser is closed you can
use a string such as "cookie_name=cookie contents" just say something like incoming.cookies["cookie_name"] = "cookie contents";
HTTP HEADERS HTTP HEADERS
The HTTP headers in the incoming_headers and response_headers are the name/value pairs The HTTP headers in the incoming.headers and outgoing.headers are the name/value pairs
of HTTP headers. For example, the HTTP header "Content-Type: text/html" would be of HTTP headers. For example, the HTTP header "Content-Type: text/html" would be
encoded such that response_headers["Content-Type"] == "text/html". encoded such that outgoing.headers["Content-Type"] == "text/html".
Also note that if you wish to change the content type of your response to the Also note that if you wish to change the content type of your response to the
client you may do so by setting the "Content-Type" header to whatever you like. client you may do so by setting the "Content-Type" header to whatever you like.
...@@ -57,53 +48,118 @@ namespace dlib ...@@ -57,53 +48,118 @@ namespace dlib
!*/ !*/
public: public:
typedef map_ss_type map_type;
typedef queue_string_type queue_type; template <typename Key, typename Value>
class constmap : public std::map<Key, Value>
{
/*!
WHAT THIS OBJECT REPRESENTS
This is simply an extension to the std::map that allows you
to use the operator[] accessor with a constant map.
!*/
public:
const Value& operator[](
const Key& k
) const;
/*!
ensures
- if (this->find(k) != this->end()) then
- This map contains the given key
- return the value associated with the given key
- else
- return a default initialized Value object
!*/
Value& operator[](
const Key& k
) { return std::map<Key, Value>::operator [](k); }
/*!
ensures
- This function does the same thing as the normal std::map operator[]
function.
- if (this->find(k) != this->end()) then
- This map contains the given key
- return the value associated with the given key
- else
- Adds a new entry into the map that is associated with the
given key. The new entry will be default initialized and
this function returns a reference to it.
!*/
};
typedef constmap<std::string, std::string> key_value_map;
struct incoming_things
{
std::string path;
std::string request_type;
std::string content_type;
key_value_map queries;
key_value_map cookies;
key_value_map headers;
std::string foreign_ip;
unsigned short foreign_port;
std::string local_ip;
unsigned short local_port;
unsigned long content_length;
};
struct outgoing_things
{
key_value_map cookies;
key_value_map headers;
unsigned short http_return;
};
private: private:
void on_request ( virtual const std::string on_request (
const std::string& path, const incoming_things& incoming,
std::string& result, outgoing_things& outgoing
const map_type& queries,
const map_type& cookies,
queue_type& new_cookies,
const map_type& incoming_headers,
map_type& response_headers,
const std::string& foreign_ip,
const std::string& local_ip,
unsigned short foreign_port,
unsigned short local_port
) = 0; ) = 0;
/*! /*!
requires requires
- on_request() is called when there is an HTTP GET or POST request to be serviced - on_request() is called when there is an HTTP GET or POST request to be serviced
- path == the path being requested by this request
- queries == a map that contains all the key/value pairs in the query string
of this request. The key and value strings of the query string will
have been decoded back into their original form before being sent to this
function (i.e. '+' decoded back to ' ' and "%hh" into its corresponding
ascii value)
- cookies == The set of cookies that came from the client along with this
request.
- foreign_ip == the foreign ip address for this request
- foreign_port == the foreign port number for this request
- local_ip == the IP of the local interface this request is coming in on
- local_port == the local port number this request is coming in on
- on_request() is run in its own thread - on_request() is run in its own thread
- is_running() == true - is_running() == true
- the number of current on_request() functions running < get_max_connection() - the number of current on_request() functions running < get_max_connection()
- new_cookies.size() == 0 - in incoming:
- response_headers.size() == 0 - incoming.path == the path being requested by this request
- incoming_headers == a map that contains all the incoming HTTP headers - incoming.request_type == the type of request, GET or POST
from the client web browser. - incoming.content_type == the content type associated with this request
- incoming.queries == a map that contains all the key/value pairs in the query
string of this request. The key and value strings of the query string will
have been decoded back into their original form before being sent to this
function (i.e. '+' decoded back to ' ' and "%hh" into its corresponding
ascii value. So the URL-encoding is decoded automatically)
- incoming.cookies == The set of cookies that came from the client along with
this request. The cookies will have been decoded back to normal form
from the URL-encoding.
- incoming.headers == a map that contains all the incoming HTTP headers
from the client web browser.
- incoming.foreign_ip == the foreign ip address for this request
- incoming.foreign_port == the foreign port number for this request
- incoming.local_ip == the IP of the local interface this request is coming in on
- incoming.local_port == the local port number this request is coming in on
- incoming.content_length == the value from the incoming Content-Length header
- in outgoing:
- outgoing.cookies.size() == 0
- outgoing.headers.size() == 0
- outgoing.http_return == 200
ensures ensures
- #result == the HTML page to be displayed as the response to this request. - This function returns the HTML page to be displayed as the response to this request.
- this function will not call clear() - this function will not call clear()
- #new_cookies == a set of new cookies to pass back to the client along - #outgoing.cookies == a set of new cookies to pass back to the client along
with the result of this request. with the result of this request. (Note that URL-encoding is automatically applied
- #response_headers == a set of additional headers you wish to appear in the so you don't have to do it yourself)
HTTP response to this request. (This may be empty) - #outgoing.headers == a set of additional headers you wish to appear in the
HTTP response to this request. (This may be empty, the minimum needed headers
will be added automatically if you don't set them)
- outgoing.http_return may be set to override the default HTTP return code of 200
throws throws
- does not throw any exceptions - does not throw any exceptions
!*/ !*/
......
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