前言
做毕设的时候用到了 Go 和 C++ 开发了两个 Web 后端,顺便简单记录一下。
Go - gin
Go 的函数返回中数据和错误一般是分开的,方便错误处理。Web 开发选用的是比较轻量级的 gin 框架,这个框架的中文文档好像有点时日没有更新了,建议去看 GitHub 上的官方文档,在开发过程中用到了这些库:
1 2 3 4 5 6 7
| "net/http" "encoding/json" "github.com/gin-gonic/gin" "github.com/gomodule/redigo/redis" _ "github.com/go-sql-driver/mysql" "github.com/jmoiron/sqlx" "sort"
|
Go 的库安装也很简单,go get 就可以了,也可以自己去 GitHub 上下载。
第一个 http 库是为了 set cookie 使用的,用来表示 cookie 的安全策略:
1
| c.SetCookie("SESSID", string(data)[:36], 3600 * 24 * 30, "/api/", "xxx.monster", http.SameSiteLaxMode, false, true)
|
第二个是 json 库,大概是这样用法:
1 2 3 4
| var session map[string]interface{} json.Unmarshal([]byte(data), &session) ... team = session["team"].(string)
|
data 是 json 字符串,值得一提的是从这个 session 里面取值的时候,需要加上数据类型,还有则是在我的 docker 环境(也就是 go 官方镜像)下,反序列化之后,数字类型会默认用 float64 存放,所以取值也需要用 float64 来取值。
第三个是 gin,不提。第四个是 redis 库,具体做法是初始化一个连接池,需要用的时候再取出使用:
1 2 3 4 5 6 7 8 9 10 11
| func newPool(addr string) *redis.Pool { return &redis.Pool{ MaxIdle: 3, IdleTimeout: 240 * time.Second, Dial: func () (redis.Conn, error) { return redis.Dial("tcp", addr) }, } } ... conn := pool.Get() defer conn.Close() encodedSession, err := redis.String(conn.Do("get", cookie))
|
五、六都是 MySQL 库,前面加个 _ 的意思是我们不使用包里面的成员函数,只使用构造函数,同样也是连接池的做法:
1 2 3 4 5 6 7 8 9 10 11 12 13
| func newDb() *sqlx.DB { Db, _ := sqlx.Open("mysql", "root:root@tcp(172.17.0.4:3306)/ctf?charset=utf8") return Db } ... rows, _ := db.Query("select cid from challenge where challenge.mode=?;", mode) data := []map[string]interface{}{} for rows.Next() { var cidstring rows.Scan(&cid) data = append(data, row) } rows.Close()
|
最后 sort 可以实现二维数组的排序:
1 2 3 4 5 6 7 8 9
| sort.Slice(rank, func(i, j int) bool { if rank[i]["score"].(float64) > rank[j]["score"].(float64) { return true } if rank[i]["score"].(float64) == rank[j]["score"].(float64) && rank[i]["time"].(int64) < rank[j]["time"].(int64) { return true } return false })
|
C++ - cinatra
C++ 找不到什么合适的框架,最后选用了 Cinatra,但是这个框架还不成熟,文档也缺失得不行。没事还是不要用 C++ 写 Web 后端了。
Redis 使用 hiredis:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| struct timeval timeout = {1, 500000}; redis = redisConnectWithTimeout("172.17.0.2", 6379, timeout); if (redis -> err) { } ... redisReply *reply; reply = (redisReply *)redisCommand(redis, "GET %s", key.c_str()); if (reply == NULL) { redisFree(redis); redis = NULL; ... }else if (reply -> len <= 0) { freeReplyObject(reply); ... } ....... else if (strcmp(reply -> str, "OK") == 0) { freeReplyObject(reply); return "OK"; } std::string result = reply -> str; freeReplyObject(reply);
|
MySQL 的写法跟 PHP 很类似,自行过滤要这么做:
1 2 3 4 5 6 7 8 9 10
| char *escape_team = new char[team.length()]; char *escape_password = new char[password.length()]; mysql_real_escape_string(&mysql, escape_team, team.data(), team.length()); mysql_real_escape_string(&mysql, escape_password, password.data(), password.length()); std::string str_sql = ""; str_sql += "select uid, mail from user where team='"; str_sql += escape_team; str_sql += "' and password='"; str_sql += escape_password; str_sql += "';";
|
C++ 里面还有一种类型叫做 string_view,是 string 的一个只读视图,可以提高字符串处理性能,Cinatra 这个框架在解析 HTTP 的时候就大量用到了这个类型,从 string_view 变成 string 可以这么做:
1
| std::string(cookies["SESSID"].data(), cookies["SESSID"].length())
|
C++ 里面的变长数组可以使用 vector:
1
| std::vector<Rank::Rank> ranks;
|
C++ 里面没有 Go 那种可以用来表示任何数据类型的 interface,所以无法制作多种成员类型的 map。使用自定义 struct 可以解决,不过这样一来,json 就无法识别我们自己设定的结构体,所以需要给它加上序列化函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| namespace Rank { struct Rank { int uid; std::string team; long long time; double score; }; void to_json(nlohmann::json& j, const Rank& p) { j = nlohmann::json { {"uid", p.uid}, {"time", p.time}, {"team", p.team}, {"score", p.score}, }; } }
|
自定义结构体数组的排序跟 Go 差不多,sort 调用自定义函数。
编译:
1
| g++ main.cpp -std=c++17 -lboost_system -lpthread -lstdc++fs -luuid -lhiredis -I/usr/include/mariadb -lmysqlclient -o web
|
Orz