腾讯云服务器 CentOS 7.6 快速搭建 LNMP 环境

我在腾讯云上购买了云服务器,系统是 CentOS 7.6 x64,自己整理了以下脚本文件,可以快速安装 LNMP 环境。

文件列表及备注如下:

1-init.sh      # 初始化更新系统软件库
2-account.sh   # 创建 www、mysql 账号
3-openss1.sh   # 安装 openssl-1.1.1d
4-nginx.sh     # 安装 nginx-1.16.1
5-php.sh       # 安装 php-7.2.28
6-mysql.sh     # 安装 mysql-5.7.28
my.cnf         # mysql 的配置文件
nginx.conf     # nginx 的全局配置文件
mysite.conf    # nginx 的示例网站配置文件

包含了 6 个 shell 脚本文件和 3 个配置文件,按顺序执行以上 6 个脚本,php、nginx、mysql 都被安装在 /usr/local 目录下的各自目录。其中,mysql 采用二进制包安装,默认密码保存在 lnmp-script/mysql_new_password.log。默认的配置文件只保证服务能够正常跑起来,如果想要达到最佳性能,需要自己结合服务器硬件配置去做优化。

分类至 杂项
0条评论

Gin 框架学习:使用 BasicAuth 中间件

basicauth.go:

package main

import (
	"github.com/gin-gonic/gin"
	"net/http"
)

var secrets = gin.H{
	"foo":    gin.H{"email": "foo@bar.com", "phone": "123433"},
	"austin": gin.H{"email": "austin@example.com", "phone": "666"},
	"lena":   gin.H{"email": "lena@guapa.com", "phone": "523443"},
}

func main() {
	r := gin.Default()

	// 路由组使用 gin.BasicAuth() 中间件
	// gin.Accounts 是 map[string]string 的一种快捷方式
	authorized := r.Group("/admin", gin.BasicAuth(gin.Accounts{
		"foo":    "bor",
		"austin": "1234",
		"lena":   "hello2",
		"manu":   "4321",
	}))

	// /admin/secrets 端点
	// 触发 "localhost:8080/admin/secrets
	authorized.GET("/secrets", func(c *gin.Context) {
		// 获取用户,它是由 BasicAuth 中间件设置的
		user := c.MustGet(gin.AuthUserKey).(string)
		if secret, ok := secrets[user]; ok {
			c.JSON(http.StatusOK, gin.H{"user": user, "secret": secret})
		} else {
			c.JSON(http.StatusOK, gin.H{"user": user, "secret": "NO SECRET :("})
		}
	})

	// 监听并在 0.0.0.0:8080 上启动服务
	r.Run(":8080")
}

关于 HTTP Basic 认证的介绍可以查看这篇博文:https://www.yangdx.com/2019/03/19.html

分类至 GO
0条评论

Gin 框架学习:优雅地重启或停止

Go 1.8+ 版本可以使用 http.Server 内置的 Shutdown() 方法优雅地关闭服务。

server.go:

package main

import (
	"context"
	"log"
	"net/http"
	"os"
	"os/signal"
	"time"

	"github.com/gin-gonic/gin"
)

func main() {
	router := gin.Default()
	router.GET("/", func(c *gin.Context) {
		time.Sleep(5 * time.Second)
		c.String(http.StatusOK, "Welcome Gin Server")
	})

	srv := &http.Server{
		Addr:    ":8080",
		Handler: router,
	}

	go func() {
		// 服务连接
		if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			log.Fatalf("listen: %s\n", err)
		}
	}()

	// 等待中断信号以优雅地关闭服务器(设置 5 秒的超时时间)
	quit := make(chan os.Signal)
	signal.Notify(quit, os.Interrupt)
	<-quit
	log.Println("Shutdown Server ...")

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	if err := srv.Shutdown(ctx); err != nil {
		log.Fatal("Server Shutdown:", err)
	}
	log.Println("Server exiting")
}

启动服务后,使用浏览器访问 http://localhost:8080/ 这个链接。由于设置了 5 秒睡眠,页面会一直处于加载中,直到 5 秒后才显示出内容。在页面响应前,按 Ctrl + C 尝试关闭正在运行的服务(Linux 系统还可以使用 kill -2 pid 指令触发),会先看到打印 Shutdown Server ...,等待几秒后前台页面加载完成,最后才打印 Server exiting,服务关闭。

可以试着把代码第 17 行的 5 秒睡眠改成 10 秒或更大,重复一遍上面的操作,会有不同效果。

分类至 GO
0条评论

Gin 框架学习:从 reader 读取数据

fromreader.go:

package main

import (
	"github.com/gin-gonic/gin"
	"net/http"
)

func main() {
	router := gin.Default()

	router.GET("/someDataFromReader", func(c *gin.Context) {
		response, err := http.Get("https://www.yangdx.com/res/img/avatar.jpg")
		if err != nil || response.StatusCode != http.StatusOK {
			c.Status(http.StatusServiceUnavailable)
			return
		}

		reader := response.Body
		contentLength := response.ContentLength
		contentType := response.Header.Get("Content-Type")

		extraHeaders := map[string]string{
			"Content-Disposition": `attachment; filename="gopher.jpg"`,
		}

		c.DataFromReader(http.StatusOK, contentLength, contentType, reader, extraHeaders)
	})

	router.Run(":8080")
}

使用浏览器访问 http://localhost:8080/someDataFromReader 将弹出文件的保持对话框。

分类至 GO
0条评论

Gin 框架学习:多文件上传

upload2.go:

package main

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"log"
	"net/http"
	"path/filepath"
)

func main() {
	router := gin.Default()
	// 为 multipart forms 设置较低的内存限制 (默认是 32 MiB)
	// router.MaxMultipartMemory = 8 << 20 // 8 MiB
	router.POST("/upload", func(c *gin.Context) {
		form, _ := c.MultipartForm()
		files := form.File["upload[]"]

		for _, file := range files {
			log.Println(file.Filename)

			dst := "./upload/" + filepath.Base(file.Filename)
			c.SaveUploadedFile(file, dst)
		}

		c.String(http.StatusOK, fmt.Sprintf("%d files uploaded!", len(files)))
	})
	router.Run(":8080")
}

使用 curl 测试上传:

$ curl -X POST http://localhost:8080/upload \
    -F "upload[]=@/Users/appleboy/test1.zip" \
    -F "upload[]=@/Users/appleboy/test2.zip" \
    -H "Content-Type: multipart/form-data"

 

分类至 GO
0条评论

Gin 框架学习:单文件上传

upload1.go:

package main

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"log"
	"net/http"
	"path/filepath"
)

func main() {
	router := gin.Default()
	// 为 multipart forms 设置较低的内存限制 (默认是 32 MiB)
	// router.MaxMultipartMemory = 8 << 20  // 8 MiB
	router.POST("/upload", func(c *gin.Context) {
		file, err := c.FormFile("file")
		if err != nil {
			c.String(http.StatusBadRequest, fmt.Sprintf("get form err: %s", err.Error()))
		}

		log.Println(file.Filename)

		dst := "./upload/" + filepath.Base(file.Filename)
		if err := c.SaveUploadedFile(file, dst); err != nil {
			c.String(http.StatusBadRequest, fmt.Sprintf("upload file err: %s", err.Error()))
		}

		c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename))
	})

	router.Run(":8080")
}

使用 curl 测试上传:

$ curl -X POST http://localhost:8080/upload \
    -F "file=@/f/folder/test.zip" \
    -H "Content-Type: multipart/form-data"

 

分类至 GO
0条评论

Gin 框架学习:XML/JSON/YAML/ProtoBuf 渲染

xml_json_yaml_protobuf.go:

package main

import (
	"github.com/gin-gonic/gin"
	"github.com/gin-gonic/gin/testdata/protoexample"
	"net/http"
)

func main() {
	r := gin.Default()

	// gin.H 是 map[string]interface{} 的一种快捷方式
	r.GET("/someJSON", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
	})

	r.GET("/moreJSON", func(c *gin.Context) {
		// 你也可以使用一个结构体
		var msg struct {
			Name    string `json:"user"`
			Message string
			Number  int
		}
		msg.Name = "Lena"
		msg.Message = "hey"
		msg.Number = 123
		// 注意 msg.Name 在 JSON 中变成了 "user"
		// 将输出:{"user":"Lena","Message":"hey","Number":123}
		c.JSON(http.StatusOK, msg)
	})

	r.GET("/someXML", func(c *gin.Context) {
		c.XML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
	})

	r.GET("/someYAML", func(c *gin.Context) {
		c.YAML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
	})

	r.GET("/someProtoBuf", func(c *gin.Context) {
		reps := []int64{int64(1), int64(2)}
		label := "test"
		// protobuf 的具体定义写在 testdata/protoexample 文件中。
		data := &protoexample.Test{
			Label: &label,
			Reps:  reps,
		}
		// 请注意,数据在响应中变为二进制数据
		// 将输出被 protoexample.Test protobuf 序列化了的数据
		c.ProtoBuf(http.StatusOK, data)
	})

	// 监听并在 0.0.0.0:8080 上启动服务
	r.Run(":8080")
}

 

分类至 GO
0条评论

Gin 框架学习:SecureJSON

使用 SecureJSON 防止 json 劫持。如果给定的结构是数组值,则默认预置 while(1); 到响应体。

secruejson.go:

package main

import "github.com/gin-gonic/gin"

func main() {
	r := gin.Default()

	r.GET("/someJSON", func(c *gin.Context) {
		names := []string{"lena", "austin", "foo"}

		c.SecureJSON(200, names)
	})

	r.Run(":8080")
}

相关阅读:Why does Google prepend while(1); to their JSON responses?

分类至 GO
0条评论

Gin 框架学习:Query 和 post form

同时接收 post 参数和 query 参数。

query_post.go:

package main

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"net/http"
)

func main() {
	router := gin.Default()

	router.POST("/post", func(c *gin.Context) {
		id := c.Query("id")
		page := c.DefaultQuery("page", "0")
		name := c.PostForm("name")
		message := c.PostForm("message")

		c.String(http.StatusOK, fmt.Sprintf("id: %s; page %s; name: %s; message: %s", id, page, name, message))
	})

	router.Run(":8080")
}

测试:

$ curl -d "name=manu&message=this_is_great" "http://localhost:8080/post?id=1234&page=1"

输出:

id: 1234; page 1; name: manu; message: this_is_great

 

分类至 GO
0条评论