Контекст getRealPath () для несуществующего файла - PullRequest
3 голосов
/ 06 марта 2020

Может кто-нибудь объяснить, в чем разница между следующими двумя вызовами ServletContext getRealPath () в Tomcat:

  • context.getRealPath("/") + "\\songModified.wav";
  • context.getRealPath("/" + "\\songModified.wav");

У меня есть очень простой метод GET на сервере, который читает файл на сервере и копирует байты в новый файл в месте, возвращаемом вышеуказанным вызовом.

На стороне клиента у меня есть аудио-тег, который ссылается на аудио-файл на сервере, вызывает этот метод, который создает новый файл и изменяет ссылку на аудио-тег на этот новый файл. Дело в том, что в обратном вызове javascript этот новый файл не может быть немедленно ссылаться, если я сохраню файл по пути, который возвращается из второго случая вышеупомянутого вызова getRealPath. По сути, он возвращает 404. Если я сохраню его по возвращенному пути первого случая вызова, он сразу же станет ссылочным, а звуковой тег обычно ссылается на новый файл.

Оба этих вызова getRealPath () возвращают точно такую ​​же строку:

C:\Users\Mihael\apache-tomcat-9.0.31\wtpwebapps\AudioSimulator\songModified.wav

Я передаю эту возвращенную строку конструктору FileOutputStream далее в коде.

Здесь следует отметить, что этот файл не существует в момент вызова getRealPath (), поэтому я не понимаю, почему он вообще что-либо возвращает во втором случае вызова.

Я знаю, что это не рекомендуемый способ хранения файлов, поэтому я спрашиваю с чисто образовательной точки зрения. Как может второй вызов этого метода нарушить мою функциональность, если они оба возвращают точно ту же строку для остальной части кода?

РЕДАКТИРОВАТЬ:

Вот очень простые Javascript и Java код для тех, кто хочет это проверить.

Javascript:

<body>
<script>

function modifyRequest() {
    var xhttp = new XMLHttpRequest();

    xhttp.onload = function() {
      var audio = document.getElementById("player");
      var currentTime = audio.currentTime;
      audio.src = "http://localhost:8080/AudioSimulator/bluesModified.wav";
      audio.currentTime = currentTime;
      audio.play();
    };

    xhttp.open("GET", "http://localhost:8080/AudioSimulator/rest/Test/testPath");  
    xhttp.send();
}

</script>

<audio id="player" src="http://localhost:8080/AudioSimulator/blues.wav"
        controls>
            Your browser does not support the
            <code>audio</code> element.
    </audio>    

    <button onclick="modifyRequest()">Test</button>

</body>

Java:

    @Path("/Test")
public class Test {

    @Context
    ServletContext context;

    @GET
    @Path("/testPath")
    public Response testPath() {
        File fileIn = new File(context.getRealPath("/") + "\\blues.wav");
        File fileOut = new File(context.getRealPath("/" + "\\bluesModified.wav"));
        //if i write it like this it would work
        //File fileOut = new File(context.getRealPath("/") + "\\bluesModified.wav");

        FileInputStream fis = null;
        FileOutputStream fos = null;

        try {
            fis = new FileInputStream(fileIn);
            fos = new FileOutputStream(fileOut);
            byte[] inArray = new byte[(int) fileIn.length()];
            try {
                fis.read(inArray);
                fos.write(inArray);
            } catch (IOException e) {
                e.printStackTrace();
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } finally {
            try {
                fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                fis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        return Response
                .ok()
                .entity("Success")
                .header("Access-Control-Allow-Origin", "null")
                .build();
    }

}

Ответы [ 2 ]

1 голос
/ 21 апреля 2020

Я потратил время, чтобы погрузиться в источник Tomcat, чтобы найти причину этого. Оказывается, что getRealPath, в дополнение к извлечению системного пути для заданного виртуального пути, также немного работает с кэшем Tomcat.

ПРИМЕЧАНИЕ:

Я знаю, что мое использование разделителя файлов не очень хорошо, но Tomcat достаточно умен, чтобы проверить приведенный выше вызов для получения /bluesModified.wav. Поэтому, даже если я назову это как @rickz, упомянутое в комментариях, результат будет таким же, и поэтому проблема не в этом.

Проблемы, с которыми я не смог сослаться на файл в в случае следующего вызова

context.getRealPath("/" + "\\bluesModified.wav")

был факт, что в этом случае мы передаем путь к файлу методу, а в случае, если это работает, мы передаем путь к каталогу.

Что происходит, так это то, что вызов getRealPath() сначала проверяет кеш на наличие ресурса, идентифицируемого webapppath /bluesModified.wav. Так как он не существует на момент вызова, Tomcat создаст экземпляр класса EmptyResource, который в основном является оберткой вокруг класса File и представляет файл, который не существует, и затем будет хранить ссылку на этот файл в кеше.

Проблема здесь в том, что, хотя я создаю файл с правильным виртуальным путем, Tomcat все равно будет иметь этот пустой ресурс, представляющий несуществующий файл в своем кэше. Другими словами, если я ссылаюсь на файл со стороны клиента следующим образом:

http://localhost:8080/AudioSimulator/bluesModified.wav

Tomcat вернет кэшированный ресурс, представляющий пустой файл, что на самом деле означает 404 для клиент, даже если файл существует.

Ожидание в течение 5 секунд, то есть времени жизни записей кэша Tomcat, и затем попытка ссылки на файл приведет к повторной проверке записи кэша и выдаст FileResource вместо EmptyResource в этом случае ссылки будут работать нормально.

В этом случае это работает

context.getRealPath("/") + "\\bluesModified.wav"

, поскольку путь кеширования - это каталог, а имя файла просто объединяется. Таким образом, строка, которую я здесь имею, представляет собой просто абсолютный путь к файлу, который я собираюсь создать, без коллизий записей кэша.

Моя ошибка заключалась в том, что getRealPath() - это просто какой-то "чистый" метод, который будет вернуть строку, которую я могу использовать для создания файлов, хотя на самом деле она имеет побочные эффекты. Эти побочные эффекты не задокументированы, и, хотя я мог бы сделать некоторые вещи неправильно, суть в том, что этот метод не настолько предсказуем, чтобы использовать его при работе с File IO.

1 голос
/ 20 апреля 2020

Строка, возвращаемая getRealPath из реализации ServletContext, нормализуется.

Поэтому, когда вы вызываете getRealPath ("/") + "\ blues.wav") , нормализуется только строка "/", а конкатенация строк "\ голубая. wav " не является.

Но когда вы вызываете getRealPath (" / "+" \ blues.wav ")) полная конкатенированная строка нормализуется.

    public String getRealPath(String path) {
    if ("".equals(path)) {
        path = "/";
    }

    if (this.resources != null) {
        try {
            WebResource resource = this.resources.getResource(path);
            String canonicalPath = resource.getCanonicalPath();
            if (canonicalPath == null) {
                return null;
            }

            if ((resource.isDirectory() && !canonicalPath.endsWith(File.separator) || !resource.exists()) && path.endsWith("/")) {
                return canonicalPath + File.separatorChar;
            }

            return canonicalPath;
        } catch (IllegalArgumentException var4) {
        }
    }

    return null;
}

Вы можете увидеть Ресурс WebResource = this.resources.getResource (путь) попытается проверить ваш путь и вернет проверенный путь:

    private String validate(String path) {
    if (!this.getState().isAvailable()) {
        throw new IllegalStateException(sm.getString("standardRoot.checkStateNotStarted"));
    } else if (path != null && path.length() != 0 && path.startsWith("/")) {
        String result;
        if (File.separatorChar == '\\') {
            result = RequestUtil.normalize(path, true);
        } else {
            result = RequestUtil.normalize(path, false);
        }

        if (result != null && result.length() != 0 && result.startsWith("/")) {
            return result;
        } else {
            throw new IllegalArgumentException(sm.getString("standardRoot.invalidPathNormal", new Object[]{path, result}));
        }
    } else {
        throw new IllegalArgumentException(sm.getString("standardRoot.invalidPath", new Object[]{path}));
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...