//*************************************************************************
/*
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 <fcntl.h>
#include <sys/stat.h>
#include <ctype.h>
#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 "<html><head><title>404 PAGE</title></head><body><center><h1>404 Page NOT FOUND</h1></center></body></html>"
#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<n; i++ )
{
if( strcmp(method,methods[i].method_name) == 0 )
rtn = methods[i].type;
}
return rtn;
}
//***********************************************************************
/*
* char *classify_url(const char *url,
* RSERV_DATA *data,
* char *lurl_buffer,
* URL_TYPE *type) - Vet incomming URL
*
* INPUT: url - Received URL
* data - Data structure with HTTP and REST bases
* lurl_buffer - Data area for building new url (if necessary)
* type - Returned type of URL
*
*
* OUTPUT: Pointer to url to be used in HTTP access
* type - HTTP_SITE - Http data file access
* REST_SITE - Restful data access
* NO_SITE - Bad URL (lurl -> NULL)
*
* RULES:
/ -> HTTP_SITE /data->http_base/index.html
// /<??> -> HTTP_SITE /data->http_base/<incomming url>
// /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; i<data->len_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;
}