客户的请求到达WebServer后,会转向fcgi程序,fcgi解释器会循环等待新的连接到来:
FCGX_Accept_r
会在OS_Accpet()
函数处堵塞,等待连接的到来,当新的连接到来时,会将request->ipcFd
设置为对应的fd
这里的listen_sock为0,即标准输入,fcgi解释器会通过监听标准输入来等待新连接的到来。
当有新的连接到来时,在此程序中第一个可用的fd是3。返回后会设置输入流以读取数据。
NewReader
创建新的FCGX_Stream
结构体,地址保存在reqDataPtr->in
中,FillBuffProc
函数来真正读取数据,读出的数据格式如下
【23】之前的为FCGI协议的header部分,后面为数据部分,每一个消息前面都会有一个header,
header的结构体如下:
typedef struct { unsigned char version; //FCGI版本信息,目前一般定义为1 unsigned char type; //每次发送的消息的类型.相当于flag,此例子中的1表示一次请求的开始 unsigned char requestIdB1; //合起来表示本次请求的编号 ID unsigned char requestIdB0; unsigned char contentLengthB1; //合起来表示 body 长度 unsigned char contentLengthB0; unsigned char paddingLength; //填充字节长度,填充长度不可超过255字节 unsigned char reserved; //保留字节 } FCGI_Header; //消息头
其中type的字段的定义为:
// FCGI_Header 中 type 的具体值 #define FCGI_BEGIN_REQUEST 1 //一次请求的开始(web->fastcgi) #define FCGI_ABORT_REQUEST 2 //异常终止一次请求(web->fastcgi) #define FCGI_END_REQUEST 3 //请求处理完毕,正常结束(fastcgi->web) #define FCGI_PARAMS 4 /*传递参数,表明消息中包含的数据为某个name-value对 (web->fastcgi)*/ #define FCGI_STDIN 5 /*POST 内容传递,从浏览器接收到的POST请求数据(表单提交等)以消息的形式发给php-fpm时, 这种消息的type就得设为5(web->fastcgi)*/ #define FCGI_STDOUT 6 //正常响应内容,php-fpm给web服务器回的正常响应消息的type就设为6(fastcgi->web) #define FCGI_STDERR 7 //php-fpm给web服务器回的错误响应设为7(fastcgi->web) #define FCGI_DATA 8 //向CGI程序传递的额外数据(WEB->FastCGI) #define FCGI_GET_VALUES 9 // 向FastCGI程序询问一些环境变量(WEB->FastCGI) #define FCGI_GET_VALUES_RESULT 10 // 询问环境变量的结果(FastCGI->WEB) #define FCGI_UNKNOWN_TYPE 11 //通知 webserver 所请求 type 非正常类型 #define FCGI_MAXTYPE (FCGI_UNKNOWN_TYPE) // 未知类型,可能用作拓展
当header中的type == 1时,接下来的8字节传递的是下面的消息体
typedef struct { unsigned char roleB1; unsigned char roleB0; //合起来表示 webserver 所期望php-fpm 扮演的角色,具体取值下面有 unsigned char flags; //确定 php-fpm 处理完一次请求之后是否关闭,flag=1,不关闭 unsigned char reserved[5]; //保留字段 } FCGI_BeginRequestBody; //开始请求体 //webserver 期望 fcgi 扮演的角色(想让fcgi做什么) #define FCGI_RESPONDER 1 //接受http关联的所有信息,并产生http响应,接受来自webserver的PARAMS环境变量 #define FCGI_AUTHORIZER 2 //对于认证的会关联其http请求,未认证的则关闭请求 #define FCGI_FILTER 3 //过滤web server 中的额外数据流,并产生过滤后的http响应
回到前面的例子中,前16个字节中,header的type为1,代表一次请求的开始,后面的header中的type为4,表示消息的数据流,此部分为HTTP请求的QUERY_STRING, HTTP_HEADER等字段,主要格式为nameLen, valueLen, name[nameLen], value[valueLen]
即前两个字节分别表示name和value的长度,接下来是name和value本身。
接下来fcgi调用ReadParams将数据流的字段解析到reqDataPtr->paramsPtr
,这主要是一个指针数组,没个指针指向的都是从数据流中解析出的key=value形式的字符串。
接下来构造了两个对象:
cgicc::FCgiIO fCgiIO(request); Cgicc cgicc(&fCgiIO);
FCgiIO类成员有:
class CGICC_API FCgiIO : public cgicc::CgiInput, public std::ostream { public: ... protected: FCGX_Request& fRequest; fcgi_streambuf fOutBuf; fcgi_streambuf fErrBuf; std::ostream fErr; std::map<std::string, std::string> fEnv; };
FCgiIO
类继承自CgiInput
和std::ostream
,其构造函数主要是将成员变量的输出流和错误流设置为request中的输出流和错误流,并且解析request中的kv格式的串,放到fEnv
。
Cgicc这个类是cgicc框架的主要部分,其类之间的继承关系架构图为
在Cgicc的构造函数中,首先初始化fEnvironment
,其成员为
class fEnvironment { ... unsigned long fServerPort; unsigned long fContentLength; bool fUsingHTTPS; std::string fServerSoftware; std::string fServerName; std::string fGatewayInterface; std::string fServerProtocol; std::string fRequestMethod; std::string fPathInfo; std::string fPathTranslated; std::string fScriptName; std::string fQueryString; std::string fRemoteHost; std::string fRemoteAddr; std::string fAuthType; std::string fRemoteUser; std::string fRemoteIdent; std::string fContentType; std::string fAccept; std::string fUserAgent; std::string fPostData; std::string fRedirectRequest; std::string fRedirectURL; std::string fRedirectStatus; std::string fReferrer; std::string fCookie; std::vector<HTTPCookie> fCookies; std::string fAcceptLanguageString; }; };
主要保存了HTTP请求的环境变量和主要的get,post数据。值得一提的是,在解析fPostData时,fEnvironment
调用了FCgiIO.read()
函数,这个函数主要调用FCGX_GetStr(data, length, fRequest.in);
来继续获取输入流fRequest.in
中的数据。
至此FCGI的输入部分就此结束。
主要的成员变量:
class FastCGI { public: ... void Run(FCGX_Request& request); void FetchHttpRequest(cgicc::FCgiIO *pCgiIO, cgicc::Cgicc* pCgicc, CReqData* reqData) protected: CReqData reqData; //输入 CResData resData;//输出 string tid; int outPutType; ///< 输出类型, 默认xml string subModule; ///< 配置结点名,默认 weixin class CReqData : public CTransData { //CTransData 只是指定了三个方法 getPara setPara,getMap ... private: CStr2Map m_cookieMap; CStr2Map m_formDataMap ; //保存前台提交的数据 CStr2Map m_formUrlDataMap; //在有POST提交的情况, 保存前台提交GET方式的数据 CStr2Map m_envMap ; //保存环境变量 Map CStr2Map m_tempMap ; //保存临时数据 vector<cgicc::FormFile> m_upLoadFileList ; //保存上传的文件属性列表 string m_strPostData; //post data string m_strQueryData; //get data };
main函数中,当接受到Request时,首先通过函数FetchHttpRequest(cgicc::FCgiIO *pCgiIO, cgicc::Cgicc* pCgicc, CReqData* reqData)
,填充ReqData,然后初始化ResData。
FetchHttpRequest
里面关键的步骤主要是填充ReqData这个类:
reqData->SetPostData(env); reqData->SetQueryData(env); reqData->SetEnv("ClientIp", "ClientIP"); reqData->SetEnv("ClientAgent", env.getUserAgent()); reqData->SetEnv("RequestMethod", env.getRequestMethod()); reqData->SetEnv("CgiName", env.getScriptName()); reqData->SetEnv("referer", env.getReferrer()); reqData->SetEnv("Authorization", pCgiIO->getenv("HTTP_AUTHORIZATION")); //reqData->SetEnv("ServerIp", env.getServerName().empty()? "" : env.getServerName()); char szIp[30] = {0}; snprintf(szIp, sizeof(szIp), "%s", "1.3.4.5"); reqData->SetEnv("ServerIp", szIp);
获取到ReqData后,输入部分的处理就到此结束了。
输出部分主要是由CCgiOutput
类处理。
class CCgiOutput { public: CCgiOutput(CResData* pResData, cgicc::FCgiIO* pOut):m_pResData(pResData), m_pOut(pOut){}; virtual ~CCgiOutput(){} ; void PrintOutput(CReqData* pReqData); protected: virtual void OutPutExcpion(CReqData* pReqData); void OutPut() ; void OutPutHtml(); void FilterData(); void SetHttpHeader(); string EncodeJsonObject(const string& JsonObj); virtual void PrintJsonElem(const string& name ,const string& value) ; void OutPutJson(int iType=0) ; //不需要框架进行自动URL转码 void PrintJsonElemNotEscape(const string& name ,const string& value); void OutPutJsonNotEscape( int iType=0 ); void PrintXmlElement(const string & sName,const string& sValue) ; void OutPutXml() ; void OutPutXmlMySelf(); stringstream m_strsteam; private: //cgi输出 CResData *m_pResData; //输出句柄 cgicc::FCgiIO* m_pOut; };
这个类的处理逻辑很简单,依据resData中指明的输出类型,调用不同的输出函数生成数据,例如输出的类型是OUTJSON
,则调用OutPutJson();
这个函数会以json的格式将resData中保存的数据转换为json字符串,保存到stringstream中,