URI 模型

Akka HTTP offers its own specialised UriUri model class which is tuned for both performance and idiomatic usage within other types of the HTTP model. For example, an HttpRequestHttpRequest’s target URI is parsed into this type, where all character escaping and other URI specific semantics are applied.

Akka HTTP 提供了自己的一套特制的 UriUri 模型,为了性能以及更好的于 HTTP 模型里的其它类型进行互动。例如, HttpRequestHttpRequest 的目标 URI 会解析成这个类型,并在解析过程中应用到所有字符转码和 URI 特殊语义等处理。

Parsing a URI string

一个 URI 字符串的语法分析

We follow RFC 3986 to implement the URI parsing rules. When you try to parse a URI string, Akka HTTP internally creates an instance of the UriUri class, which holds the modeled URI components inside.

我们按 RFC 3986 实现了 URI 的语法分析规则。当你尝试解析一个 URI 字符串时, Akka HTTP 内部分创建一个 UriUri 类的实例,其中保存建立的 URI 组件。

For example, the following creates an instance of a simple valid URI:

例如,下面创建了一个简单有效的 URI 实例:

Scala
Uri("http://localhost")
Java
Uri.create("http://localhost");

Below are some more examples of valid URI strings, and how you can construct a UriUri model class instances ,using Uri.from() method by passing scheme, host, path and query parameters.

以下再给出几个怎样构造 UriUri 模型实例的例子,它们都是有效 URI 字符串@scala[,使用 Uri.from() 方法和相关参数 schemehostpathquery]。

Scala
Uri("ftp://ftp.is.co.za/rfc/rfc1808.txt") shouldEqual
  Uri.from(scheme = "ftp", host = "ftp.is.co.za", path = "/rfc/rfc1808.txt")

Uri("http://www.ietf.org/rfc/rfc2396.txt") shouldEqual
  Uri.from(scheme = "http", host = "www.ietf.org", path = "/rfc/rfc2396.txt")

Uri("ldap://[2001:db8::7]/c=GB?objectClass?one") shouldEqual
  Uri.from(scheme = "ldap", host = "[2001:db8::7]", path = "/c=GB", queryString = Some("objectClass?one"))

Uri("mailto:John.Doe@example.com") shouldEqual
  Uri.from(scheme = "mailto", path = "John.Doe@example.com")

Uri("news:comp.infosystems.www.servers.unix") shouldEqual
  Uri.from(scheme = "news", path = "comp.infosystems.www.servers.unix")

Uri("tel:+1-816-555-1212") shouldEqual
  Uri.from(scheme = "tel", path = "+1-816-555-1212")

Uri("s3:image.png") shouldEqual
  Uri.from(scheme = "s3", path = "image.png")

Uri("telnet://192.0.2.16:80/") shouldEqual
  Uri.from(scheme = "telnet", host = "192.0.2.16", port = 80, path = "/")

Uri("urn:oasis:names:specification:docbook:dtd:xml:4.1.2") shouldEqual
  Uri.from(scheme = "urn", path = "oasis:names:specification:docbook:dtd:xml:4.1.2")
Java
Uri uri1 = Uri.create("ftp://ftp.is.co.za/rfc/rfc1808.txt");
assertEquals("ftp", uri1.getScheme());
assertEquals(Host.create("ftp.is.co.za"), uri1.getHost());
assertEquals("/rfc/rfc1808.txt", uri1.getPathString());

Uri uri2 = Uri.create("http://www.ietf.org/rfc/rfc2396.txt");
assertEquals("http", uri2.getScheme());
assertEquals(Host.create("www.ietf.org"), uri2.getHost());
assertEquals("/rfc/rfc2396.txt", uri2.getPathString());

Uri uri3 = Uri.create("ldap://[2001:db8::7]/c=GB?objectClass?one");
assertEquals("ldap", uri3.getScheme());
assertEquals(Host.create("[2001:db8::7]"), uri3.getHost());
assertEquals("objectClass?one", uri3.query().toString());

Uri uri4 = Uri.create("mailto:John.Doe@example.com");
assertEquals("mailto", uri4.getScheme());
assertEquals("John.Doe@example.com", uri4.getPathString());

Uri uri5 = Uri.create("news:comp.infosystems.www.servers.unix");
assertEquals("news", uri5.getScheme());
assertEquals("comp.infosystems.www.servers.unix", uri5.getPathString());

Uri uri6 = Uri.create("tel:+1-816-555-1212");
assertEquals("tel", uri6.getScheme());
assertEquals("+1-816-555-1212", uri6.getPathString());

Uri uri7 = Uri.create("telnet://192.0.2.16:80/");
assertEquals("telnet", uri7.getScheme());
assertEquals(Host.create("192.0.2.16"), uri7.getHost());
assertEquals("/", uri7.getPathString());

Uri uri8 = Uri.create("urn:oasis:names:specification:docbook:dtd:xml:4.1.2");
assertEquals("urn", uri8.getScheme());
assertEquals("oasis:names:specification:docbook:dtd:xml:4.1.2", uri8.getPathString());

For exact definitions of the parts of a URI, like scheme, path and query refer to RFC 3986. Here’s a little overview:

对于一个 URI 的精确定义,例如 schemepathquery ,参考 RFC 3986 。 这是一个简短描述:

  foo://example.com:8042/over/there?name=ferret#nose
  \_/   \______________/\_________/ \_________/ \__/
   |           |            |            |        |
scheme     authority       path        query   fragment
   |   _____________________|__
  / \ /                        \
  urn:example:animal:ferret:nose

For “special” characters in URI, you typically use percent encoding like below. Percent encoding is discussed in more detail in the Query String in URI section.

对于 URI 里的“特殊”字符,一般使用如下的百分号编码。有关编码细节的更多讨论在 URI 里的查询字符串 部分。

Scala
// don't double decode
Uri("%2520").path.head shouldEqual "%20"
Uri("/%2F%5C").path shouldEqual Path / """/\"""
Java
Uri uri1 = Uri.create("http://foo.com?foo=%2520");
assertEquals(Optional.of("%20"), uri1.query().get("foo"));
Uri uri2 = Uri.create("http://foo.com?foo=%2F%5C");
assertEquals(Optional.of("/\\"), uri2.query().get("foo"));

Invalid URI strings and IllegalUriException

无效 URI 字符串以及 IllegalUriException

When an invalid URI string is passed to Uri() as below, an IllegalUriException is thrown.

当如下一个无效的 URI 字符串被传给 Uri() ,一个 IllegalUriException 异常会抛出。

Scala
//illegal scheme
the[IllegalUriException] thrownBy Uri("foö:/a") shouldBe {
  IllegalUriException(
    "Illegal URI reference: Invalid input 'ö', expected scheme-char, 'EOI', '#', ':', '?', slashSegments or pchar (line 1, column 3)",
    "foö:/a\n" +
      "  ^")
}

// illegal userinfo
the[IllegalUriException] thrownBy Uri("http://user:ö@host") shouldBe {
  IllegalUriException(
    "Illegal URI reference: Invalid input 'ö', expected userinfo-char, pct-encoded, '@' or port (line 1, column 13)",
    "http://user:ö@host\n" +
      "            ^")
}

// illegal percent-encoding
the[IllegalUriException] thrownBy Uri("http://use%2G@host") shouldBe {
  IllegalUriException(
    "Illegal URI reference: Invalid input 'G', expected HEXDIG (line 1, column 13)",
    "http://use%2G@host\n" +
      "            ^")
}

// illegal percent-encoding ends with %
the[IllegalUriException] thrownBy Uri("http://www.example.com/%CE%B8%") shouldBe {
  IllegalUriException(
    "Illegal URI reference: Unexpected end of input, expected HEXDIG (line 1, column 31)",
    "http://www.example.com/%CE%B8%\n" +
      "                              ^")
}

// illegal path
the[IllegalUriException] thrownBy Uri("http://www.example.com/name with spaces/") shouldBe {
  IllegalUriException(
    "Illegal URI reference: Invalid input ' ', expected '/', 'EOI', '#', '?' or pchar (line 1, column 28)",
    "http://www.example.com/name with spaces/\n" +
      "                           ^")
}

// illegal path with control character
the[IllegalUriException] thrownBy Uri("http:///with\newline") shouldBe {
  IllegalUriException(
    "Illegal URI reference: Invalid input '\\n', expected '/', 'EOI', '#', '?' or pchar (line 1, column 13)",
    "http:///with\n" +
      "            ^")
}
Java
@Test(expected = IllegalUriException.class)
public void testIllegalScheme() {
  Uri.create("foö:/a");
  //IllegalUriException(
  //  "Illegal URI reference: Invalid input 'ö', expected scheme-char, 'EOI', '#', ':', '?', slashSegments or pchar (line 1, column 3)",
  //  "http://user:ö@host\n" +
  //  "            ^"
  //)
}
@Test(expected = IllegalUriException.class)
public void testIllegalUserInfo() {
  Uri.create("http://user:ö@host");
  //IllegalUriException(
  //  "Illegal URI reference: Invalid input 'ö', expected userinfo-char, pct-encoded, '@' or port (line 1, column 13)",
  //  "http://use%2G@host\n" +
  //  "            ^"
  //)
}
@Test(expected = IllegalUriException.class)
public void testIllegalPercentEncoding() {
  Uri.create("http://use%2G@host");
  //IllegalUriException(
  //  "Illegal URI reference: Invalid input 'G', expected HEXDIG (line 1, column 13)",
  //  "http://www.example.com/name with spaces/\n" +
  //  "                           ^"
  //)
}
@Test(expected = IllegalUriException.class)
public void testIllegalPath() {
  Uri.create("http://www.example.com/name with spaces/");
  //IllegalUriException(
  //  "Illegal URI reference: Invalid input ' ', expected '/', 'EOI', '#', '?' or pchar (line 1, column 28)",
  //  "http://www.example.com/name with spaces/\n" +
  //  "                           ^"
  //)
}
@Test(expected = IllegalUriException.class)
public void testIllegalPathWithControlCharacter() {
  Uri.create("http:///with\newline");
  //IllegalUriException(
  //  "Illegal URI reference: Invalid input '\\n', expected '/', 'EOI', '#', '?' or pchar (line 1, column 13)",
  //  "http:///with\n" +
  //  "            ^"
  //)
}

Directives to extract URI components

抽取 URI 组件的指令

To extract URI components with directives, see following references: 要使用指令抽取 URI 组件,可参考下列资料:

Obtaining the raw request URI

获取原始请求 URI

Sometimes it may be needed to obtain the “raw” value of an incoming URI, without applying any escaping or parsing to it. While this use case is rare, it comes up every once in a while. It is possible to obtain the “raw” request URI in Akka HTTP Server side by turning on the akka.http.server.raw-request-uri-header flag. When enabled, a Raw-Request-URI header will be added to each request. This header will hold the original raw request’s URI that was used. For an example check the reference configuration.

有时需要获取“原始”的 URI 值,原始 URI 不做转码和解析。虽然这种情况比较少,但也偶尔会遇到。在 Akka HTTP 服务器端打开 akka.http.server.raw-request-uri-header 标记就可获取”原始“请求URI。当标记被打开,一个 Raw-Request-URI 头将被添加到每个请求。这个头域将保存外部的原始请求 URI 以供使用。

Query string in URI

URI 里的查询字符串

Although any part of URI can have special characters, it is more common for the query string in URI to have special characters, which are typically percent encoded.

虽然 URI 的任何部分都可能有特殊字符,但 URI 的查询字符串里有特殊字符更常见。

UriUri class’s query() methodThe method Uri::query() returns the query string of the URI, which is modeled in an instance of the Query class. When you instantiate a UriUri class by passing a URI string, the query string is stored in its raw string form. Then, when you call the query() method, the query string is parsed from the raw string.

UriUri 类的 query() 方法方法 Uri::query() 返回一个 Query 类实例的 URI 查询字符串。 当你传一个 URI 字符串生成一个 UriUri 类实例时,查询字符串将以原始字符串的形式保存。直到你调用 query 方法时才解析。

The below code illustrates how valid query strings are parsed. Especially, you can check how percent encoding is used and how special characters like + and ; are parsed.

下面的代码展示了怎样解析有效的查询字符串。特别是,可以留意下百分比编码的应用和怎样解析特殊字符串,如:+;

Note

The mode parameter to Query() and Uri.query() is discussed in Strict and Relaxed Mode.

严格和宽松模式 讨论了 QueryUri.query()mode 参数。

Scala
def strict(queryString: String): Query = Query(queryString, mode = Uri.ParsingMode.Strict)
Java
public Query strict(String query){
  return Query.create(query, akka.http.javadsl.model.Uri.STRICT);
}
Scala
//query component "a=b" is parsed into parameter name: "a", and value: "b"
strict("a=b") shouldEqual ("a", "b") +: Query.Empty

strict("") shouldEqual ("", "") +: Query.Empty
strict("a") shouldEqual ("a", "") +: Query.Empty
strict("a=") shouldEqual ("a", "") +: Query.Empty
strict("a=+") shouldEqual ("a", " ") +: Query.Empty //'+' is parsed to ' '
strict("a=%2B") shouldEqual ("a", "+") +: Query.Empty
strict("=a") shouldEqual ("", "a") +: Query.Empty
strict("a&") shouldEqual ("a", "") +: ("", "") +: Query.Empty
strict("a=%62") shouldEqual ("a", "b") +: Query.Empty

strict("a%3Db=c") shouldEqual ("a=b", "c") +: Query.Empty
strict("a%26b=c") shouldEqual ("a&b", "c") +: Query.Empty
strict("a%2Bb=c") shouldEqual ("a+b", "c") +: Query.Empty
strict("a%3Bb=c") shouldEqual ("a;b", "c") +: Query.Empty

strict("a=b%3Dc") shouldEqual ("a", "b=c") +: Query.Empty
strict("a=b%26c") shouldEqual ("a", "b&c") +: Query.Empty
strict("a=b%2Bc") shouldEqual ("a", "b+c") +: Query.Empty
strict("a=b%3Bc") shouldEqual ("a", "b;c") +: Query.Empty

strict("a+b=c") shouldEqual ("a b", "c") +: Query.Empty //'+' is parsed to ' '
strict("a=b+c") shouldEqual ("a", "b c") +: Query.Empty //'+' is parsed to ' '
Java
//query component (name: "a", and value: "b") is equal to parsed query string "a=b"
assertEquals(Query.create(Pair.create("a", "b")), strict("a=b"));

assertEquals(Query.create(Pair.create("", "")), strict(""));
assertEquals(Query.create(Pair.create("a", "")), strict("a"));
assertEquals(Query.create(Pair.create("a", "")), strict("a="));
assertEquals(Query.create(Pair.create("a", " ")), strict("a=+"));
assertEquals(Query.create(Pair.create("a", "+")), strict("a=%2B"));
assertEquals(Query.create(Pair.create("", "a")), strict("=a"));
assertEquals(Query.create(Pair.create("a", "")).withParam("", ""), strict("a&"));
assertEquals(Query.create(Pair.create("a", "b")), strict("a=%62"));

assertEquals(Query.create(Pair.create("a=b", "c")), strict("a%3Db=c"));
assertEquals(Query.create(Pair.create("a&b", "c")), strict("a%26b=c"));
assertEquals(Query.create(Pair.create("a+b", "c")), strict("a%2Bb=c"));
assertEquals(Query.create(Pair.create("a;b", "c")), strict("a%3Bb=c"));

assertEquals(Query.create(Pair.create("a", "b=c")), strict("a=b%3Dc"));
assertEquals(Query.create(Pair.create("a", "b&c")), strict("a=b%26c"));
assertEquals(Query.create(Pair.create("a", "b+c")), strict("a=b%2Bc"));
assertEquals(Query.create(Pair.create("a", "b;c")), strict("a=b%3Bc"));

assertEquals(Query.create(Pair.create("a b", "c")), strict("a+b=c")); //'+' is parsed to ' '
assertEquals(Query.create(Pair.create("a", "b c")), strict("a=b+c")); //'+' is parsed to ' '

Note that:

注意:

  Uri("http://localhost?a=b").query()

is equivalent to:

等价于:

  Query("a=b")

As in the section 3.4 of RFC 3986, some special characters like “/” and “?” are allowed inside a query string, without escaping them using (“%”) signs.

RFC 3986 的 3.4 部分 里,一些特殊字符被允许在查询字符串里,例如:'?, 它们不需要使用(“%”)标记。

The characters slash (“/”) and question mark (“?”) may represent data within the query component.

字符斜杠(/)和问号(?)可以表示查询组件中的数据。

“/” and “?” are commonly used when you have a URI whose query parameter has another URI.

当 URI 中的查询参数有另一个 URI 时,/? 常被用的。

Scala
strict("a?b=c") shouldEqual ("a?b", "c") +: Query.Empty
strict("a/b=c") shouldEqual ("a/b", "c") +: Query.Empty

strict("a=b?c") shouldEqual ("a", "b?c") +: Query.Empty
strict("a=b/c") shouldEqual ("a", "b/c") +: Query.Empty
Java
assertEquals(Query.create(Pair.create("a?b", "c")), strict("a?b=c"));
assertEquals(Query.create(Pair.create("a/b", "c")), strict("a/b=c"));

assertEquals(Query.create(Pair.create("a", "b?c")), strict("a=b?c"));
assertEquals(Query.create(Pair.create("a", "b/c")), strict("a=b/c"));

However, some other special characters can cause IllegalUriException without percent encoding as follows.

但是,有些其它的特殊字符不使用百分比编码的话会抛出 IllegalUriException 异常,如下。

Scala
the[IllegalUriException] thrownBy strict("a^=b") shouldBe {
  IllegalUriException(
    "Illegal query: Invalid input '^', expected '+', '=', query-char, 'EOI', '&' or pct-encoded (line 1, column 2)",
    "a^=b\n" +
      " ^")
}
the[IllegalUriException] thrownBy strict("a;=b") shouldBe {
  IllegalUriException(
    "Illegal query: Invalid input ';', expected '+', '=', query-char, 'EOI', '&' or pct-encoded (line 1, column 2)",
    "a;=b\n" +
      " ^")
}
Java
@Test(expected = IllegalUriException.class)
public void testStrictModeException1() {
  strict("a^=b");
  //IllegalUriException(
  //  "Illegal query: Invalid input '^', expected '+', '=', query-char, 'EOI', '&' or pct-encoded (line 1, column 2)",
  //  "a^=b\n" +
  //  " ^")
}
Scala
//double '=' in query string is invalid
the[IllegalUriException] thrownBy strict("a=b=c") shouldBe {
  IllegalUriException(
    "Illegal query: Invalid input '=', expected '+', query-char, 'EOI', '&' or pct-encoded (line 1, column 4)",
    "a=b=c\n" +
      "   ^")
}
//following '%', it should be percent encoding (HEXDIG), but "%b=" is not a valid percent encoding
the[IllegalUriException] thrownBy strict("a%b=c") shouldBe {
  IllegalUriException(
    "Illegal query: Invalid input '=', expected HEXDIG (line 1, column 4)",
    "a%b=c\n" +
      "   ^")
}
Java
@Test(expected = IllegalUriException.class)
public void testStrictModeException2() {
  strict("a;=b");
  //IllegalUriException(
  //  "Illegal query: Invalid input ';', expected '+', '=', query-char, 'EOI', '&' or pct-encoded (line 1, column 2)",
  //  "a;=b\n" +
  //  " ^")
}

Strict and Relaxed Mode

严格和宽松模式

The Uri.query() method and Query() take a parameter mode, which is either Uri.ParsingMode.Strict or Uri.ParsingMode.Relaxed. Switching the mode gives different behavior on parsing some special characters in URI.

Uri.query() 函数和 Query() 构造函数都可以读入一个参数 mode, 这个参数的类型可以是 Uri.ParsingMode.StrictUri.ParsingMode.Relaxed。模式不同的选择也会带来不同的字符串解析行为。

Scala
def relaxed(queryString: String): Query = Query(queryString, mode = Uri.ParsingMode.Relaxed)
Java
public Query relaxed(String query){
  return Query.create(query,  akka.http.javadsl.model.Uri.RELAXED);
}

The below two cases threw IllegalUriException when you specified the Strict mode,

当你指定 Strict 模式时,以下两种情况抛出 IllegalUriException 异常,

Scala
the[IllegalUriException] thrownBy strict("a^=b") shouldBe {
  IllegalUriException(
    "Illegal query: Invalid input '^', expected '+', '=', query-char, 'EOI', '&' or pct-encoded (line 1, column 2)",
    "a^=b\n" +
      " ^")
}
the[IllegalUriException] thrownBy strict("a;=b") shouldBe {
  IllegalUriException(
    "Illegal query: Invalid input ';', expected '+', '=', query-char, 'EOI', '&' or pct-encoded (line 1, column 2)",
    "a;=b\n" +
      " ^")
}
Java
@Test(expected = IllegalUriException.class)
public void testStrictModeException1() {
  strict("a^=b");
  //IllegalUriException(
  //  "Illegal query: Invalid input '^', expected '+', '=', query-char, 'EOI', '&' or pct-encoded (line 1, column 2)",
  //  "a^=b\n" +
  //  " ^")
}
@Test(expected = IllegalUriException.class)
public void testStrictModeException2() {
  strict("a;=b");
  //IllegalUriException(
  //  "Illegal query: Invalid input ';', expected '+', '=', query-char, 'EOI', '&' or pct-encoded (line 1, column 2)",
  //  "a;=b\n" +
  //  " ^")
}

but the Relaxed mode parses them as they are.

Relaxed 模式下解析正常。

Scala
relaxed("a^=b") shouldEqual ("a^", "b") +: Query.Empty
relaxed("a;=b") shouldEqual ("a;", "b") +: Query.Empty
relaxed("a=b=c") shouldEqual ("a", "b=c") +: Query.Empty
Java
assertEquals(Query.create(Pair.create("a^", "b")), relaxed("a^=b"));
assertEquals(Query.create(Pair.create("a;", "b")), relaxed("a;=b"));
assertEquals(Query.create(Pair.create("a", "b=c")), relaxed("a=b=c"));

However, even with the Relaxed mode, there are still invalid special characters which require percent encoding.

然而,即使在 Relaxed 模式,也存在一些无效的特殊字符需要进行百分比编码。

Scala
//following '%', it should be percent encoding (HEXDIG), but "%b=" is not a valid percent encoding
//still invalid even in relaxed mode
the[IllegalUriException] thrownBy relaxed("a%b=c") shouldBe {
  IllegalUriException(
    "Illegal query: Invalid input '=', expected HEXDIG (line 1, column 4)",
    "a%b=c\n" +
      "   ^")
}
Java
@Test(expected = IllegalUriException.class)
public void testRelaxedModeException1() {
  //following '%', it should be percent encoding (HEXDIG), but "%b=" is not a valid percent encoding
  //still invalid even in relaxed mode
  relaxed("a%b=c");
  //IllegalUriException(
  //  "Illegal query: Invalid input '=', expected '+', query-char, 'EOI', '&' or pct-encoded (line 1, column 4)",
  //  "a%b=c\n" +
  //  "   ^")
}

Other than specifying the mode in the parameters, like when using directives, you can specify the mode in your configuration as follows.

mode 除了可以作为一个参数设定,像使用指令时,你还可以在配置里设置,如下:

    # Sets the strictness mode for parsing request target URIs.
    # The following values are defined:
    #
    # `strict`: RFC3986-compliant URIs are required,
    #     a 400 response is triggered on violations
    #
    # `relaxed`: all visible 7-Bit ASCII chars are allowed
    #
    uri-parsing-mode = strict

To access the raw, unparsed representation of the query part of a URI use the rawQueryString member of the UriUri class.

要一个 URI 的访问原始的、未解析过的查询部分,使用 UriUrirawQueryString 成员。

Directives to extract query parameters

抽取查询参数的指令

If you want to use directives to extract query parameters, see below pages.

如果你想使用指令抽取查询参数,见以下页面:

在此文档中发现错误?该页面的源代码可以在 这里 找到。欢迎随时编辑并提交 Pull Request。