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

文章目录

  • 1. http协议
    • 1.1 http协议简介
    • 1.2 url组成
    • 1.3 urlencode与urldecode
  • 2. http协议的格式
    • 2.1 http协议的格式
    • 2.2 一些细节问题
  • 3. http的方法、状态码和常见响应报头
    • 3.1 http请求方法
    • 3.2 http状态码
    • 3.3 http常见的响应报头属性
  • 4. 一个非常简单的http协议服务端
  • 5. http长链接
  • 6. http会话保持

1. http协议

1.1 http协议简介

在上一篇文章中我们了解到应用层协议是可以由程序员自己定制的

计算机领域经过了这么长时间的发展,肯定会出现很多已经写好的协议,我们直接拿来用就可以了的。事实也确实如此,http协议(超文本传输协议)就是其中之一。

这个协议是用于客户端向服务端请求“资源”,包括文本、图片、音频、视频等资源的协议。因为它不只能拿文本资源,所以叫超文本传输协议。

1.2 url组成

我们平常说的网址,其实就是URL,这个URL有很多个部分组成的

image-20240229152800115

在客户端向服务端发起通信的时候,通过DNS将这个服务器地址转换成IP地址,在其后面应该有端口号的,但是http协议的端口号固定就是80,https端口号固定是443,就能通过这个I P地址+端口号找到指定服务器的指定进程,然后通过对应的资源地址在web根目录下找到对应的资源

1.3 urlencode与urldecode

对于像 / + : ?等字符, 已经被url特殊处理了。比如, 某个参数中需要带有这些特殊字符, 就必须先对特殊字符进行转义.

转义的规则如下:取出字符的ASCII码,转成16进制,然后前面加上百分号即可。编码成%XY格式。服务器收到url请求,将会对%XY进行解码,该过程称为decode

2. http协议的格式

2.1 http协议的格式

http协议的请求和响应都分为四个部分。对于请求,分为1. 请求行; 2. 请求报头; 3. 一个空行; 4.请求正文;对于响应,分为1. 状态行; 2.响应报头; 3. 一个空行; 4. 响应正文

image-20240229155740289

其中在请求行,有三个部分内容,通过空格来区分,这三个部分分别是1. 请求方法; 2. url 3. http版本,这个版本现在有1.0;1.1;2.0

格式是http/版本号,例如http/1.1

2.2 一些细节问题

1. 请求和响应怎么保证读取完了?

每次可以读取完整的一行 ==> 循环读取每一行,直到遇到空行 ==> 此时就读取了所有的请求报头和请求行 ==> 在请求报头里面有一个属性Content-Length表示正文长度,解析这个长度,然后按照指定长度读取正文即可

2. 请求和响应是怎么做到序列化和反序列化的?

http不用关注json等序列化和反序列化工具,直接发送即可。服务器解析客户端的请求,获取其中的信息填充至响应缓冲区。服务器通过响应报头的方式返回请求的参数,在响应正文中返回请求的资源。

3. http的方法、状态码和常见响应报头

3.1 http请求方法

请求方法说明支持的http协议版本
GET获取资源(表单在url中携带)1.0/1.1
POST传输实体主体(表单在请求正文中携带)1.0/1.1

其他方法不常用,这里就不列出来了

我们经常会在网页填写一些内容提交,如果使用GET方法的话,这些内容会被浏览器拼接到url后面(使用?作为分隔符),如果使用PSOT方法的话,这些内容就会在请求正文中

1、GET方法通过URL传递参数。例如http://ip:port/XXX/YY?key1=value1&key2=value2。像百度的搜索就是用的GET方法。GET方法通过url传递参数,参数注定不能太大,例如上传视频等巨长的二进制文件就不适合用GET了。

2、POST提交参数通过http请求正文提交参数。请求正文可以很大,可以提交视频等巨长的文件。

3、POST方法提交参数,用户是看不到的,私密性更高,而GET方法不私密。私密性不等于安全性,POST方法和GET方法其实都不安全!(http请求都是可以被抓到的,想要安全必须加密,使用https协议)

3.2 http状态码

http协议在响应的时候就会在状态行给出本次请求的响应状态,可以理解成是这个请求的“退出码”。

一般来说,http的状态码分为5类

类别原因短语
1xxInformational(信息性状态码)接收的请求正在处理
2xxSuccess(成功状态码)接收的请求处理完毕
3xxRedirection(重定向状态码)需要进行附加操作以完成请求
4xxClinet Error(客户端错误状态码)服务器无法完成请求
5xxServer Error(服务器错误状态码)服务器完成请求出错

几个比较常见的状态码, 比如 200(OK), 404(Not Found), 403(Forbidden), 302(Redirect, 重定向), 504(Bad Gateway)

3.3 http常见的响应报头属性

  • Content-Type: 响应正文的数据类型(text/html等)
  • Content-Length: 响应正文的长度
  • Host: 客户端告知服务器, 所请求的资源是在哪个主机的哪个端口上;
  • User-Agent: 声明用户的操作系统和浏览器版本信息;
  • referer: 当前页面是从哪个页面跳转过来的;
  • location: 搭配3xx状态码使用, 告诉客户端接下来要去哪里访问;
  • Cookie: 用于在客户端存储少量信息. 通常用于实现会话(session)的功能;

4. 一个非常简单的http协议服务端

设计思路:我们日常使用的浏览器就是http协议的客户端,我们现在只需要实现服务端即可。既然要实现支持http协议的服务端,那么只需要按照tcp协议的方式构建传输层,然后按照http协议的约定来解析客户端发过来的消息,然后按照约定的响应格式发送数据给客户端

那么其实我们之前实现的socket编程的代码是可以用上的

enum
{USAGE_ERR = 1,SOCKET_ERR,BIND_ERR,LISTEN_ERR
};static const uint16_t gport = 8080;
static const int gbacklog = 5;typedef std::function<bool(const HttpRequest &req, HttpResponse &resp)> func_t;class HttpServer
{public:HttpServer(func_t func, const uint16_t &port = gport) : _port(port), _func(func){}void initServer(){// 1. 创建socket文件套接字对象_listensock = socket(AF_INET, SOCK_STREAM, 0);if (_listensock == -1){exit(SOCKET_ERR);}// 2.bind自己的网络信息sockaddr_in local;local.sin_family = AF_INET;local.sin_port = htons(_port);local.sin_addr.s_addr = INADDR_ANY;int n = bind(_listensock, (struct sockaddr *)&local, sizeof local);if (n == -1){exit(BIND_ERR);}// 3. 设置socket为监听状态if (listen(_listensock, gbacklog) != 0) // listen 函数{exit(LISTEN_ERR);}}void start(){while (true){struct sockaddr_in peer;socklen_t len = sizeof peer;int sock = accept(_listensock, (struct sockaddr *)&peer, &len);if (sock < 0){continue;}pid_t id = fork();if (id == 0){close(_listensock);if (fork() > 0)exit(0);handleHttp(sock); // 这里就是需要服务端执行的内容了(传输层上层的内容)close(sock);exit(0);}waitpid(id, nullptr, 0);close(sock);}}void handleHttp(int sock) // 服务端调用{// 1. 读到完整的http请求// 2. 反序列化// 3. 调用回调函数// 4. 将resp序列化// 5. sendchar buffer[4096];HttpRequest req;HttpResponse resp;ssize_t n = recv(sock, buffer, sizeof(buffer) - 1, 0);if(n > 0){buffer[n] = 0; // 添加一个字符串的结尾req.inbuffer = buffer;req.parse(); // 解析调用的内容_func(req, resp); // req -> respsend(sock, resp.outbuffer.c_str(), resp.outbuffer.size(), 0);}}~HttpServer() {}private:uint16_t _port;int _listensock;func_t _func;
};

在应用层我们就要设计我们服务端的”http协议了“

#pragma once#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>#include <string>
#include <sstream>
#include <iostream>#include "Util.hpp" // 这是工具类,提供了一些工具函数// 一些配置文件,这里写死(可以集成为一个配置文件,在服务器启动的时候加载)
const std::string sep = "\r\n"; // 分隔符
const std::string default_root = "./webroot"; // web根目录
const std::string home_page = "index.html"; // 首页
const std::string html_404 = "404.html"; // 找不到页面显示的页面class HttpRequest // http请求类
{
public:HttpRequest(){}~HttpRequest(){}bool parse() // 解析{// 1. 提取inbuffer中的第一行内容std::string line = Util::getOneline(inbuffer, sep);if (line.empty())return false;// 2. 解析内容 method url httpversionstd::stringstream ss(line);ss >> method >> url >> httpversion;// 3. 添加默认路径path += default_root;path += url;if(path[path.size() - 1] == '/') // 访问不合法资源path += home_page;// 4. 获取path对应的资源后缀(资源类型)auto pos = path.rfind(".");if(pos == std::string::npos)suffix = ".html";elsesuffix = path.substr(pos);// 5. 获取的资源大小struct stat st;int n = stat(path.c_str(), &st);if(n != 0) stat((default_root + html_404).c_str(), &st);size = st.st_size;return true;}public:std::string inbuffer; // 缓冲区,保存接收到的所有内容std::string method;      // 浏览器请求方法std::string url;         // 相对于default_root的资源路径std::string httpversion; // http协议版本std::string path;        // 要访问的资源路径std::string suffix;      // 资源后缀int size;                // 资源大小
};class HttpResponse // http响应类
{
public:std::string outbuffer; // 这里保存所有序列化之后的结果,最终发送这个outbuffer中的数据即可
};

同时我们需要设计一下服务端的回调函数

/*httpServer.cc*/
#include <memory>
#include <iostream>#include "httpServer.hpp"using namespace Server;
using namespace std;static void Usage(std::string proc)
{std::cout << "\n\tUsage: " << proc << " port\n";
}
static std::string suffixToDesc(const std::string &suffix)
{std::string ct = "Content-Type: ";if (suffix == ".html")ct += "text/html";else if (suffix == "jpg")ct += "application/x-jpg";elsect += "text/html";ct += "\r\n";return ct;
}
bool Get(const HttpRequest &req, HttpResponse &resp)
{cout << "-------------------http start-----------------------" << endl;cout << req.inbuffer << endl;cout << "method: " << req.method << endl;cout << "url: " << req.url << endl;cout << "httpversion: " << req.httpversion << endl;cout << "path: " << req.path << endl;cout << "suffix: " << req.suffix << endl;cout << "size: " << req.size << "字节" << endl;cout << "-------------------http end-----------------------" << endl;std::string respline = "HTTP/1.1 200 OK\r\n";      // 返回的第一行std::string respheader = suffixToDesc(req.suffix); // 协议报头std::string respblank = "\r\n";std::string body;body.resize(req.size + 1);if (Util::readFile(req.path, const_cast<char *>(body.c_str()), req.size)){// 没有指定资源Util::readFile(html_404, const_cast<char *>(body.c_str()), req.size); // 这个页面一定存在}respheader += "Content-Length: ";respheader += std::to_string(body.size());respheader += "\r\n";resp.outbuffer += respline;resp.outbuffer += respheader;resp.outbuffer += respblank;cout << "-------------------http response start-----------------------" << endl;cout << resp.outbuffer << endl;cout << "-------------------http response end-----------------------" << endl;resp.outbuffer += body;return true;
}int main(int argc, char *argv[])
{if (argc != 2){Usage(argv[0]);exit(USAGE_ERR);}uint16_t port = atoi(argv[1]);std::unique_ptr<HttpServer> hsvr(new HttpServer(Get, port));hsvr->initServer();hsvr->start();return 0;
}

同时,这里附上工具类的函数

#pragma once#include <string>
#include <iostream>
#include <fstream>class Util
{
public:static std::string getOneline(std::string &buffer, const std::string &sep) // 获取一行内容{auto pos = buffer.find(sep);if(pos == std::string::npos) return "";std::string sub = buffer.substr(0, pos);buffer.erase(0, pos + sep.size());return sub;}static bool readFile(const std::string &resource, char* buffer, int size) // 二进制方式读取文件{std::ifstream in(resource, std::ios::binary);if(!in.is_open()) return false; // open file failin.read(buffer, size); // 从in中使用二进制读取的方式读取size个字节到buffer中in.close();return true;}
};

运行结果:

image-20240229174354837

image-20240229174430018

我们在服务端看到了响应结果,会发现客户端的一次点击在服务端会接收到多次请求,这是因为我们看到的网页是由多个资源组合而成的,所以要获取一个完整的网页效果浏览器就需要发起多次http请求,包括我们要请求的index.html网页和相关图标等

一些小细节

  1. http协议之所以在首行存在httpversion是因为http请求会交换通信双方B/S的协议版本,以明确能够接收/传输的资源类型和支持的协议内容
  2. 如果没有找到指定访问的资源,webServer会有默认的首页

5. http长链接

我们知道http请求是基于tcp协议的,tcp在通信的过程中需要发起并建立连接。一个网页中可能存在很多个元素,也就是说浏览器在将一个网页显示给用户的时候会经过多次http请求,所以就会面临着tcp频繁创建连接的问题

所以为了减少连接次数,需要客户端和服务器均支持长链接,建立一条连接,传输一份大的资源通过一条连接完成。

在http的请求报头中,可能会看到这样一行内容

Connection: keep-alive

表示支持长链接

6. http会话保持

严格意义上来说,会话保持并不是http天然所具备的,而是在后面使用的时候发现需要的

我们知道,http协议是无状态的,但是用户需要。

首先,用户查看新的网页是常规操作,如果网页发生跳转,那么新的网页是不知道已经登录的用户的身份的,也就需要用户重新进行身份验证。然后每次切换网页都重新输入账号密码着也太扯了,因此人们使用了一个办法:将用户输入的账号和密码保存起来,往后只要访问同一个网站,浏览器就会自动推送保存的信息,这个保存起来的东西就叫做cookie。cookie有内存级和文件级的,这里不做区分和了解。

举个最简单的例子:我们在登录CSDN的时候,只需要一次登录,以后再访问CSDN相关的网页,就会发现我们会自动登录,这就是因为浏览器保存了我们的账号信息,也就是当前网页的cookie信息.

但是本地的Cookie如果被不法分子拿到,那就危险了,所以信息的保存是在服务器上完成的,服务器会对每个用户创建一份独有的sessionid,并将其返回给浏览器,浏览器存到Cookie的其实是session id。但这样只能保证原始的账号密码不会被泄漏,黑客盗取了用户的session id后仍可以非法登录,只能靠服务端的安全策略保障安全,例如账号被异地登录了,服务端察觉后只要让session id失效即可,这样异地登录将会使用户重新验证账号密码或手机或人脸信息(尽可能确保是本人),一定程度上保障了信息的安全。

服务端可以通过在报头加上Set-Cookie: 属性将对应的cookie返回给客户端。往后,每次http请求都会自动携带曾经设置的所有Cookie,帮助服务器的鉴权行为————http会话保持

respHeader += "Set-Cookie: name=12345abcde; Max-Age=120\r\n";//设置Cookie响应报头,有效期2分钟

实际上在浏览器也是能看到对应的cookie的


本节完…

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/266295.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

蓝桥杯练习系统(算法训练)ALGO-995 24点

资源限制 内存限制&#xff1a;256.0MB C/C时间限制&#xff1a;1.0s Java时间限制&#xff1a;3.0s Python时间限制&#xff1a;5.0s 问题描述 24点游戏是一个非常有意思的游戏&#xff0c;很流行&#xff0c;玩法很简单&#xff1a;给你4张牌&#xff0c;每张牌上有数…

LeetCode -- 79.单词搜索

1. 问题描述 给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 单词必须按照字母顺序&#xff0c;通过相邻的单元格内的字母构成&#xff0c;其中“相邻”单元格是那些水…

arm板运行程序时寻找动态库的路径设置

问题&#xff1a;error while loading shared libraries: libQt5Widgets.so.5: cannot open shared object file&#xff1f; 第一种方法---- 解决&#xff1a; ①复制需要用到的arm库到板子上。 ②pwd指令获取该库的绝对路径&#xff0c;把路径复制到/etc/ld.so.conf文件 ③输…

Python:练习:编写一个程序,写入一个美金数量,然后显示出如何用最少的20美元、10美元、5美元和1美元来付款

案例&#xff1a; python编写一个程序&#xff0c;写入一个美金数量&#xff0c;然后显示出如何用最少的20美元、10美元、5美元和1美元来付款&#xff1a; Enter a dollar amout:93 $20 bills: 4 $10 bills: 1 $5 bills:0 $1 bills:3 思考&#xff1a; 写入一个美金数量&…

Day06:基础入门-抓包技术HTTPS协议APP小程序PC应用WEB转发联动

目录 HTTP/HTTPS协议抓包工具 Web浏览器抓包 APP应用抓包 WX小程序&PC应用抓包 思维导图 章节知识点&#xff1a; 应用架构&#xff1a;Web/APP/云应用/三方服务/负载均衡等 安全产品&#xff1a;CDN/WAF/IDS/IPS/蜜罐/防火墙/杀毒等 渗透命令&#xff1a;文件上传下载…

lv20 QT对话框3

1 内置对话框 标准对话框样式 内置对话框基类 QColorDialog, QErrorMessage QFileDialog QFontDialog QInputDialog QMessageBox QProgressDialogQDialog Class帮助文档 示例&#xff1a;各按钮激发对话框实现基类提供的各效果 第一步&#xff1a;实现组件布局&…

小(2)型土石坝安全监测设施配置详解

小(2)型土石坝的安全监测是确保大坝稳定、安全运行的重要环节。为此&#xff0c;合理配置安全监测设施显得尤为重要。以下是对小(2)型土石坝安全监测设施配置的详细介绍。 一、渗流量监测 渗流量是反映大坝安全状况的关键指标之一。为准确监测渗流量&#xff0c;我们采用仪器量…

【Android12】Monkey压力测试源码执行流程分析

Monkey压力测试源码执行流程分析 Monkey是Android提供的用于应用程序自动化测试、压力测试的测试工具。 其源码路径(Android12)位于 /development/cmds/monkey/部署形式为Java Binary # development/cmds/monkey/Android.bp // Copyright 2008 The Android Open Source Proj…

【树莓派系统配置+python3.8+环境配置踩坑点汇总】raspberrypi

最近又开始搞树莓派的深度学习模型。很多windows端的环境需要在树莓派上重新部署&#xff0c;中间出现了非常多的问题。主要以各种库的下载安装为主要。 首先&#xff0c;第一个问题&#xff1a; 树莓派系统烧录之后&#xff0c;默认apt一般需要升级看&#xff0c;而默认下载…

Docker基础篇(六) dockerfile体系结构语法

FROM&#xff1a;基础镜像&#xff0c;当前新镜像是基于哪个镜像的 MAINTAINER &#xff1a;镜像维护者的姓名和邮箱地址 RUN&#xff1a;容器构建时需要运行的命令 EXPOSE &#xff1a;当前容器对外暴露出的端口号 WORKDIR&#xff1a;指定在创建容器后&#xff0c;终端默认登…

k8s(5)

目录 使用Kubeadm安装k8s集群&#xff1a; 初始化操作&#xff1a; 每台主从节点&#xff1a; 升级内核&#xff1a; 所有节点安装docker &#xff1a; 所有节点安装kubeadm&#xff0c;kubelet和kubectl&#xff1a; 修改了 kubeadm-config.yaml&#xff0c;将其传输给…

(正规api接口代发布权限)短视频账号矩阵系统实现开发--技术全自动化saas营销链路生态

短视频账号矩阵系统实现开发--技术全自动化saas营销链路生态源头开发&#xff08;本篇禁止抄袭复刻&#xff09; 一、短视频矩阵系统开发者架构 云罗短视频矩阵系统saas化系统&#xff0c;开发层将在CAP原则基础上使用分布式架构,对此网站的整体架构采用了基于B/S三层架构模式…

MySQL锁机制【重点】

参考链接 【1】https://xiaolincoding.com/mysql/lock/mysql_lock.html 【2】https://learnku.com/articles/39212?order_byvote_count& 重要的锁&#xff1a; 表级锁&#xff08;Table-level locks&#xff09;&#xff1a; 表级锁是对整个表进行加锁&#xff0c;当一个事…

【通信基础知识】完整通信系统的流程图及各模块功能详解

2024.2.29 抱歉最近在写毕设大论文&#xff0c;因此没有太多时间更新。然而&#xff0c;在写论文的过程中&#xff0c;发现自己对通信系统的了解还不够全明白&#xff0c;因此差了一些硕博论文总结了一个完整的通信系统流程图。若有不对的地方请多多指正//部分内容有参考ChatGP…

TeXiFy IDEA 编译后文献引用为 “[?]“

文章目录 1. 问题描述2. 原因分析3. 解决方案3.1 添加自动化脚本3.2 附录——配置一览表 1. 问题描述 在 IDEA 中使用 TeXiFy IDEA 编译后的文章文献引用是 [?] 2. 原因分析 根据网上教程所生成的目录结构如下&#xff1a; 报错日志&#xff1a; 根据 /out 目录结构&#x…

Qt注册类对象单例与单类型区别

1.实现类型SingletonTypeExample #ifndef SINGLETONTYPEEXAMPLE_H #define SINGLETONTYPEEXAMPLE_H#include <QObject>class SingletonTypeExample : public QObject {Q_OBJECT public://只能显示构造类对象explicit SingletonTypeExample(QObject *parent nullptr);//…

Linux Shell脚本练习(三)

1、测试用户名与密码是否正确。 2、输出1-1000内的素数。 3、对 100 以内的所有正整数相加求和(1234...100)。 4、输出9*9 乘法表。 5、编写脚本,显示进度条。 、 6、输入三个数并进行升序排序

金融帝国实验室(CapLab)官方更新_V9.1.65版本(2024年第13次)

〖金融帝国实验室〗&#xff08;Capitalism Lab&#xff09;游戏更新记录&#xff08;2024年度&#xff09; ————————————— ◎游戏开发&#xff1a;Enlight Software Ltd.&#xff08;微启软件有限公司&#xff09; ◎官方网站&#xff1a;https://www.capitalism…

鸿蒙Harmony应用开发—ArkTS声明式开发(鼠标事件)

在鼠标的单个动作触发多个事件时&#xff0c;事件的顺序是固定的&#xff0c;鼠标事件默认透传。 说明&#xff1a; 从API Version 8开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。目前仅支持通过外接鼠标触发。 onHover onHover(event: …

Oracle中序列

1. Sequence 定义 在Oracle中可以用SEQUENCE生成自增字段。Sequence序列是Oracle中用于生成数字序列的对象&#xff0c;可以创建一个唯一的数字作为主键。 2. 为什么要用 Sequence 你可能有疑问为什么要使用序列&#xff1f; 不能使用一个存储主键的表并每次递增吗&#xf…