initial proof of concept
This commit is contained in:
parent
446712627d
commit
3f531aa48e
0
.fuse_hidden0000013e00000002
Normal file
0
.fuse_hidden0000013e00000002
Normal file
1
.gitignore
vendored
1
.gitignore
vendored
@ -62,3 +62,4 @@
|
||||
# Debug files
|
||||
*.dSYM/
|
||||
|
||||
work/
|
||||
|
||||
BIN
bin/uce_fastcgi.debug.linux.bin
Executable file
BIN
bin/uce_fastcgi.debug.linux.bin
Executable file
Binary file not shown.
33
build_linux.sh
Executable file
33
build_linux.sh
Executable file
@ -0,0 +1,33 @@
|
||||
#!/bin/bash
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
BUILDMODE=${2:-"debug"}
|
||||
OPT_FLAG="O2"
|
||||
GF="uce_fastcgi"
|
||||
|
||||
if [ "$BUILDMODE" == "debug" ]; then
|
||||
OPT_FLAG="O0"
|
||||
fi
|
||||
echo "Build mode: $BUILDMODE"
|
||||
|
||||
mkdir bin > /dev/null 2>&1
|
||||
mkdir bin/tmp > /dev/null 2>&1
|
||||
mkdir bin/assets > /dev/null 2>&1
|
||||
mkdir work > /dev/null 2>&1
|
||||
|
||||
COMPILER="clang++"
|
||||
FLAGS="-w -Wall -$OPT_FLAG -std=c++17 -fpermissive -ffast-math"
|
||||
|
||||
LIBS="-ldl -lm -lpthread "
|
||||
SRCFLAGS="-D EXEC_NAME=\"$GF\" -D PLATFORM_NAME=\"linux\""
|
||||
|
||||
echo "Compliling executable..."
|
||||
time -p $COMPILER src/linux_fastcgi.cpp $SRCFLAGS $FLAGS $LIBS -o bin/$GF.$BUILDMODE.linux.bin
|
||||
|
||||
if [ $? -eq 0 ]
|
||||
then
|
||||
ls -lh bin/ | grep $GF
|
||||
exit 0
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
39
scripts/compile
Executable file
39
scripts/compile
Executable file
@ -0,0 +1,39 @@
|
||||
#!/bin/bash
|
||||
cd "$(dirname "$0")"
|
||||
cd ..
|
||||
|
||||
SRC_DIR="$1"
|
||||
DEST_DIR="$2"
|
||||
|
||||
SRC_FN="$3"
|
||||
PP_FN="$4"
|
||||
SO_FN="$5"
|
||||
|
||||
#echo "Source Dir: $SRC_DIR"
|
||||
#echo "Dest Dir: $DEST_DIR"
|
||||
#echo "Source File: $SRC_FN"
|
||||
#echo "Preprocessed File: $PP_FN"
|
||||
#echo "Dest File: $SO_FN"
|
||||
|
||||
mkdir -p "$DEST_DIR" > /dev/null 2>&1
|
||||
|
||||
BUILDMODE="debug"
|
||||
OPT_FLAG="O0"
|
||||
|
||||
COMPILER="clang++"
|
||||
#COMPILER="g++"
|
||||
FLAGS="-shared -w -Wall -$OPT_FLAG -std=c++17 -fpermissive -ffast-math -fPIC"
|
||||
|
||||
LIBS="-ldl -lm -lpthread "
|
||||
SRCFLAGS="-D PLATFORM_NAME=\"linux\""
|
||||
|
||||
# echo "Compliling executable..."
|
||||
$COMPILER "$DEST_DIR/$PP_FN" $SRCFLAGS $FLAGS $LIBS -o "$DEST_DIR/$SO_FN"
|
||||
|
||||
if [ $? -eq 0 ]
|
||||
then
|
||||
# ls -lh "$DEST_DIR"
|
||||
exit 0
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
8
src/fastcgi/CHANGES.txt
Normal file
8
src/fastcgi/CHANGES.txt
Normal file
@ -0,0 +1,8 @@
|
||||
Version 0.1.3 on 2013-02-10:
|
||||
* Included required C header files.
|
||||
|
||||
Version 0.1.2 on 2011-11-30:
|
||||
* Renamed stdin, stdout, stderr to in, out, err.
|
||||
|
||||
Version 0.1.1 on 2009-08-12:
|
||||
* First release.
|
||||
34
src/fastcgi/CMakeLists.txt
Normal file
34
src/fastcgi/CMakeLists.txt
Normal file
@ -0,0 +1,34 @@
|
||||
CMAKE_MINIMUM_REQUIRED( VERSION 2.6 )
|
||||
|
||||
PROJECT( fcgicc CXX )
|
||||
SET( PROJECT_VERSION 0.1.3 )
|
||||
SET( CMAKE_BUILD_TYPE RELEASE )
|
||||
SET( CMAKE_INSTALL_PREFIX ${PREFIX} )
|
||||
|
||||
FIND_PATH( FCGI_INCLUDE_DIR fastcgi.h )
|
||||
IF( NOT FCGI_INCLUDE_DIR )
|
||||
FIND_PATH( FCGI_INCLUDE_DIR fastcgi.h ${PROJECT_SOURCE_DIR}/fastcgi_devkit )
|
||||
ENDIF()
|
||||
INCLUDE_DIRECTORIES( ${FCGI_INCLUDE_DIR} )
|
||||
|
||||
ADD_SUBDIRECTORY( src )
|
||||
ADD_SUBDIRECTORY( test EXCLUDE_FROM_ALL )
|
||||
|
||||
INSTALL( FILES LICENSE.txt README.txt DESTINATION share/doc/${PROJECT_NAME} )
|
||||
|
||||
SET( DIST_FILE ${PROJECT_NAME}-${PROJECT_VERSION} )
|
||||
ADD_CUSTOM_TARGET( dist ln -sf ${PROJECT_SOURCE_DIR} ${DIST_FILE} &&
|
||||
tar cjf ${DIST_FILE}.tar.bz2
|
||||
${DIST_FILE}/LICENSE.txt
|
||||
${DIST_FILE}/README.txt
|
||||
${DIST_FILE}/CHANGES.txt
|
||||
${DIST_FILE}/CMakeLists.txt
|
||||
${DIST_FILE}/fastcgi_devkit/LICENSE.TERMS
|
||||
${DIST_FILE}/fastcgi_devkit/fastcgi.h
|
||||
${DIST_FILE}/src/fcgicc.cc
|
||||
${DIST_FILE}/src/fcgicc.h
|
||||
${DIST_FILE}/src/CMakeLists.txt
|
||||
${DIST_FILE}/test/test1.cc
|
||||
${DIST_FILE}/test/test2.cc
|
||||
${DIST_FILE}/test/lighttpd.conf
|
||||
${DIST_FILE}/test/CMakeLists.txt )
|
||||
25
src/fastcgi/LICENSE.txt
Normal file
25
src/fastcgi/LICENSE.txt
Normal file
@ -0,0 +1,25 @@
|
||||
Copyright 2008, 2009 Andrey Zholos. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
3. Neither the names of the copyright holders nor the names of contributors
|
||||
may be used to endorse or promote products derived from this software
|
||||
without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
154
src/fastcgi/README.txt
Normal file
154
src/fastcgi/README.txt
Normal file
@ -0,0 +1,154 @@
|
||||
FastCGI C++ Class library (fcgicc)
|
||||
|
||||
1. Introduction
|
||||
|
||||
This is a simple C++ class library that provides FastCGI server functionality.
|
||||
FastCGI is a protocol for connecting web servers with programs that generate
|
||||
content. The protocol is described in more detail at http://www.fastcgi.com.
|
||||
This library provides a single class which handles FastCGI connections on TCP/IP
|
||||
or local domain sockets. Multiple connections are handled in a single thread
|
||||
using select(). When a request is ready, it is passed to an application-
|
||||
supplied callback for processing, after which the generated response is sent
|
||||
back to the client.
|
||||
|
||||
|
||||
2. Version information
|
||||
|
||||
This is the first release of fcgicc, version 0.1.2. It provides a full
|
||||
implementation of the responder mode of FastCGI, but has undergone only limited
|
||||
testing. Use with care!
|
||||
|
||||
|
||||
3. Licensing
|
||||
|
||||
fcgicc is free software, available under a BSD-style license. There is no
|
||||
warranty; not even for merchantability or fitness for a particular purpose. See
|
||||
the file LICENSE.txt for complete information.
|
||||
|
||||
|
||||
4. Installing
|
||||
|
||||
This library depends on the FastCGI Development Kit from http://www.fastcgi.com.
|
||||
For convenience the required part of it is included in this distribution and
|
||||
will be used if the development kit is not found on your system.
|
||||
|
||||
To build and install fcgicc as a standalone library you will need CMake, which
|
||||
is available from http://cmake.org.
|
||||
|
||||
cd fcgicc
|
||||
|
||||
cmake .
|
||||
make install
|
||||
|
||||
- or -
|
||||
|
||||
cmake -DPREFIX=$HOME/local .
|
||||
make install
|
||||
|
||||
Alternatively, it may be simpler to import the two source files into your
|
||||
project and build them as part of it.
|
||||
|
||||
|
||||
5. Using
|
||||
|
||||
Here is how it works:
|
||||
|
||||
Client ------------> Web server ------> FastCGIServer ----------------.
|
||||
HTTP request params FastCGIRequest |
|
||||
in '
|
||||
Application
|
||||
.
|
||||
|
|
||||
Client <------------ Web server <------ FastCGIServer <---------------'
|
||||
HTTP response out FastCGIRequest
|
||||
err
|
||||
|
||||
The web server, which is a client to the FastCGI server, forwards a request as a
|
||||
set of key-value parameter pairs and a standard input stream. The parameter
|
||||
pairs are the environment variables from plain CGI, and they include such
|
||||
important variables as REQUEST_URI. The standard input stream contains data
|
||||
from POST requests.
|
||||
|
||||
An instance of the FastCGIServer class listens for requests from the web server,
|
||||
builds a FastCGIRequest instance for each one, calls event handlers defined by
|
||||
the application to process them, and responds to the web server.
|
||||
|
||||
The application processes requests using event handlers like this:
|
||||
|
||||
...
|
||||
|
||||
#include <fcgicc.h>
|
||||
|
||||
...
|
||||
|
||||
int handle_request(FastCGIRequest& request) {
|
||||
// This is always the first event to occur. It occurs when the
|
||||
// server receives all parameters. There may be more data coming on the
|
||||
// standard input stream.
|
||||
|
||||
if (request.params.count("REQUEST_URI"))
|
||||
return 0; // OK, continue processing
|
||||
else
|
||||
return 1; // Stop processing and return error code
|
||||
}
|
||||
|
||||
int handle_data(FastCGIRequest& request) {
|
||||
// This event occurs when data is received on the standard input stream.
|
||||
// A simple string is used to hold the input stream, so it is the
|
||||
// responsibility of the application to remember which data it has
|
||||
// processed. The application may modify it; new data will be appended
|
||||
// to it by the server. The same goes for the output and error streams:
|
||||
// the application should append data to them; the server will remove
|
||||
// all sent data from them.
|
||||
|
||||
std::transform(request.stdin.begin(), request.stdin.end(),
|
||||
std::back_inserter(request.stderr),
|
||||
std::bind1st(std::plus<char>(), 1));
|
||||
request.stdin.clear(); // don't process it again
|
||||
return 0; // still OK
|
||||
}
|
||||
|
||||
class Application {
|
||||
public:
|
||||
int handle_complete(FastCGIRequest& request) {
|
||||
// The event handler can also be a class member function. This
|
||||
// event occurs when the parameters and standard input streams are
|
||||
// both closed, and thus the request is complete.
|
||||
|
||||
request.out.append("Content-Type: text/plain\r\n\r\n");
|
||||
request.out.append("You requested: ");
|
||||
request.out.append(request.params[std::string("REQUEST_URI")]);
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
...
|
||||
|
||||
The application sets up the FastCGI server like this:
|
||||
|
||||
...
|
||||
|
||||
FastCGIServer server; // Instantiate a server
|
||||
|
||||
// Set up our request handlers
|
||||
server.request_handler(&handle_request);
|
||||
server.data_handler(&handle_data);
|
||||
server.complete_handler(application, &Application::handle_complete);
|
||||
|
||||
server.listen(7000); // Listen on a TCP port
|
||||
server.listen(7001); // ... or on two
|
||||
server.listen("./socket"); // ... and also on a local doman socket
|
||||
|
||||
server.process(100); // Process some data, but don't wait more
|
||||
// than 100 ms for it to arrive.
|
||||
server.process(); // Process some data with no timeout
|
||||
server.process_forever(); // Process everything
|
||||
|
||||
...
|
||||
|
||||
|
||||
6. Updates and feedback
|
||||
|
||||
This library is hosted at http://althenia.net/fcgicc. It is programmed by
|
||||
Andrey Zholos <aaz@althenia.net>. Comments, bug reports and testing results are
|
||||
welcome and will be appreciated.
|
||||
28
src/fastcgi/fastcgi_devkit/LICENSE.TERMS
Normal file
28
src/fastcgi/fastcgi_devkit/LICENSE.TERMS
Normal file
@ -0,0 +1,28 @@
|
||||
This FastCGI application library source and object code (the
|
||||
"Software") and its documentation (the "Documentation") are
|
||||
copyrighted by Open Market, Inc ("Open Market"). The following terms
|
||||
apply to all files associated with the Software and Documentation
|
||||
unless explicitly disclaimed in individual files.
|
||||
|
||||
Open Market permits you to use, copy, modify, distribute, and license
|
||||
this Software and the Documentation for any purpose, provided that
|
||||
existing copyright notices are retained in all copies and that this
|
||||
notice is included verbatim in any distributions. No written
|
||||
agreement, license, or royalty fee is required for any of the
|
||||
authorized uses. Modifications to this Software and Documentation may
|
||||
be copyrighted by their authors and need not follow the licensing
|
||||
terms described here. If modifications to this Software and
|
||||
Documentation have new licensing terms, the new terms must be clearly
|
||||
indicated on the first page of each file where they apply.
|
||||
|
||||
OPEN MARKET MAKES NO EXPRESS OR IMPLIED WARRANTY WITH RESPECT TO THE
|
||||
SOFTWARE OR THE DOCUMENTATION, INCLUDING WITHOUT LIMITATION ANY
|
||||
WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. IN
|
||||
NO EVENT SHALL OPEN MARKET BE LIABLE TO YOU OR ANY THIRD PARTY FOR ANY
|
||||
DAMAGES ARISING FROM OR RELATING TO THIS SOFTWARE OR THE
|
||||
DOCUMENTATION, INCLUDING, WITHOUT LIMITATION, ANY INDIRECT, SPECIAL OR
|
||||
CONSEQUENTIAL DAMAGES OR SIMILAR DAMAGES, INCLUDING LOST PROFITS OR
|
||||
LOST DATA, EVEN IF OPEN MARKET HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES. THE SOFTWARE AND DOCUMENTATION ARE PROVIDED "AS IS".
|
||||
OPEN MARKET HAS NO LIABILITY IN CONTRACT, TORT, NEGLIGENCE OR
|
||||
OTHERWISE ARISING OUT OF THIS SOFTWARE OR THE DOCUMENTATION.
|
||||
136
src/fastcgi/fastcgi_devkit/fastcgi.h
Normal file
136
src/fastcgi/fastcgi_devkit/fastcgi.h
Normal file
@ -0,0 +1,136 @@
|
||||
/*
|
||||
* fastcgi.h --
|
||||
*
|
||||
* Defines for the FastCGI protocol.
|
||||
*
|
||||
*
|
||||
* Copyright (c) 1995-1996 Open Market, Inc.
|
||||
*
|
||||
* See the file "LICENSE.TERMS" for information on usage and redistribution
|
||||
* of this file, and for a DISCLAIMER OF ALL WARRANTIES.
|
||||
*
|
||||
* $Id: fastcgi.h,v 1.1.1.1 1997/09/16 15:36:32 stanleyg Exp $
|
||||
*/
|
||||
|
||||
#ifndef _FASTCGI_H
|
||||
#define _FASTCGI_H
|
||||
|
||||
/*
|
||||
* Listening socket file number
|
||||
*/
|
||||
#define FCGI_LISTENSOCK_FILENO 0
|
||||
|
||||
typedef struct {
|
||||
unsigned char version;
|
||||
unsigned char type;
|
||||
unsigned char requestIdB1;
|
||||
unsigned char requestIdB0;
|
||||
unsigned char contentLengthB1;
|
||||
unsigned char contentLengthB0;
|
||||
unsigned char paddingLength;
|
||||
unsigned char reserved;
|
||||
} FCGI_Header;
|
||||
|
||||
#define FCGI_MAX_LENGTH 0xffff
|
||||
|
||||
/*
|
||||
* Number of bytes in a FCGI_Header. Future versions of the protocol
|
||||
* will not reduce this number.
|
||||
*/
|
||||
#define FCGI_HEADER_LEN 8
|
||||
|
||||
/*
|
||||
* Value for version component of FCGI_Header
|
||||
*/
|
||||
#define FCGI_VERSION_1 1
|
||||
|
||||
/*
|
||||
* Values for type component of FCGI_Header
|
||||
*/
|
||||
#define FCGI_BEGIN_REQUEST 1
|
||||
#define FCGI_ABORT_REQUEST 2
|
||||
#define FCGI_END_REQUEST 3
|
||||
#define FCGI_PARAMS 4
|
||||
#define FCGI_STDIN 5
|
||||
#define FCGI_STDOUT 6
|
||||
#define FCGI_STDERR 7
|
||||
#define FCGI_DATA 8
|
||||
#define FCGI_GET_VALUES 9
|
||||
#define FCGI_GET_VALUES_RESULT 10
|
||||
#define FCGI_UNKNOWN_TYPE 11
|
||||
#define FCGI_MAXTYPE (FCGI_UNKNOWN_TYPE)
|
||||
|
||||
/*
|
||||
* Value for requestId component of FCGI_Header
|
||||
*/
|
||||
#define FCGI_NULL_REQUEST_ID 0
|
||||
|
||||
|
||||
typedef struct {
|
||||
unsigned char roleB1;
|
||||
unsigned char roleB0;
|
||||
unsigned char flags;
|
||||
unsigned char reserved[5];
|
||||
} FCGI_BeginRequestBody;
|
||||
|
||||
typedef struct {
|
||||
FCGI_Header header;
|
||||
FCGI_BeginRequestBody body;
|
||||
} FCGI_BeginRequestRecord;
|
||||
|
||||
/*
|
||||
* Mask for flags component of FCGI_BeginRequestBody
|
||||
*/
|
||||
#define FCGI_KEEP_CONN 1
|
||||
|
||||
/*
|
||||
* Values for role component of FCGI_BeginRequestBody
|
||||
*/
|
||||
#define FCGI_RESPONDER 1
|
||||
#define FCGI_AUTHORIZER 2
|
||||
#define FCGI_FILTER 3
|
||||
|
||||
|
||||
typedef struct {
|
||||
unsigned char appStatusB3;
|
||||
unsigned char appStatusB2;
|
||||
unsigned char appStatusB1;
|
||||
unsigned char appStatusB0;
|
||||
unsigned char protocolStatus;
|
||||
unsigned char reserved[3];
|
||||
} FCGI_EndRequestBody;
|
||||
|
||||
typedef struct {
|
||||
FCGI_Header header;
|
||||
FCGI_EndRequestBody body;
|
||||
} FCGI_EndRequestRecord;
|
||||
|
||||
/*
|
||||
* Values for protocolStatus component of FCGI_EndRequestBody
|
||||
*/
|
||||
#define FCGI_REQUEST_COMPLETE 0
|
||||
#define FCGI_CANT_MPX_CONN 1
|
||||
#define FCGI_OVERLOADED 2
|
||||
#define FCGI_UNKNOWN_ROLE 3
|
||||
|
||||
|
||||
/*
|
||||
* Variable names for FCGI_GET_VALUES / FCGI_GET_VALUES_RESULT records
|
||||
*/
|
||||
#define FCGI_MAX_CONNS "FCGI_MAX_CONNS"
|
||||
#define FCGI_MAX_REQS "FCGI_MAX_REQS"
|
||||
#define FCGI_MPXS_CONNS "FCGI_MPXS_CONNS"
|
||||
|
||||
|
||||
typedef struct {
|
||||
unsigned char type;
|
||||
unsigned char reserved[7];
|
||||
} FCGI_UnknownTypeBody;
|
||||
|
||||
typedef struct {
|
||||
FCGI_Header header;
|
||||
FCGI_UnknownTypeBody body;
|
||||
} FCGI_UnknownTypeRecord;
|
||||
|
||||
#endif /* _FASTCGI_H */
|
||||
|
||||
3
src/fastcgi/src/CMakeLists.txt
Normal file
3
src/fastcgi/src/CMakeLists.txt
Normal file
@ -0,0 +1,3 @@
|
||||
ADD_LIBRARY( fcgicc fcgicc.cc fcgicc.h )
|
||||
INSTALL( FILES fcgicc.h DESTINATION include )
|
||||
INSTALL( TARGETS fcgicc LIBRARY DESTINATION lib ARCHIVE DESTINATION lib )
|
||||
648
src/fastcgi/src/fcgicc.cc
Normal file
648
src/fastcgi/src/fcgicc.cc
Normal file
@ -0,0 +1,648 @@
|
||||
/*
|
||||
* Copyright 2008, 2009 Andrey Zholos. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* 3. Neither the names of the copyright holders nor the names of contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is part of the FastCGI C++ Class library (fcgicc) version 0.1,
|
||||
* available at http://althenia.net/fcgicc
|
||||
*/
|
||||
|
||||
|
||||
#include "fcgicc.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <errno.h> // E*
|
||||
#include <unistd.h> // read, write, close, unlink
|
||||
#include <arpa/inet.h> // hton*
|
||||
#include <netinet/in.h> // sockaddr_in, INADDR_*
|
||||
#include <sys/select.h> // select, fd_set, FD_*, timeval
|
||||
#include <sys/socket.h> // socket, bind, accept, listen, sockaddr, AF_*, SOCK_*
|
||||
#include <sys/un.h> // sockaddr_un
|
||||
|
||||
#include "../fastcgi_devkit/fastcgi.h"
|
||||
|
||||
|
||||
FastCGIServer::RequestInfo::RequestInfo() :
|
||||
params_closed(false),
|
||||
in_closed(false),
|
||||
output_closed(false)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
FastCGIServer::Connection::Connection() :
|
||||
close_responsibility(false),
|
||||
close_socket(false)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
FastCGIServer::HandlerBase::operator()(FastCGIRequest&)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
FastCGIServer::FastCGIServer() :
|
||||
handle_request(new HandlerBase),
|
||||
handle_data(new HandlerBase),
|
||||
handle_complete(new HandlerBase)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
FastCGIServer::~FastCGIServer()
|
||||
{
|
||||
for (std::vector<int>::iterator it = listen_sockets.begin();
|
||||
it != listen_sockets.end(); ++it)
|
||||
close(*it);
|
||||
|
||||
for (std::vector<std::string>::iterator it = listen_unlink.begin();
|
||||
it != listen_unlink.end(); ++it)
|
||||
unlink(it->c_str());
|
||||
|
||||
for (std::map<int, Connection*>::iterator it = read_sockets.begin();
|
||||
it != read_sockets.end(); ++it) {
|
||||
close(it->first);
|
||||
for (RequestList::iterator req_it = it->second->requests.begin();
|
||||
req_it != it->second->requests.end(); ++req_it)
|
||||
delete req_it->second;
|
||||
delete it->second;
|
||||
}
|
||||
|
||||
delete handle_request;
|
||||
delete handle_data;
|
||||
delete handle_complete;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
FastCGIServer::request_handler(int (* function)(FastCGIRequest&))
|
||||
{
|
||||
handle_request = new StaticHandler(function);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
FastCGIServer::data_handler(int (* function)(FastCGIRequest&))
|
||||
{
|
||||
handle_data = new StaticHandler(function);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
FastCGIServer::complete_handler(int (* function)(FastCGIRequest&))
|
||||
{
|
||||
handle_complete = new StaticHandler(function);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
FastCGIServer::set_handler(HandlerBase*& handler, HandlerBase* new_handler)
|
||||
{
|
||||
delete handler;
|
||||
handler = new_handler;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
FastCGIServer::listen(unsigned tcp_port)
|
||||
{
|
||||
int listen_socket = socket(PF_INET, SOCK_STREAM, 0);
|
||||
if (listen_socket == -1)
|
||||
throw std::runtime_error("socket() failed");
|
||||
|
||||
try {
|
||||
struct sockaddr_in sa;
|
||||
bzero(&sa, sizeof(sa));
|
||||
sa.sin_family = AF_INET;
|
||||
sa.sin_port = htons(tcp_port);
|
||||
sa.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
if (bind(listen_socket, (struct sockaddr*)&sa, sizeof(sa)) == -1)
|
||||
throw std::runtime_error("bind() failed");
|
||||
|
||||
if (::listen(listen_socket, 100))
|
||||
throw std::runtime_error("listen() failed");
|
||||
|
||||
listen_sockets.push_back(listen_socket);
|
||||
|
||||
} catch (...) {
|
||||
close(listen_socket);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
FastCGIServer::listen(const std::string& local_path)
|
||||
{
|
||||
int listen_socket = socket(PF_UNIX, SOCK_STREAM, 0);
|
||||
if (listen_socket == -1)
|
||||
throw std::runtime_error("socket() failed");
|
||||
|
||||
try {
|
||||
struct sockaddr_un sa;
|
||||
bzero(&sa, sizeof(sa));
|
||||
sa.sun_family = AF_LOCAL;
|
||||
|
||||
std::string::size_type size = local_path.size();
|
||||
if (size >= sizeof(sa.sun_path))
|
||||
throw std::runtime_error("path too long");
|
||||
if (local_path.find_first_of('\0') != std::string::npos)
|
||||
throw std::runtime_error("null character in path");
|
||||
|
||||
std::memcpy(sa.sun_path, local_path.data(), size);
|
||||
|
||||
unlink(local_path.c_str());
|
||||
try {
|
||||
if (bind(listen_socket, (struct sockaddr*)&sa,
|
||||
sizeof(sa) - (sizeof(sa.sun_path) - size - 1)) == -1)
|
||||
throw std::runtime_error("bind() failed");
|
||||
|
||||
if (::listen(listen_socket, 100))
|
||||
throw std::runtime_error("listen() failed");
|
||||
|
||||
listen_sockets.push_back(listen_socket);
|
||||
listen_unlink.push_back(local_path);
|
||||
|
||||
} catch (...) {
|
||||
unlink(local_path.c_str());
|
||||
throw;
|
||||
}
|
||||
|
||||
} catch (...) {
|
||||
close(listen_socket);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
FastCGIServer::abandon_files()
|
||||
{
|
||||
listen_unlink.clear();
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
FastCGIServer::process(int timeout_ms)
|
||||
{
|
||||
char buffer[4096];
|
||||
fd_set fs_read;
|
||||
fd_set fs_write;
|
||||
int nfd = 0;
|
||||
struct timeval tv = { timeout_ms / 1000, (timeout_ms % 1000) * 1000 };
|
||||
|
||||
FD_ZERO(&fs_read);
|
||||
FD_ZERO(&fs_write);
|
||||
|
||||
for (std::vector<int>::const_iterator it = listen_sockets.begin();
|
||||
it != listen_sockets.end(); ++it) {
|
||||
FD_SET(*it, &fs_read);
|
||||
nfd = std::max(nfd, *it);
|
||||
}
|
||||
|
||||
for (std::map<int, Connection*>::const_iterator it = read_sockets.begin();
|
||||
it != read_sockets.end(); ++it) {
|
||||
FD_SET(it->first, &fs_read);
|
||||
if (!it->second->output_buffer.empty())
|
||||
FD_SET(it->first, &fs_write);
|
||||
nfd = std::max(nfd, it->first);
|
||||
}
|
||||
|
||||
int select_result = select(nfd + 1, &fs_read, &fs_write, NULL,
|
||||
timeout_ms < 0 ? NULL : &tv);
|
||||
if (select_result == -1)
|
||||
if (errno == EINTR)
|
||||
return;
|
||||
else
|
||||
throw std::runtime_error("select() failed");
|
||||
|
||||
for (std::vector<int>::const_iterator it = listen_sockets.begin();
|
||||
it != listen_sockets.end(); ++it)
|
||||
if (FD_ISSET(*it, &fs_read)) {
|
||||
int read_socket = accept(*it, NULL, NULL);
|
||||
if (read_socket == -1)
|
||||
throw std::runtime_error("accept() failed");
|
||||
Connection* connection = new Connection;
|
||||
try {
|
||||
read_sockets.insert(std::map<int, Connection*>::value_type(
|
||||
read_socket, connection));
|
||||
} catch (...) {
|
||||
delete connection;
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
for (std::map<int, Connection*>::iterator it = read_sockets.begin();
|
||||
it != read_sockets.end();) {
|
||||
int read_socket = it->first;
|
||||
|
||||
if (FD_ISSET(read_socket, &fs_read)) {
|
||||
int read_result = read(read_socket, buffer, sizeof(buffer));
|
||||
if (read_result == -1)
|
||||
if (errno == ECONNRESET)
|
||||
goto close_socket;
|
||||
else
|
||||
throw std::runtime_error("read() on socket failed");
|
||||
if (read_result == 0)
|
||||
it->second->close_socket = true;
|
||||
else {
|
||||
it->second->input_buffer.append(buffer, read_result);
|
||||
process_connection_read(*it->second);
|
||||
}
|
||||
}
|
||||
|
||||
if (!it->second->output_buffer.empty() &&
|
||||
FD_ISSET(read_socket, &fs_write)) {
|
||||
process_connection_write(*it->second);
|
||||
int write_result = write(read_socket,
|
||||
it->second->output_buffer.data(),
|
||||
it->second->output_buffer.size());
|
||||
if (write_result == -1)
|
||||
throw std::runtime_error("write() failed");
|
||||
it->second->output_buffer.erase(0, write_result);
|
||||
}
|
||||
|
||||
if (it->second->close_socket && it->second->output_buffer.empty()) {
|
||||
close_socket:
|
||||
int close_result = close(it->first);
|
||||
if (close_result == -1 && errno != ECONNRESET)
|
||||
throw std::runtime_error("close() failed");
|
||||
Connection* connection = it->second;
|
||||
read_sockets.erase(it++);
|
||||
delete connection;
|
||||
} else
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
FastCGIServer::process_forever()
|
||||
{
|
||||
for (;;)
|
||||
process();
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
FastCGIServer::process_connection_read(Connection& connection)
|
||||
{
|
||||
std::string::size_type n = 0;
|
||||
while (connection.input_buffer.size() - n >= FCGI_HEADER_LEN) {
|
||||
const FCGI_Header& header = *reinterpret_cast<const FCGI_Header*>(
|
||||
connection.input_buffer.data() + n);
|
||||
if (header.version != FCGI_VERSION_1) {
|
||||
connection.close_socket = true;
|
||||
break;
|
||||
}
|
||||
|
||||
unsigned content_length =
|
||||
(header.contentLengthB1 << 8) + header.contentLengthB0;
|
||||
if (connection.input_buffer.size() - n <
|
||||
FCGI_HEADER_LEN + content_length + header.paddingLength)
|
||||
break;
|
||||
const char* content =
|
||||
connection.input_buffer.data() + n + FCGI_HEADER_LEN;
|
||||
|
||||
RequestID request_id = (header.requestIdB1 << 8) + header.requestIdB0;
|
||||
|
||||
switch (header.type) {
|
||||
case FCGI_GET_VALUES: {
|
||||
Pairs pairs = parse_pairs(content, content_length);
|
||||
|
||||
std::string::size_type base = connection.output_buffer.size();
|
||||
connection.output_buffer.push_back(FCGI_VERSION_1);
|
||||
connection.output_buffer.push_back(FCGI_GET_VALUES_RESULT);
|
||||
connection.output_buffer.append(FCGI_HEADER_LEN - 2, 0);
|
||||
|
||||
for (Pairs::iterator it = pairs.begin(); it != pairs.end(); ++it)
|
||||
if (it->first == FCGI_MAX_CONNS)
|
||||
write_pair(connection.output_buffer,
|
||||
it->first, std::string("100"));
|
||||
else if (it->first == FCGI_MAX_REQS)
|
||||
write_pair(connection.output_buffer,
|
||||
it->first, std::string("1000"));
|
||||
else if (it->first == FCGI_MPXS_CONNS)
|
||||
write_pair(connection.output_buffer,
|
||||
it->first, std::string("1"));
|
||||
|
||||
std::string::size_type len = connection.output_buffer.size() - base;
|
||||
connection.output_buffer[base + 4] = (len >> 8) & 0xff;
|
||||
connection.output_buffer[base + 5] = len & 0xff;
|
||||
break;
|
||||
}
|
||||
case FCGI_BEGIN_REQUEST: {
|
||||
if (content_length < sizeof(FCGI_BeginRequestBody))
|
||||
break;
|
||||
const FCGI_BeginRequestBody& body =
|
||||
*reinterpret_cast<const FCGI_BeginRequestBody*>(content);
|
||||
|
||||
if (!(body.flags & FCGI_KEEP_CONN))
|
||||
connection.close_responsibility = true;
|
||||
|
||||
unsigned role = (body.roleB1 << 8) + body.roleB0;
|
||||
if (role != FCGI_RESPONDER) {
|
||||
FCGI_EndRequestRecord unknown;
|
||||
bzero(&unknown, sizeof(unknown));
|
||||
unknown.header.version = FCGI_VERSION_1;
|
||||
unknown.header.type = FCGI_END_REQUEST;
|
||||
unknown.header.contentLengthB0 = sizeof(unknown.body);
|
||||
unknown.body.protocolStatus = FCGI_UNKNOWN_ROLE;
|
||||
connection.output_buffer.append(
|
||||
reinterpret_cast<const char*>(&unknown), sizeof(unknown));
|
||||
if (connection.close_responsibility)
|
||||
connection.close_socket = true;
|
||||
break;
|
||||
}
|
||||
|
||||
{
|
||||
RequestList::iterator it = connection.requests.find(request_id);
|
||||
if (it != connection.requests.end()) {
|
||||
delete it->second;
|
||||
connection.requests.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
RequestInfo* new_request = new RequestInfo;
|
||||
try {
|
||||
connection.requests.insert(RequestList::value_type(
|
||||
request_id, new_request));
|
||||
} catch (...) {
|
||||
delete new_request;
|
||||
throw;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case FCGI_ABORT_REQUEST: {
|
||||
RequestList::iterator it = connection.requests.find(request_id);
|
||||
if (it == connection.requests.end())
|
||||
break;
|
||||
|
||||
FCGI_EndRequestRecord aborted;
|
||||
bzero(&aborted, sizeof(aborted));
|
||||
aborted.header.version = FCGI_VERSION_1;
|
||||
aborted.header.type = FCGI_END_REQUEST;
|
||||
aborted.header.contentLengthB0 = sizeof(aborted.body);
|
||||
aborted.body.appStatusB0 = 1;
|
||||
aborted.body.protocolStatus = FCGI_REQUEST_COMPLETE;
|
||||
connection.output_buffer.append(
|
||||
reinterpret_cast<const char*>(&aborted), sizeof(aborted));
|
||||
if (connection.close_responsibility)
|
||||
connection.close_socket = true;
|
||||
|
||||
delete it->second;
|
||||
connection.requests.erase(it);
|
||||
break;
|
||||
}
|
||||
case FCGI_PARAMS: {
|
||||
RequestList::iterator it = connection.requests.find(request_id);
|
||||
if (it == connection.requests.end())
|
||||
break;
|
||||
|
||||
RequestInfo& request = *it->second;
|
||||
if (!request.params_closed)
|
||||
if (content_length != 0)
|
||||
request.params_buffer.append(content, content_length);
|
||||
else {
|
||||
request.params = parse_pairs(request.params_buffer.data(),
|
||||
request.params_buffer.size());
|
||||
request.params_buffer.clear();
|
||||
request.params_closed = true;
|
||||
|
||||
request.status = (*handle_request)(request);
|
||||
if (request.status == 0 && !request.in.empty()) {
|
||||
request.status = (*handle_data)(request);
|
||||
if (request.status == 0 && request.in_closed)
|
||||
request.status = (*handle_complete)(request);
|
||||
}
|
||||
process_write_request(connection, request_id, request);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case FCGI_STDIN: {
|
||||
RequestList::iterator it = connection.requests.find(request_id);
|
||||
if (it == connection.requests.end())
|
||||
break;
|
||||
|
||||
RequestInfo& request = *it->second;
|
||||
if (!request.in_closed)
|
||||
if (content_length != 0) {
|
||||
request.in.append(content, content_length);
|
||||
if (request.params_closed && request.status == 0) {
|
||||
request.status = (*handle_data)(request);
|
||||
process_write_request(connection, request_id, request);
|
||||
}
|
||||
} else {
|
||||
request.in_closed = true;
|
||||
if (request.params_closed && request.status == 0) {
|
||||
request.status = (*handle_complete)(request);
|
||||
process_write_request(connection, request_id, request);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case FCGI_DATA:
|
||||
break;
|
||||
default: {
|
||||
FCGI_UnknownTypeRecord unknown;
|
||||
bzero(&unknown, sizeof(unknown));
|
||||
unknown.header.version = FCGI_VERSION_1;
|
||||
unknown.header.type = FCGI_UNKNOWN_TYPE;
|
||||
unknown.header.contentLengthB0 = sizeof(unknown.body);
|
||||
unknown.body.type = header.type;
|
||||
connection.output_buffer.append(
|
||||
reinterpret_cast<const char*>(&unknown), sizeof(unknown));
|
||||
}
|
||||
}
|
||||
|
||||
n += FCGI_HEADER_LEN + content_length + header.paddingLength;
|
||||
}
|
||||
|
||||
connection.input_buffer.erase(0, n);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
FastCGIServer::process_write_request(Connection& connection, RequestID id,
|
||||
RequestInfo& request)
|
||||
{
|
||||
if (!request.out.empty()) {
|
||||
write_data(connection.output_buffer, id, request.out, FCGI_STDOUT);
|
||||
request.out.clear();
|
||||
}
|
||||
if (!request.err.empty()) {
|
||||
write_data(connection.output_buffer, id, request.err, FCGI_STDERR);
|
||||
request.err.clear();
|
||||
}
|
||||
if ((request.in_closed || request.status != 0) &&
|
||||
!request.output_closed) {
|
||||
write_data(connection.output_buffer, id, request.out, FCGI_STDOUT);
|
||||
write_data(connection.output_buffer, id, request.err, FCGI_STDERR);
|
||||
|
||||
FCGI_EndRequestRecord complete;
|
||||
bzero(&complete, sizeof(complete));
|
||||
complete.header.version = FCGI_VERSION_1;
|
||||
complete.header.type = FCGI_END_REQUEST;
|
||||
complete.header.requestIdB1 = (id >> 8) & 0xff;
|
||||
complete.header.requestIdB0 = id & 0xff;
|
||||
complete.header.contentLengthB0 = sizeof(complete.body);
|
||||
complete.body.appStatusB3 = (request.status >> 24) & 0xff;
|
||||
complete.body.appStatusB2 = (request.status >> 16) & 0xff;
|
||||
complete.body.appStatusB1 = (request.status >> 8) & 0xff;
|
||||
complete.body.appStatusB0 = request.status & 0xff;
|
||||
complete.body.protocolStatus = FCGI_REQUEST_COMPLETE;
|
||||
connection.output_buffer.append(
|
||||
reinterpret_cast<const char*>(&complete), sizeof(complete));
|
||||
if (connection.close_responsibility)
|
||||
connection.close_socket = true;
|
||||
|
||||
request.output_closed = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
FastCGIServer::process_connection_write(Connection& connection)
|
||||
{
|
||||
for (RequestList::iterator it = connection.requests.begin();
|
||||
it != connection.requests.end();) {
|
||||
process_write_request(connection, it->first, *it->second);
|
||||
if (it->second->params_closed && it->second->in_closed) {
|
||||
RequestInfo* request = it->second;
|
||||
connection.requests.erase(it++);
|
||||
delete request;
|
||||
} else
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
FastCGIServer::Pairs
|
||||
FastCGIServer::parse_pairs(const char* data, std::string::size_type n)
|
||||
{
|
||||
Pairs pairs;
|
||||
|
||||
const unsigned char* u = reinterpret_cast<const unsigned char*>(data);
|
||||
|
||||
for (std::string::size_type m = 0; m < n;) {
|
||||
std::string::size_type name_length, value_length;
|
||||
|
||||
if (u[m] >> 7) {
|
||||
if (n - m < 4)
|
||||
break;
|
||||
name_length = ((u[m] & 0x7f) << 24) + (u[m + 1] << 16) +
|
||||
(u[m + 2] << 8) + u[m + 3];
|
||||
m += 4;
|
||||
} else
|
||||
name_length = u[m++];
|
||||
if (m >= n)
|
||||
break;
|
||||
|
||||
if (u[m] >> 7) {
|
||||
if (n - m < 4)
|
||||
break;
|
||||
value_length = ((u[m] & 0x7f) << 24) + (u[m + 1] << 16) +
|
||||
(u[m + 2] << 8) + u[m + 3];
|
||||
m += 4;
|
||||
} else
|
||||
value_length = u[m++];
|
||||
|
||||
if (n - m < name_length)
|
||||
break;
|
||||
std::string key(data + m, name_length);
|
||||
m += name_length;
|
||||
|
||||
if (n - m < value_length)
|
||||
break;
|
||||
pairs.insert(Pairs::value_type(
|
||||
key, std::string(data + m, value_length)));
|
||||
m += value_length;
|
||||
}
|
||||
|
||||
return pairs;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
FastCGIServer::write_pair(std::string& buffer,
|
||||
const std::string& key, const std::string& value)
|
||||
{
|
||||
if (key.size() > 0x7f) {
|
||||
buffer.push_back(0x80 + ((key.size() >> 24) & 0x7f));
|
||||
buffer.push_back((key.size() >> 16) & 0xff);
|
||||
buffer.push_back((key.size() >> 8) & 0xff);
|
||||
buffer.push_back(key.size() & 0xff);
|
||||
} else
|
||||
buffer.push_back(key.size());
|
||||
|
||||
if (value.size() > 0x7f) {
|
||||
buffer.push_back(0x80 + ((value.size() >> 24) & 0x7f));
|
||||
buffer.push_back((value.size() >> 16) & 0xff);
|
||||
buffer.push_back((value.size() >> 8) & 0xff);
|
||||
buffer.push_back(value.size() & 0xff);
|
||||
} else
|
||||
buffer.push_back(value.size());
|
||||
|
||||
buffer.append(key);
|
||||
buffer.append(value);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
FastCGIServer::write_data(std::string& buffer, RequestID id,
|
||||
const std::string& input, unsigned char type)
|
||||
{
|
||||
FCGI_Header header;
|
||||
bzero(&header, sizeof(header));
|
||||
header.version = FCGI_VERSION_1;
|
||||
header.type = type;
|
||||
header.requestIdB1 = (id >> 8) & 0xff;
|
||||
header.requestIdB0 = id & 0xff;
|
||||
|
||||
for (std::string::size_type n = 0;;) {
|
||||
std::string::size_type written = std::min(input.size() - n,
|
||||
(std::string::size_type)0xffffu);
|
||||
|
||||
header.contentLengthB1 = written >> 8;
|
||||
header.contentLengthB0 = written & 0xff;
|
||||
header.paddingLength = (8 - (written % 8)) % 8;
|
||||
buffer.append(
|
||||
reinterpret_cast<const char*>(&header), sizeof(header));
|
||||
buffer.append(input.data() + n, written);
|
||||
buffer.append(header.paddingLength, 0);
|
||||
|
||||
n += written;
|
||||
if (n == input.size())
|
||||
break;
|
||||
}
|
||||
}
|
||||
150
src/fastcgi/src/fcgicc.h
Normal file
150
src/fastcgi/src/fcgicc.h
Normal file
@ -0,0 +1,150 @@
|
||||
/*
|
||||
* Copyright 2008, 2009 Andrey Zholos. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* 3. Neither the names of the copyright holders nor the names of contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is part of the FastCGI C++ Class library (fcgicc) version 0.1,
|
||||
* available at http://althenia.net/fcgicc
|
||||
*/
|
||||
|
||||
|
||||
#ifndef FCGICC_H
|
||||
#define FCGICC_H
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class FastCGIServer {
|
||||
public:
|
||||
FastCGIServer();
|
||||
~FastCGIServer();
|
||||
|
||||
// called when the parameters and standard input have been receieved
|
||||
void request_handler(int (* function)(FastCGIRequest&));
|
||||
template<class C>
|
||||
void request_handler(C& object, int (C::* function)(FastCGIRequest&)) {
|
||||
set_handler(handle_request, new Handler<C>(object, function));
|
||||
}
|
||||
|
||||
// called when new data appears on stdin
|
||||
void data_handler(int (* function)(FastCGIRequest&));
|
||||
template<class C>
|
||||
void data_handler(C& object, int (C::* function)(FastCGIRequest&)) {
|
||||
set_handler(handle_data, new Handler<C>(object, function));
|
||||
}
|
||||
|
||||
// called when the complete request has been received
|
||||
void complete_handler(int (* function)(FastCGIRequest&));
|
||||
template<class C>
|
||||
void complete_handler(C& object, int (C::* function)(FastCGIRequest&)) {
|
||||
set_handler(handle_complete, new Handler<C>(object, function));
|
||||
}
|
||||
|
||||
void listen(unsigned tcp_port);
|
||||
void listen(const std::string& local_path);
|
||||
void abandon_files();
|
||||
|
||||
void process(int timeout_ms = -1); // timeout_ms<0 blocks forever
|
||||
void process_forever();
|
||||
|
||||
protected:
|
||||
struct RequestInfo : FastCGIRequest {
|
||||
RequestInfo();
|
||||
|
||||
std::string params_buffer;
|
||||
bool params_closed;
|
||||
bool in_closed;
|
||||
int status;
|
||||
bool output_closed;
|
||||
|
||||
friend class FastCGIServer;
|
||||
};
|
||||
|
||||
typedef unsigned RequestID;
|
||||
typedef std::map<RequestID, RequestInfo*> RequestList;
|
||||
struct Connection {
|
||||
Connection();
|
||||
|
||||
RequestList requests;
|
||||
std::string input_buffer;
|
||||
std::string output_buffer;
|
||||
bool close_responsibility;
|
||||
bool close_socket;
|
||||
};
|
||||
|
||||
typedef std::map<std::string, std::string> Pairs;
|
||||
|
||||
std::vector<int> listen_sockets;
|
||||
std::vector<std::string> listen_unlink;
|
||||
|
||||
std::map<int, Connection*> read_sockets;
|
||||
|
||||
void process_connection_read(Connection&);
|
||||
static void process_write_request(Connection&, RequestID, RequestInfo&);
|
||||
static void process_connection_write(Connection&);
|
||||
static Pairs parse_pairs(const char*, std::string::size_type);
|
||||
static void write_pair(std::string& buffer,
|
||||
const std::string& key, const std::string&);
|
||||
static void write_data(std::string& buffer, RequestID id,
|
||||
const std::string& input, unsigned char type);
|
||||
|
||||
|
||||
struct HandlerBase {
|
||||
virtual int operator()(FastCGIRequest&);
|
||||
};
|
||||
|
||||
struct StaticHandler : public HandlerBase {
|
||||
StaticHandler(int (* p_function)(FastCGIRequest&)) :
|
||||
function(p_function) {}
|
||||
int operator()(FastCGIRequest& request) {
|
||||
return function(request);
|
||||
}
|
||||
|
||||
int (* function)(FastCGIRequest&);
|
||||
};
|
||||
|
||||
template<class C>
|
||||
struct Handler : public HandlerBase {
|
||||
Handler(C& p_object, int (C::* p_function)(FastCGIRequest&)) :
|
||||
object(p_object), function(p_function) {}
|
||||
int operator()(FastCGIRequest& request) {
|
||||
return (object.*function)(request);
|
||||
}
|
||||
|
||||
C& object;
|
||||
int (C::* function)(FastCGIRequest&);
|
||||
};
|
||||
|
||||
void set_handler(HandlerBase*&, HandlerBase*);
|
||||
|
||||
HandlerBase* handle_request;
|
||||
HandlerBase* handle_data;
|
||||
HandlerBase* handle_complete;
|
||||
};
|
||||
|
||||
#endif // !FCGICC_H
|
||||
5
src/fastcgi/test/CMakeLists.txt
Normal file
5
src/fastcgi/test/CMakeLists.txt
Normal file
@ -0,0 +1,5 @@
|
||||
ADD_EXECUTABLE( test1 test1.cc )
|
||||
TARGET_LINK_LIBRARIES( test1 fcgicc )
|
||||
ADD_EXECUTABLE( test2 test2.cc )
|
||||
TARGET_LINK_LIBRARIES( test2 fcgicc )
|
||||
INCLUDE_DIRECTORIES( ${PROJECT_SOURCE_DIR}/src )
|
||||
12
src/fastcgi/test/lighttpd.conf
Normal file
12
src/fastcgi/test/lighttpd.conf
Normal file
@ -0,0 +1,12 @@
|
||||
server.bind = "127.0.0.1"
|
||||
server.port = 8080
|
||||
server.modules = ( "mod_fastcgi" )
|
||||
server.document-root = "."
|
||||
|
||||
fastcgi.server = (
|
||||
"/" => ((
|
||||
"host" => "127.0.0.1",
|
||||
"port" => 7000,
|
||||
"check-local" => "disable"
|
||||
))
|
||||
)
|
||||
102
src/fastcgi/test/test1.cc
Normal file
102
src/fastcgi/test/test1.cc
Normal file
@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright 2008, 2009 Andrey Zholos. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* 3. Neither the names of the copyright holders nor the names of contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is part of the FastCGI C++ Class library (fcgicc) version 0.1,
|
||||
* available at http://althenia.net/fcgicc
|
||||
*/
|
||||
|
||||
|
||||
#include <fcgicc.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
|
||||
|
||||
int handle_request(FastCGIRequest& request) {
|
||||
// This is always the first event to occur. It occurs when the
|
||||
// server receives all parameters. There may be more data coming on the
|
||||
// standard input stream.
|
||||
|
||||
if (request.params.count("REQUEST_URI"))
|
||||
return 0; // OK, continue processing
|
||||
else
|
||||
return 1; // Stop processing and return error code
|
||||
}
|
||||
|
||||
int handle_data(FastCGIRequest& request) {
|
||||
// This event occurs when data is received on the standard input stream.
|
||||
// A simple string is used to hold the input stream, so it is the
|
||||
// responsibility of the application to remember which data it has
|
||||
// processed. The application may modify it; new data will be appended
|
||||
// to it by the server. The same goes for the output and error streams:
|
||||
// the application should append data to them; the server will remove
|
||||
// all sent data from them.
|
||||
|
||||
std::transform(request.in.begin(), request.in.end(),
|
||||
std::back_inserter(request.err),
|
||||
std::bind1st(std::plus<char>(), 1));
|
||||
request.in.clear(); // don't process it again
|
||||
return 0; // still OK
|
||||
}
|
||||
|
||||
class Application {
|
||||
public:
|
||||
int handle_complete(FastCGIRequest& request) {
|
||||
// The event handler can also be a class member function. This
|
||||
// event occurs when the parameters and standard input streams are
|
||||
// both closed, and thus the request is complete.
|
||||
|
||||
request.out.append("Content-Type: text/plain\r\n\r\n");
|
||||
request.out.append("You requested: ");
|
||||
request.out.append(request.params[std::string("REQUEST_URI")]);
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
int main() {
|
||||
Application application;
|
||||
|
||||
FastCGIServer server; // Instantiate a server
|
||||
|
||||
// Set up our request handlers
|
||||
server.request_handler(&handle_request);
|
||||
server.data_handler(&handle_data);
|
||||
server.complete_handler(application, &Application::handle_complete);
|
||||
|
||||
server.listen(7000); // Listen on a TCP port
|
||||
server.listen(7001); // ... or on two
|
||||
server.listen("./socket"); // ... and also on a local doman socket
|
||||
|
||||
server.process(100); // Process some data, but don't wait more
|
||||
// than 100 ms for it to arrive.
|
||||
server.process(); // Process some data with no timeout
|
||||
server.process_forever(); // Process everything
|
||||
|
||||
return 0;
|
||||
}
|
||||
406
src/fastcgi/test/test2.cc
Normal file
406
src/fastcgi/test/test2.cc
Normal file
@ -0,0 +1,406 @@
|
||||
/*
|
||||
* Copyright 2008, 2009 Andrey Zholos. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* 3. Neither the names of the copyright holders nor the names of contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is part of the FastCGI C++ Class library (fcgicc) version 0.1,
|
||||
* available at http://althenia.net/fcgicc
|
||||
*/
|
||||
|
||||
/*
|
||||
|
||||
$ ./test2
|
||||
|
||||
This starts a simple FastCGI server on ports 7000-7009. It responds to HTTP
|
||||
requests from a server such as lighttpd with the provided lighttpd.conf. It
|
||||
also responds with a particular transformation of standard input.
|
||||
|
||||
$ ./test2 -c
|
||||
|
||||
This acts as a client by sending multiple concurrent data requests to the
|
||||
handler on ports 7000 through 7009 and validates the responses.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
#include <fcgicc.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
#include <fastcgi.h>
|
||||
|
||||
|
||||
static const int base_port = 7000;
|
||||
static const int processes = 25;
|
||||
static const int requests = 1000;
|
||||
|
||||
static const std::string param_rot13("ROT13");
|
||||
|
||||
|
||||
struct Rot13 : public std::unary_function<char, char> {
|
||||
char operator() (char c) const {
|
||||
if (c >= 'a' && c <= 'm' || c >= 'A' && c <= 'M')
|
||||
return c + 13;
|
||||
if (c >= 'n' && c <= 'z' || c >= 'N' && c <= 'Z')
|
||||
return c - 13;
|
||||
return c;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
struct RandomChar {
|
||||
char operator()() const {
|
||||
return rand() % 256;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
struct Handler {
|
||||
int handle_request(FastCGIRequest& request)
|
||||
{
|
||||
static const std::string request_uri("REQUEST_URI");
|
||||
FastCGIRequest::Params::const_iterator it =
|
||||
request.params.find(request_uri);
|
||||
if (it != request.params.end()) {
|
||||
request.out.append("Content-Type: text/html\r\n\r\n"
|
||||
"<html><body><h3>FastCGI C++ Class (fcgicc) test</h3>"
|
||||
"<p>Request: ");
|
||||
request.out.append(it->second);
|
||||
request.out.append("</p></body></html>\n");
|
||||
} else {
|
||||
it = request.params.find(param_rot13);
|
||||
if (it != request.params.end())
|
||||
std::transform(it->second.begin(), it->second.end(),
|
||||
std::back_inserter(request.out), Rot13());
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
int handle_data(FastCGIRequest& request)
|
||||
{
|
||||
std::transform(request.in.begin(), request.in.end(),
|
||||
std::back_inserter(request.out), Rot13());
|
||||
request.in.clear();
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void server()
|
||||
{
|
||||
Handler handler;
|
||||
|
||||
FastCGIServer server;
|
||||
server.request_handler(handler, &Handler::handle_request);
|
||||
server.data_handler(&handle_data);
|
||||
for (int i = 0; i < 10; i++)
|
||||
server.listen(base_port + i);
|
||||
server.process_forever();
|
||||
}
|
||||
|
||||
|
||||
struct Querier {
|
||||
int socket;
|
||||
|
||||
void write(const char* data, size_t length) {
|
||||
for (;;) {
|
||||
int result = ::write(socket, data, length);
|
||||
if (result == length)
|
||||
return;
|
||||
if (result <= 0)
|
||||
throw std::runtime_error("write() failed in data client");
|
||||
data += result;
|
||||
length -= result;
|
||||
}
|
||||
}
|
||||
|
||||
bool read(std::string& output) {
|
||||
char buf[4096];
|
||||
int result = ::read(socket, buf, sizeof(buf));
|
||||
if (result == -1)
|
||||
throw std::runtime_error("read() failed in data client");
|
||||
if (result == 0)
|
||||
return false;
|
||||
output.append(buf, result);
|
||||
return true;
|
||||
}
|
||||
|
||||
void write_stream(int type, const std::string& stream, size_t& i) {
|
||||
size_t n = std::min(stream.size() - i, (std::string::size_type)65535);
|
||||
if (n >= 256 || n > 1 && rand() % 3 != 0)
|
||||
n = rand() % (n - 1) + 1;
|
||||
|
||||
size_t m = i == stream.size() || rand() % 3 != 0 ? 0 :
|
||||
rand() % std::min(stream.size() - i, (std::string::size_type)63);
|
||||
char padding[m];
|
||||
bzero(padding, m);
|
||||
|
||||
FCGI_Header header;
|
||||
bzero(&header, sizeof(header));
|
||||
header.version = FCGI_VERSION_1;
|
||||
header.type = type;
|
||||
header.contentLengthB1 = n / 256;
|
||||
header.contentLengthB0 = n % 256;
|
||||
header.paddingLength = m;
|
||||
|
||||
write(reinterpret_cast<const char*>(&header), sizeof(header));
|
||||
write(stream.data() + i, n);
|
||||
write(padding, m);
|
||||
i += n;
|
||||
}
|
||||
|
||||
void encode_size(std::string& params, size_t n) {
|
||||
if (n >> 7 == 0)
|
||||
params.push_back(static_cast<char>(n));
|
||||
else {
|
||||
char c[4];
|
||||
c[0] = static_cast<char>(n >> 24 | 0x80);
|
||||
c[1] = static_cast<char>(n >> 16);
|
||||
c[2] = static_cast<char>(n >> 8);
|
||||
c[3] = static_cast<char>(n);
|
||||
params.append(c, 4);
|
||||
}
|
||||
}
|
||||
|
||||
void random_params(std::string& params) {
|
||||
int i = rand() % 15;
|
||||
if (i > 10)
|
||||
i = 0;
|
||||
for (; i >= 0; i--) {
|
||||
size_t m = rand() % 300, n = rand() % 700;
|
||||
encode_size(params, m + 1);
|
||||
encode_size(params, n);
|
||||
params.push_back('_');
|
||||
std::generate_n(std::back_inserter(params), m + n, RandomChar());
|
||||
}
|
||||
}
|
||||
|
||||
void process() {
|
||||
for (int i = 0; i < requests; i++) {
|
||||
// Generate random request and send it either as a special parameter
|
||||
// or as the standard input stream. Pad with random parameters.
|
||||
std::string params, in, request;
|
||||
|
||||
std::generate_n(std::back_inserter(request),
|
||||
rand() % 10000, RandomChar());
|
||||
|
||||
random_params(params);
|
||||
if (rand() % 3 == 0)
|
||||
in.append(request);
|
||||
else {
|
||||
encode_size(params, param_rot13.size());
|
||||
encode_size(params, request.size());
|
||||
params.append(param_rot13);
|
||||
params.append(request);
|
||||
}
|
||||
random_params(params);
|
||||
|
||||
// Connect to the server
|
||||
socket = ::socket(PF_INET, SOCK_STREAM, 0);
|
||||
if (socket == -1)
|
||||
throw std::runtime_error("socket() failed in data client");
|
||||
|
||||
struct sockaddr_in sa;
|
||||
bzero(&sa, sizeof(sa));
|
||||
sa.sin_family = AF_INET;
|
||||
sa.sin_port = htons(base_port + rand() % 10);
|
||||
sa.sin_addr.s_addr = htonl(0x7f000001);
|
||||
if (::connect(socket, (struct sockaddr*)&sa, sizeof(sa)) == -1)
|
||||
throw std::runtime_error("connect() failed in data client");
|
||||
|
||||
static FCGI_BeginRequestRecord begin;
|
||||
bzero(&begin, sizeof(begin));
|
||||
begin.header.version = FCGI_VERSION_1;
|
||||
begin.header.type = FCGI_BEGIN_REQUEST;
|
||||
begin.header.contentLengthB0 = sizeof(begin.body);
|
||||
begin.body.roleB0 = FCGI_RESPONDER;
|
||||
if (rand() % 3 == 0)
|
||||
begin.body.flags = FCGI_KEEP_CONN;
|
||||
write(reinterpret_cast<const char*>(&begin), sizeof(begin));
|
||||
|
||||
// Send streams in random chunks
|
||||
size_t in_i = 0, params_i = 0;
|
||||
while (in_i < in.size() || params_i < params.size()) {
|
||||
if (in_i == in.size() ||
|
||||
params_i < params.size() && rand() % 3 == 0)
|
||||
write_stream(FCGI_PARAMS, params, params_i);
|
||||
else
|
||||
write_stream(FCGI_STDIN, in, in_i);
|
||||
}
|
||||
if (rand() % 3 == 0) {
|
||||
write_stream(FCGI_PARAMS, params, params_i);
|
||||
write_stream(FCGI_STDIN, in, in_i);
|
||||
} else {
|
||||
write_stream(FCGI_STDIN, in, in_i);
|
||||
write_stream(FCGI_PARAMS, params, params_i);
|
||||
}
|
||||
|
||||
// Sometimes close our end of the socket
|
||||
if (rand() % 5 == 0)
|
||||
if (::shutdown(socket, SHUT_WR) == -1)
|
||||
throw std::runtime_error("shutdown() failed "
|
||||
"in data client");
|
||||
|
||||
// Receive and verify results
|
||||
std::string output;
|
||||
std::string out, err;
|
||||
bool closed_out = false, closed_err = false;
|
||||
while (read(output)) {
|
||||
another_record:
|
||||
if (output.size() < sizeof(FCGI_Header))
|
||||
continue;
|
||||
const FCGI_Header& header =
|
||||
*reinterpret_cast<const FCGI_Header*>(output.data());
|
||||
|
||||
if (header.version != FCGI_VERSION_1)
|
||||
throw std::runtime_error("received: incorrect version");
|
||||
|
||||
size_t content = (header.contentLengthB1 << 8) +
|
||||
header.contentLengthB0;
|
||||
size_t padding = header.paddingLength;
|
||||
if (output.size() - sizeof(FCGI_Header) < content + padding)
|
||||
continue;
|
||||
|
||||
switch (header.type) {
|
||||
case FCGI_STDOUT:
|
||||
if (closed_out)
|
||||
throw std::runtime_error("received: "
|
||||
"data on closed stream");
|
||||
out.append(output.data() + sizeof(FCGI_Header), content);
|
||||
if (content == 0)
|
||||
closed_out = true;
|
||||
break;
|
||||
case FCGI_STDERR:
|
||||
if (closed_err)
|
||||
throw std::runtime_error("received: "
|
||||
"data on closed stream");
|
||||
err.append(output.data() + sizeof(FCGI_Header), content);
|
||||
if (content == 0)
|
||||
closed_err = true;
|
||||
break;
|
||||
case FCGI_END_REQUEST: {
|
||||
if (!closed_out || !closed_err)
|
||||
throw std::runtime_error("received: "
|
||||
"streams not closed");
|
||||
if (output.size() - sizeof(FCGI_Header) <
|
||||
sizeof(FCGI_EndRequestBody))
|
||||
continue;
|
||||
const FCGI_EndRequestBody& body =
|
||||
*reinterpret_cast<const FCGI_EndRequestBody*>(
|
||||
output.data() + sizeof(FCGI_Header));
|
||||
if ((body.appStatusB3 | body.appStatusB2 |
|
||||
body.appStatusB1 | body.appStatusB0) != 0)
|
||||
throw std::runtime_error("received: bad exit code");
|
||||
if (body.protocolStatus != FCGI_REQUEST_COMPLETE)
|
||||
throw std::runtime_error("received: bad status");
|
||||
if (content + padding !=
|
||||
output.size() - sizeof(FCGI_Header))
|
||||
throw std::runtime_error("received: extra data");
|
||||
goto request_complete;
|
||||
}
|
||||
default:
|
||||
throw std::runtime_error("received: unexpected type");
|
||||
}
|
||||
|
||||
output.erase(0, sizeof(FCGI_Header) + content + padding);
|
||||
goto another_record;
|
||||
}
|
||||
throw std::runtime_error("received: not enough data");
|
||||
|
||||
request_complete:
|
||||
if (::close(socket) == -1 && errno != ECONNRESET)
|
||||
throw std::runtime_error("close() failed in data client");
|
||||
|
||||
if (!err.empty())
|
||||
throw std::runtime_error("received: incorrect stderr");
|
||||
std::string result;
|
||||
std::transform(out.begin(), out.end(),
|
||||
std::back_inserter(result), Rot13());
|
||||
if (result != request)
|
||||
throw std::runtime_error("received: incorrect stdout");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
void client()
|
||||
{
|
||||
for (int i = 0; i < processes; i++) {
|
||||
switch (::fork()) {
|
||||
case -1:
|
||||
throw std::runtime_error("fork() failed");
|
||||
case 0:
|
||||
::srand(i);
|
||||
Querier querier;
|
||||
querier.process();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < processes; i++) {
|
||||
int status;
|
||||
if (::wait(&status) == -1)
|
||||
throw std::runtime_error("wait() failed");
|
||||
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
|
||||
throw std::runtime_error("data client failed");
|
||||
}
|
||||
std::cout << "Success!\n";
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, const char* argv[])
|
||||
{
|
||||
try {
|
||||
static const std::string arg_client("-c");
|
||||
for (int i = 1; i < argc; i++)
|
||||
if (argv[i] == arg_client) {
|
||||
client();
|
||||
return 0;
|
||||
}
|
||||
|
||||
server();
|
||||
return 0;
|
||||
|
||||
} catch (std::exception& e) {
|
||||
std::cerr << "Error: " << e.what() << ".\n";
|
||||
} catch (...) {
|
||||
std::cerr << "Error.\n";
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
0
src/lib/.fuse_hidden0000013000000001
Normal file
0
src/lib/.fuse_hidden0000013000000001
Normal file
252
src/lib/compiler.h
Normal file
252
src/lib/compiler.h
Normal file
@ -0,0 +1,252 @@
|
||||
#include <dlfcn.h>
|
||||
|
||||
#define RENDER() extern "C" void render()
|
||||
|
||||
typedef void (*call_handler)();
|
||||
typedef void (*request_handler)(Request* request);
|
||||
|
||||
struct SharedUnit {
|
||||
string file_name;
|
||||
string so_name;
|
||||
void* so_handle;
|
||||
request_handler on_setup;
|
||||
call_handler on_render;
|
||||
string compiler_messages;
|
||||
time_t last_compiled;
|
||||
|
||||
~SharedUnit()
|
||||
{
|
||||
if(so_handle)
|
||||
{
|
||||
dlclose(so_handle);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
std::map<string, SharedUnit*> units;
|
||||
|
||||
string process_html_literal(string content, string file_name)
|
||||
{
|
||||
string pc;
|
||||
string HT_START = "context->print(R\"(";
|
||||
string HT_END = ")\");";
|
||||
|
||||
u8 mode = 0;
|
||||
bool inside_quote = false;
|
||||
string code_buffer = "";
|
||||
bool is_field = false;
|
||||
|
||||
for(u32 i = 0; i < content.length(); i++)
|
||||
{
|
||||
char c = content[i];
|
||||
if(mode == 1)
|
||||
{
|
||||
if(c == '\"')
|
||||
{
|
||||
inside_quote = !inside_quote;
|
||||
code_buffer.append(1, c);
|
||||
}
|
||||
else if(c == '?' && content[i+1] == '>')
|
||||
{
|
||||
mode = 0;
|
||||
i += 1;
|
||||
if(is_field)
|
||||
{
|
||||
pc.append(
|
||||
HT_END +
|
||||
"echo(html_escape( " +
|
||||
code_buffer +
|
||||
" )); " +
|
||||
HT_START
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
pc.append(HT_END + code_buffer + HT_START);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
code_buffer.append(1, c);
|
||||
}
|
||||
}
|
||||
else if(!inside_quote && c == '<' && content[i+1] == '?')
|
||||
{
|
||||
code_buffer = "";
|
||||
if(content[i+2] == '=')
|
||||
{
|
||||
is_field = true;
|
||||
i += 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
is_field = false;
|
||||
i += 1;
|
||||
}
|
||||
mode = 1;
|
||||
}
|
||||
else if(c == '\"')
|
||||
{
|
||||
inside_quote = !inside_quote;
|
||||
pc.append(1, c);
|
||||
}
|
||||
else
|
||||
{
|
||||
pc.append(1, c);
|
||||
}
|
||||
}
|
||||
|
||||
return(HT_START + pc + HT_END);
|
||||
}
|
||||
|
||||
string preprocess(string content, string file_name)
|
||||
{
|
||||
string pc = "#include \""+get_cwd()+"/src/lib/uce_gen.h\" \n";
|
||||
string token = "";
|
||||
string html_buffer = "";
|
||||
u8 mode = 0;
|
||||
bool inside_quote = false;
|
||||
for(u32 i = 0; i < content.length(); i++)
|
||||
{
|
||||
char c = content[i];
|
||||
if(mode == 2)
|
||||
{
|
||||
auto end_pos = content.find(string("</")+token+">", i);
|
||||
if(end_pos != string::npos)
|
||||
{
|
||||
u32 len = token.length() + 3 + end_pos - i;
|
||||
html_buffer.append(content.substr(i, len));
|
||||
i += len - 1;
|
||||
pc.append(process_html_literal(html_buffer, file_name));
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("(!) unterminated HTML literal <%s> in %s", token.c_str(), file_name.c_str());
|
||||
}
|
||||
mode = 0;
|
||||
}
|
||||
else if(mode == 1)
|
||||
{
|
||||
if(isspace(c) || c == '>')
|
||||
mode = 2;
|
||||
else
|
||||
token.append(1, c);
|
||||
html_buffer.append(1, c);
|
||||
}
|
||||
else if(!inside_quote && c == '<' && isalpha(content[i+1]))
|
||||
{
|
||||
mode = 1;
|
||||
token = "";
|
||||
html_buffer = "";
|
||||
html_buffer.append(1, c);
|
||||
}
|
||||
else if(c == '\"')
|
||||
{
|
||||
inside_quote = !inside_quote;
|
||||
pc.append(1, c);
|
||||
}
|
||||
else
|
||||
{
|
||||
pc.append(1, c);
|
||||
}
|
||||
}
|
||||
return(pc);
|
||||
}
|
||||
|
||||
void load_shared_unit(SharedUnit* su)
|
||||
{
|
||||
su->on_render = 0;
|
||||
su->so_handle = dlopen(su->so_name.c_str(), RTLD_NOW);
|
||||
if(su->so_handle)
|
||||
{
|
||||
su->last_compiled = file_mtime(su->so_name);
|
||||
char *error;
|
||||
su->on_setup = (request_handler)dlsym(su->so_handle, "set_current_request");
|
||||
su->on_render = (call_handler)dlsym(su->so_handle, "render");
|
||||
if ((error = dlerror()) != NULL)
|
||||
printf("Error - %s in %s\n", error, su->file_name.c_str());
|
||||
else
|
||||
printf("(i) loaded unit %s\n", su->file_name.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("Error loading unit %s, could not open %s\n", su->file_name.c_str(), su->so_name.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
SharedUnit* compile_shared_unit(string file_name)
|
||||
{
|
||||
SharedUnit* su = new SharedUnit();
|
||||
|
||||
string src_path = dirname(file_name);
|
||||
string src_fn = basename(file_name);
|
||||
string dest_path = Config.BIN_DIRECTORY + src_path;
|
||||
string pp_fn = src_fn + ".cpp";
|
||||
string so_fn = src_fn + ".so";
|
||||
|
||||
su->file_name = file_name;
|
||||
su->so_name = dest_path + "/" + so_fn;
|
||||
|
||||
string pc = preprocess(file_get_contents(file_name), file_name);
|
||||
file_put_contents(dest_path + "/" + pp_fn, pc);
|
||||
|
||||
su->compiler_messages = trim(shell_exec(Config.COMPILE_SCRIPT+" "+
|
||||
shell_escape(src_path)+" "+
|
||||
shell_escape(dest_path)+" "+
|
||||
shell_escape(src_fn)+" "+
|
||||
shell_escape(pp_fn)+" "+
|
||||
shell_escape(so_fn)
|
||||
));
|
||||
|
||||
if(su->compiler_messages.length() > 0)
|
||||
printf("%s \n", su->compiler_messages.c_str());
|
||||
|
||||
printf("(i) compiled unit %s\n", file_name.c_str());
|
||||
|
||||
load_shared_unit(su);
|
||||
|
||||
return(su);
|
||||
}
|
||||
|
||||
SharedUnit* get_shared_unit(string file_name)
|
||||
{
|
||||
SharedUnit* su = units[file_name];
|
||||
if(su && su->last_compiled < file_mtime(file_name))
|
||||
{
|
||||
delete su;
|
||||
su = 0;
|
||||
}
|
||||
if(!su)
|
||||
{
|
||||
su = units[file_name] = compile_shared_unit(file_name);
|
||||
}
|
||||
return(su);
|
||||
}
|
||||
|
||||
void invoke(Request* req, string file_name)
|
||||
{
|
||||
auto su = get_shared_unit(file_name);
|
||||
if(!su)
|
||||
{
|
||||
printf("Error loading unit %s\n", su->file_name.c_str());
|
||||
}
|
||||
else if(!su->on_render)
|
||||
{
|
||||
req->print("Compiler error: "+su->compiler_messages);
|
||||
}
|
||||
else
|
||||
{
|
||||
if(su->compiler_messages.length() > 0)
|
||||
req->print(su->compiler_messages);
|
||||
else
|
||||
{
|
||||
su->on_setup(req);
|
||||
su->on_render();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void invoke(string file_name)
|
||||
{
|
||||
return(invoke(context, file_name));
|
||||
}
|
||||
114
src/lib/datastructures.h
Normal file
114
src/lib/datastructures.h
Normal file
@ -0,0 +1,114 @@
|
||||
string var_dump(StringMap map, string prefix = "", string postfix = "\n")
|
||||
{
|
||||
string result = "";
|
||||
|
||||
for (auto it = map.begin(); it != map.end(); ++it)
|
||||
{
|
||||
result.append(prefix + it->first + ": " + it->second + postfix);
|
||||
}
|
||||
|
||||
return(result);
|
||||
}
|
||||
|
||||
u8 char_to_u8(char input)
|
||||
{
|
||||
if(input >= '0' && input <= '9')
|
||||
return input - '0';
|
||||
if(input >= 'A' && input <= 'F')
|
||||
return input - 'A' + 10;
|
||||
if(input >= 'a' && input <= 'f')
|
||||
return input - 'a' + 10;
|
||||
return(0);
|
||||
}
|
||||
|
||||
u8 hex_to_u8(string src)
|
||||
{
|
||||
return(char_to_u8(src[0])*16 + char_to_u8(src[1]));
|
||||
}
|
||||
|
||||
string trim(string raw)
|
||||
{
|
||||
u32 len = raw.length();
|
||||
u32 start_pos = 0;
|
||||
u32 end_pos = len - 1;
|
||||
if(len == 0 || (len == 1 && isspace(raw[0])))
|
||||
return("");
|
||||
while(start_pos < len && isspace(raw[start_pos]))
|
||||
start_pos++;
|
||||
while(end_pos >= 0 && isspace(raw[end_pos]))
|
||||
end_pos--;
|
||||
if(end_pos < start_pos)
|
||||
return("");
|
||||
return(raw.substr(start_pos, 1 + end_pos - start_pos));
|
||||
}
|
||||
|
||||
StringList explode(string str, string delim = "\n")
|
||||
{
|
||||
StringList result;
|
||||
int start = 0;
|
||||
int end = str.find(delim);
|
||||
while (end != -1)
|
||||
{
|
||||
result.push_back(str.substr(start, end - start));
|
||||
start = end + delim.size();
|
||||
end = str.find(delim, start);
|
||||
}
|
||||
result.push_back(str.substr(start, end - start));
|
||||
}
|
||||
|
||||
string implode(StringList l, string delim = "\n")
|
||||
{
|
||||
string result;
|
||||
for(string &s : l)
|
||||
{
|
||||
if(result.length() > 0)
|
||||
result.append(delim+s);
|
||||
result.append(s);
|
||||
}
|
||||
return(result);
|
||||
}
|
||||
|
||||
string html_escape(string s)
|
||||
{
|
||||
string result;
|
||||
|
||||
for(u32 i = 0; i < s.length(); i++)
|
||||
{
|
||||
char c = s[i];
|
||||
switch(c)
|
||||
{
|
||||
case('&'):
|
||||
result.append("&");
|
||||
break;
|
||||
case('<'):
|
||||
result.append("<");
|
||||
break;
|
||||
case('>'):
|
||||
result.append(">");
|
||||
break;
|
||||
case('"'):
|
||||
result.append(""");
|
||||
break;
|
||||
default:
|
||||
result.append(1, c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return(result);
|
||||
}
|
||||
|
||||
string html_escape(u64 a)
|
||||
{
|
||||
return(to_string(a));
|
||||
}
|
||||
|
||||
string html_escape(f64 a)
|
||||
{
|
||||
return(to_string(a));
|
||||
}
|
||||
|
||||
u64 int_val(string s, u32 base = 10)
|
||||
{
|
||||
return(strtoll(s.c_str(), 0, base));
|
||||
}
|
||||
73
src/lib/dtree.h
Normal file
73
src/lib/dtree.h
Normal file
@ -0,0 +1,73 @@
|
||||
struct DNode {
|
||||
|
||||
char type = 'S';
|
||||
|
||||
string _string;
|
||||
f64 _float;
|
||||
bool _bool;
|
||||
void* _ptr;
|
||||
std::map<string, DNode*> _map;
|
||||
|
||||
string get_string()
|
||||
{
|
||||
switch(type)
|
||||
{
|
||||
case('S'):
|
||||
return(_string);
|
||||
break;
|
||||
case('F'):
|
||||
return(to_string(_float));
|
||||
break;
|
||||
case('B'):
|
||||
return(_bool ? "true" : "false");
|
||||
break;
|
||||
case('M'):
|
||||
return("array()");
|
||||
break;
|
||||
case('P'):
|
||||
return("pointer()");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
string set_string(string s)
|
||||
{
|
||||
if(type == 'M') _map.clear();
|
||||
type = 'S';
|
||||
_string = s;
|
||||
}
|
||||
|
||||
string set_pointer(void* p)
|
||||
{
|
||||
if(type == 'M') _map.clear();
|
||||
type = 'P';
|
||||
_ptr = p;
|
||||
}
|
||||
|
||||
string set_float(f64 f)
|
||||
{
|
||||
if(type == 'M') _map.clear();
|
||||
type = 'F';
|
||||
_float = f;
|
||||
}
|
||||
|
||||
string set_bool(bool b)
|
||||
{
|
||||
if(type == 'M') _map.clear();
|
||||
type = 'B';
|
||||
_bool = b;
|
||||
}
|
||||
|
||||
DNode* set(string s)
|
||||
{
|
||||
type = 'M';
|
||||
auto child = _map[s];
|
||||
if(!child)
|
||||
{
|
||||
child = new DNode();
|
||||
_map[s] = child;
|
||||
}
|
||||
return(child);
|
||||
}
|
||||
|
||||
};
|
||||
10
src/lib/settings.h
Normal file
10
src/lib/settings.h
Normal file
@ -0,0 +1,10 @@
|
||||
struct Settings {
|
||||
|
||||
string BIN_DIRECTORY = "work";
|
||||
string COMPILE_SCRIPT = "scripts/compile";
|
||||
string LIT_ESC = "3d5b5_1";
|
||||
string CONTENT_TYPE = "text/html; charset=utf-8";
|
||||
string SOCKET_PATH = "/run/uce.sock";
|
||||
u32 LISTEN_PORT = 9993;
|
||||
|
||||
} Config;
|
||||
112
src/lib/sys.h
Normal file
112
src/lib/sys.h
Normal file
@ -0,0 +1,112 @@
|
||||
string shell_exec(string cmd)
|
||||
{
|
||||
string data;
|
||||
FILE * stream;
|
||||
const int max_buffer = 256;
|
||||
char buffer[max_buffer];
|
||||
cmd.append(" 2>&1");
|
||||
|
||||
stream = popen(cmd.c_str(), "r");
|
||||
|
||||
if (stream) {
|
||||
while (!feof(stream))
|
||||
if (fgets(buffer, max_buffer, stream) != NULL) data.append(buffer);
|
||||
pclose(stream);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
string shell_escape(string raw)
|
||||
{
|
||||
// FIXME
|
||||
return("\"" + raw + "\"");
|
||||
/*
|
||||
` U+0060 (Grave Accent) Backtick Command substitution
|
||||
~ U+007E Tilde Tilde expansion
|
||||
! U+0021 Exclamation mark History expansion
|
||||
# U+0023 Number sign Hash Comments
|
||||
$ U+0024 Dollar sign Parameter expansion
|
||||
& U+0026 Ampersand Background commands
|
||||
* U+002A Asterisk Filename expansion and globbing
|
||||
( U+0028 Left Parenthesis Subshells
|
||||
) U+0029 Right Parenthesis Subshells
|
||||
U+0009 Tab (⇥) Word splitting (whitespace)
|
||||
{ U+007B Left Curly Bracket Left brace Brace expansion
|
||||
[ U+005B Left Square Bracket Filename expansion and globbing
|
||||
| U+007C Vertical Line Vertical bar Pipelines
|
||||
\ U+005C Reverse Solidus Backslash Escape character
|
||||
; U+003B Semicolon Separating commands
|
||||
' U+0027 Apostrophe Single quote String quoting
|
||||
" U+0022 Quotation Mark Double quote String quoting with interpolation
|
||||
↩ U+000A Line Feed Newline Line break
|
||||
< U+003C Less than Input redirection
|
||||
> U+003E Greater than Output redirection
|
||||
? U+003F Question mark Filename expansion and globbing
|
||||
U+0020 Space Word splitting1 (whitespace)
|
||||
*/
|
||||
}
|
||||
|
||||
string basename(string fn)
|
||||
{
|
||||
return(trim(shell_exec("basename "+shell_escape(fn))));
|
||||
}
|
||||
|
||||
string dirname(string fn)
|
||||
{
|
||||
return(trim(shell_exec("dirname "+shell_escape(fn))));
|
||||
}
|
||||
|
||||
bool file_exists(string path)
|
||||
{
|
||||
std::filesystem::path fp{ path };
|
||||
return(std::filesystem::exists(fp));
|
||||
}
|
||||
|
||||
string file_get_contents(string file_name)
|
||||
{
|
||||
try
|
||||
{
|
||||
std::ifstream ifs(file_name);
|
||||
string content(
|
||||
(std::istreambuf_iterator<char>(ifs) ),
|
||||
(std::istreambuf_iterator<char>() ) );
|
||||
return(content);
|
||||
}
|
||||
catch(std::exception e)
|
||||
{
|
||||
return("");
|
||||
}
|
||||
}
|
||||
|
||||
bool file_put_contents(string file_name, string content)
|
||||
{
|
||||
try
|
||||
{
|
||||
std::ofstream out(file_name);
|
||||
out << content;
|
||||
out.close();
|
||||
return(true);
|
||||
}
|
||||
catch(std::exception e)
|
||||
{
|
||||
return(false);
|
||||
}
|
||||
}
|
||||
|
||||
string get_cwd()
|
||||
{
|
||||
return(std::filesystem::current_path());
|
||||
}
|
||||
|
||||
time_t file_mtime(string file_name)
|
||||
{
|
||||
struct stat info;
|
||||
if (stat(file_name.c_str(), &info) != 0)
|
||||
{
|
||||
printf("file_mtime(%s) error\n", file_name.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
return(info.st_mtime);
|
||||
}
|
||||
}
|
||||
33
src/lib/time.h
Normal file
33
src/lib/time.h
Normal file
@ -0,0 +1,33 @@
|
||||
f64 microtime()
|
||||
{
|
||||
return ((f64)std::chrono::duration_cast<std::chrono::microseconds>(
|
||||
std::chrono::high_resolution_clock::now().time_since_epoch()).count()) / 1000000;
|
||||
}
|
||||
|
||||
u64 time()
|
||||
{
|
||||
return(std::time(0));
|
||||
}
|
||||
|
||||
string date(string format = "", u64 timestamp = 0)
|
||||
{
|
||||
string ts;
|
||||
string fmt;
|
||||
if(timestamp > 0) ts = string("-d '@")+to_string(timestamp)+"'";
|
||||
if(format != "") fmt = string("+'"+format+"'");
|
||||
return(trim(shell_exec("date "+ts+" "+fmt)));
|
||||
}
|
||||
|
||||
string gmdate(string format = "", u64 timestamp = 0)
|
||||
{
|
||||
string ts;
|
||||
string fmt;
|
||||
if(timestamp > 0) ts = string("'@")+to_string(timestamp)+"'";
|
||||
if(format != "") fmt = string("+'"+format+"'");
|
||||
return(trim(shell_exec("date -u "+ts+" "+fmt)));
|
||||
}
|
||||
|
||||
u64 parse_time(string time_string)
|
||||
{
|
||||
return(int_val(trim(shell_exec("date -u -d '"+time_string+"' +'%s'"))));
|
||||
}
|
||||
36
src/lib/types.h
Normal file
36
src/lib/types.h
Normal file
@ -0,0 +1,36 @@
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <filesystem>
|
||||
#include <ctype.h>
|
||||
#include <fstream>
|
||||
#include <vector>
|
||||
#include <sys/stat.h>
|
||||
#include <ctime>
|
||||
|
||||
typedef std::string string;
|
||||
|
||||
typedef unsigned char u8;
|
||||
typedef signed char s8;
|
||||
|
||||
typedef unsigned short u16;
|
||||
typedef signed short s16;
|
||||
|
||||
typedef unsigned int u32;
|
||||
typedef signed int s32;
|
||||
typedef float f32;
|
||||
|
||||
typedef double f64;
|
||||
typedef unsigned long long u64;
|
||||
typedef long long s64;
|
||||
|
||||
#define to_string(a) std::to_string(a)
|
||||
#define echo(s) context->print(s)
|
||||
|
||||
typedef std::map<string, string> StringMap;
|
||||
typedef std::vector<string> StringList;
|
||||
|
||||
#include "dtree.h"
|
||||
|
||||
37
src/lib/uce_gen.h
Normal file
37
src/lib/uce_gen.h
Normal file
@ -0,0 +1,37 @@
|
||||
#include "types.h"
|
||||
#include "settings.h"
|
||||
#include "datastructures.h"
|
||||
#include "sys.h"
|
||||
#include "time.h"
|
||||
#include "uri.h"
|
||||
|
||||
struct FastCGIRequest {
|
||||
StringMap params;
|
||||
StringMap header;
|
||||
URI uri;
|
||||
|
||||
std::string in;
|
||||
std::string out;
|
||||
std::string err;
|
||||
|
||||
f64 time_init;
|
||||
f64 time_start;
|
||||
f64 time_end;
|
||||
|
||||
void print(string s)
|
||||
{
|
||||
out.append(s);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
typedef FastCGIRequest Request;
|
||||
|
||||
Request* context;
|
||||
|
||||
extern "C" void set_current_request(Request* _request)
|
||||
{
|
||||
context = _request;
|
||||
}
|
||||
|
||||
#include "compiler.h"
|
||||
185
src/lib/uri.h
Normal file
185
src/lib/uri.h
Normal file
@ -0,0 +1,185 @@
|
||||
struct URI {
|
||||
|
||||
StringMap query;
|
||||
StringMap parts;
|
||||
|
||||
static URI parse(string uri_string)
|
||||
{
|
||||
URI result;
|
||||
|
||||
u8 state = 0;
|
||||
string current = "";
|
||||
char expect = 0;
|
||||
|
||||
result.parts["raw"] = uri_string;
|
||||
|
||||
string part_names[] = {
|
||||
"scheme",
|
||||
"host",
|
||||
"port",
|
||||
"path",
|
||||
"query",
|
||||
"fragment",
|
||||
};
|
||||
|
||||
if(uri_string[0] == '/')
|
||||
state = 3;
|
||||
|
||||
for (char &c: uri_string)
|
||||
{
|
||||
bool append_it = true;
|
||||
|
||||
if(expect && expect != c)
|
||||
{
|
||||
result.parts["error"] = string("\'") + c + string("\' expected");
|
||||
result.parts["error_parsing"] = current;
|
||||
result.parts["error_part"] = part_names[state];
|
||||
return(result);
|
||||
}
|
||||
expect = 0;
|
||||
|
||||
switch(state)
|
||||
{
|
||||
case(0): // scheme
|
||||
if(c == ':')
|
||||
{
|
||||
result.parts[part_names[state]] = current;
|
||||
append_it = false;
|
||||
current = "";
|
||||
state = 1;
|
||||
}
|
||||
break;
|
||||
case(1): // host name
|
||||
if(c == '/')
|
||||
{
|
||||
if(current == "")
|
||||
{
|
||||
append_it = false;
|
||||
break;
|
||||
}
|
||||
result.parts[part_names[state]] = current;
|
||||
append_it = false;
|
||||
current = "";
|
||||
state = 3;
|
||||
expect = '/';
|
||||
}
|
||||
else if(c == ':')
|
||||
{
|
||||
result.parts[part_names[state]] = current;
|
||||
append_it = false;
|
||||
current = "";
|
||||
state = 2;
|
||||
}
|
||||
break;
|
||||
case(2): // port
|
||||
if(c == '/')
|
||||
{
|
||||
result.parts[part_names[state]] = current;
|
||||
append_it = false;
|
||||
current = "";
|
||||
state = 3;
|
||||
expect = '/';
|
||||
}
|
||||
break;
|
||||
case(3): // path
|
||||
if(c == '/' && current == "")
|
||||
{
|
||||
append_it = false;
|
||||
break;
|
||||
}
|
||||
if(c == '?')
|
||||
{
|
||||
result.parts[part_names[state]] = current;
|
||||
append_it = false;
|
||||
current = "";
|
||||
state = 4;
|
||||
}
|
||||
break;
|
||||
case(4): // query
|
||||
if(c == '#')
|
||||
{
|
||||
result.parts[part_names[state]] = current;
|
||||
append_it = false;
|
||||
current = "";
|
||||
state = 5;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if(append_it)
|
||||
current.append(1, c);
|
||||
}
|
||||
|
||||
result.parts[part_names[state]] = current;
|
||||
|
||||
result.query = URI::parse_query(result.parts["query"]);
|
||||
|
||||
return(result);
|
||||
}
|
||||
|
||||
static StringMap parse_query(string q)
|
||||
{
|
||||
StringMap result;
|
||||
if(q.length() == 0)
|
||||
return(result);
|
||||
|
||||
bool is_key = true;
|
||||
string key = "";
|
||||
string value = "";
|
||||
for (char &c: q)
|
||||
{
|
||||
if(c == '=')
|
||||
{
|
||||
is_key = !is_key;
|
||||
}
|
||||
else if(c == '&')
|
||||
{
|
||||
result[URI::decode(key)] = URI::decode(value);
|
||||
key = "";
|
||||
value = "";
|
||||
}
|
||||
else if(is_key)
|
||||
{
|
||||
key.append(1, c);
|
||||
}
|
||||
else
|
||||
{
|
||||
value.append(1, c);
|
||||
}
|
||||
}
|
||||
|
||||
result[URI::decode(key)] = URI::decode(value);
|
||||
|
||||
return(result);
|
||||
}
|
||||
|
||||
static string decode(string q)
|
||||
{
|
||||
string result;
|
||||
for(u32 i = 0; i < q.length(); i++)
|
||||
{
|
||||
char c = q[i];
|
||||
if(c == '%' && q[i+1] != '%')
|
||||
{
|
||||
result.append(1, hex_to_u8(q.substr(i+1, 2)));
|
||||
i += 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
result.append(1, c);
|
||||
}
|
||||
}
|
||||
return(result);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
string var_dump(URI uri, string prefix = "", string postfix = "\n")
|
||||
{
|
||||
return(
|
||||
prefix + "URI Parts: " + postfix +
|
||||
var_dump(uri.parts, prefix+" ", postfix)+
|
||||
prefix + " Query: " + postfix +
|
||||
var_dump(uri.query, prefix+" ", postfix)
|
||||
);
|
||||
}
|
||||
85
src/linux_fastcgi.cpp
Normal file
85
src/linux_fastcgi.cpp
Normal file
@ -0,0 +1,85 @@
|
||||
#include <algorithm>
|
||||
#include <sys/stat.h>
|
||||
#include <iostream>
|
||||
|
||||
#include "lib/uce_gen.h"
|
||||
|
||||
#include "fastcgi/src/fcgicc.cc"
|
||||
|
||||
int handle_request(FastCGIRequest& request) {
|
||||
// This is always the first event to occur. It occurs when the
|
||||
// server receives all parameters. There may be more data coming on the
|
||||
// standard input stream.
|
||||
request.time_init = microtime();
|
||||
request.out.reserve(128 * 1024);
|
||||
request.in.reserve(128 * 1024);
|
||||
request.err.reserve(128 * 1024);
|
||||
if (request.params.count("REQUEST_URI"))
|
||||
return 0; // OK, continue processing
|
||||
else
|
||||
return 1; // stop processing and return error code
|
||||
}
|
||||
|
||||
int handle_data(FastCGIRequest& request) {
|
||||
// This event occurs when data is received on the standard input stream.
|
||||
// A simple string is used to hold the input stream, so it is the
|
||||
// responsibility of the application to remember which data it has
|
||||
// processed. The application may modify it; new data will be appended
|
||||
// to it by the server. The same goes for the output and error streams:
|
||||
// the application should append data to them; the server will remove
|
||||
// all sent data from them.
|
||||
return 0; // still OK
|
||||
|
||||
std::transform(request.in.begin(), request.in.end(),
|
||||
std::back_inserter(request.err),
|
||||
std::bind1st(std::plus<char>(), 1));
|
||||
request.in.clear(); // don't process it again
|
||||
return 0; // still OK
|
||||
}
|
||||
|
||||
int handle_complete(FastCGIRequest& request) {
|
||||
// The event handler can also be a class member function. This
|
||||
// event occurs when the parameters and standard input streams are
|
||||
// both closed, and thus the request is complete.
|
||||
request.time_start = microtime();
|
||||
request.header["Content-Type"] = Config.CONTENT_TYPE;
|
||||
request.uri = URI::parse(request.params["REQUEST_URI"]);
|
||||
request.uri.parts["scheme"] = request.params["REQUEST_SCHEME"];
|
||||
request.uri.parts["method"] = request.params["REQUEST_METHOD"];
|
||||
request.uri.parts["host"] = request.params["HTTP_HOST"];
|
||||
|
||||
invoke(&request, request.params["SCRIPT_FILENAME"]);
|
||||
|
||||
string headers = var_dump(request.header, "", "\r\n") + "\r\n";
|
||||
request.out = headers + request.out;
|
||||
|
||||
request.time_end = microtime();
|
||||
printf("(r) %s\t%0.6fs\t%0.1fkB\n",
|
||||
request.params["REQUEST_URI"].c_str(),
|
||||
request.time_end - request.time_start,
|
||||
(f32)(request.out.length()/1024)
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
FastCGIServer server;
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
// Set up our request handlers
|
||||
server.request_handler(&handle_request);
|
||||
server.data_handler(&handle_data);
|
||||
server.complete_handler(&handle_complete);
|
||||
|
||||
if(Config.LISTEN_PORT)
|
||||
server.listen(Config.LISTEN_PORT);
|
||||
if(Config.SOCKET_PATH != "")
|
||||
server.listen(Config.SOCKET_PATH);
|
||||
chmod(Config.SOCKET_PATH.c_str(), S_IRWXU | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
|
||||
|
||||
//server.process(100);
|
||||
//server.process();
|
||||
server.process_forever();
|
||||
|
||||
return 0;
|
||||
}
|
||||
176
test/hello.uce
Normal file
176
test/hello.uce
Normal file
@ -0,0 +1,176 @@
|
||||
|
||||
|
||||
RENDER()
|
||||
{
|
||||
//context->header["Content-Type"] = "text/plain";
|
||||
//echo(to_string(time())+"\n");
|
||||
|
||||
<html>
|
||||
Mwahahaaha <?= time() ?>
|
||||
<div>hello world: <?= context->params["HTTP_HOST"] ?></div>
|
||||
<pre style="background:rgba(0,0,0,0.1);max-height: 200px; overflow: auto;">
|
||||
Display the date using any or all of the following elements:
|
||||
|
||||
%a: abbreviated day name (i.e. mon, tue, wed)
|
||||
%A: full day name (i.e. Monday, Tuesday, Wednesday)
|
||||
%b or %h: abbreviated month name (i.e. jan, feb, mar)
|
||||
%B: full month name (January, February, March)
|
||||
%c: locales date and time (full date and time)
|
||||
%C: century - displays the first two numbers of the year (i.e 19 for 1999 and 20 for 2020)
|
||||
%d: day of month (i.e. 01, 02, 03)
|
||||
%D: same as M/D/Y (i.e. 04/20/16)
|
||||
%e: day of month padded (i.e. ' 1', ' 2')
|
||||
%F: full date, same as yyyy-mm-dd
|
||||
%H: hour (00, 01, 02, 21, 22, 23)
|
||||
%I: hour (1,2,3,10,11,12)
|
||||
%j: day of year (i.e. 243)
|
||||
%k: hour padded (i.e. '1' becomes ' 1'
|
||||
%l: hour padded (12 hour clock)
|
||||
%m: month number (1,2,3)
|
||||
%M: minute (1,2,3,57,58,59)
|
||||
%n: new line
|
||||
%N: nanoseconds
|
||||
%p: AM or PM
|
||||
%P: like %p but lowercase (ironically)
|
||||
%r: locales 12 hour clock time
|
||||
%R: 24 hour version of hour and minute
|
||||
%s: seconds since 1970-01-01 00:00:00
|
||||
%S: second (01,02,03, 57, 58, 59)
|
||||
%t: a tab
|
||||
%T: time same as %H:%M:%S
|
||||
%u: day of week (1 is Monday, 2 is Tuesday etc)
|
||||
%U: week number of year (assuming Sunday as first day of the week)
|
||||
%V: ISO week number with Monday as the first day of the week
|
||||
%w: day of week (0 is Sunday)
|
||||
%W: week number of the year with Monday as the first day of the week
|
||||
%x: locales date representation (12/31/2015)
|
||||
%X: locales time representation (14:44:44)
|
||||
%y: last two digits of year
|
||||
%Y: year
|
||||
%z: numeric time zone (i.e. -0400)
|
||||
%:z: numeric time zone as follows (i.e. -04:00)
|
||||
%::z: numeric time zone as follows (i.e. -04:00:00)
|
||||
%Z: alphabetic time zone abbreviation (GMT)
|
||||
-: a single hyphen prevents zero padding
|
||||
_: a single underscore pads with spaces
|
||||
0: pads with zeroes
|
||||
^: use uppercase if possible
|
||||
#: use opposite case if possible
|
||||
To display just the time use the following:
|
||||
|
||||
date +%T
|
||||
Alternatively, use the following:
|
||||
|
||||
date +%H:%M:%S
|
||||
Attach the date, as well, using the command:
|
||||
|
||||
date +%d/%m/%Y%t%H:%M:%S
|
||||
Alternatively, use the follow (since %T is equivalent to %H:%M:%S):
|
||||
|
||||
date +$d/%m/%Y%t%T
|
||||
The : and / characters are optional and can be whatever you want. For example:
|
||||
|
||||
date +%dc%mc%Y
|
||||
|
||||
outputs: 24c09c2020, if you wanted to use 'c' as a delimiter for some reason.
|
||||
|
||||
Use any combination of the above switches after the plus symbol to output the date as you so wish. If you want to add spaces you can use quotes around the date.
|
||||
|
||||
date +'%d/%m/%Y %H:%M:%S'
|
||||
How to Show the UTC Date
|
||||
View the UTC date for your computer using the following command:
|
||||
|
||||
date -u
|
||||
If you are in the UK you will notice that instead of showing "18:58:20" as the time it will show "17:58:20" as the time.
|
||||
|
||||
How to Show the RFC Date
|
||||
View the RFC date for your computer using the following command:
|
||||
|
||||
date --rfc-2822
|
||||
This displays the date in the following format:
|
||||
|
||||
Wed, 20 Apr 2018 19:56:52 +0100
|
||||
This flag is useful as it shows that you are an hour ahead of GMT.
|
||||
|
||||
Some Useful Date Commands
|
||||
Do you want to know the date next Monday? Try this:
|
||||
|
||||
date -d "next Monday"
|
||||
|
||||
At the point of writing this returns "Mon 25 Apr 00:00:00 BST 2016"
|
||||
|
||||
The -d basically prints a date in the future or the past. So, you can use "next Monday" or "last Friday".
|
||||
|
||||
Using the same command you can find out which day of the week your birthday or Christmas falls upon.
|
||||
|
||||
date -d 12/25/2016
|
||||
|
||||
The result is Sun Dec 25.
|
||||
|
||||
Summary
|
||||
It is worth checking out the manual page for the date command using the following command:
|
||||
|
||||
man date
|
||||
Was this page helpful?
|
||||
|
||||
More from Lifewire
|
||||
Businessman checking the time on his watch
|
||||
How to Understand the Date and Time in Email Headers
|
||||
Calendar next to an Apple keyboard
|
||||
How to Change the Date and Time on a Mac Manually
|
||||
Turning on automatic date and time settings in Windows 10.
|
||||
Change the Date and Time Zone on Your Windows Laptop
|
||||
People comparing the calendars on their iPhones
|
||||
How to Change Date on iPhone
|
||||
Woman walking with Black Friday shopping bag
|
||||
What Is Black Friday?
|
||||
Clock On White Wall
|
||||
Find the Sent Timestamp on Gmail Messages
|
||||
Homescreen with clock on Android
|
||||
How to Change the Time on Android
|
||||
Person taking a photo of a flower with a smartphone
|
||||
How to Adjust the Date, Time, and Location of Photos in iOS 15
|
||||
Dark office with many computers, one lit up
|
||||
Understanding the Linux Command: Ar
|
||||
A human hand pressing an old-fashioned alarm clock
|
||||
Learn the Linux Command 'at'
|
||||
Close-Up Of Thumbtack On Calendar Date
|
||||
Using the DATE Function in Google Sheets
|
||||
DATE function in Excel
|
||||
How to Use the Excel DATE Function
|
||||
Tux the penguin is the official Linux mascot.
|
||||
Delete Files Using the Linux Command Line
|
||||
Close-Up Of Clock Against Calendar
|
||||
Serial Number and Serial Date in Excel
|
||||
Person running Linux sleep command for 20 seconds on a laptop
|
||||
How to Use the Linux Sleep Command to Pause a BASH Script
|
||||
Cropped Hand Of Person Using Laptop By Alarm Clock At Table
|
||||
Excel's Volatile NOW Function for the Date and Time
|
||||
Lifewire
|
||||
Tech for Humans
|
||||
Follow Us
|
||||
Subscribe to our newsletter and get tech’s top stories in 30 seconds.
|
||||
Email Address
|
||||
enter email
|
||||
SUBMIT
|
||||
News
|
||||
Best Products
|
||||
Mobile Phones
|
||||
Computers
|
||||
About Us
|
||||
Advertise
|
||||
Privacy Policy
|
||||
Cookie Policy
|
||||
Careers
|
||||
Editorial Guidelines
|
||||
Contact
|
||||
Terms of Use
|
||||
EU Privacy
|
||||
California Privacy Notice
|
||||
Lifewire is part of the Dotdash publishing family.
|
||||
|
||||
</pre>
|
||||
</html>
|
||||
|
||||
}
|
||||
|
||||
38
todo.txt
Normal file
38
todo.txt
Normal file
@ -0,0 +1,38 @@
|
||||
Library
|
||||
=================================
|
||||
- memcached
|
||||
- mysql
|
||||
- sha1 / md5 / meow
|
||||
- cache
|
||||
- json_encode / json_decode
|
||||
- cookies
|
||||
- session
|
||||
- curl
|
||||
- random
|
||||
- pseudorandom
|
||||
|
||||
|
||||
Bugs
|
||||
=================================
|
||||
- shell_escape()
|
||||
- Include Path
|
||||
|
||||
|
||||
Framework
|
||||
=================================
|
||||
- DTree
|
||||
- POST
|
||||
- File Upload
|
||||
- Proxy mode
|
||||
- WebSockets
|
||||
- Persistent code / Cron / Tick?
|
||||
- Fork
|
||||
- Multithreading
|
||||
- Optionally store compiled units alongside source
|
||||
|
||||
|
||||
Nice to Have
|
||||
=================================
|
||||
- Solr
|
||||
- XML components?
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user