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、响应报文
-
响应行(状态码): 在 HTTP 响应中,响应行由三部分组成:HTTP 版本、状态码和状态文本。状态码是一个三位数字,用来表示服务器对请求的处理结果。常见的状态码包括 200 OK(请求成功)、404 Not Found(资源未找到)、500 Internal Server Error(服务器内部错误)等。在 Go 中,可以通过设置
http.ResponseWriter的WriteHeader()方法来指定响应的状态码。func handler(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) // 设置状态码为200 OK } -
响应头: HTTP 响应头是由服务器返回给客户端的一组键值对,用来描述响应的属性和元数据。在 Go 中,您可以使用
http.ResponseWriter的Header()方法来设置响应头。例如,可以设置Content-Type表头来指定响应体的类型。goCopy codefunc handler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/plain; charset=utf-8") } -
响应体: HTTP 响应体是服务器返回给客户端的实际内容。在 Go 中,可以使用
http.ResponseWriter的Write()方法将内容写入响应体。常见的用法是将字符串或字节切片写入响应体,例如:func handler(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Hello, World!")) } -
重定向: 重定向是一种特殊的响应,用来告诉客户端重新向其他 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" 页面。
-
响应字符数据: 对于响应的字符数据,可以直接使用
http.ResponseWriter的Write()方法将字符串写入响应体,或者将字符串转换为字节数组后使用相同的方法。例如: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实例
// 定义两个通道
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)
}
}
-
如果任意某个通道可以进行,它就执行,其他被忽略。
-
如果有多个 case 都可以运行,select 会随机公平地选出一个执行,其他不会执行。
-
如果有 default 子句,则执行该语句。
-
如果没有 default 子句,select 将阻塞,直到某个通道可以运行;Go 不会重新对 channel 或值进行求值。
-
在管道关闭的时候也会返回空构造体,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)
}