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()
        fm...

剩余50%的内容订阅专栏后可查看

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论