部署web页面实现文件上传操作
1、将zyFile2拷贝到ngixn安装目录
/usr/local/nginx/
zyFile2获取链接:https://pan.baidu.com/s/16CIznNCMihlhpCHAh6q12Q
2、修改nginx.conf文件
zyFile2中是成形的网页,直接拿来使用。设置静态首页为demo.html,该网页提交表单有一个upload事件,将该事件与fcgi进行绑定。
配置好nginx之后,nginx.conf为(删掉了无用的注释部分)
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server {
listen 80;
server_name localhost;
#部署静态网页
location / {
root zyFile2;
index demo.html;
}
#upload事件绑定端口为8002
location /upload{
fastcgi_pass localhost:8002;
include fastcgi.conf;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
}
3、编写一个fastcgi程序
将上传的文件的内容 - 提交到fastdfs存储节点
- 将上传文件内容保存到web服务器
- 调用fastdfs api将文件上传到fastdfs存储节点上
- 得到一文件ID
- 将文件和对应的fastdfs返回的文件ID存储到mysql数据库
- 将上传之后的文件从web服务器删除
用于上传文件的程序源代码(模仿echo.c,调研fcgi的api):
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include "fcgi_config.h"
#include "fcgi_stdio.h"
#include "fdfs_api.h"
#include "deal_mysql.h"
#include "make_log.h"
//full_data字符串首地址,full_data_len字符串长度,substr是要匹配的字符串
char* memstr(char* full_data, int full_data_len, char* substr){
if (full_data == NULL || full_data_len <= 0 || substr == NULL)
return NULL;
if (*substr == '\0') return NULL;
int sublen = strlen(substr);
char* cur = full_data;
int last_possible = full_data_len - sublen + 1;
for (int i = 0; i < last_possible; i++) {
if (*cur == *substr) {
if (memcmp(cur, substr, sublen) == 0) {
// found
return cur;
}
}
cur++;
}
return NULL;
}
//提取http请求里面的数据(就是两个分界线中间的部分)
int get_file_content(char* begin, char* end, int len, char* filename){
char *p = NULL;
// 取出并保存分界线字符串
char boundary[256] = {0};
// 从大串里面匹配子串“\r\n”。使得p 指向该行的\r字符
p = strstr(begin, "\r\n");
//拷贝begin到p-begin之间的字符(p指向了\r)
strncpy(boundary, begin, p-begin);
// p指向下一行行首
p += 2;
// 剩余字符串长度
len -= (p-begin);
printf("boundary: %s", boundary);
// begin记录第二行行首位置
begin = p;
// content-disp, 保存到begin中
p = strstr(begin, "\r\n");
// 移动到下一行行首
p += 2;
// 剩余数据长度
len -= (p-begin);
// 将此行的filename提取出来,pt指向 f
char* pt = strstr(begin, "filename=");
//将pt向后移动,为了让他指向文件名
pt += strlen("filename=");
// 指针指向文件名的第一个字符
pt ++;
// 找到文件名的结尾字符
char* q = strchr(pt, '"');
// 保存文件名
strncpy(filename, pt, q-pt);
printf("<br>filename: %s<br>", filename);
//查找本行的 content-type
begin = p;
// 跳过\r\n和空行(\r\n)
p += 4;
// 剩余数据长度
len -= (p-begin);
// 文件内容正式开始
begin = p;
// 从剩余数据块中找到结束分界线(分界线字符串存储在了boundary中)。
p = memstr(begin, len, boundary);
//说明当前串中不包含分界线
if(p == NULL) {
// 减去\r\n长度
p = end - 2;
}
else {
// 使得p指向\r\n
p -= 2;
}
// 将上传文件内容写入文件中
int fd = open(filename, O_CREAT|O_WRONLY, 0664);
write(fd, begin, p-begin);
close(fd);
return 0;
}
int store_data(char* filename, char* fileid){
// 连接数据库
MYSQL* conn = NULL;
//数据库名为test,用户名和密码都是root。msql_conn函数在deal_mysql.h中定义
conn = msql_conn("root", "root", "test");
if(conn == NULL){
LOG("upload_file", "mysql", "数据库连接失败!");
return -1;
}
// 设置数据库编码,否则,中文插入乱码
mysql_query(conn, "set names utf8");
// 插入数据
char buf[1024];
sprintf(buf, "insert into file (name, fileid) values ('%s', '%s')",
filename, fileid);
printf("<br> sql: %s<br>\n", buf);
// 执行sql语句
// 如果成功,它返回0。
if ( mysql_query (conn, buf) != 0 ) {
LOG("upload_file", "mysql", "数据插入失败");
return -1;
}
// 关闭数据库连接
mysql_close(conn);
return 0;
}
int main (){
//保证cgi程序不退出,阻塞等待请求
while (FCGI_Accept() >= 0){
int len = 0;
//获取post数据的长度。环境变量CONTENT_LENGTH对应的值是一个字符串
char *contentLength = getenv("CONTENT_LENGTH");
printf("Content-type: text/html\r\n"
"\r\n");
if (contentLength != NULL){
//将字符串转整型值
len = strtol(contentLength, NULL, 10);
}
if (len <= 0){
printf("No data from standard input.<p>\n");
}
else {
int ch;
char filename[128] = {0};
// 根据post数据长度, 创建对应大小的数组
char* file_data = (char*)malloc(len);
char *begin, *end, *p;
// 指针初始化
end = NULL;
begin = p = file_data;
for (int i = 0; i < len; i++){
if ((ch = getchar()) < 0){
printf("Error: Not enough bytes received on standard input<p>\n");
break;
}
*p = ch; // 每读一个字符,就将字符的值赋值给p
p++; //最后将所有的数据都存储在file_data里面
}
// end 指向文件结束位置
end = p;
// 得到文件內容
get_file_content(begin, end, len, filename);
// 文件上传到fastdfs
char fileid[1024] = {0};
//fileid是一个传出参数,用以记录文件id
//fdfs_upload_file在fdfs_api.h中定义
fdfs_upload_file(filename, fileid);
printf("<br>fileid: %s\n<br>", fileid);
// 将数据存储到数据库中
store_data(filename, fileid);
// 释放内存
free(file_data);
// 删除文件
unlink(filename);
}
} /* while */
return 0;
}
4、写一个脚本,实现启动spawn-fcgi
#!/bin/bash
#确保启动的时候能启动成功,如果之前开过该进程,则先杀死该进程
# stop
kill -9 $(ps aux | grep "./bin_cgi/upload.cgi" | grep -v grep | awk '{print $2}')
# start
spawn-fcgi -a 127.0.0.1 -p 8002 -f ./bin_cgi/upload.cgi
5、再写一个脚本,用于启动fastdfs(项目(1)中的内容)
#!/bin/bash
# 关闭tracker 和 storage服务
tracker_start(){
ps aux | grep fdfs_trackerd | grep -v grep > /dev/null
if [ $? -eq 0 ];then
echo "fdfs_trackerd 已经在运行中, 无需启动..."
else
sudo fdfs_trackerd /etc/fdfs/tracker.conf
if [ $? -ne 0 ];then
echo "tracker start failed ..."
else
echo "tracker start success ..."
fi
fi
}
storage_start(){
ps aux | grep fdfs_storaged | grep -v grep > /dev/null
if [ $? -eq 0 ];then
echo "fdfs_storaged 已经在运行中, 无需启动..."
else
sudo fdfs_storaged /etc/fdfs/storage.conf
if [ $? -ne 0 ];then
echo "storage start failed ..."
else
echo "storage start success ..."
fi
fi
}
echo "Operation:"
echo " start storage please input: storage"
echo " start tracker please input: tracker"
echo " start storage && tracker please input: all"
echo " stop storage && tracker input: stop"
read ARG
case $ARG in
storage)
storage_start
;;
tracker)
tracker_start
;;
all)
storage_start
tracker_start
;;
stop)
sudo fdfs_trackerd /etc/fdfs/tracker.conf stop
sudo fdfs_storaged /etc/fdfs/storage.conf stop
;;
*)
echo "nothing ......"
esac
6、测试一下
图片的上传功能成功了
7、查看机器里面的fastDFS里面是否真的存储了上传的文件
进入fastDFS的storage目录
根据屏幕上上传成功的返回值
查找对应的文件
图片真的存储在了storage里面了。
post提交数据的四种格式
post提交数据,而不是get,是出于安全考虑。get提交数据会把数据的内容写在http请求里面,这样不安全。
概述
- HTTP 请求分为四个部分:状态行、请求头、空行消息、主体
- 协议规定 POST 提交的数据必须放在消息主体(entity-body)中,但协议并没有规定数据必须使用什么编码方式。
- 开发者完全可以自己决定消息主体的格式
- 数据发送出去,还要服务端解析成功才有意义, 服务端通常是根据请求头(headers)中的 Content-Type 字段来获知请求中的消息主体是用何种方式编码,再对主体进行解析。
post提交数据的四种格式
第一种,content-Type: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&sub%5B%5D=1&sub%5B%5D=2&sub%5B%5D=3
第二种,application/json(后面是json数据)
POST http://www.example.com HTTP/1.1
Content-Type: application/json;charset=utf-8
{"title":"test","sub":[1,2,3]}
第三种,text/xml(后面是xml数据)
POST http://www.example.com HTTP/1.1
Content-Type: text/xml
<!--?xml version="1.0"?-->
<methodcall>
<methodname>examples.getStateName</methodname>
<params>
<value><i4>41</i4></value>
</params>
</methodcall>
第四种,multipart/form-data(后面跟着实际的数据格式)
------WebKitFormBoundaryPpL3BfPQ4cHShsBz
Content-Disposition: form-data; name="file"; filename="qw.png"
Content-Type: image/png
PNG
...................................................................................
...................................................................................
------WebKitFormBoundaryPpL3BfPQ4cHShsBz
Content-Disposition: form-data; name="tailor"
false
------WebKitFormBoundaryPpL3BfPQ4cHShsBz--
我们提交数据的方式是第四种。
可以看出,提交上来的信息被两行相同的boundary(即------WebKitFormBoundaryPpL3BfPQ4cHShsBz)包裹。因此,如果想解析post上来的信息,需要进行如下操作:
- 先读第一行 : 遇到分界线(boundary),说明后面就是我们要存储的内容
- 遇到第二个同样的分界线, 说明文件内容结束了.
//big代指整块数据,sub代指big中被包裹的数据,即我们提交的文件内容
char *p = strstr(big, sub)
注意事项:
- boundary是一个字符串,用来切分数据
- 在HTML协议中换行使用的是:"\r\n"
- BODY里面的bounday比HEADER里面的前面都多了“--”
上传文件流程梳理
- 浏览器将图片上传给服务器
- nginx检测到上传操作,将操作交给fastCGI
- fastCGI将收到的数据进行处理,提取出要存储的信息交给fastDFS的client进程进行存储
- client进程询问tracker,获取到storage的ip地址和端口号,将数据存储到storage中
下载文件
1、分析用户下载文件的流程
- 浏览器访问web服务器
- web服务器调用cgi程序
- cgi程序需要访问tracker
- tracker会提供storage的ip和端口
- cgi程序, 去访问storage,下载数据
- web服务器需要再次将数据发给浏览器
2、改进下载流程
- 通过浏览器访问web服务器
- 拿到一个地址, 直接通过这个地址, 来访问storage上的图片
3、如何实现改进版本?
- 有一个storage服务器
- 在上边部署一个nginx服务器
- 需要在nginx中添加一fastdfs模块
实现功能一通过ip地址+文件路径,让浏览器展示存储在该位置的图片
为了演示安装storage服务器而做的例子
安装插件fastdfs-nginx-module
获取链接:https://pan.baidu.com/s/1wxD3qBltUSEFevbrD9PTIw
第一步。配置nginx
1、进入nginx的安装目录
2、执行 ./configure --add-module=fastdfs-module的src目录
出现图片内容,则说明配置成功
3、执行make,报错
搜索该文件的路径
进入makefile来将对应的路径添加。
发现makefile中默认执行build操作,而build操作执行的是objs/makefile,因此去修改该makefile
打开objs中的makefile文件,在ALL_INCS中添加对应文件路径(-I指定头文件目录)
之后还会发现缺失头文件,以此类推的继续进行修改即可
4、执行make install
第二步、添加相应的conf文件
重启nginx,发现worker进程没有被开启,查看log发现缺失文件
缺失的三个文件
- 拷贝fastdfs-nginx-module/src中的mod_fastdfs.conf到/etc/fdfs中
- 从fastDFS的源码安装包中的conf中 将http.conf 拷贝到 /etc/fdfs
- 从nginx的源码安装包中的conf中mime.types拷贝到 /etc/fdfs
第三步、修改mod_fastdfs.conf文件信息
1、修改log日志存储位置(将路径与storage的log存储路径相同)
storage.conf截图
2、修改tracker_server对应的ip
3、检查存储节点的端口
4、检查存储节点属于哪个组
5、设置url_have_group_name的值为true
因为我们希望在url中看到图片的存储路径(这是例子的需求)
6、检查当前存储节点个数和存储路径(与storage.conf要保持一致)
storage.conf的截图
7、修改group_count,以及group_count对应的信息
改完了,累死了。
测试一下,现在nginx启动时,两个进程都能开启了
第四步、修改nginx.conf
该nginx是storage上的nginx。用于方便storage服务器来获取数据。
重启nginx之后测试一下。通过路径在浏览器中展示storage中存储所图片的功能实现
./nginx -s reload