http协议

519 阅读6分钟

 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)​

  1. 方法(Method)​:如GET、POST、PUT、DELETE等,表示请求的操作类型。
  2. 请求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)​

  1. HTTP版本(HTTP-Version)​:如HTTP/1.1。
  2. 状态码(Status-Code)​:三位数字,表示请求的结果,如200、404、500等。
  3. 原因短语(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)
	}
}