Completed
Branch master (5acd24)
by John
02:19
created

HTTPServer::handleAPIRequest()   C

Complexity

Conditions 8
Paths 43

Size

Total Lines 33
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 28
CRAP Score 8

Importance

Changes 0
Metric Value
dl 0
loc 33
ccs 28
cts 28
cp 1
rs 5.3846
c 0
b 0
f 0
cc 8
eloc 29
nc 43
nop 2
crap 8
1
<?php
2
namespace LunixREST;
3
4
use LunixREST\RequestFactory\Exceptions\UnableToCreateRequestException;
5
use LunixREST\Server\APIRequest\APIRequest;
6
use LunixREST\Server\Router\Endpoint\Exceptions\ElementConflictException;
7
use LunixREST\Server\Router\Endpoint\Exceptions\ElementNotFoundException;
8
use LunixREST\Server\Router\Endpoint\Exceptions\InvalidRequestException;
9
use LunixREST\Server\Router\EndpointFactory\Exceptions\UnknownEndpointException;
10
use LunixREST\Server\Exceptions\AccessDeniedException;
11
use LunixREST\Server\Exceptions\InvalidAPIKeyException;
12
use LunixREST\RequestFactory\RequestFactory;
13
use LunixREST\RequestFactory\URLParser\Exceptions\InvalidRequestURLException;
14
use LunixREST\Server\Router\Exceptions\MethodNotFoundException;
15
use LunixREST\Server\Exceptions\ThrottleLimitExceededException;
16
use LunixREST\Server\ResponseFactory\Exceptions\NotAcceptableResponseTypeException;
17
use LunixREST\Server\Server;
18
use Psr\Http\Message\ResponseInterface;
19
use Psr\Http\Message\ServerRequestInterface;
20
use Psr\Log\LoggerAwareTrait;
21
use Psr\Log\LoggerInterface;
22
use Psr\Log\LogLevel;
23
24
/**
25
 * A class that interfaces PSR-7 with our APIRequests and uses a Server to handle the APIRequest. Handles the PSR-7 response building as well.
26
 * Class HTTPServer
27
 * @package LunixREST
28
 */
29
class HTTPServer
30
{
31
    use LoggerAwareTrait;
32
33
    /**
34
     * @var Server
35
     */
36
    protected $server;
37
    /**
38
     * @var RequestFactory
39
     */
40
    private $requestFactory;
41
42
    /**
43
     * HTTPServer constructor.
44
     * @param Server $server
45
     * @param RequestFactory $requestFactory
46
     * @param LoggerInterface $logger
47
     */
48 14
    public function __construct(Server $server, RequestFactory $requestFactory, LoggerInterface $logger)
49
    {
50 14
        $this->server = $server;
51 14
        $this->requestFactory = $requestFactory;
52 14
        $this->logger = $logger;
53 14
    }
54
55
    /**
56
     * Clones a response, changing contents based on the handling of a given request.
57
     * Taking in a response allows us not to define a specific response implementation to create.
58
     * @param ServerRequestInterface $serverRequest
59
     * @param ResponseInterface $response
60
     * @return ResponseInterface
61
     */
62 14
    public function handleRequest(ServerRequestInterface $serverRequest, ResponseInterface $response): ResponseInterface
63
    {
64 14
        $response = $response->withProtocolVersion($serverRequest->getProtocolVersion());
65
66
        try {
67 14
            $APIRequest = $this->requestFactory->create($serverRequest);
68
69 12
            return $this->handleAPIRequest($APIRequest, $response);
70 3
        } catch (UnableToCreateRequestException $e) {
71 1
            $this->logCaughtThrowableResultingInHTTPCode(400, $e, LogLevel::INFO);
72 1
            return $response->withStatus(400, "Bad Request");
73 2
        } catch (\Throwable $e) {
74 2
            $this->logCaughtThrowableResultingInHTTPCode(500, $e, LogLevel::CRITICAL);
75 2
            return $response->withStatus(500, "Internal Server Error");
76
        }
77
    }
78
79
    /**
80
     * Takes an APIRequest and a ResponseInterface and creates a new ResponseInterface derived from the passed implementation and returns it.
81
     * @param APIRequest $APIRequest
82
     * @param ResponseInterface $response
83
     * @return ResponseInterface
84
     */
85 12
    protected function handleAPIRequest(APIRequest $APIRequest, ResponseInterface $response)
86
    {
87
        try {
88 12
            $APIResponse = $this->server->handleRequest($APIRequest);
89
90 2
            $response = $response->withStatus(200, "200 OK");
91 2
            $response = $response->withAddedHeader("Content-Type", $APIResponse->getMIMEType());
92 2
            $response = $response->withAddedHeader("Content-Length", $APIResponse->getAsDataStream()->getSize());
93 2
            $this->logger->debug("Responding to request successfully");
94 2
            return $response->withBody($APIResponse->getAsDataStream());
95 10
        } catch (InvalidRequestException $e) {
96 1
            $this->logCaughtThrowableResultingInHTTPCode(400, $e, LogLevel::INFO);
97 1
            return $response->withStatus(400, "Bad Request");
98 9
        } catch (UnknownEndpointException | ElementNotFoundException $e) {
99 2
            $this->logCaughtThrowableResultingInHTTPCode(404, $e, LogLevel::INFO);
100 2
            return $response->withStatus(404, "Not Found");
101 7
        } catch (NotAcceptableResponseTypeException $e) {
102 1
            $this->logCaughtThrowableResultingInHTTPCode(406, $e, LogLevel::INFO);
103 1
            return $response->withStatus(406, "Not Acceptable");
104 6
        } catch (InvalidAPIKeyException | AccessDeniedException $e) {
105 2
            $this->logCaughtThrowableResultingInHTTPCode(403, $e, LogLevel::NOTICE);
106 2
            return $response->withStatus(403, "Access Denied");
107 4
        } catch (ElementConflictException $e) {
108 1
            $this->logCaughtThrowableResultingInHTTPCode(409, $e, LogLevel::NOTICE);
109 1
            return $response->withStatus(409, "Conflict");
110 3
        } catch (ThrottleLimitExceededException $e) {
111 1
            $this->logCaughtThrowableResultingInHTTPCode(429, $e, LogLevel::WARNING);
112 1
            return $response->withStatus(429, "Too Many Requests");
113 2
        } catch (MethodNotFoundException | \Throwable $e) {
114 2
            $this->logCaughtThrowableResultingInHTTPCode(500, $e, LogLevel::CRITICAL);
115 2
            return $response->withStatus(500, "Internal Server Error");
116
        }
117
    }
118
119
    /**
120
     * Dumps a PSR-7 ResponseInterface to the SAPI.
121
     * @param ResponseInterface $response
122
     */
123 3
    public static function dumpResponse(ResponseInterface $response)
124
    {
125 3
        $statusLine = sprintf(
126 3
            "HTTP/%s %d %s",
127 3
            $response->getProtocolVersion(),
128 3
            $response->getStatusCode(),
129 3
            $response->getReasonPhrase()
130
        );
131
132 3
        header($statusLine, true, $response->getStatusCode());
133
134 3
        foreach ($response->getHeaders() as $name => $values) {
135 1
            foreach ($values as $value) {
136 1
                header(sprintf('%s: %s', $name, $value), false);
137
            }
138
        }
139
140 3
        $body = $response->getBody();
141 3
        while(!$body->eof()) {
142 3
            echo $body->read(1024);
143
        }
144 3
    }
145
146 13
    protected function logCaughtThrowableResultingInHTTPCode(int $code, \Throwable $exception, $level): void
147
    {
148 13
        $this->logger->log($level, "Returning HTTP {code}: {message}", ["code" => $code, "message" => $exception->getMessage()]);
149 13
    }
150
}
151