diff options
Diffstat (limited to 'ml/dlib/dlib/server')
-rw-r--r-- | ml/dlib/dlib/server/server_http.cpp | 409 | ||||
-rw-r--r-- | ml/dlib/dlib/server/server_http.h | 242 | ||||
-rw-r--r-- | ml/dlib/dlib/server/server_http_abstract.h | 390 | ||||
-rw-r--r-- | ml/dlib/dlib/server/server_iostream.cpp | 14 | ||||
-rw-r--r-- | ml/dlib/dlib/server/server_iostream.h | 155 | ||||
-rw-r--r-- | ml/dlib/dlib/server/server_iostream_abstract.h | 84 | ||||
-rw-r--r-- | ml/dlib/dlib/server/server_kernel.cpp | 595 | ||||
-rw-r--r-- | ml/dlib/dlib/server/server_kernel.h | 234 | ||||
-rw-r--r-- | ml/dlib/dlib/server/server_kernel_abstract.h | 310 |
9 files changed, 2433 insertions, 0 deletions
diff --git a/ml/dlib/dlib/server/server_http.cpp b/ml/dlib/dlib/server/server_http.cpp new file mode 100644 index 000000000..9e3051a43 --- /dev/null +++ b/ml/dlib/dlib/server/server_http.cpp @@ -0,0 +1,409 @@ +// Copyright (C) 2003 Davis E. King (davis@dlib.net) +// License: Boost Software License See LICENSE.txt for the full license. +#ifndef DLIB_SERVER_HTTP_CPp_ +#define DLIB_SERVER_HTTP_CPp_ + +#include "server_http.h" + +namespace dlib +{ + +// ---------------------------------------------------------------------------------------- + + 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 ) + { + 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(); + } + + inline unsigned char from_hex ( + unsigned char ch + ) + { + if (ch <= '9' && ch >= '0') + ch -= '0'; + else if (ch <= 'f' && ch >= 'a') + ch -= 'a' - 10; + else if (ch <= 'F' && ch >= 'A') + ch -= 'A' - 10; + else + ch = 0; + return ch; + } + + const std::string urldecode ( + const std::string& str + ) + { + using namespace std; + string result; + string::size_type i; + for (i = 0; i < str.size(); ++i) + { + if (str[i] == '+') + { + result += ' '; + } + else if (str[i] == '%' && str.size() > 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; + } + else + { + result += str[i]; + } + } + return result; + } + + 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. + !*/ + { + std::string::size_type pos; + + for (pos = 0; pos < word.size(); ++pos) + { + if (word[pos] == '&') + word[pos] = ' '; + } + + std::istringstream sin(word); + sin >> word; + while (sin) + { + pos = word.find_first_of("="); + if (pos != std::string::npos) + { + std::string key = urldecode(word.substr(0,pos)); + std::string value = urldecode(word.substr(pos+1)); + + queries[key] = value; + } + sin >> word; + } + } + + void read_with_limit( + std::istream& in, + std::string& buffer, + int delim = '\n' + ) + { + using namespace std; + const size_t max = 64*1024; + buffer.clear(); + buffer.reserve(300); + + while (in.peek() != delim && in.peek() != '\n' && in.peek() != EOF && buffer.size() < max) + { + buffer += (char)in.get(); + } + + // if we quit the loop because the data is longer than expected or we hit EOF + if (in.peek() == EOF) + throw http_parse_error("HTTP field from client terminated incorrectly", 414); + if (buffer.size() == max) + throw http_parse_error("HTTP field from client is too long", 414); + + in.get(); + // eat any remaining whitespace + if (delim == ' ') + { + while (in.peek() == ' ') + in.get(); + } + } + } + +// ---------------------------------------------------------------------------------------- + + unsigned long parse_http_request ( + std::istream& in, + incoming_things& incoming, + unsigned long max_content_length + ) + { + using namespace std; + using namespace http_impl; + read_with_limit(in, incoming.request_type, ' '); + + // get the path + read_with_limit(in, incoming.path, ' '); + + // Get the HTTP/1.1 - Ignore for now... + read_with_limit(in, incoming.protocol); + + key_value_map_ci& incoming_headers = incoming.headers; + key_value_map& cookies = incoming.cookies; + std::string& path = incoming.path; + std::string& content_type = incoming.content_type; + unsigned long content_length = 0; + + string line; + read_with_limit(in, line); + string first_part_of_header; + string::size_type position_of_double_point; + // now loop over all the incoming_headers + while (line != "\r") + { + position_of_double_point = line.find_first_of(':'); + if ( position_of_double_point != string::npos ) + { + first_part_of_header = dlib::trim(line.substr(0, position_of_double_point)); + + if ( !incoming_headers[first_part_of_header].empty() ) + incoming_headers[ first_part_of_header ] += " "; + incoming_headers[first_part_of_header] += dlib::trim(line.substr(position_of_double_point+1)); + + // look for Content-Type: + 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 && strings_equal_ignore_case(line, "Content-Length:", 15)) + { + istringstream sin(line.substr(16)); + sin >> content_length; + if (!sin) + { + 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)) + { + string::size_type pos = 6; + string key, value; + bool seen_key_start = false; + bool seen_equal_sign = false; + while (pos + 1 < line.size()) + { + ++pos; + // ignore whitespace between cookies + if (!seen_key_start && line[pos] == ' ') + continue; + + seen_key_start = true; + if (!seen_equal_sign) + { + if (line[pos] == '=') + { + seen_equal_sign = true; + } + else + { + key += line[pos]; + } + } + else + { + if (line[pos] == ';') + { + cookies[urldecode(key)] = urldecode(value); + seen_equal_sign = false; + seen_key_start = false; + key.clear(); + value.clear(); + } + else + { + value += line[pos]; + } + } + } + if (key.size() > 0) + { + cookies[urldecode(key)] = urldecode(value); + key.clear(); + value.clear(); + } + } + } // no ':' in it! + read_with_limit(in, line); + } // while (line != "\r") + + + // If there is data being posted back to us as a query string then + // pick out the queries using parse_url. + if ((strings_equal_ignore_case(incoming.request_type, "POST") || + 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); + } + + string::size_type pos = path.find_first_of("?"); + if (pos != string::npos) + { + parse_url(path.substr(pos+1), incoming.queries); + } + + + if (!in) + throw http_parse_error("Error parsing HTTP request", 500); + + return content_length; + } + +// ---------------------------------------------------------------------------------------- + + void read_body ( + std::istream& in, + incoming_things& incoming + ) + { + // 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) + { + in.read(&incoming.body[0],content_length); + } + } + } + +// ---------------------------------------------------------------------------------------- + + 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_ci& 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; + for(key_value_map_ci::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 ) + { + outgoing.http_return = 302; + } + + if ( !has_content_type ) + { + response_headers["Content-Type"] = "text/html"; + } + + response_headers["Content-Length"] = cast_to_string(result.size()); + + out << "HTTP/1.0 " << outgoing.http_return << " " << outgoing.http_return_status << "\r\n"; + + // Set any new headers + for(key_value_map_ci::const_iterator ci = response_headers.begin(); ci != response_headers.end(); ++ci ) + { + out << ci->first << ": " << ci->second << "\r\n"; + } + + // set any cookies + for(key_value_map::const_iterator ci = new_cookies.begin(); ci != new_cookies.end(); ++ci ) + { + out << "Set-Cookie: " << urlencode(ci->first) << '=' << urlencode(ci->second) << "\r\n"; + } + out << "\r\n" << result; + } + +// ---------------------------------------------------------------------------------------- + + 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()); + } + +// ---------------------------------------------------------------------------------------- + + void write_http_response ( + std::ostream& out, + const std::exception& e + ) + { + 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()); + } + +// ---------------------------------------------------------------------------------------- + + const logger server_http::dlog("dlib.server_http"); + +// ---------------------------------------------------------------------------------------- + +} + +#endif // DLIB_SERVER_HTTP_CPp_ + diff --git a/ml/dlib/dlib/server/server_http.h b/ml/dlib/dlib/server/server_http.h new file mode 100644 index 000000000..4e95f679f --- /dev/null +++ b/ml/dlib/dlib/server/server_http.h @@ -0,0 +1,242 @@ +// Copyright (C) 2006 Davis E. King (davis@dlib.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_ + + +#include "server_http_abstract.h" +#include <iostream> +#include <sstream> +#include <string> +#include <cctype> +#include <map> +#include "../logger.h" +#include "../string.h" +#include "server_iostream.h" + +#ifdef __INTEL_COMPILER +// ignore the bogus warning about hiding on_connect() +#pragma warning (disable: 1125) +#endif + +#if _MSC_VER +# pragma warning( disable: 4503 ) +#endif // _MSC_VER + + +namespace dlib +{ + +// ---------------------------------------------------------------------------------------- + + class http_parse_error : public error + { + public: + http_parse_error(const std::string& str, int http_error_code_): + error(str),http_error_code(http_error_code_) {} + + const int http_error_code; + }; + +// ---------------------------------------------------------------------------------------- + + template <typename Key, typename Value, typename Comparer = std::less<Key> > + class constmap : public std::map<Key, Value, Comparer> + { + public: + const Value& operator[](const Key& k) const + { + static const Value dummy = Value(); + + typename std::map<Key, Value, Comparer>::const_iterator ci = std::map<Key, Value, Comparer>::find(k); + + if ( ci == this->end() ) + return dummy; + else + return ci->second; + } + + Value& operator[](const Key& k) + { + return std::map<Key, Value, Comparer>::operator [](k); + } + }; + + + class less_case_insensitive + { + public: + bool operator()(const std::string& a, const std::string& b) const + { + unsigned long i = 0; + while (i < a.size() && i < b.size()) + { + const int cha = std::tolower(a[i]); + const int chb = std::tolower(b[i]); + if (cha < chb) + return true; + else if (cha > chb) + return false; + ++i; + } + if (a.size() < b.size()) + return true; + else + return false; + } + }; + typedef constmap< std::string, std::string, less_case_insensitive > key_value_map_ci; + typedef constmap< std::string, std::string > key_value_map; + + struct incoming_things + { + 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; + std::string content_type; + std::string protocol; + std::string body; + + key_value_map queries; + key_value_map cookies; + key_value_map_ci headers; + + std::string foreign_ip; + unsigned short foreign_port; + std::string local_ip; + unsigned short local_port; + }; + + struct outgoing_things + { + outgoing_things() : http_return(200), http_return_status("OK") { } + + key_value_map cookies; + key_value_map_ci headers; + unsigned short http_return; + std::string http_return_status; + }; + +// ---------------------------------------------------------------------------------------- + + unsigned long parse_http_request ( + std::istream& in, + incoming_things& incoming, + unsigned long max_content_length + ); + + void read_body ( + std::istream& in, + incoming_things& incoming + ); + + void write_http_response ( + std::ostream& out, + outgoing_things outgoing, + const std::string& result + ); + + void write_http_response ( + std::ostream& out, + const http_parse_error& e + ); + + void write_http_response ( + std::ostream& out, + const std::exception& e + ); + +// ---------------------------------------------------------------------------------------- + + class server_http : public server_iostream + { + + public: + + server_http() + { + max_content_length = 10*1024*1024; // 10MB + } + + unsigned long get_max_content_length ( + ) const + { + auto_mutex lock(http_class_mutex); + return max_content_length; + } + + void set_max_content_length ( + unsigned long max_length + ) + { + auto_mutex lock(http_class_mutex); + max_content_length = max_length; + } + + + private: + virtual const std::string on_request ( + const incoming_things& incoming, + outgoing_things& outgoing + ) = 0; + + + virtual 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, get_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); + } + } + + mutex http_class_mutex; + unsigned long max_content_length; + const static logger dlog; + }; + +// ---------------------------------------------------------------------------------------- + +} + +#ifdef NO_MAKEFILE +#include "server_http.cpp" +#endif + +#endif // DLIB_SERVER_HTTp_1_ + diff --git a/ml/dlib/dlib/server/server_http_abstract.h b/ml/dlib/dlib/server/server_http_abstract.h new file mode 100644 index 000000000..0bebfb61d --- /dev/null +++ b/ml/dlib/dlib/server/server_http_abstract.h @@ -0,0 +1,390 @@ +// Copyright (C) 2006 Davis E. King (davis@dlib.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_ + +#include "server_iostream_abstract.h" +#include <iostream> +#include <string> +#include <map> + +namespace dlib +{ + +// ----------------------------------------------------------------------------------------- + + template < + typename Key, + typename Value, + typename Comparer = std::less<Key> + > + class constmap : public std::map<Key, Value, Comparer> + { + /*! + 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; + // This version of key_value_map treats the key string as being case-insensitive. + // For example, a key string of "Content-Type" would access the same element as a key + // of "content-type". + typedef constmap<std::string, std::string, less_case_insensitive> key_value_map_ci; + +// ----------------------------------------------------------------------------------------- + + struct incoming_things + { + /*! + WHAT THIS OBJECT REPRESENTS + This object contains all the various bits of information that describe a + HTTP request that comes into a web server. + + For a detailed discussion of the fields of this object, see the + server_http::on_request() method defined later in this file. + !*/ + + incoming_things ( + const std::string& foreign_ip_, + const std::string& local_ip_, + unsigned short foreign_port_, + unsigned short local_port_ + ); + /*! + ensures + - #foreign_ip = foreign_ip_ + - #foreign_port = foreign_port_ + - #local_ip = local_ip_ + - #local_port = local_port_ + !*/ + + std::string path; + std::string request_type; + std::string content_type; + std::string protocol; + std::string body; + + key_value_map queries; + key_value_map cookies; + key_value_map_ci headers; + + std::string foreign_ip; + unsigned short foreign_port; + std::string local_ip; + unsigned short local_port; + }; + + struct outgoing_things + { + /*! + WHAT THIS OBJECT REPRESENTS + This object contains all the various bits of information that describe a + HTTP response from a web server. + + For a detailed discussion of the fields of this object, see the + server_http::on_request() method defined later in this file. + !*/ + + outgoing_things( + ); + /*! + ensures + - #http_return == 200 + - #http_return_status == "OK" + !*/ + + key_value_map cookies; + key_value_map_ci headers; + unsigned short http_return; + std::string http_return_status; + }; + +// ----------------------------------------------------------------------------------------- + + class http_parse_error : public error + { + /*! + WHAT THIS OBJECT REPRESENTS + This is an exception thrown by the parse_http_request() routine if + there is a problem. + !*/ + }; + +// ----------------------------------------------------------------------------------------- + + unsigned long parse_http_request ( + std::istream& in, + incoming_things& incoming, + unsigned long max_content_length + ); + /*! + ensures + - Attempts to read a HTTP GET, POST, or PUT request from the given input + stream. + - Reads all headers of the request and puts them into #incoming. In particular, + this function populates the following fields: + - #incoming.path + - #incoming.request_type + - #incoming.content_type + - #incoming.protocol + - #incoming.queries + - #incoming.cookies + - #incoming.headers + - This function also populates the #incoming.body field if and only if the + Content-Type field is equal to "application/x-www-form-urlencoded". + Otherwise, the content is not read from the stream. + throws + - http_parse_error + This exception is thrown if the Content-Length coming from the web + browser is greater than max_content_length or if any other problem + is detected with the request. + !*/ + + void read_body ( + std::istream& in, + incoming_things& incoming + ); + /*! + requires + - parse_http_request(in,incoming,max_content_length) has already been called + and therefore populated the fields of incoming. + ensures + - if (incoming.body has already been populated with the content of an HTTP + request) then + - this function does nothing + - else + - reads the body of the HTTP request into #incoming.body. + !*/ + + void write_http_response ( + std::ostream& out, + outgoing_things outgoing, + const std::string& result + ); + /*! + ensures + - Writes an HTTP response, defined by the data in outgoing, to the given output + stream. + - The result variable is written out as the content of the response. + !*/ + + void write_http_response ( + std::ostream& out, + const http_parse_error& e + ); + /*! + ensures + - Writes an HTTP error response based on the information in the exception + object e. + !*/ + + void write_http_response ( + std::ostream& out, + const std::exception& e + ); + /*! + ensures + - Writes an HTTP error response based on the information in the exception + object e. + !*/ + +// ----------------------------------------------------------------------------------------- +// ----------------------------------------------------------------------------------------- + + class server_http : public server_iostream + { + + /*! + WHAT THIS EXTENSION DOES FOR server_iostream + This extension turns the server object into a simple HTTP server. It only + handles HTTP GET, PUT and POST requests and each incoming request triggers + the on_request() callback. + + COOKIE STRINGS + 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 + just say something like incoming.cookies["cookie_name"] = "cookie contents"; + + HTTP HEADERS + 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 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. + However, setting this field manually is not necessary as it will default to + "text/html" if you don't explicitly set it to something. + !*/ + + public: + + server_http ( + ); + /*! + ensures + - #get_max_content_length() == 10*1024*1024 + !*/ + + unsigned long get_max_content_length ( + ) const; + /*! + ensures + - returns the max allowable content length, in bytes, of the post back to + the web server. If a client attempts to send more data than this then an + error number 413 is returned back to the client and the request is not + processed by the web server. + !*/ + + void set_max_content_length ( + unsigned long max_length + ); + /*! + ensures + - #get_max_content_length() == max_length + !*/ + + private: + + 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 + - on_request() is run in its own thread + - is_running() == true + - the number of current on_request() functions running < get_max_connection() + - 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.protocol == The protocol being used by the web browser (e.g. HTTP/1.1) + - incoming.body == a string that contains the data that was posted back to the + web server by the client (e.g. The string has the length specified by the + Content-Length header). + - incoming.body.size() < get_max_content_length() + - 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 + - in outgoing: + - outgoing.cookies.size() == 0 + - outgoing.headers.size() == 0 + - outgoing.http_return == 200 + - outgoing.http_return_status == "OK" + ensures + - This function returns the HTML page to be displayed as the response to this request. + - this function will not call clear() + - #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 and outgoing.http_return_status may be set to override the + default HTTP return code of 200 OK + throws + - 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. + !*/ + + + // ----------------------------------------------------------------------- + // Implementation Notes + // ----------------------------------------------------------------------- + + virtual 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 + ) + /*! + on_connect() is the function defined by server_iostream which is overloaded by + server_http. In particular, the server_http's implementation is shown below. + In it you can see how the server_http parses the incoming http request, gets a + response by calling on_request(), and sends it back using the helper routines + defined at the top of this file. + + Therefore, if you want to modify the behavior of the HTTP server, for example, + to do some more complex data streaming requiring direct access to the + iostreams, then you can do so by defining your own on_connect() routine. In + particular, the default implementation shown below is a good starting point. + !*/ + { + try + { + incoming_things incoming(foreign_ip, local_ip, foreign_port, local_port); + outgoing_things outgoing; + + parse_http_request(in, incoming, get_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) + { + write_http_response(out, e); + } + catch (std::exception& e) + { + write_http_response(out, e); + } + } + }; + +} + +#endif // DLIB_SERVER_HTTp_ABSTRACT_ + + + diff --git a/ml/dlib/dlib/server/server_iostream.cpp b/ml/dlib/dlib/server/server_iostream.cpp new file mode 100644 index 000000000..0fd49b67c --- /dev/null +++ b/ml/dlib/dlib/server/server_iostream.cpp @@ -0,0 +1,14 @@ +// Copyright (C) 2003 Davis E. King (davis@dlib.net) +// License: Boost Software License See LICENSE.txt for the full license. +#ifndef DLIB_SERVER_IOSTREAM_CPp_ +#define DLIB_SERVER_IOSTREAM_CPp_ + +#include "server_iostream.h" + +namespace dlib +{ + const logger server_iostream::_dLog("dlib.server_iostream"); +} + +#endif // DLIB_SERVER_IOSTREAM_CPp_ + diff --git a/ml/dlib/dlib/server/server_iostream.h b/ml/dlib/dlib/server/server_iostream.h new file mode 100644 index 000000000..eed349015 --- /dev/null +++ b/ml/dlib/dlib/server/server_iostream.h @@ -0,0 +1,155 @@ +// Copyright (C) 2006 Davis E. King (davis@dlib.net) +// License: Boost Software License See LICENSE.txt for the full license. +#ifndef DLIB_SERVER_IOSTREAm_1_ +#define DLIB_SERVER_IOSTREAm_1_ + +#include <iostream> +#include "server_iostream_abstract.h" +#include "../logger.h" +#include "../uintn.h" +#include "server_kernel.h" +#include "../sockstreambuf.h" +#include "../map.h" + + +namespace dlib +{ + + class server_iostream : public server + { + + /*! + INITIAL VALUE + - next_id == 0 + - con_map.size() == 0 + + CONVENTION + - next_id == the id of the next connection + - for all current connections + - con_map[id] == the connection object with the given id + - m == the mutex that protects the members of this object + !*/ + + typedef map<uint64,connection*,memory_manager<char>::kernel_2a>::kernel_1b id_map; + + public: + server_iostream( + ) : + next_id(0) + {} + + ~server_iostream( + ) + { + server::clear(); + } + + protected: + + void shutdown_connection ( + uint64 id + ) + { + auto_mutex M(m); + if (con_map.is_in_domain(id)) + { + con_map[id]->shutdown(); + } + } + + private: + + virtual 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 connection_id + )=0; + + void on_connect ( + connection& con + ) + { + bool my_fault = true; + uint64 this_con_id=0; + try + { + sockstreambuf buf(&con); + std::istream in(&buf); + std::ostream out(&buf); + in.tie(&out); + + // add this connection to the con_map + { + auto_mutex M(m); + this_con_id = next_id; + connection* this_con = &con; + con_map.add(this_con_id,this_con); + this_con_id = next_id; + ++next_id; + } + + my_fault = false; + on_connect( + in, + out, + con.get_foreign_ip(), + con.get_local_ip(), + con.get_foreign_port(), + con.get_local_port(), + this_con_id + ); + + // remove this connection from the con_map + { + auto_mutex M(m); + connection* this_con; + uint64 junk; + con_map.remove(this_con_id,junk,this_con); + } + + } + catch (std::bad_alloc&) + { + // make sure we remove this connection from the con_map + { + auto_mutex M(m); + if (con_map.is_in_domain(this_con_id)) + { + connection* this_con; + uint64 junk; + con_map.remove(this_con_id,junk,this_con); + } + } + + _dLog << LERROR << "We ran out of memory in server_iostream::on_connect()"; + // if this is an escaped exception from on_connect 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; + } + } + + uint64 next_id; + id_map con_map; + const static logger _dLog; + mutex m; + + + }; + + +} + +#ifdef NO_MAKEFILE +#include "server_iostream.cpp" +#endif + +#endif // DLIB_SERVER_IOSTREAm_1_ + + + diff --git a/ml/dlib/dlib/server/server_iostream_abstract.h b/ml/dlib/dlib/server/server_iostream_abstract.h new file mode 100644 index 000000000..86cd460bf --- /dev/null +++ b/ml/dlib/dlib/server/server_iostream_abstract.h @@ -0,0 +1,84 @@ +// Copyright (C) 2006 Davis E. King (davis@dlib.net) +// License: Boost Software License See LICENSE.txt for the full license. +#undef DLIB_SERVER_IOSTREAm_ABSTRACT_ +#ifdef DLIB_SERVER_IOSTREAm_ABSTRACT_ + + +#include "server_kernel_abstract.h" +#include <iostream> +#include <string> +#include "../uintn.h" + +namespace dlib +{ + + class server_iostream : public server + { + + /*! + WHAT THIS EXTENSION DOES FOR server + This extension redefines the on_connect() function so that + instead of giving you a connection object you get an istream + and ostream object. + + THREAD SAFETY + Note that in on_connect() the input stream in is tied to the output stream + out. This means that when you read from in it will modify out and thus + it is not safe to touch in and out concurrently from different threads + unless you untie them (which you do by saying in.tie(0);) + !*/ + + protected: + + void shutdown_connection ( + uint64 id + ); + /*! + ensures + - if (there is a connection currently being serviced with the given id) then + - the specified connection is shutdown. (i.e. connection::shutdown() is + called on it so the iostreams operating on it will return EOF) + !*/ + + private: + + virtual 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 connection_id + )=0; + /*! + requires + - on_connect() is called when there is a new TCP connection that needs + to be serviced. + - in == the input stream that reads data from the new connection + - out == the output stream that writes data to the new connection + - in.tie() == &out (i.e. when you read from in it automatically calls out.flush()) + - foreign_ip == the foreign ip address for this connection + - foreign_port == the foreign port number for this connection + - local_ip == the IP of the local interface this connection is using + - local_port == the local port number for this connection + - on_connect() is run in its own thread + - is_running() == true + - the number of current connections < get_max_connection() + - connection_id == an integer that uniquely identifies this connection. + It can be used by shutdown_connection() to terminate this connection. + ensures + - when the iostreams hit EOF on_connect() will terminate. + (because this is how clear() signals you the server is shutting down) + - this function will not call clear() + throws + - does not throw any exceptions + !*/ + + }; + +} + +#endif // DLIB_SERVER_IOSTREAm_ABSTRACT_ + + diff --git a/ml/dlib/dlib/server/server_kernel.cpp b/ml/dlib/dlib/server/server_kernel.cpp new file mode 100644 index 000000000..9e8130e78 --- /dev/null +++ b/ml/dlib/dlib/server/server_kernel.cpp @@ -0,0 +1,595 @@ +// Copyright (C) 2003 Davis E. King (davis@dlib.net) +// License: Boost Software License See LICENSE.txt for the full license. +#ifndef DLIB_SERVER_KERNEL_CPp_ +#define DLIB_SERVER_KERNEL_CPp_ + +#include "server_kernel.h" +#include "../string.h" + +namespace dlib +{ + +// ---------------------------------------------------------------------------------------- + + server:: + server ( + ) : + listening_port(0), + running(false), + shutting_down(false), + running_signaler(running_mutex), + thread_count(0), + thread_count_signaler(thread_count_mutex), + max_connections(1000), + thread_count_zero(thread_count_mutex), + graceful_close_timeout(500) + { + } + +// ---------------------------------------------------------------------------------------- + + server:: + ~server ( + ) + { + clear(); + } + +// ---------------------------------------------------------------------------------------- + + unsigned long server:: + get_graceful_close_timeout ( + ) const + { + auto_mutex lock(max_connections_mutex); + return graceful_close_timeout; + } + +// ---------------------------------------------------------------------------------------- + + void server:: + set_graceful_close_timeout ( + unsigned long timeout + ) + { + auto_mutex lock(max_connections_mutex); + graceful_close_timeout = timeout; + } + +// ---------------------------------------------------------------------------------------- + + + int server:: + get_max_connections ( + ) const + { + max_connections_mutex.lock(); + int temp = max_connections; + max_connections_mutex.unlock(); + return temp; + } + +// ---------------------------------------------------------------------------------------- + + void server:: + set_max_connections ( + int max + ) + { + // make sure requires clause is not broken + DLIB_CASSERT( + max >= 0 , + "\tvoid server::set_max_connections" + << "\n\tmax == " << max + << "\n\tthis: " << this + ); + + max_connections_mutex.lock(); + max_connections = max; + max_connections_mutex.unlock(); + } + +// ---------------------------------------------------------------------------------------- + + void server:: + clear ( + ) + { + // signal that we are shutting down + shutting_down_mutex.lock(); + shutting_down = true; + shutting_down_mutex.unlock(); + + + + max_connections_mutex.lock(); + listening_port_mutex.lock(); + listening_ip_mutex.lock(); + listening_ip = ""; + listening_port = 0; + max_connections = 1000; + graceful_close_timeout = 500; + listening_port_mutex.unlock(); + listening_ip_mutex.unlock(); + max_connections_mutex.unlock(); + + + // tell all the connections to shut down + cons_mutex.lock(); + connection* temp; + while (cons.size() > 0) + { + cons.remove_any(temp); + temp->shutdown(); + } + cons_mutex.unlock(); + + + // wait for all the connections to shut down + thread_count_mutex.lock(); + while (thread_count > 0) + { + thread_count_zero.wait(); + } + thread_count_mutex.unlock(); + + + + + // wait for the listener to close + running_mutex.lock(); + while (running == true) + { + running_signaler.wait(); + } + running_mutex.unlock(); + + + + // signal that the shutdown is complete + shutting_down_mutex.lock(); + shutting_down = false; + shutting_down_mutex.unlock(); + } + +// ---------------------------------------------------------------------------------------- + + void server:: + start_async_helper ( + ) + { + try + { + start_accepting_connections(); + } + catch (std::exception& e) + { + sdlog << LERROR << e.what(); + } + } + +// ---------------------------------------------------------------------------------------- + + void server:: + start_async ( + ) + { + auto_mutex lock(running_mutex); + if (running) + return; + + // Any exceptions likely to be thrown by the server are going to be + // thrown when trying to bind the port. So calling this here rather + // than in the thread we are about to make will cause start_async() + // to report errors back to the user in a very straight forward way. + open_listening_socket(); + + async_start_thread.reset(new thread_function(make_mfp(*this,&server::start_async_helper))); + } + +// ---------------------------------------------------------------------------------------- + + void server:: + open_listening_socket ( + ) + { + if (!sock) + { + int status = create_listener(sock,listening_port,listening_ip); + const int port_used = listening_port; + + // if there was an error then clear this object + if (status < 0) + { + max_connections_mutex.lock(); + listening_port_mutex.lock(); + listening_ip_mutex.lock(); + listening_ip = ""; + listening_port = 0; + max_connections = 1000; + graceful_close_timeout = 500; + listening_port_mutex.unlock(); + listening_ip_mutex.unlock(); + max_connections_mutex.unlock(); + } + + + + // throw an exception for the error + if (status == PORTINUSE) + { + throw dlib::socket_error( + EPORT_IN_USE, + "error occurred in server::start()\nport " + cast_to_string(port_used) + " already in use" + ); + } + else if (status == OTHER_ERROR) + { + throw dlib::socket_error( + "error occurred in server::start()\nunable to create listener" + ); + } + } + + running_mutex.lock(); + running = true; + running_mutex.unlock(); + } + +// ---------------------------------------------------------------------------------------- + + void server:: + start ( + ) + { + // make sure requires clause is not broken + DLIB_CASSERT( + this->is_running() == false, + "\tvoid server::start" + << "\n\tis_running() == " << this->is_running() + << "\n\tthis: " << this + ); + + start_accepting_connections(); + + } + +// ---------------------------------------------------------------------------------------- + + void server:: + start_accepting_connections ( + ) + { + open_listening_socket(); + + // determine the listening port + bool port_assigned = false; + listening_port_mutex.lock(); + if (listening_port == 0) + { + port_assigned = true; + listening_port = sock->get_listening_port(); + } + listening_port_mutex.unlock(); + if (port_assigned) + on_listening_port_assigned(); + + + + int status = 0; + + connection* client; + bool exit = false; + while ( true ) + { + + + // accept the next connection + status = sock->accept(client,1000); + + + // if there was an error then quit the loop + if (status == OTHER_ERROR) + { + break; + } + + shutting_down_mutex.lock(); + // if we are shutting down then signal that we should quit the loop + exit = shutting_down; + shutting_down_mutex.unlock(); + + + // if we should be shutting down + if (exit) + { + // if a connection was opened then close it + if (status == 0) + delete client; + break; + } + + + + // if the accept timed out + if (status == TIMEOUT) + { + continue; + } + + + + + + // add this new connection to cons + cons_mutex.lock(); + connection* client_temp = client; + try{cons.add(client_temp);} + catch(...) + { + sock.reset(); + delete client; + cons_mutex.unlock(); + + // signal that we are not running start() anymore + running_mutex.lock(); + running = false; + running_signaler.broadcast(); + running_mutex.unlock(); + + + clear(); + throw; + } + cons_mutex.unlock(); + + + // make a param structure + param* temp = 0; + try{ + temp = new param ( + *this, + *client, + get_graceful_close_timeout() + ); + } catch (...) + { + sock.reset(); + delete client; + running_mutex.lock(); + running = false; + running_signaler.broadcast(); + running_mutex.unlock(); + clear(); + throw; + } + + + // if create_new_thread failed + if (!create_new_thread(service_connection,temp)) + { + delete temp; + // close the listening socket + sock.reset(); + + // close the new connection and remove it from cons + cons_mutex.lock(); + connection* ctemp; + if (cons.is_member(client)) + { + cons.remove(client,ctemp); + } + delete client; + cons_mutex.unlock(); + + + // signal that the listener has closed + running_mutex.lock(); + running = false; + running_signaler.broadcast(); + running_mutex.unlock(); + + // make sure the object is cleared + clear(); + + // throw the exception + throw dlib::thread_error( + ECREATE_THREAD, + "error occurred in server::start()\nunable to start thread" + ); + } + // if we made the new thread then update thread_count + else + { + // increment the thread count + thread_count_mutex.lock(); + ++thread_count; + if (thread_count == 0) + thread_count_zero.broadcast(); + thread_count_mutex.unlock(); + } + + + + + // check if we have hit the maximum allowed number of connections + max_connections_mutex.lock(); + // if max_connections is zero or the loop is ending then skip this + if (max_connections != 0) + { + // wait for thread_count to be less than max_connections + thread_count_mutex.lock(); + while (thread_count >= max_connections) + { + max_connections_mutex.unlock(); + thread_count_signaler.wait(); + max_connections_mutex.lock(); + + // if we are shutting down the quit the loop + shutting_down_mutex.lock(); + exit = shutting_down; + shutting_down_mutex.unlock(); + if (exit) + break; + } + thread_count_mutex.unlock(); + } + max_connections_mutex.unlock(); + + if (exit) + { + break; + } + } //while ( true ) + + + // close the socket + sock.reset(); + + // signal that the listener has closed + running_mutex.lock(); + running = false; + running_signaler.broadcast(); + running_mutex.unlock(); + + // if there was an error with accept then throw an exception + if (status == OTHER_ERROR) + { + // make sure the object is cleared + clear(); + + // throw the exception + throw dlib::socket_error( + "error occurred in server::start()\nlistening socket returned error" + ); + } + } + +// ---------------------------------------------------------------------------------------- + + bool server:: + is_running ( + ) const + { + running_mutex.lock(); + bool temp = running; + running_mutex.unlock(); + return temp; + } + +// ---------------------------------------------------------------------------------------- + + const std::string server:: + get_listening_ip ( + ) const + { + listening_ip_mutex.lock(); + std::string ip(listening_ip); + listening_ip_mutex.unlock(); + return ip; + } + +// ---------------------------------------------------------------------------------------- + + int server:: + get_listening_port ( + ) const + { + listening_port_mutex.lock(); + int port = listening_port; + listening_port_mutex.unlock(); + return port; + } + +// ---------------------------------------------------------------------------------------- + + void server:: + set_listening_port ( + int port + ) + { + // make sure requires clause is not broken + DLIB_CASSERT( + ( port >= 0 && + this->is_running() == false ), + "\tvoid server::set_listening_port" + << "\n\tport == " << port + << "\n\tis_running() == " << this->is_running() + << "\n\tthis: " << this + ); + + listening_port_mutex.lock(); + listening_port = port; + listening_port_mutex.unlock(); + } + +// ---------------------------------------------------------------------------------------- + + void server:: + set_listening_ip ( + const std::string& ip + ) + { + // make sure requires clause is not broken + DLIB_CASSERT( + ( ( is_ip_address(ip) || ip == "" ) && + this->is_running() == false ), + "\tvoid server::set_listening_ip" + << "\n\tip == " << ip + << "\n\tis_running() == " << this->is_running() + << "\n\tthis: " << this + ); + + listening_ip_mutex.lock(); + listening_ip = ip; + listening_ip_mutex.unlock(); + } + +// ---------------------------------------------------------------------------------------- +// ---------------------------------------------------------------------------------------- + // static member function definitions +// ---------------------------------------------------------------------------------------- +// ---------------------------------------------------------------------------------------- + + const logger server::sdlog("dlib.server"); + + void server:: + service_connection( + void* item + ) + { + param& p = *static_cast<param*>(item); + + + p.the_server.on_connect(p.new_connection); + + + // remove this connection from cons and close it + p.the_server.cons_mutex.lock(); + connection* temp; + if (p.the_server.cons.is_member(&p.new_connection)) + p.the_server.cons.remove(&p.new_connection,temp); + p.the_server.cons_mutex.unlock(); + + try{ close_gracefully(&p.new_connection, p.graceful_close_timeout); } + catch (...) { sdlog << LERROR << "close_gracefully() threw"; } + + // decrement the thread count and signal if it is now zero + p.the_server.thread_count_mutex.lock(); + --p.the_server.thread_count; + p.the_server.thread_count_signaler.broadcast(); + if (p.the_server.thread_count == 0) + p.the_server.thread_count_zero.broadcast(); + p.the_server.thread_count_mutex.unlock(); + + delete &p; + + + } + +// ---------------------------------------------------------------------------------------- + +} + +#endif // DLIB_SERVER_KERNEL_CPp_ + diff --git a/ml/dlib/dlib/server/server_kernel.h b/ml/dlib/dlib/server/server_kernel.h new file mode 100644 index 000000000..4232ff343 --- /dev/null +++ b/ml/dlib/dlib/server/server_kernel.h @@ -0,0 +1,234 @@ +// Copyright (C) 2003 Davis E. King (davis@dlib.net) +// License: Boost Software License See LICENSE.txt for the full license. +#ifndef DLIB_SERVER_KERNEL_1_ +#define DLIB_SERVER_KERNEL_1_ + +#include "server_kernel_abstract.h" + +#include <memory> +#include <string> + +#include "../threads.h" +#include "../sockets.h" +#include "../algs.h" +#include "../set.h" +#include "../logger.h" + + +namespace dlib +{ + + // These forward declarations are here so we can use them in the typedefs in the server + // class. The reason for this is for backwards compatibility with previous versions of + // dlib. + class server_http; + class server_iostream; + + class server + { + + + /*! + INITIAL VALUE + listening_port == 0 + listening_ip == "" + running == false + shutting_down == false + cons.size() == 0 + listening_port_mutex == a mutex + listening_ip_mutex == a mutex + running_mutex == a mutex + running_signaler == a signaler associated with running_mutex + shutting_down_mutex == a mutex + cons_mutex == a mutex + thread_count == 0 + thread_count_mutex == a mutex + thread_count_signaler == a signaler associated with thread_count_mutex + thread_count_zero == a signaler associated with thread_count_mutex + max_connections == 1000 + max_connections_mutex == a mutex for max_connections and graceful_close_timeout + graceful_close_timeout == 500 + + CONVENTION + listening_port == get_listening_port() + listening_ip == get_listening_ip() + running == is_running() + shutting_down == true while clear() is running. this + bool is used to tell the thread blocked on + accept that it should terminate + cons == a set containing all open connections + listening_port_mutex == a mutex for listening_port + listening_ip_mutex == a mutex for listening_ip + running_mutex == a mutex for running + running_signaler == a signaler for running and + is associated with running_mutex. it is + used to signal when running is false + shutting_down_mutex == a mutex for shutting_down + cons_mutex == a mutex for cons + thread_count == the number of threads currently running + thread_count_mutex == a mutex for thread_count + thread_count_signaler == a signaler for thread_count and + is associated with thread_count_mutex. it + is used to signal when thread_count is + decremented + thread_count_zero == a signaler for thread_count and + is associated with thread_count_mutex. it + is used to signal when thread_count becomes + zero + max_connections == get_max_connections() + max_connections_mutex == a mutex for max_connections + !*/ + + + typedef set<connection*>::kernel_1a set_of_connections; + + // this structure is used to pass parameters to new threads + struct param + { + param ( + server& server_, + connection& new_connection_, + unsigned long graceful_close_timeout_ + ) : + the_server(server_), + new_connection(new_connection_), + graceful_close_timeout(graceful_close_timeout_) + {} + + server& the_server; + connection& new_connection; + unsigned long graceful_close_timeout; + }; + + + + public: + + // These typedefs are here for backward compatibility with previous versions of dlib + typedef server kernel_1a; + typedef server kernel_1a_c; + typedef server_iostream iostream_1a; + typedef server_iostream iostream_1a_c; + typedef server_http http_1a; + typedef server_http http_1a_c; + + server( + ); + + virtual ~server( + ); + + void clear( + ); + + void start ( + ); + + bool is_running ( + ) const; + + const std::string get_listening_ip ( + ) const; + + int get_listening_port ( + ) const; + + void set_listening_port ( + int port + ); + + void set_listening_ip ( + const std::string& ip + ); + + void set_max_connections ( + int max + ); + + int get_max_connections ( + ) const; + + void start_async ( + ); + + void set_graceful_close_timeout ( + unsigned long timeout + ); + + unsigned long get_graceful_close_timeout ( + ) const; + + private: + + void start_async_helper ( + ); + + void start_accepting_connections ( + ); + + void open_listening_socket ( + ); + + virtual void on_connect ( + connection& new_connection + )=0; + + virtual void on_listening_port_assigned ( + ) {} + + const static logger sdlog; + + static void service_connection( + void* item + ); + /*! + requires + item is a pointer to a param struct + ensures + services the new connection + will take care of closing the connection and + adding the connection to cons when it first starts and + remove the connection from cons and signal that it has + done so when it ends + !*/ + + // data members + int listening_port; + std::string listening_ip; + bool running; + bool shutting_down; + set_of_connections cons; + mutex listening_port_mutex; + mutex listening_ip_mutex; + rmutex running_mutex; + rsignaler running_signaler; + mutex shutting_down_mutex; + mutex cons_mutex; + int thread_count; + mutex thread_count_mutex; + signaler thread_count_signaler; + int max_connections; + mutex max_connections_mutex; + signaler thread_count_zero; + std::unique_ptr<thread_function> async_start_thread; + std::unique_ptr<listener> sock; + unsigned long graceful_close_timeout; + + + // restricted functions + server(server&); + server& operator= ( + server& + ); + }; + +// ---------------------------------------------------------------------------------------- + +} + +#ifdef NO_MAKEFILE +#include "server_kernel.cpp" +#endif + +#endif // DLIB_SERVER_KERNEL_1_ + diff --git a/ml/dlib/dlib/server/server_kernel_abstract.h b/ml/dlib/dlib/server/server_kernel_abstract.h new file mode 100644 index 000000000..f7860d26c --- /dev/null +++ b/ml/dlib/dlib/server/server_kernel_abstract.h @@ -0,0 +1,310 @@ +// Copyright (C) 2003 Davis E. King (davis@dlib.net) +// License: Boost Software License See LICENSE.txt for the full license. +#undef DLIB_SERVER_KERNEL_ABSTRACT_ +#ifdef DLIB_SERVER_KERNEL_ABSTRACT_ + +#include "../threads/threads_kernel_abstract.h" +#include "../sockets/sockets_kernel_abstract.h" +#include <string> + + +namespace dlib +{ + class server + { + + /*! + INITIAL VALUE + get_listening_ip() == "" + get_listening_port() == 0 + is_running() == false + get_max_connections() == 1000 + get_graceful_close_timeout() == 500 + + + CALLBACK FUNCTIONS + on_connect(): + To use this object inherit from it and define the pure virtual function + on_connect. Inside this function is where you will handle each new + connection. Note that the connection object passed to on_connect() should + NOT be closed, just let the function end and it will be gracefully closed + for you. Also note that each call to on_connect() is run in its own + thread. Also note that on_connect() should NOT throw any exceptions, + all exceptions must be dealt with inside on_connect() and cannot be + allowed to leave. + + on_listening_port_assigned(): + This function is called to let the client know that the operating + system has assigned a port number to the listening port. This + happens if a port number of zero was given. Note that this + function does not need to be defined. If you don't care then + don't define it and it will do nothing. Note also that this function + is NOT called in its own thread. Thus, making it block might hang the + server. + + WHAT THIS OBJECT REPRESENTS + This object represents a server that listens on a port and spawns new + threads to handle each new connection. + + Note that the clear() function does not return until all calls to + on_connect() have finished and the start() function has been shutdown. + Also note that when clear() is called all open connection objects + will be shutdown(). + + A note about get_max_connections(): when the maximum number of connections + has been reached accept() will simply not be called until the number of + open connections drops below get_max_connections(). This means connections + will just wait to be serviced, rather than being outright refused. + + THREAD SAFETY + All member functions are thread-safe. + !*/ + + public: + + server( + ); + /*! + ensures + - #*this is properly initialized + throws + - std::bad_alloc + - dlib::thread_error + !*/ + + virtual ~server( + ); + /*! + requires + - is not called from any of server's callbacks + ensures + - all resources associated with *this have been released + !*/ + + void clear( + ); + /*! + requires + - is not called from any of server's callbacks + ensures + - #*this has its initial value + - all open connection objects passed to on_connect() are shutdown() + - blocks until all calls to on_connect() have finished + - blocks until the start() function has released all its resources + throws + - std::bad_alloc + if this exception is thrown then the server object is unusable + until clear() is called and succeeds + !*/ + + void start ( + ); + /*! + requires + - is_running() == false + ensures + - starts listening on the port and ip specified by get_listening_ip() + and #get_listening_port() for new connections. + - if (get_listening_port() == 0) then + - a port to listen on will be automatically selected + - #get_listening_port() == the selected port being used + - if (get_listening_ip() == "" ) then + - all local IPs will be listened on + - blocks until clear() is called or an error occurs + throws + - dlib::socket_error + start() will throw this exception if there is some problem binding + ports and/or starting the server or if there is a problem + accepting new connections while it's running. + If this happens then + - All open connection objects passed to on_connect() are shutdown() + and the exception will not be thrown until all on_connect() calls + have terminated. + - The server will be cleared and returned to its initial value. + - dlib::thread_error + start() will throw this exception if there is a problem + creating new threads. Or it may throw this exception if there + is a problem creating threading objects. + If this happens then + - All open connection objects passed to on_connect() are shutdown() + and the exception will not be thrown until all on_connect() calls + have terminated. + - The server will be cleared and returned to its initial value. + - std::bad_alloc + start() may throw this exception and if it does then the object + will be unusable until clear() is called and succeeds + !*/ + + void start_async ( + ); + /*! + ensures + - starts listening on the port and ip specified by get_listening_ip() + and #get_listening_port() for new connections. + - if (get_listening_port() == 0) then + - a port to listen on will be automatically selected + - #get_listening_port() == the selected port being used + - if (get_listening_ip() == "" ) then + - all local IPs will be listened on + - does NOT block. That is, this function will return right away and + the server will run on a background thread until clear() or this + object's destructor is called (or until some kind of fatal error + occurs). + - if an error occurs in the background thread while the server is + running then it will shut itself down, set is_running() to false, and + log the error to a dlib::logger object. + - calling start_async() on a running server has no effect. + throws + - dlib::socket_error + start_async() will throw this exception if there is some problem binding + ports and/or starting the server. + If this happens then + - The server will be cleared and returned to its initial value. + !*/ + + bool is_running ( + ) const; + /*! + ensures + - returns true if start() is running + - returns false if start() is not running or has released all + its resources and is about to terminate + throws + - std::bad_alloc + !*/ + + int get_max_connections ( + ) const; + /*! + ensures + - returns the maximum number of connections the server will accept + at a time. + - returns 0 if the server will accept any number of connections + throws + - std::bad_alloc + !*/ + + + const std::string get_listening_ip ( + ) const; + /*! + ensures + - returns the local ip to listen for new connections on + - returns "" if ALL local ips are to be listened on + throws + - std::bad_alloc + !*/ + + int get_listening_port ( + ) const; + /*! + ensures + - returns the local port number to listen for new connections on + - returns 0 if the local port number has not yet been set + throws + - std::bad_alloc + !*/ + + void set_listening_port ( + int port + ); + /*! + requires + - port >= 0 + - is_running() == false + ensures + - #get_listening_port() == port + throws + - std::bad_alloc + !*/ + + void set_listening_ip ( + const std::string& ip + ); + /*! + requires + - is_ip_address(ip) == true or ip == "" + - is_running() == false + ensures + - #get_listening_ip() == ip + throws + - std::bad_alloc + !*/ + + void set_max_connections ( + int max + ); + /*! + requires + - max >= 0 + ensures + - #get_max_connections() == max + throws + - std::bad_alloc + !*/ + + void set_graceful_close_timeout ( + unsigned long timeout + ); + /*! + ensures + - #get_graceful_close_timeout() == timeout + !*/ + + unsigned long get_graceful_close_timeout ( + ) const; + /*! + ensures + - When on_connect() terminates, it will close the connection using + close_gracefully(). This is done so that any data still in the + operating system's output buffers gets a chance to be properly + transmitted to the remote host. Part of this involves waiting for + the remote host to close their end of the connection. Therefore, + get_graceful_close_timeout() returns the timeout, in milliseconds, + that we wait for the remote host to close their end of the + connection. This is the timeout value given to close_gracefully(). + !*/ + + private: + + virtual void on_connect ( + connection& new_connection + )=0; + /*! + requires + - on_connect() is run in its own thread + - is_running() == true + - the number of current connections < get_max_connection() + - new_connection == the new connection to the server which is + to be serviced by this call to on_connect() + ensures + - when new_connection is shutdown() on_connect() will terminate + - this function will not call clear() + throws + - does not throw any exceptions + !*/ + + // do nothing by default + virtual void on_listening_port_assigned ( + ) {} + /*! + requires + - is called if a listening port of zero was specified and + an actual port number has just been assigned to the server + ensures + - this function will not block + - this function will not call clear() + throws + - does not throw any exceptions + !*/ + + + // restricted functions + server(server&); // copy constructor + server& operator=(server&); // assignment operator + }; + +} + +#endif // DLIB_SERVER_KERNEL_ABSTRACT_ + |