Firenet

rest.c
Login

rest.c

File luasupport/HTTP/rest/rest.c from the latest check-in


//*************************************************************************
/*
    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;
}