Initial implementation of a new HTTP library based on pipelines.
authorRobert Sesek <rsesek@bluestatic.org>
Mon, 28 Nov 2016 07:13:47 +0000 (02:13 -0500)
committerRobert Sesek <rsesek@bluestatic.org>
Mon, 28 Nov 2016 07:13:47 +0000 (02:13 -0500)
A Pipeline is a series of chained Middleware that execute in order to produce a
response.

The goal is for the http2 subsystem to implement the same concepts as http, but
with looser coupling between the controllers and output filters.

http2/api.php [new file with mode: 0644]
http2/middleware.php [new file with mode: 0644]
http2/pipeline.php [new file with mode: 0644]
http2/request.php [new file with mode: 0644]
http2/response.php [new file with mode: 0644]

diff --git a/http2/api.php b/http2/api.php
new file mode 100644 (file)
index 0000000..0a901ac
--- /dev/null
@@ -0,0 +1,61 @@
+<?php
+// Hoplite
+// Copyright (c) 2016 Blue Static
+//
+// This program is free software: you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation, either version 3 of the License, or any later version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+// more details.
+//
+// You should have received a copy of the GNU General Public License along with
+// this program.  If not, see <http://www.gnu.org/licenses/>.
+
+namespace hoplite\http2;
+
+require_once HOPLITE_ROOT . '/http2/middleware.php';
+require_once HOPLITE_ROOT . '/http2/request.php';
+require_once HOPLITE_ROOT . '/http2/response.php';
+
+class DataResponse extends Response {
+  public $data;
+
+  public function __construct($data) {
+    parent::__construct();
+    $this->data = $data;
+  }
+
+  public function generate() {
+    throw new \Exception(__CLASS__ . ' cannot generate a response');
+  }
+}
+
+class JsonResponse extends Response {
+  private $data;
+
+  public function __construct(DataResponse $r) {
+    $this->code = $r->code;
+    $this->data = $r->data;
+  }
+
+  public function generate() {
+    $this->headers['Content-Type'] = 'application/json';
+    parent::generate();
+    print json_encode($this->data);
+  }
+}
+
+class JsonResponseFilter extends Middleware {
+  public function execute(Request $request) {
+    $response = $this->next->execute($request);
+
+    if ($response instanceof DataResponse) {
+      return new JsonResponse($response);
+    }
+
+    return $response;
+  }
+}
diff --git a/http2/middleware.php b/http2/middleware.php
new file mode 100644 (file)
index 0000000..4e51841
--- /dev/null
@@ -0,0 +1,62 @@
+<?php
+// Hoplite
+// Copyright (c) 2016 Blue Static
+//
+// This program is free software: you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation, either version 3 of the License, or any later version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+// more details.
+//
+// You should have received a copy of the GNU General Public License along with
+// this program.  If not, see <http://www.gnu.org/licenses/>.
+
+namespace hoplite\http2;
+
+require_once HOPLITE_ROOT . '/http2/pipeline.php';
+require_once HOPLITE_ROOT . '/http2/request.php';
+require_once HOPLITE_ROOT . '/http2/response.php';
+
+abstract class Middleware {
+  protected $pipeline;
+  protected $next;
+
+  public function __construct(Pipeline $pipeline,
+                              Middleware $next) {
+    $this->pipeline = $pipeline;
+    $this->next = $next;
+  }
+
+  public abstract /*http\Response*/ function execute(Request $request);
+}
+
+class Sentinel extends Middleware {
+  private $response;
+
+  public function __construct(Response $response) {
+    $this->response = $response;
+  }
+
+  public function execute(Request $request) {
+    return $this->response;
+  }
+}
+
+class ClosureMiddleware extends Middleware {
+  private $closure;
+
+  public function __construct(Pipeline $pipeline,
+                              Middleware $next,
+                              \Closure $closure) {
+    parent::__construct($pipeline, $next);
+    $this->closure = $closure;
+  }
+
+  public function execute(Request $request) {
+    $closure = $this->closure;
+    return $closure($request, $this->next);
+  }
+}
diff --git a/http2/pipeline.php b/http2/pipeline.php
new file mode 100644 (file)
index 0000000..5d7f885
--- /dev/null
@@ -0,0 +1,80 @@
+<?php
+// Hoplite
+// Copyright (c) 2016 Blue Static
+//
+// This program is free software: you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation, either version 3 of the License, or any later version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+// more details.
+//
+// You should have received a copy of the GNU General Public License along with
+// this program.  If not, see <http://www.gnu.org/licenses/>.
+
+namespace hoplite\http2;
+
+require_once HOPLITE_ROOT . '/http2/middleware.php';
+require_once HOPLITE_ROOT . '/http2/request.php';
+require_once HOPLITE_ROOT . '/http2/response.php';
+
+class Pipeline {
+  private $middleware = [];
+
+  public function add($middleware) {
+    /*
+    if (!($middleware instanceof Middleware ||
+          $middleware instanceof \Closure ||
+          is_array($middleware))) {
+      // TODO(php7): TypeError
+      throw new \Exception(__FUNCTION__ . ' requires a Middleware or Closure');
+    }
+     */
+    $this->middleware[] = $middleware;
+    return $this;
+  }
+
+  public function build() {
+    $chain = new Sentinel(new NotFoundResponse());
+    for ($i = count($this->middleware) - 1; $i >= 0; --$i) {
+      $args = $this->middleware[$i];
+      if ($args instanceof \Closure) {
+        $chain = new ClosureMiddleware($this, $chain, $args);
+      } else if (is_array($args)) {
+        $class = array_shift($args);
+        array_unshift($args, $this, $chain);
+        $chain = new $class(...$args);
+      } else {
+        $chain = new $args($this, $chain);
+      }
+    }
+
+    return $chain;
+  }
+
+  public function run(Middleware $chain) {
+    $response = $chain->execute(new Request());
+    $response->generate();
+  }
+
+  public function buildAndRun() {
+    $this->run($this->build());
+  }
+}
+
+class SubPipeline extends Middleware {
+  public function __construct(Pipeline $subpipe) {
+    // Skip calling super since this is a new pipeline and the
+    // next middleware will be selected by building the subpipe.
+    // It would be possible to build the middleware chain here, but
+    // doing it lazily on execution is more efficient, since SubPipeline
+    // should be used to partition applications.
+    $this->subpipe = $subpipe;
+  }
+
+  public function execute(Request $request) {
+    return $this->subpipe->build()->execute($request);
+  }
+}
diff --git a/http2/request.php b/http2/request.php
new file mode 100644 (file)
index 0000000..f1ad7eb
--- /dev/null
@@ -0,0 +1,64 @@
+<?php
+// Hoplite
+// Copyright (c) 2016 Blue Static
+//
+// This program is free software: you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation, either version 3 of the License, or any later version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+// more details.
+//
+// You should have received a copy of the GNU General Public License along with
+// this program.  If not, see <http://www.gnu.org/licenses/>.
+
+namespace hoplite\http2;
+
+/*!
+  A Request represents a HTTP request and holds the data and context associated
+  with it.
+*/
+class Request
+{
+  /*! @var string The request method (upper case). */
+  public $http_method = NULL;
+
+  /*! @var string The URL, relataive to the RootController. */
+  public $url = '';
+
+  /*! @var array HTTP request data. */
+  public $data = [];
+
+  /*! @var array Context data. */
+  public $context = [];
+
+  /*!
+    Constructor. Takes an optional URL.
+  */
+  public function __construct($url='')
+  {
+    $this->url = $url;
+  }
+
+  /*!
+    Wrapper around filter_input() that stores the result in the ::$data field.
+   */
+  public function filter($type, $name, $filter=FILTER_SANITIZE_STRING, $options=NULL)
+  {
+    $rv = filter_input($type, $name, $filter, $options);
+    $this->data[$name] = $rv;
+    return $rv;
+  }
+
+  /*!
+    Wrapper around filter_input() that merges the result in the ::$data field.
+   */
+  public function filterArray($type, $definition, $add_empty=TRUE)
+  {
+    $rv = filter_input_array($type, $definition, $add_empty);
+    $this->data = array_merge($this->data, $rv);
+    return $rv;
+  }
+}
diff --git a/http2/response.php b/http2/response.php
new file mode 100644 (file)
index 0000000..f29f5f8
--- /dev/null
@@ -0,0 +1,66 @@
+<?php
+// Hoplite
+// Copyright (c) 2016 Blue Static
+//
+// This program is free software: you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation, either version 3 of the License, or any later version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+// more details.
+//
+// You should have received a copy of the GNU General Public License along with
+// this program.  If not, see <http://www.gnu.org/licenses/>.
+
+namespace hoplite\http2;
+
+require_once HOPLITE_ROOT . '/http/response_code.php';
+
+class Response
+{
+  /*! @var integer The HTTP response code to return. */
+  public $code;
+
+  /*! @var array A map of headers to values to be sent with the response. */
+  public $headers = [];
+
+  public function __construct($code=\hoplite\http\ResponseCode::OK) {
+    $this->code = $code;
+  }
+
+  /*! @var string Raw HTTP response body. */
+  public function generate() {
+    http_response_code($this->code);
+    foreach ($this->headers as $header => $value) {
+      header("$header: $value");
+    }
+  }
+}
+
+class NotFoundResponse extends Response {
+  public function __construct() {
+    parent::__construct(\hoplite\http\ResponseCode::NOT_FOUND);
+  }
+
+  public function generate() {
+    parent::generate();
+    print '<h1>404 - Not Found</h1>';
+  }
+}
+
+class TextResponse extends Response {
+  private $text;
+
+  public function __construct($text) {
+    parent::__construct();
+    $this->text = $text;
+    $this->headers['Content-Type'] = 'text/plain';
+  }
+
+  public function generate() {
+    parent::generate();
+    print $this->text;
+  }
+}