一、服务器 API 设计
要实现一个 HTTP 服务器,HTTP 服务器需要接受到 http请求,再返回 http响应,此处需要约定不同的请求来表示不同的操作方式,例如:有些请求表示上传图片,有些请求表示查看图片,有些表示删除图片。
此处我们使用 RESTful风格 的设计。
何为 RESTful风格 ?
- http method 来表示操作的动词: POST增、DELETE删、PUT改、GET查。
- http path表示操作的对象。
- 补充信息用 body 传递,body 中通常使用 JSON 格式的数据,响应数据通常也用 JSON 格式组织。
何为 JSON ?
- JSON 源于 JavaScript,是一种轻量级的数据交换格式,最主要的用途之一就是序列化。
- 对象格式一般为形如 “key:value” 的键值对。
- JSON 的优点:简洁易读易编写,支持任何类型,扩展性高。
如何使用 JSON ?
- 本项目使用语言为 C++,故选用 jsoncpp 库操作和解析 JSON 格式数据。
- Reader::parse (Reader是类,parse是方法),把一个 JSON 字符串转成 Json::Value 对象。
- Write::write (Write类,write方法),把一个 Json::Value 对象转成 JSON 字符串。
- 如何安装 jsoncpp:yum install jsoncpp-devel
具体设计:
1、上传图片
请求:
POST /image
Content-Type: application/x-www-form-urlencoded
------WebKitFormBoundary5muoelvEmAAVUyQB
Content-Disposition: form-data; name="filename";
filename="测试.jpg" Content-Type: image/jpeg
.....[图片].....
响应:
HTTP/1.1 200 OK
{
"ok": true,
}
2、查看所有图片信息
请求:
GET /image/
响应:
HTTP/1.1 200 OK
[
{
"image_id": 1,
"image_name": "测试.png",
"content_type": "image/png",
"md5": "123456" ,
···
}
{
"image_id": 2,
"image_name": "测试2.png",
"content_type": "image/png",
"md5": "654321" ,
···
}
]
3、查看指定图片元信息
请求:
GET /image/:image_id
响应:
HTTP/1.1 200 OK
{
"image_id": 1,
"image_name": "测试.png",
"content_type": "image/png",
"md5": "123456"
}
4、查看指定图片内容
请求:
GET /image/show/:image_id
响应:
HTTP/1.1 200 OK
content-type: image/png
[图片]
5、删除图片
请求:
DELETE /image/:image_id
响应:
HTTP/1.1 200 OK
{
"ok": true
}
二、封装数据库操作
//db.hpp
#include <stdint.h>
#include <string>
#include <memory>
#include <mysql/mysql.h>
#include <json/json.h>
namespace image_system {
static MYSQL* MySQLInit() {
MYSQL* connect_fd = mysql_init(NULL);
if (mysql_real_connect(connect_fd, "127.0.0.1", "root", "12345678",
"image_system", 3306, NULL, 0) == NULL) {
printf("连接失败! %s\n", mysql_error(connect_fd));
return NULL;
}
mysql_set_character_set(connect_fd, "utf8");
return connect_fd;
}
static void MySQLRelease(MYSQL* mysql) {
mysql_close(mysql);
}
class ImageTable {
public:
ImageTable(MYSQL* mysql) : mysql_(mysql) { }
bool Insert(const Json::Value& image) {
char sql[4096] = { 0 };
sprintf(sql, "insert into image_table values(null, '%s', %d, '%s','%s', '%s', '%s')",
image["name"].asCString(), image["size"].asInt(), image["upload_time"].asCString(),
image["md5"].asCString(), image["content_type"].asCString(),
image["path"].asCString());
int ret = mysql_query(mysql_, sql);
if (ret != 0) {
printf("执行 sql 失败! sql=%s, %s\n", sql, mysql_error(mysql_));
return false;
}
return true;
}
bool SelectAll(Json::Value* images) {
char sql[1024 * 4] = { 0 };
sprintf(sql, "select * from image_table");
int ret = mysql_query(mysql_, sql);
if (ret != 0) {
printf("执行 sql 失败! %s\n", mysql_error(mysql_));
return false;
}
MYSQL_RES* result = mysql_store_result(mysql_);
if (result == NULL) {
printf("获取结果失败! %s\n", mysql_error(mysql_));
return false;
}
int rows = mysql_num_rows(result);
for (int i = 0; i < rows; ++i) {
MYSQL_ROW row = mysql_fetch_row(result);
Json::Value image;
image["image_id"] = atoi(row[0]);
image["image_name"] = row[1];
image["size"] = atoi(row[2]);
image["upload_time"] = row[3];
image["md5"] = row[4];
image["content_type"] = row[5];
image["path"] = row[6];
images->append(image);
}
return true;
}
bool SelectOne(int32_t image_id, Json::Value* image) {
char sql[1024 * 4] = { 0 };
sprintf(sql, "select * from image_table where image_id = %d", image_id);
int ret = mysql_query(mysql_, sql);
if (ret != 0) {
printf("执行 sql 失败! %s\n", mysql_error(mysql_));
return false;
}
MYSQL_RES* result = mysql_store_result(mysql_);
if (result == NULL) {
printf("获取结果失败! %s\n", mysql_error(mysql_));
return false;
}
int rows = mysql_num_rows(result);
if (rows != 1) {
printf("查找结果不为 1 条. rows = %d!\n", rows);
return false;
}
MYSQL_ROW row = mysql_fetch_row(result);
(*image)["image_id"] = atoi(row[0]);
(*image)["image_name"] = row[1];
(*image)["size"] = atoi(row[2]);
(*image)["upload_time"] = row[3];
(*image)["md5"] = row[4];
(*image)["content_type"] = row[5];
(*image)["path"] = row[6];
return true;
}
bool Delete(int image_id) {
char sql[1024 * 4] = { 0 };
sprintf(sql, "delete from image_table where image_id=%d", image_id);
int ret = mysql_query(mysql_, sql);
if (ret != 0) {
printf("执行 sql 失败! sql=%s, %s\n", sql, mysql_error(mysql_));
return false;
}
return true;
}
private:
MYSQL* mysql_;
};
} // end image_system
测试db.hpp:
//db_test.cc
#if defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#elif defined(_MSC_VER)
#pragma warning(disable : 4996)
#endif
#include <iostream>
#include "db.hpp"
using namespace image_system;
void TestImageTable() {
bool ret = false;
Json::StyledWriter writer;
MYSQL* mysql = MySQLInit();
Json::Value image;
image["image_name"] = "测试.jpg";
image["size"] = 16 * 1024;
image["upload_time"] = "2020/11/03";
image["md5"] = "123456";
image["content_type"] = "image/jpg";
image["path"] = "./测试.jpg";
std::cout << "==============测试插入=====================" << std::endl;
ImageTable image_table(mysql);
ret = image_table.Insert(image);
std::cout << "Insert: " << ret << std::endl;
std::cout << "==============测试查找=====================" << std::endl;
Json::Value images;
ret = image_table.SelectAll(&images);
std::cout << "SelectAll: " << ret << std::endl
<< writer.write(images) << std::endl;
Json::Value image_out;
ret = image_table.SelectOne(1, &image_out);
std::cout << "SelectOne: " << ret << std::endl
<< writer.write(image_out) << std::endl;
std::cout << "==============测试删除=====================" << std::endl;
int image_id = 2;
ret = image_table.Delete(image_id);
std::cout << "Delete: " << ret << std::endl;
MySQLRelease(mysql);
}
int main() {
TestImageTable();
return 0;
}
db_test.cc的Makefile:
FLAGS=-std=c++11 -L/usr/lib64/mysql -lmysqlclient -ljsoncpp -lpthread -g
.PHONY:all
all:db_test
db_test:db_test.cc db.hpp
g++ db_test.cc -o db_test $(FLAGS)
.PHONY:clean
clean:
rm db_test
总结:
第一部分数据库模块全部完成,第二部分服务器模块已完成 API 设计。
接下来进行服务器设计。