使用原生PHP构建一个简单的PHPWeb服务器

1.目录机构

webserver
--src
-- Response.php
-- Server.php
-- Request.php
-- vendor
-- Server.php
-- composer.json

2. 使用comoposer构建自动加载

编辑`comooser.json`文件

{
"autoload": {
"psr-4": {
"Icarus\\Station\\PHPServer\\": "src/"
}
}
}

使用PSR-4自动加载方式构建自动加载

3. 编写 Server文件

该文件作为启动文件,使用以下命令 php Server 8080 启动服务

<?php

use Icarus\Station\PHPServer\Server;
use Icarus\Station\PHPServer\Request;
use Icarus\Station\PHPServer\Response; array_shift($argv); //获取端口号
if (empty($argv)) {
$port = 80;
}else {
$port = (int)array_shift($argv);
} require_once "./vendor/autoload.php"; $server = new Server("127.0.0.1",$port); $server->listen(function(Request $request){
return new Response("Hello World!");
});

4. 编写Response.php

该类实现对请求的响应

<?php

namespace Icarus\Station\PHPServer;

class Response
{
protected static $statusCodes = [
100 => 'Continue',
101 => 'Switching Protocols', // Success 2xx
200 => 'OK',
201 => 'Created',
202 => 'Accepted',
203 => 'Non-Authoritative Information',
204 => 'No Content',
205 => 'Reset Content',
206 => 'Partial Content', // Redirection 3xx
300 => 'Multiple Choices',
301 => 'Moved Permanently',
302 => 'Found', // 1.1
303 => 'See Other',
304 => 'Not Modified',
305 => 'Use Proxy',
// 306 is deprecated but reserved
307 => 'Temporary Redirect', // Client Error 4xx
400 => 'Bad Request',
401 => 'Unauthorized',
402 => 'Payment Required',
403 => 'Forbidden',
404 => 'Not Found',
405 => 'Method Not Allowed',
406 => 'Not Acceptable',
407 => 'Proxy Authentication Required',
408 => 'Request Timeout',
409 => 'Conflict',
410 => 'Gone',
411 => 'Length Required',
412 => 'Precondition Failed',
413 => 'Request Entity Too Large',
414 => 'Request-URI Too Long',
415 => 'Unsupported Media Type',
416 => 'Requested Range Not Satisfiable',
417 => 'Expectation Failed', // Server Error 5xx
500 => 'Internal Server Error',
501 => 'Not Implemented',
502 => 'Bad Gateway',
503 => 'Service Unavailable',
504 => 'Gateway Timeout',
505 => 'HTTP Version Not Supported',
509 => 'Bandwidth Limit Exceeded'
]; protected $status = 200;
protected $body = '';
protected $headers = []; public function __construct($body, $status = null)
{
if (!is_null($status)) {
$this->status = $status;
}
$this->body = $body;
$this->header('Date', gmdate('D, d M Y H:i:s T'));
$this->header('Content-Type', 'text/html; charset=utf-8');
$this->header('Server', 'PHPServer/1.0.0');
} public function header($key, $val)
{
$this->headers[ucwords($key)] = $val;
} public function buildHeaderString()
{
$lines = [];
$lines[] = "HTTP/1.1 " . $this->status . " " . static::$statusCodes[$this->status]; foreach ($this->headers as $key => $value) {
$lines[] = $key . ": " . $value;
} return implode(" \r\n", $lines) . "\r\n\r\n";
} public static function error($statusCode)
{
header(self::$statusCodes[$statusCode], '', $statusCode);
} public function __toString()
{
return $this->buildHeaderString() . $this->body;
}
}

5. 编写Request.php

该类主要实现请求的解析(暂时为GET请求)

<?php

namespace Icarus\Station\PHPServer;

class Request
{
protected $uri = '';
protected $method = '';
protected $params = [];
protected $headers = []; public function __construct($method, $uri, $headers)
{
$this->method = strtolower($method);
$this->headers = $headers;
list($this->uri, $param) = explode('?', $uri);
parse_str($param, $this->params);
} public function method()
{
return $this->method;
} public function headers($key, $default = null)
{
if (isset($this->headers[$key])) {
$default = $this->headers[$key];
}
return $default;
} public function uri()
{
return $this->uri;
} public function params($key, $default = null)
{
if (isset($this->params[$key])) {
$default = $this->params($key);
}
return $default;
} public static function withHeaderString($data)
{
$headers = explode("\n", $data);
list($method, $uri) = explode(" ", array_shift($headers));
$header = [];
foreach ($headers as $value) {
$value = trim($value);
if (strpos($value, ":") !== false) {
list($key, $value) = explode(":", $value);
$header[$key] = $value;
}
}
return new static($method, $uri, $header);
} public function __toString()
{
return json_encode($this->headers);
}
}

6.编写Server.php

该模块主要实现对socket的封装。

<?php

namespace Icarus\Station\PHPServer;

class Server
{ protected $host = null;
protected $port = null;
protected $socket = null; public function __construct($host, $port)
{
$this->host = $host;
$this->port = $port;
$this->createSocket();
$this->bind();
} protected function createSocket()
{
$this->socket = socket_create(AF_INET, SOCK_STREAM, 0);
} protected function bind()
{
if (!socket_bind($this->socket,$this->host, $this->port)) {
throw new \Exception("未能绑定socket: " . $this->host . $this->port . socket_strerror(socket_last_error()));
} } public function listen(callable $callback)
{
while (true) {
socket_listen($this->socket);
if (!$client = socket_accept($this->socket)) {
socket_close($client);
continue;
}
$data = socket_read($client, 1024);
$request = Request::withHeaderString($data);
$response = call_user_func($callback, $request);
if (!$response | !$response instanceof Response) {
$response = Response::error(404);
}
$response = (string)$response;
socket_write($client,$response,strlen($response));
socket_close($client);
}
}
}

7. 使用

1. 进入到项目目录
2. 执行` php Server 8080`
3. 另起一个终端,执行 `curl "http://127.0.0.1:8080`
4. 显示 `Hello World!`

8. 总结

该demo使用socket来实现一个简单的webserver,但是由于php不支持多线程的的特性(其实也能实现,不过需要安装pthreads扩展),还是不适合开发webserver,此demo主要用来学习webserver的概念。

此demo参考了http://station.clancats.com/writing-a-webserver-in-pure-php/的讲解及实现。

使用纯php构建一个简单的PHP服务器的更多相关文章

  1. 通过python 构建一个简单的聊天服务器

    构建一个 Python 聊天服务器 一个简单的聊天服务器 现在您已经了解了 Python 中基本的网络 API:接下来可以在一个简单的应用程序中应用这些知识了.在本节中,将构建一个简单的聊天服务器.使 ...

  2. struts1:(Struts重构)构建一个简单的基于MVC模式的JavaWeb

    在构建一个简单的基于MVC模式的JavaWeb 中,我们使用了JSP+Servlet+JavaBean构建了一个基于MVC模式的简单登录系统,但在其小结中已经指出,这种模式下的Controller 和 ...

  3. 【Android Developers Training】 3. 构建一个简单UI

    注:本文翻译自Google官方的Android Developers Training文档,译者技术一般,由于喜爱安卓而产生了翻译的念头,纯属个人兴趣爱好. 原文链接:http://developer ...

  4. 构建一个简单的Linux系统 MenuOs —— start_kernel到init进程(20135304刘世鹏)

    构建一个简单的Linux系统 MenuOs —— start_kernel到init进程 作者:刘世鹏20135304 <Linux内核分析>MOOC课程http://mooc.study ...

  5. gRPC初探——概念介绍以及如何构建一个简单的gRPC服务

    目录 引言 1. gRPC简介 2. 使用Protocol Buffers进行服务定义 2.1 定义消息 2.2 定义服务接口 3.构建简单的gRPC服务 3.1 编写proto文件,定义消息和接口 ...

  6. 第三周——构建一个简单的Linux系统MenuOS

    [洪韶武 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 ] 第三周  构建一个 ...

  7. 构建一个简单的基于MVC模式的JavaWeb

    零晨三点半了,刚刚几个兄弟一起出去吼歌,才回来,这应该是我大学第二次去K歌,第一次是大一吧,之后每次兄弟喊我,我都不想去,因为我还是很害怕去KTV,或许是因为那里是我伤心的地方,也或许是因为我在那里失 ...

  8. 手把手教你用vue-cli构建一个简单的路由应用

    上一章说道:十分钟上手-搭建vue开发环境(新手教程)https://www.jianshu.com/p/0c6678671635 开发环境搭建好之后,那么开始新添加一些页面,构建最基本的vue项目, ...

  9. Ant—使用Ant构建一个简单的Java工程(两)

    博客<Ant-使用Ant构建一个简单的Java项目(一)>演示了使用Ant工具构建简单的Java项目,接着这个样例来进一步学习Ant: 上面样例须要运行多条ant命令才干运行Test类中的 ...

随机推荐

  1. $_POST 和 php://input 的区别

    手册中摘取的几句话: 当 HTTP POST 请求的 Content-Type 是 application/x-www-form-urlencoded 或 multipart/form-data 时, ...

  2. Tenka1 Programmer Contest D - Crossing

    链接 Tenka1 Programmer Contest D - Crossing 给定\(n\),要求构造\(k\)个集合\({S_k}\),使得\(1\)到\(n\)中每个元素均在集合中出现两次, ...

  3. Python---基础---爱因斯坦阶梯问题

    写一个程序,打印出0-100所有奇数 ls = range(0, 101)for i in ls: if i % 2 == 1: print(i)--------------------------- ...

  4. web前后端分离漏洞分析防御

    web前后端分离漏洞分析防御 漏洞分析,主要漏洞有 一.跨站脚本攻击XSS 程序 + 数据 = 结果:攻击后,数据夹杂一部分程序(执行代码),导致结果改变: 1.XSS攻击注入点 (a):HTML节点 ...

  5. 使用linkedlist封装简单的先进先出队列

    创建一个类Queue代表队列(先进先出),添加add(Object obj) 及get()方法, 并添加main()方法进行验证 思路: 使用LinkedList实现队列,在向LinkedList中添 ...

  6. How To Create/Extend Swap Partition In Linux Using LVM

    https://www.2daygeek.com/how-to-create-extend-swap-partition-in-linux-using-lvm/ BY RAMYA NUVVULA ·  ...

  7. 用java实现文件的断点续传并发下载

    需求: 支持文件批量下载.现在有很多小图片需要批量下载,不希望在服务器打包下载. 支持大文件断点下载.比如下载10G的文件. PC端全平台支持.Windows,macOS,Linux 全浏览器支持.i ...

  8. subprocess.Popen运行报错WindowsError: [Error 740]

    subprocess.Popen在win10下运行报740错时 使用os.popen替换,运行OK,exe程序成功启动 import subprocess import uiautomation as ...

  9. P2258子矩阵

    传送 一道看起来就很暴力的题. 这道题不仅暴力,还要用正确的姿势打开暴力. 因为子矩阵的参数有两个,一个行一个列(废话) 我们一次枚举两个参数很容易乱对不对?所以我们先枚举行,再枚举列 枚举完行,列, ...

  10. JS-线程、事件循环、任务队列

    JS 是单线程的,但是却能执行异步任务,这主要是因为 JS 中存在事件循环(Event Loop)和任务队列(Task Queue). 事件循环: JS 会创建一个类似于 while (true) 的 ...