一次完整的 HTTP 请求处理流程如上图所示。(图片出自《HTTP 权威指南》,推荐想全面理解 HTTP 的读者阅读此书。)
客户端发送 HTTP 请求后,服务器会根据域名进行域名解析,就是将网站名称转变成 IP 地址:localhost -> 127.0.0.1,Linux hosts文件、DNS 域名解析等可以实现这种功能。之后通过发起 TCP 的三次握手建立连接。TCP 三次连接请参考 TCP三次握手详解及释放连接过程,建立连接之后就可以发送 HTTP 请求了。
HTTP 服务器软件进程,这里指的是 API 服务器,在接收到请求之后,首先根据 HTTP 请求行的信息来解析到 HTTP 方法和路径,在上图所示的报文中,方法是 GET,路径是 /index.html,之后根据 API 服务器注册的路由信息(大概可以理解为:HTTP 方法 + 路径和具体处理函数的映射)找到具体的处理函数。
在接收到请求之后,API 通常会解析 HTTP 请求报文获取请求头和消息体,然后根据这些信息进行相应的业务处理,HTTP 框架一般都有自带的解析函数,只需要输入 HTTP 请求报文,就可以解析到需要的请求头和消息体。通常情况下,业务逻辑处理可以分为两种:包含对数据库的操作和不包含对数据的操作。大型系统中通常两种都会有:
在业务逻辑处理过程中,需要记录一些关键信息,方便后期 Debug 用。在 Go 中有各种各样的日志包可以用来记录这些信息。
go get github.com/gin-gonic/gin
go get github.com/go-sql-driver/mysql
go get github.com/spf13/viper
项目根目录是 webproject
webproject的目录结构
├── conf # 配置文件统一存放目录
│ ├── config.yaml # 配置文件
├── config # 专门用来处理配置和配置文件的Go package
│ └── config.go
├── handler # 类似MVC架构中的C,用来读取输入,并将处理流程转发给实际的处理函数,最后返回结果
│ ├── handler.go
├── model # 数据库相关的操作统一放在这里,包括数据库初始化和对表的增删改查
│ ├── init.go # 初始化和连接数据库
│ ├── model.go # 存放一些公用的go struct
│ └── user.go # 用户相关的数据库CURD操作
├── pkg # 引用的包
│ ├── errno # 错误码存放位置
│ │ ├── code.go
│ │ └── errno.go
├── router # 路由相关处理
│ ├── middleware # API服务器用的是Gin Web框架,Gin中间件存放位置
│ │ ├── header.go
│ └── router.go # 路由
├── service # 实际业务处理函数存放位置
│ └── service.go
├── database.sql # 数据库文件
├── main.go # Go程序唯一入口
common:
runmode: debug # 开发模式, debug, release, test
addr: :6663 # HTTP绑定端口
name: apiserver # API Server的名字
url: http://10.10.87.243:6663 # pingServer函数请求的API服务器的ip:port
max_ping_count: 10 # pingServer函数尝试的次数
database: #数据库配置
#最大空闲连接数
max_idle_conns: 50
#数据库名称
dbname: test
#数据库服务器ip
host: 10.10.87.244
#端口
port: 3306
#登录用户名
username: root
#登录密码
password: 123456
package config
import (
"github.com/spf13/viper"
"time"
"os"
"log"
)
// LogInfo 初始化日志配置
func LogInfo() {
file := "./logs/" + time.Now().Format("2006-01-02") + ".log"
logFile, _ := os.OpenFile(file,os.O_RDWR| os.O_CREATE| os.O_APPEND, 0766)
log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
log.SetOutput(logFile)
}
// Init 读取初始化配置文件
func Init() error {
//初始化配置
if err := Config();err != nil{
return err
}
//初始化日志
LogInfo()
return nil
}
// Config viper解析配置文件
func Config() error{
viper.AddConfigPath("conf")
viper.SetConfigName("config")
if err := viper.ReadInConfig();err != nil{
return err
}
return nil
}
package handler
import (
"net/http"
"webproject/pkg/errno"
"github.com/gin-gonic/gin"
)
type Response struct {
Code int `json:"code"`
Message string `json:"message"`
Data interface{} `json:"data"`
}
//返回json 格式
func SendResponse(c *gin.Context,err error,data interface{}){
code,message := errno.DecodeErr(err)
//总是返回http状态ok
c.JSON(http.StatusOK,Response{
Code: code,
Message:message,
Data: data,
})
}
//返回html 格式
func SendResponseHtml(c *gin.Context,err error,data string){
c.Header("Content-Type", "text/html; charset=utf-8")
//总是返回http状态ok
c.String(http.StatusOK,data)
}
package model
import (
"database/sql"
"log"
"fmt"
"os"
"errors"
"strings"
"io/ioutil"
_ "github.com/go-sql-driver/mysql" //这个引用是必不可少的,因为需要调用driver.go文件里的init方法来提供一个数据库驱动程序
"github.com/spf13/viper"
)
var DB *sql.DB //全局变量,这样可以在别处调用
func Init() error{
var err error
var connectstring string
max_idle_conns := viper.GetInt("common.database.max_idle_conns")
username := viper.GetString("common.database.username")
password := viper.GetString("common.database.password")
host := viper.GetString("common.database.host")
port := viper.GetInt("common.database.port")
dbname := viper.GetString("common.database.dbname")
// 账号:密码@tcp(IP:端口号)/数据库名?parseTime=true&charset=utf8&loc=Local
connectstring = fmt.Sprintf("%s%s%s%s%s%s%d%s%s%s",username,":",password,"@tcp(",host,":",port,")/",dbname,"?parseTime=true&charset=utf8&loc=Local")
//这行代码的作用就是初始化一个sql.DB对象
DB,err = sql.Open("mysql",connectstring)
if err != nil{
return err
}
//设置最大超时时间
DB.SetMaxIdleConns(max_idle_conns)
//建立连接
err = DB.Ping()
if err != nil{
return err
}else{
log.Println("数据库连接成功")
}
return nil
}
//检查是否安装数据库
func CheckInstalled() error{
//先判断是否已经生成 install.lock文件,表示已经安装过了
exists,_ := PathExists("install.lock")
if exists {
//fmt.Println("已经安装过数据库")
return errors.New("已经安装过数据库")
}
return nil
}
//安装数据库
func CreateDatabase() error{
//读取 database.sql 全部内容
database, err := ioutil.ReadFile("database.sql")
if err != nil {
//fmt.Println("ioutil ReadFile database.sql error: ", err)
return err
}
// 读取的数据转换成字符串类型
databasestring := string(database)
//去除换行符
databasestring = strings.Replace(databasestring, "\n", "", -1)
databasestring = strings.Replace(databasestring, "\r", "", -1)
//strings.Split(string , "切割符") 把字符串转换成 数组
databasearr := strings.Split(string(databasestring),";")
//fmt.Printf("%v\n", reflect.TypeOf(databasearr))
//fmt.Printf("%v\n", databasearr)
//循环sql 数组,使用事务提交到数据库
if(len(databasearr) > 0){
transaction_con,err := DB.Begin()
if err != nil{
return err
}
for i := 0;i < len(databasearr);i++{
if databasearr[i] == ""{
continue
}
_, err = transaction_con.Exec(databasearr[i])
if err != nil{
// 失败回滚
transaction_con.Rollback()
return err
}
}
//提交数据库
err = transaction_con.Commit()
if(err != nil){
// 失败回滚
transaction_con.Rollback()
return err
}
//写入install.lock文件
lock := []byte("1\n")
err = ioutil.WriteFile("install.lock", lock, 0777)
if err != nil{
return err
}
}
//
//stmt,err := DB.Prepare(databasestring)
//if err != nil{
// return err
//}
//defer stmt.Close()
//_,err = stmt.Exec()
//if err != nil{
// return err
//}
return nil
}
//检查文件是否存在
func PathExists(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil {
return true, nil
}
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
package model
import (
"fmt"
"log"
)
var code int64
// Insert 插入操作
func Insert(sql string,args...interface{})(int64,error){
stmt,err := DB.Prepare(sql)
defer stmt.Close()
code,err = CheckErr(err,"sql语句设置失败",1)
if(code != 1){return code,err}
result,err := stmt.Exec(args...)
code,err = CheckErr(err,"参数添加失败",1)
if(code != 1){return code,err}
id,err := result.LastInsertId()
code,err = CheckErr(err,"插入失败",1)
if(code != 1){return code,err}
fmt.Printf("插入成功,id为%v\n", id)
return id,err
}
// Delete删除
func Delete(sql string,args...interface{})(int64,error){
stmt,err := DB.Prepare(sql)
defer stmt.Close()
code,err = CheckErr(err,"sql语句设置失败",1)
if(code != 1){return code,err}
result,err := stmt.Exec(args...)
code,err = CheckErr(err,"参数添加失败",1)
if(code != 1){return code,err}
num,err := result.RowsAffected()
code,err = CheckErr(err,"删除失败",1)
if(code != 1){return code,err}
fmt.Printf("删除成功,删除行数为%d\n",num)
return num,err
}
// Update 更新
func Update(sql string,args...interface{})(int64,error){
stmt, err := DB.Prepare(sql)
defer stmt.Close()
code,err = CheckErr(err, "SQL语句设置失败",1)
if(code != 1){return code,err}
result, err := stmt.Exec(args...)
code,err = CheckErr(err, "参数添加失败",1)
if(code != 1){return code,err}
num, err := result.RowsAffected()
code,err = CheckErr(err,"修改失败",1)
if(code != 1){return code,err}
fmt.Printf("修改成功,修改行数为%d\n",num)
return num,err
}
// 检查错误
func CheckErr(err error,msg string,code int)(int64,error){
if err != nil{
log.Panicln(msg,err,code)
return 0,err
}
return 1,nil
}
package model
import (
"errors"
"webproject/pkg/errno"
"encoding/json"
"log"
)
type User struct {
UserName string `json:"user_name"`
Password string `json:"password"`
}
//查询
func (user *User) SelectUserByName(name string) error {
stmt,err := DB.Prepare("select user_name,password from user where user_name = ?")
if err != nil{
return err
}
defer stmt.Close()
rows,err := stmt.Query(name)
defer rows.Close()
if err != nil{
return err
}
//数据处理
for rows.Next(){
rows.Scan(&user.UserName,&user.Password)
}
if err := rows.Err();err != nil{
return err
}
return nil
}
//验证字段
func (u *User) Validate() error{
if u.UserName == "" || u.Password == ""{
return errors.New(errno.ErrValidation.Message)
}
return nil
}
//创建用户
func (u *User) Create() (int64,error){
id,err := Insert("insert into user(user_name,password) values(?,?)",u.UserName, &u.Password)
if err != nil{
return 0,err
}
return id,nil
}
//转换user 作为json字符串
func (user *User)UserToJson() string{
jsonStr,err := json.Marshal(user)
if err != nil{
log.Println(err)
}
return string(jsonStr)
}
//转换json 作为user构造体
func (user *User)JsonToUser(jsonBlob string) error{
err := json.Unmarshal([]byte(jsonBlob),&user)
if err != nil{
return err
}
return nil
}
package errno
var (
// Common errors
OK = &Errno{Code: 0, Message: "OK"}
INSTALLERROR = &Errno{Code: -1, Message: "安装失败"}
INSTALLED = &Errno{Code: -1, Message: "你已经安装过数据库"}
InternalServerError = &Errno{Code: 10001, Message: "接口服务器错误"}
ErrBind = &Errno{Code: 10002, Message: "绑定数据错误"}
ErrValidation = &Errno{Code: 20001, Message: "验证失败"}
ErrDatabase = &Errno{Code: 20002, Message: "数据库出错"}
// user errors
ErrUserNotFound = &Errno{Code: 20101, Message: "该用户不存在"}
ErrPasswordIncorrect = &Errno{Code: 20102, Message: "密码错误"}
)
package errno
import "fmt"
type Errno struct {
Code int
Message string
}
//返回错误信息
func (err Errno) Error() string{
return err.Message
}
//设置 Err 结构体
type Err struct {
Code int
Message string
Err error
}
//声明构造体
func New(errno *Errno,err error) *Err{
return &Err{Code:errno.Code,Message:errno.Message,Err:err}
}
//添加错误信息
func (err *Err) Add(message string) error{
err.Message += " " + message
return err
}
//添加指定格式的错误信息
func (err * Err) Addf(format string,args...interface{}) error{
err.Message += " " + fmt.Sprintf(format,args...)
return err
}
//拼接错误信息字符串
func (err *Err) Error() string{
return fmt.Sprintf("Err - code: %d, message: %s, error: %s",err.Code,err.Message,err.Err)
}
//用户不存在错误
func IsErrUserNotFound(err error) bool{
code,_ := DecodeErr(err)
return code == ErrUserNotFound.Code
}
// 解析 错误信息, 返回字符串
func DecodeErr(err error) (int,string){
if err == nil{
return OK.Code,OK.Message
}
switch typed := err.(type) {
case *Err:
return typed.Code,typed.Message
case *Errno:
return typed.Code,typed.Message
default:
}
return InternalServerError.Code,err.Error()
}
package middleware
import (
"net/http"
"time"
"github.com/gin-gonic/gin"
)
//无缓存头部中间件 ,
//要来防止客户端获取已经缓存的响应信息
func NoCache(c *gin.Context){
c.Header("Cache-Control", "no-cache, no-store, max-age=0, must-revalidate, value")
c.Header("Expires", "Thu, 01 Jan 1970 00:00:00 GMT")
c.Header("Last-Modified", time.Now().UTC().Format(http.TimeFormat))
c.Next()
}
//选项中间件
//要来给预请求 终止并退出中间件 ,链接并结束请求
func Options(c *gin.Context){
if c.Request.Method != "OPTIONS"{
c.Next()
}else{
c.Header("Access-Control-Allow-Origin","*")
c.Header("Access-Control-Allow-Methods", "GET,POST,PUT,PATCH,DELETE,OPTIONS")
c.Header("Access-Control-Allow-Headers", "authorization, origin, content-type, accept")
c.Header("Allow", "HEAD,GET,POST,PUT,PATCH,DELETE,OPTIONS")
c.Header("Content-Type", "application/json")
c.AbortWithStatus(200)
}
}
//安全中间件
//要来保障数据安全的头部
func Secure(c *gin.Context){
c.Header("Access-Control-Allow-Origin", "*")
c.Header("X-Frame-Options", "DENY")
c.Header("X-Content-Type-Options", "nosniff")
c.Header("X-XSS-Protection", "1; mode=block")
if c.Request.TLS != nil {
c.Header("Strict-Transport-Security", "max-age=31536000")
}
//也可以考虑添加一个安全代理的头部
//c.Header("Content-Security-Policy", "script-src 'self' https://cdnjs.cloudflare.com")
}
package router
import (
"net/http"
"webproject/service"
"webproject/router/middleware"
"github.com/gin-gonic/gin"
)
//初始化路由
func InitRouter(g *gin.Engine){
middlewares := []gin.HandlerFunc{}
//中间件
g.Use(gin.Recovery())
g.Use(middleware.NoCache)
g.Use(middleware.Options)
g.Use(middleware.Secure)
g.Use(middlewares...)
//404处理
g.NoRoute(func(c *gin.Context){
c.String(http.StatusNotFound,"该路径不存在")
})
//健康检查中间件
g.GET("/",service.Index)//主页
g.GET("/install",service.Install)//创建数据库
usergroup := g.Group("/user") //创建路由组
{
usergroup.POST("/addUser",service.AddUser) //添加用户
usergroup.GET("/selectUser",service.SelectUser)//查询用户
}
}
package service
import (
"github.com/gin-gonic/gin"
"webproject/model"
. "webproject/handler"
"webproject/pkg/errno"
"fmt"
)
//首页
func Index(c *gin.Context){
html := `
hello world
hello world
`
SendResponseHtml(c,nil,html)
}
//新建用户
func AddUser(c *gin.Context){
var r model.User
if err := c.Bind(&r); err != nil{
SendResponse(c,errno.ErrBind,nil)
return
}
u := model.User{
UserName: r.UserName,
Password: r.Password,
}
//验证数据
if err := u.Validate(); err != nil{
SendResponse(c,errno.ErrValidation,nil)
return
}
//插入数据
if _,err := u.Create();err != nil{
SendResponse(c,errno.ErrDatabase,nil)
return
}
SendResponse(c,nil,u)
}
//查询用户
func SelectUser(c *gin.Context){
name := c.Query("user_name")
if name == ""{
SendResponse(c,errno.ErrValidation,nil)
return
}
var user model.User
if err := user.SelectUserByName(name); err != nil{
fmt.Println(err)
SendResponse(c,errno.ErrUserNotFound,nil)
return
}
//验证数据
if err := user.Validate(); err != nil{
SendResponse(c,errno.ErrUserNotFound,nil)
return
}
SendResponse(c,nil,user)
}
//创建数据库
func Install(c *gin.Context){
//检查是否安装数据库
if err := model.CheckInstalled(); err != nil{
fmt.Println(err)
SendResponse(c,errno.INSTALLED,err)
return
}
//安装数据库
if err := model.CreateDatabase(); err != nil{
fmt.Println(err)
SendResponse(c,errno.INSTALLERROR,err)
return
}
SendResponse(c,nil,"成功")
}
DROP TABLE IF EXISTS user;
CREATE TABLE user (
id bigint(20) NOT NULL AUTO_INCREMENT,
user_name varchar(20) DEFAULT "",
password varchar(20) DEFAULT "",
age tinyint(11) DEFAULT "0",
PRIMARY KEY (id)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4;
INSERT INTO user VALUES ("1", "admin", "12164","18");
package main
import (
"webproject/config"
"webproject/model"
"github.com/gin-gonic/gin"
"log"
"github.com/spf13/viper"
"webproject/router"
)
func main() {
if err := config.Init();err != nil{
panic(err)
}
if err := model.Init();err != nil{
panic(err)
}
//g := gin.default
//设置gin模式
gin.SetMode(viper.GetString("common.runmode"))
//创建一个gin引擎
g := gin.New()
router.InitRouter(g)
log.Printf("开始监听服务器地址: %s\n", viper.GetString("common.url"))
//log.Println(http.ListenAndServe(viper.GetString("common.addr"), g).Error())
if err := g.Run(viper.GetString("common.addr"));err != nil {
log.Fatal("监听错误:", err)
}
}
[root@localhost webproject]# go mod init webproject
go: creating new go.mod: module webproject
[root@localhost webproject]# go run main.go
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] GET / --> webproject/service.Index (5 handlers)
[GIN-debug] GET /install --> webproject/service.Install (5 handlers)
[GIN-debug] POST /user/addUser --> webproject/service.AddUser (5 handlers)
[GIN-debug] GET /user/selectUser --> webproject/service.SelectUser (5 handlers)
[GIN-debug] Listening and serving HTTP on :6663
web服务器搭建参考:https://www.cnblogs.com/Survivalist/articles/10596106.html