测试代码
TestFCGI.cpp
#include "FCGIHelper.h" #include "StringHelper.h" #ifdef WIN32 #pragma comment(lib,"libfcgi.lib") #endif int main(int argc, char* argv[]) { FCGX_Request request; FCGX_Init(); FCGX_InitRequest(&request, 0, 0); bool isNeedResponseTheSameAgain_OnlyNeedInPostMethodRequest = false; while(FCGX_Accept_r(&request) >= 0) { kagula::CFCGIHelper fcgi(&request); /* //return jpg file in disk, only valid in windows OS. //after every XXXXFinish routine, you need call continue statement. fcgi.WriteJpgFileResponseAndFinish("d:\\t.jpg"); continue; */ if (isNeedResponseTheSameAgain_OnlyNeedInPostMethodRequest) { isNeedResponseTheSameAgain_OnlyNeedInPostMethodRequest = false; fcgi.WriteLastSendAndFinish(); continue; } if (fcgi._requestData.data.find("testType")!=fcgi._requestData.data.end()) { string testType = kagula::StringHelper::Array2Str(fcgi._requestData.data["testType"].value); if (testType == "jpg" ) { //return jpg data. if (fcgi._requestData.data.find("MyJPGFile")!=fcgi._requestData.data.end()) { //if client using post method, you write jpg response, fast cgi will call us again! //image get only by get method request, so here(post method) will be called by twice. //if client using get method request, no need response the same again. fcgi.WriteResponseAndFinish("jpg","",fcgi._requestData.data["MyJPGFile"].value); isNeedResponseTheSameAgain_OnlyNeedInPostMethodRequest = true; continue; } } else if (testType == "json") { //return json object. fcgi.WriteResponseAndFinish("json","{name:\"kagula\"}",std::list<char>()); continue; }else if (testType == "text") { //return html page. fcgi.WriteResponseAndFinish("text","<h1>Hello,World</h1>",std::list<char>()); continue; } } //return fcgi run status. fcgi.WriteResponseAndFinish("","",std::list<char>()); } return 0; }
依赖的代码
FCGIHelper.cpp
#include "FCGIHelper.h" #include <algorithm> #include <vector> #include <boost/regex.hpp> #include <boost/algorithm/string.hpp> #include "StringHelper.h" #include "FileHelper.h" #pragma warning ( disable : 4129 ) namespace kagula { std::vector<char> CFCGIHelper::_vecLastSend; bool CFCGIHelper::getBlockInfo(std::list<char> &block,std::string &outBlockInfo) { if ( block.size()<6 ) { return false; } std::list<char> buf; std::list<char> retData; for (std::list<char>::iterator iter = block.begin(); iter!=block.end();iter++) { retData.push_back(*iter); buf.push_back(*iter); if (buf.size()>=4) { if (kagula::StringHelper::IsEqualBinary(buf, kagula::StringHelper::g_crlfcrlf)) { break; } buf.pop_front(); }//end if }//end for // outBlockInfo= kagula::StringHelper::Array2Str(retData); if (outBlockInfo.size()>4) { outBlockInfo = outBlockInfo.substr(0,outBlockInfo.size()-4); } return true; } bool CFCGIHelper::getBlockBody(const std::list<char> &block, std::list<char> &outBlockbody) { std::list<char> listRet; if ( block.size()<6 ) { return false; } bool bFind = false; std::list<char> buf; std::list<char>::const_iterator iter = block.begin(); for (;iter!=block.end();iter++) { buf.push_back(*iter); if (buf.size()>=4) { if (kagula::StringHelper::IsEqualBinary(buf,kagula::StringHelper::g_crlfcrlf)) { bFind = true; break; } buf.pop_front(); }//end if }//end for if (!bFind) { return false; } iter++; while(iter!=block.end()) { outBlockbody.push_back(*iter); iter++; } return true; } //输入数据的形式“Content-Disposition: form-data; name="userKey"” std::map<std::string,std::string> CFCGIHelper::extractSemicolonData(std::string &strInfo) { std::map<std::string,std::string> mapReturn; std::vector<std::string> vecItems; boost::split(vecItems, strInfo, boost::is_any_of(";\n")); for (size_t i=0;i<vecItems.size();i++) { std::vector<std::string> item2; boost::split(item2, vecItems[i], boost::is_any_of(":=")); if (item2.size()==2) { boost::trim_if(item2[0], boost::is_any_of("\" \n\r\t'")); boost::trim_if(item2[1], boost::is_any_of("\" \n\r\t'")); if (item2[0].size()>0) { mapReturn[item2[0]]=item2[1]; } }//end if }//end for return mapReturn; } CFCGIHelper::CFCGIHelper(FCGX_Request *request) { //initialization internal data _request = request; //get environment grab_envs(); if (IsPOSTRequest()) { int lenRequest = atoi(_contentLen.c_str()); char *body = new char[lenRequest+1]; memset(body, 0, lenRequest); FCGX_GetStr(body, lenRequest, request->in); if (_contentType == "application/x-www-form-urlencoded" || _contentType == "application/x-url-encoded") { _queryString = body; } else { parseRequest(body,lenRequest); }//end if delete body; body = NULL; }//end if kagula::StringHelper::Str2Map(_httpCookie, _mapCookieRead); }//end func CFCGIHelper::~CFCGIHelper() { } bool CFCGIHelper::IsGetRequest() { if (_requestMethod == "GET") return true; return false; } bool CFCGIHelper::IsPOSTRequest() { if (_requestMethod == "POST") return true; return false; } void CFCGIHelper::grab_envs() { _remoteIP = FCGX_GetParam("REMOTE_ADDR", _request->envp) == NULL ? "" : FCGX_GetParam("REMOTE_ADDR", _request->envp); _serverName = FCGX_GetParam("SERVER_NAME", _request->envp) == NULL ? "" : FCGX_GetParam("SERVER_NAME", _request->envp); _requestURI = FCGX_GetParam("REQUEST_URI", _request->envp) == NULL ? "" : FCGX_GetParam("REQUEST_URI", _request->envp); _requestMethod = FCGX_GetParam("REQUEST_METHOD", _request->envp) == NULL ? "" : FCGX_GetParam("REQUEST_METHOD", _request->envp); _requestMethod = kagula::StringHelper::Trim(_requestMethod); std::transform(_requestMethod.begin(), _requestMethod.end(), _requestMethod.begin(), ::toupper); _queryString = FCGX_GetParam("QUERY_STRING", _request->envp) == NULL ? "" : FCGX_GetParam("QUERY_STRING", _request->envp); _contentType = FCGX_GetParam("CONTENT_TYPE", _request->envp) == NULL ? "" : FCGX_GetParam("CONTENT_TYPE", _request->envp); _contentLen = FCGX_GetParam("CONTENT_LENGTH", _request->envp) == NULL ? "" : FCGX_GetParam("CONTENT_LENGTH", _request->envp); _httpCookie = FCGX_GetParam("HTTP_COOKIE", _request->envp) == NULL ? "" : FCGX_GetParam("HTTP_COOKIE", _request->envp); } void CFCGIHelper::test_envs() { FCGX_FPrintF(_request->out, "Content-type: text/plain\r\n\r\n"); FCGX_FPrintF(_request->out, "[CFCGIHelper Test Page]\r\n"); FCGX_FPrintF(_request->out, "REQUEST_URI: %s\r\n", _requestURI.c_str()); FCGX_FPrintF(_request->out, "REMOTE_ADDR: %s\r\n", _remoteIP.c_str()); FCGX_FPrintF(_request->out, "REQUEST_METHOD: %s\r\n", _requestMethod.c_str()); FCGX_FPrintF(_request->out, "SERVER_NAME: %s\r\n", _serverName.c_str()); FCGX_FPrintF(_request->out, "SERVER_PORT: %s\r\n", FCGX_GetParam("SERVER_PORT", _request->envp) == NULL ? "does not exist" : FCGX_GetParam("SERVER_PORT", _request->envp)); FCGX_FPrintF(_request->out, "SERVER_PROTOCOL: %s\r\n", FCGX_GetParam("SERVER_PROTOCOL", _request->envp) == NULL ? "is not exist" : FCGX_GetParam("SERVER_PROTOCOL", _request->envp)); FCGX_FPrintF(_request->out, "QUERY_STRING: %s\r\n", _queryString.c_str()); } void CFCGIHelper::parseRequest(const char* inBuffer, const int nLength) { //Step1: get separator int i = 0; int countLF = 0; while (inBuffer[i]!=0&&inBuffer[i]!='\r'&&inBuffer[i]!='\n') { _requestData.separator.push_back(inBuffer[i++]); } //Step2: partition region by separator. std::list<char> tempBuffer; std::vector<std::list<char> > vecPartition; while(i<nLength) { char *pData = (char *)(inBuffer+i); if ( kagula::StringHelper::IsEqualBinary(pData, nLength-i, _requestData.separator)) { if (tempBuffer.size()>0) { vecPartition.push_back(tempBuffer); tempBuffer.clear(); } i+=_requestData.separator.length(); pData = (char*)(inBuffer + i); }//end if tempBuffer.push_back(*pData); i++; }//end while //pop up carriage and line feed characters in every partition head and tail for (size_t i=0;i<vecPartition.size();i++) { if (vecPartition[i].size()>3) { vecPartition[i].pop_front(); vecPartition[i].pop_front(); vecPartition[i].pop_back(); vecPartition[i].pop_back(); } } //Step3: extract data from region. for (size_t i=0;i<vecPartition.size();i++) { RequestDataItem rdi; std::string strInfo; if (!getBlockInfo(vecPartition[i],strInfo)) { continue; } std::map<std::string,std::string> mapBlockHeadInfo = extractSemicolonData(strInfo); if(!getBlockBody(vecPartition[i],rdi.value)) { continue; } if (mapBlockHeadInfo.find("name")!=mapBlockHeadInfo.end()) { rdi.name = mapBlockHeadInfo["name"]; } else { continue; } if (mapBlockHeadInfo.find("Content-Type")!=mapBlockHeadInfo.end()) { rdi.contentType = mapBlockHeadInfo["Content-Type"]; } else { std::string temp = kagula::StringHelper::Array2Str(rdi.value); } _requestData.data[rdi.name] = rdi; } }//end function std::string CFCGIHelper::GetCookie(std::string key) { if (_mapCookieRead.find(key) != _mapCookieRead.end()) { return _mapCookieRead[key]; } if (_mapCookieWrite.find(key) != _mapCookieWrite.end()) { return _mapCookieWrite[key]; } return std::string(); } void CFCGIHelper::SetCookie(std::string key, std::string value) { _mapCookieWrite[key] = value; _mapCookieRead[key] = value; } void CFCGIHelper::WriteResponseAndFinish(const std::string &response_type, const std::string &stringContent, const std::list<char> &binaryContent) { if (response_type=="text") { return WriteTextResponseAndFinish(stringContent); } else if (response_type=="json") { return WriteJsonResponseAndFinish(stringContent); } else if (response_type=="jpg") { return WriteJpgResponseAndFinish(binaryContent); } else { return test_envs(); } } void CFCGIHelper::WriteLastSendAndFinish() { FCGX_PutStr(_vecLastSend.data(), _vecLastSend.size(), _request->out); } void CFCGIHelper::WriteJpgResponseAndFinish(const std::list<char> &outBlockbody) { std::string header = "Content-type: image/jpg;Content-Transfer-Encoding: binary\r\n\r\n"; FCGX_PutStr(header.c_str(),header.size(),_request->out); //store last send data.begin _vecLastSend.resize(header.size() + outBlockbody.size()); size_t i = 0; for (; i < header.size();i++) { _vecLastSend[i] = header.at(i); } //end for (std::list<char>::const_iterator iter=outBlockbody.begin();iter!=outBlockbody.end();iter++) { FCGX_PutStr(&(*iter), 1, _request->out); _vecLastSend[i++] = *iter; } } void CFCGIHelper::WriteJsonResponseAndFinish(const std::string &json) { std::string send; send = "Content-type: application/json\r\n\r\n"; send.append(json); FCGX_FPrintF(_request->out, "%s\r\n", send.c_str()); } void CFCGIHelper::WriteTextResponseAndFinish(const std::string &text) { //define header! std::string response = "Content-type: text/html; charset=UTF-8\r\n";; if (_mapCookieWrite.size() > 0) { std::map<std::string, std::string>::iterator iterCookie = _mapCookieWrite.begin(); /* example: set-cookie:key=value:a:b; expires=Sun, 08-Jun-2014 14:27:09 GMT; path=/; domain=.domain.com\r\n set-cookie:key2=value2:a:b; expires=Sat, 08-Dec-2012 14:27:09 GMT; path=/; domain=.domain.com; HttpOnly\r\n\r\n */ while (iterCookie != _mapCookieWrite.end()) { std::string cookie = "Set-Cookie: "; cookie.append(iterCookie->first); cookie.append("="); cookie.append(iterCookie->second); cookie.append("\r\n"); response.append(cookie); iterCookie++; } } /* indicate HTTP header is end. if no the "\r\n\r\n" flag, will throw out internal error! */ response.append("\r\n"); response.append(text); FCGX_FPrintF(_request->out, "%s\r\n", response.c_str()); } void CFCGIHelper::WriteJpgFileResponseAndFinish(const char * fileName) { vector<char> buffer; if (!kagula::FileHelper::ReadFile(fileName,buffer)) { return; } std::string header = "Content-type: image/jpg;Content-Transfer-Encoding: binary\r\n\r\n"; FCGX_PutStr(header.c_str(), header.size(), _request->out); int i = 0; for (std::vector<char>::const_iterator iter = buffer.begin(); iter != buffer.end(); iter++) { FCGX_PutStr(&(*iter), 1, _request->out); } } }//end class
FCGIHelper.h
#ifndef _CFGIHELPER_H_ #define _CFGIHELPER_H_ #include <vector> #include <map> #include <string> #include <list> #include <fcgi_stdio.h> namespace kagula { /* Title:FastCGI Helper API Author:kagula LastUpdateData: 2015-09-12 Revision:2 Contact: [email protected] [email protected] Test Environment [1]Win7, Visual Studio 2010 SP1, boost 1.55 [2]Win10, Visual Studio 2013 Update5, Apache 2.2, boost 1.55 [3]CentOS6.5, CMake 2.8.12.2, GCC 4.4.7, NGINX 1.8.0, boost 1.59 Hypothesis [1]string only support utf-8. Note [1]if the fcgi process has not exist, apache will start it. [2]if PostMan send file too big,Apache will return 500 error instead convey request to fcgi program. History [1]2015-09-11 add receive file function and fixed a few bugs. Reference [1]CGI Environment Variables http://www.cgi101.com/class/ch3/text.html */ struct RequestDataItem{ std::string name; std::list<char> value;//string value or binary value. std::string contentType;//if empty, this item is a string type object, the string value at the value field. }; struct RequestData{ std::string separator; std::map<std::string,RequestDataItem> data; }; class CFCGIHelper { public: CFCGIHelper(FCGX_Request *request); ~CFCGIHelper(); bool IsGetRequest(); bool IsPOSTRequest(); void WriteResponseAndFinish(const std::string &response_type, const std::string &stringContent, const std::list<char> &binaryContent); void SetCookie(std::string key, std::string value); std::string GetCookie(std::string key); RequestData _requestData; std::string _remoteIP; std::string _serverName; std::string _requestURI; std::string _requestMethod; std::string _queryString; std::string _contentType; std::string _contentLen; std::string _httpCookie; void WriteJpgFileResponseAndFinish(const char * fileName); void WriteLastSendAndFinish(); private: FCGX_Request *_request; static std::vector<char> _vecLastSend;//if response is the image file return, need remember last send. std::map<std::string, std::string> _mapCookieRead; std::map<std::string, std::string> _mapCookieWrite; void grab_envs(); void test_envs(); void parseRequest(const char* inBuffer,const int nLength); void extractFromQueryString(); bool getBlockInfo(std::list<char> &block,std::string &outBlockInfo); bool getBlockBody(const std::list<char> &block, std::list<char> &outBlockbody); std::map<std::string,std::string> extractSemicolonData(std::string &strInfo); void WriteJpgResponseAndFinish(const std::list<char> &outBlockbody); void WriteTextResponseAndFinish(const std::string &text); void WriteJsonResponseAndFinish(const std::string &json); }; } #endif
FileHelper.cpp
#include "FileHelper.h" #include <string> #include <fstream> using namespace std; namespace kagula { namespace FileHelper { bool ReadFile(const char * fileName, vector<char> &outBuffer) { std::ifstream file(fileName, std::ios::binary); file.seekg(0, std::ios::end); std::streamsize size = file.tellg(); file.seekg(0, std::ios::beg); outBuffer.resize(size); if (!file.read(outBuffer.data(), size)) { return false; } return true; } } }
FileHelper.h
#ifndef _FILEHELPER_H_ #define _FILEHELPER_H_ #include <vector> namespace kagula { namespace FileHelper { bool ReadFile(const char * fileName, std::vector<char> &outBuffer); } } #endif
StringHelper.cpp
#include "StringHelper.h" #include <algorithm> #include <vector> #include <boost/regex.hpp> #include <boost/algorithm/string.hpp> namespace kagula { namespace StringHelper { extern const char g_crlfcrlf[] = "\r\n\r\n"; extern const char g_crlf[] = "\r\n"; bool IsEqualBinary(const char *bufSrc, const unsigned int nLength, std::string &dest) { //将来有时间这段代码用CPU的SIMD来优化。 for (unsigned int i=0; i<dest.length() && i<nLength; i++) { //dest字符串中不可能含0,所以遇到0直接返回。 if (bufSrc[i]=='\0') return false; // if (bufSrc[i]!=dest.at(i)) return false; } return true; } bool IsEqualBinary( const list<char> &bufSrc, const string &dest ) { unsigned int i = 0; for (list<char>::const_iterator iter = bufSrc.begin(); iter!=bufSrc.end() && i<dest.length(); iter++,i++) { if (*iter!=dest.at(i)) return false; } return true; } std::string Array2Str( const list<char> &bufSrc ) { std::string ret; for (list<char>::const_iterator iter = bufSrc.begin(); iter!=bufSrc.end();iter++) { ret.push_back(*iter); } return ret; } std::string <rim(std::string &s) { s.erase(s.begin(), std::find_if(s.begin(), s.end(), std::not1(std::ptr_fun<int, int>(std::isspace)))); return s; } // trim from end std::string &rtrim(std::string &s) { s.erase(std::find_if(s.rbegin(), s.rend(), std::not1(std::ptr_fun<int, int>(std::isspace))).base(), s.end()); return s; } string &Trim(string &s) { return ltrim(rtrim(s)); } std::string Map2Str(std::map<std::string, std::string> &mapKeyValue) { std::string strR; std::map<std::string, std::string>::iterator iter = mapKeyValue.begin(); while (iter != mapKeyValue.end()) { strR.append(iter->first); strR.append("="); strR.append(iter->second); strR.append("\r\n"); iter++; } return strR; } void Str2Map(std::string strSrc, std::map<std::string, std::string> &mapKeyValue) { std::vector<std::string> line; boost::split(line, strSrc, boost::is_any_of(";")); for (unsigned int i = 0; i < line.size(); i++) { std::vector<std::string> vecT; boost::split(vecT, line[i], boost::is_any_of("=")); if (vecT.size() == 2) { vecT[0] = vecT[0].substr(vecT[0].find_first_not_of(' '), vecT[0].find_last_not_of(' ')); mapKeyValue[vecT[0]] = vecT[1]; }//end if }//end for } } }
StringHelper.h
#ifndef _STRING_HELPER_H_ #define _STRING_HELPER_H_ #include <map> #include <list> #include <string> using namespace std; //这里收集的帮助类API,是平台无关性的。 namespace kagula { namespace StringHelper { bool IsEqualBinary(const char *bufSrc, const unsigned int nLength, string &dest); bool IsEqualBinary(const list<char> &bufSrc, const string &dest); string Array2Str(const list<char> &bufSrc); void Str2Map(string strSrc, map<string, string> &mapKeyValue); string Map2Str(map<std::string, string> &mapKeyValue); string &Trim(string &s); extern const char g_crlfcrlf[]; extern const char g_crlf[]; } } #endif
CMakeLists.txt
#设置项目名称 project(TestFCGI) #要求CMake的最低版本为2.8 cmake_minimum_required(VERSION 2.8) #For Boost library add_definitions(-DBOOST_ALL_NO_LIB) set(Boost_USE_STATIC_LIBS OFF) # using dynamic files set(Boost_USE_MULTITHREADED ON) set(Boost_USE_STATIC_RUNTIME OFF) find_package(Boost 1.55 COMPONENTS regex REQUIRED) include_directories(${Boost_INCLUDE_DIRS}) #For FCGI library set(FCGI_INCLUDE_DIRS /usr/local/include/) set(FCGI_LIBRARY_DIRS /usr/local/lib/) include_directories(${FCGI_INCLUDE_DIRS}) #用于将当前目录下的所有源文件的名字保存在变量 DIR_SRCS 中 aux_source_directory(. DIR_SRCS) #用于指定从一组源文件 source1 source2 … sourceN(在变量DIR_SRCS中定义) #编译出一个可执行文件且命名为TestFCGI add_executable(TestFCGI ${DIR_SRCS}) #We need some third party libraries to run our program! target_link_libraries(TestFCGI ${Boost_LIBRARIES} fcgi)