//************************************************************************* /* Copyright (c) 2010. Pandora Products All Rights Reserved. */ //************************************************************************* /* * Module Name: REST - Application Web server * * Description: This server is used to serve web pages and * do RESTful web operations * * Author: Jim Schimpf * * Revision History: 26 Sep 2010 Initial version * 10 Oct 2010 Add restcom comunications * * Uses RSERV_DATA structure with the following field values * to start operation * http_port - Port used for HTTP interaction * http_site - Path to HTML files served * rest_base - Base of REST command tree * Will run till run_flag set FALSE in the RSERV_DATA structure. * * Callbacks: * Once running when a RESTful URI is received it will call the * rest() routine and from there the request will be sent via * restcom command link to the handler (Lua script) * Results will be returned the same way and the request returned * to libmicrohttpd * This program uses libmicrohttpd from Christian Grothoff as the HTTP * protocol manager * * 26 Sep 2010 Initial version * 29 Sep 2010 Convert to a module * 10-Dec-2010 Change to require two tries to handle request. * 23-Dec-2011 [7b5a95ed75] Modify to allow just IP address to get * initial page */ //************************************************************************ #include #include #include #include "platform.h" #include "microhttpd.h" #include "internal.h" #include "rest.h" #include "restcom.h" #if 0 #pragma mark - #pragma mark -- Data -- #endif typedef enum { NO_SITE, HTTP_SITE, REST_SITE, } URL_TYPE; typedef struct { const char *method_name; HTTP_TYPE type; } HTTP_METHOD_LIST; static HTTP_METHOD_LIST methods[] = { MHD_HTTP_METHOD_CONNECT, HTTP_CONNECT, MHD_HTTP_METHOD_DELETE, HTTP_DELETE, MHD_HTTP_METHOD_GET, HTTP_GET, MHD_HTTP_METHOD_HEAD, HTTP_HEAD, MHD_HTTP_METHOD_OPTIONS, HTTP_OPTIONS, MHD_HTTP_METHOD_POST, HTTP_POST, MHD_HTTP_METHOD_PUT, HTTP_PUT, MHD_HTTP_METHOD_TRACE, HTTP_TRACE, }; static int distributor (void *cls, struct MHD_Connection *connection, const char *url, const char *method, const char *version, const char *upload_data, size_t *upload_data_size, void **ptr); static HTTP_TYPE get_method( const char *method ); static char *classify_url(const char *url, RSERV_DATA *data, char *lurl_buffer, URL_TYPE *type); static void set_address( struct MHD_Connection *connection, RSERV_DATA *data); static int check_address( RSERV_DATA *data); static int http (void *cls, struct MHD_Connection *connection, const char *url, HTTP_TYPE method, const char *version, const char *upload_data, size_t *upload_data_size, void **ptr); static char *get_file( char *path, size_t *size ); static int rest (void *cls, struct MHD_Connection *connection, const char *url, HTTP_TYPE method, const char *version, const char *upload_data, size_t *upload_data_size, void **ptr); #define MSG_404 "404 PAGE

404 Page NOT FOUND

" #define DEF_HTTP_PORT 8080 static RSERV_DATA local_data; #if 0 #pragma mark - #pragma mark -- Server External API -- #endif /*********************************************************************** * * int rest_server( NONE ) - Run server * * INPUT: NONE * * OUTPUT: Return 0 if run ok (when server shutdown) * < 0 if server start/run problem * **********************************************************************/ int rest_server( void ) { struct MHD_Daemon *d = NULL; int rtnval = -1; // Set for failure RSERV_DATA *rdata = rest_data(); // (0) Find max length of REST and HTTP base sizes if( rdata->len_http_base > rdata->len_rest_base ) rdata->len_header = rdata->len_http_base; else rdata->len_header = rdata->len_rest_base; // (1) Set up RESTCOM channel if( restcom_server_open() == 0 ) { // (2) Start HTTP server rdata->run_flag = 1; // Set for RUN d = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION | MHD_USE_DEBUG, rdata->http_port, NULL, NULL, &distributor, rdata , MHD_OPTION_END); // (3) If it started then keep running till user // calls quits if( d != NULL ) { rtnval = 0; // Successful run rdata->d = (void *)d; } } return rtnval; } /*********************************************************************** * * int rest_server_close( NONE ) - Close server * * INPUT: NONE * * OUTPUT: NONE * **********************************************************************/ void rest_server_close( void ) { RSERV_DATA *rdata = rest_data(); struct MHD_Daemon *d; // (1) Shutdown HTTP d = (struct MHD_Daemon *)rdata->d; MHD_stop_daemon (d); // (2) Shutdown Server comm restcom_server_close(); } /*********************************************************************** * * RSERV_DATA *rest_data(void) - Return pointer to server data struct * * INPUT: NONE * * OUTPUT: Pointer to local server data struct * **********************************************************************/ RSERV_DATA *rest_data(void) { return( &local_data ); } #if 0 #pragma mark - #pragma mark -- MHD Support --- #endif /*********************************************************************** * * static int distributor () - Do HTTP Request * * INPUT: cls - Registration option * connection - Context * url - URL requested * method - Type of action needed * version - HTTP version string * upload_data - Passed in data on POST * upload_data_size - Size in bytes of post * ptr - * * OUTPUT: Return MHD_YES if successful, MHD_NO if not * NOTE: Change to requre two tries to get message (not sure why....) * **********************************************************************/ static int distributor (void *cls, struct MHD_Connection *connection, const char *url, const char *method, const char *version, const char *upload_data, size_t *upload_data_size, void **ptr) { RSERV_DATA *data = (RSERV_DATA *)cls; //char *site; char *lurl; // Local use only char lurl_buffer[256]; int ret = MHD_NO; HTTP_TYPE call_method; struct MHD_Response *response = NULL; URL_TYPE type; // (0) Change to only answer on second try (?) if (NULL == *ptr) { *ptr = connection; // Set to receive second request return MHD_YES; } // (1) Classify the requested method call_method = get_method(method); // (2) Look at the incomming URL // Convert to acceptable type lurl = classify_url(url,data,lurl_buffer,&type); // (3) Store the current request IP in the internal data // Then check if we have lock on that requires a particular value set_address( connection,data ); if( !check_address(data ) ) type = NO_SITE; // Just give them a 404 if no match // (4) Switch on request type to correct handler switch(type) { case HTTP_SITE: // Serve pages ret = http( cls, connection, lurl, call_method, version, upload_data, upload_data_size, ptr); break; case REST_SITE: // Do RESTful stuff ret = rest( cls, connection, lurl, call_method, version, upload_data, upload_data_size, ptr); break; case NO_SITE: // 404 response = MHD_create_response_from_data (strlen (MSG_404), (void *) MSG_404, MHD_NO, MHD_NO); if( response != NULL ) { ret = MHD_queue_response (connection, MHD_HTTP_OK, response); MHD_destroy_response (response); } break; } return ret; } /*********************************************************************** * * static HTTP_TYPE get_method( char *method ) * * INPUT: method - HTTP method string * * OUTPUT: Return method type or HTTP_NONE if not found * **********************************************************************/ static HTTP_TYPE get_method( const char *method ) { int i; int n = sizeof(methods)/sizeof(HTTP_METHOD_LIST); HTTP_TYPE rtn = HTTP_NONE; // Find the matching method for( i=0; i NULL) * * RULES: / -> HTTP_SITE /data->http_base/index.html // / -> HTTP_SITE /data->http_base/ // /data->http_base/.. HTTP_SITE & pass url // /data->rest_base/.. REST_SITE & pass url // Anything else NO_SITE -> 404 */ //**********************************************************************/ static char *classify_url(const char *url, RSERV_DATA *data, char *lurl_buffer, URL_TYPE *type) { int i; char *lurl; // (1) Is is the "/" case ? if( strcmp(url,"/") == 0 ) { // Yes so return index page URL sprintf(lurl_buffer,"/%s/index.html",data->http_base); lurl = (char *)&lurl_buffer[0]; *type = HTTP_SITE; } else { // (2) Not that so see if first chunk is either // REST or HTTP // NOTE: During copy we skip the first character (/) // of the input URL for( i=0; ilen_header; i++ ) { lurl_buffer[i] = toupper( url[i+1]); } *type = NO_SITE; if( strncmp(lurl_buffer,data->http_base,data->len_http_base) == 0 ) { *type = HTTP_SITE; lurl = (char *)url; } else { if( data->len_rest_base > 0 && strncmp(lurl_buffer,data->rest_base,data->len_rest_base) == 0 ) { *type = REST_SITE; lurl = (char *)url; } } // (3) If it's still NO site then stack on /http_base on the front // and return it as HTTP if( *type == NO_SITE ) { sprintf(lurl_buffer,"/%s/%s",data->http_base,url); lurl = (char *)&lurl_buffer[0]; *type = HTTP_SITE; } } return lurl; } #if 0 #pragma mark - #pragma mark -- HTTP support --- #endif /*********************************************************************** * * static int http () - Do HTTP Request * * INPUT: cls - Registration option * connection - Context * url - URL requested * method - Type of action needed * version - HTTP version string * upload_data - Passed in data on POST * upload_data_size - Size in bytes of post * * OUTPUT: Return MHD_YES if successful, MHD_NO if not * **********************************************************************/ static int http (void *cls, struct MHD_Connection *connection, const char *url, HTTP_TYPE method, const char *version, const char *upload_data, size_t *upload_data_size, void **ptr) { RSERV_DATA *data = (RSERV_DATA *)cls; struct MHD_Response *response = NULL; int ret = MHD_NO; char fname[256]; char *path; char *fdata; size_t fsize; // (1) Switch on requested method switch( method ) { case HTTP_GET: // GET METHOD // Build the file location, first strip off the // http_base and the leading / from the URL path = (char *)&url[(int)(strlen(data->http_base)+1)]; strcpy(fname,data->http_site); strcat(fname,path); // Pointer to actual file inside http_base fdata = get_file(fname,&fsize); if( fdata != NULL) { // If we have file data then build a response from it response = MHD_create_response_from_data (fsize,(void *)fdata, MHD_YES, MHD_NO); } else { // Otherwise return a 404 response = MHD_create_response_from_data (strlen (MSG_404), (void *) MSG_404, MHD_NO, MHD_NO); } break; default: // Unknown method break; } // Return response and status if( response != NULL ) { ret = MHD_queue_response (connection, MHD_HTTP_OK, response); MHD_destroy_response (response); } return ret; } /*********************************************************************** * * char *get_file( char *path, size_t *size ) - Read in a file * * INPUT: path - Path to file * size - Size in bytes if read * * OUTPUT: Pointer to allocated buffer with file data * **********************************************************************/ static char *get_file( char *path, size_t *size ) { char *data = NULL; int fd; struct stat stats; int sz; // (1) Open and get the file size fd = open(path,O_RDONLY); if( fd >= 0 ) { // (2) Get the size if( fstat(fd,&stats) == 0 ) { // (3) Build the buffer data = MEMALLOC( stats.st_size ); if( data != NULL ) { // (4) Read the file & close it sz = read(fd, (void *)data, stats.st_size); if( sz <= 0 ) { printf("Read [%s] Bad FILE\n",path); MEMFREE(data); data = NULL; } else { *size = (size_t)sz; printf("Read: [%s] %d bytes\n",path,(int)(*size)); } } } close(fd); } return data; } /*********************************************************************** * * int set_address( struct MHD_Connection *connection, RSERV_DATA *data) * Set current request IP into internal data * * INPUT: connection - Connection data (client address = connection->addr) * data - Store request IP here * * OUTPUT: NONE * **********************************************************************/ static void set_address( struct MHD_Connection *connection, RSERV_DATA *data) { char buf[128]; char *ptr = &(buf[0]); // (1) Get address and store ptr = (char *)inet_ntop(AF_INET, (void const *)&(connection->addr->sin_addr), ptr,(socklen_t)128); if( ptr != NULL ) { strcpy(data->request_ip_addr,ptr); } } /*********************************************************************** * * int check_address( struct MHD_Connection *connection, RSERV_DATA *data) * Check if current connection is required to match * specified address and check match * * INPUT: data - Check flag for match test and match data here * * OUTPUT: 1 if no match required or if match success * 0 if not * **********************************************************************/ static int check_address( RSERV_DATA *data) { int rtnval = 1; // Set for success // (1) Match required ? if( data->lock_addr_flag ) { // (2) Check against request IP if( strcmp(data->request_ip_addr,data->client_ip_addr) == 0 ) rtnval = 1; // SUCCESS else rtnval = 0; // Failure } return rtnval; } #if 0 #pragma mark - #pragma mark -- RESTful support --- #endif /*********************************************************************** * * static int rest () - Do RESTful Request * * INPUT: cls - Registration option * connection - Context * url - URL requested * method - Type of action needed * version - HTTP version string * upload_data - Passed in data on POST * upload_data_size - Size in bytes of post * * OUTPUT: Return MHD_YES if successful, MHD_NO if not * **********************************************************************/ static int rest (void *cls, struct MHD_Connection *connection, const char *url, HTTP_TYPE method, const char *version, const char *upload_data, size_t *upload_data_size, void **ptr) { struct MHD_Response *response = NULL; int ret = MHD_NO; RESTQEL *el; int count = 0; int fail = 0; int size = 0; char *data = NULL; /* ***** DOES NOT WORK WITH AJAX POST DATA ***** MUST PUT DATA INTO URI // (1) Send data to client // NOTE: On post send read buffer with POST data if( method == HTTP_POST ) { size = strlen(connection->read_buffer); data = connection->read_buffer; } */ if( restcom_master_send( (void *)connection, method, (char *)url, size, (char *)data) == 0 ) { // (2) Wait for the result while( (el = restcom_master_receive((void *)connection)) == NULL ) { usleep( 100000 ); // Sleep for 100 ms count = count + 1; if( count > 200 ) // Wait 20 seconds { fail = 1; break; } } // (3) If OK, build the response if( fail == 0 ) { response = MHD_create_response_from_data ((size_t)el->size,(void *)el->data, MHD_YES, MHD_NO); } // Return response and status if( response != NULL ) { ret = MHD_queue_response (connection, MHD_HTTP_OK, response); MHD_destroy_response (response); } // (4) Remove received element restcom_delete_packet( el ); } return ret; }