一、 CGI是什么
- nginx服务器接收http请求并进行解析,但无法进行处理,而是交由CGI程序来处理。
- 实际使用时,服务器会创建一个CGI进程。CGI程序会启动CGI解析器、连接其它服务器(如数据库服务器),进行逻辑处理后返回给服务器,服务器将结果发送给客户端。
二、fastCGI
- CGI:客户端每进行一次请求,就需要创建一个CGI进程,会造成频繁的进程创建与销毁。
- FastCGI:使用持续的进程来处理一连串的请求。这些进程由FastCGI进程管理器启动,而不是web服务器
- 一般fastCGI和nginx安装在同一台主机上,数据库安装在另一台主机上。
fastCGI进程不是由服务器创建的,因此它们之间通信可以应该采用本地套接字或者是网络通信。
具体流程:
请求的url:http://localhost/login?user=zhang3&passwd=123456&age=12&sex=man
命令(/login):去掉协议、域名IP、端口,尾部有文件名则去掉,去掉?及后面的字符串
当客户端点击上传时报错,报错信息在/usr/local/nginx/logs/error.log中,在其中就能看到出错时的请求行是什么
如果看到的是请求行, 如何找处理指令?
POST /upload/UploadAction HTTP/1.1
GET /?username=tom&phone=123&email=hello%40qq.com&date=2018-01-01&sex=male&class=3&rule=on HTTP/1.1
- 如果是post, 处理指令就是请求行的第二部分
- 如果是get, 处理指令就是请求行的第二部分, ? 以前的内容
- 1.浏览器解析url,向服务器发出http请求
- 2.服务器发现自己无法处理该请求,就会将请求转发给fastCGI进程处理
- 3.fastCGI读取配置文件(例如用户保存的登录信息),连接数据库服务器进行查询
- 4.fastCGI执行逻辑处理,将处理结果返回给服务器
- 5.服务器将结果返回给浏览器
三、安装
fcgi-2.4.1-SNAP-0910052249.tar.gz
spawn-fcgi-1.6.5.tar.gz
四、nginx怎么把请求转发给fastcgi进程
location /login
{
# 转发这个数据, fastCGI进程
fastcgi_pass 地址信息:端口;
# fastcgi.conf 和nginx.conf在同一级目录: /usr/local/nginx/conf
# 这个文件中定义了一些http通信的时候用到环境变量, nginx赋值的
include fastcgi.conf;
}
地址信息: 一般nginx和fastcgi部署在同一台电脑上,因此写localhost、127.0.0.1,或者实际的IP地址。
端口: 找一个空闲的没有被占用的端口即可
五、怎么启动spawn-fcgi
当fastCGI程序编写完毕后,可以按下面的方式启动(IP和端口要与上面一样)
(注意:这里不能写成localhost,而是写具体的IP,如127.0.0.1)
spawn-fcgi -a IP地址 -p 端口 -f fastcgi可执行程序
六、fastCGI程序怎么写
- 发送与接收数据
标准输入和标准输出已经被重定向到 nginx与fastCGI进程管理器的通信fd 了
因此,使用printf就是向nginx发送数据,getchar就是接收nginx转发过来的命令
- 请求体的长度
get方式肯定没有请求体,无Content-Length,无Content-Type 而post方式有
该信息被初始化到环境变量中了,在fastCGI程序中可以使用getenv("CONTENT_LENGTH")得到长度
-char * contentLength转10进制数
int len = strtol(contentLength, NULL, 10);
-读写数据块
fread/fwrite是标准C库函数,而read/write是linux系统库函数,因此这里使用通用的前者。
//由于标准输入与输出已经被重定向过了,这里的FILE *stream直接写stdin或者stdout即可
#include <stdio.h>
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
// http://localhost/login?user=zhang3&passwd=123456&age=12&sex=man
// 要包含的头文件
#include "fcgi_config.h" // 可选,看是否要读取配置文件
#include "fcgi_stdio.h" // 必须
// 编写代码的流程
int main()
{
// FCGI_Accept()是一个阻塞函数, nginx给fastcgi程序发送数据的时候解除阻塞
while (FCGI_Accept() >= 0)
{
// 1. 接收数据
// 1.1 get方式提交数据 - 数据在请求行的第二部分
// user=zhang3&passwd=123456&age=12&sex=man
char *text = getenv("QUERY_STRING");
// 1.2 post方式提交数据
char *contentLength = getenv("CONTENT_LENGTH");
// 根据长度大小判断是否需要循环
// 2. 按照业务流程进行处理
// 3. 将处理结果发送给nginx
// 数据回发的时候, 需要告诉nginx处理结果的格式 - 假设是html格式
printf("Content-type: text/html;charset=utf-8\r\n\r\n");
printf("<html>处理结果</html>");
}
}
七、实际使用
1 启动nginx 2 启动spawn-fcgi 3 打开http://192.168.232.132/demo.html
先编译一份fastcgi程序,这里将就使用example提供的代码。
book@100ask:~/Downloads/fcgi-2.4.1-SNAP-0910052249/examples$ gcc echo.c -o app -lfcgi
运行程序前,先使用ldd检查一下它的动态库是不是都能找到
book@100ask:~/Downloads/fcgi-2.4.1-SNAP-0910052249/examples$ ldd app
linux-vdso.so.1 (0x00007ffeb4be2000)
libfcgi.so.0 => not found
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f36a622a000)
/lib64/ld-linux-x86-64.so.2 (0x00007f36a681e000)
由上可知,libfcgi.so动态库找不到(注意,后面的.0是版本号,不要管),因此先find找一下:
book@100ask:~/Downloads/fcgi-2.4.1-SNAP-0910052249/examples$ sudo find / -name libfcgi.so
find: ‘/run/user/1001/gvfs’: Permission denied
/home/book/Downloads/fcgi-2.4.1-SNAP-0910052249/libfcgi/.libs/libfcgi.so
/usr/local/lib/libfcgi.so
可以看到,/usr/local/lib目录下有该动态库文件,需要把这个路径加入到 /etc/ld.so.conf ,然后执行sudo ldconfig。
/etc/ld.so.conf记录了编译时使用的动态库的路径,也就是加载so库的路径。安装源码包时,如果没有指定--prefix为/usr/lib,就会默认安装在usr/local下,也就是动态库在/usr/local/lib下,导致系统找不到该动态库。因此,最好将/usr/local/lib这个目录添加到该文件中
启动fcgi进程
spawn-fcgi -a 127.0.0.1 -p 10000 -f ./app
八、fastcgi.conf里是fastCGI的环境变量
文件位置:/usr/local/nginx/conf/fastcgi.conf
环境变量 | 说明 |
---|---|
SCRIPT_FILENAME | 脚本文件请求的路径 |
QUERY_STRING | 请求的参数;如?app=123 |
REQUEST_METHOD | 请求的动作(GET,POST) |
CONTENT_TYPE | 请求头中的Content-Type字段 |
CONTENT_LENGTH | 请求头中的Content-length字段 |
SCRIPT_NAME | 脚本名称(就是指令) |
REQUEST_URI | 请求的地址不带参数 |
DOCUMENT_URI | 与$uri相同 |
DOCUMENT_ROOT | 网站的根目录。在server配置中root指令中指定的值 |
SERVER_PROTOCOL | 请求使用的协议,通常是HTTP/1.0或HTTP/1.1 |
GATEWAY_INTERFACE | cgi 版本 |
SERVER_SOFTWARE | nginx 版本号,可修改、隐藏 |
REMOTE_ADDR | 客户端IP |
REMOTE_PORT | 客户端端口 |
SERVER_ADDR | 服务器IP地址 |
SERVER_PORT | 服务器端口 |
SERVER_NAME | 服务器名,域名在server配置中指定的server_name |
九、
Post提交数据常用的四种编码方式
-
application/x-www-form-urlencoded
# 请求行 POST http://www.example.com HTTP/1.1 # 请求头 Content-Type: application/x-www-form-urlencoded;charset=utf-8 # 空行 # 请求数据(向服务器提交的数据) title=test&user=kevin&passwd=32222
-
application/json
POST / HTTP/1.1 Content-Type: application/json;charset=utf-8 {"title":"test","sub":[1,2,3]}
-
text/xml
POST / HTTP/1.1 Content-Type: text/xml <?xml version="1.0" encoding="utf8"?> <methodcall> <methodname color="red">examples.getStateName</methodname> <params> <value><i4>41</i4></value> </params> </methodcall>
-
multipart/form-data 传输大文件,多个数据块
- 在请求体中,首先是随机生成的一对字符串作为分割符,其中才是要传输的数据
- 传输的数据中,首先是一些键值对用来描述文件属性,然后才是文件内容。
- Content-Disposition可以自己随便加,但是Content-Type是有固定格式的Content-Type的对应写法
POST / HTTP/1.1 Content-Type: multipart/form-data # 发送的数据 ------WebKitFormBoundaryPpL3BfPQ4cHShsBz \r\n Content-Disposition: form-data; name="file"; filename="qw.png" Content-Type: image/png\r\n; md5="xxxxxxxxxx" \r\n .............文件内容................ .............文件内容................ ------WebKitFormBoundaryPpL3BfPQ4cHShsBz-- Content-Disposition: form-data; name="file"; filename="qw.png" Content-Type: image/png\r\n; md5="xxxxxxxxxx" \r\n .............文件内容................ .............文件内容................ ------WebKitFormBoundaryPpL3BfPQ4cHShsBz--
一些问题:
访问时,仍然是访问nginx监听的端口,而不是fcgi的端口。
出现spawn-fcgi: child exited with: 126时,表示程序不是可执行的, chmod 777 xxx就好
注意响应空行的问题