Go语言HTTP编程

52 阅读7分钟

HTTP协议概述

1. HTTP协议基础

  • HTTP: Hypertext Transfer Protocol-超文本传输协议
  • HTTP 是一种无状态应用层协议,基于TCP/IP,默认端口为80(HTTPS为443)
  • 无连接 每次请求/响应后连接通常关闭,HTTP/1.0默认如此,HTTP/1.1支持长久连接
  • 无状态 服务器不保留客户端请求状态,如需保存状态可通过Cookie, Session等机制维持状态
HTTP(应用层)
SSL/TLS
TCP
IP

HTTP + 加密 + 认证 + 完整性保护 = HTTPS(HTTP Secure)

2. HTTP请求结构

一个HTTP请求由三部分组成:

  • 请求行
  • 请求头
  • 请求体
POST /login HTTP/1.1 //请求行
Content-Type: application/json //请求头
Cookie: name1=value1; name2=value2 //Cookie
{"username":"admin","password":"123456"} //请求体

用一个表格展示组成结构

请求结构.png

2.1 请求方法

方法名称概述
GET请求Request-URI所标识的资源
POST向URI提交数据(如提交表单或上传数据)
HEAD类似GET,但返回响应中没有具体内容,用于获取报头
PUT对服务器上已存在的资源进行更新
DELETE请求服务器删除指定页面
CONNECTHTTP/1.1预留,能够将连接改为管道方式的代理服务器
OPTIONS查看服务端性能
TRACE回显服务器收到请求,主要用于测试或诊断
PATCH类似PUT,只对资源一部分更新,如果资源不存在会创建

2.2 URL

http://wwww.example.com:8080/news/tech/12345.html?id=111&name=aaa#pic
 |             |          |      |        |          |             |
协议          域名       端口   路径     文件名       参数          锚点

2.3 请求头

Header概述示例
Accept指定客户端能够接收的内容类型Accept:text/plain, text/html
Accept-Charset客户端可以接受的字符编码集Accept-Charset:iso-8859-5
Accept-Encoding客户端可支持的压缩编码类型Accept-Encoding:compress, gzip
Accept-Language客户端可接受的语言Accept-Language:en, zh
AuthorizationHTTP授权的证书Authorization: Basic YWRtaW46MTIzNDU2
Cache-Control指定请求和响应的缓存机制Cache-Control:no-cache
Connection判定是否需要持久连接(HTTP1.1默认进行持久连接)Connection:close
CookieHTTP请求发送时,保存在该请求域名下的所有cookie值一起发送给服务器Cookie:$Version=1;Skin=new;
Content-Length请求内容长度Content-Length:348
Content-Type指定请求体的数据格式Content-Type:application/x-www-form-urlencoded
User-Agent浏览器信息Chrome/3.0(Windows NT 6.1;Win 64;x64)

2.4 请求体

POST/post?id=aaa&page=12 HTTP/1.1
Content-Type:application/x-www-form-urlencoded

name=LiMing&message=how_are_you //请求体
Content-Type
类型概述示例
appplication/x-www-form-urlencoded浏览器原生fom表单,不过不设置Content-Type,默认以该类型输出name=LiMing&message=how_are_you
multipart/form-data上传文件时使用,支持多种文件格式name="test"name="file";filename="Edge.png"Content-type:img/png...content of Edge.png
application/json发的请求体(body)是一段JSON文本,按JSON的格式去解析它{"title":"test", "sub":[1,2,3,4,5]}
text/xml发的请求体(body)是xml,请用xml解析器处理

3. HTTP响应结构

一个HTTP响应结构由三部分组成:

  • 响应行
  • 响应头
  • 响应体
HTTP/1.1 200 OK //响应行
Content-Type: application/json; charset=utf-8  响应头

{"status":"OK","data":{"orderId":"ORD-20251108-001","amount":299.99,"currency":"CNY"}} //响应体

响应结构.png

3.1 状态及话术

状态类别状态码状态名称适用场景标准话术模板
信息响应100Continue客户端应继续发送请求"请求已接收,请继续发送剩余数据。"
101Switching Protocols协议切换"正在切换协议,请稍候。"
成功响应200OK请求成功处理"您的请求已成功处理,结果如下:[数据/信息]"
201Created资源创建成功"资源创建成功,新资源已生成。"
204No Content请求成功但无返回内容"请求已成功处理,无需返回内容。"
重定向301Moved Permanently资源永久移动"资源已永久移动至新地址:[新URL]"
302Found资源临时移动"资源临时移动,请访问:[临时URL]"
304Not Modified资源未修改"资源未更新,可使用缓存版本。"
客户端错误400Bad Request请求语法错误"请求格式有误,请检查输入参数后重试。"
401Unauthorized未授权访问"请先登录或获取访问权限后再尝试。"
403Forbidden服务器拒绝访问"您无权访问此资源,请联系管理员。"
404Not Found资源不存在"抱歉,未找到您请求的资源,请确认URL是否正确。"
429Too Many Requests请求过于频繁"您的请求过于频繁,请稍后再试。"
服务器错误500Internal Server Error服务器内部错误"服务器暂时无法处理请求,请稍后再试或联系技术支持。"
502Bad Gateway网关错误"网络连接异常,请检查网络设置或稍后重试。"
503Service Unavailable服务器不可用"服务暂时不可用,正在紧急修复中,请稍后再试。"

3.2 响应头

Header概述示例
Cache-Control控制缓存策略Cache-Control: max-age=3600, must-revalidate
Content-Type响应体 MIME 类型及编码Content-Type: application/json; charset=utf-8
Content-Length响应体字节长度Content-Length: 4213
Content-Encoding响应体压缩算法Content-Encoding: gzip
Location重定向目标地址Location: example.com/new-path
Access-Control-Allow-OriginCORS 允许源Access-Control-Allow-Origin: app.example.com
Strict-Transport-Security强制 HTTPS(HSTS)Strict-Transport-Security: max-age=63072000; includeSubDomains
X-Content-Type-Options禁用 MIME 嗅探X-Content-Type-Options: nosniff

Go语言中HTTP请求处理的底层实现

1. 工作流程

客户端发起HTTP请求,通过Go语言实现服务器监听、接收、处理并返回响应。底层工作流程如下:

a. 创建Listen Socket, 监听指定端口,等待客户端请求;

b. Listen Socket接收客户端的请求, 得到Client Socket,通过Client Socket与客户端通信;

c. 处理客户端请求,读取Client Socket的请求头,如果是POST方法,则读取客户端提交的数据,然后交给Handler(处理器)处理,Handler处理完毕后转载完客户端需要的数据,通过Client Socket返回给客户端;

d. 生成响应并传回给客户端;

f. 关闭连接。

2. 创建Listen Socket监听端口

使用Golang的net包,用Listen函数创建。示例代码如下:

package main

import (
	"fmt"
	"net"
)

func handleConnection(conn net.Conn) {
	defer conn.Close()
        // 处理连接请求
}

func main(){
	listen, err := net.Listen("tcp", ":8080")
	if err != nil {
		fmt.Println("Failed to listen", err)
	}

	defer listen.Close()

	fmt.Println("Listening 8080")

	for {
		conn, err := listen.Accept()
		if err != nil {
			fmt.Println("Failed to accept connection:", err)
			continue
		}

		go handleConnection(conn)
	}
}

在这段代码里,我们使用net.Listen函数创建了一个TCP的Listen Socket, 监听端口为8080。如果监听失败,则会打印错误信息退出。for循环中,通过listen.Accept接收客户端连接,并将连接交给handleConnection函数进行处理。

3. 接收客户端请求并建立连接

当Listen Socket监听到客户端的连接请求后,通过istener.Accept方法与客户端建立连接。

func handleConnection(conn net.Conn) {
	defer conn.Close()

	buffer := make([]byte, 1024)
	n, err := conn.Read(buffer)
	if err != nil {
		fmt.Println("Failed to read request:", err)
		return
	}

	request := string(buffer[:n])
	fmt.Println("Received request:", request)

	// 处理请求
}

通过conn.Read方法从连接中读取请求数据,并将其转换为字符串。

4. 处理客户端请求并返回响应

接上段代码

  //处理请求
	response := "HTTP/1.1 200 OK\r\nContent-Length:12\r\nHello, World!"
        _, err = conn.Write([]byte(response))
	if err != nil {
		fmt.Println("Failed to response:", err)
		return
	}

	fmt.Println("Sent response:", response)
}

通过conn.Write方法将响应发送给客户端。具体响应格式可以翻看前面 ## 3.3响应结构

完整代码展示

package main

import (
	"fmt"
	"net"
)

func handleConnection(conn net.Conn) {
	defer conn.Close()

	buffer := make([]byte, 1024)
	n, err := conn.Read(buffer)
	if err != nil {
		fmt.Println("Failed to read request:", err)
		return
	}

	request := string(buffer[:n])
	fmt.Println("Received request:", request)

	// 处理请求
	response := "HTTP/1.1 200 OK\r\nContent-Length:12\r\nHello, World!"
	_, err := conn.Write([]byte(response))
	err != nil {
		fmt.Println("Failed to response:", err)
		return
	}

	fmt.Println("Sent response:", response)
}

func main() {
	listen, err := net.Listen("tcp", ":8080")
	if err != nil {
		fmt.Println("Failed to listen", err)
	}

	defer listen.Close()

	fmt.Println("Listening 8080")

	for {
		conn, err := listen.Accept()
		if err != nil {
			fmt.Println("Failed to accept connection:", err)
			continue
		}

		go handleConnection(conn)
	}
}

在这个示例中,我们创建了一个Listen Socket来监听端口8080, 通过for循环不断接受客户端连接。每当接收到连接时,会创建一个goroutine来处理请求。处理完请求之后,生成响应并返回给客户端。