如何用C写一个web服务器之CGI协议

目录
  • 前言
  • CGI
    • CGI请求
    • CGI响应
  • Nginx和PHP的CGI实现
    • SAPI
    • PHP-FPM
    • 纠偏
  • 代码实现
    • http_parser
    • cJSON

前言

这次更新主要实现一下 CGI 协议。

先放上GitHub链接https://github.com/zhenbianshu/tinyServer

作为一个服务器,基本要求是能受理请求,提取信息并将消息分发给 CGI 解释器,再将解释器响应的消息包装后返回客户端。在这个过程中,除了和客户端 socket 之间的交互,还要牵扯到第三个实体 - 请求解释器。

如上图所示,客户端负责封装请求和解析响应,服务器的主要职责是管理连接、数据转换、传输和分发客户端请求,而真正进行数据文档处理与数据库操作的就是请求解释器,这个解释器,在 PHP 中一般是 PHP-FPM,JAVA 中是 Servlet。

我们之前进行的处理多在客户端和服务器之间的通信,以及服务器的内部调整,这次更新的内容主要是后面两个实体之间的进程间通信。

进程间通信牵涉到三个方面,即方式和形式和内容。

方式指的是进程间通信的传输媒介,如 Nginx 中实现的 TCP 方式和 Unix Domain Socket,它们分别有跨机器和高效率的优点,还有我实现的服务器用了很 low 的popen方式。

而形式就是数据格式了,我认为它并无定式,只要服务器容易组织数据,解释器能方便地接收并解析,最好也能节约传输资源,提高传输效率。目前的解决方案有经典的 xml,轻巧易理解的 json 和谷歌高效率的 protobuf。它们各有优点,我选择了 json,主要是因为有CJson库的存在,数据在 C 中方便组织,而在PHP中,一个json_decode()方法就完成了数据解析。

至于应该传输哪些内容呢?CGI 描述了一套协议:

CGI

通用网关接口(Common Gateway Interface/CGI)是一种重要的互联网技术,可以让一个客户端,从网页浏览器向执行在网络服务器上的程序请求数据。CGI描述了服务器和请求处理程序之间传输数据的一种标准。

CGI 是服务器与解释器交互的接口,服务器负责受理请求,并将请求信息解释为一条条基本的请求信息(在文档中被称为“元数据”),传送给解释器来解释执行,而解释器响应文档和数据库操作信息。

之前看了一下 CGI 的 RFC 文档,总结了几个重要点,有兴趣的可以看下底部参考文献。常见规范(信息太多,只考虑 MUST 的情况)如下:

CGI请求

  • 服务器根据 以 / 分隔的路径选择解释器;
  • 如果有 AUTH 字段,需要先执行 AUTH,再执行解释器;
  • 服务器确认 CONTENT-LENGTH 表示的是数据解析出来的长度,如果附带信息体,则必须将长度字段传送到解释器;
  • 如果有 CONTENT-TYPE 字段,服务器必须将其传给解释器;若无此字段,但有信息体,则服务器判断此类型或抛弃信息体;
  • 服务器必须设置 QUERY_STRING 字段,如果客户端没有设置,服务端要传一个空字符串“”
  • 服务器必须设置 REMOTE_ADDR,即客户端请求IP;
  • REQUEST_METHOD 字段必须设置, GET POST 等,大小写敏感;
  • SCRIPT_NAME 表示执行的解释器脚本名,必须设置;
  • SERVER_NAME 和 SERVER_PORT 代表着大小写敏感的服务器名和服务器受理时的TCP/IP端口;
  • SERVER_PROTOCOL 字段指示着服务器与解释器协商的协议类型,不一定与客户端请求的SCHEMA 相同,如'https://'可能为HTTP;
  • 在 CONTENT-LENGTH 不为 NULL 时,服务器要提供信息体,此信息体要严格与长度相符,即使有更多的可读信息也不能多传;
  • 服务器必须将数据压缩等编码解析出来;

CGI响应

  • CGI解释器必须响应 至少一行头 + 换行 + 响应内容;
  • 解释器在响应文档时,必须要有 CONTENT-TYPE 头;
  • 在客户端重定向时,解释器除了 client-redir-response=绝对url地址,不能再有其他返回,然后服务器返回一个 302 状态码;
  • 解释器响应 三位数字状态码,具体配置可自行搜索;
  • 服务器必须将所有解释器返回的数据响应给客户端,除非需要压缩等编码,服务器不能修改响应数据;

Nginx和PHP的CGI实现

介绍完了 CGI,我们来参考一下当前服务器 CGI 协议实现的成熟方案,这里挑选我熟悉的 Nginx 和 PHP。

在 Nginx 和 PHP 的配合中,Nginx 自然是服务器,而解释器是 PHP 的 SAPI。

SAPI

SAPI: Server abstraction API,指的是 PHP 具体应用的编程接口,它使得 PHP 可以和其他应用进行交互数据。

PHP 脚本要执行可以通过很多种方式,通过 Web 服务器,或者直接在命令行下,也可以嵌入在其他程序中。常见的 sapi 有apache2handler、fpm-fcgi、cli、cgi-fcgi,可以通过 PHP 函数php_sapi_name()来查看当前 PHP 执行所使用的 sapi。

PHP5.3 之前使用的与服务器交互的 sapi 是cgi,它实现基本的 CGI 协议,由于它每次处理请求都要创建一个进程、初始化进程、处理请求、销毁进程,消耗过大,使得系统性能大大下降。

这时候便出现了 CGI 协议的升级版本 Fast-CGI。

PHP-FPM

快速通用网关接口(Fast Common Gateway Interface/FastCGI)是一种让交互程序与Web服务器通信的协议。FastCGI是早期通用网关接口(CGI)的增强版本。

Fast-CGI 提升效率主要靠将 CGI 解释器长驻内存重现,避免了进程反复加载的损耗。PHP 的 sapi cgi-fcgi实现了 Fast-CGI 协议,提升了 PHP 处理 Web 请求的效率。

那么我们常见的 php-fpm 是什么呢?它是一种进程管理器(PHP-FastCGI Process Manager),它负责管理实现 Fast-CGI 的那些进程(worker进程),它加载php.ini信息,初始化 worker 进程,并实现平滑重启和其他高级功能。

Nginx 将请求都交给 php-fpm,fpm 选择一个空闲工作进程来处理请求。

纠偏

这里总结一下几个名字,以防混淆:

  • sapi,是 PHP 与外部进程交互的接口;
  • CGI/Fast-CGI(大写)是一种协议;
  • 本节中出现的 cgi(小写),是指 PHP 的 sapi,即实现 CGI 协议的一种接口。
  • php-fpm 是管理实现了Fast-CGI协议的进程的一个进程。

代码实现

介绍完了高端的Nginx服务器,说一下我的实现:

服务器解析 http 报文,实现 CGI 协议,将数据包装成 json 格式,通过 PHP 的cli sapi 发送至 PHP 进程,PHP 进程解析后响应 json 格式数据,服务器解析响应数据后包装成 http 响应报文发送给客户端。

http_parser

首要任务是解析 http 报文,C 中没有很丰富字符串函数,我也没有封装过常用的函数库,所以只好临时自己实现了一个util_http.c,这里介绍几个处理 http 报文时好用的字符串函数。

strtok(char str[], const *delimeter),将 delimeter 设置为 "\n",分行处理 http 报文头正好适合。

sscanf(const *str, format, dest1[,dest...]),它从字符串中以特定格式读取字符串,读取时的分隔符是空格,用它来处理 http 请求行十分方便。

至于解析 http 报文头的键值对应,没想到好方法,只好使用字符遍历来判断。

cJSON

cJSON 是一个 C 实现的用以生成和解析 json 格式数据的函数库,在 GitHub 上可以轻松搜到,只用两个文件 cJSON.c和cJSON.h即可。

需要注意:C 作为强类型语言,往 json 内添加不同类型的数据要使用不同的方法,cJSON 支持 string, bool, number, cJSON object等类型。

这里简单地介绍一下生成和解析的一般方法;

生成:

cJSON *root; // 声明cJSON格式数据
root = cJSON_CreateObject(); // 创建一个cJSON对象
cJSON_AddStringToObject(root, "key", "value") // 往cJSON对象内添加键值对
char *output = cJSON_PrintUnformatted(root); // 生成json字符串
cJSON_Delete(root); // 别忘记释放内存

解析:

cJSON *json = cJSON_Parse(response_json);
value = cJSON_GetObjectItem(cJSON, "key");

当然,也可以声明 cJSON 类型的数据进行嵌套;

以上就是如何用C写一个web服务器之CGI协议的详细内容,更多关于用C写一个web服务器之CGI协议的资料请关注我们其它相关文章!

(0)

相关推荐

  • 用Ruby进行CGI编程的入门指引

    编写CGI脚本: 最基本的Ruby CGI脚本看起来像这样: #!/usr/bin/ruby puts "HTTP/1.0 200 OK" puts "Content-type: text/html\n\n" puts "<html><body>This is a test</body></html>" 如果调用这个脚本 test.cgi 上传到基于Unix/Linux的Web托管服务提供商并具有合

  • perl的cgi高级编程介绍

    一 CGI.pm中的方法(routines)调用 1. CGI.pm实现了两种使用方法,分别是面向对象的方式和传统的perlmodule方法的方式.面向对象的方式: 复制代码 代码如下: #!/usr/local/bin/perl -wuse CGI;   # load CGI routines$q = CGI->new;                        # create new CGI objectprint $q->header,                    # c

  • 如何用C写一个web服务器之基础功能

    服务器架构 目标架构 以 nginx 的思想来考虑本服务器架构,初步考虑如下图: 当然 php 进程也可以替换为其他的脚本语言,可以更改源码中的 command 变量实现. 服务器有一个 master 进程,其有多个子进程为 worker 进程,master 进程受理客户端的请求,然后分发给 worker 进程,worker 进程处理 http 头信息后将参数传递给 php 进程处理后,将结果返回到上层,再响应给客户端. 也考虑过使用 php-fpm 的 worker 进程池方式,那样的话 ph

  • 如何用C写一个web服务器之I/O多路复用

    前言 I/O模型 接触过 socket 编程的同学应该都知道一些 I/O 模型的概念,linux 中有阻塞 I/O.非阻塞 I/O.I/O 多路复用.信号驱动 I/O 和 异步 I/O 五种模型. 其他模型的具体概念这里不多介绍,只简单地提一下自己理解的 I/O 多路复用:简单的说就是由一个进程来管理多个 socket,即将多个 socket 放入一个表中,在其中有 socket 可操作时,通知进程来处理, I/O 多路复用的实现方式有 select.poll 和 epoll. select/p

  • 编写Python CGI脚本的教程

    你是否想使用Python语言创建一个网页,或者处理用户从web表单输入的数据?这些任务可以通过Python CGI(公用网关接口)脚本以及一个Apache web服务器实现.当用户请求一个指定URL或者和网页交互(比如点击""提交"按钮)的时候,CGI脚本就会被web服务器启用.CGI脚本调用执行完毕后,它的输出结果就会被web服务器用来创建显示给用户的网页. 配置Apache web服务器,让其能运行CGI脚本 在这个教程里,我们假设Apache web服务器已经安装好,并

  • 基于Python_脚本CGI、特点、应用、开发环境(详解)

    CGI CGI 目前由NCSA维护,NCSA定义CGI如下: CGI(Common Gateway Interface),通用网关接口,它是一段程序,运行在服务器上如:HTTP服务器,提供同客户端HTML页面的接口. CGI程序可以是Python脚本.Perl脚本.Shell脚本.C或者C++程序等. 服务器 在你进行CGI编程前,确保您的Web服务器支持CGI及已经配置了CGI的处理程序. 所有的HTTP服务器执行CGI程序都保存在一个预先配置的目录.这个目录被称为CGI目录,并按照惯例,它被

  • 如何用C写一个web服务器之GCC项目编译

    前言 本想着接下来大概实现一下 CGI 协议,但是实现过程中被一个问题卡住了: C进程与php进程的交互数据类型问题: 在 C 进程中我准备将服务器处理后的请求数据存储在一个结构体内,然后将此结构体中的信息传给 PHP,而 PHP 进程内也会有一个全局数组与之对应,可是众所周之,结构体是 C 进程内的内存数据,是无法直接传给 PHP 使用的. 这时候我们也需要一种"协议"来解决进程数据类型的异构性.当然这个解决方案确定起来还是很简单的,无非是对C结构体进行序列化,使用xml,json,

  • 如何用C写一个web服务器之CGI协议

    目录 前言 CGI CGI请求 CGI响应 Nginx和PHP的CGI实现 SAPI PHP-FPM 纠偏 代码实现 http_parser cJSON 前言 这次更新主要实现一下 CGI 协议. 先放上GitHub链接https://github.com/zhenbianshu/tinyServer 作为一个服务器,基本要求是能受理请求,提取信息并将消息分发给 CGI 解释器,再将解释器响应的消息包装后返回客户端.在这个过程中,除了和客户端 socket 之间的交互,还要牵扯到第三个实体 -

  • 如何给ss bash 写一个 WEB 端查看流量的页面

    由于刚毕业的穷大学生,和朋友合租了一台服务器开了多个端口提供 ss 服务,懒得配置 ss-panel,就使用了 ss-bash 来监控不同端口的流量,但每次都要等上服务器才能看到流量使用情况,很麻烦,于是就写了个简单的页面来提供 WEB 访问. JavaScript 版本 用 crontab 定时把流量记录文件复制到 WEB 目录下,写个 JS 脚本作数据处理. function successFunction(data) { var allRows = data.split(/\r?\n|\r

  • 使用python 写一个静态服务(实战)

    师父布置的任务,让我写一个服务练练手,搞清楚socket的原理和过程后跑了一个小demo,很有成就感,代码内容也比较清晰易懂,很有教育启发意义. 代码 # coding:utf-8 import socket from multiprocessing import Process HTML_ROOT_DIR = "" def handle_client(client_socket): """处理客户端请求""" # 获取客户端

  • Python Tornado框架轻松写一个Web应用的全过程

    目录 Tornado是什么 安装 试试看使用tornado框架来写一个web application 总结 Tornado是什么 学委之前在看Jupyter组件的源码的时候,发现了tornado这个web框架. 不仅仅做一个web框架, 通过使用非阻塞网络I/O,Tornado可以扩展到数万个开放连接. 这样非常适合long polling, WebSockets以及其他需要与每个用户建立长期连接的应用程序. 好,下面安装试用一下. 安装 pip install tornado pip 不会用的

  • 如何用Python写一个简单的通讯录

    目录 用Python写一个简单的通讯录 一.构思 1.定义空列表和一个空字典来存储 2.定义功能选项 3.添加通讯录功能 3.2 删除学员功能 二.整体项目演示 用Python写一个简单的通讯录 一.构思 1.定义空列表和一个空字典来存储 list1=[] #用于储存字典中的信息 dict1={} #用于储存联系人信息 2.定义功能选项 def Menu(): print('请选择功能--------\n' '1.添加学员\n' '2.删除学员\n' '3.修改学员\n' '4.查询学员\n'

  • shell linux中如何用shell写一个占用CPU的脚本

    使用场景: 向公司申请的虚机资源自己工作用的比较方便,因占用较小basis要求回收掉,现写一个脚本,让CPU跑满一些. 首先看下共有几颗逻辑CPU cat /proc/cpuinfo |grep "processor"|wc -l 上图可以看到是4颗,我现在跑满2颗 脚本如下 #! /bin/bash # filename killcpu.sh endless_loop() { echo -ne "i=0; while true do i=i+100; i=100 done&

  • javascript如何用递归写一个简单的树形结构示例

    现在有一个数据,需要你渲染出对应的列表出来: var data = [ {"id":1}, {"id":2}, {"id":3}, {"id":4}, ]; var str="<ul>"; data.forEach(function(v,i){ str+="<li><span>"+v.id+"</span></li>&

随机推荐