Go Web编程基础

Web基础Web工作方式客户端-服务器模型:Web应用基于客户端-服务器架构。客户端(如浏览器)通过HTTP协议向服务器发送请求,服务器响应这些请求。请求与响应:每个Web交互都包含一个从客户端到服务器的请求和从服务器到客户端的响应。使用Go搭建一个简单的Web服务packagema

Web基础

Web工作方式

  • 客户端-服务器模型:Web应用基于客户端-服务器架构。客户端(如浏览器)通过HTTP协议向服务器发送请求,服务器响应这些请求。
  • 请求与响应:每个Web交互都包含一个从客户端到服务器的请求和从服务器到客户端的响应。

使用Go搭建一个简单的Web服务

package main

import (
    "fmt"
    "net/http"
)

func helloHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello, World!")
}

func main() {
    http.HandleFunc("/", helloHandler)
    http.ListenAndServe(":8080", nil)
}

这段代码创建了一个监听8080端口的简单Web服务器,任何访问根路径/的请求都会被重定向到helloHandler函数处理。

Go如何使得Web工作

  • HTTP库:Go内置了强大的net/http包来处理HTTP请求。
  • 路由:通过注册不同的处理函数来响应不同URL路径的请求。
  • 中间件:可以在处理请求前或后执行额外逻辑,比如日志记录、身份验证等。

net/http包详解

基本概念

  • Server:代表整个HTTP服务器实例。
  • Request:包含客户端发送的所有信息。
  • ResponseWriter:用于构造返回给客户端的响应。

主要功能

  • 处理函数:定义如何处理特定类型的请求。
  • 路由:根据请求URL选择正确的处理函数。
  • 中间件:增强功能,如日志记录、认证等。

详细学习知识教程精讲

初始化服务器

http.HandleFunc("/", handlerFunc)
http.ListenAndServe(":8080", nil)

处理请求

func handlerFunc(w http.ResponseWriter, r *http.Request) {
    // 处理逻辑
}
读取请求参数
go
func handlerFunc(w http.ResponseWriter, r *http.Request) {
    param := r.URL.Query().Get("name")
    fmt.Fprintf(w, "Hello, %s!", param)
}
设置响应头
go
func handlerFunc(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(map[string]string{"message": "Hello"})
}
使用中间件
go
func loggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        log.Printf("Handling request: %s", r.URL.Path)
        next.ServeHTTP(w, r)
    })
}

表单处理

处理表单的输入

package main

import (
    "fmt"
    "html/template"
    "net/http"
)

type FormData struct {
    Name  string
    Email string
}

func formHandler(w http.ResponseWriter, r *http.Request) {
    if r.Method == "POST" {
        err := r.ParseForm()
        if err != nil {
            http.Error(w, err.Error(), http.StatusBadRequest)
            return
        }

        name := r.FormValue("name")
        email := r.FormValue("email")

        fmt.Fprintf(w, "Name: %s\nEmail: %s", name, email)
    } else {
        tmpl := template.Must(template.ParseFiles("form.html"))
        tmpl.Execute(w, FormData{})
    }
}

func main() {
    http.HandleFunc("/", formHandler)
    http.ListenAndServe(":8080", nil)
}

HTML模板 (form.html)

<!DOCTYPE html>
<html>
<head>
    <title>Form Example</title>
</head>
<body>
    <h1>Submit Form</h1>
    <form action="/" method="post">
        Name: <input type="text" name="name"><br>
        Email: <input type="email" name="email"><br>
        <button type="submit">Submit</button>
    </form>
</body>
</html>

验证表单的输入

package main

import (
    "fmt"
    "html/template"
    "net/http"
    "strings"
)

type FormData struct {
    Name  string
    Email string
    Error string
}

func formHandler(w http.ResponseWriter, r *http.Request) {
    if r.Method == "POST" {
        err := r.ParseForm()
        if err != nil {
            http.Error(w, err.Error(), http.StatusBadRequest)
            return
        }

        name := r.FormValue("name")
        email := r.FormValue("email")

        if strings.TrimSpace(name) == "" || strings.TrimSpace(email) == "" {
            data := FormData{Name: name, Email: email, Error: "Please fill in all fields."}
            tmpl := template.Must(template.ParseFiles("form.html"))
            tmpl.Execute(w, data)
            return
        }

        fmt.Fprintf(w, "Name: %s\nEmail: %s", name, email)
    } else {
        tmpl := template.Must(template.ParseFiles("form.html"))
        tmpl.Execute(w, FormData{})
    }
}

func main() {
    http.HandleFunc("/", formHandler)
    http.ListenAndServe(":8080", nil)
}

HTML模板 (form.html)

<!DOCTYPE html>
<html>
<head>
    <title>Form Example</title>
</head>
<body>
    <h1>Submit Form</h1>
    {{ if .Error }}
    <p style="color:red;">{{ .Error }}</p>
    {{ end }}
    <form action="/" method="post">
        Name: <input type="text" name="name" value="{{ .Name }}"><br>
        Email: <input type="email" name="email" value="{{ .Email }}"><br>
        <button type="submit">Submit</button>
    </form>
</body>
</html>

预防跨站脚本 (XSS)

package main

import (
    "fmt"
    "html/template"
    "net/http"
    "strings"
)

type FormData struct {
    Name  string
    Email string
    Error string
}

func formHandler(w http.ResponseWriter, r *http.Request) {
    if r.Method == "POST" {
        err := r.ParseForm()
        if err != nil {
            http.Error(w, err.Error(), http.StatusBadRequest)
            return
        }

        name := r.FormValue("name")
        email := r.FormValue("email")

        if strings.TrimSpace(name) == "" || strings.TrimSpace(email) == "" {
            data := FormData{Name: name, Email: email, Error: "Please fill in all fields."}
            tmpl := template.Must(template.ParseFiles("form.html"))
            tmpl.Execute(w, data)
            return
        }

        // Escape user input to prevent XSS
        name = html.EscapeString(name)
        email = html.EscapeString(email)

        fmt.Fprintf(w, "Name: %s\nEmail: %s", name, email)
    } else {
        tmpl := template.Must(template.ParseFiles("form.html"))
        tmpl.Execute(w, FormData{})
    }
}

func main() {
    http.HandleFunc("/", formHandler)
    http.ListenAndServe(":8080", nil)
}

防止多次递交表单

package main

import (
    "fmt"
    "html/template"
    "net/http"
    "strings"
)

type FormData struct {
    Name  string
    Email string
    Error string
    Token string
}

func formHandler(w http.ResponseWriter, r *http.Request) {
    if r.Method == "POST" {
        err := r.ParseForm()
        if err != nil {
            http.Error(w, err.Error(), http.StatusBadRequest)
            return
        }

        token := r.FormValue("token")
        if token != r.FormValue("csrf_token") {
            http.Error(w, "Invalid CSRF token", http.StatusBadRequest)
            return
        }

        name := r.FormValue("name")
        email := r.FormValue("email")

        if strings.TrimSpace(name) == "" || strings.TrimSpace(email) == "" {
            data := FormData{Name: name, Email: email, Token: token, Error: "Please fill in all fields."}
            tmpl := template.Must(template.ParseFiles("form.html"))
            tmpl.Execute(w, data)
            return
        }

        // Escape user input to prevent XSS
        name = html.EscapeString(name)
        email = html.EscapeString(email)

        fmt.Fprintf(w, "Name: %s\nEmail: %s", name, email)
    } else {
        token := generateToken()
        data := FormData{Token: token}
        tmpl := template.Must(template.ParseFiles("form.html"))
        tmpl.Execute(w, data)
    }
}

func generateToken() string {
    // Generate a random token here
    return "random-token"
}

func main() {
    http.HandleFunc("/", formHandler)
    http.ListenAndServe(":8080", nil)
}

HTML模板 (form.html)

<!DOCTYPE html>
<html>
<head>
    <title>Form Example</title>
</head>
<body>
    <h1>Submit Form</h1>
    {{ if .Error }}
    <p style="color:red;">{{ .Error }}</p>
    {{ end }}
    <form action="/" method="post">
        <input type="hidden" name="csrf_token" value="{{ .Token }}">
        Name: <input type="text" name="name" value="{{ .Name }}"><br>
        Email: <input type="email" name="email" value="{{ .Email }}"><br>
        <button type="submit">Submit</button>
    </form>
</body>
</html>

处理文件上传

package main

import (
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
    "os"
    "path/filepath"
)

func uploadHandler(w http.ResponseWriter, r *http.Request) {
    if r.Method == "POST" {
        r.ParseMultipartForm(10 << 20) // 10 MB limit

        file, handler, err := r.FormFile("upload")
        if err != nil {
            http.Error(w, err.Error(), http.StatusBadRequest)
            return
        }
        defer file.Close()

        tempFile, err := ioutil.TempFile("uploads", "upload-*.png")
        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }
        defer tempFile.Close()

        fileBytes, err := ioutil.ReadAll(file)
        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }

        tempFile.Write(fileBytes)

        fmt.Fprintf(w, "Successfully uploaded %s\n", handler.Filename)
    } else {
        tmpl := template.Must(template.ParseFiles("upload.html"))
        tmpl.Execute(w, nil)
    }
}

func main() {
    http.HandleFunc("/", uploadHandler)
    http.ListenAndServe(":8080", nil)
}

HTML模板 (upload.html)

<!DOCTYPE html>
<html>
<head>
    <title>Upload File</title>
</head>
<body>
    <h1>Upload File</h1>
    <form action="/" method="post" enctype="multipart/form-data">
        Select file to upload:
        <input type="file" name="upload">
        <button type="submit">Upload</button>
    </form>
</body>
</html>

访问数据库

database/sql 接口

基础概念

  • database/sql:Go标准库中的数据库抽象层。
  • 驱动程序:特定数据库的驱动程序,如_mysql, _sqlite3, _pq等。
  • 连接池:自动管理数据库连接。
package main

import (
    "database/sql"
    "fmt"
    "log"

    _ "github.com/go-sql-driver/mysql"
)

func main() {
    db, err := sql.Open("mysql", "user:password@tcp(127.0.0.1:3306)/dbname")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    err = db.Ping()
    if err != nil {
        log.Fatal(err)
    }

    rows, err := db.Query("SELECT id, name FROM users")
    if err != nil {
        log.Fatal(err)
    }
    defer rows.Close()

    for rows.Next() {
        var id int
        var name string
        err := rows.Scan(&id, &name)
        if err != nil {
            log.Fatal(err)
        }
        fmt.Println(id, name)
    }

    err = rows.Err()
    if err != nil {
        log.Fatal(err)
    }
}

使用MySQL数据库

package main

import (
    "database/sql"
    "fmt"
    "log"

    _ "github.com/go-sql-driver/mysql"
)

func main() {
    db, err := sql.Open("mysql", "user:password@tcp(127.0.0.1:3306)/dbname")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    err = db.Ping()
    if err != nil {
        log.Fatal(err)
    }

    // 创建表
    _, err = db.Exec("CREATE TABLE IF NOT EXISTS users (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255))")
    if err != nil {
        log.Fatal(err)
    }

    // 插入数据
    _, err = db.Exec("INSERT INTO users (name) VALUES (?)", "Alice")
    if err != nil {
        log.Fatal(err)
    }

    // 查询数据
    rows, err := db.Query("SELECT id, name FROM users")
    if err != nil {
        log.Fatal(err)
    }
    defer rows.Close()

    for rows.Next() {
        var id int
        var name string
        err := rows.Scan(&id, &name)
        if err != nil {
            log.Fatal(err)
        }
        fmt.Println(id, name)
    }

    err = rows.Err()
    if err != nil {
        log.Fatal(err)
    }
}

使用SQLite数据库

package main

import (
    "database/sql"
    "fmt"
    "log"

    _ "github.com/mattn/go-sqlite3"
)

func main() {
    db, err := sql.Open("sqlite3", "./test.db")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    err = db.Ping()
    if err != nil {
        log.Fatal(err)
    }

    // 创建表
    _, err = db.Exec("CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT)")
    if err != nil {
        log.Fatal(err)
    }

    // 插入数据
    _, err = db.Exec("INSERT INTO users (name) VALUES (?)", "Alice")
    if err != nil {
        log.Fatal(err)
    }

    // 查询数据
    rows, err := db.Query("SELECT id, name FROM users")
    if err != nil {
        log.Fatal(err)
    }
    defer rows.Close()

    for rows.Next() {
        var id int
        var name string
        err := rows.Scan(&id, &name)
        if err != nil {
            log.Fatal(err)
        }
        fmt.Println(id, name)
    }

    err = rows.Err()
    if err != nil {
        log.Fatal(err)
    }
}

使用PostgreSQL数据库

package main

import (
    "database/sql"
    "fmt"
    "log"

    _ "github.com/lib/pq"
)

func main() {
    db, err := sql.Open("postgres", "user=postgres dbname=mydb sslmode=disable password=secret")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    err = db.Ping()
    if err != nil {
        log.Fatal(err)
    }

    // 创建表
    _, err = db.Exec("CREATE TABLE IF NOT EXISTS users (id SERIAL PRIMARY KEY, name TEXT)")
    if err != nil {
        log.Fatal(err)
    }

    // 插入数据
    _, err = db.Exec("INSERT INTO users (name) VALUES ($1)", "Alice")
    if err != nil {
        log.Fatal(err)
    }

    // 查询数据
    rows, err := db.Query("SELECT id, name FROM users")
    if err != nil {
        log.Fatal(err)
    }
    defer rows.Close()

    for rows.Next() {
        var id int
        var name string
        err := rows.Scan(&id, &name)
        if err != nil {
            log.Fatal(err)
        }
        fmt.Println(id, name)
    }

    err = rows.Err()
    if err != nil {
        log.Fatal(err)
    }
}

使用beego/beedb库进行ORM开发

package main

import (
    "fmt"
    "log"
    "os"

    "github.com/astaxie/beego/orm"
)

type User struct {
    Id   int
    Name string
}

func init() {
    orm.RegisterDriver("mysql", orm.DRMySQL)
    orm.RegisterDataBase("default", "mysql", "user:password@tcp(127.0.0.1:3306)/dbname")
    orm.RegisterModel(new(User))
}

func main() {
    orm.Debug = true

    o := orm.NewOrm()
    o.Using("default") // 默认使用 default

    // 创建表
    o.CreateTable(&User{})

    // 插入数据
    user := User{Name: "Alice"}
    id, err := o.Insert(&user)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("Inserted ID:", id)

    // 查询数据
    var users []User
    _, err = o.QueryTable("users").All(&users)
    if err != nil {
        log.Fatal(err)
    }
    for _, u := range users {
        fmt.Println(u.Id, u.Name)
    }

    // 更新数据
    user.Name = "Bob"
    _, err = o.Update(&user)
    if err != nil {
        log.Fatal(err)
    }

    // 删除数据
    _, err = o.Delete(&user)
    if err != nil {
        log.Fatal(err)
    }
}

NoSQL数据库操作

使用MongoDB

package main

import (
    "context"
    "fmt"
    "log"

    "go.mongodb.org/mongo-driver/bson"
    "go.mongodb.org/mongo-driver/mongo"
    "go.mongodb.org/mongo-driver/mongo/options"
)

func main() {
    clientOptions := options.Client().ApplyURI("mongodb://localhost:27017")
    client, err := mongo.Connect(context.TODO(), clientOptions)
    if err != nil {
        log.Fatal(err)
    }
    defer client.Disconnect(context.TODO())

    collection := client.Database("test").Collection("users")

    // 插入数据
    user := bson.M{"name": "Alice"}
    _, err = collection.InsertOne(context.TODO(), user)
    if err != nil {
        log.Fatal(err)
    }

    // 查询数据
    var result bson.M
    err = collection.FindOne(context.TODO(), bson.M{"name": "Alice"}).Decode(&result)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(result)

    // 更新数据
    filter := bson.M{"name": "Alice"}
    update := bson.M{"$set": bson.M{"name": "Bob"}}
    _, err = collection.UpdateOne(context.TODO(), filter, update)
    if err != nil {
        log.Fatal(err)
    }

    // 删除数据
    _, err = collection.DeleteOne(context.TODO(), bson.M{"name": "Bob"})
    if err != nil {
        log.Fatal(err)
    }
}

session和数据存储

Session 和 Cookie 的区别

Session

  • 存储在服务器端,通常包含用户会话所需的信息。
  • 安全性较高,因为敏感信息不存储在客户端。
  • 可能消耗更多的服务器资源,特别是当大量用户同时在线时。
  • 会话ID通过cookie或其他方式(如URL参数)发送给客户端。

Cookie

  • 存储在客户端浏览器上,可以用来存储小量的数据。
  • 安全性较低,因为数据存储在客户端,容易被截获或篡改。
  • 不占用服务器资源,但可能受到浏览器的限制(例如单个cookie大小限制)。

Go 如何使用 Session

在Go中,可以使用多种方法来管理和使用session。一种常见的方式是使用第三方库,如gorilla/sessions。

使用 gorilla/sessions 安装库:

go get github.com/gorilla/sessions

配置和创建 Session Store:

import (
    "github.com/gorilla/sessions"
)

store := sessions.NewCookieStore([]byte("unique-secret-key"))

设置 Session:

session, _ := store.Get(r, "session-name")
session.Values["username"] = "John Doe"
err := session.Save(r, w)
if err != nil {
    // 处理错误
}

获取 Session:

session, _ := store.Get(r, "session-name")
username, ok := session.Values["username"].(string)
if !ok {
    // 用户名不存在或者类型转换失败
} else {
    fmt.Println("Username is", username)
}

Session 存储

Session数据可以存储在不同的地方,包括但不限于:

  • 内存:适用于小型应用,简单快速,但不适合高并发环境。
  • 文件系统:每个会话对应一个文件,适合低并发环境。
  • 数据库:适合高并发环境,可以使用关系型数据库或NoSQL数据库。
  • 缓存系统:如Redis,适合分布式环境下的会话共享。

预防 Session 劫持

Session劫持是指攻击者获取合法用户的会话标识符(通常是session ID),从而冒充该用户。为了防止这种情况发生,可以采取以下措施:

使用HTTPS:确保所有通信都是加密的,防止中间人攻击。

设置Cookie属性

  • HttpOnly:阻止JavaScript访问cookie,减少XSS攻击的风险。
  • Secure:确保cookie只通过HTTPS传输。
  • 定期更新Session ID:登录成功后立即更改Session ID,或者在用户执行敏感操作后更改。
  • 限制Session的有效期:设置合理的过期时间,避免长时间有效的会话。

检测并限制IP地址或浏览器指纹的变化:如果用户从不同的位置或设备登录,要求重新验证身份。

文本文件处理

文本文件处理

基础概念

  • 读取文件:使用os.Open打开文件,然后使用bufio.Scanner逐行读取。
  • 写入文件:使用os.Create创建文件,然后使用bufio.Writer写入内容。
package main

import (
    "bufio"
    "fmt"
    "log"
    "os"
)

func main() {
    // 读取文件
    file, err := os.Open("input.txt")
    if err != nil {
        log.Fatal(err)
    }
    defer file.Close()

    scanner := bufio.NewScanner(file)
    for scanner.Scan() {
        line := scanner.Text()
        fmt.Println(line)
    }

    if err := scanner.Err(); err != nil {
        log.Fatal(err)
    }

    // 写入文件
    outFile, err := os.Create("output.txt")
    if err != nil {
        log.Fatal(err)
    }
    defer outFile.Close()

    writer := bufio.NewWriter(outFile)
    _, err = writer.WriteString("Hello, world!\n")
    if err != nil {
        log.Fatal(err)
    }
    writer.Flush()
}

XML处理

基础概念

  • 解析XML:使用encoding/xml包解析XML文件。
  • 生成XML:使用xml.Marshal将结构体转换为XML。
package main

import (
    "encoding/xml"
    "fmt"
    "log"
    "os"
)

type Person struct {
    Name    string `xml:"name"`
    Age     int    `xml:"age"`
    Address struct {
        City  string `xml:"city"`
        State string `xml:"state"`
    } `xml:"address"`
}

func main() {
    // 解析XML
    file, err := os.Open("person.xml")
    if err != nil {
        log.Fatal(err)
    }
    defer file.Close()

    decoder := xml.NewDecoder(file)
    var person Person
    err = decoder.Decode(&person)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("%+v\n", person)

    // 生成XML
    person = Person{
        Name: "Alice",
        Age:  30,
        Address: struct {
            City  string `xml:"city"`
            State string `xml:"state"`
        }{
            City:  "New York",
            State: "NY",
        },
    }

    xmlData, err := xml.MarshalIndent(person, "", "  ")
    if err != nil {
        log.Fatal(err)
    }
    xmlData = append([]byte(xml.Header), xmlData...)

    outFile, err := os.Create("output.xml")
    if err != nil {
        log.Fatal(err)
    }
    defer outFile.Close()

    _, err = outFile.Write(xmlData)
    if err != nil {
        log.Fatal(err)
    }
}

JSON处理

基础概念

  • 解析JSON:使用encoding/json包解析JSON文件。
  • 生成JSON:使用json.Marshal将结构体转换为JSON。
package main

import (
    "encoding/json"
    "fmt"
    "log"
    "os"
)

type Person struct {
    Name    string `json:"name"`
    Age     int    `json:"age"`
    Address struct {
        City  string `json:"city"`
        State string `json:"state"`
    } `json:"address"`
}

func main() {
    // 解析JSON
    file, err := os.Open("person.json")
    if err != nil {
        log.Fatal(err)
    }
    defer file.Close()

    decoder := json.NewDecoder(file)
    var person Person
    err = decoder.Decode(&person)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("%+v\n", person)

    // 生成JSON
    person = Person{
        Name: "Alice",
        Age:  30,
        Address: struct {
            City  string `json:"city"`
            State string `json:"state"`
        }{
            City:  "New York",
            State: "NY",
        },
    }

    jsonData, err := json.MarshalIndent(person, "", "  ")
    if err != nil {
        log.Fatal(err)
    }

    outFile, err := os.Create("output.json")
    if err != nil {
        log.Fatal(err)
    }
    defer outFile.Close()

    _, err = outFile.Write(jsonData)
    if err != nil {
        log.Fatal(err)
    }
}

正则处理

基础概念

  • 编译正则表达式:使用regexp.MustCompile编译正则表达式。
  • 匹配字符串:使用MatchString检查字符串是否匹配。
  • 查找子串:使用FindString查找第一个匹配的子串。
  • 替换字符串:使用ReplaceAllString替换匹配的子串。
package main

import (
    "fmt"
    "log"
    "regexp"
)

func main() {
    // 编译正则表达式
    re := regexp.MustCompile(`\b[A-Za-z]+\b`)

    // 匹配字符串
    text := "Hello, world!"
    matched := re.MatchString(text)
    if matched {
        fmt.Println("Matched!")
    } else {
        fmt.Println("Not matched!")
    }

    // 查找子串
    submatch := re.FindString(text)
    fmt.Println("Submatch:", submatch)

    // 替换字符串
    replaced := re.ReplaceAllString(text, "Hi")
    fmt.Println("Replaced:", replaced)
}

模板处理

基础概念

  • 创建模板:使用html/template包创建模板。
  • 渲染模板:使用Execute方法将数据渲染到模板中。
package main

import (
    "html/template"
    "log"
    "os"
)

type Person struct {
    Name string
    Age  int
}

func main() {
    // 创建模板
    tmpl := template.Must(template.ParseFiles("template.html"))

    // 渲染模板
    person := Person{
        Name: "Alice",
        Age:  30,
    }

    err := tmpl.Execute(os.Stdout, person)
    if err != nil {
        log.Fatal(err)
    }
}

文件操作

基础概念

  • 读取文件:使用os.Open打开文件。
  • 写入文件:使用os.Create创建文件。
  • 删除文件:使用os.Remove删除文件。
  • 重命名文件:使用os.Rename重命名文件。
  • 列出目录:使用os.ReadDir列出目录内容。
package main

import (
    "fmt"
    "log"
    "os"
)

func main() {
    // 读取文件
    file, err := os.Open("input.txt")
    if err != nil {
        log.Fatal(err)
    }
    defer file.Close()

    content, err := os.ReadFile("input.txt")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(string(content))

    // 写入文件
    err = os.WriteFile("output.txt", []byte("Hello, world!"), 0644)
    if err != nil {
        log.Fatal(err)
    }

    // 删除文件
    err = os.Remove("output.txt")
    if err != nil {
        log.Fatal(err)
    }

    // 重命名文件
    err = os.Rename("input.txt", "new-input.txt")
    if err != nil {
        log.Fatal(err)
    }

    // 列出目录
    files, err := os.ReadDir(".")
    if err != nil {
        log.Fatal(err)
    }
    for _, file := range files {
        fmt.Println(file.Name())
    }
}

字符串处理

基础概念

  • 拼接字符串:使用+或strings.Join。
  • 分割字符串:使用strings.Split。
  • 替换字符串:使用strings.ReplaceAll。
  • 格式化字符串:使用fmt.Sprintf。
package main

import (
    "fmt"
    "strings"
)

func main() {
    // 拼接字符串
    str1 := "Hello"
    str2 := "world"
    result := str1 + " " + str2
    fmt.Println(result)

    // 分割字符串
    text := "Hello, world!"
    parts := strings.Split(text, ",")
    fmt.Println(parts)

    // 替换字符串
    replaced := strings.ReplaceAll(text, "world", "there")
    fmt.Println(replaced)

    // 格式化字符串
    formatted := fmt.Sprintf("Name: %s, Age: %d", "Alice", 30)
    fmt.Println(formatted)
}

Web服务

Socket编程

基础概念

  • TCP/IP:基于TCP/IP协议的网络通信。
  • UDP:基于UDP协议的网络通信。
  • 连接管理:建立、维护和关闭连接。

示例代码 使用net库创建一个简单的TCP服务器和客户端:

服务器端:

package main

import (
    "bufio"
    "fmt"
    "net"
    "os"
)

func handleConnection(conn net.Conn) {
    defer conn.Close()
    scanner := bufio.NewScanner(conn)
    for scanner.Scan() {
        line := scanner.Text()
        fmt.Fprintf(conn, "Echo: %s\n", line)
    }
}

func main() {
    listener, err := net.Listen("tcp", ":8080")
    if err != nil {
        fmt.Println("Error listening:", err)
        os.Exit(1)
    }
    defer listener.Close()

    for {
        conn, err := listener.Accept()
        if err != nil {
            fmt.Println("Error accepting:", err)
            continue
        }
        go handleConnection(conn)
    }
}

客户端:

package main

import (
    "bufio"
    "fmt"
    "net"
    "os"
)

func main() {
    conn, err := net.Dial("tcp", "localhost:8080")
    if err != nil {
        fmt.Println("Error connecting:", err)
        os.Exit(1)
    }
    defer conn.Close()

    scanner := bufio.NewScanner(os.Stdin)
    for scanner.Scan() {
        line := scanner.Text()
        fmt.Fprintf(conn, "%s\n", line)
    }
}

WebSocket

基础概念

  • WebSocket协议:基于HTTP升级的一种双向通信协议。
  • WebSocket服务器:处理WebSocket连接和消息。
  • WebSocket客户端:与WebSocket服务器进行双向通信。

示例代码

使用gorilla/websocket库创建一个简单的WebSocket服务器和客户端:

安装库:

go get github.com/gorilla/websocket

服务器端:

package main

import (
    "fmt"
    "log"
    "net/http"

    "github.com/gorilla/websocket"
)

var upgrader = websocket.Upgrader{}

func handleWebSocket(w http.ResponseWriter, r *http.Request) {
    conn, err := upgrader.Upgrade(w, r, nil)
    if err != nil {
        log.Println("Upgrade error:", err)
        return
    }
    defer conn.Close()

    for {
        _, message, err := conn.ReadMessage()
        if err != nil {
            log.Println("Read error:", err)
            break
        }
        log.Printf("Received: %s", message)
        err = conn.WriteMessage(websocket.TextMessage, message)
        if err != nil {
            log.Println("Write error:", err)
            break
        }
    }
}

func main() {
    http.HandleFunc("/ws", handleWebSocket)
    log.Fatal(http.ListenAndServe(":8080", nil))
}

客户端:

<!DOCTYPE html>
<html>
<head>
    <title>WebSocket Client</title>
    <script>
        var ws = new WebSocket('ws://localhost:8080/ws');

        ws.onopen = function(event) {
            console.log('WebSocket connected');
        };

        ws.onmessage = function(event) {
            console.log('Received:', event.data);
        };

        ws.onclose = function(event) {
            console.log('WebSocket closed');
        };

        function sendMessage() {
            var message = document.getElementById('message').value;
            ws.send(message);
        }
    </script>
</head>
<body>
    <input type="text" id="message">
    <button onclick="sendMessage()">Send</button>
</body>
</html>

RESTful API

基础概念

  • REST:Representational State Transfer,一种架构风格。
  • HTTP方法:GET、POST、PUT、DELETE等。
  • 资源:通过URI表示的资源。

示例代码 使用gin库创建一个简单的RESTful API:

安装库:

go get github.com/gin-gonic/gin

API代码:

package main

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

type User struct {
    ID   int    `json:"id"`
    Name string `json:"name"`
}

var users = []User{
    {ID: 1, Name: "Alice"},
    {ID: 2, Name: "Bob"},
}

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

    api := router.Group("/api")
    {
        api.GET("/users", getUsers)
        api.GET("/users/:id", getUserByID)
        api.POST("/users", createUser)
        api.PUT("/users/:id", updateUser)
        api.DELETE("/users/:id", deleteUser)
    }

    router.Run(":8080")
}

func getUsers(c *gin.Context) {
    c.JSON(200, users)
}

func getUserByID(c *gin.Context) {
    id := c.Param("id")
    userID, err := strconv.Atoi(id)
    if err != nil {
        c.JSON(400, gin.H{"error": "Invalid user ID"})
        return
    }

    for _, user := range users {
        if user.ID == userID {
            c.JSON(200, user)
            return
        }
    }
    c.JSON(404, gin.H{"error": "User not found"})
}

func createUser(c *gin.Context) {
    var user User
    if err := c.ShouldBindJSON(&user); err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }

    users = append(users, user)
    c.JSON(201, user)
}

func updateUser(c *gin.Context) {
    id := c.Param("id")
    userID, err := strconv.Atoi(id)
    if err != nil {
        c.JSON(400, gin.H{"error": "Invalid user ID"})
        return
    }

    var updatedUser User
    if err := c.ShouldBindJSON(&updatedUser); err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }

    for i, user := range users {
        if user.ID == userID {
            users[i] = updatedUser
            c.JSON(200, updatedUser)
            return
        }
    }
    c.JSON(404, gin.H{"error": "User not found"})
}

func deleteUser(c *gin.Context) {
    id := c.Param("id")
    userID, err := strconv.Atoi(id)
    if err != nil {
        c.JSON(400, gin.H{"error": "Invalid user ID"})
        return
    }

    filteredUsers := make([]User, 0)
    for _, user := range users {
        if user.ID != userID {
            filteredUsers = append(filteredUsers, user)
        }
    }

    users = filteredUsers
    c.JSON(200, gin.H{"message": "User deleted"})
}

RPC

基础概念

  • 远程过程调用:在不同机器上执行函数调用。
  • gRPC:基于HTTP/2的高性能RPC框架。

示例代码 使用gRPC创建一个简单的RPC服务:

安装库:

go get google.golang.org/grpc

定义服务接口:

syntax = "proto3";

service CalculatorService {
  rpc Add (AddRequest) returns (AddResponse) {}
}

message AddRequest {
  int32 a = 1;
  int32 b = 2;
}

message AddResponse {
  int32 result = 1;
}

生成代码:

protoc --go_out=. --go-grpc_out=. calculator.proto

服务端代码:

package main

import (
    "context"
    "log"
    "net"

    calculatorpb "path/to/calculatorpb"

    "google.golang.org/grpc"
)

type server struct{}

func (s *server) Add(ctx context.Context, req *calculatorpb.AddRequest) (*calculatorpb.AddResponse, error) {
    result := req.A + req.B
    return &calculatorpb.AddResponse{Result: result}, nil
}

func main() {
    lis, err := net.Listen("tcp", ":50051")
    if err != nil {
        log.Fatalf("Failed to listen: %v", err)
    }
    grpcServer := grpc.NewServer()
    calculatorpb.RegisterCalculatorServiceServer(grpcServer, &server{})
    log.Printf("Starting gRPC server at %s", lis.Addr())
    if err := grpcServer.Serve(lis); err != nil {
        log.Fatalf("Failed to serve: %v", err)
    }
}

安全与加密

防止CSRF(跨站请求伪造)攻击

  • 使用Token验证:在表单中加入一个隐藏字段,包含一个唯一的token,这个token应该在服务器端生成并在每个会话中唯一。当表单提交时,服务器端验证请求中的token是否与会话中的token匹配。
  • Referer检查:尽管不是绝对可靠,但检查HTTP头部中的Referer字段可以帮助识别请求是否来自预期的站点。
  • SameSite Cookie属性:设置Cookie的SameSite属性为Strict或Lax,这可以减少CSRF攻击的风险,尤其是在第三方上下文中。

输入过滤与验证

  • 白名单验证:只接受预期的数据格式,拒绝所有不符合规则的输入。
  • 黑名单验证:虽然不如白名单安全,但在某些情况下也可以使用,例如禁止特定的字符或模式。

避免XSS(跨站脚本)攻击

  • 输出编码:对于任何用户提供的数据,在输出到HTML之前进行适当的编码,例如使用htmlspecialchars或HTML实体编码。
  • HTTP头设置:设置Content-Security-Policy HTTP头部来限制源的内容加载,从而降低XSS攻击的风险。
  • 安全的HTTP响应头:设置X-XSS-Protection和其他相关的安全响应头。

避免SQL注入

  • 参数化查询:使用预编译的语句或参数化的查询来确保用户输入不会被解释为SQL命令的一部分。
  • ORM框架:使用对象关系映射(ORM)框架通常会自动处理SQL注入的问题。
  • 输入验证:虽然不能完全依赖,但对输入进行验证仍然有助于提高安全性。

存储密码

  • 哈希密码:永远不要以明文形式存储密码,而是使用强哈希算法(如bcrypt、scrypt或argon2)来存储密码的哈希值。
  • 加盐:为每个用户的密码哈希添加一个唯一的盐值,以防止彩虹表攻击。

加密和解密数据

  • 对称加密:对于需要频繁加密解密的数据,可以使用对称加密算法(如AES),并确保密钥的安全。
  • 非对称加密:对于密钥交换或数字签名等场景,使用非对称加密算法(如RSA或ECC)。
  • 密钥管理:确保密钥的安全存储,并定期轮换密钥。

示例代码

使用Go语言的CSRF保护

package main

import (
    "fmt"
    "net/http"
    "time"

    "github.com/gorilla/csrf"
    "github.com/gorilla/mux"
)

func main() {
    r := mux.NewRouter()

    csrfMiddleware := csrf.Protect([]byte("my_secret_key"), csrf.Secure(false))

    r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        token := csrf.Token(r)
        fmt.Fprintf(w, "<form action=\"/submit\" method=\"post\">")
        fmt.Fprintf(w, "<input type=\"hidden\" name=\"%[1]s\" value=\"%[2]s\"/>", csrf.TokenKey, token)
        fmt.Fprintf(w, "<input type=\"text\" name=\"data\"/>")
        fmt.Fprintf(w, "<input type=\"submit\" value=\"Submit\"/>")
        fmt.Fprintf(w, "</form>")
    })

    r.HandleFunc("/submit", csrfMiddleware, func(w http.ResponseWriter, r *http.Request) {
        data := r.FormValue("data")
        fmt.Fprintf(w, "Data submitted: %s", data)
    })

    http.ListenAndServe(":8080", r)
}

使用Go语言的密码哈希

package main

import (
    "fmt"
    "log"
    "golang.org/x/crypto/bcrypt"
)

func main() {
    password := []byte("password123")

    // Hash the password
    hashedPassword, err := bcrypt.GenerateFromPassword(password, bcrypt.DefaultCost)
    if err != nil {
        log.Fatalf("Error hashing password: %v", err)
    }

    fmt.Printf("Hashed Password: %s\n", hashedPassword)

    // Check the password
    err = bcrypt.CompareHashAndPassword(hashedPassword, password)
    if err != nil {
        log.Fatalf("Error comparing password: %v", err)
    }

    fmt.Println("Password is correct.")
}

国际化和本地化

国际化和本地化基础概念

国际化(i18n)

  • 目标:使软件能够在多种语言环境中运行,而无需修改源代码。
  • 主要任务:分离语言资源,使其易于替换或更新。

本地化(l10n)

  • 目标:使软件适应特定地区的语言、文化和习惯。
  • 主要任务:翻译和调整软件界面,使其符合特定地区的要求。

设置默认地区

在Web应用中,通常需要设置一个默认地区,以便在没有明确指定地区的情况下也能正常工作。

示例代码 假设我们使用Go语言来设置默认地区:

package main

import (
    "fmt"
    "time"
)

func main() {
    // 设置默认地区
    defaultLocation, _ := time.LoadLocation("Asia/Shanghai")
    time.Local = defaultLocation

    // 获取当前时间
    now := time.Now().In(defaultLocation)
    fmt.Println("Current Time:", now.Format("2006-01-02 15:04:05"))
}

本地化资源

在Web应用中,本地化资源主要包括文本、日期、货币等格式化方式。以下是一些常用的本地化资源处理方法。

文本本地化 使用Go语言的i18n包来处理文本本地化:

package main

import (
    "fmt"
    "strings"

    "github.com/nicksnyder/go-i18n/v2/i18n"
)

// 定义翻译资源
var (
    bundle = &i18n.Bundle{}
)

func init() {
    // 加载翻译文件
    enTranslation := map[string]string{
        "hello": "Hello, %s!",
        "goodbye": "Goodbye, %s!",
    }
    zhTranslation := map[string]string{
        "hello": "你好,%s!",
        "goodbye": "再见,%s!",
    }

    bundle.RegisterUnstructured("en", enTranslation)
    bundle.RegisterUnstructured("zh", zhTranslation)
}

func translate(locale string, key string, args ...interface{}) string {
    // 创建本地化器
    localizer := i18n.NewLocalizer(bundle, locale)

    // 翻译文本
    msg, err := localizer.Localize(&i18n.LocalizeConfig{
        MessageID: key,
        TemplateData: args,
    })
    if err != nil {
        return fmt.Sprintf("Translation Error: %v", err)
    }
    return msg
}

func main() {
    // 测试翻译
    fmt.Println(translate("en", "hello", "World"))
    fmt.Println(translate("zh", "hello", "世界"))
    fmt.Println(translate("en", "goodbye", "Alice"))
    fmt.Println(translate("zh", "goodbye", "爱丽丝"))
}

日期和时间格式化 使用Go语言的time包来处理日期和时间的格式化:

package main

import (
    "fmt"
    "time"
)

func formatDateTime(t time.Time, locale string) string {
    switch locale {
    case "en":
        return t.Format("January 2, 2006 3:04 PM")
    case "zh":
        return t.Format("2006年1月2日 15:04")
    default:
        return t.Format(time.RFC3339)
    }
}

func main() {
    now := time.Now()
    fmt.Println("English Date:", formatDateTime(now, "en"))
    fmt.Println("Chinese Date:", formatDateTime(now, "zh"))
}

货币格式化 使用Go语言的fmt.Sprintfstrconv包来处理货币的格式化:

package main

import (
    "fmt"
    "strconv"
)

func formatCurrency(amount float64, locale string) string {
    switch locale {
    case "en":
        return fmt.Sprintf("$%.2f", amount)
    case "zh":
        return fmt.Sprintf("¥%.2f", amount)
    default:
        return fmt.Sprintf("%.2f", amount)
    }
}

func main() {
    amount := 1234.56
    fmt.Println("English Currency:", formatCurrency(amount, "en"))
    fmt.Println("Chinese Currency:", formatCurrency(amount, "zh"))
}

国际化站点

在Web应用中,国际化站点通常需要支持多语言版本,并且能够根据用户的地理位置或选择的语言进行切换。

示例代码 使用Go语言和gin框架来实现一个多语言版本的Web站点:

安装库:

go get github.com/gin-gonic/gin
go get github.com/nicksnyder/go-i18n/v2/i18n

定义翻译资源:

package main

import (
    "fmt"
    "net/http"
    "strings"

    "github.com/gin-gonic/gin"
    "github.com/nicksnyder/go-i18n/v2/i18n"
)

// 定义翻译资源
var (
    bundle = &i18n.Bundle{}
)

func init() {
    // 加载翻译文件
    enTranslation := map[string]string{
        "welcome": "Welcome!",
        "goodbye": "Goodbye!",
    }
    zhTranslation := map[string]string{
        "welcome": "欢迎!",
        "goodbye": "再见!",
    }

    bundle.RegisterUnstructured("en", enTranslation)
    bundle.RegisterUnstructured("zh", zhTranslation)
}

func translate(locale string, key string, args ...interface{}) string {
    // 创建本地化器
    localizer := i18n.NewLocalizer(bundle, locale)

    // 翻译文本
    msg, err := localizer.Localize(&i18n.LocalizeConfig{
        MessageID: key,
        TemplateData: args,
    })
    if err != nil {
        return fmt.Sprintf("Translation Error: %v", err)
    }
    return msg
}

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

    // 设置默认语言
    router.DefaultWriter.Header().Set("Content-Language", "en")

    // 处理根路径
    router.GET("/", func(c *gin.Context) {
        locale := c.Query("locale")
        if locale == "" {
            locale = "en"
        }

        c.String(http.StatusOK, translate(locale, "welcome"))
    })

    // 处理其他路径
    router.GET("/goodbye", func(c *gin.Context) {
        locale := c.Query("locale")
        if locale == "" {
            locale = "en"
        }

        c.String(http.StatusOK, translate(locale, "goodbye"))
    })

    router.Run(":8080")
}

使用环境变量设置地区

在生产环境中,可以通过环境变量来设置默认地区:


dockerfile
FROM golang:latest

WORKDIR /app

COPY . .

ENV LANG=en_US.UTF-8
ENV LC_ALL=en_US.UTF-8

CMD ["go", "run", "main.go"]

运行容器

docker build -t myapp .
docker run -p 8080:8080 myapp
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
天涯学馆
天涯学馆
0x9d6d...50d5
资深大厂程序员,12年开发经验,致力于探索前沿技术!