MENU

FastCGI协议

前言

最近采用C++开发一个webserver,功能类似于nginx。支持其中的多站点host、反向代理、负载均衡、支持cgi、支持session、cookie。支持FastCGI,类似nginx可以监听9000端口连接php-fpm,因此查资料找相关FastCGI协议相关。

CGI及FastCGI相关知识点

  • CGI(common gateway interface)通常翻译为共同网关接口:是Web Server与后台编程语言交互的一种协议。但是CGI有一个致命的缺点,就是每处理一个请求度需要fork一个进程,不断地fork是一项很消耗时间和资源的工作,这样低效的方式明显不能满足需求。
  • FastCGI是一个常驻型的CGI,可以一直执行,只要激活后,不会每次都花时间去fork一次,而且还支持分布式运算。

学习过PHP的童鞋都知道在PHP有个叫php-fpm,它是FastCGI在PHP中的一种实现。我们常常在nginx中配置fastpass 127.0.0.1:9000,其实nginx将动态语言交由PHP处理。

FastCGI协议详细说明

  • 列举一些常用传入参数

    SCRIPT_FILENAME  # 执行脚本文件所在路径
    REQUEST_METHOD   # 请求方式 常见的GET POST
    CONTENT_LENGTH   # 针对POST请求中body的长度
    CONTENT_TYPE     # 请求MIME类型
    QUERY_STRING     # url参数(URL中?后面参数)
    HTTP_ACCEPT      # 浏览器能够接收Content-types类型
    HTTP_COOKIE      # 传入HTTP头部中cookie值
    HTTP_USER_AGENT  # 传入HTTP头部中user-agent值
    HTTP_REFERER     # HTTP头部中Referer值
    

    详细可查看CGI/1.1 标准

  • 协议请求头
    在发送开始报文以及发送结束报文都涉及到其中的header头部信息,将其共同部分抽取为一个struct结构体。header请求头在协议中占用8个字节

    struct FASTCGI_Header {
          unsigned char version;   // FastCGI协议版本
          unsigned char type;      // 协议操作类型(开始请求、发送参数、发送POST中body数据、结束请求、错误输出等)
          unsigned char requestIdB1;  // 请求requestId
          unsigned char requestIdB0;
          unsigned char contentLengthB1;      // 请求body长度
          unsigned char contentLengthB0;
          unsigned char paddingLength;    // 填充长度
          unsigned char reserved;     // 保留字节
    };
    
  • 协议请求体

    • 开始请求body:
    struct FastCgiBeginRequestBody {
          unsigned char roleB1;   // Web服务器期望 php 所扮演的角色(常见有三种角色)
          unsigned char roleB0;
          unsigned char flags;    // 响应后是否立即关闭
          unsigned char reserved[5];  // 保留字节
    };
    

    role角色分析说明:
    ①php-fpm接收http请求并产生相应响应(响应器)
    ②php-fpm对于请求先进行一次认证AUTHORIZER,不通过则拒绝响应返回(鉴权器)
    ③过滤请求中的额外数据流,并产生过滤后的http响应(过滤器)

*   **结束请求body:**
struct FastCgiEndRequestBody {
      unsigned char appStatusB3;  // 4个字节表示结束状态
      unsigned char appStatusB2;
      unsigned char appStatusB1;
      unsigned char appStatusB0;
      unsigned char protocolStatus;   // 协议状态
      unsigned char reserved[3];
};
  • GET请求中参数构造(传入参数按照key-value)

    初始部分计算整体body长度,body总体长度为:len(key) + len(value) + (key大于128字节则用4个字节保存key,否则使用1个字节) + (value大于128字节则用4个字节保存key,否则使用1个字节)

    unsigned char bodyBuff[bodylen];
      int j = 0;
      /* 如果 nameLen 小于128字节 */
      if (nlen < 128) {
          bodyBuff[j++] = (unsigned char) nlen; //nameLen用1个字节保存
      } else {
          /* nameLen 用 4 个字节保存 */
          bodyBuff[j++] = (unsigned char) ((nlen >> 24) | 0x80);
          bodyBuff[j++] = (unsigned char) (nlen >> 16);
          bodyBuff[j++] = (unsigned char) (nlen >> 8);
          bodyBuff[j++] = (unsigned char) nlen;
      }
    
      /* valueLen 小于 128 就用一个字节保存 */
      if (vlen < 128) {
          bodyBuff[j++] = (unsigned char) vlen;
      } else {
          /* valueLen 用 4 个字节保存 */
          bodyBuff[j++] = (unsigned char) ((vlen >> 24) | 0x80);
          bodyBuff[j++] = (unsigned char) (vlen >> 16);
          bodyBuff[j++] = (unsigned char) (vlen >> 8);
          bodyBuff[j++] = (unsigned char) vlen;
      }
    
      for (size_t i = 0; i < strlen(name); i++) {
          bodyBuff[j++] = name[i];
      }
    
      for (size_t i = 0; i < strlen(value); i++) {
          bodyBuff[j++] = value[i];
      }
    
  • POST请求中参数构造

    POST请求发送body数据大小或许比较大,采用while循环发送,限制每次发送的最大字节长度。

    while (len > 0) {
    
          if (len > FASTCGI_MAX_LENGTH) {
              bodylen = FASTCGI_MAX_LENGTH;
          }
    
          FASTCGI_Header header = makeHeader(FASTCGI_STDIN, requestId, bodylen, 0);
    
          ret = writen(socketFd, (char *) &header, FASTCGI_HEADER_LENGTH);
          if (ret != FASTCGI_HEADER_LENGTH) {
              return -1;
          }
    
          ret = writen(socketFd, data, bodylen);
          if (ret != bodylen) {
              return -1;
          }
    
          len -= bodylen;
          data += bodylen;
      }
    
  • 结束请求(FCGI_END_REQUEST)

    通过发送一个FCGI_END_REQUEST记录给Web服务器,来通知它某个请求被终止。

    struct FastCgiEndRequestBody {
          unsigned char appStatusB3;
          unsigned char appStatusB2;
          unsigned char appStatusB1;
          unsigned char appStatusB0;
          unsigned char protocolStatus;
          unsigned char reserved[3];
    };
    
    

    appStatus代表角色(响应器、鉴权器、过滤器)
    protocolStatus代表结束状态。有四种取值,①FASTCGI_REQUEST_COMPLETE 正常请求结束 ②FASTCGI_CANT_MPX_CONN 拒绝新请求进入,无法并发处理 ③FASTCGI_OVERLOADED 拒绝新请求进入,资源不足的情况发生 ④FASTCGI_UNKNOWN_ROLE 拒绝新请求进入,未知角色

  • 返回数据接收(FASTCGI_STDOUT、FASTCGI_STDERR)

    判断其中返回头部type的类型,常见的两种正常输出、错误输出情况。并且读取其中的头部中的内容长度(contentLen)。

    if (respHeader.type == FASTCGI_STDOUT) {
      // 正常情况          
    } else if (respHeader.type == FASTCGI_STDERR) {
      // 异常情况        
    }
    
  • 返回数据解析处理成header头、body数据

    由于直接返回的数据包含header头部数据,例如Content-type值、网页跳转、set-cookie等混合在body中,解析分为header头部、body体。
    运用状态机方式变量字符。key-value串结束后即为body部分。

    for (; (*cur != '\0' && !isFinish); cur++) {
          switch (hState) {
              case H_START:
                  if (*cur == CR || *cur == LF) {
                      break;
                  }
                  key_start = cur;
                  hState = H_KEY;
                  break;
    
              case H_KEY:
                  if (*cur == ':') {
                      key_end = cur;
                      hState = H_SPACES_AFTER_COLON;
                      break;
                  }
                  break;
    
              case H_SPACES_AFTER_COLON:
                  if (*cur == ' ') {
                      break;
                  }
                  hState = H_VALUE;
                  value_start = cur;
                  break;
    
              case H_VALUE:
                  if (*cur == CR) {
                      value_end = cur;
                      hState = H_CR;
                  }
                  break;
              case H_CR:
                  if (*cur == LF) {
                      // key - start
                      while (key_start != key_end) {
                          header += *key_start;
                          key_start++;
                      }
                      header += ": ";
                      // key - end
                      // value - start
                      while (value_start != value_end) {
                          header += *value_start;
                          value_start++;
                      }
                      header += "\r\n";
                      // value - end
                      hState = H_LF;
                  } else {
                      isFinish = true;
                  }
                  break;
              case H_LF:
                  if (*cur == CR) {
                      hState = H_END_CR;
                  } else {
                      key_start = cur;
                      hState = H_KEY;
                  }
                  break;
              case H_END_CR:
                  if (*cur == LF) {
                      hState = H_END_LF;
                  } else {
                      isFinish = true;
                  }
                  break;
              case H_END_LF:
                  isFinish = true;
                  break;
          }
      }
    

详细代码见项目代码FastCGI

参考文章
fastcgi协议分析与实例
FastCGI 协议规范中文版

返回文章列表 文章二维码 打赏
本页链接的二维码
打赏二维码