Skip to content

Commit

Permalink
feat: utopia compression
Browse files Browse the repository at this point in the history
  • Loading branch information
loks0n committed Oct 16, 2024
1 parent c4b9303 commit 4b844e8
Show file tree
Hide file tree
Showing 2 changed files with 96 additions and 23 deletions.
17 changes: 17 additions & 0 deletions src/Http/Http.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ class Http extends Base
protected string|null $requestClass = null;
protected string|null $responseClass = null;

protected bool $compression = false;
protected int $compressionLevel = 0;

/**
* Http
*
Expand Down Expand Up @@ -79,6 +82,15 @@ public function setRequestClass(string $requestClass)
$this->requestClass = $requestClass;
}

/**
* Set Compression
*/
public function setCompression(bool $compression, int $compressionLevel = 0)
{
$this->compression = $compression;
$this->compressionLevel = $compressionLevel;
}

/**
* GET
*
Expand Down Expand Up @@ -317,6 +329,11 @@ public function start()

if (!\is_null($this->responseClass)) {
$response = new $this->responseClass($response);

if ($this->compression) {
$response->setAcceptEncoding($request->getHeader('accept-encoding') ?? '');
$response->setCompressionLevel($this->compressionLevel ?? 1);
}
}

$context = clone $this->container;
Expand Down
102 changes: 79 additions & 23 deletions src/Http/Response.php
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,16 @@ abstract class Response
*/
protected int $size = 0;

/**
* @var string
*/
protected string $acceptEncoding = '';

/**
* @var int
*/
protected int $compressionLevel = 0;

/**
* Response constructor.
*
Expand All @@ -270,6 +280,19 @@ public function setContentType(string $type, string $charset = ''): static
return $this;
}

/**
* Set accept encoding
*
* Set HTTP accept encoding header.
*
* @param string $acceptEncoding
*/
public function setAcceptEncoding(string $acceptEncoding): static
{
$this->acceptEncoding = $acceptEncoding;
return $this;
}

/**
* Get content type
*
Expand Down Expand Up @@ -459,6 +482,25 @@ public function getCookies(): array
return $this->cookies;
}

public function getEncoding(): ?string
{
if (empty($this->acceptEncoding || isset($this->compressed[$this->contentType]))) {
return null;
}

if (strpos($this->acceptEncoding, 'br') !== false && function_exists('brotli_compress')) {
return 'br';
}

if (strpos($this->acceptEncoding, 'gzip') !== false && function_exists('gzencode')) {
return 'gzip';
}

if (strpos($this->acceptEncoding, 'deflate') !== false && function_exists('gzdeflate')) {
return 'deflate';
}
}

/**
* Output response
*
Expand All @@ -475,37 +517,51 @@ public function send(string $body = ''): void

$this->sent = true;

$this
->addHeader('Server', array_key_exists('Server', $this->headers) ? $this->headers['Server'] : 'Utopia/Http')
->addHeader('X-Debug-Speed', (string) (\microtime(true) - $this->startTime))
;

$this
->appendCookies()
->appendHeaders();

if (!$this->disablePayload) {
$length = strlen($body);
$serverHeader = $this->headers['Server'] ?? 'Utopia/Http';
$this->addHeader('Server', $serverHeader);
$this->addHeader('X-Debug-Speed', (string) (microtime(true) - $this->startTime));

$this->size = $this->size + strlen(implode("\n", $this->headers)) + $length;
$this->appendCookies()->appendHeaders();

if (array_key_exists(
$this->contentType,
$this->compressed
) && ($length <= self::CHUNK_SIZE)) { // Dont compress with GZIP / Brotli if header is not listed and size is bigger than 2mb
$this->end($body);
} else {
for ($i = 0; $i < ceil($length / self::CHUNK_SIZE); $i++) {
$this->write(substr($body, ($i * self::CHUNK_SIZE), min(self::CHUNK_SIZE, $length - ($i * self::CHUNK_SIZE))));
}
// Send response
if ($this->disablePayload) {
$this->end();
return;
}

$this->end();
// Compress body
$encoding = $this->getEncoding();
if ($encoding) {
switch($encoding) {
case 'br':
$body = brotli_compress($body, $this->compressionLevel);
break;
case 'gzip':
$body = gzencode($body, $this->compressionLevel);
break;
case 'deflate':
$body = gzdeflate($body, $this->compressionLevel);
break;
}
$this->addHeader('Content-Encoding', $encoding);
$this->addHeader('Vary', 'Accept-Encoding');
}

$headerSize = strlen(implode("\n", $this->headers));
$bodyLength = strlen($body);
$this->size += $headerSize + $bodyLength;

$this->disablePayload();
if ($bodyLength <= self::CHUNK_SIZE) {
$this->end($body);
} else {
$chunks = str_split($body, self::CHUNK_SIZE);
foreach ($chunks as $chunk) {
$this->write($chunk);
}
$this->end();
}

$this->disablePayload();
}

/**
Expand Down

0 comments on commit 4b844e8

Please sign in to comment.