HTTP 协议详解
HTTP(HyperText Transfer Protocol,超文本传输协议)是互联网中最基本的协议之一,用于在客户端(通常是浏览器)和服务器之间传输超媒体文档(HTML)。
HTTP协议包含、请求与响应结构、常用方法、状态码、头部字段以及安全性等内容。
HTTP 是一种应用层协议,主要用于分布式、协作式信息系统中。它基于请求-响应模型,客户端发送请求到服务器,服务器返回响应。HTTP 通常运行在TCP之上,默认端口为80。
HTTP 的发展历史
- HTTP/0.9(1991年):最早的版本,仅支持GET请求,用于传输纯文本HTML。
- HTTP/1.0(1996年):引入了请求和响应头,支持多种方法和状态码,增强了协议的功能。
- HTTP/1.1(1997年):当前广泛使用的版本,增加了持久连接、请求流水线、分块传输等特性,提高了性能和灵活性。
- HTTP/2(2015年):引入二进制协议、多路复用、头部压缩等优化,显著提升传输效率。
- HTTP/3(2022年):基于QUIC传输协议,旨在解决HTTP/2中的队头阻塞问题,进一步提升性能和安全性。
HTTP 的基本概念
- 客户端(Client):发起HTTP请求的实体,通常是浏览器或移动应用。
- 服务器(Server):接收并处理HTTP请求,返回响应的实体。
- 请求(Request):客户端向服务器发送的信息,包括方法、URL、头部和主体。
- 响应(Response):服务器返回给客户端的信息,包括状态码、头部和主体。
- 无状态协议(Stateless Protocol):每个请求都是独立的,服务器不保留客户端的状态信息。
HTTP 请求与响应结构
请求结构
一个标准的HTTP请求由以下几个部分组成:
<Method> <Request-URL> <HTTP-Version>
<Header1>: <Value1>
<Header2>: <Value2>
...
<Body>
- 请求行(Request Line):
- 方法(Method):如GET、POST、PUT、DELETE等,表示请求的操作类型。
- 请求URL(Request-URL):请求的资源路径。
3.HTTP版本(HTTP-Version):如HTTP/1.1、HTTP/2。
- 请求头部(Headers):包含关于请求的元数据,如Host、User-Agent、Content-Type等。
- 空行
- 请求体(Body):仅在某些方法(如POST、PUT)中存在,携带要发送给服务器的数据。
示例:
GET /index.html HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0
Accept: text/html
响应结构
一个标准的HTTP响应由以下几个部分组成:
<HTTP-Version> <Status-Code> <Reason-Phrase>
<Header1>: <Value1>
<Header2>: <Value2>
...
<Body>
- 响应行(Status Line):
- HTTP版本(HTTP-Version):如HTTP/1.1。
- 状态码(Status-Code):三位数字,表示请求的结果,如200、404、500等。
- 原因短语(Reason-Phrase):对状态码的简短描述。
- 响应头部(Headers):包含关于响应的元数据,如Content-Type、Content-Length、Set-Cookie等。
- 空行
- 响应体(Body):包含服务器返回的数据
示例:
HTTP/1.1 200 OK
Content-Type: text/html; charset=UTF-8
Content-Length: 1354
<!DOCTYPE html>
<html>
<head>
<title>示例页面</title>
</head>
<body>
<h1>欢迎来到示例页面</h1>
</body>
</html>
HTTP 请求方法
HTTP定义了一组方法(也称为动词),用于指示对资源执行的操作。常用的HTTP方法包括:
- GET:请求获取指定资源的信息。
- POST:提交数据以创建新资源。
- PUT:更新现有资源。
- DELETE:删除指定资源。
- HEAD:类似于GET,但只返回响应头,不返回响应体。
- OPTIONS:查询服务器支持的HTTP方法。
- PATCH:对资源进行部分修改。
示例:
POST /submit-form HTTP/1.1
Host: www.example.com
Content-Type: application/x-www-form-urlencoded
name=John&age=30
HTTP 响应状态码
HTTP状态码用于表示请求的处理结果,分为五类:
-
1xx(信息性响应):请求已被接收,继续处理。
-
2xx(成功):请求已成功被服务器接收、理解并接受。
- 200 OK:请求成功。
- 201 Created:资源成功创建。
-
3xx(重定向):需要进一步操作以完成请求。
- 301 Moved Permanently:资源永久移动到新位置。
- 302 Found:资源临时移动。
-
4xx(客户端错误):请求包含语法错误或无法完成。
- 400 Bad Request:请求格式错误。
- 401 Unauthorized:需要认证。
- 403 Forbidden:服务器理解请求但拒绝执行。
- 404 Not Found:资源未找到。
-
5xx(服务器错误):服务器在处理请求时发生了错误。
- 500 Internal Server Error:服务器内部错误。
- 503 Service Unavailable:服务暂时不可用。
示例:
HTTP/1.1 404 Not Found
Content-Type: text/html; charset=UTF-8
<!DOCTYPE html>
<html>
<head>
<title>404 页面未找到</title>
</head>
<body>
<h1>404 页面未找到</h1>
</body>
</html>
HTTP 头部字段
HTTP头部字段用于传递附加信息,增强请求和响应的语义。头部字段可以分为通用头部、请求头部、响应头部和实体头部。
常见头部字段:
-
通用头部:
Cache-Control:控制缓存行为。Connection:控制连接方式(如keep-alive)。
-
请求头部:
Host:指定服务器的域名和端口。User-Agent:标识客户端软件。Accept:指明客户端能接收的内容类型。
-
响应头部:
Server:标识服务器软件。Set-Cookie:设置客户端的Cookie。
-
实体头部:
Content-Type:描述实体主体的媒体类型。Content-Length:实体主体的长度。
示例:
GET /search?q=HTTP HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
用socket发送http响应给浏览器:
package main
import (
"bufio"
"fmt"
"io"
"log"
"net"
"os"
"strconv"
"strings"
"time"
)
var (
requestTime int
)
func handleConnection(conn net.Conn) {
defer conn.Close()
defer func() {
if r := recover(); r != nil {
fmt.Printf("捕获到 panic:%#v", r)
}
fmt.Println("响应结束。释放资源===========")
}()
requestTime++
fmt.Printf("客户端【%s】开始第【%d】请求开始:~~~~~~~\n", conn.RemoteAddr().String(), requestTime)
// 设置读写超时
conn.SetReadDeadline(time.Now().Add(10 * time.Second))
conn.SetWriteDeadline(time.Now().Add(10 * time.Second))
reader := bufio.NewReader(conn)
// 读取请求行
requestLine, err := reader.ReadString('\n')
if err != nil {
if err != io.EOF {
log.Printf("读取请求行失败: %v", err)
}
return
}
fmt.Printf("收到请求行: %s", requestLine)
//判断请求行,如果是favicon.ico。返回本路径下的图标文件
requestLine = strings.ToLower(requestLine)
if strings.Contains(requestLine, "favicon.ico") {
b, err := os.ReadFile("test.ico")
if err != nil {
panic(err)
}
//响应图片
respstr := `HTTP/1.1 200 OK
Content-Type: image/x-icon
Content-Length: ` + strconv.Itoa(len(b)) + "\r\n\r\n"
_, err = conn.Write([]byte(respstr))
conn.Write(b)
if err != nil {
log.Printf("发送响应失败: %v", err)
return
}
fmt.Println("收藏夹图标。浏览器的地址栏显示网站的图标发送完毕。")
return
}
// 读取请求头
for {
header, err := reader.ReadString('\n')
if err != nil || header == "\r\n" || header == "\n" || strings.TrimSpace(header) == "" {
break
}
fmt.Printf("请求头: %s", header)
}
fmt.Println("解析完请求头")
// 构造HTTP响应
body := "Hello, 这是一个通过Socket编写的HTTP服务器!"
response := fmt.Sprintf("HTTP/1.1 200 OK\r\n"+
"Content-Type: text/plain; charset=UTF-8\r\n"+
"Content-Length: %d\r\n"+
"\r\n"+
"%s", len(body), body)
// 发送响应
_, err = conn.Write([]byte(response))
if err != nil {
log.Printf("发送响应失败: %v", err)
return
}
fmt.Println("已发送响应给客户端")
}
func main() {
listener, err := net.Listen("tcp", ":80")
if err != nil {
log.Fatalf("监听端口失败: %v", err)
}
defer listener.Close()
fmt.Println("服务器正在监听端口80...")
for {
conn, err := listener.Accept()
if err != nil {
log.Printf("接受连接失败: %v", err)
continue
}
go handleConnection(conn)
}
}