//*************************************************************************
/*
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
*
* SYNTAX: httptest
* -http_port Server port for WWW
* -http_base Base file location for Web page service
* -rest_port Connection port to RESTful app
* -rest_base RESTful base address
* -v Show version and information
*
* This program uses libmicrohttpd from Christian Grothoff as the HTTP
* protocol manager
*/
//************************************************************************
#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 URL_TYPE url_type( char *url,RSERV_DATA *rdata );
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();
// (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
*
* OUTPUT: Return MHD_YES if successful, MHD_NO if not
*
**********************************************************************/
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;
int ret = MHD_NO;
HTTP_TYPE call_method;
struct MHD_Response *response = NULL;
URL_TYPE type;
// (1) Classify the requested method
call_method = get_method(method);
// (1) Where is this going ?
// See if it's to the HTTP or RESTful site
// Compare the URL (less the first character) with the
// HTTP base
site = (char *)&(url[1]);
type = url_type(site,data);
switch(type)
{
case HTTP_SITE: // Serve pages
ret = http( cls,
connection,
url,
call_method,
version,
upload_data,
upload_data_size,
ptr);
break;
case REST_SITE: // Do RESTful stuff
ret = rest( cls,
connection,
url,
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;
}
/***********************************************************************
*
* URL_TYPE url_type( char *url,RSERV_DATA *rdata ) - Classify URL
*
* INPUT: received URL
*
* OUTPUT: Return if URL is going to REST, HTTP or nothing
* Compare URL up to first / and see if it matches
* the HTTP or REST path or neither
*
**********************************************************************/
static URL_TYPE url_type( char *url,RSERV_DATA *rdata )
{
int i;
URL_TYPE type = NO_SITE;
char base[256];
// (1) Copy first 256 bytes of the URL and use that for further work
// also upper case it
strncpy(base,url,256);
for(i=0; i<256; i++ )
base[i] = toupper(base[i]);
// (2) Now compare it with the http_base & rest base
if( strncmp(base,rdata->http_base,strlen(rdata->http_base)) == 0 )
{
type = HTTP_SITE;
}
else
{
if( strncmp(base,rdata->rest_base,strlen(rdata->rest_base)) == 0 )
{
type = REST_SITE;
}
}
return type;
}
#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;
}
#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;
// (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;
}