详解HTTP协议


前言

虽然说应用层协议是我们程序猿自己来定的!!!
但实际上,已经有大佬们定义了一些现成的,又非常好用的应用层协议,可以供我们直接参考使用.HTTP(超文本传输协议)就是其中的一个.


正文开始!

一、认识URL

在这里插入图片描述
在这里插入图片描述

但是在我们现在使用中,端口号大多都被省略了!

如下:

在这里插入图片描述

这是因为在使用确定协议的时候,一般显示的时候,会缺省端口号(有端口号被省略了).

所以,浏览器访问指定的URL的时候,浏览器必须给我们自动添加port;

浏览器如何得知,URL匹配的port是谁呢? 特定的众所周知的服务,端口号必须是确定的!!!

http->80
https->443
sshd->22

注意:
我们自己写的网络服务bind端口的时候,只能绑定[1024,n]范围内的端口号.

在这里插入图片描述
http就是获取网页资源的,视频,音频等的也都是文件!!!也就是向特定的服务器申请特定的"资源",然后获取到本地,进行展示和某种使用的!

如果client没有获取的时候,资源在哪里呢?—>远端的服务器!!!—>服务器都是Linux系统!—>这些资源都是文件!—>通过路径来访问文件!

urlencode和urldecode

像 / ? : 等这样的字符,已经被url当做特殊意义理解了.因此这些字符不能随意出现.

比如,某个参数中需要带有这些字符,就必须先对特殊字符进行转义.

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

例如:

在这里插入图片描述


在这里插入图片描述

“+“就被转义为”%2B”.
urldecode就是urlencode的逆过程.

urlencode工具!!!

二、http协议格式

在这里插入图片描述

任何协议的request or response:
报头+有效载荷

那么http如何保证自己的报头和有效载荷被全部读取呢?----无论是请求还是响应.

  1. 读取完整报头:按行读取,直到读取到空行!
  2. 你又如何保证,你能读取到完整的正文呢?(报头能读取完毕,请求或者响应属性中,"一定"要包含正文的长度!)

server.hpp

#include<iostream>
#include<string>
#include<cassert>
#include<unistd.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<sys/socket.h>
#include<string.h>
#include<arpa/inet.h>
#include<fstream>
using namespace std;

#define CRLF "\r\n"
#define SPACE " "
#define SPACE_LEN strlen(SPACE)
#define HOME_PAGE "index.html" //首页字段
#define ROOT_PATH "wwwroot"

string GetPath(string httpRequest)
{
    size_t pos = httpRequest.find(CRLF);
    if(pos == string::npos) return "";
    string requestLine = httpRequest.substr(0,pos);
    //GET /a/b/index.html http/1.0
    size_t first = requestLine.find(SPACE);
    if(first == string::npos) return "";
    size_t second = requestLine.rfind(SPACE);
    if(second == string::npos) return "";

    string path = requestLine.substr(first+SPACE_LEN,second-(first+SPACE_LEN));
    if(path.size()==1 && path[0]=='/') path+=HOME_PAGE;
    return path;

}
string readFile(const string& recource)
{
    // int fd = open(recource.c_str(),O_RDONLY|O_APPEND,0666);

    ifstream in(recource);
    if(!in.is_open()) return "404";
    string content;
    string line;
    while(getline(in,line))
    {
        content += line;
    }
    
    in.close();
    return content;

}

void handlerHttpRequest(int sock)
{
    char buffer[10240];
    ssize_t s = read(sock,buffer,sizeof buffer);
    if(s>0)
    {
        cout<<buffer<<endl;
    }
    string path = GetPath(buffer);
    cout<<path<<endl;

    // path = "/a/b/index.html";
    // recource = "./wwroot"; //我们的web根目录
    // recource += path; // ./wwwroot/a/b/index.html



    // 1.文件在哪里? 在请求的请求行中,第二个字段
    // 2.如何读取?
    string recource = ROOT_PATH;
    recource += path;
    cout<<recource<<endl;

    string html = readFile(recource);
    size_t pos = recource.rfind('.');
    string suffix = recource.substr(pos);
    //开始响应
    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: "+to_string(html.size()) + "\r\n");
    response += "\r\n";
    response += html;
    send(sock,response.c_str(),response.size(),0);

}

class ServerTcp
{
public:
    ServerTcp(uint16_t port, string ip = "")
        : _listenSock(-1), _port(port), _ip(ip)
    {}
    ~ServerTcp()
    {
    }

public:
    void init()
    {
        // 1.创建socket
        _listenSock = socket(AF_INET, SOCK_STREAM, 0);
        if (_listenSock < 0)
        {
            exit(1);
        }
        // 2.bind绑定
        // 2.1填充服务器
        struct sockaddr_in local; // 用户栈
        memset(&local, 0, sizeof local);
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);
        _ip.empty() ? (local.sin_addr.s_addr = INADDR_ANY) : (inet_aton(_ip.c_str(), &local.sin_addr));
        // 2.2本地socket信息,写入_sock对应的内核区域
        if (bind(_listenSock, (const sockaddr *)&local, sizeof local) < 0)
        {
            exit(2);
        }
        // 3.监听socket,为何要监听呢?tcp是面向连接的!
        if (listen(_listenSock, 5 /*后面再说*/) < 0)
        {
            exit(3);
        }
        // 允许别人来连接你了
    }
    void loop()
    {
        while (true)
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            // 4.获取连接,accept的返回值是一个新的socket fd??
            // 4.1 _listenScok:监听&&获取新的连接--->sock
            // 4.2 serviceSock:给用户提供新的socket服务
            int serviceSock = accept(_listenSock, (struct sockaddr *)&peer, &len);
            if (serviceSock < 0)
            {
                continue;
            }

            // //5.1 V1.1---多进程版本
            //爷爷进程
            pid_t id=fork();
            assert(id != -1);
            if(id==0)
            {
                //爸爸进程
                close(_listenSock);
                //又进行了一次fork
                if(fork()>0) exit(0);
                //孙子进程--就没有爸爸进程了--孤儿进程--被系统领养了--回收问题就交给了系统来回收
                handlerHttpRequest(serviceSock);
                exit(0);
            }
            close(serviceSock);
            //爸爸进程直接终止,立马得到退出码,释放僵尸进程状态
            pid_t ret=waitpid(id,nullptr,0);//就用阻塞式等待
            (void)ret;
        }
    }



private:
    int _listenSock;
    uint16_t _port;
    string _ip;
};

serverTcp.cc

#include"server.hpp"
static void Usage(string proc)
{
    cerr << "Usage\n\t" << proc << " port ip" << endl;
    cerr << "Example\n\t" << proc << " 8080  127.0.0.1\n"
         << endl;
}


// ./serverTcp local_port [local_ip]
int main(int argc, char *argv[])
{
    if (argc != 2 && argc != 3)
    {
        Usage(argv[0]);
        exit(4);
    }
    uint16_t port = stoi(argv[1]);
    string ip;
    if (argc == 3)
    {
        ip = argv[2];
    }
    ServerTcp svr(port, ip);
    svr.init();
    svr.loop();
    return 0;
}

在这里插入图片描述

在这里插入图片描述
备注:此处我使用了8080端口号启动了HTTP服务器,虽然HTTP服务器一般使用80端口!
但这只是一个通用的习惯.并不是说HTTP服务器就不能使用其他的端口号.

以上就是一个表单!
我们的网络行为无非有两种:

  1. 我想把远端的资源拿到你的本地:GET /index.html htpp/1.1
  2. 我们想把我们的属性字段提交给远端. GET or POST

用抓包软件进行抓包!
在这里插入图片描述

三、http的请求方法

方法说明支持的HTTP协议版本
GET获取资源1.0、1.1
POST传输实体主体1.0、1.1
PUT传输文件1.0、1.1
HEAD获得报文首部1.0、1.
DELETE删除文件1.0、1.1
OPTIONS询问支持的方法1.1
TRACE追踪路径1.1
CONNECT要求用隧道协议连接代理1.1
LINK建立和资源之间的联系1.0、1.1
UNLINE断开连接关系1.0、1.1

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

GET和POST的区别

在这里插入图片描述
区别:

  1. GET通过url传参
  2. POST通过正文传参
  3. GET方法传参不私密
  4. POST方法通过正文传参,所以相对私密一些.
  5. GET通过url传参,POST通过正文传参,所以一般一些比较大的内容都是通过POST方式传参的!

三、HTTP的状态码

类别原因短语
1XXInformational(信息状态码)接受的请求正在处理
2XXSuccess(成功状态码)请求正常处理完毕
3XXRedirection(重定向状态码)需要进行附加操作以完成请求
4XXClient Error(客服端错误状态码)服务器无法处理请求
5XXServer Error(服务器错误状态码)服务器处理请求出错

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

重定向

301:永久重定向
302:临时重定向
在这里插入图片描述

四、HTTP常见的Header

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

在这里插入图片描述

cookie

http协议特点之一:无状态
举个栗子:比如你刚才访问一个网页之后,http是不会帮你做记录的,也就是说http不知道你之前访问的是那个网页.

那么用户需要一个功能:会话保持.

一旦登陆,会有各种会话保持的策略.

使用B站举个栗子:

在这里插入图片描述
在这里插入图片描述
删除这些字段以后

在这里插入图片描述
刷新一下页面

在这里插入图片描述

这个网页就不认识这个用户了!

cookie:存储用户名&&密码
以后发起http请求访问该网站就会自动携带cookie文件中的内容.

在这里插入图片描述

在这里插入图片描述
cookie:浏览器维护的文件.真正的存在磁盘&&内存级文件.

注意: cookie的安全问题,比如中间人可能通过某种手段(植入病毒等)获取到了我们的cookie文件中的用户名和密码,获取到我们的信息,那么就可以以我们的身份访问网站了???

接下来引入session字段,解决这个问题!

session

在这里插入图片描述

Connection

Connection:keep-alive 长连接
用户所看到的的完整的页面内容–背后可能是无数次http请求
http底层主流采用的就是tcp协议!
一个tcp链接会有多个http请求!(根据请求报头中的Content-Length字段读取多个请求!)

http协议是无连接的!


总结

(本章完!)

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

拾至灬名瑰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值