go的web开发入门 | 青训营

127 阅读5分钟

goweb

参考


1、定义拦截器

  • 配置一下路由http.HandleFunc(路由,拦截方法)
  • 监听http.ListenAndServe()
func main() {
    http.HandleFunc("/", handler) // each request calls handler
    log.Fatal(http.ListenAndServe("localhost:8000", nil))
}

请求

  • r *http.Request:请求对象

响应

  • w http.ResponseWriter:一个响应流,只接受字节[]
  • fmt.Fprintf直接写到了http.ResponseWriter
// handler echoes the Path component of the request URL r.
func handler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "URL.Path = %q\n", r.URL.Path)
    //w.Write([]byte(r.URL.Path))
}
  • 使用bufio输出流响应
// handler echoes the Path component of the request URL r.
func handler(w http.ResponseWriter, r *http.Request) {
    // Creating a buffered writer
    buffer := bufio.NewWriter(w)
​
    // Writing the URL.Path to the buffered writer
    buffer.WriteString(r.URL.Path)
​
    buffer.Flush()
}
  • go run server.go
  • 请求
http://localhost:8000?name=zc

2、多个拦截器

    http.HandleFunc("/", handler) //访问其他url都会跑到 '/' 下
    http.HandleFunc("/count", counter)

如果你的请求pattern是以/结尾,那么所有以该url为前缀的url都会被这条规则匹配。在这些代码的背后,服务器每一次接收请求处理时都会另起一个goroutine,这样服务器就可以同一时间处理多个请求。

  • 对于有并发安全问题

    var mu sync.Mutex
    var count int //全局变量,系统运行时一直存放着//使用锁即可
    mu.Lock()
    fmt.Fprintf(w, "Count %d\n", count)
    mu.Unlock()
    

3、请求报文

func handler(w http.ResponseWriter, r *http.Request) {
    //请求行
    fmt.Fprintf(w, "%s %s %s\n", r.Method, r.URL, r.Proto)
    
    //请求头
    for k, v := range r.Header {
        fmt.Fprintf(w, "Header[%q] = %q\n", k, v)
    }
    fmt.Fprintf(w, "Host = %q\n", r.Host)
    fmt.Fprintf(w, "RemoteAddr = %q\n", r.RemoteAddr)
    
    //请求参数
    err := r.ParseForm()
     if err != nil {
         log.Print(err)
     }
    
    for k, v := range r.Form {
        fmt.Fprintf(w, "Form[%q] = %q\n", k, v)
    }
}
http://localhost:8000?name=zc
​
GET /?name=zc HTTP/1.1
Header["Accept-Encoding"] = ["gzip"]
Header["User-Agent"] = ["Go-http-client/1.1"]
Host = "localhost:8000"
RemoteAddr = "127.0.0.1:11192"
Form["name"] = ["zc"]

请求转发

在 Go 中实现请求转发(forwarding)或代理转发是通过创建一个中间处理程序(handler)来完成的。该中间处理程序接收来自客户端的请求,然后将该请求转发到另一个服务器,并将该服务器返回的响应转发回客户端。

func forwardHandler(targetURL string) http.Handler {
    target, _ := url.Parse(targetURL)
​
    // 创建 ReverseProxy 实例,用于请求转发
    proxy := httputil.NewSingleHostReverseProxy(target)
​
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // 设置转发的请求头
        r.Host = target.Host
        r.URL.Host = target.Host
        r.URL.Scheme = target.Scheme
​
        // 转发请求到目标服务器
        proxy.ServeHTTP(w, r)
    })
}
​
func main() {
    // 创建一个请求转发处理程序,将请求转发到目标服务器
    // 例如:将来自客户端的请求转发到 http://example.com
    http.Handle("/", forwardHandler("http://example.com"))
​
    // 启动 HTTP 服务器并监听指定端口
    http.ListenAndServe(":8080", nil)
}

请求参数

无论是来自 POST 请求的表单数据,还是来自 GET 请求的 URL 查询参数。HTTP 请求中的参数包含在 r.URL.Query()r.Form 字段中

  • 对于 GET 请求

    • r.URL.Query():查询参数是通过 URL 中的查询字符串(query string)传递
    • Form.Get("q"):字段获取表单数据
func handler(w http.ResponseWriter, r *http.Request) {
    //1、
    query := r.URL.Query()
    keyword := query.Get("q")
    page := query.Get("page")
​
    fmt.Println("Keyword:", keyword)
    fmt.Println("Page:", page)
    
    //2、
    // 不需要调用 r.ParseForm(),因为 GET 请求的查询参数已经自动解析到 r.Form 字段中
    keyword := r.Form.Get("q")
    page := r.Form.Get("page")
​
    fmt.Println("Keyword:", keyword)
    fmt.Println("Page:", page)
}

对于 GET 请求,r.ParseForm() 是可选的,因为 Go 的 http.Request 结构在处理 GET 请求时自动解析并将查询参数放入 r.Form 字段中。

  • 对于 POST 请求,表单数据通常是通过请求体(request body)传递的。可以通过 r.ParseForm() 方法解析请求体,并通过 r.Form 字段获取表单数据。
func handler(w http.ResponseWriter, r *http.Request) {
    
    if err != nil {
        // 处理解析表单数据时可能出现的错误
        fmt.Println("Error parsing form:", err)
        http.Error(w, "Error parsing form", http.StatusBadRequest)
        return
    }
​
    username := r.Form.Get("username")
    password := r.Form.Get("password")
​
    fmt.Println("Username:", username)
    fmt.Println("Password:", password)
}

总之统一使用

  • err := r.ParseForm()
  • r.Form.Get("username")

4、响应报文

  1. 响应行(状态码): 在 HTTP 响应中,响应行由三部分组成:HTTP 版本、状态码和状态文本。状态码是一个三位数字,用来表示服务器对请求的处理结果。常见的状态码包括 200 OK(请求成功)、404 Not Found(资源未找到)、500 Internal Server Error(服务器内部错误)等。在 Go 中,可以通过设置 http.ResponseWriterWriteHeader() 方法来指定响应的状态码。

    func handler(w http.ResponseWriter, r *http.Request) {
        w.WriteHeader(http.StatusOK) // 设置状态码为200 OK
    }
    
  2. 响应头: HTTP 响应头是由服务器返回给客户端的一组键值对,用来描述响应的属性和元数据。在 Go 中,您可以使用 http.ResponseWriterHeader() 方法来设置响应头。例如,可以设置 Content-Type 表头来指定响应体的类型。

    goCopy codefunc handler(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "text/plain; charset=utf-8")
    }
    
  3. 响应体: HTTP 响应体是服务器返回给客户端的实际内容。在 Go 中,可以使用 http.ResponseWriterWrite() 方法将内容写入响应体。常见的用法是将字符串或字节切片写入响应体,例如:

    func handler(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("Hello, World!"))
    }
    
  4. 重定向: 重定向是一种特殊的响应,用来告诉客户端重新向其他 URL 发起请求。在 Go 中,可以使用 http.Redirect() 函数来实现重定向。该函数会设置响应的状态码为 3xx,并在响应头中添加 Location 字段指向新的 URL。

    func handler(w http.ResponseWriter, r *http.Request) {
        http.Redirect(w, r, "https://example.com/new-page", http.StatusFound)
    }
    

    上述代码会将客户端重定向到 "example.com/new-page" 页面。

  5. 响应字符数据: 对于响应的字符数据,可以直接使用 http.ResponseWriterWrite() 方法将字符串写入响应体,或者将字符串转换为字节数组后使用相同的方法。例如:

    func handler(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "text/plain; charset=utf-8")
        w.Write([]byte("这是中文字符数据"))
    }
    

5、主动请求

  • 请求:resp=http.Get(url)
  • 响应:resp.Body
package main
import (
    "bufio"
    "fmt"
    "net/http"
)
func main() {
//向服务端发送一个 HTTP GET 请求。 http.Get 是创建 http.Client 对象并调用其 Get 方法的快捷方式。 它使用了 http.DefaultClient 对象及其默认设置。
    resp, err := http.Get("http://gobyexample.com")
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()
​
    //打印 HTTP response 状态.
    fmt.Println("Response status:", resp.Status)
​
    //打印 response body
    scanner := bufio.NewScanner(resp.Body)
    for scanner.Scan() {
        fmt.Println(scanner.Text())
    }
}

6、context

首先了解一下select管道通信

select实例

// 定义两个通道
ch1 := make(chan string)
ch2 := make(chan string)
​
// 启动两个 goroutine,分别从两个通道中获取数据
go func() {
    for {
        ch1 <- "from 1"
    }
}()
go func() {
    for {
        ch2 <- "from 2"
    }
}()
​
// 使用 select 语句非阻塞地从两个通道中获取数据
for {
    select {
        case msg1 := <-ch1:
        fmt.Println(msg1)
        case msg2 := <-ch2:
        fmt.Println(msg2)
        default:
        // 如果两个通道都没有可用的数据,则执行这里的语句
        fmt.Println("no message received")
    }
}c1 := make(chan string)
c2 := make(chan string)
​
go func() {
    time.Sleep(1 * time.Second)
    c1 <- "one"
}()
go func() {
    time.Sleep(2 * time.Second)
    c2 <- "two"
}()
​
for i := 0; i < 2; i++ {
    select {
        case msg1 := <-c1:
        fmt.Println("received", msg1)
        case msg2 := <-c2:
        fmt.Println("received", msg2)
    }
}
  1. 如果任意某个通道可以进行,它就执行,其他被忽略。

  2. 如果有多个 case 都可以运行,select 会随机公平地选出一个执行,其他不会执行。

  3. 如果有 default 子句,则执行该语句。

  4. 如果没有 default 子句,select 将阻塞,直到某个通道可以运行;Go 不会重新对 channel 或值进行求值。

  5. 在管道关闭的时候也会返回空构造体,case也可以接受到信号

        ch1 := make(chan string)
        go func() {
            time.Sleep(time.Second * 2)
            close(ch1)
        }()
        select {
        case msg1 := <-ch1: //管道就算关闭了返回空构造体
            fmt.Println(msg1) //空
        }
    

Go语言中的select语句,以及Java中的wait()notify()方法,以及操作系统中的同步机制,都采用了被动等待的策略。这些机制允许线程或协程在条件不满足时进入等待状态,并在条件满足时被唤醒,避免了不必要的轮询,减少了资源的浪费。

context上下文

context.Context 被用于控制 cancel。 Context 跨 API 边界和协程携带了:deadline、取消信号以及其他请求范围的值。

  • net/http 机制为每个请求创建了一个 context.Context, 并且可以通过 Context() 方法获取并使用它。

  • 在工作时,请密切关注 context 的 Done() 通道的信号, 一旦收到该信号,表明我们应该取消工作并尽快返回。

    • Done() 返回通道的信号:客户端主动断开连接触发
func hello(w http.ResponseWriter, req *http.Request) {
​
    ctx := req.Context()
    
    fmt.Println("server: hello handler started")
    defer fmt.Println("server: hello handler ended")
​
    select {
    case <-time.After(10 * time.Second):
        fmt.Fprintf(w, "hello\n")
        
    case <-ctx.Done():  //触发时会返回一个空构造体struct{}到通道中
        err := ctx.Err()
        fmt.Println("server:", err)
        internalError := http.StatusInternalServerError
        http.Error(w, err.Error(), internalError)
    }
}
​
func main() {
    http.HandleFunc("/hello", hello)
    http.ListenAndServe(":8090", nil)
}
​