跳至主要內容

http协议

Moments大约 15 分钟

http协议


万维网

web(world wide web)即全球广域网,也称为万维网

web是一种基于超文本和HTTP的,全球的,动态交互的,跨平台的分布式图形信息系统。

cgi简介

cgi是web服务器和一个独立进程之间通信的协议.

将http请求Request的Header头封装成进程的环境变量,Body正文写入标准输入, 进程的标准输出写入响应Response,响应包含了Header头和Body正文.

cgi体系结构

  • 公共网关接口(CGI)
  • 应用程序接口(API)

web服务器(如:nginx)

web服务器只是内容的分发者。 比如:

如果请求/index.html,那么web server会去文件系统中找到这个文件, 发送给浏览器,这里分发的是静态数据。

如果请求/index.php,根据配置文件,nginx知道这个不是静态文件, 需要去找PHP解析器来处理,那么他会把这个请求简单处理后交给PHP解析器。

nginx会传哪些数据给PHP解析器呢?

  • url
  • 查询字符串
  • HTTP header

CGI就是规定要传哪些数据,以什么样的格式传递给后方处理这个请求的协议。

index.php(HTTP协议GET请求web服务器nginx)
启动CGI程序(PHP解析器),传递规定数据
PHP解析器,解析php.ini文件,初始化执行环境,处理请求
返回规定格式的处理结果,退出进程
nginx把结果返回给浏览器

CGI会每次Fork一个进程,性能和资源消耗太大。 Fastcgi先启一个master进程,再启动多个worker将master传递给worker,动态管理worker数量。 PHP-FPM(FastCGI Process Manager)是被PHP官方收录一个Fastcgi扩展。

超文本(Hyper text)

超文本是一种用户接口方式,用以显示文本及与文本相关的内容。 常用的有超文本标记语言(Hyper Text Markup Language), 富文本格式(Rich Text Format,RTF)

超媒体

超媒体是超级媒体的简称。 是超文本和多媒体的结合。

超文本传输协议

超文本传输协议(HTTP, HyperText Transfer Protocol)

HTTP协议是互联网上应用最为广泛的一种网络协议。

HTTP协议

HTTP协议(HyperText Transfer Protocol,超文本传输协议)是因特网上应用最为广泛的一种网络传输协议, 所有的WWW文件都必须 遵守这个标准。

HTTP是一个基于TCP/IP通信协议来传递数据。

  • HTTP是无连接:指限制每次连接只处理一个请求.
  • HTTP是媒体独立的:指定合适的MIME-type可以发送任何类型的数据.
  • HTTP是无状态:无记忆功能,要后续处理前面的信息,则必须重传.

客户端(浏览器)-->HTTP Protocol(HTTP协议)-->web服务器(nginx)-->CGI解释器(PHP)-->数据库(mysql)

HTTP消息结构

  • 请求行(request line)
  • 请求头(header)
  • 空行
  • 请求体

HTTP请求方法

HTTP1.0定义了三种请求方法:GET,POST,HEAD方法. HTTP1.1新增了五种请求方法:OPTIONS, PUT, DELETE, TRACE, CONNECT方法.

方法描述
GET请求指定的页面信息,并返回实体主体.
HEAD类似于get请求,只不过返回的响应中没有具体的内容,用于获取报头.
POST向指定服务器提交数据(例如提交表单或上传文件).
PUT从客户端向服务器传送的数据取代指定的文档的内容.
DELETE请求服务器删除指定的页面.
CONNECTHTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器.
OPTIONS允许客户端查看服务器的性能.
TRACE回显服务器收到的请求,主要用于测试或诊断.

HTTP响应头信息

方法描述
Allow服务器支持哪些请求方法(如GET,POST)
Content-Encoding文档的编码.
Content-Length表示内容长度.
Content-Type表示后台的文档属于什么MIME类型.
Date当前的GMT时间.
Expires生命周期.
Last-Modified文档的最后改动时间.
Location表示客户应当到哪里去提取文档.
Refresh表示浏览器应该在多少时间之后刷新文档.
Server服务器名字.
Set-Cookie设置和页面关联的Cookie.

HTTP状态码

状态码描述
200请求成功
301永久移动
302临时移动
304未修改
401请求要求用户的身份认证
403服务器理解请求客户端的请求,但是拒绝执行此请求
404服务器无法根据客户端的请求找到资源
500服务器内部错误
502网关错误,比如php挂了
503服务器超载
504超时

HTTP 协议状态码概述

状态码英文名称中文描述
1xxInformational信息,服务器收到请求,需要请求者继续执行操作
2xxSuccess成功,操作被成功接收并处理
3xxRedirection重定向,需要进一步的操作以完成请求
4xxClient Error客户端错误,请求包含语法错误或无法完成请求
5xxServer Error服务器错误,服务器在处理请求的过程中发生了错误

HTTP协议状态码明细

状态码英文名称中文描述
100Continue继续。客户端应继续其请求
101Switching Protocols切换协议。服务器根据客户端的请求切换协议。只能切换到更高级的协议,例如,切换到HTTP的新版本协议
200OK请求成功。一般用于GET与POST请求
201Created已创建。成功请求并创建了新的资源
202Accepted已接受。已经接受请求,但未处理完成
203Non-Authoritative Information非授权信息。请求成功。但返回的meta信息不在原始的服务器,而是一个副本
204No Content无内容。服务器成功处理,但未返回内容。在未更新网页的情况下,可确保浏览器继续显示当前文档
205Reset Content重置内容。服务器处理成功,用户终端(例如:浏览器)应重置文档视图。可通过此返回码清除浏览器的表单域
206Partial Content部分内容。服务器成功处理了部分GET请求
300Multiple Choices多种选择。请求的资源可包括多个位置,相应可返回一个资源特征与地址的列表用于用户终端(例如:浏览器)选择
301Moved Permanently永久移动。请求的资源已被永久的移动到新URI,返回信息会包括新的URI,浏览器会自动定向到新URI。今后任何新的请求都应使用新的URI代替
302Found临时移动。与301类似。但资源只是临时被移动。客户端应继续使用原有URI
303See Other查看其它地址。与301类似。使用GET和POST请求查看
304Not Modified未修改。所请求的资源未修改,服务器返回此状态码时,不会返回任何资源。客户端通常会缓存访问过的资源,
通过提供一个头信息指出客户端希望只返回在指定日期之后修改的资源
305Use Proxy使用代理。所请求的资源必须通过代理访问
307Temporary Redirect临时重定向。与302类似。使用GET请求重定向
400Bad Request客户端请求的语法错误,服务器无法理解
401Unauthorized未经授权,请求要求用户的身份认证
402Payment Required保留,将来使用
403Forbidden服务器理解请求客户端的请求,但是拒绝执行此请求
404Not Found服务器无法根据客户端的请求找到资源(网页)。通过此代码,网站设计人员可设置"您所请求的资源无法找到"的个性页面
405Method Not Allowed客户端请求中的方法被禁止
406Not Acceptable服务器无法根据客户端请求的内容特性完成请求
407Proxy Authentication Required请求要求代理的身份认证,与401类似,但请求者应当使用代理进行授权
408Request Time-out服务器等待客户端发送的请求时间过长,超时
409Conflict服务器完成客户端的PUT请求是可能返回此代码,服务器处理请求时发生了冲突
410Gone客户端请求的资源已经不存在。410不同于404,如果资源以前有现在被永久删除了可使用410代码,网站设计人员可通过301代码指定资源的新位置
411Length Required服务器无法处理客户端发送的不带Content-Length的请求信息
412Precondition Failed客户端请求信息的先决条件错误
413Request Entity Too Large由于请求的实体过大,服务器无法处理,因此拒绝请求。为防止客户端的连续请求,服务器可能会关闭连接。
如果只是服务器暂时无法处理,则会包含一个Retry-After的响应信息
414Request-URI Too Large请求的URI过长(URI通常为网址),服务器无法处理
415Unsupported Media Type服务器无法处理请求附带的媒体格式
416Requested range not satisfiable客户端请求的范围无效
417Expectation Failed服务器无法满足Expect的请求头信息
500Internal Server Error服务器内部错误,无法完成请求
501Not Implemented服务器不支持请求的功能,无法完成请求
502Bad Gateway充当网关或代理的服务器,从远端服务器接收到了一个无效的请求
503Service Unavailable服务暂时不可用,由于超载或系统维护,服务器暂时的无法处理客户端的请求。延时的长度可包含在服务器的Retry-After头信息中
504Gateway Time-out网关超时,充当网关或代理的服务器,未及时从远端服务器获取请求
505HTTP Version not supported服务器不支持请求的HTTP协议的版本,无法完成处理

几个重点的状态码:504、503、500、401、200、301、302

HTTP 504 错误 – 网关超时

只要您遇到 504 错误,就请与网站官方联系(例如通过电子邮件) - 此问题个人无法解决。 需要网站方和互联网服务供应商 (ISP) 及 Web 服务器软件供应商联络, 检查在其控制下的不同电脑之间的 IP 数据传输的流通状况。 然而, 这个错误不易解决, 由于互联网流通的无规律性, 这种类型的错误是瞬态的。

HTTP 503 错误 – 服务暂时不可用

比如CPU超时,请求过多都可能导致503错误。

HTTP 500 错误 - 服务器内部错误

编写php代码出错也会导致500错误

HTTP 401 错误 - 未经授权

由于用户匿名访问使用的账号(默认是IUSR_机器名)被禁用,或者没有权限访问计算机,将造成用户无法访问。

HTTP 200 请求成功

HTTP 301 永久重定向

HTTP 302 临时重定向

表单提交中的Get和Post的异同点

幂等通俗来说是指不管进行多少次重复操作,都是实现相同的结果。

  • Get请求一般用于向服务端获取数据,Post一般向服务端提交数据。
  • Get传输的参数在url中,传递参数大小有限制,Post理论上没有大小限制。
  • Get不安全,Post安全性比Get高,这个是相对的毕竟https都可以抓包。
  • Get、Put、Delete都是幂等操作,而Post不是。
  • HTTP请求报文由3部分组成请求行,请求头,请求体,GET没有请求体.
  • Get请求可以被浏览器缓存,Post请求不会被缓存.

HTTP文档类型

Content-Type内容类型,一般是指网页中的Content-Type, 用于定义网络文件的类型和网页的编码,决定浏览器将以什么形式,什么编码读取这个文件.

php解析cgi数据

// cgi协议=>请求行和请求头写入$_SERVER超全局变量
// cgi协议=>请求体写入标准输入流

$html = '';
// 请求行
$html .= "{$_SERVER['REQUEST_METHOD']} {$_SERVER['REQUEST_URI']} {$_SERVER['SERVER_PROTOCOL']}\r\n";
// 请求头
foreach ($_SERVER as $key => $value) {
    if (0 === strpos($key, 'HTTP_')) {
        $key = ucwords(strtolower(str_replace('_', '-', substr($key, 5))), '-');
        $html .= sprintf("%s:%s\r\n", $key, $value);
    }
}
// 空行结束
$html .= "\r\n";
// 请求体
if (in_array($_SERVER['REQUEST_METHOD'], ['GET'])) {
    // 根据http协议,GET请求请求体为空,这个嘛,只是规定,增加Content-length即可
    $html .= file_get_contents('php://input');
} else {
    $html .= file_get_contents('php://input');
}
// 打印内容
dump($html);

php实现get请求

// 创建连接
$fp = fsockopen($this->hostName, $this->port, $errno, $errstr, $this->timeOut);
// 创建失败,退出并打印错误提示
if (!$fp) {
    exit($errstr);
}
// 拼接http请求报文
$http = '';
// 请求行,请求方式,URL,http协议版本
$http .= "GET $this->url HTTP/1.1\r\n";
// 请求头
$http .= "Host: $this->hostName\r\n";
$http .= "Connection: close\r\n\r\n"; // 关闭长连接
// 请求体,无
// 发送请求
fwrite($fp, $http);
// 获取响应内容
$result = '';
while (!feof($fp)) {
    $result .= fgets($fp);
}
// 打印响应内容
dump($result);

php实现post请求

// 创建连接
$fp = fsockopen($this->hostName, $this->port, $errno, $errstr, $this->timeOut);
// 创建失败,退出并打印错误提示
if (!$fp) {
    exit($errstr);
}
// 拼接http请求报文
$http = '';
// 请求行,请求方式,URL,http协议版本
$http .= "POST $this->url HTTP/1.1\r\n";
// 请求头
$len = strlen("user=Moments&password=123456");
$http .= "Host: $this->hostName\r\n";
$http .= "Connection: close\r\n"; // 关闭长连接
$http .= "Cookie: _token=kC3gyCSZcuPcbqq6DHZvCRNAYMZRsuZl8muAVchj\r\n";
$http .= "Content-type: application/x-www-form-urlencoded\r\n";
$http .= "Content-length: $len\r\n\r\n"; // 此处为请求体的长度
// 请求体
$http .= "user=Moments&password=123456\r\n";
// 发送请求
fwrite($fp, $http);
// 获取响应内容
$result = '';
while (!feof($fp)) {
    $result .= fgets($fp);
}
// 打印响应内容
dump($result);

php实现服务器功能

// 创建套接字(通信节点),AF_INET=>ipv4协议,SOCK_STREAM=>流式套接字(tcp),SOL_TCP=>tcp协议
$sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
$this->checkSock($sock);

// 给套接字绑定地址和端口
$bind = socket_bind($sock, '0.0.0.0', $this->port); // 类似php-cgi -b 127.0.0.1:9000
$this->checkBind($bind, $sock);

// 监听套接字上的连接,SOMAXCONN=>队列长度
$listen = socket_listen($sock, SOMAXCONN);
$this->checkListen($listen, $sock);

$this->comment('## 服务已初始化,准备开始接受请求...');

do {
    // 接受请求
    $request = socket_accept($sock);
    if ($request === false) {
        $this->error("socket_accept() failed: reason: ".socket_strerror(socket_last_error($sock)));
        break;
    }

    do {
        // 读取请求体
        $body = socket_read($request, 2048, PHP_NORMAL_READ);
        if ($body === false) {
            $this->error("socket_read() failed: reason: ".socket_strerror(socket_last_error($request)));
            break 2;
        }

        switch (trim($body)) {
            case '':
                continue 2;
                break;
            case 'index':
                $response = "欢迎,欢迎!\n";
                socket_write($request, $response, strlen($response));
                break;
            case 'exit': // 客户端退出
                break 2;
            case 'close': // 服务端退出
                socket_close($request);
                break 3;
            default: // 人机对话
                socket_getpeername($request, $ip, $port);
                $response = sprintf("Hello %s:%s => %s\n", $ip, $port, $body);
                socket_write($request, $response, strlen($response));
                break;
        }
    } while (true);
    socket_close($request);
} while (true);

// 关闭套接字
socket_close($sock);

php实现聊天功能

// 创建套接字(通信节点),AF_INET=>ipv4协议,SOCK_STREAM=>流式套接字(tcp),SOL_TCP=>tcp协议
$sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
$this->checkSock($sock);

// 重用端口
socket_set_option($sock, SOL_SOCKET, SO_REUSEADDR, 1);

// 给套接字绑定地址和端口
$bind = socket_bind($sock, '0.0.0.0', $this->port); // 类似php-cgi -b 127.0.0.1:9000
$this->checkBind($bind, $sock);

// 监听套接字上的连接,SOMAXCONN=>队列长度
$listen = socket_listen($sock, SOMAXCONN);
$this->checkListen($listen, $sock);

$this->comment('## 服务已初始化,准备开始接受请求...');

// 添加到群组
$this->group[(int) $sock] = $sock;

do {
    $read = $this->group; // 监控的套接字数组是否有可读取的内容
    $write = []; // 监控的套接字数组是否有可写入内容
    $except = []; // 异常
    $tv_sec = 3; // 轮询时间

    // select调度模式
    $select = socket_select($read, $write, $except, $tv_sec);
    if ($select === false) {
        $this->comment("socket_select() failed, reason: ".socket_strerror(socket_last_error()));
    }

    // 轮询时间已到,但没有发现变化
    if ($select < 1) {
        continue;
    }

    // 服务端,可以理解为master,它是常驻内存的所以$sock的值在其生命周期不变,$read是轮询触发的
    if (in_array($sock, $read)) {
        // 接收一个新请求
        $request = socket_accept($sock);
        // 附加新请求
        $this->group[(int) $request] = $request;
        // 取ip和端口
        socket_getpeername($request, $ip, $port);
        // 服务器端占一个,所以减1,count($this->group) - 1
        $response = sprintf("## %s:%s:%s => 当前连接数 => %s\n", (int) $request, $ip, $port, count($this->group) - 1);
        // 打个招呼,欢迎新请求(客户端)
        socket_write($request, $response);
        // 删除服务端,不参与请求体的读取
        unset($read[(int) $sock]);
    }

    // 客户端,可以理解为worker
    foreach ($read as $key => $request) {
        // 读取请求体
        $body = @socket_read($request, 2048, PHP_NORMAL_READ);
        if ($body === false) {
            $this->error("socket_read() failed: reason: ".socket_strerror(socket_last_error($request)));
            // 连接关闭删除之
            unset($this->group[$key]);
            continue;
        }
        // 消息过滤
        $body = trim($body);
        if (empty($body)) {
            continue;
        }
        // 消息推送
        foreach ($this->group as $send) {
            // 如果是服务器端则跳过
            if ($send == $sock) {
                continue;
            }
            // 如果是发送者,则跳过
            if ($send == $request) {
                // 用户退出
                if ($body == 'exit') {
                    socket_close($request);
                    unset($this->group[$key]);
                }
                $response = sprintf("## 你的消息发送成功! => %s\n", $body);
                @socket_write($send, $response, strlen($response));
                continue;
            }
            // 构造响应参数
            socket_getpeername($request, $ip, $port);
            $response = sprintf("## 发送者%s:%s:%s => %s\n", (int) $request, $ip, $port, $body);
            $write = @socket_write($send, $response, strlen($response));
            if ($write === false) {
                $this->error("socket_write() failed: reason: ".socket_strerror(socket_last_error($request)));
                // 连接关闭删除之
                unset($this->group[$key]);
                continue;
            }
        }
    }
} while (true);

socket_close($sock);
上次编辑于:
贡献者: Moments