HTTP(Hypertext Transfer Protocol,超文本传输协议)是一种用于传输超媒体文档(例如HTML、XML)的应用层协议。它是构建在TCP/IP协议之上的,通过客户端-服务器模型进行通信。HTTP的工作原理是客户端发送请求给服务器,服务器接收请求并返回响应给客户端。请求由一个URL(Uniform Resource Locator,统一资源定位符)标识资源,并使用不同的HTTP方法(例如GET、POST、PUT、DELETE)来指定请求的操作类型。响应包括一个状态码表示请求的结果状态(例如200表示成功,404表示未找到资源)和响应的内容。

一、HTTP协议的工作流程及实例

1、HTTP协议工作流程

HTTP协议的基本工作流程。通过这种请求-响应模型,客户端和服务器之间可以进行有效的数据传输和通信。HTTP协议在互联网上广泛应用于Web浏览器和服务器之间的数据交互。

1)客户端发起请求

客户端(例如Web浏览器)向服务器发送HTTP请求。请求由请求行、请求头部和请求体组成。请求行包含请求方法(GET、POST等)、请求的URL和HTTP协议版本。

2)服务器接收请求

服务器接收到客户端发送的HTTP请求,并解析请求行和请求头部,获取请求的目标资源。

3)服务器处理请求

服务器根据请求的方法和URL,执行相应的处理逻辑。这可能涉及到查询数据库、处理业务逻辑等操作。

4)服务器生成响应

服务器生成一个HTTP响应,包含一个状态行、响应头部和响应体。状态行包含响应的状态码(例如200表示成功,404表示未找到资源),响应头部包含附加信息(例如内容类型、内容长度),响应体包含实际的响应数据。

5)服务器发送响应

服务器将生成的HTTP响应发送回客户端。响应经过网络传输到客户端。

6)客户端接收响应

客户端接收到服务器发送的HTTP响应。

7)客户端处理响应

客户端根据响应的状态码和响应头部进行相应的处理。例如,如果状态码为200,表示成功,客户端可以解析响应体中的数据并进行展示;如果状态码为404,表示未找到资源,客户端可以显示错误页面或者执行其他操作。

8)客户端关闭连接

客户端关闭与服务器的连接,完成一次HTTP请求-响应的交互。

HTTP协议的工作流程示意图:

   +-------------+                       +-------------+
   |             |                       |             |
   |   Client    |                       |   Server    |
   |             |                       |             |
   +-------------+                       +-------------+
        |                                       |
        |        Step 1: 发起请求                |
        |-------------------------------------->|
        |                                       |
        |        Step 2: 接收请求                |
        |<--------------------------------------|
        |                                       |
        |        Step 3: 处理请求                |
        |                                       |
        |                                       |
        |        Step 4: 生成响应                |
        |                                       |
        |                                       |
        |        Step 5: 发送响应                |
        |-------------------------------------->|
        |                                       |
        |        Step 6: 接收响应                |
        |<--------------------------------------|
        |                                       |
        |        Step 7: 处理响应                |
        |                                       |
        |                                       |
        |        Step 8: 关闭连接                |
        |-------------------------------------->|
        |                                       |

注意:我们在浏览器输入网址,执行HTTP请求,浏览器就是客户端,服务端是相应网站的服务器。

2、HTTP协议简单工作流程实例

我们通过浏览器输入http://www.example.com/index.html网址进行访问,一般简单的请求响应流程如下

1)客户端(浏览器)发送一个HTTP请求给服务器

请求的格式如下:

GET /index.html HTTP/1.1
Host: www.example.com

2)服务器接收到请求后,解析请求行和请求头,并根据请求的路径和参数执行相应的处理逻辑

3)服务器生成一个HTTP响应,响应的格式如下:

HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 1234

<html>
<body>
  <h1>Hello, World!</h1>
</body>
</html>

4)服务器将生成的响应发送回客户端。

5)客户端接收到响应后,解析响应头和响应体,并根据需要进行相应的处理,如渲染页面或执行其他操作。

二、HTTP 协议格式

HTTP协议的格式可以分为请求格式和响应格式两部分。HTTP 协议并没有规定报头部分的键值对有多少个,HTTP 报文中空行当于是报文的结束标记或报文和正文之间的分隔符。HTTP 在传输层依赖 TCP 协议,TCP 是面向字节流的。如果没有这个空行,就会出现”粘包问题“。

1、请求格式(Request Format)

请求格式由请求行(Request Line)、请求头(Headers)和请求体(Body)组成。

请求行的格式为:

METHOD PATH/URL HTTP/版本号

1)方法(method):

方法

说明

适用版本号

GET

获取资源,GET 是最常用的 HTTP 方法,

常用于获取服务器上的某个资源。

GET 请求的特点:

首行里面的第一个部分就是 GET

URL 里面的 query string 可以为空,

也可以不为空 %E8%9B%8B%E7%B3%95

GET 请求的 header 有若干个键值对结构

GET 请求的 body 一般是空的

HTTP 1.0、HTTP 1.1

POST

传输实体主体,POST 方法也是一种常见的方法,

多用于提交用户输入的数据给服务器(如登录页面)。

POST 请求的特点:

首行第一个部分就是 POST

URL 里面的 query string 一般是空的

POST 请求的 header 里面有若干个键值对

POST 请求的 body 一般不为空(body 的具体数据格式,

由 header 中的 Content-Type 来描述; body 的具体数据长度,

由 header 中的 Content-Length 来描述

HTTP 1.0、HTTP 1.1

PUT

传输文件

HTTP 1.0、HTTP 1.1

HEAD

获得报文首部

HTTP 1.0、HTTP 1.1

DELETE

删除文件

HTTP 1.0、HTTP 1.1

OPTIONS

访问支持的方法

HTTP 1.1

TRACE

追踪路径

HTTP 1.1

CONNECT

要求用隧道协议连接代理

HTTP 1.1

LINK

建立和资源之间的联系

HTTP 1.1

UNLINE

断开连接关系

HTTP 1.1

请求头包含了关于请求的附加信息,每个请求头由键值对组成,格式为:

Header-Name: Header-Value

请求体是可选的,用于传输请求的数据。

2)GET 和 POST 请求的区别

GET请求参数通过URL的查询字符串(Query String)进行传递,参数会附加在URL的末尾,以?开头,多个参数之间使用&符号连接。例如:http://www.example.com/path?param1=value1&m2=value2,POST请求参数通过请求体(Request Body)进行传递,参数会包含在请求体中,并且不会显示在URL中。GET请求由于参数附加在URL中,受到URL长度的限制,浏览器和服务器对URL长度都有一定的限制,一般在2048个字符左右。POST请求请求体中的数据没有固定长度限制,可以传输较大的数据。

GET请求用于获取资源,请求的副作用较小,多用于查询数据。GET请求是幂等的,多次发送相同的GET请求会得到相同的结果,不会对服务器产生影响。POST请求用于提交数据,请求的副作用较大,多用于新增、修改或删除数据等操作。POST请求不是幂等的,多次发送相同的POST请求会产生不同的结果,可能对服务器产生影响。GET请求参数暴露在URL中,相对不太安全,不适合传递敏感信息。POST请求参数在请求体中,相对安全,适合传递敏感信息。

3)HTTP协议头(Header)

HTTP协议头(Header)是在HTTP请求和响应中用于传递附加信息的部分。它由一个或多个字段构成,每个字段由字段名和字段值组成,中间用冒号(:)分隔。HTTP协议头包含在请求和响应的起始行和实体主体之间。

字段名称

描述

Accept

指定客户端能够处理的媒体类型,

用于告知服务器可以接受哪些内容类型。

Content-Type

指定请求或响应的媒体类型,

用于告知对方发送的实体主体的类型。

Content-Length

指定请求或响应的实体主体的长度,以字节为单位。

User-Agent

指定发送请求的用户代理(浏览器、应用程序等)的标识信息。

Host

指定请求的目标服务器的主机名和端口号。

Cookie

用于在客户端和服务器之间传递会话状态信息的数据。

Authorization

指定请求的身份验证凭证,用于在请求中包含用户凭据。

Cache-Control

指定请求或响应的缓存机制和行为。

Location

指定重定向的目标URL,

用于在响应中告知客户端进行重定向。

Referer

指定当前请求的来源URL,

用于告知服务器当前请求的上一个页面的URL

Content-Type 有以下三种请求中的数据格式:

  • application/x-www-form-urlencoded是 form 表单提交的数据格式,此时 body 的格式就类似于 query string(是键值对的结构,键值对之间使用 & 分割,键与值之间使用 = 分割
  • multipart/form-data是 form 表单提交的数据格式(需要在 from 标签上加上 enctyped="multipart/form-data"),通常用于 HTML 提交图片或者文件
  • application/json表示 body 数据为 json 格式,json 格式就是源自 js 的对象的格式。用一个 { } 括住,里面有多个键值对,键值对之间使用逗号分割,键和值之间使用冒号分割

2、响应格式(Response Format)

响应格式由响应行(Response Line)、响应头(Headers)和响应体(Body)组成。

响应行的格式为:

HTTP/版本号 状态码 状态消息

响应头与请求头类似,包含了关于响应的附加信息。

响应体是可选的,用于传输响应的数据。

1)状态码

常见状态码

说明

200 OK

这是一个最常见的状态码, 表示访问成功。抓包抓到的大部分结果都是 200

404 Not Found

没有找到资源。URL 标识的资源不存在, 那么就会出现 404

403 Forbidden

表示访问被拒绝。有的页面通常需要用户具有一定的权限才能访问(登陆后才能访问).。如果用户没有登陆直接访问, 就容易见到 403

405 Method Not Allowed

我们学习了 HTTP 中所支持的方法, 有 GET, POST, PUT, DELETE 等。但是对方的服务器不一定都支持所有的方法(或者不允许用户使用一些其他的方法).

500 Internal Server Error

服务器出现内部错误. 一般是服务器的代码执行过程中遇到了一些特殊情况(服务器异常崩溃)会产生这个状态码,一般很少见

504 Gateway Timeout

当服务器负载比较大的时候, 服务器处理单条请求的时候消耗的时间就会很长, 就可能会导致出现超时的情况

302 Move temporarily

临时重定向。在登陆页面中经常会见到 302. 用于实现登陆成功后自动跳转到主页

301 Moved Permanently

永久重定向。当浏览器收到这种响应时, 后续的请求都会被自动改成新的地址。301 也是通过 Location 字段来表示要重定向到的新地址

2)协议头(header)

响应报头的基本格式和请求报头的格式基本相同,响应报头的 Content-Type 参数:

  • text/html表示数据格式是 HTML
  • text/css表示数据格式是 CSS
  • application/javascript表示数据各式是 JavaScript
  • application/json表示数据格式是 JSON

3、HTTP请求和响应的示例

1)请求示例

GET /index.html HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.82 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8

2)响应示例

HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 1234
Server: Apache/2.4.29 (Unix)
Date: Tue, 20 Apr 2021 12:00:00 GMT
<!DOCTYPE html>
<html>
<head>
  <title>Welcome to Example.com</title>
</head>
<body>
  <h1>Hello, World!</h1>
</body>
</html>

三、编程实现HTTP请求响应

通过Socket在C#中实现HTTP请求和响应,需要手动构建HTTP报文并发送到服务器,然后接收服务器的响应并解析。

1)HTTP客户端

using System;
using System.Net.Sockets;
using System.Text;

public class Program
{
    public static void Main()
    {
        // 设置请求信息
        string host = "localhost";
        int port = 80;
        string path = "/";
        
        // 构建HTTP请求报文
        string request = $"GET {path} HTTP/1.1\r\nHost: {host}\r\nConnection: close\r\n\r\n";
        
        // 创建Socket连接
        using (Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
        {
            // 连接服务器
            socket.Connect(host, port);
            
            // 发送请求
            byte[] requestData = Encoding.ASCII.GetBytes(request);
            socket.Send(requestData);
            
            // 接收响应
            byte[] buffer = new byte[1024];
            int bytesRead = socket.Receive(buffer);
            
            // 解析响应并输出
            string response = Encoding.ASCII.GetString(buffer, 0, bytesRead);
            Console.WriteLine(response);
        }
    }
}

2)HTTP服务端

using System;
using System.Net;
using System.Net.Sockets;
using System.Text;

public class Program
{
    public static void Main()
    {
        // 设置监听地址和端口
        string ipAddress = "127.0.0.1";
        int port = 8080;
        
        // 创建监听器
        TcpListener listener = new TcpListener(IPAddress.Parse(ipAddress), port);
        listener.Start();
        Console.WriteLine($"HTTP server started on {ipAddress}:{port}");
        
        while (true)
        {
            // 接受客户端连接
            TcpClient client = listener.AcceptTcpClient();
            
            // 处理客户端请求
            HandleClientRequest(client);
            
            // 关闭客户端连接
            client.Close();
        }
    }
    
    private static void HandleClientRequest(TcpClient client)
    {
        // 读取客户端请求
        byte[] buffer = new byte[1024];
        int bytesRead = client.GetStream().Read(buffer, 0, buffer.Length);
        
        // 解析HTTP请求报文
        string request = Encoding.ASCII.GetString(buffer, 0, bytesRead);
        Console.WriteLine($"Received request:\n{request}");
        
        // 构建HTTP响应报文
        string response = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\nHello, World!";
        byte[] responseData = Encoding.ASCII.GetBytes(response);
        
        // 发送HTTP响应
        client.GetStream().Write(responseData, 0, responseData.Length);
        Console.WriteLine($"Sent response:\n{response}");
    }
}