惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

The Last Watchdog
The Last Watchdog
S
Securelist
T
Threat Research - Cisco Blogs
Forbes - Security
Forbes - Security
AI
AI
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Attack and Defense Labs
Attack and Defense Labs
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Know Your Adversary
Know Your Adversary
P
Privacy International News Feed
W
WeLiveSecurity
P
Palo Alto Networks Blog
O
OpenAI News
V2EX - 技术
V2EX - 技术
H
Heimdal Security Blog
L
Lohrmann on Cybersecurity
NISL@THU
NISL@THU
T
Tor Project blog
T
Threatpost
G
GRAHAM CLULEY
T
The Exploit Database - CXSecurity.com
云风的 BLOG
云风的 BLOG
人人都是产品经理
人人都是产品经理
A
Arctic Wolf
www.infosecurity-magazine.com
www.infosecurity-magazine.com
Latest news
Latest news
Application and Cybersecurity Blog
Application and Cybersecurity Blog
T
Tenable Blog
量子位
L
LINUX DO - 最新话题
Webroot Blog
Webroot Blog
F
Fortinet All Blogs
C
CERT Recently Published Vulnerability Notes
Hacker News: Ask HN
Hacker News: Ask HN
U
Unit 42
T
Tailwind CSS Blog
WordPress大学
WordPress大学
N
News | PayPal Newsroom
C
CXSECURITY Database RSS Feed - CXSecurity.com
N
Netflix TechBlog - Medium
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Stack Overflow Blog
Stack Overflow Blog
Cloudbric
Cloudbric
C
Check Point Blog
V
Visual Studio Blog
The GitHub Blog
The GitHub Blog
The Hacker News
The Hacker News
B
Blog

青空之蓝

[青空之蓝-2023] - 色彩 | 青空之蓝 [青空之蓝-2022] - 平静 | 青空之蓝 [青空之蓝-2021] - 远望 | 青空之蓝 浅谈垃圾回收 | 青空之蓝 浅谈泛型擦除 | 青空之蓝 浅谈单点登录 | 青空之蓝 使用 Kotlin 编写 Spring 测试 | 青空之蓝 设计模式系列文章 | 青空之蓝 从零实现一个 Java 微框架 - IoC | 青空之蓝 从零实现一个 Java 微框架 - 前言 | 青空之蓝 浅谈 JVM:类加载 | 青空之蓝 浅谈 IO | 青空之蓝 浅谈并发:synchronized & ReentrantLock | 青空之蓝 浅谈并发:CAS & AQS | 青空之蓝 浅谈并发:ThreadLocal | 青空之蓝 浅谈并发:三大特性 | 青空之蓝 浅谈组合注解 & 注解别名 | 青空之蓝 [青空之蓝-2020]-迷茫 | 青空之蓝 Java 系列文章 | 青空之蓝 HTTP 系列文章 | 青空之蓝 浅谈 EatWhatYouKill | 青空之蓝 浅谈可扩展线程池 | 青空之蓝 聊聊写框架 | 青空之蓝 聊聊现状-[2020-09] | 青空之蓝 浅谈并发:锁 | 青空之蓝 浅谈并发:基础 | 青空之蓝 浅谈缓存 | 青空之蓝 无须定义类,Spring 快速注入 Json 参数 | 青空之蓝 浅谈 Proxy 和 Aop | 青空之蓝 为 Vue3 添加一个简单的 Store | 青空之蓝 从零实现一个 PHP 微框架 - 服务提供者 | 青空之蓝 WSL2 踩坑记录 | 青空之蓝 浅谈浏览器Event Loop [更新] | 青空之蓝 从零实现一个 PHP 微框架 - Bootstrap 启动加载 | 青空之蓝 从零实现一个 PHP 微框架 - IoC 容器 | 青空之蓝 从零实现一个 PHP 微框架 - PSR & Composer | 青空之蓝 从零实现一个 PHP 微框架 - 前言 | 青空之蓝 MVVM 简单实现 | 青空之蓝 浅谈 DI 和 IoC | 青空之蓝 中间件实现 [PHP] | 青空之蓝 告别 Windows 终端的难看难用,打造好用的 PowerShell | 青空之蓝 VSCode Java输出中文乱码问题解决[更新] | 青空之蓝 浅谈浏览器渲染 | 青空之蓝 Vue-Cli@2 项目迁移日志 | 青空之蓝 Laragon & Scoop 集成踩坑记录 | 青空之蓝 「一行代码」优雅管理 Windows 软件 | 青空之蓝 [青空之蓝-2019]-年度总结 | 青空之蓝 为Vue添加简单的Store | 青空之蓝 为React添加简单的Store | 青空之蓝 为Vuex添加同步Action | 青空之蓝 浅谈B+树 | 青空之蓝 浅谈跳表 | 青空之蓝 浅谈数据库索引 | 青空之蓝 MySQL事务隔离 | 青空之蓝 算法复杂度分析(1) | 青空之蓝 一年来的经验总结 | 青空之蓝 Acrylic - VSCode Extension | 青空之蓝 ace编辑器设置惯性滚动 | 青空之蓝 Java二叉树实现 | 青空之蓝 Java图实现 | 青空之蓝 为apt方式安装的nginx重新编译增加WebDAV | 青空之蓝 XK-Editor - 一个支持富文本和Markdown的编辑器 | 青空之蓝 JS生成列表树 | 青空之蓝 Laravel生成目录树 | 青空之蓝 XK-Note - 集各种神奇功能的云笔记 | 青空之蓝 PHP GD生成验证码 | 青空之蓝 PHP GD图片处理[转换格式-水印-缩略图] | 青空之蓝 Origami - 简洁轻快的WordPress主题 | 青空之蓝 为WordPress启用WorkBox | 青空之蓝 [青空之蓝-2018]-年度总结 | 青空之蓝 VSCode Java手动导入jar和源码包 | 青空之蓝 Windows IP变化自动发送邮件 | 青空之蓝 C链表实现重制版 | 青空之蓝 C 结构体的定义和使用 | 青空之蓝 图的搜索(遍历) - BFS & DFS | 青空之蓝 Java链表实现 | 青空之蓝 C 快速排序 | 青空之蓝 C 插入排序 | 青空之蓝 C 归并排序 | 青空之蓝 C语言链表实现 | 青空之蓝 VSCode配置Java调试环境[Windows] | 青空之蓝 C 选择排序 | 青空之蓝 C 冒泡排序 | 青空之蓝 VSCode配置PHP调试环境[Windows] | 青空之蓝 VSCode配置C/C++ GDB调试环境[Windows] | 青空之蓝 WordPress友情链接模板 | 青空之蓝 Intel Optane 傲腾内存体验 | 青空之蓝 Mysql双机热备实战 | 青空之蓝 博客一年记录 | 青空之蓝 为WordPress启用Service Worker | 青空之蓝 Bing每日一图API | 青空之蓝 iframe延迟加载 | 青空之蓝 写在2018年高考前 | 青空之蓝 The Fox主题汉化分享 | 青空之蓝 [青空之蓝-2017]-崭新 | 青空之蓝 本博客评论规则 | 青空之蓝 世界,您好! | 青空之蓝
从零实现一个 PHP 微框架 - 初始化请求 | 青空之蓝
Otstar Lin · 2020-07-25 · via 青空之蓝

前言

更新一波文章。

这次的内容相对简单点,初始化请求的过程包括封装 $_GET $_POST 等关联数组到 Request 对象中,用于后续流程的使用,以及从封装 Request 到路由之前的这段过程。

构造 Request

构造 Request 是通过 Application.dispatchToEmit 里的 $request = $this->make(Request::class) 初始化的,make 方法会通知容器初始化 Request 对象。

<?php
protected function dispatchToEmit(): void
{
    // 获取请求
    $request = $this->make(Request::class);

    // 处理
    $response = $this->make(RouteManager::class)->dispatch($request);

    // 发送响应
    $response->send();
}
<?php
protected function dispatchToEmit(): void
{
    // 获取请求
    $request = $this->make(Request::class);

    // 处理
    $response = $this->make(RouteManager::class)->dispatch($request);

    // 发送响应
    $response->send();
}

既然是通过容器来初始化的,那么就需要绑定该对象到容器,Request 对象是通过 RequestProvider 进行绑定的:

<?php

namespace App\Providers;

use App\Http\Request;

class RequestProvider extends Provider
{
    public function register(): void
    {
        $this->app->singleton(
            Request::class,
            function () {
                return Request::make();
            },
            'request'
        );
    }
}
<?php

namespace App\Providers;

use App\Http\Request;

class RequestProvider extends Provider
{
    public function register(): void
    {
        $this->app->singleton(
            Request::class,
            function () {
                return Request::make();
            },
            'request'
        );
    }
}

Request::make

从上面的代码可以看到初始化 Request 是通过 Request::make() 的静态工厂方法构造的:

<?php
public static function make(
    array $server = null,
    array $query = null,
    array $body = null,
    array $cookies = null,
    array $files = null
): Request {
    $files = Functions::convertFiles($files ?: $_FILES);
    $server = $server ?: $_SERVER;
    $uri =
        isset($server['HTTPS']) && $server['HTTPS'] === 'on'
            ? 'https://'
            : 'http://';
    if (isset($server['HTTP_HOST'])) {
        $uri .= $server['HTTP_HOST'];
    } else {
        $uri .=
            $server['SERVER_NAME'] .
            (isset($server['SERVER_PORT']) &&
            $server['SERVER_PORT'] !== '80' &&
            $server['SERVER_PORT'] !== '443'
                ? ':' . $server['SERVER_PORT']
                : '');
    }
    $uri .= $server['REQUEST_URI'];
    $protocol = '1.1';
    if (isset($server['SERVER_PROTOCOL'])) {
        preg_match(
            '|^(HTTP/)?(?P<version>[1-9]\d*(?:\.\d)?)$|',
            $server['SERVER_PROTOCOL'],
            $matches
        );
        $protocol = $matches['version'];
    }
    return new static(
        $server,
        $files,
        $uri,
        $server['REQUEST_METHOD'],
        'php://input',
        Functions::parseHeaders($server),
        $cookies ?: $_COOKIE,
        $query ?: $_GET,
        $body ?: $_POST,
        $protocol
    );
}
<?php
public static function make(
    array $server = null,
    array $query = null,
    array $body = null,
    array $cookies = null,
    array $files = null
): Request {
    $files = Functions::convertFiles($files ?: $_FILES);
    $server = $server ?: $_SERVER;
    $uri =
        isset($server['HTTPS']) && $server['HTTPS'] === 'on'
            ? 'https://'
            : 'http://';
    if (isset($server['HTTP_HOST'])) {
        $uri .= $server['HTTP_HOST'];
    } else {
        $uri .=
            $server['SERVER_NAME'] .
            (isset($server['SERVER_PORT']) &&
            $server['SERVER_PORT'] !== '80' &&
            $server['SERVER_PORT'] !== '443'
                ? ':' . $server['SERVER_PORT']
                : '');
    }
    $uri .= $server['REQUEST_URI'];
    $protocol = '1.1';
    if (isset($server['SERVER_PROTOCOL'])) {
        preg_match(
            '|^(HTTP/)?(?P<version>[1-9]\d*(?:\.\d)?)$|',
            $server['SERVER_PROTOCOL'],
            $matches
        );
        $protocol = $matches['version'];
    }
    return new static(
        $server,
        $files,
        $uri,
        $server['REQUEST_METHOD'],
        'php://input',
        Functions::parseHeaders($server),
        $cookies ?: $_COOKIE,
        $query ?: $_GET,
        $body ?: $_POST,
        $protocol
    );
}

首先是使用 Functions::convertFiles 方法将 $_FILES 关联数组转化到 UploadFile 数组,转化的步骤就不说明了,就是将数组的结构封装到对象(之所以要这么做是为了遵循 PSR 标准)。

<?php
public static function convertFiles(array $files): array
{
    $result = [];
    foreach ($files as $key => $value) {
        if ($value instanceof UploadedFileInterface) {
            $result[$key] = $value;
            continue;
        }
        if (
            is_array($value) &&
            isset($value['tmp_name']) &&
            is_array($value['tmp_name'])
        ) {
            $result[$key] = self::resolveStructure($value);
            continue;
        }
        if (is_array($value) && isset($value['tmp_name'])) {
            $result[$key] = new UploadFile(
                $value['tmp_name'],
                $value['size'],
                $value['error'],
                $value['name'],
                $value['type']
            );
            continue;
        }
        if (is_array($value)) {
            $result[$key] = self::convertFiles($value);
            continue;
        }
    }
    return $result;
}
<?php
public static function convertFiles(array $files): array
{
    $result = [];
    foreach ($files as $key => $value) {
        if ($value instanceof UploadedFileInterface) {
            $result[$key] = $value;
            continue;
        }
        if (
            is_array($value) &&
            isset($value['tmp_name']) &&
            is_array($value['tmp_name'])
        ) {
            $result[$key] = self::resolveStructure($value);
            continue;
        }
        if (is_array($value) && isset($value['tmp_name'])) {
            $result[$key] = new UploadFile(
                $value['tmp_name'],
                $value['size'],
                $value['error'],
                $value['name'],
                $value['type']
            );
            continue;
        }
        if (is_array($value)) {
            $result[$key] = self::convertFiles($value);
            continue;
        }
    }
    return $result;
}

然后是拼接 URL,由于 PHP 已经对 URL 进行切割,所以我们还需要拼接回去,以便后续的代码使用。以及 Protocol 的提取。

由于 PHP 将 Request header 存入了 $_SERVER 为了方便使用,我们需要把 $_SERVER 中带有 HTTP_ 前缀的字段都提取出来,这些就是 Request header,同时由于 header 是不区分大小写的,我们直接把 header 的名称转成小写即可。

<?php
public static function parseHeaders(array $server): array
{
    $headers = [];
    foreach ($server as $key => $value) {
        if (!is_string($key)) {
            continue;
        }
        if ($value === '') {
            continue;
        }
        if (strpos($key, 'HTTP_') === 0) {
            $name = str_replace('_', '-', strtolower(substr($key, 5)));
            $headers[$name] = $value;
            continue;
        }
    }
    return $headers;
}
<?php
public static function parseHeaders(array $server): array
{
    $headers = [];
    foreach ($server as $key => $value) {
        if (!is_string($key)) {
            continue;
        }
        if ($value === '') {
            continue;
        }
        if (strpos($key, 'HTTP_') === 0) {
            $name = str_replace('_', '-', strtolower(substr($key, 5)));
            $headers[$name] = $value;
            continue;
        }
    }
    return $headers;
}

new Request

有了上面的一些基础的信息,就可以正式的创建 Request 对象的:

<?php
public function __construct(
    array $server = [],
    array $files = [],
    $uri = '',
    string $method = 'GET',
    $body = 'php://input',
    array $headers = [],
    array $cookies = [],
    array $query = [],
    $parsed_body = null,
    string $protocol = '1.1'
) {
    $this->validateFiles($files);
    if ($body === 'php://input') {
        $body = new Stream($body);
    }
    $this->setMethod($method);
    if ($uri instanceof UriInterface) {
        $this->uri = $uri;
    } else {
        $this->uri = new Uri($uri);
    }
    if ($body instanceof StreamInterface) {
        $this->stream = $body;
    } else {
        $this->stream = new Stream($body, 'wb+');
    }
    $this->setHeaders($headers);
    $this->server = $server;
    $this->files = $files;
    $this->cookies = $cookies;
    $this->query = $query;
    $this->protocol = $protocol;

    $content_type = $this->header('Content-Type');
    if (
        $content_type !== null &&
        stripos($content_type, 'application/json') !== false
    ) {
        $this->parsed_body = array_merge(
            $parsed_body,
            json_decode($this->stream->getContents(), true)
        );
    } else {
        $this->parsed_body = $parsed_body;
    }

    if (!$this->hasHeader('Host') && $this->uri->getHost()) {
        $host = $this->uri->getHost();
        $host .= $this->uri->getPort() ? ':' . $this->uri->getPort() : '';
        $this->headerAlias['host'] = 'Host';
        $this->headers['Host'] = [$host];
    }
}
<?php
public function __construct(
    array $server = [],
    array $files = [],
    $uri = '',
    string $method = 'GET',
    $body = 'php://input',
    array $headers = [],
    array $cookies = [],
    array $query = [],
    $parsed_body = null,
    string $protocol = '1.1'
) {
    $this->validateFiles($files);
    if ($body === 'php://input') {
        $body = new Stream($body);
    }
    $this->setMethod($method);
    if ($uri instanceof UriInterface) {
        $this->uri = $uri;
    } else {
        $this->uri = new Uri($uri);
    }
    if ($body instanceof StreamInterface) {
        $this->stream = $body;
    } else {
        $this->stream = new Stream($body, 'wb+');
    }
    $this->setHeaders($headers);
    $this->server = $server;
    $this->files = $files;
    $this->cookies = $cookies;
    $this->query = $query;
    $this->protocol = $protocol;

    $content_type = $this->header('Content-Type');
    if (
        $content_type !== null &&
        stripos($content_type, 'application/json') !== false
    ) {
        $this->parsed_body = array_merge(
            $parsed_body,
            json_decode($this->stream->getContents(), true)
        );
    } else {
        $this->parsed_body = $parsed_body;
    }

    if (!$this->hasHeader('Host') && $this->uri->getHost()) {
        $host = $this->uri->getHost();
        $host .= $this->uri->getPort() ? ':' . $this->uri->getPort() : '';
        $this->headerAlias['host'] = 'Host';
        $this->headers['Host'] = [$host];
    }
}

首先需要对 files 进行验证,判断 files 是否实现了 UploadedFileInterface

接着就需要对 Request body 进行封装了,StreamStreamInterface 的实现类,提供了对 body 数据流的一些操作方法。

除了 filebody,我们还需要把 url 封装成 Uri 对象,该对象实现了 UriInterface,提供了对 url 的一些操作方法。

由于请求的方式可能是通过 JSON 的格式传输的,此时 $_POST 就无法获取到这些通过 JSON 传输的数据,所以,我们还需要解析 JSON。

在 PSR 标准中有说明,当请求没有 Host 头的时候,需要手动设置,保证 Request 对象中存在 Host 头。

结语

到这里初始化 Request 的部分就完成了。由于博主忙着重写 XK-Editor,所以更新 文章的速度可能会慢一点 2333。

从零实现一个 PHP 微框架 - 初始化请求

https://blog.ixk.me/post/implement-a-php-microframework-from-zero-6
  • 许可协议

    BY-NC-SA

  • 本文作者

    Otstar Lin

  • 发布于

    2020/07/25

转载或引用本文时请遵守许可协议,注明出处、不得用于商业用途!

浅谈 Proxy 和 Aop为 Vue3 添加一个简单的 Store