【HTTP图片服务器】【项目记录5】:服务器设计

以下为正文:

//image_server.cc

#if defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#elif defined(_MSC_VER)
#pragma warning(disable : 4996)
#endif

#include <fstream>
#include <signal.h>
#include <sys/stat.h>
#include "httplib.h"
#include "db.hpp"
#include "time_md5.hpp"

//文件读写功能
class FileUtil {
public:
    static bool Write(const std::string& file_name,
                      const std::string& content) {
        std::ofstream file(file_name.c_str());
        if (!file.is_open()) {
            return false;

        }
        file.write(content.c_str(), content.length());
        file.close();
        return true;

    }

    static bool Read(const std::string& file_name,
                     std::string* content) {
        std::ifstream file(file_name.c_str());
        if (!file.is_open()) {
            return false;

        }
        struct stat st;
        stat(file_name.c_str(), &st);
        content->resize(st.st_size);
        // 直接把整个文件都读完
        // 需要先知道该文件的大小
        // char* 缓冲区长度
        // int 读取多长
        file.read((char*)content->c_str(), content->size());
        file.close();
        return true;

    }
};

MYSQL* mysql = NULL;

int main() {
    using namespace httplib;

    mysql = image_system::MySQLInit();
    image_system::ImageTable image_table(mysql);
    signal(SIGINT, [](int) {
           image_system::MySQLRelease(mysql);
           exit(0);

           });
    Server server;
    
    //[&image_table] 是 lambda 的重要特性, 捕获变量
    // 捕捉之后就可以访问其中 & 的含义是相当于按引用捕获
    //功能1:上传图片
    server.Post("/image", [&image_table](const Request& req, Response& resp) {
                    Json::FastWriter writer;
                    Json::Value resp_json;
                    printf("上传图片\n");
                    // 1. 对参数进行校验
                    auto ret = req.has_file("upload");
                    if (!ret) {
                        printf("文件上传出错!\n");
                        resp.status = 404;
                        // 使用 json 格式组织一个返回结果
                        resp_json["ok"] = false;
                        resp_json["reason"] = "上传文件出错, 没有需要的upload 字段";
                        resp.set_content(writer.write(resp_json), "application/json");
                        return;
                    }
                    // 2. 根据文件名获取到文件数据 file 对象
                    const auto& file = req.get_file_value("upload");
                    // file.filename;
                    // file.content_type;
                
                    // 3. 把图片属性信息插入到数据库中
                    auto body = req.body.substr(file.offset, file.length);

                    Json::Value image;
                    image["image_name"] = file.filename;
                    image["size"] = (int)file.length;
                    image["upload_time"] = TimeUtil::FormatTime();
                    image["md5"] = StringMD5(body);
                    image["content_type"] = file.content_type;
                    image["path"] = "./" + file.filename;
                    ret = image_table.Insert(image);
                    
                    if (!ret) {
                        printf("image_table Insert failed!\n");
                        resp_json["ok"] = false;
                        resp_json["reason"] = "数据库插入失败!";
                        resp.status = 500;
                        resp.set_content(writer.write(resp_json), "application/json");
                        return;
                    }

                    // 4. 把图片保存到指定的磁盘目录中
                    FileUtil::Write(file.filename,body);

                    // 5. 构造一个响应数据通知客户端上传成功
                    resp_json["ok"] = true;
                    resp.status = 200;
                    resp.set_content(writer.write(resp_json), "application/json");
                    return;
        });

	//功能2:获取所有图片信息
    server.Get("/image", [&image_table](const Request& req,
                                        Response& resp) {
               (void)req;
               printf("获取所有图片信息\n");
               Json::Value resp_json;
               Json::FastWriter writer;
               // 1. 调用数据库接口来获取数据
               bool ret = image_table.SelectAll(&resp_json);
               if (!ret) {
               printf("查询数据库失败!\n");
               resp_json["ok"] = false;
               resp_json["reason"] = "查询数据库失败!";
               resp.status = 500;
               resp.set_content(writer.write(resp_json), "application/json");
               return;
               }
               // 2. 构造响应结果返回给客户端
               resp.status = 200;
               resp.set_content(writer.write(resp_json), "application/json");
               });
	//功能3:获取指定图片信息
    server.Get(R"(/image/(\d+))", [&image_table](const Request& req, Response& resp) {
               Json::FastWriter writer;
               Json::Value resp_json;
               // 1. 先获取到图片 id
               int image_id = std::stoi(req.matches[1]);
               printf("获取 id 为 %d 的图片信息!\n", image_id);
               // 2. 再根据图片 id 查询数据库
               bool ret = image_table.SelectOne(image_id, &resp_json);
               if (!ret) {
               printf("数据库查询出错!\n");
               resp_json["ok"] = false;
               resp_json["reason"] = "数据库查询出错";
               resp.status = 404;
               resp.set_content(writer.write(resp_json), "application/json");
               return;
               }
               // 3. 把查询结果返回给客户端
               resp_json["ok"] = true;
               resp.set_content(writer.write(resp_json), "application/json");
               return;
               });
	
	//功能4:查看指定图片
    server.Get(R"(/show/(\d+))", [&image_table](const Request& req,
                                                Response& resp) {
               Json::FastWriter writer;
               Json::Value resp_json;
               // 1. 根据 image_id 去数据库中查到对应的目录
               int image_id = std::stoi(req.matches[1]);
               printf("获取 id 为 %d 的图片内容!\n", image_id);
               Json::Value image;
               bool ret = image_table.SelectOne(image_id, &image);
               if (!ret) {
               printf("读取数据库失败!\n");
               resp_json["ok"] = false;
               resp_json["reason"] = "数据库查询出错";
               resp.status = 404;
               resp.set_content(writer.write(resp_json), "application/json");
               return;
               }
               // 2. 根据目录找到文件内容, 读取文件内容
               std::string image_body;
               printf("%s\n", image["path"].asCString());
               ret = FileUtil::Read(image["path"].asString(), &image_body);
               if (!ret) {
                   printf("读取图片文件失败!\n");
                   resp_json["ok"] = false;
                   resp_json["reason"] = "读取图片文件失败";
                   resp.status = 500;
                   resp.set_content(writer.write(resp_json), "application/json");
                   return;
               }
               // 3. 把文件内容构造成一个响应
               resp.status = 200;
               // 不同的图片, 设置的 content type 是不一样的. 
               // 如果是 png 应该设为 image/png
               // 如果是 jpg 应该设为 image/jpg
               resp.set_content(image_body, image["content_type"].asCString());
    });

	//功能5:删除指定图片及信息
    server.Delete(R"(/image/(\d+))", [&image_table](const Request& req, Response& resp) {
                  Json::FastWriter writer;
                  Json::Value resp_json;

                  // 1. 根据 image_id 去数据库中查到对应的目录
                  int image_id = std::stoi(req.matches[1]);
                  printf("删除 id 为 %d 的图片!\n", image_id);
                  // 2. 查找到对应文件的路径
                  Json::Value image;
                  bool ret = image_table.SelectOne(image_id, &image);
                  if (!ret) {
                  printf("删除图片文件失败!\n");
                  resp_json["ok"] = false;
                  resp_json["reason"] = "删除图片文件失败";
                  resp.status = 404;
                  resp.set_content(writer.write(resp_json), "application/json");
                  return;
                  }
                  // 3. 调用数据库操作进行删除
                  ret = image_table.Delete(image_id);
                  if (!ret) {
                      printf("删除图片文件失败!\n");
                      resp_json["ok"] = false;
                      resp_json["reason"] = "删除图片文件失败";
                      resp.status = 404;
                      resp.set_content(writer.write(resp_json), "application/json");
                      return;
                  }
                  // 4. 删除磁盘上的文件
                  unlink(image["path"].asCString());
                  // 5. 构造响应
                  resp_json["ok"] = true;
                  resp.status = 200;
                  resp.set_content(writer.write(resp_json), "application/json");
    });

    //设置静态文件目录
    server.set_base_dir("./wwwroot");
    server.listen("0.0.0.0", 9094);

    return 0;
}

上述文件中使用到的 “获取当前时间” 与 “计算文件md5值” 功能进行了另外的封装:

//time_md5.hpp

#pragma once
#include <iostream>
#include <sys/time.h>
#include <openssl/md5.h>

class TimeUtil {
public:
    static int64_t TimeStamp() {
        struct timeval tv;
        ::gettimeofday(&tv, NULL);
        return tv.tv_sec;

    }

    static int64_t TimeStampMS() {
        struct timeval tv;
        ::gettimeofday(&tv, NULL);
        return tv.tv_sec * 1000 + tv.tv_usec / 1000;

    }

    static std::string FormatTime() {
        time_t time_stamp = TimeStamp();
        struct tm t;
        localtime_r(&time_stamp, &t);
        char buf[100] = {0};
        strftime(buf, sizeof(buf), "%Y/%m/%d %H:%M:%S", &t);
        return std::string(buf);

    }
};

std::string StringMD5(const std::string& str) {
    const int MD5LENTH = 16;
    unsigned char MD5result[MD5LENTH];
    // 调用 openssl 的函数计算 md5
    MD5((const unsigned char*)str.c_str(),str.size(),MD5result);
    // 转换成字符串的形式方便存储和观察
    char output[1024] = {0};
    int offset = 0;
    for (int i = 0; i < MD5LENTH; ++i) {
        offset += sprintf(output + offset, "%x", MD5result[i]);
    }
    return std::string(output);
}

image_server.cc 的 Makefile:

FLAG=-L /usr/lib64/mysql -lmysqlclient -ljsoncpp -lcrypto -g
 
all:image_server
	 
image_server:image_server.cc
		g++ $^ -o $@ -std=c++11 -lpthread $(FLAG)
		 
.PHONY:clean
clean:
		rm image_server

另外在同一目录下新建了一个静态文件目录,用来存放主页:

mkdir wwwroot

主页设计及其他功能设计请见下一篇。

本项目是基于 C++ 的 HTTP 服务器,故选择了 cpp-httplib 库使用。

  1. 其最新版本请见网站:https://github.com/yhirose/cpp-httplib
  2. 本文所用版本请见网站:https://github.com/Dex5wu/Project-Image-Server/blob/master/httplib.h

何为 Lambda表达式 与 原始字符串?

  1. Lambda表达式 与 原始字符串 都是 C++11 新特性。
  2. 本文使用了 Lambda表达式 与 原始字符串 来限定客户端的 URL 使用方式,使其能并且只能通过 image_id 去调用指定图片及其信息。

下一篇:【HTTP图片服务器】【项目记录6】:测试全部功能

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值