diff --git a/akka-http-core/src/main/scala/akka/http/impl/engine/ws/WebSocketClientBlueprint.scala b/akka-http-core/src/main/scala/akka/http/impl/engine/ws/WebSocketClientBlueprint.scala index 1894d3077f..9986b88eb9 100644 --- a/akka-http-core/src/main/scala/akka/http/impl/engine/ws/WebSocketClientBlueprint.scala +++ b/akka-http-core/src/main/scala/akka/http/impl/engine/ws/WebSocketClientBlueprint.scala @@ -53,7 +53,7 @@ object WebSocketClientBlueprint { val valve = StreamUtils.OneTimeValve() val (initialRequest, key) = Handshake.Client.buildRequest(uri, extraHeaders, subprotocol.toList, settings.websocketRandomFactory()) - val hostHeader = Host(uri.authority) + val hostHeader = Host(uri.authority.normalizedFor(uri.scheme)) val renderedInitialRequest = HttpRequestRendererFactory.renderStrict(RequestRenderingContext(initialRequest, hostHeader), settings, log) diff --git a/akka-http-core/src/main/scala/akka/http/impl/model/JavaUri.scala b/akka-http-core/src/main/scala/akka/http/impl/model/JavaUri.scala index 9edca4b287..1003b69b02 100644 --- a/akka-http-core/src/main/scala/akka/http/impl/model/JavaUri.scala +++ b/akka-http-core/src/main/scala/akka/http/impl/model/JavaUri.scala @@ -21,7 +21,7 @@ case class JavaUri(uri: sm.Uri) extends jm.Uri { def scheme(): String = uri.scheme def host(): jm.Host = uri.authority.host - def port(): Int = uri.authority.port + def port(): Int = uri.effectivePort def userInfo(): String = uri.authority.userinfo def path(): String = uri.path.toString diff --git a/akka-http-core/src/main/scala/akka/http/scaladsl/model/Uri.scala b/akka-http-core/src/main/scala/akka/http/scaladsl/model/Uri.scala index 7afdab3d49..9c99da0fb5 100644 --- a/akka-http-core/src/main/scala/akka/http/scaladsl/model/Uri.scala +++ b/akka-http-core/src/main/scala/akka/http/scaladsl/model/Uri.scala @@ -43,13 +43,11 @@ sealed abstract case class Uri(scheme: String, authority: Authority, path: Path, def queryString(charset: Charset = UTF8): Option[String] = rawQueryString.map(s ⇒ decode(s, charset)) /** - * INTERNAL API - * * The effective port of this Uri given the currently set authority and scheme values. * If the authority has an explicitly set port (i.e. a non-zero port value) then this port * is the effective port. Otherwise the default port for the current scheme is returned. */ - private[akka] def effectivePort: Int = if (authority.port != 0) authority.port else defaultPorts(scheme) + def effectivePort: Int = if (authority.port != 0) authority.port else defaultPorts(scheme) /** * Returns a copy of this Uri with the given components. @@ -210,7 +208,7 @@ object Uri { /** * Creates a new Uri instance from the given components. - * All components are verified and normalized. + * All components are verified and normalized except the authority which is kept as provided. * If the given combination of components does not constitute a valid URI as defined by * http://tools.ietf.org/html/rfc3986 the method throws an `IllegalUriException`. */ @@ -219,7 +217,7 @@ object Uri { val p = verifyPath(path, scheme, authority.host) create( scheme = normalizeScheme(scheme), - authority = authority.normalizedFor(scheme), + authority = authority, path = if (scheme.isEmpty) p else collapseDotSegments(p), queryString = queryString, fragment = fragment) @@ -277,8 +275,11 @@ object Uri { * If strict is `false`, accepts unencoded visible 7-bit ASCII characters in addition to the RFC. * If the given string is not a valid URI the method throws an `IllegalUriException`. */ - def normalize(uri: ParserInput, charset: Charset = UTF8, mode: Uri.ParsingMode = Uri.ParsingMode.Relaxed): String = - UriRendering.renderUri(new StringRendering, apply(uri, charset, mode), charset).get + def normalize(uri: ParserInput, charset: Charset = UTF8, mode: Uri.ParsingMode = Uri.ParsingMode.Relaxed): String = { + val parsed = apply(uri, charset, mode) + val normalized = parsed.copy(authority = parsed.authority.normalizedFor(parsed.scheme)) + UriRendering.renderUri(new StringRendering, normalized, charset).get + } /** * Converts a set of URI components to an "effective HTTP request URI" as defined by @@ -307,6 +308,10 @@ object Uri { def httpScheme(securedConnection: Boolean = false) = if (securedConnection) "https" else "http" + /** + * @param port A port number that may be `0` to signal the default port of for scheme. + * In general what you want is not the value of this field but [[Uri.effectivePort]]. + */ final case class Authority(host: Host, port: Int = 0, userinfo: String = "") { def isEmpty = equals(Authority.Empty) def nonEmpty = !isEmpty @@ -742,7 +747,7 @@ object Uri { private[http] def create(scheme: String, userinfo: String, host: Host, port: Int, path: Path, queryString: Option[String], fragment: Option[String]): Uri = - create(scheme, Authority(host, normalizePort(port, scheme), userinfo), path, queryString, fragment) + create(scheme, Authority(host, port, userinfo), path, queryString, fragment) private[http] def create(scheme: String, authority: Authority, path: Path, queryString: Option[String], fragment: Option[String]): Uri = diff --git a/akka-http-core/src/test/scala/akka/http/scaladsl/model/UriSpec.scala b/akka-http-core/src/test/scala/akka/http/scaladsl/model/UriSpec.scala index 454bb1d667..b17884d834 100644 --- a/akka-http-core/src/test/scala/akka/http/scaladsl/model/UriSpec.scala +++ b/akka-http-core/src/test/scala/akka/http/scaladsl/model/UriSpec.scala @@ -382,7 +382,7 @@ class UriSpec extends WordSpec with Matchers { // empty host Uri("http://:8000/foo") shouldEqual Uri("http", Authority(Host.Empty, 8000), Path / "foo") - Uri("http://:80/foo") shouldEqual Uri("http", Authority.Empty, Path / "foo") + Uri("http://:80/foo") shouldEqual Uri("http", Authority(Host.Empty, 80), Path / "foo") } "properly complete a normalization cycle" in { @@ -571,38 +571,55 @@ class UriSpec extends WordSpec with Matchers { } "provide sugar for fluent transformations" in { - val uri = Uri("http://host:80/path?query#fragment") + val uri = Uri("http://host/path?query#fragment") + val explicitDefault = Uri("http://host:80/path?query#fragment") val nonDefaultUri = Uri("http://host:6060/path?query#fragment") uri.withScheme("https") shouldEqual Uri("https://host/path?query#fragment") + explicitDefault.withScheme("https") shouldEqual Uri("https://host:80/path?query#fragment") nonDefaultUri.withScheme("https") shouldEqual Uri("https://host:6060/path?query#fragment") uri.withAuthority(Authority(Host("other"), 3030)) shouldEqual Uri("http://other:3030/path?query#fragment") uri.withAuthority(Host("other"), 3030) shouldEqual Uri("http://other:3030/path?query#fragment") uri.withAuthority("other", 3030) shouldEqual Uri("http://other:3030/path?query#fragment") - uri.withHost(Host("other")) shouldEqual Uri("http://other:80/path?query#fragment") - uri.withHost("other") shouldEqual Uri("http://other:80/path?query#fragment") + uri.withHost(Host("other")) shouldEqual Uri("http://other/path?query#fragment") + explicitDefault.withHost(Host("other")) shouldEqual Uri("http://other:80/path?query#fragment") + uri.withHost("other") shouldEqual Uri("http://other/path?query#fragment") + explicitDefault.withHost("other") shouldEqual Uri("http://other:80/path?query#fragment") uri.withPort(90) shouldEqual Uri("http://host:90/path?query#fragment") + explicitDefault.withPort(90) shouldEqual Uri("http://host:90/path?query#fragment") uri.withPath(Path("/newpath")) shouldEqual Uri("http://host/newpath?query#fragment") - uri.withUserInfo("someInfo") shouldEqual Uri("http://someInfo@host:80/path?query#fragment") + explicitDefault.withPath(Path("/newpath")) shouldEqual Uri("http://host:80/newpath?query#fragment") - uri.withQuery(Query("param1" -> "value1")) shouldEqual Uri("http://host:80/path?param1=value1#fragment") - uri.withQuery(Query(Map("param1" -> "value1"))) shouldEqual Uri("http://host:80/path?param1=value1#fragment") - uri.withRawQueryString("param1=value1") shouldEqual Uri("http://host:80/path?param1=value1#fragment") + uri.withUserInfo("someInfo") shouldEqual Uri("http://someInfo@host/path?query#fragment") + explicitDefault.withUserInfo("someInfo") shouldEqual Uri("http://someInfo@host:80/path?query#fragment") - uri.withFragment("otherFragment") shouldEqual Uri("http://host:80/path?query#otherFragment") + uri.withQuery(Query("param1" -> "value1")) shouldEqual Uri("http://host/path?param1=value1#fragment") + uri.withQuery(Query(Map("param1" -> "value1"))) shouldEqual Uri("http://host/path?param1=value1#fragment") + uri.withRawQueryString("param1=value1") shouldEqual Uri("http://host/path?param1=value1#fragment") + + uri.withFragment("otherFragment") shouldEqual Uri("http://host/path?query#otherFragment") } "return the correct effective port" in { - 80 shouldEqual Uri("http://host/").effectivePort - 21 shouldEqual Uri("ftp://host/").effectivePort - 9090 shouldEqual Uri("http://host:9090/").effectivePort - 443 shouldEqual Uri("https://host/").effectivePort + Uri("http://host/").effectivePort shouldEqual 80 + Uri("ftp://host/").effectivePort shouldEqual 21 + Uri("http://host:9090/").effectivePort shouldEqual 9090 + Uri("https://host/").effectivePort shouldEqual 443 - 4450 shouldEqual Uri("https://host/").withPort(4450).effectivePort - 4450 shouldEqual Uri("https://host:3030/").withPort(4450).effectivePort + Uri("https://host/").withPort(4450).effectivePort shouldEqual 4450 + Uri("https://host:3030/").withPort(4450).effectivePort shouldEqual 4450 + } + + "keep the specified authority port" in { + Uri("example.com").withPort(0).authority.port shouldEqual 0 + Uri("example.com").withPort(80).authority.port shouldEqual 80 + Uri("http://example.com").withPort(80).authority.port shouldEqual 80 + Uri("http://example.com").withPort(0).authority.port shouldEqual 0 + Uri("https://example.com").withPort(0).authority.port shouldEqual 0 + Uri("https://example.com").withPort(443).authority.port shouldEqual 443 } "properly render as HTTP request target origin forms" in { diff --git a/akka-http-tests/src/test/scala/akka/http/scaladsl/server/ConnectionTestApp.scala b/akka-http-tests/src/test/scala/akka/http/scaladsl/server/ConnectionTestApp.scala index ebd6796e8e..413b7bbe30 100644 --- a/akka-http-tests/src/test/scala/akka/http/scaladsl/server/ConnectionTestApp.scala +++ b/akka-http-tests/src/test/scala/akka/http/scaladsl/server/ConnectionTestApp.scala @@ -57,7 +57,7 @@ object ConnectionTestApp { def sendSingle(uri: Uri, id: Int): Unit = { val connectionFlow: Flow[HttpRequest, HttpResponse, Future[Http.OutgoingConnection]] = - Http().outgoingConnection(uri.authority.host.address, uri.authority.port) + Http().outgoingConnection(uri.authority.host.address, uri.effectivePort) val responseFuture: Future[HttpResponse] = Source.single(buildRequest(uri)) .via(connectionFlow)