initial proof of concept
This commit is contained in:
@@ -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.
|
||||
@@ -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 )
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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 */
|
||||
|
||||
@@ -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 )
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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 )
|
||||
@@ -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"
|
||||
))
|
||||
)
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user