Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8235459

HttpRequest.BodyPublishers#ofFile(Path) assumes the default file system

    Details

    • Subcomponent:
    • Resolved In Build:
      b17
    • CPU:
      x86_64
    • OS:
      windows_10

      Description

      ADDITIONAL SYSTEM INFORMATION :
      C:\java>systeminfo | findstr /B /C:"OS Name" /C:"OS Version"
      OS Name: Microsoft Windows 10 Pro
      OS Version: 10.0.18362 N/A Build 18362

      C:\java>java --version
      openjdk 13.0.1 2019-10-15
      OpenJDK Runtime Environment (build 13.0.1+9)
      OpenJDK 64-Bit Server VM (build 13.0.1+9, mixed mode, sharing)

      A DESCRIPTION OF THE PROBLEM :
      HttpRequest.BodyPublishers#ofFile(Path) assumes that the specified file lives in the default file system. If the file lives in a different file system, an UnsupportedOperationException is thrown.

      This is because the current BodyPublisher implementation calls Path#toFile() on the file in an attempt to convert it to a java.io.File, and that throws UOE. This line throws:
      https://github.com/openjdk/jdk/blob/5845912fdbffd09a8d8dd0e3f766137a4939e34e/src/java.net.http/share/classes/jdk/internal/net/http/RequestPublishers.java#L265

      Theoretically there is no reason the implementation should need to convert the file to a java.io.File or otherwise assume the default file system is being used.

      Use cases for non-default file systems in this context include:

      (1) A zip file system, if you did Files.newFileSystem(zipFile) and wanted to upload one of the files contained within the zip file in an HTTP request.

      (2) An in-memory file system like the one provided by the Jimfs library, which is useful during tests when you want to ensure that no debris is left on the real file system.

      I ran into this while doing (2). To work around the issue I'm using BodyPublishers.ofInputStream instead.

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      1. Have a non-default FileSystem such as a zip file system or a Jimfs file system.
      2. Have or create a file within that file system.
      3. Invoke HttpRequest.BodyPublishers.ofFile(file).

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      BodyPublishers.ofFile(file) should return a working BodyPublisher.
      ACTUAL -
      BodyPublishers.ofFile(file) throws UOE.

      ---------- BEGIN SOURCE ----------
      // Maven coordinates of third-party libraries used:
      //
      // com.google.jimfs:jimfs:1.1
      // io.undertow:undertow-core:2.0.28.Final

      package example;

      import static java.nio.charset.StandardCharsets.UTF_8;

      import com.google.common.jimfs.Configuration;
      import com.google.common.jimfs.Jimfs;
      import io.undertow.Undertow;
      import io.undertow.server.handlers.BlockingHandler;
      import java.io.BufferedOutputStream;
      import java.io.FileNotFoundException;
      import java.io.IOException;
      import java.io.UncheckedIOException;
      import java.net.URI;
      import java.net.http.HttpClient;
      import java.net.http.HttpRequest;
      import java.net.http.HttpRequest.BodyPublisher;
      import java.net.http.HttpRequest.BodyPublishers;
      import java.net.http.HttpResponse;
      import java.nio.file.FileSystems;
      import java.nio.file.Files;
      import java.nio.file.Path;
      import java.util.function.Supplier;
      import java.util.zip.ZipOutputStream;

      public class FilePublisherTest {

        public static void main(String[] args) throws Exception {
          var requestHandler =
              new BlockingHandler(
                  exchange -> {
                    var bytes = exchange.getInputStream().readAllBytes();
                    var string = new String(bytes, UTF_8);
                    System.out.println(
                        "\t\t...server received file with contents: \""
                            + string
                            + "\"");
                  });

          var server =
              Undertow.builder()
                      .addHttpListener(8080, "localhost")
                      .setHandler(requestHandler)
                      .build();

          server.start();
          testDefaultFs();
          testJimFs();
          testZipFs();
          server.stop();
        }

        // this works
        private static void testDefaultFs() throws Exception {
          var file = Files.createTempFile("FilePublisherTest", ".txt");
          try {
            Files.writeString(file, "default fs");
            sendFile(file);
          } finally {
            Files.deleteIfExists(file);
          }
        }

        // this fails, prints UOE stack trace
        private static void testJimFs() throws Exception {
          try (var fs = Jimfs.newFileSystem(Configuration.unix())) {
            var file = fs.getPath("example.txt");
            Files.writeString(file, "in-memory fs");
            sendFile(file);
          }
        }

        // this fails, prints UOE stack trace
        private static void testZipFs() throws Exception {
          var zipFile = Files.createTempFile("FilePublisherTest", ".zip");
          try {

            // create an empty zip file
            try (var fos = Files.newOutputStream(zipFile);
                 var bos = new BufferedOutputStream(fos);
                 var zos = new ZipOutputStream(bos)) {}

            try (var fs = FileSystems.newFileSystem(zipFile)) {
              var file = fs.getPath("example.txt");
              Files.writeString(file, "zip fs");
              sendFile(file);
            }
          } finally {
            Files.deleteIfExists(zipFile);
          }
        }

        private static void sendFile(Path file)
            throws IOException, InterruptedException {

          Supplier<BodyPublisher> ofFile =
              () -> {
                try {
                  return BodyPublishers.ofFile(file);
                } catch (FileNotFoundException e) {
                  throw new UncheckedIOException(e);
                }
              };

          Supplier<BodyPublisher> ofInputStream =
              () -> {
                return BodyPublishers.ofInputStream(() -> {
                  try {
                    return Files.newInputStream(file);
                  } catch (IOException e) {
                    throw new UncheckedIOException(e);
                  }
                });
              };

          System.out.println("sending file");
          System.out.println("\tfile: " + file);
          System.out.println("\tfile system: " + file.getFileSystem());
          System.out.println("\tfile contents: \"" + Files.readString(file) + "\"");

          System.out.println("\ttrying BodyPublishers.ofFile...");
          try {
            sendFile(ofFile);
            System.out.println("\t\t...success!");
          } catch (UnsupportedOperationException e) {
            System.out.println("\t\t...failed with UOE");
            e.printStackTrace(System.out);
          }

          System.out.println("\ttrying BodyPublishers.ofInputStream...");
          try {
            sendFile(ofInputStream);
            System.out.println("\t\t...success!");
          } catch (UnsupportedOperationException e) {
            System.out.println("\t\t...failed with UOE");
            e.printStackTrace(System.out);
          }
        }

        private static void sendFile(Supplier<BodyPublisher> supplier)
            throws IOException, InterruptedException {

          var client = HttpClient.newHttpClient();
          var uri = URI.create("http://localhost:8080/file_publisher");
          var publisher = supplier.get(); // might throw UOE
          var request = HttpRequest.newBuilder(uri).POST(publisher).build();
          var handler = HttpResponse.BodyHandlers.discarding();
          var response = client.send(request, handler);
        }
      }

      ---------- END SOURCE ----------

      CUSTOMER SUBMITTED WORKAROUND :
      Open the Path as an InputStream and send it using BodyPublishers.ofInputStream.

        Attachments

          Issue Links

            Activity

              People

              • Assignee:
                jboes Julia Boes
                Reporter:
                webbuggrp Webbug Group
              • Votes:
                0 Vote for this issue
                Watchers:
                5 Start watching this issue

                Dates

                • Created:
                  Updated:
                  Resolved: