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
typedef sockstreambuf::kernel_1a ssbuf1a;
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;
public:
......@@ -52,9 +49,9 @@ namespace dlib
iostream_1a_c;
// http_1a
typedef server_http_1<iostream_1a,map_ss_type,queue_type>
typedef server_http_1<iostream_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;
};
......
// 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.
#ifndef DLIB_SERVER_HTTp_1_
#define DLIB_SERVER_HTTp_1_
......@@ -17,13 +17,16 @@
#pragma warning (disable: 1125)
#endif
#if _MSC_VER
# pragma warning( disable: 4503 )
#endif // _MSC_VER
namespace dlib
{
template <
typename server_base,
typename map_ss_type,
typename queue_string_type
typename server_base
>
class server_http_1 : public server_base
{
......@@ -35,26 +38,100 @@ namespace dlib
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 (
const std::string& path,
std::string& result,
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
typename std::map<Key, Value>::const_iterator ci = std::map<Key, Value>::find(k);
if ( ci == this->end() )
return dummy;
else
return ci->second;
}
Value& operator[](const Key& k)
{
return std::map<Key, Value>::operator [](k);
}
};
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;
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
) const
{
......@@ -69,7 +146,7 @@ namespace dlib
return ch;
}
const std::string decode_query_string (
const std::string urldecode (
const std::string& str
) const
{
......@@ -84,8 +161,8 @@ namespace dlib
}
else if (str[i] == '%' && str.size() > i+2)
{
const unsigned char ch1 = to_hex(str[i+1]);
const unsigned char ch2 = to_hex(str[i+2]);
const unsigned char ch1 = from_hex(str[i+1]);
const unsigned char ch2 = from_hex(str[i+2]);
const unsigned char ch = (ch1 << 4) | ch2;
result += ch;
i += 2;
......@@ -109,95 +186,56 @@ namespace dlib
)
{
bool my_fault = true;
using namespace std;
try
{
enum req_type {get, post} rtype;
using namespace std;
map_type cookies;
string word;
string path;
in >> word;
if (word == "GET" || word == "get")
{
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;
}
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;
in >> incoming.request_type;
// 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;
getline(in,line);
unsigned long content_length = 0;
string content_type;
map_type incoming_headers;
string first_part_of_header;
string::size_type position_of_double_point;
// now loop over all the incoming_headers
while (line.size() > 2)
{
position_of_double_point = line.find_first_of(':');
if ( position_of_double_point != string::npos )
{
first_part_of_header = line.substr(0, position_of_double_point);
if ( incoming_headers.is_in_domain(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 );
}
if ( !incoming_headers[first_part_of_header].empty() )
incoming_headers[ first_part_of_header ] += " ";
incoming_headers[first_part_of_header] += line.substr(position_of_double_point+1);
// look for Content-Type:
if (line.size() > 14 &&
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] == ':'
)
if (line.size() > 14 && strings_equal_ignore_case(line, "Content-Type:", 13))
{
content_type = line.substr(14);
if (content_type[content_type.size()-1] == '\r')
content_type.erase(content_type.size()-1);
}
// look for Content-Length:
else if (line.size() > 16 &&
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] == ':'
)
else if (line.size() > 16 && strings_equal_ignore_case(line, "Content-Length:", 15))
{
istringstream sin(line.substr(16));
sin >> content_length;
......@@ -205,15 +243,7 @@ namespace dlib
content_length = 0;
}
// look for any cookies
else if (line.size() > 6 &&
line[0] == 'C' &&
line[1] == 'o' &&
line[2] == 'o' &&
line[3] == 'k' &&
line[4] == 'i' &&
line[5] == 'e' &&
line[6] == ':'
)
else if (line.size() > 6 && strings_equal_ignore_case(line, "Cookie:", 7))
{
string::size_type pos = 6;
string key, value;
......@@ -242,10 +272,11 @@ namespace dlib
{
if (line[pos] == ';')
{
if (cookies.is_in_domain(key) == false)
cookies.add(key,value);
cookies[urldecode(key)] = urldecode(value);
seen_equal_sign = false;
seen_key_start = false;
key.clear();
value.clear();
}
else
{
......@@ -253,8 +284,12 @@ namespace dlib
}
}
}
if (key.size() > 0 && cookies.is_in_domain(key) == false)
cookies.add(key,value);
if (key.size() > 0)
{
cookies[urldecode(key)] = urldecode(value);
key.clear();
value.clear();
}
}
} // no ':' in it!
getline(in,line);
......@@ -263,20 +298,21 @@ namespace dlib
// 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
// 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)
{
line.resize(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("?");
if (pos != string::npos)
{
word = path.substr(pos+1);
std::string word = path.substr(pos+1);
path = path.substr(0,pos);
for (pos = 0; pos < word.size(); ++pos)
{
......@@ -291,10 +327,10 @@ namespace dlib
pos = word.find_first_of("=");
if (pos != string::npos)
{
string key = decode_query_string(word.substr(0,pos));
string value = decode_query_string(word.substr(pos+1));
if (queries.is_in_domain(key) == false)
queries.add(key,value);
string key = urldecode(word.substr(0,pos));
string value = urldecode(word.substr(pos+1));
queries[key] = value;
}
sin >> word;
}
......@@ -302,33 +338,59 @@ namespace dlib
my_fault = false;
queue_type new_cookies;
map_type response_headers;
key_value_map& new_cookies = outgoing.cookies;
key_value_map& response_headers = outgoing.headers;
// if there wasn't a problem with the input stream at some point
// then lets trigger this request callback.
std::string result;
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;
out << "HTTP/1.0 200 OK\r\n";
// only send this header if the user hasn't told us to send another kind
if (response_headers.is_in_domain("Content-Type") == false &&
response_headers.is_in_domain("content-type") == 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") )
{
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
response_headers.reset();
while (response_headers.move_next())
out << response_headers.element().key() << ':' << response_headers.element().value() << "\r\n";
for( typename key_value_map::const_iterator ci = response_headers.begin(); ci != response_headers.end(); ++ci )
{
out << ci->first << ':' << ci->second << "\r\n";
}
// set any cookies
new_cookies.reset();
while (new_cookies.move_next())
for( typename key_value_map::const_iterator ci = new_cookies.begin(); ci != new_cookies.end(); ++ci )
{
out << "Set-Cookie: " << new_cookies.element() << "\r\n";
out << "Set-Cookie: " << urlencode(ci->first) << '=' << urlencode(ci->second) << "\r\n";
}
out << "\r\n" << result;
}
......@@ -348,11 +410,9 @@ namespace dlib
};
template <
typename server_base,
typename map_ss_type,
typename queue_string_type
typename server_base
>
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_
......
// 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.
#undef DLIB_SERVER_HTTp_ABSTRACT_
#ifdef DLIB_SERVER_HTTp_ABSTRACT_
......@@ -13,9 +13,7 @@ namespace dlib
{
template <
typename server_base,
typename map_ss_type,
typename queue_string_type
typename server_base
>
class server_http : public server_base
{
......@@ -24,31 +22,24 @@ namespace dlib
REQUIREMENTS ON server_base
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
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
on_request() callback.
COOKIE STRINGS
The strings returned in the new_cookies queue should be of the following form:
cookie_name=cookie contents; expires=Fri, 31-Dec-2010 23:59:59 GMT; path=/; domain=.example.net
The strings returned in the cookies key_value_map should be of the following form:
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
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
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
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
client you may do so by setting the "Content-Type" header to whatever you like.
......@@ -57,53 +48,118 @@ namespace dlib
!*/
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:
void on_request (
const std::string& path,
std::string& result,
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
virtual const std::string on_request (
const incoming_things& incoming,
outgoing_things& outgoing
) = 0;
/*!
requires
- 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
- is_running() == true
- the number of current on_request() functions running < get_max_connection()
- new_cookies.size() == 0
- response_headers.size() == 0
- incoming_headers == a map that contains all the incoming HTTP headers
from the client web browser.
- in incoming:
- incoming.path == the path being requested by this request
- incoming.request_type == the type of request, GET or POST
- 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
- #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()
- #new_cookies == a set of new cookies to pass back to the client along
with the result of this request.
- #response_headers == a set of additional headers you wish to appear in the
HTTP response to this request. (This may be empty)
- #outgoing.cookies == a set of new cookies to pass back to the client along
with the result of this request. (Note that URL-encoding is automatically applied
so you don't have to do it yourself)
- #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
- 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