Add .ini for configuration

This commit is contained in:
Joop Schilder 2020-06-20 19:04:10 +02:00
parent 906fe39e39
commit 744373e584
10 changed files with 92 additions and 63 deletions

1
.gitignore vendored
View File

@ -1,4 +1,3 @@
/vendor/
/.idea/ /.idea/
/var/* /var/*
!/var/.gitignore !/var/.gitignore

View File

@ -3,10 +3,9 @@
This is a simple fileserver written in PHP (7.4+) using [ZeroMQ](https://zeromq.org/). This is a simple fileserver written in PHP (7.4+) using [ZeroMQ](https://zeromq.org/).
There are three executable files: There are three executable files:
- `$ bin/server` runs the fileserver
- `$ ./server` runs the fileserver - `$ bin/command <arg1> <arg2> ...` sends a command to the server
- `$ ./command <arg1> <arg2> ...` sends a command to the server - `$ bin/query <arg1> <arg2> ...` sends a query to the server and returns the response
- `$ ./query <arg1> <arg2> ...` sends a query to the server and returns the response
## Commands ## Commands
@ -15,9 +14,8 @@ There are three executable files:
- `DELETE_ALL <namespace>` deletes an entire namespace - `DELETE_ALL <namespace>` deletes an entire namespace
To save a file to the fileserver you can do: To save a file to the fileserver you can do:
`$ ./command SAVE my-project-namespace 1.html "<!DOCTYPE html><html>...</html>"` `$ bin/command SAVE my-project-namespace 1.html "<!DOCTYPE html><html>...</html>"`
`$ ./command SAVE my-project-namespace 2.xml "$(cat some/xml/file)"` `$ bin/command SAVE my-project-namespace 2.xml "$(cat some/xml/file)"`
## Queries ## Queries
@ -25,5 +23,8 @@ To save a file to the fileserver you can do:
- `LOAD <namespace> <name>` returns the file contents if it exists, else `-1` - `LOAD <namespace> <name>` returns the file contents if it exists, else `-1`
To load a file from the fileserver you can do: To load a file from the fileserver you can do:
`$ ./query LOAD my-project-namespace 1.html` `$ bin/query LOAD my-project-namespace 1.html`
## Configuration
See `config/config.ini`. It contains an example configuration.

13
bin/command Executable file
View File

@ -0,0 +1,13 @@
#!/usr/bin/env php
<?php
array_shift($argv);
if (count($argv) === 0) {
print('command needs at least one argument' . PHP_EOL);
die(1);
}
$ini = parse_ini_file(__DIR__ . '/../config/config.ini', true)['command'];
$socket = new ZMQSocket(new ZMQContext, ZMQ::SOCKET_PUSH);
$socket->connect($ini['dsn']);
$socket->sendmulti($argv);

15
bin/query Executable file
View File

@ -0,0 +1,15 @@
#!/usr/bin/env php
<?php
array_shift($argv);
if (count($argv) === 0) {
print('query needs at least one argument' . PHP_EOL);
die(1);
}
$ini = parse_ini_file(__DIR__ . '/../config/config.ini', true)['query'];
$socket = new ZMQSocket(new ZMQContext, ZMQ::SOCKET_REQ);
$socket->connect($ini['dsn']);
$socket->sendmulti($argv);
print(implode(PHP_EOL, $socket->recvMulti()));
print(PHP_EOL);

10
bin/server Executable file
View File

@ -0,0 +1,10 @@
#!/usr/bin/env php
<?php
require_once __DIR__ . '/../src/FileServer.php';
$ini = parse_ini_file(__DIR__ . '/../config/config.ini', true)['server'];
$ini['storage_dir'] = str_replace('%ROOT_DIR%', realpath(__DIR__ . '/..'), $ini['storage_dir']);
$server = new FileServer(new ZMQContext, $ini['storage_dir'], $ini['command_dsn'], $ini['query_dsn']);
$server->run();

13
command
View File

@ -1,13 +0,0 @@
#!/usr/bin/env php
<?php
array_shift($argv);
if (count($argv) === 0) {
print('Command needs at least one argument' . PHP_EOL);
die(1);
}
$context = new ZMQContext();
$socket = new ZMQSocket($context, ZMQ::SOCKET_PUSH);
$socket->connect('ipc:///tmp/storage_server_command.ipc');
$socket->sendmulti($argv);

21
config/config.ini Normal file
View File

@ -0,0 +1,21 @@
[server]
; %ROOT_DIR% is replaced by the actual directory of the package
storage_dir = %ROOT_DIR%/var/storage
; Allow network-wide connections for queries
query_dsn[] = ipc:///tmp/storage_server_query.ipc
query_dsn[] = tcp://0.0.0.0:5678
; Allow local connections for commands
command_dsn[] = ipc:///tmp/storage_server_command.ipc
command_dsn[] = tcp://127.0.0.1:5679
[command]
; Connect through IPC socket
dsn = ipc:///tmp/storage_server_command.ipc
[query]
; Connect through TCP socket
dsn = tcp://localhost:5678

15
query
View File

@ -1,15 +0,0 @@
#!/usr/bin/env php
<?php
array_shift($argv);
if (count($argv) === 0) {
print('Query needs at least one argument' . PHP_EOL);
die(1);
}
$context = new ZMQContext();
$socket = new ZMQSocket($context, ZMQ::SOCKET_REQ);
$socket->connect('ipc:///tmp/storage_server_query.ipc');
$socket->sendmulti($argv);
print(implode(PHP_EOL, $socket->recvMulti()));
print(PHP_EOL);

10
server
View File

@ -1,10 +0,0 @@
#!/usr/bin/env php
<?php
require_once __DIR__ . '/src/FileServer.php';
$dir = ($argv[1] ?? __DIR__ . '/var/documents');
$commandEndpoints = ['ipc:///tmp/storage_server_command.ipc'];
$queryEndpoints = ['ipc:///tmp/storage_server_query.ipc'];
(new FileServer($dir, new ZMQContext(), $commandEndpoints, $queryEndpoints))->run();

View File

@ -1,6 +1,5 @@
<?php <?php
class FileServer class FileServer
{ {
private ZMQSocket $commandSocket; private ZMQSocket $commandSocket;
@ -8,11 +7,11 @@ class FileServer
private ZMQPoll $poll; private ZMQPoll $poll;
private string $rootDirectory; private string $rootDirectory;
public function __construct(string $rootDirectory, ZMQContext $context, array $commandDsns, array $queryDsns) public function __construct(ZMQContext $context, string $rootDirectory, array $commandDSNs, array $queryDSNs)
{ {
$this->initializeCommandSocket($context, $commandDSNs);
$this->initializeQuerySocket($context, $queryDSNs);
$this->initializeRootDirectory($rootDirectory); $this->initializeRootDirectory($rootDirectory);
$this->initializeCommandSocket($context, $commandDsns);
$this->initializeQuerySocket($context, $queryDsns);
$this->initializePoll(); $this->initializePoll();
} }
@ -22,28 +21,31 @@ class FileServer
if (!is_dir($this->rootDirectory)) { if (!is_dir($this->rootDirectory)) {
mkdir($this->rootDirectory, 0777, true); mkdir($this->rootDirectory, 0777, true);
} }
if (!is_dir($this->rootDirectory)) {
throw new RuntimeException("Unable to create directory '{$this->rootDirectory}'");
}
} }
private function initializeCommandSocket(ZMQContext $context, array $bindings): void private function initializeCommandSocket(ZMQContext $context, array $commandDSNs): void
{ {
if (count($bindings) === 0) { if (count($commandDSNs) === 0) {
throw new InvalidArgumentException("At least one binding required"); throw new InvalidArgumentException("At least one binding required");
} }
$this->commandSocket = new ZMQSocket($context, ZMQ::SOCKET_PULL); $this->commandSocket = new ZMQSocket($context, ZMQ::SOCKET_PULL);
$this->commandSocket->setSockOpt(ZMQ::SOCKOPT_HWM, 5); $this->commandSocket->setSockOpt(ZMQ::SOCKOPT_HWM, 5);
foreach ($bindings as $dsn) { foreach ($commandDSNs as $dsn) {
$this->commandSocket->bind($dsn); $this->commandSocket->bind($dsn);
} }
} }
private function initializeQuerySocket(ZMQContext $context, array $bindings): void private function initializeQuerySocket(ZMQContext $context, array $queryDSNs): void
{ {
if (count($bindings) === 0) { if (count($queryDSNs) === 0) {
throw new InvalidArgumentException("At least one binding required"); throw new InvalidArgumentException("At least one binding required");
} }
$this->querySocket = new ZMQSocket($context, ZMQ::SOCKET_REP); $this->querySocket = new ZMQSocket($context, ZMQ::SOCKET_REP);
$this->querySocket->setSockOpt(ZMQ::SOCKOPT_HWM, 5); $this->querySocket->setSockOpt(ZMQ::SOCKOPT_HWM, 5);
foreach ($bindings as $dsn) { foreach ($queryDSNs as $dsn) {
$this->querySocket->bind($dsn); $this->querySocket->bind($dsn);
} }
} }
@ -75,7 +77,7 @@ class FileServer
case 'LOAD': case 'LOAD':
if (count($arguments) !== 2) { if (count($arguments) !== 2) {
$this->querySocket->send(-1); $this->querySocket->send(-1);
print("Malformed LOAD query\n"); $this->onNotEnoughArguments($query, 2, count($arguments));
return; return;
} }
$this->querySocket->send(@file_get_contents("{$this->rootDirectory}/{$arguments[0]}/{$arguments[1]}") ?: -1); $this->querySocket->send(@file_get_contents("{$this->rootDirectory}/{$arguments[0]}/{$arguments[1]}") ?: -1);
@ -83,7 +85,7 @@ class FileServer
case 'CONTAINS': case 'CONTAINS':
if (count($arguments) !== 2) { if (count($arguments) !== 2) {
$this->querySocket->send(-1); $this->querySocket->send(-1);
print("Malformed CONTAINS query\n"); $this->onNotEnoughArguments($query, 2, count($arguments));
return; return;
} }
$this->querySocket->send(file_exists("{$this->rootDirectory}/{$arguments[0]}/{$arguments[1]}") ? 'Y' : 'N'); $this->querySocket->send(file_exists("{$this->rootDirectory}/{$arguments[0]}/{$arguments[1]}") ? 'Y' : 'N');
@ -102,21 +104,22 @@ class FileServer
switch ($command) { switch ($command) {
case 'SAVE': case 'SAVE':
if (count($arguments) !== 3) { if (count($arguments) !== 3) {
print("Malformed SAVE command\n"); $this->onNotEnoughArguments($command, 3, count($arguments));
return; return;
} }
$this->saveFile($arguments[0], $arguments[1], $arguments[2]); $this->saveFile($arguments[0], $arguments[1], $arguments[2]);
break; break;
case 'DELETE': case 'DELETE':
if (count($arguments) !== 2) { if (count($arguments) !== 2) {
print("Malformed DELETE command\n"); $this->onNotEnoughArguments($command, 2, count($arguments));
return; return;
} }
$this->deleteFile($arguments[0], $arguments[1]); $this->deleteFile($arguments[0], $arguments[1]);
break; break;
case 'DELETE_ALL': case 'DELETE_ALL':
if (count($arguments) !== 1) { if (count($arguments) !== 1) {
print("Malformed DELETE_ALL command\n"); $this->onNotEnoughArguments($command, 1, count($arguments));
return;
} }
$this->deleteAll($arguments[0]); $this->deleteAll($arguments[0]);
break; break;
@ -126,10 +129,15 @@ class FileServer
} }
} }
private function onNotEnoughArguments(string $input, int $expected, int $actual): void
{
printf(date('[H:i:s]') . " Error: unexpected amount of arguments for input '%s' (%d/%d)\n", $input, $actual, $expected);
}
private function onUnknownInput(string $input, array $arguments): void private function onUnknownInput(string $input, array $arguments): void
{ {
$arguments = array_map(fn($v) => substr($v, 0, 12), $arguments); $arguments = array_map(fn($v) => substr($v, 0, 12), $arguments);
printf("Unknown input '%s' with args (%s)\n", $input, implode(', ', $arguments)); printf(date('[H:i:s]') . " Error: unknown input '%s' with arguments [%s]\n", $input, implode(', ', $arguments));
} }
private function saveFile(string &$namespace, string &$name, string &$content): void private function saveFile(string &$namespace, string &$name, string &$content): void