FastCGI收发jpg图片的例子

测试代码

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)


你可能感兴趣的:(C++,centos,fcgi,Ngin)