您的位置:首页 > 运维架构 > Linux

FastCGI收发jpg图片的例子

2015-09-12 20:05 866 查看
测试代码

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: lee353086@163.com
lee353086@msn.com
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)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  C++ FCGI CentOS NGIN