Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## [Unreleased]

### Changed

- Requires `innmind/foundation:~1.4`

## 2.1.0 - 2024-03-10

### Added
Expand Down
6 changes: 1 addition & 5 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,7 @@
},
"require": {
"php": "~8.2",
"innmind/http": "~7.0",
"innmind/immutable": "~5.2",
"innmind/time-continuum": "~3.2",
"innmind/stream": "~4.0",
"innmind/io": "~2.7"
"innmind/foundation": "~1.4"
},
"autoload": {
"psr-4": {
Expand Down
43 changes: 27 additions & 16 deletions src/Request/Frame/Body.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@
Header\ContentLength,
};
use Innmind\Filesystem\File\Content;
use Innmind\IO\Readable\Frame;
use Innmind\IO\Frame;
use Innmind\Immutable\{
Str,
Maybe,
Sequence,
Pair,
};

final class Body
Expand Down Expand Up @@ -46,22 +47,32 @@ public static function of(Maybe $headers): Frame
*/
private static function bounded(int $length): Frame
{
$accumulated = 0;

/**
* @psalm-suppress MixedOperand
* @var Frame<Sequence<Str>>
*/
return match (\min($length, 8192)) {
$length => Frame\Chunk::of($length)->map(Sequence::of(...)),
default => Frame\Chunks::of(8192)->until(
1,
static function($chunk) use (&$accumulated, $length) {
/**
* @psalm-suppress MixedOperand
* @psalm-suppress MixedAssignment
*/
$accumulated += $chunk->length();

return $accumulated >= $length;
},
),
$length => Frame::chunk($length)->strict()->map(Sequence::of(...)),
default => Frame::sequence(
Frame::chunk(8192)->loose(),
)
->map(
static fn($lines) => $lines
->map(static fn($line) => $line->unwrap()) // todo find a way to not throw
->map(static fn($line) => new Pair(
$line->length(),
$line,
))
->aggregate(static fn(Pair $a, Pair $b) => Sequence::of(
$a,
new Pair(
$a->key() + $b->key(),
$b->value(),
),
))
->takeWhile(static fn($pair) => $pair->key() < $length)
->map(static fn($pair) => $pair->value()),
),
};
}

Expand Down
117 changes: 42 additions & 75 deletions src/Request/Frame/Body/Unbounded.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,98 +3,65 @@

namespace Innmind\HttpParser\Request\Frame\Body;

use Innmind\IO\{
Readable\Frame,
Readable\Frame\Map,
Readable\Frame\Filter,
Readable\Frame\FlatMap,
Exception\FailedToLoadStream,
};
use Innmind\IO\Frame;
use Innmind\Immutable\{
Sequence,
Maybe,
Str,
Monoid\Concat,
};

/**
* @internal
* @implements Frame<Sequence<Str>>
*/
final class Unbounded implements Frame
final class Unbounded
{
private function __construct()
{
}

#[\Override]
public function __invoke(
callable $read,
callable $readLine,
): Maybe {
return Maybe::just(Sequence::lazy(function() use ($read) {
$buffer = Str::of('');

do {
$chunk = $read(8192)->match(
static fn($chunk) => $chunk,
static fn() => throw new FailedToLoadStream,
);

// use prepend to make sure to use the encoding of the chunk and
// not the default utf8 from the initial buffer
$tmpBuffer = $chunk
->prepend($buffer)
->takeEnd(4);
$endReached = $this->end($tmpBuffer);

if ($endReached) {
$chunk = match ($chunk->endsWith("\n\n")) {
true => $chunk->dropEnd(2),
false => $chunk->dropEnd(4),
};
}

yield $chunk;

$buffer = $tmpBuffer;
} while (!$endReached);
}));
}

public static function new(): self
{
return new self;
}

/**
* @psalm-mutation-free
*/
#[\Override]
public function filter(callable $predicate): Frame
{
return Filter::of($this, $predicate);
}

/**
* @psalm-mutation-free
* @return Frame<Sequence<Str>>
*/
#[\Override]
public function map(callable $map): Frame
public static function new(): Frame
{
return Map::of($this, $map);
return Frame::sequence(
Frame::chunk(8192)->loose(),
)
->map(
static fn($chunks) => $chunks
->map(static fn($chunk) => $chunk->unwrap()) // todo find a way to not throw
->flatMap(static fn($chunk) => match ($chunk->empty()) {
true => Sequence::of($chunk),
false => $chunk->chunk(),
})
->windows(4)
->via(self::bound(...))
->aggregate(static fn(Str $a, Str $b) => match (true) {
$a->length() < 8192 => Sequence::of($a->append($b)),
default => Sequence::of($a, $b),
}),
);
}

/**
* @psalm-mutation-free
* @param Sequence<Sequence<Str>> $chunks
*
* @return Sequence<Str>
*/
#[\Override]
public function flatMap(callable $map): Frame
private static function bound(Sequence $chunks): Sequence
{
return FlatMap::of($this, $map);
}
$end = Str::of('');

private function end(Str $buffer): bool
{
return $buffer->empty() || $buffer->equals(Str::of("\r\n\r\n")) || $buffer->takeEnd(2)->equals(Str::of("\n\n"));
return $chunks
->flatMap(static fn($chunk) => match (true) {
$chunk
->fold(new Concat)
->empty() => Sequence::of($end),
$chunk
->fold(new Concat)
->equals(Str::of("\r\n\r\n")) => Sequence::of($end),
$chunk
->drop(2)
->fold(new Concat)
->equals(Str::of("\n\n")) => $chunk->take(2)->add($end),
default => $chunk->take(1),
})
->takeWhile(static fn($chunk) => $chunk !== $end);
}
}
4 changes: 2 additions & 2 deletions src/Request/Frame/FirstLine.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
ProtocolVersion,
};
use Innmind\Url\Url;
use Innmind\IO\Readable\Frame;
use Innmind\IO\Frame;
use Innmind\Immutable\{
Str,
Maybe,
Expand All @@ -22,7 +22,7 @@ final class FirstLine
*/
public static function new(): Frame
{
return Frame\Line::new()
return Frame::line()
->map(static fn($line) => $line->trim())
->map(static fn(Str $line) => $line->capture('~^(?<method>[A-Z]+) (?<url>.+) HTTP/(?<protocol>1(\.[01])?)$~'))
->map(static function($parts) {
Expand Down
31 changes: 18 additions & 13 deletions src/Request/Frame/Headers.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
use Innmind\Http\{
Headers as Model,
Header,
Factory\Header\TryFactory,
Factory\Header\Factory,
};
use Innmind\IO\Readable\Frame;
use Innmind\IO\Frame;
use Innmind\Immutable\{
Str,
Maybe,
Expand All @@ -20,12 +20,16 @@ final class Headers
/**
* @return Frame<Maybe<Model>>
*/
public static function of(TryFactory $factory): Frame
public static function of(Factory $factory): Frame
{
return Frame\Sequence::of(
Frame\Line::new()->map(static fn($line) => $line->trim()),
return Frame::sequence(
Frame::line()->map(static fn($line) => $line->trim()),
)
->until(static fn($line) => $line->empty())
->map(
static fn($lines) => $lines
->map(static fn($line) => $line->unwrap()) // todo find a way to not throw
->takeWhile(static fn($line) => !$line->empty()),
)
->map(
static fn($lines) => $lines
->filter(static fn($line) => !$line->empty())
Expand All @@ -35,9 +39,9 @@ public static function of(TryFactory $factory): Frame
}

/**
* @return Maybe<Header>
* @return Maybe<Header|Header\Custom>
*/
private static function parse(Str $line, TryFactory $factory): Maybe
private static function parse(Str $line, Factory $factory): Maybe
{
$captured = $line->capture('~^(?<name>[a-zA-Z0-9\-\_\.]+): (?<value>.*)$~');

Expand All @@ -47,15 +51,16 @@ private static function parse(Str $line, TryFactory $factory): Maybe
}

/**
* @param Sequence<Maybe<Header>> $headers
* @param Sequence<Maybe<Header|Header\Custom>> $headers
*
* @return Maybe<Model>
*/
private static function build(Sequence $headers): Maybe
{
return $headers->match(
static fn($header, $rest) => Maybe::all($header, ...$rest->toList())->map(Model::of(...)),
static fn() => Maybe::just(Model::of()),
);
return $headers
->sink(Model::of())
->maybe(static fn($headers, $header) => $header->map(
$headers,
));
}
}
22 changes: 11 additions & 11 deletions src/Request/Parse.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,15 @@
Method,
ProtocolVersion,
Headers as HttpHeaders,
Factory\Header\TryFactory,
Factory\Header\Factories,
Factory\Header\Factory,
};
use Innmind\TimeContinuum\Clock;
use Innmind\Filesystem\File\Content;
use Innmind\Url\Url;
use Innmind\IO\{
Readable\Stream,
Readable\Frame,
Sockets\Client,
Streams\Stream\Read,
Sockets\Clients\Client,
Frame,
};
use Innmind\Immutable\{
Maybe,
Expand All @@ -31,19 +30,19 @@

final class Parse
{
private TryFactory $factory;
private Factory $factory;

private function __construct(TryFactory $factory)
private function __construct(Factory $factory)
{
$this->factory = $factory;
}

/**
* @return Maybe<Request>
*/
public function __invoke(Stream|Client $stream): Maybe
public function __invoke(Read|Client $stream): Maybe
{
$frame = Frame\Composite::of(
$frame = Frame::compose(
self::build(...),
FirstLine::new(),
Headers::of($this->factory)->flatMap(
Expand All @@ -57,17 +56,18 @@ public function __invoke(Stream|Client $stream): Maybe
->toEncoding(Str\Encoding::ascii)
->frames($frame)
->one()
->maybe()
->flatMap(static fn($request) => $request);
}

public static function of(TryFactory $factory): self
public static function of(Factory $factory): self
{
return new self($factory);
}

public static function default(Clock $clock): self
{
return new self(new TryFactory(Factories::default($clock)));
return new self(Factory::new($clock));
}

/**
Expand Down
Loading
Loading