php做一个webserver
2021-05-30 13:03
标签:图片 vendor lod 指定端口 router write 报错 指定 live 利用php实现一个不依靠nginx/apache的简易webserver,同时支持Router路由功能,实现如在命令行键入 做一个webserver需要做的模块: 项目根目录创建一个 先是获取命令行参数,有一个提前定义好的变量名 运行 数据结果为: 我们发现第一个选项是文件名,这个并不是我们所需要的,所以使用 利用composer生成自动加载的文件: 生成 下面是我的composer.json: 生成自动引入程序: 创建webserver目录和src目录: 结构如下: 接下来进行验证是否可以 使用: 在src下新建Hello.php,内容如下: 根目录的server.php: 如果正确打印,且不报错,说明到这里所有的步骤都是争取的. 下面开始我们的核心部分 启用一个webserver需要一个服务去不停的去监听该端口是不是又请求进来,在src目录下,新建 创建监听,又可以查分成三部: 第一步: 创建socket: 这里我们使用函数 该函数用法: 需要注意的,这几个参数,的值都是int,所以需要查找手册,看一下他预定义的常量表示的含义 下面是创建 第二步:绑定端口到socket 这里使用的函数 用法: 手册地址https://www.php.net/manual/en/function.socket-bind.php 第一个参数是,上面我们定义好的socket的实例, 绑定socket到端口,然后将这两部都放进初始化函数中 第三步: 监听端口 前面我们第一步创建socket的一端,然后绑定到指定端口,接下来我们要告诉socket,开始监听了,使用 用法: 第二个参数有默认值0, 返回值是布尔 开始监听以后使用 下面是具体的代码: 修改一下根目录下的 启动webserver: 如果感觉server.php不好看的话,可以将文件名改成 server 那么命令就变成 测试结果 这一步我们将得到head头数据进行解析,获取到uri地址,以及数据,然后将请求与响应的路由做适配,最后将适配的结果返回给浏览器,大体是这样一个流程. 所以我们接下来做的就是解析header头信息: 首先我们获取请求类型,get/post 然后是他的路由地址 然后我们将其他header头参数进行解析: 现在的话,我们得到的数据有,请求参数,请求uri,各个header信息,接下来我们将得到的数据,放到全局中,便于调用,这些参数不能别修改,所以使用protected修饰,同时提供访问的方法 下面是一个汇总以后的方法: 我们将request请求抽离成一个单独的文件 根据不同的请求地址,访问到不同的控制器,所以这里我们需要首先实现一个路由功能 做一个路由: 首先我们在 实现的效果: 下面是Router中的方法: 接下来,我们创建定义路由的文件,同样在同级目录,创建 加载路由 路由我们做好了,接下来就是在程序初始的时候,将路由的映射加载进来: 下面我们在 并在构造函数中调用 路由和控制器做绑定 我们得到了路由,通过Requst.php我们得到了请求信息,接下来我们来做映射关系: 创建 下面是处理逻辑: Response.php: 上面的程序也很简单,需要注意的是,我们在返回给浏览器的时候要有响应头 将数据写入浏览器 在 基本完成了,下面我们新建一个控制器做一个测试,创建一个controller/index.php 最终的目录结构为: 最后我们修改一下入口文件: 运行 打开浏览器访问: http://192.168.2.10:9001/ 最后放一张效果图: php做一个webserver 标签:图片 vendor lod 指定端口 router write 报错 指定 live 原文地址:https://www.cnblogs.com/callmelx/p/14675226.htmlphp做一个webserver
1. 目标
php server 8080
启动的功能2. 流程
3. 接收命令行参数
server.php
文件,用于接收命令行参数,并进行项目初始化工作argv
,我们直接打印测试一下php server a b c d
array(5) {
[0]=>
string(10) "server.php"
[1]=>
string(1) "a"
[2]=>
string(1) "b"
[3]=>
string(1) "c"
[4]=>
string(1) "d"
}
array_shift
去除他,这样我们就能获取他的参数了,下面我们实现通过php server port
这条命令5 实现自动加载
composer.json
文件composer init
{
"name": "lx/webserver",
"description": "a toy webserver",
"type": "lib",
"license": "mit",
"minimum-stability": "dev",
"require": {},
"autoload": {
"psr-4": {
"webserver\\":[
"vendor/webserver/src/"
]
}
}
}
copmoser install
├── composer.json
├── composer.lock
├── server.php
└── vendor
├── autoload.php
├── composer
│ ├── autoload_classmap.php
│ ├── autoload_namespaces.php
│ ├── autoload_psr4.php
│ ├── autoload_real.php
│ ├── autoload_static.php
│ ├── ClassLoader.php
│ ├── installed.json
│ ├── installed.php
│ ├── InstalledVersions.php
│ └── LICENSE
└── webserver
└── src
say();
var_dump($a); //hello.world
6 .server服务
server.php
文件
socket_create()
函数socket_create ( int
$domain, int
$type, int
$protocol)`socket
的代码:socket = socket_create(AF_INET,SOCK_STREAM,SOL_TCP);
}
}
socket_bind
socket_bind ( Socket $socket, string $address , int $port):bool
createSocket();
$this->bind($host,$port);
}
protected function createSocket(){
$this->socket = socket_create(AF_INET,SOCK_STREAM,SOL_TCP);
}
protected function bind($host,$port){
$res = socket_bind($this->socket,$host,$port);
if (!$res){
throw new Exception("socket 绑定失败:".socket_strerror(socket_last_error()));
}
}
}
socket_listen函数
socket_listen ( Socket $socket , int $backlog = 0 ) : bool
socket_accept
用来接收请求参数,socket_accept 的返回值是一个含有请求信息的socket,然后在利用socket_read去读取这个socket里面的内容.public function listen(){
$listen_res = socket_listen($this->socket);
if (!$listen_res){
var_dump(socket_strerror(socket_last_error()));
contine();
}
//socket_set_nonblock($this->socket);
while(true){
//判断接收请求是否存在异常,如果有异常则跳过该条请求
if(!$client=socket_accept($this->socket)){
socket_close($this->socket);
continue;
}
//进入到这里说明请求没有问题,接下进行解析请求参数
$data = socket_read($this->socket,1024);
var_dump($data);
}
}
server.php
:listen();
php server.php 9001
php server 9001
"
string(730) "GET / HTTP/1.1
Host: 192.168.2.10:9001
Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.128 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Cookie: experimentation_subject_id=eyJfcmFpbHMiOnsibWVzc2FnZSI6IklqSTNaak5tT0dJMUxUSmhNbUl0TkdGaVlpMWlZelUyTFRVeU5HRmpaVGMyT1dJeE15ST0iLCJleHAiOm51bGwsInB1ciI6ImNvb2tpZS5leHBlcmltZW50YXRpb25fc3ViamVjdF9pZCJ9fQ%3D%3D--98ff316f61bc94dfd47dc8cfdd41e6d568723001
"
7.解析请求
$data = explode("\n",$data);
//首先获取请求方法
list($method,$uri) = explode(" ",array_shift($data));
@list($uri,$param_str) = explode("?",$uri);// 因为可能没有参数,所以用错误抑制符
parse_str( $param_str, $params); //将路由参数,解析成数组
$headers = [];
foreach($data as $headOpt){
$arr = explode(":",$headOpt);
if(count($arr)==2){
$headers[$arr[0]] = $arr[1];
}
}
protected $uri;
protected $method;
protected $params;
protected $headers;
//....
protected function getRequest($data){
$data = explode("\n",$data);
//首先获取请求方法
list($method,$uri) = explode(" ",array_shift($data));
@list($uri,$param_str) = explode("?",$uri);
parse_str( $param_str, $params);
$headers = [];
foreach($data as $headOpt){
$arr = explode(":",$headOpt);
if(count($arr)==2){
$headers[$arr[0]] = $arr[1];
}
}
$this->uri = $uri;
$this->method = strtoupper($method);
$this->params = $params;
$this->headers = $headers;
return $this;
}
public function method(){
return $this->method;
}
public function uri(){
return $this->uri;
}
public function params(){
return $this->params;
}
public function headers(){
return $this->headers;
}
Request.php
:uri = $uri;
$this->method = strtoupper($method);
$this->params = $params;
$this->headers = $headers;
return $this;
}
public function method(){
return $this->method;
}
public function uri(){
return $this->uri;
}
public function params(){
return $this->params;
}
public function headers(){
return $this->headers;
}
}
8.响应请求
src/server.php
同级目录创建一个Router.php
文件,这个文件作为我们的处理逻辑与请求地址的映射关系.一般我们平时使用的框架,写一条路由会包含三部分,请求方法
,请求uri
,逻辑处理文件方法
,这里我们同样需要这样做:Router::get();
这种形式 $class,
"method"=>$method
];
}
public static function post($uri, $reflect){
//首先将$method解析一下
@list($class,$method) = explode("@",$reflect);
self::$PostRouter[$uri] = [
"class" => $class,
"method"=>$method
];
}
}
config.php
,用于注册路由:
src/server.php
中增加init
方法//初始化一些准备工作
protected function init(){
require_once __DIR__."/config.php";
}
public function __construct($host,$port){
$this->init();
//....
}
Response.php
作为相应处理, 在这之前我们需要将src/server.php
做一些调整,我们让request获取的数据传递给response进行处理//src/server.php
public function listen(){
$listen_res = socket_listen($this->socket);
if (!$listen_res){
var_dump(socket_strerror(socket_last_error()));
contine();
}
//socket_set_nonblock($this->socket);
$request = new Request();
$response = new Response();
while(true){
//判断接收请求是否存在异常,如果有异常则跳过该条请求
if(!$client=socket_accept($this->socket)){
socket_close($this->socket);
continue;
}
//进入到这里说明请求没有问题,接下进行解析请求参数
$data = socket_read($client,1024);
$requestObj =$request->getRequest($data);
$resCtx = $response->handle($requestObj);//交给response进行处理
}
}
method();
switch($method){
case "GET":
$map = Router::$GetRouter;
break;
case "POST":
$map = Router::$PostRouter;
break;
}
if(isset($map[$request->uri()])){
$className = $map[$request->uri()]["class"];
$methodName = $map[$request->uri()]["method"];
$obj = new $className;
$content = (string)$obj->$methodName();
$header = $this->setHeader(200,"OK",strlen($content));
return $header. $content;
}else{
$header = $this->setHeader(404,"Not Found",0);
return $header;
}
}
}
src/server.php
中 //进入到这里说明请求没有问题,接下进行解析请求参数
$data = socket_read($client,1024);
$requestObj =$request->getRequest($data);
$resCtx = $response->handle($requestObj);//交给response进行处理
//上面是之前的代码,只为了标识一下位置,
socket_write( $client, $resCtx, strlen($resCtx));
socket_close( $client );
9.完成
hello,world";
}
public function welcome(){
return json_encode([
"msg"=>"welcome"
]);
}
}
.
├── composer.json
├── composer.lock
├── server
├── src
│ ├── config.php # 路由文件
│ ├── controller
│ │ └── index.php #测试的控制器
│ ├── Request.php # 处理请求
│ ├── Response.php #处理响应
│ ├── Router.php #路由逻辑处理
│ └── Server.php #socket服务
└── vendor
├── autoload.php
└── composer
├── autoload_classmap.php
├── autoload_namespaces.php
├── autoload_psr4.php
├── autoload_real.php
├── autoload_static.php
├── ClassLoader.php
├── installed.json
├── installed.php
├── InstalledVersions.php
└── LICENSE
listen();
php server 9001