diff --git a/core/core.services.yml b/core/core.services.yml
index 40c6a8a..3d4cab8 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -357,6 +357,9 @@ services:
   file_system:
     class: Drupal\Core\File\FileSystem
     arguments: ['@stream_wrapper_manager', '@settings', '@logger.channel.file']
+  file_url_generator:
+    class: Drupal\Core\File\FileUrlGenerator
+    arguments: ['@file_system', '@stream_wrapper_manager', '@request_stack', '@module_handler']
   form_builder:
     class: Drupal\Core\Form\FormBuilder
     arguments: ['@form_validator', '@form_submitter', '@form_cache', '@module_handler', '@event_dispatcher', '@request_stack', '@class_resolver', '@element_info', '@theme.manager', '@?csrf_token']
diff --git a/core/includes/file.inc b/core/includes/file.inc
index c78ee5b..f5c717b 100644
--- a/core/includes/file.inc
+++ b/core/includes/file.inc
@@ -178,57 +178,12 @@ function file_stream_wrapper_uri_normalize($uri) {
  *
  * @see https://www.drupal.org/node/515192
  * @see file_url_transform_relative()
+ *
+ * @deprecated in Drupal 8.4.x and will be removed before Drupal 9.0.0.
+ *   Use\Drupal\Core\File\FileUrlGenerator::generate().
  */
 function file_create_url($uri) {
-  // Allow the URI to be altered, e.g. to serve a file from a CDN or static
-  // file server.
-  \Drupal::moduleHandler()->alter('file_url', $uri);
-
-  $scheme = \Drupal::service('file_system')->uriScheme($uri);
-
-  if (!$scheme) {
-    // Allow for:
-    // - root-relative URIs (e.g. /foo.jpg in http://example.com/foo.jpg)
-    // - protocol-relative URIs (e.g. //bar.jpg, which is expanded to
-    //   http://example.com/bar.jpg by the browser when viewing a page over
-    //   HTTP and to https://example.com/bar.jpg when viewing a HTTPS page)
-    // Both types of relative URIs are characterized by a leading slash, hence
-    // we can use a single check.
-    if (Unicode::substr($uri, 0, 1) == '/') {
-      return $uri;
-    }
-    else {
-      // If this is not a properly formatted stream, then it is a shipped file.
-      // Therefore, return the urlencoded URI with the base URL prepended.
-      $options = UrlHelper::parse($uri);
-      $path = $GLOBALS['base_url'] . '/' . UrlHelper::encodePath($options['path']);
-      // Append the query.
-      if ($options['query']) {
-        $path .= '?' . UrlHelper::buildQuery($options['query']);
-      }
-
-      // Append fragment.
-      if ($options['fragment']) {
-        $path .= '#' . $options['fragment'];
-      }
-
-      return $path;
-    }
-  }
-  elseif ($scheme == 'http' || $scheme == 'https' || $scheme == 'data') {
-    // Check for HTTP and data URI-encoded URLs so that we don't have to
-    // implement getExternalUrl() for the HTTP and data schemes.
-    return $uri;
-  }
-  else {
-    // Attempt to return an external URL using the appropriate wrapper.
-    if ($wrapper = \Drupal::service('stream_wrapper_manager')->getViaUri($uri)) {
-      return $wrapper->getExternalUrl();
-    }
-    else {
-      return FALSE;
-    }
-  }
+  return \Drupal::service('file_url_generator')->generate($uri);
 }
 
 /**
@@ -246,23 +201,12 @@ function file_create_url($uri) {
  *   original value of $file_url.
  *
  * @see file_create_url()
+ *
+ * @deprecated in Drupal 8.4.x and will be removed before Drupal 9.0.0.
+ *   Use\Drupal\Core\File\FileUrlGenerator::transformRelative().
  */
 function file_url_transform_relative($file_url) {
-  // Unfortunately, we pretty much have to duplicate Symfony's
-  // Request::getHttpHost() method because Request::getPort() may return NULL
-  // instead of a port number.
-  $request = \Drupal::request();
-  $host = $request->getHost();
-  $scheme = $request->getScheme();
-  $port = $request->getPort() ?: 80;
-  if (('http' == $scheme && $port == 80) || ('https' == $scheme && $port == 443)) {
-    $http_host = $host;
-  }
-  else {
-    $http_host = $host . ':' . $port;
-  }
-
-  return preg_replace('|^https?://' . $http_host . '|', '', $file_url);
+  return \Drupal::service('file_url_generator')->transformRelative($file_url);
 }
 
 /**
diff --git a/core/lib/Drupal/Core/File/FileUrlGenerator.php b/core/lib/Drupal/Core/File/FileUrlGenerator.php
new file mode 100644
index 0000000..d078119
--- /dev/null
+++ b/core/lib/Drupal/Core/File/FileUrlGenerator.php
@@ -0,0 +1,180 @@
+<?php
+
+namespace Drupal\Core\File;
+
+use Drupal\Component\Utility\Unicode;
+use Drupal\Component\Utility\UrlHelper;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\StreamWrapper\StreamWrapperManagerInterface;
+use Symfony\Component\HttpFoundation\RequestStack;
+
+/**
+ * Generates file URLs.
+ */
+class FileUrlGenerator {
+
+  /**
+   * The file system service.
+   *
+   * @var \Drupal\Core\File\FileSystemInterface
+   */
+  protected $fileSystem;
+
+  /**
+   * The stream wrapper manager.
+   *
+   * @var \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface
+   */
+  protected $streamWrapperManager;
+
+  /**
+   * The request stack.
+   *
+   * @var \Symfony\Component\HttpFoundation\RequestStack
+   */
+  protected $requestStack;
+
+  /**
+   * The module handler.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface
+   */
+  protected $moduleHandler;
+
+  /**
+   * Constructs a new file URL generator object.
+   *
+   * @param \Drupal\Core\File\FileSystemInterface
+   *   The file system service.
+   * @param \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface
+   *   The stream wrapper manager.
+   * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
+   *   The request stack.
+   * @param $module_handler
+   *   The module handler.
+   */
+  public function __construct(FileSystemInterface $file_system, StreamWrapperManagerInterface $stream_wrapper_manager, RequestStack $request_stack, ModuleHandlerInterface $module_handler) {
+    $this->fileSystem = $file_system;
+    $this->streamWrapperManager = $stream_wrapper_manager;
+    $this->requestStack = $request_stack;
+    $this->moduleHandler = $module_handler;
+  }
+
+  /**
+   * Creates a web-accessible URL for a stream to an external or local file.
+   *
+   * Compatibility: normal paths and stream wrappers.
+   *
+   * There are two kinds of local files:
+   * - "managed files", i.e. those stored by a Drupal-compatible stream wrapper.
+   *   These are files that have either been uploaded by users or were generated
+   *   automatically (for example through CSS aggregation).
+   * - "shipped files", i.e. those outside of the files directory, which ship as
+   *   part of Drupal core or contributed modules or themes.
+   *
+   * @param string $uri
+   *   The URI to a file for which we need an external URL, or the path to a
+   *   shipped file.
+   * @param bool $relative
+   *   Set to TRUE to return a URL that is relative to the current request's
+   *   base URI.
+   *
+   * @return string
+   *   A string containing a URL that may be used to access the file.If the
+   *   provided string already contains a preceding 'http', 'https', or '/',
+   *   nothing is done and the same string is returned. If a stream wrapper
+   *   could not be found to generate an external URL, then FALSE is returned.
+   *
+   * @see https://www.drupal.org/node/515192
+   * @see file_url_transform_relative()
+   */
+  public function generate($uri, $relative = FALSE) {
+    // Allow the URI to be altered, e.g. to serve a file from a CDN or static
+    // file server.
+    $this->moduleHandler->alter('file_url', $uri);
+
+    $scheme = $this->fileSystem->uriScheme($uri);
+
+    if (!$scheme) {
+      // Allow for:
+      // - root-relative URIs (e.g. /foo.jpg in http://example.com/foo.jpg)
+      // - protocol-relative URIs (e.g. //bar.jpg, which is expanded to
+      //   http://example.com/bar.jpg by the browser when viewing a page over
+      //   HTTP and to https://example.com/bar.jpg when viewing a HTTPS page)
+      // Both types of relative URIs are characterized by a leading slash, hence
+      // we can use a single check.
+      if (Unicode::substr($uri, 0, 1) == '/') {
+        return $uri;
+      }
+      else {
+        $base_url = $this->requestStack->getCurrentRequest()->getSchemeAndHttpHost() . base_path();
+        // If this is not a properly formatted stream, then it is a shipped
+        // file. Therefore, return the urlencoded URI with the base URL
+        // prepended.
+        $options = UrlHelper::parse($uri);
+        $path = $base_url . UrlHelper::encodePath($options['path']);
+        // Append the query.
+        if ($options['query']) {
+          $path .= '?' . UrlHelper::buildQuery($options['query']);
+        }
+
+        // Append fragment.
+        if ($options['fragment']) {
+          $path .= '#' . $options['fragment'];
+        }
+
+        return $path;
+      }
+    }
+    elseif ($scheme == 'http' || $scheme == 'https' || $scheme == 'data') {
+      // Check for HTTP and data URI-encoded URLs so that we don't have to
+      // implement getExternalUrl() for the HTTP and data schemes.
+      if ($relative) {
+        return $this->transformRelative($uri);
+      }
+      return $uri;
+    }
+    else {
+      // Attempt to return an external URL using the appropriate wrapper.
+      if ($wrapper = $this->streamWrapperManager->getViaUri($uri)) {
+        return $wrapper->getExternalUrl();
+      }
+      else {
+        return FALSE;
+      }
+    }
+  }
+
+  /**
+   * Transforms an absolute URL of a local file to a relative URL.
+   *
+   * May be useful to prevent problems on multisite set-ups and prevent mixed
+   * content errors when using HTTPS + HTTP.
+   *
+   * @param string $file_url
+   *   A file URL of a local file as generated by file_create_url().
+   *
+   * @return string
+   *   If the file URL indeed pointed to a local file and was indeed absolute,
+   *   then the transformed, relative URL to the local file. Otherwise: the
+   *   original value of $file_url.
+  */
+  public function transformRelative($file_url) {
+    // Unfortunately, we pretty much have to duplicate Symfony's
+    // Request::getHttpHost() method because Request::getPort() may return NULL
+    // instead of a port number.
+    $request = $this->requestStack->getCurrentRequest();
+    $host = $request->getHost();
+    $scheme = $request->getScheme();
+    $port = $request->getPort() ?: 80;
+    if (('http' == $scheme && $port == 80) || ('https' == $scheme && $port == 443)) {
+      $http_host = $host;
+    }
+    else {
+      $http_host = $host . ':' . $port;
+    }
+
+    return preg_replace('|^https?://' . $http_host . '|', '', $file_url);
+  }
+
+}
