request_dispatcher.cpp

00001
00002 /***************************************************************************
00003  *  request_dispatcher.cpp - Web request dispatcher
00004  *
00005  *  Created: Mon Oct 13 22:48:04 2008
00006  *  Copyright  2006-2008  Tim Niemueller [www.niemueller.de]
00007  *
00008  ****************************************************************************/
00009
00010 /*  This program is free software; you can redistribute it and/or modify
00011  *  it under the terms of the GNU General Public License as published by
00012  *  the Free Software Foundation; either version 2 of the License, or
00013  *  (at your option) any later version.
00014  *
00015  *  This program is distributed in the hope that it will be useful,
00016  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00017  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00018  *  GNU Library General Public License for more details.
00019  *
00020  *  Read the full text in the LICENSE.GPL file in the doc directory.
00021  */
00022
00023 #include "request_dispatcher.h"
00024 #include "request_processor.h"
00025 #include "page_reply.h"
00026 #include "error_reply.h"
00027
00028 #include <utils/logging/cache.h>
00029 #include <utils/misc/string_urlescape.h>
00030
00031 #include <microhttpd.h>
00032 #include <cstring>
00033 #include <cstdlib>
00034 
00035 /** @class WebRequestDispatcher "request_dispatcher.h"
00036  * Web request dispatcher.
00037  * Takes web request received via a webserver run by libmicrohttpd and dispatches
00038  * pages to registered WebRequestProcessor instances or gives a 404 error if no
00039  * processor was registered for the given base url.
00040  * @author Tim Niemueller
00041  */
00042 
00043 /** Process request callback for libmicrohttpd.
00044  * @param callback_data instance of WebRequestDispatcher to call
00045  * @param connection libmicrohttpd connection instance
00046  * @param url URL, may contain escape sequences
00047  * @param method HTTP method
00048  * @param version HTTP version
00049  * @param upload_data uploaded data
00050  * @param upload_data_size size of upload_data parameter
00051  * @param session_data session data pointer
00052  * @return appropriate return code for libmicrohttpd
00053  */
00054 int
00055 WebRequestDispatcher::process_request_cb(void *callback_data,
00056                                          struct MHD_Connection * connection,
00057                                          const char *url,
00058                                          const char *method,
00059                                          const char *version,
00060                                          const char *upload_data,
00061                                          size_t *upload_data_size,
00062                                          void **session_data)
00063 {
00064   WebRequestDispatcher *rd = static_cast<WebRequestDispatcher *>(callback_data);
00065   return rd->process_request(connection, url, method, version,
00066                              upload_data, upload_data_size, session_data);
00067 }
00068
00069 
00070 /** Callback based chunk-wise data.
00071  * Supplies data chunk based.
00072  * @param reply instance of DynamicWebReply
00073  * @param pos position in stream
00074  * @param buf buffer to put data in
00075  * @param max maximum number of bytes that can be put in buf
00076  * @return suitable libmicrohttpd return code
00077  */
00078 int
00079 WebRequestDispatcher::dynamic_reply_data_cb(void *reply,
00080                                             uint64_t pos, char *buf, int max)
00081 {
00082   DynamicWebReply *dreply = static_cast<DynamicWebReply *>(reply);
00083   return dreply->next_chunk(pos, buf, max);
00084 }
00085
00086 
00087 /** Callback to free dynamic web reply.
00088  * @param reply Instance of DynamicWebReply to free.
00089  */
00090 void
00091 WebRequestDispatcher::dynamic_reply_free_cb(void *reply)
00092 {
00093   DynamicWebReply *dreply = static_cast<DynamicWebReply *>(reply);
00094   delete dreply;
00095 }
00096
00097 
00098 /** Queue a static web reply.
00099  * @param connection libmicrohttpd connection to queue response to
00100  * @param sreply static web reply to queue
00101  * @return suitable libmicrohttpd return code
00102  */
00103 int
00104 WebRequestDispatcher::queue_static_reply(struct MHD_Connection * connection,
00105                                          StaticWebReply *sreply)
00106 {
00107   struct MHD_Response *response;
00108   sreply->pack();
00109   if (sreply->body_length() > 0) {
00110     response = MHD_create_response_from_data(sreply->body_length(),
00111                                              (void*) sreply->body().c_str(),
00112                                              /* free */ MHD_YES,
00113                                              /* copy */ MHD_YES);
00114   } else {
00115     response = MHD_create_response_from_data(0, (void*) "",
00116                                              /* free */ MHD_NO,
00117                                              /* copy */ MHD_NO);
00118   }
00119
00120   const WebReply::HeaderMap &headers = sreply->headers();
00121   for (WebReply::HeaderMap::const_iterator i = headers.begin(); i != headers.end(); ++i) {
00122     MHD_add_response_header(response, i->first.c_str(), i->second.c_str());
00123   }
00124
00125   int rv = MHD_queue_response(connection, sreply->code(), response);
00126   MHD_destroy_response(response);
00127   return rv;
00128 }
00129
00130 
00131 /** Process request callback for libmicrohttpd.
00132  * @param connection libmicrohttpd connection instance
00133  * @param url URL, may contain escape sequences
00134  * @param method HTTP method
00135  * @param version HTTP version
00136  * @param upload_data uploaded data
00137  * @param upload_data_size size of upload_data parameter
00138  * @param session_data session data pointer
00139  * @return appropriate return code for libmicrohttpd
00140  */
00141 int
00142 WebRequestDispatcher::process_request(struct MHD_Connection * connection,
00143                                       const char *url,
00144                                       const char *method,
00145                                       const char *version,
00146                                       const char *upload_data,
00147                                       size_t *upload_data_size,
00148                                       void **session_data)
00149 {
00150   std::string surl = url;
00151   static int dummy;
00152   int ret;
00153
00154   if (0 != strcmp(method, "GET"))
00155     return MHD_NO; /* unexpected method */
00156
00157   if (&dummy != *session_data) {
00158     // The first time only the headers are valid,
00159     // do not respond in the first round...
00160     *session_data = &dummy;
00161     return MHD_YES;
00162   }
00163   *session_data = NULL; /* clear context pointer */
00164
00165   WebRequestProcessor *proc = NULL;
00166   std::map<std::string, WebRequestProcessor *>::iterator __pit;
00167   for (__pit = __processors.begin(); (proc == NULL) && (__pit != __processors.end()); ++__pit) {
00168     if (surl.find(__pit->first) == 0) {
00169       WebPageReply::set_active_baseurl(__pit->first);
00170       proc = __pit->second;
00171     }
00172   }
00173
00174   if ( surl == "/" ) {
00175     if ( __startpage_processor ) {
00176       proc = __startpage_processor;
00177     } else {
00178       WebPageReply preply("Fawkes", "<h1>Welcome to Fawkes.</h1><hr />");
00179       ret = queue_static_reply(connection, &preply);
00180     }
00181   }
00182
00183   if (proc) {
00184     struct MHD_Response *response;
00185
00186     char *urlc = strdup(url);
00187     fawkes::hex_unescape(urlc);
00188
00189     WebReply *reply = proc->process_request(urlc, method, version, upload_data, upload_data_size, session_data);
00190
00191     free(urlc);
00192
00193     if ( reply ) {
00194       StaticWebReply  *sreply = dynamic_cast<StaticWebReply *>(reply);
00195       DynamicWebReply *dreply = dynamic_cast<DynamicWebReply *>(reply);
00196       if (sreply) {
00197         ret = queue_static_reply(connection, sreply);
00198         delete reply;
00199       } else if (dreply) {
00200         response = MHD_create_response_from_callback(dreply->size(),
00201                                                      dreply->chunk_size(),
00202                                                      WebRequestDispatcher::dynamic_reply_data_cb,
00203                                                      dreply,
00204                                                      WebRequestDispatcher::dynamic_reply_free_cb);
00205         ret = MHD_queue_response (connection, dreply->code(), response);
00206         MHD_destroy_response (response);
00207       } else {
00208         WebErrorPageReply ereply(WebReply::HTTP_INTERNAL_SERVER_ERROR);
00209         ret = queue_static_reply(connection, &ereply);
00210         delete reply;
00211       }
00212     } else {
00213       WebErrorPageReply ereply(WebReply::HTTP_NOT_FOUND);
00214       ret = queue_static_reply(connection, &ereply);
00215     }
00216   } else {
00217     WebErrorPageReply ereply(WebReply::HTTP_NOT_FOUND);
00218     ret = queue_static_reply(connection, &ereply);
00219   }
00220   return ret;
00221 }
00222 
00223 /** Add a request processor.
00224  * @param url_prefix baseurl this processor should handle
00225  * @param processor processor for baseurl
00226  */
00227 void
00228 WebRequestDispatcher::add_processor(const char *url_prefix,
00229                                     WebRequestProcessor *processor)
00230 {
00231   if (std::string(url_prefix) == "/") {
00232     __startpage_processor = processor;
00233   } else {
00234     __processors[url_prefix] = processor;
00235   }
00236 }
00237
00238 
00239 /** Remove a request processor.
00240  * @param url_prefix baseurl the processor handled
00241  */
00242 void
00243 WebRequestDispatcher::remove_processor(const char *url_prefix)
00244 {
00245   if (std::string(url_prefix) == "/") {
00246     __startpage_processor = NULL;
00247   } else {
00248     __processors.erase(url_prefix);
00249   }
00250 }