Add command, query, stable server with argument parsing
This commit is contained in:
parent
3b8438f9e7
commit
ae7f92d51d
6
.gitignore
vendored
6
.gitignore
vendored
@ -1,6 +1,4 @@
|
|||||||
/vendor/
|
/vendor/
|
||||||
/.idea/
|
/.idea/
|
||||||
|
/var/*
|
||||||
/out/
|
!/var/.gitignore
|
||||||
/documents/
|
|
||||||
|
|
||||||
|
13
command
Executable file
13
command
Executable 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
$context = new ZMQContext();
|
||||||
|
$socket = new ZMQSocket($context, ZMQ::SOCKET_PUSH);
|
||||||
|
$socket->connect('ipc:///tmp/storage_server_command.ipc');
|
||||||
|
$socket->sendmulti($argv);
|
@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "joopschilder/zmq-fileserver",
|
|
||||||
"authors": [
|
|
||||||
{
|
|
||||||
"name": "Joop Schilder",
|
|
||||||
"email": "jnmschilder@protonmail.com"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"require": {
|
|
||||||
"fzaninotto/faker": "^1.9"
|
|
||||||
}
|
|
||||||
}
|
|
69
composer.lock
generated
69
composer.lock
generated
@ -1,69 +0,0 @@
|
|||||||
{
|
|
||||||
"_readme": [
|
|
||||||
"This file locks the dependencies of your project to a known state",
|
|
||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
|
||||||
"This file is @generated automatically"
|
|
||||||
],
|
|
||||||
"content-hash": "541af5d4dea7e6b160f304dd7ba3073f",
|
|
||||||
"packages": [
|
|
||||||
{
|
|
||||||
"name": "fzaninotto/faker",
|
|
||||||
"version": "v1.9.1",
|
|
||||||
"source": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/fzaninotto/Faker.git",
|
|
||||||
"reference": "fc10d778e4b84d5bd315dad194661e091d307c6f"
|
|
||||||
},
|
|
||||||
"dist": {
|
|
||||||
"type": "zip",
|
|
||||||
"url": "https://api.github.com/repos/fzaninotto/Faker/zipball/fc10d778e4b84d5bd315dad194661e091d307c6f",
|
|
||||||
"reference": "fc10d778e4b84d5bd315dad194661e091d307c6f",
|
|
||||||
"shasum": ""
|
|
||||||
},
|
|
||||||
"require": {
|
|
||||||
"php": "^5.3.3 || ^7.0"
|
|
||||||
},
|
|
||||||
"require-dev": {
|
|
||||||
"ext-intl": "*",
|
|
||||||
"phpunit/phpunit": "^4.8.35 || ^5.7",
|
|
||||||
"squizlabs/php_codesniffer": "^2.9.2"
|
|
||||||
},
|
|
||||||
"type": "library",
|
|
||||||
"extra": {
|
|
||||||
"branch-alias": {
|
|
||||||
"dev-master": "1.9-dev"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"autoload": {
|
|
||||||
"psr-4": {
|
|
||||||
"Faker\\": "src/Faker/"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"notification-url": "https://packagist.org/downloads/",
|
|
||||||
"license": [
|
|
||||||
"MIT"
|
|
||||||
],
|
|
||||||
"authors": [
|
|
||||||
{
|
|
||||||
"name": "François Zaninotto"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "Faker is a PHP library that generates fake data for you.",
|
|
||||||
"keywords": [
|
|
||||||
"data",
|
|
||||||
"faker",
|
|
||||||
"fixtures"
|
|
||||||
],
|
|
||||||
"time": "2019-12-12T13:22:17+00:00"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"packages-dev": [],
|
|
||||||
"aliases": [],
|
|
||||||
"minimum-stability": "stable",
|
|
||||||
"stability-flags": [],
|
|
||||||
"prefer-stable": false,
|
|
||||||
"prefer-lowest": false,
|
|
||||||
"platform": [],
|
|
||||||
"platform-dev": [],
|
|
||||||
"plugin-api-version": "1.1.0"
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
#!/usr/bin/env php
|
|
||||||
<?php
|
|
||||||
$argc < 2 and die('Usage: file_generator <namespace>' . PHP_EOL);
|
|
||||||
require_once __DIR__ . '/vendor/autoload.php';
|
|
||||||
$push = (new ZMQContext)->getSocket(ZMQ::SOCKET_PUSH)->connect('ipc:///tmp/storage_server_sink.ipc');
|
|
||||||
$faker = Faker\Factory::create();
|
|
||||||
|
|
||||||
$id = 1;
|
|
||||||
while (true) {
|
|
||||||
$document = file_get_contents("https://twitter.com/{$faker->firstName}");
|
|
||||||
$push->send($argv[1], ZMQ::MODE_SNDMORE)
|
|
||||||
->send($id++ . '.html', ZMQ::MODE_SNDMORE)
|
|
||||||
->send($document);
|
|
||||||
|
|
||||||
sleep(2);
|
|
||||||
}
|
|
15
query
Executable file
15
query
Executable 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
$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);
|
12
request_file
12
request_file
@ -1,12 +0,0 @@
|
|||||||
#!/usr/bin/env php
|
|
||||||
<?php
|
|
||||||
|
|
||||||
$argc < 3 and die('Usage: request_file <namespace> <name>' . PHP_EOL);
|
|
||||||
|
|
||||||
$req = (new ZMQContext)->getSocket(ZMQ::SOCKET_REQ)
|
|
||||||
->connect('ipc:///tmp/storage_server_query.ipc')
|
|
||||||
->send(@$argv[1], ZMQ::MODE_SNDMORE)
|
|
||||||
->send(@$argv[2]);
|
|
||||||
|
|
||||||
print($req->recv());
|
|
||||||
print(PHP_EOL);
|
|
10
save_file
10
save_file
@ -1,10 +0,0 @@
|
|||||||
#!/usr/bin/env php
|
|
||||||
<?php
|
|
||||||
|
|
||||||
$argc < 4 and die('Usage: save_file NAMESPACE NAME CONTENTS' . PHP_EOL);
|
|
||||||
|
|
||||||
(new ZMQContext)
|
|
||||||
->getSocket(ZMQ::SOCKET_PUSH)->connect('ipc:///tmp/storage_server_sink.ipc')
|
|
||||||
->send(@$argv[1], ZMQ::MODE_SNDMORE)
|
|
||||||
->send(@$argv[2], ZMQ::MODE_SNDMORE)
|
|
||||||
->send(@$argv[3]);
|
|
10
server
Executable file
10
server
Executable file
@ -0,0 +1,10 @@
|
|||||||
|
#!/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();
|
||||||
|
|
157
src/FileServer.php
Normal file
157
src/FileServer.php
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
|
||||||
|
class FileServer
|
||||||
|
{
|
||||||
|
private ZMQSocket $commandSocket;
|
||||||
|
private ZMQSocket $querySocket;
|
||||||
|
private ZMQPoll $poll;
|
||||||
|
private string $rootDirectory;
|
||||||
|
|
||||||
|
public function __construct(string $rootDirectory, ZMQContext $context, array $commandDsns, array $queryDsns)
|
||||||
|
{
|
||||||
|
$this->initializeRootDirectory($rootDirectory);
|
||||||
|
$this->initializeCommandSocket($context, $commandDsns);
|
||||||
|
$this->initializeQuerySocket($context, $queryDsns);
|
||||||
|
$this->initializePoll();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function initializeRootDirectory(string $rootDirectory): void
|
||||||
|
{
|
||||||
|
$this->rootDirectory = rtrim($rootDirectory, DIRECTORY_SEPARATOR);
|
||||||
|
if (!is_dir($this->rootDirectory)) {
|
||||||
|
mkdir($this->rootDirectory, 0777, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function initializeCommandSocket(ZMQContext $context, array $bindings): void
|
||||||
|
{
|
||||||
|
if (count($bindings) === 0) {
|
||||||
|
throw new InvalidArgumentException("At least one binding required");
|
||||||
|
}
|
||||||
|
$this->commandSocket = new ZMQSocket($context, ZMQ::SOCKET_PULL);
|
||||||
|
$this->commandSocket->setSockOpt(ZMQ::SOCKOPT_HWM, 5);
|
||||||
|
foreach ($bindings as $dsn) {
|
||||||
|
$this->commandSocket->bind($dsn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function initializeQuerySocket(ZMQContext $context, array $bindings): void
|
||||||
|
{
|
||||||
|
if (count($bindings) === 0) {
|
||||||
|
throw new InvalidArgumentException("At least one binding required");
|
||||||
|
}
|
||||||
|
$this->querySocket = new ZMQSocket($context, ZMQ::SOCKET_REP);
|
||||||
|
$this->querySocket->setSockOpt(ZMQ::SOCKOPT_HWM, 5);
|
||||||
|
foreach ($bindings as $dsn) {
|
||||||
|
$this->querySocket->bind($dsn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function initializePoll(): void
|
||||||
|
{
|
||||||
|
$this->poll = new ZMQPoll();
|
||||||
|
$this->poll->add($this->commandSocket, ZMQ::POLL_IN);
|
||||||
|
$this->poll->add($this->querySocket, ZMQ::POLL_IN);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function run()
|
||||||
|
{
|
||||||
|
$readable = $writable = [];
|
||||||
|
while (true) {
|
||||||
|
$this->poll->poll($readable, $writable);
|
||||||
|
foreach ($readable as $socket) {
|
||||||
|
$socket === $this->querySocket and $this->onQuery();
|
||||||
|
$socket === $this->commandSocket and $this->onCommand();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function onQuery(): void
|
||||||
|
{
|
||||||
|
$arguments = $this->querySocket->recvMulti();
|
||||||
|
$query = array_shift($arguments);
|
||||||
|
switch ($query) {
|
||||||
|
case 'LOAD':
|
||||||
|
if (count($arguments) !== 2) {
|
||||||
|
$this->querySocket->send(-1);
|
||||||
|
print("Malformed LOAD query\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$this->querySocket->send(@file_get_contents("{$this->rootDirectory}/{$arguments[0]}/{$arguments[1]}") ?: -1);
|
||||||
|
break;
|
||||||
|
case 'CONTAINS':
|
||||||
|
if (count($arguments) !== 2) {
|
||||||
|
$this->querySocket->send(-1);
|
||||||
|
print("Malformed CONTAINS query\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$this->querySocket->send(file_exists("{$this->rootDirectory}/{$arguments[0]}/{$arguments[1]}") ? 'Y' : 'N');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$this->querySocket->send(-1);
|
||||||
|
$this->onUnknownInput($query, $arguments);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function onCommand(): void
|
||||||
|
{
|
||||||
|
$arguments = $this->commandSocket->recvMulti();
|
||||||
|
$command = array_shift($arguments);
|
||||||
|
switch ($command) {
|
||||||
|
case 'SAVE':
|
||||||
|
if (count($arguments) !== 3) {
|
||||||
|
print("Malformed SAVE command\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$this->saveFile($arguments[0], $arguments[1], $arguments[2]);
|
||||||
|
break;
|
||||||
|
case 'DELETE':
|
||||||
|
if (count($arguments) !== 2) {
|
||||||
|
print("Malformed DELETE command\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$this->deleteFile($arguments[0], $arguments[1]);
|
||||||
|
break;
|
||||||
|
case 'DELETE_ALL':
|
||||||
|
if (count($arguments) !== 1) {
|
||||||
|
print("Malformed DELETE_ALL command\n");
|
||||||
|
}
|
||||||
|
$this->deleteAll($arguments[0]);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$this->onUnknownInput($command, $arguments);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function onUnknownInput(string $input, array $arguments): void
|
||||||
|
{
|
||||||
|
$arguments = array_map(fn($v) => substr($v, 0, 12), $arguments);
|
||||||
|
printf("Unknown input '%s' with args (%s)\n", $input, implode(', ', $arguments));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function saveFile(string &$namespace, string &$name, string &$content): void
|
||||||
|
{
|
||||||
|
is_dir($targetDir = "{$this->rootDirectory}/$namespace") or mkdir($targetDir, 0777, true);
|
||||||
|
file_put_contents("$targetDir/$name", $content);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function deleteFile(string &$namespace, string &$name): void
|
||||||
|
{
|
||||||
|
@unlink("{$this->rootDirectory}/$namespace/$name");
|
||||||
|
}
|
||||||
|
|
||||||
|
private function deleteAll(string &$namespace): void
|
||||||
|
{
|
||||||
|
$directory = "{$this->rootDirectory}/$namespace";
|
||||||
|
if (!is_dir($directory)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
static $source = '/tmp/zmq.fileserver.rsync_empty_dir.d';
|
||||||
|
is_dir($source) or @mkdir($source);
|
||||||
|
@system("nohup rsync --archive --recursive --delete {$source}/ {$directory}/ 2>&1 >/dev/null &");
|
||||||
|
@rmdir($directory);
|
||||||
|
}
|
||||||
|
}
|
@ -1,36 +0,0 @@
|
|||||||
#!/usr/bin/env php
|
|
||||||
<?php
|
|
||||||
|
|
||||||
is_dir($rootDir = rtrim($argv[1] ?? __DIR__ . '/documents', '/')) or mkdir($rootDir, 0777, true);
|
|
||||||
$zmq = new ZMQContext();
|
|
||||||
$pullSocket = $zmq->getSocket(ZMQ::SOCKET_PULL)->bind('ipc:///tmp/storage_server_sink.ipc');
|
|
||||||
$replySocket = $zmq->getSocket(ZMQ::SOCKET_REP)->bind('ipc:///tmp/storage_server_query.ipc');
|
|
||||||
$poll = new ZMQPoll();
|
|
||||||
$poll->add($pullSocket, ZMQ::POLL_IN);
|
|
||||||
$poll->add($replySocket, ZMQ::POLL_IN);
|
|
||||||
$readable = $writable = [];
|
|
||||||
while (true) {
|
|
||||||
$poll->poll($readable, $writable);
|
|
||||||
foreach ($readable as $socket) {
|
|
||||||
$socket === $pullSocket and handleIncomingFile();
|
|
||||||
$socket === $replySocket and handleIncomingRequest();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleIncomingFile(): void
|
|
||||||
{
|
|
||||||
global $pullSocket, $rootDir;
|
|
||||||
$namespace = $pullSocket->recv(ZMQ::MODE_SNDMORE);
|
|
||||||
$name = $pullSocket->recv(ZMQ::MODE_SNDMORE);
|
|
||||||
$contents = $pullSocket->recv();
|
|
||||||
is_dir($targetDir = "$rootDir/$namespace") or @mkdir($targetDir, 0777, true);
|
|
||||||
file_put_contents("$targetDir/$name", $contents) and print('S');
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleIncomingRequest(): void
|
|
||||||
{
|
|
||||||
global $replySocket, $rootDir;
|
|
||||||
$namespace = $replySocket->recv(ZMQ::MODE_SNDMORE);
|
|
||||||
$name = $replySocket->recv();
|
|
||||||
$replySocket->send(@file_get_contents("$rootDir/{$namespace}/{$name}") ?: 'NOT_FOUND') and print('R');
|
|
||||||
}
|
|
0
var/.gitignore
vendored
Normal file
0
var/.gitignore
vendored
Normal file
Loading…
Reference in New Issue
Block a user