Go实现FastCgi Proxy Client 系列(一)

Stella981
• 阅读 1379

什么是FastCgi

再了解FastCgi之前,我们一定要先知道,什么叫Cgi。

CGI全称是“通用网关接口”(Common Gateway Interface),HTTP服务器与你的或其它机器上的程序进行“交谈”的一种工具,其程序一般运行在网络服务器上。 CGI可以用任何一种语言编写,只要这种语言具有标准输入、输出和环境变量。如php,perl,tcl等。

cgi的弊端

cgi会产生什么问题呢?

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

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

对比

盗2张百度的图

Go实现FastCgi Proxy Client 系列(一)

Go实现FastCgi Proxy Client 系列(一)

从图中可以看出,FastCgi解决的就是这一痛点,当一个新的请求进来的时候,会交给一个已经产生的进程进行处理,而不是fork新的东西出来(php 7还有更多优化,欢迎大家进坑,比如opcached)。

FastCgi协议组成

我目前了解的协议都是由三个部分组成:协议头、正文、结束符(或者说协议尾部),现在我们就来分析下这个协议。

主要参考资料来源于:麻省理工文档

FastCgi协议头

协议有这几种消息类型

#define FCGI_BEGIN_REQUEST 1
#define FCGI_ABORT_REQUEST 2
#define FCGI_END_REQUEST 3
#define FCGI_PARAMS 4
#define FCGI_STDIN 5
#define FCGI_STDOUT 6
#define FCGI_STDERR 7
#define FCGI_DATA 8
#define FCGI_GET_VALUES 9
#define FCGI_GET_VALUES_RESULT 10
#define FCGI_UNKNOWN_TYPE 11
#define FCGI_MAXTYPE (FCGI_UNKNOWN_TYPE)

很明显,如果我们要发起一个请求,必然要使用消息类型FCGI_BEGIN_REQUEST的进行请求。

看一段通用的包头。

typedef struct {
    unsigned char roleB1;
    unsigned char roleB0;
    unsigned char flags;
    unsigned char reserved[5];
} FCGI_BeginRequestBody;

typedef struct {
    FCGI_Header header;
    FCGI_BeginRequestBody body;
} FCGI_BeginRequestRecord;

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

我们可以把这个过程看成 begin->(header and content)->end。header这个很有意思,不过联系一下http协议的header,自然一下就了解了,其实在cgi协议里,header应该也属于正文的一种,只是类型比较特殊。

golang的实现

假如我们要实现一个golang fastcgi proxy client的部分,那自然,首先我们要先讲http请求解析成cgi协议的形式。

beginRequest

分析协议得出,web服务器向FastCGI程序发送一个 8 字节 type=FCGI_BEGIN_REQUEST的消息头和一个8字节 FCGI_BeginRequestBody 结构的 消息体,标志一个新请求的开始。

再仔细一想,这不对,服务端是如何知道我们发送的是啥,接受到的数据该如何解析。

从下面开始,我都会用golang来显示代码。

所以,这时候我们需要一个包头。fastCgi通用的包头(此处的header不是http header)为:

type header struct {
    Version       uint8        //协议版本  默认 01
    Type          uint8        // 请求类型
    Id            uint16       // 请求id
    ContentLength uint16  //正文长度
    PaddingLength uint8  //是否有字符补齐
    Reserved      uint8    //预留
}

先产生我们要产生的包体

b := [8]byte{byte(role >> 8), byte(role), flags}
role一般默认用1   1响应器 2验证器 3过滤器
flags 标志是否保持连接 就是http的keeponlive

这个时候,我们就先写包头,然后写包体即可

func (cgi *FCGIClient) writeRecord(recType uint8, reqId uint16, content []byte) (err error) {
    cgi.mutex.Lock()
    defer cgi.mutex.Unlock()
    cgi.buf.Reset()
    cgi.h.init(recType, reqId, len(content))
        //写包头
    if err := binary.Write(&cgi.buf, binary.BigEndian, cgi.h); err != nil {
        return err
    } 
        //写包体
    if _, err := cgi.buf.Write(content); err != nil {
        return err
    }
        //写补位
    if _, err := cgi.buf.Write(pad[:cgi.h.PaddingLength]); err != nil {
        return err
    }
        //将缓冲区写到之前打开的链接  
    _, err = cgi.rwc.Write(cgi.buf.Bytes())
    return err
}

这里应该会很好奇,cgi.rwc是什么。这里我们预留到下一次讲,知道这里是一个建立的socket连接即可。

请求正文

大家都知道 http协议分成 header和body

http request header

cgi协议里有单独对header的处理,即消息类型为FCGI_PARAMS(4)。

我们要从请求里获取到这次的header,很明显,header是一个map[string]string的结构。

func buildEnv(documentRoot string,r *http.Request) (err error,env map[string]string){
    env = make(map[string]string)
    index := "index.php"
    filename := documentRoot+"/"+index
    if r.URL.Path == "/.env" {
        return errors.New("not allow"),env
    } else if r.URL.Path == "/" || r.URL.Path == "" {
        filename = documentRoot + "/" + index
    } else {
        filename = documentRoot + r.URL.Path
    }

    for name,value := range serverEnvironment {
        env[name] = value
    }

    //......其他mapping


    for header, values := range r.Header {
        env["HTTP_" + strings.Replace(strings.ToUpper(header), "-", "_", -1)] = values[0]
    }
    return errors.New("not allow"),env
}

同样,我们先发送包头,再发送包体即可。

http request body

包体就是我们提交的数据,比如Post,delete,put等等操作中,正文包含的数据。

同样,我们先发送包头,再发送包体即可。

但是需要注意的是,一般包体都会很大,但是明显,我们ContentLength只有16位的长度,很大可能是无法一次发送完毕。

因此,我们需要分包进行发送(最大65535)。

请求结束 endRequest

依照上面的,同样,我们发送结束的包头和包体,修改type即可。

获取返回数据

开始我又说一个cgi.rwc是一个socket连接,数据都写往了那里,自然也要从那里读回来。

// recive untill EOF or FCGI_END_REQUEST
    for {
        err1 = rec.read(cgi.rwc)
        if err1 != nil {
            if err1 != io.EOF {
                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
        }
    }

一个简单的demo就完成了。这个时候,我们只需要将接收的数据格式化输出给用户即可。

我的github

给力点,大哥们,来点star吧。

在下一篇中,我们将讲建立连接的协议实现的细节部分。

spinx 一个golang实现的fastcgi proxy client

点赞
收藏
评论区
推荐文章
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
Karen110 Karen110
3年前
一篇文章带你了解JavaScript日期
日期对象允许您使用日期(年、月、日、小时、分钟、秒和毫秒)。一、JavaScript的日期格式一个JavaScript日期可以写为一个字符串:ThuFeb02201909:59:51GMT0800(中国标准时间)或者是一个数字:1486000791164写数字的日期,指定的毫秒数自1970年1月1日00:00:00到现在。1\.显示日期使用
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Stella981 Stella981
3年前
Apache使用fcgi方式与PHP结合
简介FCGI全称Fast Common Gateway Interface(快速通用网关协议),是一种让交互程序与Web服务器通信的协议。FastCGI是早期通用网关接口(CGI)的增强版本,FastCGI致力于减少网页服务器与CGI程序之间交互的开销,从而使服务器可以同时处理更多的网页请求。早期的CGI技术使
Easter79 Easter79
3年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Stella981 Stella981
3年前
PHP基础 CGI,FastCGI,PHP
CGICGI全称是“公共网关接口”(CommonGatewayInterface),HTTP服务器与你的或其它机器上的程序进行“交谈”的一种工具,其程序须运行在网络服务器上。CGI可以用任何一种语言编写,只要这种语言具有标准输入、输出和环境变量。如php,perl,tcl等。FastCGIFastCGI像是一个常驻(l
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
FastCGI 不完全高级指南(转载)
作者:zealy一、FastCGI是什么?FastCGI是语言无关的、可伸缩架构的CGI开放扩展,其主要行为是将CGI解释器进程保持在内存中并因此获得较高的性能。众所周知,CGI解释器的反复加载是CGI性能低下的主要原因,如果CGI解释器保持在内存中并接受FastCGI进程管理器调度,则可以提供良好的性能、伸缩性、FailOver特性等等。