Add proper response handling for serving files

This commit is contained in:
Jansen Price 2020-08-27 01:37:46 -05:00
parent 213e548593
commit 4083638ab0
6 changed files with 178 additions and 4 deletions

View file

@ -13,6 +13,7 @@ $args = new \Qi_Console_ArgV(
'hostname:' => 'Set hostname of server',
'tls-cert:' => 'Set cert PEM file to use (default null)',
'tls-key:' => 'Set private key PEM file to use (default null)',
'root-dir:' => 'Set the file root directory',
'log:' => 'Set log filename (default orbit.log)',
'help|h' => 'Show help',
'verbose|v' => 'Include more verbose output',

View file

@ -15,4 +15,6 @@ class Config
public $quiet = false;
public $verbose = false;
public $root_dir = ".";
}

View file

@ -10,6 +10,10 @@ class Console extends \Qi_Console_Client
{
public function execute()
{
if ($this->_args->get('no-color')) {
$this->_terminal->setIsatty(false);
}
if ($this->_args->version) {
$this->showVersion();
return 0;
@ -59,6 +63,10 @@ class Console extends \Qi_Console_Client
$config->verbose = $this->_args->verbose;
}
if ($this->_args->get("root-dir")) {
$config->root_dir = $this->_args->get("root-dir");
}
if ($this->_args->get("tls-cert")) {
$config->tls_certfile = $this->_args->get("tls-cert");
} else {

View file

@ -20,7 +20,20 @@ class Request
$data = parse_url($request_input);
foreach ($data as $key => $value) {
$this->{$key} = $value;
$this->{$key} = urldecode($value);
}
}
public function getUrlAppendPath($text)
{
return $this->scheme . '://'
. ($this->user ? $this->user : '')
. ($this->pass ? ':' . $this->pass : '')
. ($this->user ? '@' : '')
. $this->host
. ($this->port ? ':' . $this->port : '')
. $this->path . $text
. ($this->query ? '?' . $this->query : '')
. ($this->fragment ? '#' . $this->fragment : '');
}
}

94
src/Orbit/Response.php Normal file
View file

@ -0,0 +1,94 @@
<?php
namespace Orbit;
class Response
{
const STATUS_INPUT = 10;
const STATUS_SENSITIVE_INPUT = 11;
const STATUS_SUCCESS = 20;
const STATUS_REDIRECT_TEMPORARY = 30;
const STATUS_REDIRECT_PERMANENT = 31;
const STATUS_TEMPORARY_FAILURE = 40;
const STATUS_SERVER_UNAVAILABLE = 41;
const STATUS_CGI_ERROR = 42;
const STATUS_PROXY_ERROR = 43;
const STATUS_SLOW_DOWN = 44;
const STATUS_PERMANENT_FAILURE = 50;
const STATUS_NOT_FOUND = 51;
const STATUS_GONE = 52;
const STATUS_PROXY_REQUEST_REFUSED = 53;
const STATUS_BAD_REQUEST = 59;
const STATUS_CLIENT_CERTIFICATE_REQUIRED = 60;
const STATUS_CERTIFICATE_NOT_AUTHORISED = 61;
const STATUS_CERTIFICATE_NOT_VALID = 62;
public $status = "";
public $meta = "";
public $body = "";
public $filepath;
public function __construct($status = "", $meta = "")
{
$this->status = $status;
$this->meta = $meta;
}
public function getHeader()
{
return sprintf("%s %s\r\n", $this->status, $this->meta);
}
public function send($client)
{
fwrite($client, $this->getHeader());
if ($this->filepath) {
$size = filesize($this->filepath);
$fp = fopen($this->filepath, "rb");
if (false === $fp) {
throw new \Exception("Error reading file '{$this->filepath}'");
}
while (!feof($fp)) {
fwrite($client, fread($fp, 8192));
}
return $size;
} else {
$body = $this->getBody();
fwrite($client, $body);
return mb_strlen($body);
}
}
public function setBody($body = '')
{
$this->body = $body;
return $this;
}
public function getBody()
{
if ($this->filepath) {
$this->body = file_get_contents($this->filepath);
}
return $this->body;
}
public function setStaticFile($filepath)
{
$this->filepath = $filepath;
return $this;
}
public function setStatus($status)
{
$this->status = $status;
return $this;
}
public function setMeta($meta)
{
$this->meta = $meta;
return $this;
}
}

View file

@ -62,8 +62,16 @@ class Server
return $this->logger;
}
public function listen()
public function listen($root_dir = ".")
{
$path = realpath($root_dir);
if (!is_dir($path)) {
throw new \Exception("Error: Root directory '$path' not a directory");
}
$this->log(Logger::DEBUG, "Root directory '$path'");
$server = stream_socket_server(
$this->getListenAddress(),
$errno, $errstr,
@ -86,20 +94,68 @@ class Server
restore_error_handler();
if ($client) {
$time = ['start' => microtime(true)];
$this->log(Logger::DEBUG, "$client_name Accepted");
$request_buffer = stream_get_line($client, 1024, "\r\n");
$this->log(Logger::INFO, "REQ: $request_buffer", ["client" => $client_name]);
$request = new Request($request_buffer);
// Respond to client
$response = implode("\r\n", ['20 text/gemini', "Thanks for all the fish\n"]);
fwrite($client, $response);
$response = $this->handleResponse($request, $path);
$size = $response->send($client);
$time['end'] = microtime(true);
$this->log(
Logger::DEBUG,
"RSP: " . trim($response->getHeader()),
['size' => $size, 'time' => $time['end'] - $time['start']]
);
fclose($client);
$this->log(Logger::DEBUG, "$client_name Closed");
}
}
}
public function handleResponse($request, $dir)
{
$resource_path = rtrim($dir, "/") . $request->path;
$response = new Response();
if (is_dir($resource_path)) {
// If missing the final slash, issue a redirect
if ($resource_path[-1] != "/") {
$response->setStatus(Response::STATUS_REDIRECT_PERMANENT);
$response->setMeta($request->getUrlAppendPath('/'));
return $response;
}
// Check if index file exists
if (file_exists($resource_path . DIRECTORY_SEPARATOR . "index.gmi")) {
$resource_path = $resource_path . "/index.gmi";
}
}
if (file_exists($resource_path)) {
$response->setStatus(Response::STATUS_SUCCESS);
$pathinfo = pathinfo($resource_path);
if ($pathinfo['extension'] == 'gmi' || $pathinfo['extension'] == 'gemini') {
$response->setMeta('text/gemini');
} elseif ($pathinfo['extension'] == 'md' || $pathinfo['extension'] == 'markdown') {
$response->setMeta('text/gemini');
} else {
$response->setMeta(mime_content_type($resource_path));
}
$response->setStaticFile($resource_path);
} else {
$response->setStatus(Response::STATUS_NOT_FOUND);
$response->setMeta('Not found!');
}
return $response;
}
public function log($level, $message, $context = [])
{
$this->getLogger()->log($level, $message, $context);