用C写一个web服务器(四) CGI协议
code[class*="language-"],
pre[class*="language-"] {
background-color: #fdfdfd;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
margin-bottom: 1em;
}
/* Inline code */
:not(pre) > code[class*="language-"] {
position: relative;
padding: .2em;
-webkit-border-radius: 0.3em;
-moz-border-radius: 0.3em;
-ms-border-radius: 0.3em;
-o-border-radius: 0.3em;
border-radius: 0.3em;
color: #c92c2c;
border: 1px solid rgba(0, 0, 0, 0.1);
display: inline;
white-space: normal;
}
pre[class*="language-"]:before,
pre[class*="language-"]:after {
content: '';
z-index: -2;
display: block;
position: absolute;
bottom: 0.75em;
left: 0.18em;
width: 40%;
height: 20%;
max-height: 13em;
-webkit-box-shadow: 0px 13px 8px #979797;
-moz-box-shadow: 0px 13px 8px #979797;
box-shadow: 0px 13px 8px #979797;
-webkit-transform: rotate(-2deg);
-moz-transform: rotate(-2deg);
-ms-transform: rotate(-2deg);
-o-transform: rotate(-2deg);
transform: rotate(-2deg);
}
:not(pre) > code[class*="language-"]:after,
pre[class*="language-"]:after {
right: 0.75em;
left: auto;
-webkit-transform: rotate(2deg);
-moz-transform: rotate(2deg);
-ms-transform: rotate(2deg);
-o-transform: rotate(2deg);
transform: rotate(2deg);
}
.token.comment,
.token.block-comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: #7D8B99;
}
.token.punctuation {
color: #5F6364;
}
.token.property,
.token.tag,
.token.boolean,
.token.number,
.token.function-name,
.token.constant,
.token.symbol,
.token.deleted {
color: #c92c2c;
}
.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.function,
.token.builtin,
.token.inserted {
color: #2f9c0a;
}
.token.operator,
.token.entity,
.token.url,
.token.variable {
color: #a67f59;
background: rgba(255, 255, 255, 0.5);
}
.token.atrule,
.token.attr-value,
.token.keyword,
.token.class-name {
color: #1990b8;
}
.token.regex,
.token.important {
color: #e90;
}
.language-css .token.string,
.style .token.string {
color: #a67f59;
background: rgba(255, 255, 255, 0.5);
}
.token.important {
font-weight: normal;
}
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}
.namespace {
opacity: .7;
}
@media screen and (max-width: 767px) {
pre[class*="language-"]:before,
pre[class*="language-"]:after {
bottom: 14px;
-webkit-box-shadow: none;
-moz-box-shadow: none;
box-shadow: none;
}
}
/* Plugin styles */
.token.tab:not(:empty):before,
.token.cr:before,
.token.lf:before {
color: #e0d7d1;
}
/* Plugin styles: Line Numbers */
pre[class*="language-"].line-numbers {
padding-left: 0;
}
pre[class*="language-"].line-numbers code {
padding-left: 3.8em;
}
pre[class*="language-"].line-numbers .line-numbers-rows {
left: 0;
}
/* Plugin styles: Line Highlight */
pre[class*="language-"][data-line] {
padding-top: 0;
padding-bottom: 0;
padding-left: 0;
}
pre[data-line] code {
position: relative;
padding-left: 4em;
}
pre .line-highlight {
margin-top: 0;
}
-->
code {
position: relative;
}
.line-numbers .line-numbers-rows {
position: absolute;
pointer-events: none;
top: 0;
font-size: 100%;
left: -3.8em;
width: 3em; /* works for line-numbers below 1000 lines */
letter-spacing: -1px;
border-right: 1px solid #999;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.line-numbers-rows > span {
pointer-events: none;
display: block;
counter-increment: linenumber;
}
.line-numbers-rows > span:before {
content: counter(linenumber);
color: #999;
display: block;
padding-right: 0.8em;
text-align: right;
}
-->
前言
时隔一个多月,终于又有时间来更新我的服务器了,这次更新主要实现一下 CGI 协议。
先放上GitHub链接 tinyServer-GitHub-枕边书
作为一个服务器,基本要求是能受理请求,提取信息并将消息分发给 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 写业务逻辑类的代码真的能折磨死人,仅一个字符串的操作就能让人欲仙欲死了。常用 C 开发的应该有各种函数库吧,就算没有自己的库也要去找开源库,自己造不了所有的轮子。
感觉服务器又被自己写残了,留了很多业务类型的坑也不知道什么时候会填,希望能有时间写一个工业级的东西。。。
如果您觉得本文对您有帮助,可以点击下面的 推荐 支持一下我。博客一直在更新,欢迎 关注 。
参考: The Common Gateway Interface (CGI) Version 1.1
用C写一个web服务器(四) CGI协议的更多相关文章
- 用C写一个web服务器(二) I/O多路复用之epoll
.container { margin-right: auto; margin-left: auto; padding-left: 15px; padding-right: 15px } .conta ...
- 使用node.js 文档里的方法写一个web服务器
刚刚看了node.js文档里的一个小例子,就是用 node.js 写一个web服务器的小例子 上代码 (*^▽^*) //helloworld.js// 使用node.js写一个服务器 const h ...
- 使用Node.js原生API写一个web服务器
Node.js是JavaScript基础上发展起来的语言,所以前端开发者应该天生就会一点.一般我们会用它来做CLI工具或者Web服务器,做Web服务器也有很多成熟的框架,比如Express和Koa.但 ...
- 用java写一个web服务器
一.超文本传输协议 Web服务器和浏览器通过HTTP协议在Internet上发送和接收消息.HTTP协议是一种请求-应答式的协议——客户端发送一个请求,服务器返回该请求的应答.HTTP协议使用可靠的T ...
- 手写一个Web服务器,极简版Tomcat
网络传输是通过遵守HTTP协议的数据格式来传输的. HTTP协议是由标准化组织W3C(World Wide Web Consortium,万维网联盟)和IETF(Internet Engineerin ...
- Tomcat源码分析 (一)----- 手写一个web服务器
作为后端开发人员,在实际的工作中我们会非常高频地使用到web服务器.而tomcat作为web服务器领域中举足轻重的一个web框架,又是不能不学习和了解的. tomcat其实是一个web框架,那么其内部 ...
- 用C写一个web服务器(一) 基础功能
.container { margin-right: auto; margin-left: auto; padding-left: 15px; padding-right: 15px } .conta ...
- 用C写一个web服务器(三) Linux下用GCC进行项目编译
.container { margin-right: auto; margin-left: auto; padding-left: 15px; padding-right: 15px } .conta ...
- java写的web服务器
经常用Tomcat,不知道的以为Tomcat很牛,其实Tomcat就是用java写的,Tomcat对jsp的支持做的很好,那么今天我们用java来写一个web服务器 //首先得到一个server, S ...
随机推荐
- [转]Installing Memcached on Windows
Installing Memcached on Windows 原文链接https://commaster.net/content/installing-memcached-windows Sub ...
- Hadoop安全机制之令牌
介绍 Hadoop中的安全机制包括认证和授权.而Hadoop RPC中采用SASL(Simple Authentication and Security Layer,简单认证和安全层)进行安全认证,具 ...
- CodeSmith生成实体的分页读取规则
首先.我得向咱们博客园提个意见,能不能我写的东西就给预保存下呢?刚才我写半天,只因为这个不给力的IE浏览器死了,导致我白写了,如果这要是那个大神直接在这上面写的非常有技术含量的贴着会因此而丢失实在是有 ...
- 第一篇 Rewrite规则简介
1.Rewirte主要的功能就是实现URL的跳转,它的正则表达式是基于Perl语言.可基于服务器级的(httpd.conf)和目录级的(.htaccess)两种方式.如果要想用到rewrite模块,必 ...
- nginx源码分析——configure脚本
源码:nginx 1.13.0-release 一.前言 在分析源码时,经常可以看到类似 #if (NGX_PCRE) .... #endif 这样的代码段,这样的设计可以在不改动源码的 ...
- lightoj1281快速幂+数学知识
https://vjudge.net/contest/70017#problem/E 后半部分快速幂就能求出来,要注意03lld不然001是输出错误为1.前半部分用log10() 对于给定的一个数n, ...
- 在Android Studio上测试运行,Unity发布成Android包过程中所遇到的问题及解决方案
问题一:Exception: JNI: Init'd AndroidJavaObject with null ptr 解决方法: 所有关于JNI出现的问题,只有三种错误存在,第一是需要在真机上运行测试 ...
- tomcat启动一闪而过问题的解决
1.今天使用startup.bat启动tomcat报错,现象是一闪而过,在logs文件夹中有没有日志文件, 可以在控制台输入startup.bat,如下: 但是也没有看出什么太有用的错误,然后可以 ...
- java内存模型1
并发编程模型的分类 在并发编程中,我们需要处理两个关键问题:线程之间如何通信及线程之间如何同步(这里的线程是指并发执行的活动实体).通信是指线程之间以何种机制来交换信息.在命令式编程中,线程之间的通信 ...
- 非阻塞式线程安全列表-ConcurrentLinkedDeque
一.ConcurrentLinkedDeque public class ConcurrentLinkedDeque<E> extends AbstractCollection<E& ...