Skip to content

Commit b9474e5

Browse files
Meaningful HTTP Producer responses
- Let the HTTP Producer return with 500 Internal Server error if not (all) jobs could be queued - Allow option to override response code and message for user exceptions
1 parent 522bde2 commit b9474e5

7 files changed

Lines changed: 265 additions & 38 deletions

File tree

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
"require": {
1313
"php": "~7.2.0|~7.3.0|~7.4.0",
1414
"ext-pcntl": "*",
15+
"ext-json": "*",
1516
"symfony/dependency-injection": "^3.3",
1617
"symfony/config": "^3.3",
1718
"symfony/yaml": "^3.3",
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Webgriffe\Esb\Exception;
6+
7+
class HttpResponseException extends \Exception
8+
{
9+
/**
10+
* @var int
11+
*/
12+
private $httpResponseCode;
13+
14+
/**
15+
* @var string
16+
*/
17+
private $clientMessage;
18+
19+
/**
20+
* @param int $httpResponseCode The HTTP response code to use
21+
* @param string $clientMessage The message to send to the client
22+
* @param string $internalMessage The message to use internally (e.g. store in error logs), when empty the client message is used
23+
* @param int $code The Exception code.
24+
* @param \Throwable|null $previous The previous throwable used for the exception chaining.
25+
*/
26+
public function __construct(
27+
int $httpResponseCode,
28+
string $clientMessage,
29+
string $internalMessage = "",
30+
int $code = 0,
31+
?\Throwable $previous = null
32+
) {
33+
$this->httpResponseCode = $httpResponseCode;
34+
$this->clientMessage = $clientMessage;
35+
parent::__construct($internalMessage ?: $clientMessage, $code, $previous);
36+
}
37+
38+
/**
39+
* @return int
40+
*/
41+
public function getHttpResponseCode(): int
42+
{
43+
return $this->httpResponseCode;
44+
}
45+
46+
/**
47+
* @return string
48+
*/
49+
public function getClientMessage(): string
50+
{
51+
return $this->clientMessage;
52+
}
53+
}

src/ProducerInstance.php

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,9 @@ function ($watcherId) {
164164
public function produceAndQueueJobs($data = null): Promise
165165
{
166166
return call(function () use ($data) {
167-
$jobsCount = 0;
167+
$flushedJobsCount = 0;
168+
$queuedJobsCount = 0;
169+
$caughtException = null;
168170
$job = null;
169171
$test = false;
170172
try {
@@ -173,7 +175,8 @@ public function produceAndQueueJobs($data = null): Promise
173175
/** @var Job $job */
174176
$job = $jobs->getCurrent();
175177
$job->addEvent(new ProducedJobEvent(new \DateTime(), \get_class($this->producer)));
176-
$jobsCount += yield $this->queueManager->enqueue($job);
178+
$flushedJobsCount += yield $this->queueManager->enqueue($job);
179+
$queuedJobsCount++;
177180
$this->logger->info(
178181
'Successfully produced a new Job',
179182
[
@@ -184,8 +187,9 @@ public function produceAndQueueJobs($data = null): Promise
184187
);
185188
}
186189

187-
$jobsCount += yield $this->queueManager->flush();
190+
$flushedJobsCount += yield $this->queueManager->flush();
188191
} catch (\Throwable $error) {
192+
$caughtException = $error;
189193
$this->logger->error(
190194
'An error occurred producing/queueing jobs.',
191195
[
@@ -195,8 +199,18 @@ public function produceAndQueueJobs($data = null): Promise
195199
'test' => $test
196200
]
197201
);
202+
203+
// At least try to flush any previously successfully queued jobs. Don't let an error in parsing job 3
204+
// details also fail jobs 1 and 2.
205+
if ($queuedJobsCount > $flushedJobsCount) {
206+
try {
207+
$flushedJobsCount += yield $this->queueManager->flush();
208+
} catch (\Throwable $nestedError) {
209+
// Ignore any further (duplicated) errors
210+
}
211+
}
198212
}
199-
return $jobsCount;
213+
return new ProducerResult($flushedJobsCount, $caughtException);
200214
});
201215
}
202216

src/ProducerResult.php

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Webgriffe\Esb;
6+
7+
class ProducerResult
8+
{
9+
/**
10+
* @var int
11+
*/
12+
private $jobsCount;
13+
14+
/**
15+
* @var \Throwable|null
16+
*/
17+
private $exception;
18+
19+
/**
20+
* @param int $jobsCount
21+
* @param \Throwable|null $exception
22+
*/
23+
public function __construct(int $jobsCount, ?\Throwable $exception = null)
24+
{
25+
$this->jobsCount = $jobsCount;
26+
$this->exception = $exception;
27+
}
28+
29+
/**
30+
* @return int
31+
*/
32+
public function getJobsCount(): int
33+
{
34+
return $this->jobsCount;
35+
}
36+
37+
/**
38+
* @return \Throwable|null
39+
*/
40+
public function getException(): ?\Throwable
41+
{
42+
return $this->exception;
43+
}
44+
}

src/Service/HttpProducersServer.php

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,11 @@
1414
use Amp\Socket;
1515
use Psr\Log\LoggerInterface;
1616
use Psr\Log\NullLogger;
17+
use Webgriffe\Esb\Exception\HttpResponseException;
1718
use Webgriffe\Esb\HttpRequestProducerInterface;
1819
use Webgriffe\Esb\ProducerInstance;
20+
use Webgriffe\Esb\ProducerResult;
21+
1922
use function Amp\call;
2023

2124
/**
@@ -59,7 +62,7 @@ public function start(): Promise
5962
Socket\listen("[::]:{$this->port}"),
6063
];
6164

62-
$this->httpServer = new \Amp\Http\Server\Server(
65+
$this->httpServer = new Server(
6366
$sockets,
6467
new CallableRequestHandler($this->callableFromInstanceMethod('requestHandler')),
6568
new NullLogger()
@@ -105,9 +108,37 @@ private function requestHandler(Request $request)
105108
'request' => sprintf('%s %s', strtoupper($request->getMethod()), $request->getUri())
106109
]
107110
);
108-
$jobsCount = yield $producerInstance->produceAndQueueJobs($request);
109-
$responseMessage = sprintf('Successfully scheduled %s job(s) to be queued.', $jobsCount);
110-
return new Response(Status::OK, [], sprintf('"%s"', $responseMessage));
111+
$producerResult = yield $producerInstance->produceAndQueueJobs($request);
112+
return $this->buildResponse($producerResult);
113+
}
114+
115+
/**
116+
* @param ProducerResult $producerResult
117+
* @return Response
118+
*/
119+
private function buildResponse(ProducerResult $producerResult): Response
120+
{
121+
$producerException = $producerResult->getException();
122+
if ($producerException === null) {
123+
$responseCode = Status::OK;
124+
$responseMessage = sprintf('Successfully scheduled %d job(s) to be queued.', $producerResult->getJobsCount());
125+
} else {
126+
$responseCode = Status::INTERNAL_SERVER_ERROR;
127+
$errorMessage = 'Internal server error';
128+
129+
if ($producerException instanceof HttpResponseException) {
130+
$responseCode = $producerException->getHttpResponseCode();
131+
$errorMessage = $producerException->getClientMessage();
132+
}
133+
134+
if ($producerResult->getJobsCount() === 0) {
135+
$responseMessage = sprintf('%s, could not schedule any jobs.', $errorMessage);
136+
} else {
137+
$responseMessage = sprintf('%s, only scheduled the first %d job(s) to be queued.', $errorMessage, $producerResult->getJobsCount());
138+
}
139+
}
140+
141+
return new Response($responseCode, [], sprintf('"%s"', $responseMessage));
111142
}
112143

113144
/**

tests/DummyHttpRequestProducer.php

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33
namespace Webgriffe\Esb;
44

55
use Amp\Http\Server\Request;
6+
use Amp\Http\Status;
67
use Amp\Iterator;
78
use Amp\Producer;
89
use Amp\Promise;
910
use Amp\Success;
11+
use Webgriffe\Esb\Exception\HttpResponseException;
1012
use Webgriffe\Esb\Model\Job;
1113

1214
final class DummyHttpRequestProducer implements HttpRequestProducerInterface
@@ -45,9 +47,19 @@ public function produce($data = null): Iterator
4547
);
4648
}
4749
$body = json_decode(yield $data->getBody()->read(), true);
50+
if (!is_array($body)) {
51+
throw new HttpResponseException(Status::BAD_REQUEST, 'Request body contains invalid JSON');
52+
}
4853
$jobsData = $body['jobs'];
4954
foreach ($jobsData as $jobData) {
50-
yield $emit(new Job([$jobData]));
55+
switch ($jobData) {
56+
case 'throw http response exception':
57+
throw new HttpResponseException(Status::PRECONDITION_FAILED, 'Some other custom message');
58+
case 'throw other exception':
59+
throw new \Exception('This message shouldn\'t be send to the client');
60+
default:
61+
yield $emit(new Job([$jobData]));
62+
}
5163
}
5264
});
5365
}

0 commit comments

Comments
 (0)