This commit is contained in:
2023-07-26 09:58:35 +08:00
parent 00cd571ffe
commit 329029169a
+369 -362
View File
@@ -4,387 +4,394 @@ declare(strict_types=1);
namespace Kiri\Router\Constrict; namespace Kiri\Router\Constrict;
use Psr\Http\Message\UriInterface; use Psr\Http\Message\UriInterface;
use Swoole\Http\Request;
class Uri implements UriInterface class Uri implements UriInterface
{ {
private string $scheme = ''; private string $scheme = '';
private string $host = ''; private string $host = '';
private int $port = 80; private int $port = 80;
private string $path = ''; private string $path = '';
private string $user = ''; private string $user = '';
private string $password = ''; private string $password = '';
private string $queryString; private string $queryString;
private string $fragment; private string $fragment = '';
/**
* Retrieve the scheme component of the URI.
*
* If no scheme is present, this method MUST return an empty string.
*
* The value returned MUST be normalized to lowercase, per RFC 3986
* Section 3.1.
*
* The trailing ":" character is not part of the scheme and MUST NOT be
* added.
*
* @see https://tools.ietf.org/html/rfc3986#section-3.1
* @return string The URI scheme.
*/
public function getScheme(): string
{
// TODO: Implement getScheme() method.
return $this->scheme;
}
/** /**
* Retrieve the authority component of the URI. * @param Request $request
* */
* If no authority information is present, this method MUST return an empty public function __construct(readonly public Request $request)
* string. {
* }
* The authority syntax of the URI is:
*
* <pre>
* [user-info@]host[:port]
* </pre>
*
* If the port component is not set or is the standard port for the current
* scheme, it SHOULD NOT be included.
*
* @see https://tools.ietf.org/html/rfc3986#section-3.2
* @return string The URI authority, in "[user-info@]host[:port]" format.
*/
public function getAuthority(): string
{
// TODO: Implement getAuthority() method.
return '';
}
/** /**
* Retrieve the user information component of the URI. * Retrieve the scheme component of the URI.
* *
* If no user information is present, this method MUST return an empty * If no scheme is present, this method MUST return an empty string.
* string. *
* * The value returned MUST be normalized to lowercase, per RFC 3986
* If a user is present in the URI, this will return that value; * Section 3.1.
* additionally, if the password is also present, it will be appended to the *
* user value, with a colon (":") separating the values. * The trailing ":" character is not part of the scheme and MUST NOT be
* * added.
* The trailing "@" character is not part of the user information and MUST *
* NOT be added. * @see https://tools.ietf.org/html/rfc3986#section-3.1
* * @return string The URI scheme.
* @return string The URI user information, in "username[:password]" format. */
*/ public function getScheme(): string
public function getUserInfo(): string {
{ // TODO: Implement getScheme() method.
// TODO: Implement getUserInfo() method. if (isset($this->request->server['https']) && $this->request->server['https'] !== 'off') {
return $this->user . '[' . $this->password . ']'; return 'https';
} }
return 'http';
}
/** /**
* Retrieve the host component of the URI. * Retrieve the authority component of the URI.
* *
* If no host is present, this method MUST return an empty string. * If no authority information is present, this method MUST return an empty
* * string.
* The value returned MUST be normalized to lowercase, per RFC 3986 *
* Section 3.2.2. * The authority syntax of the URI is:
* *
* @see http://tools.ietf.org/html/rfc3986#section-3.2.2 * <pre>
* @return string The URI host. * [user-info@]host[:port]
*/ * </pre>
public function getHost(): string *
{ * If the port component is not set or is the standard port for the current
// TODO: Implement getHost() method. * scheme, it SHOULD NOT be included.
return $this->host; *
} * @see https://tools.ietf.org/html/rfc3986#section-3.2
* @return string The URI authority, in "[user-info@]host[:port]" format.
*/
public function getAuthority(): string
{
// TODO: Implement getAuthority() method.
return '';
}
/** /**
* Retrieve the port component of the URI. * Retrieve the user information component of the URI.
* *
* If a port is present, and it is non-standard for the current scheme, * If no user information is present, this method MUST return an empty
* this method MUST return it as an integer. If the port is the standard port * string.
* used with the current scheme, this method SHOULD return null. *
* * If a user is present in the URI, this will return that value;
* If no port is present, and no scheme is present, this method MUST return * additionally, if the password is also present, it will be appended to the
* a null value. * user value, with a colon (":") separating the values.
* *
* If no port is present, but a scheme is present, this method MAY return * The trailing "@" character is not part of the user information and MUST
* the standard port for that scheme, but SHOULD return null. * NOT be added.
* *
* @return null|int The URI port. * @return string The URI user information, in "username[:password]" format.
*/ */
public function getPort(): ?int public function getUserInfo(): string
{ {
// TODO: Implement getPort() method. // TODO: Implement getUserInfo() method.
return $this->port; return $this->user . '[' . $this->password . ']';
} }
/** /**
* Retrieve the path component of the URI. * Retrieve the host component of the URI.
* *
* The path can either be empty or absolute (starting with a slash) or * If no host is present, this method MUST return an empty string.
* rootless (not starting with a slash). Implementations MUST support all *
* three syntaxes. * The value returned MUST be normalized to lowercase, per RFC 3986
* * Section 3.2.2.
* Normally, the empty path "" and absolute path "/" are considered equal as *
* defined in RFC 7230 Section 2.7.3. But this method MUST NOT automatically * @see http://tools.ietf.org/html/rfc3986#section-3.2.2
* do this normalization because in contexts with a trimmed base path, e.g. * @return string The URI host.
* the front controller, this difference becomes significant. It's the task */
* of the user to handle both "" and "/". public function getHost(): string
* {
* The value returned MUST be percent-encoded, but MUST NOT double-encode // TODO: Implement getHost() method.
* any characters. To determine what characters to encode, please refer to return $this->request->server['host'] ?? '127.0.0.1';
* RFC 3986, Sections 2 and 3.3. }
*
* As an example, if the value should include a slash ("/") not intended as
* delimiter between path segments, that value MUST be passed in encoded
* form (e.g., "%2F") to the instance.
*
* @see https://tools.ietf.org/html/rfc3986#section-2
* @see https://tools.ietf.org/html/rfc3986#section-3.3
* @return string The URI path.
*/
public function getPath(): string
{
// TODO: Implement getPath() method.
return $this->path;
}
/** /**
* Retrieve the query string of the URI. * Retrieve the port component of the URI.
* *
* If no query string is present, this method MUST return an empty string. * If a port is present, and it is non-standard for the current scheme,
* * this method MUST return it as an integer. If the port is the standard port
* The leading "?" character is not part of the query and MUST NOT be * used with the current scheme, this method SHOULD return null.
* added. *
* * If no port is present, and no scheme is present, this method MUST return
* The value returned MUST be percent-encoded, but MUST NOT double-encode * a null value.
* any characters. To determine what characters to encode, please refer to *
* RFC 3986, Sections 2 and 3.4. * If no port is present, but a scheme is present, this method MAY return
* * the standard port for that scheme, but SHOULD return null.
* As an example, if a value in a key/value pair of the query string should *
* include an ampersand ("&") not intended as a delimiter between values, * @return null|int The URI port.
* that value MUST be passed in encoded form (e.g., "%26") to the instance. */
* public function getPort(): ?int
* @see https://tools.ietf.org/html/rfc3986#section-2 {
* @see https://tools.ietf.org/html/rfc3986#section-3.4 // TODO: Implement getPort() method.
* @return string The URI query string. return $this->request->server['server_port'];
*/ }
public function getQuery(): string
{
// TODO: Implement getQuery() method.
return $this->queryString;
}
/** /**
* Retrieve the fragment component of the URI. * Retrieve the path component of the URI.
* *
* If no fragment is present, this method MUST return an empty string. * The path can either be empty or absolute (starting with a slash) or
* * rootless (not starting with a slash). Implementations MUST support all
* The leading "#" character is not part of the fragment and MUST NOT be * three syntaxes.
* added. *
* * Normally, the empty path "" and absolute path "/" are considered equal as
* The value returned MUST be percent-encoded, but MUST NOT double-encode * defined in RFC 7230 Section 2.7.3. But this method MUST NOT automatically
* any characters. To determine what characters to encode, please refer to * do this normalization because in contexts with a trimmed base path, e.g.
* RFC 3986, Sections 2 and 3.5. * the front controller, this difference becomes significant. It's the task
* * of the user to handle both "" and "/".
* @see https://tools.ietf.org/html/rfc3986#section-2 *
* @see https://tools.ietf.org/html/rfc3986#section-3.5 * The value returned MUST be percent-encoded, but MUST NOT double-encode
* @return string The URI fragment. * any characters. To determine what characters to encode, please refer to
*/ * RFC 3986, Sections 2 and 3.3.
public function getFragment(): string *
{ * As an example, if the value should include a slash ("/") not intended as
// TODO: Implement getFragment() method. * delimiter between path segments, that value MUST be passed in encoded
return $this->fragment; * form (e.g., "%2F") to the instance.
} *
* @see https://tools.ietf.org/html/rfc3986#section-2
* @see https://tools.ietf.org/html/rfc3986#section-3.3
* @return string The URI path.
*/
public function getPath(): string
{
// TODO: Implement getPath() method.
return $this->request->server['path_info'];
}
/** /**
* Return an instance with the specified scheme. * Retrieve the query string of the URI.
* *
* This method MUST retain the state of the current instance, and return * If no query string is present, this method MUST return an empty string.
* an instance that contains the specified scheme. *
* * The leading "?" character is not part of the query and MUST NOT be
* Implementations MUST support the schemes "http" and "https" case * added.
* insensitively, and MAY accommodate other schemes if required. *
* * The value returned MUST be percent-encoded, but MUST NOT double-encode
* An empty scheme is equivalent to removing the scheme. * any characters. To determine what characters to encode, please refer to
* * RFC 3986, Sections 2 and 3.4.
* @param string $scheme The scheme to use with the new instance. *
* @return static A new instance with the specified scheme. * As an example, if a value in a key/value pair of the query string should
* @throws \InvalidArgumentException for invalid or unsupported schemes. * include an ampersand ("&") not intended as a delimiter between values,
*/ * that value MUST be passed in encoded form (e.g., "%26") to the instance.
public function withScheme(string $scheme): static *
{ * @see https://tools.ietf.org/html/rfc3986#section-2
// TODO: Implement withScheme() method. * @see https://tools.ietf.org/html/rfc3986#section-3.4
$this->scheme = $scheme; * @return string The URI query string.
return $this; */
} public function getQuery(): string
{
// TODO: Implement getQuery() method.
return $request->server['query_string'] ?? '';
}
/** /**
* Return an instance with the specified user information. * Retrieve the fragment component of the URI.
* *
* This method MUST retain the state of the current instance, and return * If no fragment is present, this method MUST return an empty string.
* an instance that contains the specified user information. *
* * The leading "#" character is not part of the fragment and MUST NOT be
* Password is optional, but the user information MUST include the * added.
* user; an empty string for the user is equivalent to removing user *
* information. * The value returned MUST be percent-encoded, but MUST NOT double-encode
* * any characters. To determine what characters to encode, please refer to
* @param string $user The user name to use for authority. * RFC 3986, Sections 2 and 3.5.
* @param null|string $password The password associated with $user. *
* @return static A new instance with the specified user information. * @see https://tools.ietf.org/html/rfc3986#section-2
*/ * @see https://tools.ietf.org/html/rfc3986#section-3.5
public function withUserInfo(string $user, ?string $password = null): static * @return string The URI fragment.
{ */
// TODO: Implement withUserInfo() method. public function getFragment(): string
$this->user = $user; {
$this->password = $password; // TODO: Implement getFragment() method.
return $this; return $this->fragment;
} }
/** /**
* Return an instance with the specified host. * Return an instance with the specified scheme.
* *
* This method MUST retain the state of the current instance, and return * This method MUST retain the state of the current instance, and return
* an instance that contains the specified host. * an instance that contains the specified scheme.
* *
* An empty host value is equivalent to removing the host. * Implementations MUST support the schemes "http" and "https" case
* * insensitively, and MAY accommodate other schemes if required.
* @param string $host The hostname to use with the new instance. *
* @return static A new instance with the specified host. * An empty scheme is equivalent to removing the scheme.
* @throws \InvalidArgumentException for invalid hostnames. *
*/ * @param string $scheme The scheme to use with the new instance.
public function withHost(string $host): static * @return static A new instance with the specified scheme.
{ * @throws \InvalidArgumentException for invalid or unsupported schemes.
// TODO: Implement withHost() method. */
$this->host = $host; public function withScheme(string $scheme): static
return $this; {
} return $this;
}
/** /**
* Return an instance with the specified port. * Return an instance with the specified user information.
* *
* This method MUST retain the state of the current instance, and return * This method MUST retain the state of the current instance, and return
* an instance that contains the specified port. * an instance that contains the specified user information.
* *
* Implementations MUST raise an exception for ports outside the * Password is optional, but the user information MUST include the
* established TCP and UDP port ranges. * user; an empty string for the user is equivalent to removing user
* * information.
* A null value provided for the port is equivalent to removing the port *
* information. * @param string $user The user name to use for authority.
* * @param null|string $password The password associated with $user.
* @param null|int $port The port to use with the new instance; a null value * @return static A new instance with the specified user information.
* removes the port information. */
* @return static A new instance with the specified port. public function withUserInfo(string $user, ?string $password = null): static
* @throws \InvalidArgumentException for invalid ports. {
*/ return $this;
public function withPort(?int $port): static }
{
// TODO: Implement withPort() method.
$this->port = $port;
return $this;
}
/** /**
* Return an instance with the specified path. * Return an instance with the specified host.
* *
* This method MUST retain the state of the current instance, and return * This method MUST retain the state of the current instance, and return
* an instance that contains the specified path. * an instance that contains the specified host.
* *
* The path can either be empty or absolute (starting with a slash) or * An empty host value is equivalent to removing the host.
* rootless (not starting with a slash). Implementations MUST support all *
* three syntaxes. * @param string $host The hostname to use with the new instance.
* * @return static A new instance with the specified host.
* If the path is intended to be domain-relative rather than path relative then * @throws \InvalidArgumentException for invalid hostnames.
* it must begin with a slash ("/"). Paths not starting with a slash ("/") */
* are assumed to be relative to some base path known to the application or public function withHost(string $host): static
* consumer. {
* // TODO: Implement withHost() method.
* Users can provide both encoded and decoded path characters. return $this;
* Implementations ensure the correct encoding as outlined in getPath(). }
*
* @param string $path The path to use with the new instance.
* @return static A new instance with the specified path.
* @throws \InvalidArgumentException for invalid paths.
*/
public function withPath(string $path): static
{
// TODO: Implement withPath() method.
$this->path = $path;
return $this;
}
/** /**
* Return an instance with the specified query string. * Return an instance with the specified port.
* *
* This method MUST retain the state of the current instance, and return * This method MUST retain the state of the current instance, and return
* an instance that contains the specified query string. * an instance that contains the specified port.
* *
* Users can provide both encoded and decoded query characters. * Implementations MUST raise an exception for ports outside the
* Implementations ensure the correct encoding as outlined in getQuery(). * established TCP and UDP port ranges.
* *
* An empty query string value is equivalent to removing the query string. * A null value provided for the port is equivalent to removing the port
* * information.
* @param string $query The query string to use with the new instance. *
* @return static A new instance with the specified query string. * @param null|int $port The port to use with the new instance; a null value
* @throws \InvalidArgumentException for invalid query strings. * removes the port information.
*/ * @return static A new instance with the specified port.
public function withQuery(string $query): static * @throws \InvalidArgumentException for invalid ports.
{ */
// TODO: Implement withQuery() method. public function withPort(?int $port): static
$this->queryString = $query; {
return $this; // TODO: Implement withPort() method.
} return $this;
}
/** /**
* Return an instance with the specified URI fragment. * Return an instance with the specified path.
* *
* This method MUST retain the state of the current instance, and return * This method MUST retain the state of the current instance, and return
* an instance that contains the specified URI fragment. * an instance that contains the specified path.
* *
* Users can provide both encoded and decoded fragment characters. * The path can either be empty or absolute (starting with a slash) or
* Implementations ensure the correct encoding as outlined in getFragment(). * rootless (not starting with a slash). Implementations MUST support all
* * three syntaxes.
* An empty fragment value is equivalent to removing the fragment. *
* * If the path is intended to be domain-relative rather than path relative then
* @param string $fragment The fragment to use with the new instance. * it must begin with a slash ("/"). Paths not starting with a slash ("/")
* @return static A new instance with the specified fragment. * are assumed to be relative to some base path known to the application or
*/ * consumer.
public function withFragment(string $fragment): static *
{ * Users can provide both encoded and decoded path characters.
// TODO: Implement withFragment() method. * Implementations ensure the correct encoding as outlined in getPath().
$this->fragment = $fragment; *
return $this; * @param string $path The path to use with the new instance.
} * @return static A new instance with the specified path.
* @throws \InvalidArgumentException for invalid paths.
*/
public function withPath(string $path): static
{
// TODO: Implement withPath() method.
return $this;
}
/** /**
* Return the string representation as a URI reference. * Return an instance with the specified query string.
* *
* Depending on which components of the URI are present, the resulting * This method MUST retain the state of the current instance, and return
* string is either a full URI or relative reference according to RFC 3986, * an instance that contains the specified query string.
* Section 4.1. The method concatenates the various components of the URI, *
* using the appropriate delimiters: * Users can provide both encoded and decoded query characters.
* * Implementations ensure the correct encoding as outlined in getQuery().
* - If a scheme is present, it MUST be suffixed by ":". *
* - If an authority is present, it MUST be prefixed by "//". * An empty query string value is equivalent to removing the query string.
* - The path can be concatenated without delimiters. But there are two *
* cases where the path has to be adjusted to make the URI reference * @param string $query The query string to use with the new instance.
* valid as PHP does not allow to throw an exception in __toString(): * @return static A new instance with the specified query string.
* - If the path is rootless and an authority is present, the path MUST * @throws \InvalidArgumentException for invalid query strings.
* be prefixed by "/". */
* - If the path is starting with more than one "/" and no authority is public function withQuery(string $query): static
* present, the starting slashes MUST be reduced to one. {
* - If a query is present, it MUST be prefixed by "?". // TODO: Implement withQuery() method.
* - If a fragment is present, it MUST be prefixed by "#". return $this;
* }
* @see http://tools.ietf.org/html/rfc3986#section-4.1
* @return string /**
*/ * Return an instance with the specified URI fragment.
public function __toString() *
{ * This method MUST retain the state of the current instance, and return
// TODO: Implement __toString() method. * an instance that contains the specified URI fragment.
return $this->scheme . '://x.x.x.x:' . $this->port . '/' . $this->path . '?' . $this->queryString; *
} * Users can provide both encoded and decoded fragment characters.
* Implementations ensure the correct encoding as outlined in getFragment().
*
* An empty fragment value is equivalent to removing the fragment.
*
* @param string $fragment The fragment to use with the new instance.
* @return static A new instance with the specified fragment.
*/
public function withFragment(string $fragment): static
{
// TODO: Implement withFragment() method.
return $this;
}
/**
* Return the string representation as a URI reference.
*
* Depending on which components of the URI are present, the resulting
* string is either a full URI or relative reference according to RFC 3986,
* Section 4.1. The method concatenates the various components of the URI,
* using the appropriate delimiters:
*
* - If a scheme is present, it MUST be suffixed by ":".
* - If an authority is present, it MUST be prefixed by "//".
* - The path can be concatenated without delimiters. But there are two
* cases where the path has to be adjusted to make the URI reference
* valid as PHP does not allow to throw an exception in __toString():
* - If the path is rootless and an authority is present, the path MUST
* be prefixed by "/".
* - If the path is starting with more than one "/" and no authority is
* present, the starting slashes MUST be reduced to one.
* - If a query is present, it MUST be prefixed by "?".
* - If a fragment is present, it MUST be prefixed by "#".
*
* @see http://tools.ietf.org/html/rfc3986#section-4.1
* @return string
*/
public function __toString()
{
// TODO: Implement __toString() method.
$url = $this->getScheme() . '://' . $this->getHost();
if ($this->getPort() !== 80 && $this->getPort() !== 443) {
$url .= ':' . $this->getPort();
}
$url .= '/' . $this->getPath() . '?' . $this->getQuery();
return $url;
}
} }