/* * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Library General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * HttpServer.cpp * Ola HTTP class * Copyright (C) 2005-2008 Simon Newton */ #include #include #include #include #include #include #include #include "olad/HttpServer.h" #include "ola/Logging.h" namespace ola { using std::ifstream; using std::pair; using std::set; using std::string; using ola::network::UnmanagedSocket; const char HttpServer::CONTENT_TYPE_PLAIN[] = "text/plain"; const char HttpServer::CONTENT_TYPE_HTML[] = "text/html"; const char HttpServer::CONTENT_TYPE_GIF[] = "image/gif"; const char HttpServer::CONTENT_TYPE_PNG[] = "image/png"; const char HttpServer::CONTENT_TYPE_CSS[] = "text/css"; const char HttpServer::CONTENT_TYPE_JS[] = "text/javascript"; /** * Called by MHD_get_connection_values to add headers to a request obect. * @param cls a pointer to an HttpRequest object. * @param key the header name * @param value the header value */ static int AddHeaders(void *cls, enum MHD_ValueKind kind, const char *key, const char *value) { HttpRequest *request = reinterpret_cast(cls); string key_string = key; string value_string = value; request->AddHeader(key, value); return MHD_YES; (void) kind; } /** * Called by MHD_create_post_processor to iterate over the post form data * @param request_cls a pointer to a HttpRequest object * @param key the header name * @param value the header value */ int IteratePost(void *request_cls, enum MHD_ValueKind kind, const char *key, const char *filename, const char *content_type, const char *transfer_encoding, const char *data, uint64_t off, size_t size) { // libmicrohttpd has a bug where the zie isn't set correctly. HttpRequest *request = reinterpret_cast(request_cls); string value(data); request->AddPostParameter(key, value); return MHD_YES; (void) content_type; (void) filename; (void) kind; (void) transfer_encoding; (void) off; (void) size; } /** * Called whenever a new request is made. This sets up HttpRequest & * HttpResponse objects and then calls DispatchRequest. */ static int HandleRequest(void *http_server_ptr, struct MHD_Connection *connection, const char *url, const char *method, const char *version, const char *upload_data, size_t *upload_data_size, void **ptr) { HttpServer *http_server = reinterpret_cast(http_server_ptr); HttpRequest *request; // on the first call ptr is null if (*ptr == NULL) { request = new HttpRequest(url, method, version, connection); if (!request) return MHD_NO; if (!request->Init()) { delete request; return MHD_NO; } *ptr = reinterpret_cast(request); return MHD_YES; } request = reinterpret_cast(*ptr); if (request->InFlight()) // don't dispatch more than once return MHD_YES; if (request->Method() == MHD_HTTP_METHOD_GET) { HttpResponse *response = new HttpResponse(connection); request->SetInFlight(); return http_server->DispatchRequest(request, response); } else if (request->Method() == MHD_HTTP_METHOD_POST) { if (*upload_data_size != 0) { request->ProcessPostData(upload_data, upload_data_size); *upload_data_size = 0; return MHD_YES; } request->SetInFlight(); HttpResponse *response = new HttpResponse(connection); return http_server->DispatchRequest(request, response); } return MHD_NO; } /** * Called when a request completes. This deletes the associated HttpRequest * object. */ void RequestCompleted(void *cls, struct MHD_Connection *connection, void **request_cls, enum MHD_RequestTerminationCode toe) { HttpRequest *request = reinterpret_cast(*request_cls); if (!request) return; delete request; *request_cls = NULL; (void) cls; (void) connection; (void) toe; } /* * HttpRequest object * Setup the header callback and the post processor if needed. */ HttpRequest::HttpRequest(const string &url, const string &method, const string &version, struct MHD_Connection *connection): m_url(url), m_method(method), m_version(version), m_connection(connection), m_processor(NULL), m_in_flight(false) { } /* * Initialize this request * @return true if succesful, false otherwise. */ bool HttpRequest::Init() { MHD_get_connection_values(m_connection, MHD_HEADER_KIND, AddHeaders, this); if (m_method == MHD_HTTP_METHOD_POST) { m_processor = MHD_create_post_processor(m_connection, K_POST_BUFFER_SIZE, IteratePost, reinterpret_cast(this)); return m_processor; } return true; } /* * Cleanup this request object */ HttpRequest::~HttpRequest() { if (m_processor) MHD_destroy_post_processor(m_processor); } /* * Add a header to the request object. * @param key the header name * @param value the value of the header */ void HttpRequest::AddHeader(const string &key, const string &value) { std::pair pair(key, value); m_headers.insert(pair); } /* * Add a post parameter. This can be called multiple times and the values will * be appended. * @param key the parameter name * @param value the value */ void HttpRequest::AddPostParameter(const string &key, const string &value) { map::iterator iter = m_post_params.find(key); if (iter == m_post_params.end()) { std::pair pair(key, value); m_post_params.insert(pair); } else { iter->second.append(value); } } /* * Process post data */ void HttpRequest::ProcessPostData(const char *data, size_t *data_size) { MHD_post_process(m_processor, data, *data_size); } /* * Return the value of the header sent with this request * @param key the name of the header * @returns the value of the header or empty string if it doesn't exist. */ const string HttpRequest::GetHeader(const string &key) const { map::const_iterator iter = m_headers.find(key); if (iter == m_headers.end()) return ""; else return iter->second; } /* * Return the value of a url parameter * @param key the name of the parameter * @return the value of the parameter */ const string HttpRequest::GetParameter(const string &key) const { const char *value = MHD_lookup_connection_value(m_connection, MHD_GET_ARGUMENT_KIND, key.data()); if (value) return string(value); else return string(); } /* * Lookup a post parameter in this request * @param key the name of the parameter * @return the value of the parameter or the empty string if it doesn't exist */ const string HttpRequest::GetPostParameter(const string &key) const { map::const_iterator iter = m_post_params.find(key); if (iter == m_post_params.end()) return ""; else return iter->second; } /* * Set the content-type header * @param type, the content type * @return true if the header was set correctly, false otherwise */ void HttpResponse::SetContentType(const string &type) { SetHeader(MHD_HTTP_HEADER_CONTENT_TYPE, type); } /* * Set a header in the response * @param key the header name * @param value the header value * @return true if the header was set correctly, false otherwise */ void HttpResponse::SetHeader(const string &key, const string &value) { std::pair pair(key, value); m_headers.insert(pair); } /* * Send the HTTP response * @returns true on success, false on error */ int HttpResponse::Send() { map::const_iterator iter; struct MHD_Response *response = MHD_create_response_from_data( m_data.length(), reinterpret_cast(const_cast(m_data.data())), MHD_NO, MHD_YES); for (iter = m_headers.begin(); iter != m_headers.end(); ++iter) MHD_add_response_header(response, iter->first.c_str(), iter->second.c_str()); int ret = MHD_queue_response(m_connection, m_status_code, response); MHD_destroy_response(response); return ret; } /* * Setup the HTTP server. * @param port the port to listen on * @param data_dir the directory to serve static content from */ HttpServer::HttpServer(unsigned int port, const string &data_dir) : OlaThread(), m_httpd(NULL), m_select_server(NULL), m_default_handler(NULL), m_port(port), m_data_dir(data_dir) { if (m_data_dir.empty()) m_data_dir = HTTP_DATA_DIR; } /* * Destroy this object */ HttpServer::~HttpServer() { Stop(); if (m_select_server) delete m_select_server; if (m_httpd) MHD_stop_daemon(m_httpd); map::const_iterator iter; for (iter = m_handlers.begin(); iter != m_handlers.end(); ++iter) delete iter->second; if (m_default_handler) { delete m_default_handler; m_default_handler = NULL; } m_handlers.clear(); } /* * Setup the HTTP server * @return true on success, false on failure */ bool HttpServer::Init() { if (m_httpd || m_select_server) { OLA_INFO << "Non null pointers found, Init() was probably called twice"; return false; } m_httpd = MHD_start_daemon(MHD_NO_FLAG, m_port, NULL, NULL, &HandleRequest, this, MHD_OPTION_NOTIFY_COMPLETED, RequestCompleted, NULL, MHD_OPTION_END); if (m_httpd) { m_select_server = new ola::network::SelectServer(); m_select_server->RunInLoop(NewClosure(this, &HttpServer::UpdateSockets)); } return m_httpd ? true : false; } /** * The entry point into the new thread */ void *HttpServer::Run() { if (!(m_httpd || m_select_server)) { OLA_WARN << "HttpServer::Run called but the server wasn't setup."; return NULL; } m_select_server->Run(); // clean up any remaining sockets set::iterator iter = m_sockets.begin(); for (; iter != m_sockets.end(); ++iter) { m_select_server->RemoveSocket(*iter); m_select_server->UnRegisterWriteSocket(*iter); delete *iter; } return NULL; } /* * Stop the HTTP server */ void HttpServer::Stop() { if (IsRunning()) { OLA_INFO << "Notifying HTTP server thread to stop"; m_select_server->Terminate(); OLA_INFO << "Waiting for HTTP server thread to exit"; Join(); OLA_INFO << "HTTP server thread exited"; } } /** * This is run every loop iteration to update the list of sockets in the * SelectServer from MHD. */ void HttpServer::UpdateSockets() { // We always call MHD_run so we send any queued responses. This isn't // inefficient because the only thing that can wake up the select server is // activity on a http socket or the client socket. The latter almost always // results in a change to HTTP state. if (MHD_run(m_httpd) == MHD_NO) { OLA_WARN << "MHD run failed"; } fd_set r_set, w_set, e_set; int max_fd = 0; FD_ZERO(&r_set); FD_ZERO(&w_set); if (MHD_YES != MHD_get_fdset(m_httpd, &r_set, &w_set, &e_set, &max_fd)) { OLA_WARN << "Failed to get a list of the file descriptors for MHD"; return; } set::iterator iter = m_sockets.begin(); // This isn't the best plan, talk to the MHD devs about exposing the list of // FD in a more suitable way int i = 0; while (iter != m_sockets.end() && i <= max_fd) { if ((*iter)->ReadDescriptor() < i) { // this socket is no longer required so remove it OLA_DEBUG << "Removing unsed socket " << (*iter)->ReadDescriptor(); m_select_server->RemoveSocket(*iter); m_select_server->UnRegisterWriteSocket(*iter); delete *iter; m_sockets.erase(iter++); } else if ((*iter)->ReadDescriptor() == i) { // this socket may need to be updated if (FD_ISSET(i, &r_set)) m_select_server->AddSocket(*iter); else m_select_server->RemoveSocket(*iter); if (FD_ISSET(i, &w_set)) m_select_server->RegisterWriteSocket(*iter); else m_select_server->UnRegisterWriteSocket(*iter); iter++; i++; } else { // this is a new socket if (FD_ISSET(i, &r_set) || FD_ISSET(i, &w_set)) { OLA_DEBUG << "Adding new socket " << i; UnmanagedSocket *socket = NewSocket(&r_set, &w_set, i); m_sockets.insert(socket); } i++; } } while (iter != m_sockets.end()) { OLA_DEBUG << "Removing " << (*iter)->ReadDescriptor() << " as it's not longer needed"; m_select_server->UnRegisterWriteSocket(*iter); m_select_server->RemoveSocket(*iter); delete *iter; m_sockets.erase(iter++); } for (;i <= max_fd; i++) { // add the remaining sockets to the SS if (FD_ISSET(i, &r_set) || FD_ISSET(i, &w_set)) { OLA_DEBUG << "Adding " << i << " as a new socket"; UnmanagedSocket *socket = NewSocket(&r_set, &w_set, i); m_sockets.insert(socket); } } } /** * Called when there is HTTP IO activity to deal with. This is a noop as * MHD_run is called in UpdateSockets above. */ void HttpServer::HandleHTTPIO() {} /* * Call the appropriate handler. */ int HttpServer::DispatchRequest(const HttpRequest *request, HttpResponse *response) { map::iterator iter = m_handlers.find(request->Url()); if (iter != m_handlers.end()) return iter->second->Run(request, response); map::iterator file_iter = m_static_content.find(request->Url()); if (file_iter != m_static_content.end()) return ServeStaticContent(&(file_iter->second), response); if (m_default_handler) return m_default_handler->Run(request, response); return ServeNotFound(response); } /* * Register a handler * @param path the url to respond on * @param handler the Closure to call for this request. These will be freed * once the HttpServer is destroyed. */ bool HttpServer::RegisterHandler(const string &path, BaseHttpClosure *handler) { map::const_iterator iter = m_handlers.find(path); if (iter != m_handlers.end()) return false; pair pair(path, handler); m_handlers.insert(pair); return true; } /* * Register a static file * @param path the path to serve on * @param file the path to the file to serve */ bool HttpServer::RegisterFile(const string &path, const string &file, const string &content_type) { map::const_iterator file_iter = ( m_static_content.find(path)); if (file_iter != m_static_content.end()) return false; static_file_info file_info; file_info.file_path = file; file_info.content_type = content_type; pair pair(path, file_info); m_static_content.insert(pair); return true; } /* * Set the default handler. * @param handler the default handler to call. This will be freed when the * HttpServer is destroyed. */ void HttpServer::RegisterDefaultHandler(BaseHttpClosure *handler) { m_default_handler = handler; } /* * Return a list of all handlers registered */ vector HttpServer::Handlers() const { vector handlers; map::const_iterator iter; for (iter = m_handlers.begin(); iter != m_handlers.end(); ++iter) handlers.push_back(iter->first); map::const_iterator file_iter; for (file_iter = m_static_content.begin(); file_iter != m_static_content.end(); ++file_iter) handlers.push_back(file_iter->first); return handlers; } /* * Serve an error. * @param response the reponse to use. * @param details the error description */ int HttpServer::ServeError(HttpResponse *response, const string &details) { response->SetStatus(MHD_HTTP_INTERNAL_SERVER_ERROR); response->SetContentType(CONTENT_TYPE_HTML); response->Append("500 Server Error"); if (!details.empty()) { response->Append("

"); response->Append(details); response->Append("

"); } int r = response->Send(); delete response; return r; } /* * Serve a 404 * @param response the response to use */ int HttpServer::ServeNotFound(HttpResponse *response) { response->SetStatus(MHD_HTTP_NOT_FOUND); response->SetContentType(CONTENT_TYPE_HTML); response->SetStatus(404); response->Append("404 Not Found"); int r = response->Send(); delete response; return r; } /* * Serve static content. * @param file_info details on the file to server * @param response the response to use */ int HttpServer::ServeStaticContent(static_file_info *file_info, HttpResponse *response) { char *data; unsigned int length; string file_path = m_data_dir; file_path.append("/"); file_path.append(file_info->file_path); ifstream i_stream(file_path.data()); if (!i_stream.is_open()) { OLA_WARN << "Missing file: " << file_path; return ServeNotFound(response); } i_stream.seekg(0, std::ios::end); length = i_stream.tellg(); i_stream.seekg(0, std::ios::beg); data = reinterpret_cast(malloc(length * sizeof(char))); i_stream.read(data, length); i_stream.close(); struct MHD_Response *mhd_response = MHD_create_response_from_data( length, reinterpret_cast(const_cast(data)), MHD_YES, MHD_NO); if (!file_info->content_type.empty()) MHD_add_response_header(mhd_response, MHD_HTTP_HEADER_CONTENT_TYPE, file_info->content_type.data()); int ret = MHD_queue_response(response->Connection(), MHD_HTTP_OK, mhd_response); MHD_destroy_response(mhd_response); delete response; return ret; } UnmanagedSocket *HttpServer::NewSocket(fd_set *r_set, fd_set *w_set, int fd) { UnmanagedSocket *socket = new UnmanagedSocket(fd); socket->SetOnData(NewClosure(this, &HttpServer::HandleHTTPIO)); socket->SetOnWritable(NewClosure(this, &HttpServer::HandleHTTPIO)); if (FD_ISSET(fd, r_set)) m_select_server->AddSocket(socket); if (FD_ISSET(fd, w_set)) m_select_server->RegisterWriteSocket(socket); return socket; } } // ola