• 使用纯php构建一个简单的PHP服务器


    使用原生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 IcarusStationPHPServerServer;
    use IcarusStationPHPServerRequest;
    use IcarusStationPHPServerResponse;
    
    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 IcarusStationPHPServer;
    
    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(" 
    ", $lines) . "
    
    ";
        }
    
        public static function error($statusCode)
        {
            header(self::$statusCodes[$statusCode], '', $statusCode);
        }
    
        public function __toString()
        {
            return $this->buildHeaderString() . $this->body;
        }
    }
    
    

    5. 编写Request.php

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

    <?php
    
    namespace IcarusStationPHPServer;
    
    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("
    ", $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 IcarusStationPHPServer;
    
    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/的讲解及实现。

  • 相关阅读:
    关于${pageContext.request.contextPath}的理解
    Spring中的八大设计模式
    mybatis pagehelper分页插件使用
    什么是JavaConfig
    springboot优缺点
    Redis持久化的两种方式和配置
    未能加载文件或程序集“System.Web.Http.WebHost, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35”或它的某一个依赖项。系统找不到指定的文件。
    关于AJAX跨域调用ASP.NET MVC或者WebAPI服务的问题及解决方案
    C#关于微信昵称中存在的表情图标乱码解决
    移动端调用相机拍照上传图片
  • 原文地址:https://www.cnblogs.com/ontheway1024/p/11884204.html
Copyright © 2020-2023  润新知