【计算机网络】应用层HTTP协议

简介: 【计算机网络】应用层HTTP协议

一、HTTP协议简介

HTTP协议全称为超文本传输协议(英语:HyperText Transfer Protocol,缩写:HTTP)是一种用于分布式、协作式和超媒体信息系统的应用层协议,是因特网上应用最为广泛的一种网络传输协议,所有的 WWW 文件都必须遵守这个标准。HTTP 是为 Web 浏览器与 Web 服务器之间的通信而设计的。


HTTP 是一个基于 TCP/IP 通信协议来传递数据的(HTML 文件、图片文件、查询结果等),是一种应用层协议。其中HTTP1.0、HTTP1.1、HTTP2.0均为TCP协议实现,HTTP3.0是基于UDP实现的,现在主要使用的是HTTP1.0和HTTP3.0 。


另外,现在我们访问一些网站时我们可以发现,很多都是使用的HTTPS协议进行通信的,其实 HTTPS 是在 HTTP 的基础之上,利用SSL/TLS来加密数据包,一定程度上保证了数据传输的安全性。HTTPS 开发的主要目的,是提供对网站服务器的身份认证,保护交换资料的隐私与完整性。关于HTTPS的详细内容可见博主的后续文章。

二、HTTP协议的工作原理

HTTP 协议工作与客户端—服务端架构上。浏览器作为 HTTP 客户端通过 URL 向 HTTP 服务端即web服务器发送请求,web服务器根据接收到的请求向客户端发送回响信息,从而实现客户端与服务端之间的通信。

HTTP的注意事项:

  • HTTP 的默认端口号是80,但也可以根据自己的意愿改为8080或者其他端口号。
  • HTTP 是无连接的:无连接的含义是限制每一次连接只处理一个请求,服务器处理完客户端的请求并接受客户端的应答后,便断开连接,采用这种连接方式可以节省传输的时间,从而提供传输效率。
  • HTTP 是媒体独立的:即只要客户端和服务器知道如何处理数据的内容,任何类型的数据都可以提供HTTP协议进行发送,客户端和服务器只需要指定适合的 MIME-type 内容类型。
  • HTTP 是无状态的:即HTTP协议对于事务的处理没有记忆能力,那么就意味着如果后续处理需要前面的信息,就必须进行重传,这样可能导致每次连接传输的数据量增大。而另一方面,在服务器不需要先前信息时的应答速度就较快。

下图展示了HTTP协议通信的基本流程:

二、HTTP协议格式

1. 请求格式

客户端发送一个HTTP请求到服务器的请求消息包括以下格式:请求行(request line)、请求头部(header)、空行和请求数据四个部分组成,下图给出了请求报文的一般格式。

请求实例:

2. 响应格式

HTTP响应也由四个部分组成,分别是:状态行、响应头部、空行和响应正文。

响应实例:


三、HTTP请求

1. URL

URL 的定义:

URL 其实就是我们所说的”网址“,URL(Uniform Resource Locator),翻译为统一资源定位符。互连网上的每个文件都有一个唯一的 URL,它包含的信息指出文件的位置以及浏览器应该怎么处理它。

URL 基本格式:

  • URL 的标准格式如下:

协议类型:[服务器地址[:端口号]][资源层级 UNIX 文件路径]文件名[?查询字符串][#片段标识符]


  • URL 的完整格式如下:

协议类型:[[访问资源需要的凭证信息@]服务器地址[:端口号]][资源层级 UNIX 文件路径]文件名[?查询字符串][#片段标识符]


URL encode 和 URL decode:

像 /、?:等这样的字符,已经被 URL 当做特殊意义理解了,因此这些字符不能随意出现。如果某个参数中需要带有这些特殊字符,就必须先对特殊字符进行转义,即 URL encode。

一个中文字符由 UTF-8 或者 GBK 这样的编码方式构成,虽然在 URL 中没有特殊含义,但是仍然需要进行转义,否则浏览器可能把 UTF-8/GBK 编码中的某个字节当做 URL 中的特殊符号。


转义的规则如下:

将需要转码的字符转为16进制,然后从右到左,取4位(不足4位直接处理),每2位做一位,前面加上%,编码成%XY格式。

例如:

我们使用百度搜索C++,会得到以下的URL:

https://www.baidu.com/s?ie=utf-8&f=8&rsv_bp=1&rsv_idx=1&tn=baidu&wd=C%2B%2B&fenlei=256&oq=

可以发现 query string 的值是 C%2B%2B、,通过使用 URL encode工具 对其进行解码,就知道 C%2B%2B 就是表示 C++。

2. HTTP请求方法

根据 HTTP 标准,HTTP 请求可以使用多种请求方法。其中HTTP1.0 定义了三种请求方法: GET, POST 和 HEAD 方法。HTTP1.1 新增了六种请求方法:OPTIONS、PUT、PATCH、DELETE、TRACE 和 CONNECT 方法。

1700834144692.png

其中最常用的就是GET方法和POST方法。

3. HTTP请求报头

HTTP请求头提供了关于请求,响应或者其他的发送实体的信息。

1700834162169.png


四、HTTP的状态码

1. HTTP 状态码的介绍

当浏览者访问一个网页时,浏览者的浏览器会向网页所在服务器发出请求。当浏览器接收并显示网页前,此网页所在的服务器会返回一个包含 HTTP 状态码的信息头(server header)用以响应浏览器的请求。HTTP 状态码的英文为 HTTP Status Code。

常见的 HTTP 状态码:

  • 200 - 请求成功
  • 301 - 资源(网页等)被永久转移到其它URL
  • 302 - 资源(网页等)被暂时转移到其它URL
  • 404 - 请求的资源(网页等)不存在
  • 500 - 内部服务器错误

2. HTTP 状态码的分类

HTTP 状态码由三个十进制数字组成,第一个十进制数字定义了状态码的类型。响应分为五类:信息响应(100 ~ 199) ,成功响应(200 ~ 299) ,重定向(300 ~ 399) ,客户端错误(400~499) 和服务器错误 (500 ~ 599) :

分类 描述
1** 信息,服务器收到请求,需要请求者继续执行操作
2** 成功,操作被成功接收并处理
3** 重定向,需要进一步的操作以完成请求
4** 客户端错误,请求包含语法错误或无法完成请求
5** 服务器错误,服务器在处理请求的过程中发生了错误

五、简单的HTTP服务器

利用TCP传输协议实现一个只在网页上显示“This is my httpServer!”,的简单HTTP服务器。

封装Server.hpp

#pragma once
#include <iostream>
#include <fstream>
#include <string>
#include <cassert>
#include <cstring>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <unistd.h>
#define CRLF "\r\n"
#define SPACE " "
#define SPACE_LEN strlen(SPACE)
#define HOME_PAGE "index.html"
#define ROOT_PATH "wwwroot"
std::string ReadFile(const std::string &path)
{
    std::ifstream in(path, std::ifstream::binary);
    if (!in.is_open())
    {
        return "404 NOT FOUND";
    }
    std::string content;
    std::string line;
    while (std::getline(in, line))
    {
        content += line;
    }
    in.close();
    return content;
}
std::string GetPath(std::string request)
{
    std::size_t pos = request.find(CRLF);
    if(pos == std::string::npos)
    {
        return nullptr;
    }
    // 获取第一行:GET PATH HTTP/1.1 
    std::string line = request.substr(0, pos);
    std::size_t first_space = line.find(SPACE);
    if(first_space == std::string::npos)
    {
        return nullptr;
    }
    std::size_t second_space = line.rfind(SPACE);
    if(second_space == std::string::npos)
    {
        return nullptr;
    }
    std::string path = line.substr(first_space + SPACE_LEN, second_space - (SPACE_LEN + first_space));
    if(path[0] == '/' && path.size() == 1)
        path += HOME_PAGE;
    return path;
}
void _handlerHttpRequest_(int sock)
{
    char buf[1024 * 10] = {0};
    ssize_t read_size = read(sock, buf, sizeof buf);
    if (read_size > 0)
    {
        std::cout << buf;
    }
    // std::string response = "HTTP/1.1 302 Temporarily Moved\r\n"; // 临时重定向
    std::string response = "HTTP/1.1 301 Permanently Moved\r\n"; // 永久重定向
    response += "Location: https://www.baidu.com/\r\n";
    response += "\r\n";
    // write(sock, response.c_str(), response.size());
    send(sock, response.c_str(), response.size(), 0);
}
void handlerHttpRequest(int sock)
{
    char buf[1024 * 10] = {0};
    ssize_t read_size = read(sock, buf, sizeof buf);
    if (read_size > 0)
    {
        std::cout << buf;
    }
    // 获取请求中的路径
    std::string path = GetPath(buf);
    // 请求的文件路径在请求行中,第二个字段就是要访问的文件
    //  GET PATH HTTP/1.1
    std::string resource = ROOT_PATH;
    resource += path;
    //debug
    std::cout << resource << std::endl;
    // 获取文件后缀
    std::size_t pos = resource.find(".");
    std::string suffix = resource.substr(pos);
    // 读取文件内容
    std::string html = ReadFile(resource);
    // 回响
    std::string response;
    response = "HTTP/1.0 200 OK\r\n";
    if (suffix == ".jpg")
        response += "Content-Type: image/jpeg\r\n";
    else
        response += "Content-Type: text/html\r\n";
    response += ("Content-Length: " + std::to_string(html.size()) + "\r\n");
    // 添加cookie
    response += "Set-Cookie: this is my cookie content;\r\n";
    response += "\r\n";
    response += html;
    send(sock, response.c_str(), response.size(), 0);
}
class Server
{
public:
    Server(uint16_t port, const std::string &ip = "")
        : _listenSock(-1), _port(port), _ip(ip), _quit(false)
    {
        signal(SIGCHLD, SIG_IGN);
    }
    ~Server()
    {
        if (_listenSock >= 0)
        {
            close(_listenSock);
        }
    }
public:
    void init()
    {
        // 创建socket
        _listenSock = socket(AF_INET, SOCK_STREAM, 0);
        if (_listenSock < 0)
        {
            std::cerr << "socket error" << std::endl;
            exit(1);
        }
        // 填充信息
        sockaddr_in addr;
        memset(&addr, 0, sizeof(addr));
        addr.sin_family = PF_INET;
        addr.sin_port = htons(_port);
        addr.sin_addr.s_addr = _ip.empty() ? htonl(INADDR_ANY) : inet_addr(_ip.c_str());
        // bind
        if (bind(_listenSock, (const sockaddr *)&addr, sizeof(addr)) < 0)
        {
            std::cerr << "bind error" << std::endl;
            exit(2);
        }
        // 监听
        if (listen(_listenSock, 5) < 0)
        {
            std::cerr << "listen error" << std::endl;
            exit(3);
        }
    }
    void loop()
    {
        while (!_quit)
        {
            sockaddr_in peer;
            socklen_t len = sizeof(peer);
            int peer_sock = accept(_listenSock, (sockaddr *)&peer, &len);
            if (peer_sock < 0)
            {
                std::cerr << "accept error" << std::endl;
                exit(4);
            }
            // 多进程版
            pid_t id = fork();
            if (id > 0)
            {
                // 父进程
                close(peer_sock);
            }
            else if (id == 0)
            {
                // 子进程
                close(_listenSock);
                handlerHttpRequest(peer_sock);
                close(peer_sock);
                exit(0);
            }
            else
            {
                std::cerr << "fork error" << std::endl;
                exit(5);
            }
        }
    }
    bool quitServer()
    {
        _quit = true;
        return true;
    }
private:
    int _listenSock; // sock
    uint16_t _port;  // 服务器端口
    std::string _ip; // IP地址
    bool _quit;      // 安全退出的标志
};

实现httpServer

#include <iostream>
#include "Server.hpp"
static void Usage(std::string proc)
{
    std::cerr << "Usage:\n\t" << proc << " port" << std::endl;
    std::cerr << "example:\n\t" << proc << " 8080\n"
              << std::endl;
}
int main(int argc, char* argv[])
{
    if(argc != 2)
    {
        Usage(argv[0]);
        exit(-1);
    }
    uint16_t port = atoi(argv[1]);
    Server server(port);
    server.init();
    server.loop();
    return 0;
}

编写 index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>我的测试</title>
</head>
<body>
    <p> This is my httpServer!</p>
</body>
</html>


运行结果:

目录
相关文章
|
3天前
|
网络协议 安全 Go
Go语言进行网络编程可以通过**使用TCP/IP协议栈、并发模型、HTTP协议等**方式
【10月更文挑战第28天】Go语言进行网络编程可以通过**使用TCP/IP协议栈、并发模型、HTTP协议等**方式
23 13
|
2天前
|
存储 缓存 网络协议
计算机网络常见面试题(二):浏览器中输入URL返回页面过程、HTTP协议特点,GET、POST的区别,Cookie与Session
计算机网络常见面试题(二):浏览器中输入URL返回页面过程、HTTP协议特点、状态码、报文格式,GET、POST的区别,DNS的解析过程、数字证书、Cookie与Session,对称加密和非对称加密
|
3天前
|
网络协议 算法 网络性能优化
计算机网络常见面试题(一):TCP/IP五层模型、TCP三次握手、四次挥手,TCP传输可靠性保障、ARQ协议
计算机网络常见面试题(一):TCP/IP五层模型、应用层常见的协议、TCP与UDP的区别,TCP三次握手、四次挥手,TCP传输可靠性保障、ARQ协议、ARP协议
|
4天前
|
网络协议 前端开发 API
HTTP 和 TCP 协议的应用场景有哪些不同
【10月更文挑战第25天】HTTP(超文本传输协议)和 TCP(传输控制协议)处于网络协议栈的不同层次,各自具有独特的功能和特点,因此它们的应用场景也存在明显的差异。
|
4天前
|
安全 前端开发 JavaScript
利用HTTP协议进行文件上传和下载的常见方法
【10月更文挑战第25天】可以利用HTTP协议方便地实现文件的上传和下载功能,满足不同应用场景下的需求。在实际应用中,还可以根据具体的业务需求和安全要求,对文件上传和下载的过程进行进一步的优化和安全处理。
|
4天前
|
网络协议 API 数据格式
HTTP 和 TCP 协议的主要区别
【10月更文挑战第25天】HTTP 和 TCP 在网络通信中扮演着不同的角色,各自具有独特的功能和特点,它们相互配合,共同为实现网络应用的各种需求提供了基础支持。
|
5天前
|
网络协议 安全 NoSQL
网络空间安全之一个WH的超前沿全栈技术深入学习之路(8-2):scapy 定制 ARP 协议 、使用 nmap 进行僵尸扫描-实战演练、就怕你学成黑客啦!
scapy 定制 ARP 协议 、使用 nmap 进行僵尸扫描-实战演练等具体操作详解步骤;精典图示举例说明、注意点及常见报错问题所对应的解决方法IKUN和I原们你这要是学不会我直接退出江湖;好吧!!!
网络空间安全之一个WH的超前沿全栈技术深入学习之路(8-2):scapy 定制 ARP 协议 、使用 nmap 进行僵尸扫描-实战演练、就怕你学成黑客啦!
|
存储 Web App开发 监控
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html><head><meta http-equiv="Cont
我们以前使用过的对hbase和hdfs进行健康检查,及剩余hdfs容量告警,简单易用 1.针对hadoop2的脚本: #/bin/bashbin=`dirname $0`bin=`cd $bin;pwd`STATE_OK=...
1050 0
|
Web App开发 前端开发 Java
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html><head><meta http-equiv="Cont
线程的状态有:new、runnable、running、waiting、timed_waiting、blocked、dead 当执行new Thread(Runnabler)后,新创建出来的线程处于new状态,这种线程不可能执行 当执行thread.start()后,线程处于runnable状态,这种情况下只要得到CPU,就可以开始执行了。
728 0
|
Java Apache
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html><head><meta http-equiv="Cont
hbase从集群中有8台regionserver服务器,已稳定运行了5个多月,8月15号,发现集群中4个datanode进程死了,经查原因是内存 outofMemory了(因为这几台机器上部署了spark,给spark开的...
810 0