FastCGI协议详解

Stella981
• 阅读 871

麻省理工-协议文档

go的转发器实现

Go实现FastCgi Proxy Client 系列(四)

Go实现FastCgi Proxy Client 系列(三)

Go实现FastCgi Proxy Client 系列(二)

Go实现FastCgi Proxy Client 系列(一)

cgi

CGI

CGI全称是“公共网关接口”(Common Gateway Interface),HTTP服务器与你的或其它机器上的程序进行“交谈”的一种工具,其程序须运行在网络服务器上。

cgi的弊端

cgi会产生什么问题呢?

当每一个请求进入的时候,cgi都会fork一个新的进程,然后以php为例,每个请求都要耗费相当大的内存,这样一来,并发起来,完全就会GG。

为了解决这个问题,于是产生了fastCgi。

FastCGI

FastCGI像是一个常驻(long-live)型的CGI,它可以一直执行着,只要激活后,不会每次都要花费时间去fork一次(这是CGI最为人诟病的fork-and-execute 模式)。

它还支持分布式的运算,即 FastCGI 程序可以在网站服务器以外的主机上执行并且接受来自其它网站服务器来的请求。

一次完整的http经cgi请求到后端的分析

http request:

Request URL: http://demo.inke.cn/server.php
Request Method: GET
Status Code: 200 OK
Remote Address: 127.0.0.1:80
Referrer Policy: no-referrer-when-downgrade
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cache-Control: max-age=0
Connection: keep-alive
Cookie: aid=fa29caf3-5b18-401c-8abd-677eb1a1c45f; s-59804d897bc03a0036dc141c=2a126bda133d4feba4169fa70308f2c7
Host: demo.inke.cn
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36

server.php

<?php
 
 
var_dump($_SERVER);
返回结果:
array(33) {
  ["USER"]=>
  string(6) "limars"
  ["HOME"]=>
  string(13) "/Users/limars"
  ["HTTP_COOKIE"]=>
  string(101) "aid=d11e3484-fd7d-4c6e-a2a5-bfabe2271da2; s-59804d897bc03a0036dc141c=fcb6a8cf23644f50b66e405d173eced7"
  ["HTTP_ACCEPT_LANGUAGE"]=>
  string(14) "zh-CN,zh;q=0.9"
  ["HTTP_ACCEPT_ENCODING"]=>
  string(13) "gzip, deflate"
  ["HTTP_ACCEPT"]=>
  string(118) "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3"
  ["HTTP_USER_AGENT"]=>
  string(121) "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36"
  ["HTTP_UPGRADE_INSECURE_REQUESTS"]=>
  string(1) "1"
  ["HTTP_CONNECTION"]=>
  string(10) "keep-alive"
  ["HTTP_HOST"]=>
  string(12) "demo.inke.cn"
  ["REDIRECT_STATUS"]=>
  string(3) "200"
  ["SERVER_NAME"]=>
  string(12) "demo.inke.cn"
  ["SERVER_PORT"]=>
  string(2) "80"
  ["SERVER_ADDR"]=>
  string(9) "127.0.0.1"
  ["REMOTE_PORT"]=>
  string(5) "51429"
  ["REMOTE_ADDR"]=>
  string(9) "127.0.0.1"
  ["SERVER_SOFTWARE"]=>
  string(12) "nginx/1.12.2"
  ["GATEWAY_INTERFACE"]=>
  string(7) "CGI/1.1"
  ["REQUEST_SCHEME"]=>
  string(4) "http"
  ["SERVER_PROTOCOL"]=>
  string(8) "HTTP/1.1"
  ["DOCUMENT_ROOT"]=>
  string(31) "/Users/limars/Desktop/inke/demo"
  ["DOCUMENT_URI"]=>
  string(11) "/server.php"
  ["REQUEST_URI"]=>
  string(11) "/server.php"
  ["SCRIPT_NAME"]=>
  string(11) "/server.php"
  ["CONTENT_LENGTH"]=>
  string(0) ""
  ["CONTENT_TYPE"]=>
  string(0) ""
  ["REQUEST_METHOD"]=>
  string(3) "GET"
  ["QUERY_STRING"]=>
  string(0) ""
  ["SCRIPT_FILENAME"]=>
  string(42) "/Users/limars/Desktop/inke/demo/server.php"
  ["FCGI_ROLE"]=>
  string(9) "RESPONDER"
  ["PHP_SELF"]=>
  string(11) "/server.php"
  ["REQUEST_TIME_FLOAT"]=>
  float(1565082651.4024)
  ["REQUEST_TIME"]=>
  int(1565082651)
}

它是个怎样的转换过程呢?仔细观察,我们会发现有很多东西是http协议里没有的,那这些东西是怎么生成的呢?

观察nginx配置:

listen       80;
server_name  demo.inke.cn;
root        /Users/limars/Desktop/inke/demo;
 
 location ~ \.php$ {
            try_files        $uri =404;
            fastcgi_pass 127.0.0.1:9000;
 
            fastcgi_param  SCRIPT_FILENAME $document_root$fastcgi_script_name;
            include        fastcgi_params;
}

我们会发现一个叫 fastcgi_param的可配置参数,而nginx一般提供了默认值。 打开nginx目录下的fastcgi_params文件可以看到:

fastcgi_param  QUERY_STRING       $query_string;
fastcgi_param  REQUEST_METHOD     $request_method;
fastcgi_param  CONTENT_TYPE       $content_type;
fastcgi_param  CONTENT_LENGTH     $content_length;
fastcgi_param  SCRIPT_NAME        $fastcgi_script_name;
fastcgi_param  REQUEST_URI        $request_uri;
fastcgi_param  DOCUMENT_URI       $document_uri;
fastcgi_param  DOCUMENT_ROOT      $document_root;
fastcgi_param  SERVER_PROTOCOL    $server_protocol;
fastcgi_param  REQUEST_SCHEME     $scheme;
fastcgi_param  HTTPS              $https if_not_empty;
fastcgi_param  GATEWAY_INTERFACE  CGI/1.1;
fastcgi_param  SERVER_SOFTWARE    nginx/$nginx_version;
fastcgi_param  REMOTE_ADDR        $remote_addr;
fastcgi_param  REMOTE_PORT        $remote_port;
fastcgi_param  SERVER_ADDR        $server_addr;
fastcgi_param  SERVER_PORT        $server_port;
fastcgi_param  SERVER_NAME        $server_name;

这时候,我们对比下$_SERVER中http_XXX_XXX的,这不是都是http自带的头部嘛?

综合下来,这就是一个http请求完整转发到php的过程,一次完整的cgi请求,而fastcgi呢,就是对cgi协议的常驻封装,主要解决的问题是:

优化性能(cgi不停fork新进程,每个进程都干很多同样的事【加载配置文件、加载扩展】) 进程池维护,异步模型,请求是分发到空闲子进程,方便创建/销毁进程。

fcgi协议组成(go语言描述)

fcgi共12种消息类型

const (
 typeBeginRequest uint8 = 1
 typeAbortRequest uint8 = 2
 typeEndRequest uint8 = 3
 typeParams uint8 = 4
 typeStdin uint8 = 5
 typeStdout uint8 = 6
 typeStderr uint8 = 7
 typeData uint8 = 8
 typeGetValues uint8 = 9
 typeGetValuesResult uint8 = 10
 typeUnknownType uint8 = 11
)

见名知意:

1~3是信号类型

4~10是不同的数据类型

11、default 是错误类型

数据传输

通常协议传输,为了考虑数据大小的原因,都会将数据进行分片处理,fcgi也同样。

fcgi协议的每个包都由包头和包体组成

//It's fcgi header
type header struct {
    Version       uint8
     Type          uint8
     Id            uint16
     ContentLength uint16
     PaddingLength uint8
     Reserved      uint8
}

包的正文,就是由分割的二进制内容组成,一个包最多传输2^16次方减一,也就是65535个字节。

一次完整的请求过程

请求:解析http请求获取配置->发送cgi请求->typeBeginRequest -> write Params -> write Stdin(if exists) -> write Data (if exists)

//写头部
if request.KeepConn {
   //if it's keep-alive
   //set flags 1
   err = cgi.writeBeginRequest(reqId, roleResponder, 1)
} else {
   err = cgi.writeBeginRequest(reqId, roleResponder, 0)
}
 
if err != nil {
   return
}
 
//写http header
err = cgi.writePairs(typeParams, reqId, request.Params)
if err != nil {
   return
}
 
 
//读取http body并写到cgi
p := make([]byte, 1024)
n, _ := request.Stdin.Read(p)
err = cgi.writeRecord(typeStdin, reqId, p[:n])
//写其他信息
 
//写最后一个换行
err = cgi.writeRecord(typeStdin, reqId, nil)

接收:check response type-> 错误处理(stderr、unknownType、default)->正确类型->接受数据复用socket返回给请求方。 rec := &record{} //建立一个记录缓冲

    var err1 error
 
    // recive untill EOF or FCGI_END_REQUEST
    for {
        err1 = rec.read(cgi.rwc)   //从cgi建立的链接读取数据
        if err1 != nil {           //判断是否有错误,判断错误是否为终止符
            if err1 != io.EOF {  // keepalive on的时候 不会有终止符  这个要注意,读完长度为content-length即当前请求返回
                err = err1
            }
            break
        }
        switch {  //根据返回的类型,将内容写到响应的字节数组中
        case rec.h.Type == typeStdout:  
            retout = append(retout, rec.content()...)
        case rec.h.Type == typeStderr:
            reterr = append(reterr, rec.content()...)
        case rec.h.Type == typeEndRequest:
            fallthrough
        default:
            break
        }
    }
点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
5个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Stella981 Stella981
3年前
Opencv中Mat矩阵相乘——点乘、dot、mul运算详解
Opencv中Mat矩阵相乘——点乘、dot、mul运算详解2016年09月02日00:00:36 \牧野(https://www.oschina.net/action/GoToLink?urlhttps%3A%2F%2Fme.csdn.net%2Fdcrmg) 阅读数:59593
Wesley13 Wesley13
3年前
P2P技术揭秘.P2P网络技术原理与典型系统开发
Modular.Java(2009.06)\.Craig.Walls.文字版.pdf:http://www.t00y.com/file/59501950(https://www.oschina.net/action/GoToLink?urlhttp%3A%2F%2Fwww.t00y.com%2Ffile%2F59501950)\More.E
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Google地球出现“无法连接到登录服务器(错误代码:c00a0194)”解决方法
Google地球出现“无法连接到登录服务器(错误代码:c00a0194)”解决方法参考文章:(1)Google地球出现“无法连接到登录服务器(错误代码:c00a0194)”解决方法(https://www.oschina.net/action/GoToLink?urlhttps%3A%2F%2Fwww.codeprj.com%2Fblo
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
11个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这