Completed
Push — master ( 0a059e...292010 )
by John
02:21
created

HTTPServer   A

Complexity

Total Complexity 17

Size/Duplication

Total Lines 121
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 8

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 17
lcom 1
cbo 8
dl 0
loc 121
ccs 60
cts 60
cp 1
rs 10
c 0
b 0
f 0

5 Methods

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