Add proper response handling for serving files
This commit is contained in:
parent
213e548593
commit
4083638ab0
|
@ -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',
|
||||
|
|
|
@ -15,4 +15,6 @@ class Config
|
|||
|
||||
public $quiet = false;
|
||||
public $verbose = false;
|
||||
|
||||
public $root_dir = ".";
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
94
src/Orbit/Response.php
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue