jpegoptim на ASP.Net - "Ошибка открытия временного файла"
Я подозреваю, что не понимаю, где jpegoptim пытается записать свои временные файлы.
У меня есть IIS 7.5 под управлением ASP.Net 4 AppDomain. В нем у меня есть процесс, который оптимизирует JPEG с помощью jpegoptim следующим образом:
FileHelper.Copy(existingPath, optimizerPath);
var jpegOptimResult = await ImageHelper.JpegOptim(optimizerPath, 30);
Работая локально, я получаю оптимизированное изображение. Запустив на вышеуказанном сервере я получаю:
D: \ www \hplusf.com\ b \ pc \ test.jpg 4096x2990 24bit N Adobe [OK] jpegoptim: ошибка открытия временного файла.
Я могу показать код для FileHelper.Copy()
, но это в основном просто File.Copy()
это перезаписывает, если файл уже существует.
Вот ImageHelper.JpegOptim:
public static async Task<string> JpegOptim(string path, int quality)
{
string jpegOptimPath = Path.GetDirectoryName(new Uri(Assembly
.GetExecutingAssembly().CodeBase).LocalPath)
+ @"\Lib\jpegoptim.exe";
var jpegOptimResult = await ProcessRunner.O.RunProcess(
jpegOptimPath,
"-m" + quality + " -o -p --strip-all --all-normal \"" + path + "\"",
false, true
);
return jpegOptimResult;
}
jpegOptimResult - это то, что вы видите там как сообщение об ошибке, которое оно выдает. А вот ProcessRunner.RunProcess:
public async Task<string> RunProcess(string command, string args,
bool window, bool captureOutput)
{
var processInfo = new ProcessStartInfo(command, args);
if (!window)
makeWindowless(processInfo);
string output = null;
if (captureOutput)
output = await runAndCapture(processInfo);
else
runDontCapture(processInfo);
return output;
}
protected void makeWindowless(ProcessStartInfo processInfo)
{
processInfo.CreateNoWindow = true;
processInfo.WindowStyle = ProcessWindowStyle.Hidden;
}
protected async Task<string> runAndCapture(ProcessStartInfo processInfo)
{
processInfo.UseShellExecute = false;
processInfo.RedirectStandardOutput = true;
processInfo.RedirectStandardError = true;
var process = Process.Start(processInfo);
var output = process.StandardOutput;
var error = process.StandardError;
while (!process.HasExited)
{
await Task.Delay(100);
}
string s = output.ReadToEnd();
s += '\n' + error.ReadToEnd();
return s;
}
Так:
jpegOptim правильно работает на моем локальном компьютере и оптимизирует файл, поэтому я не так называю jpegOptim.
Операция копирования успешно выполняется без исключения, так что это не проблема с разрешениями для пользователя ASP.Net, читающего / пишущего из этого каталога
jpegOptim просто оптимизирует и перезаписывает файл, поэтому, если он действительно работает под тем же пользователем ASP.Net, у него не должно возникнуть проблем при записи этого файла, но...
Неясно, где jpegOptim пытается записать свой временный файл, поэтому, возможно, основная проблема заключается в том, где записывается этот временный файл.
Однако, судя по источнику Windows:
http://sourceforge.net/p/jpegoptim/code/HEAD/tree/jpegoptim-1.3.0/trunk/jpegoptim.c
"Временный файл" jpegOptim, кажется, просто файл назначения, когда используется с вышеуказанными опциями. Соответствующие строки источника jpegOptim:
int dest = 0;
int main(int argc, char **argv)
{
...
Здесь есть какой-то код, ищущий аргумент -d, который устанавливает dest=1 - то есть здесь dest остается 0. Он затем попадает в ветвь if, и предложение else для dest == 0 делает это:
if (!splitdir(argv[i],tmpdir,sizeof(tmpdir)))
fatal("splitdir() failed!");
strncpy(newname,argv[i],sizeof(newname));
Это копирует часть имени каталога входного имени файла изображения в переменную tmpdir
- так что C:\Blah\18.jpg присваивает tmpdir="C:\Blah\"
, Затем он сбрасывает все имя файла входного изображения в newname
Это означает, что он просто перезапишет его на месте.
На данный момент в коде переменные, которые он использует, должны быть:
dest=0
argv[i]=D:\www\hplusf.com\b\pc\test.jpg
tmpdir=D:\www\hplusf.com\b\pc\
newname=D:\www\hplusf.com\b\pc\test.jpg
Затем он фактически открывает файл, и есть возможность сделать ошибку, предполагая, что jpegoptim успешно открывает файл. Он также распаковывает файл, подтверждая, что он успешно открывается.
Конкретное сообщение об ошибке, которое я вижу, появляется в этих строках - признаюсь, я не знаю, установлен ли MKSTEMPS для сборки по умолчанию (которую я использую):
snprintf(tmpfilename,sizeof(tmpfilename),
"%sjpegoptim-%d-%d.XXXXXX.tmp", tmpdir, (int)getuid(), (int)getpid());
#ifdef HAVE_MKSTEMPS
if ((tmpfd = mkstemps(tmpfilename,4)) < 0)
fatal("error creating temp file: mkstemps() failed");
if ((outfile=fdopen(tmpfd,"wb"))==NULL)
#else
tmpfd=0;
if ((outfile=fopen(tmpfilename,"wb"))==NULL)
#endif
fatal("error opening temporary file");
Так snprintf
это как C# String.Format()
, который должен производить путь как:
D: \ WWW \hplusf.com\ Ь \ пк \jpegoptim-1-2.XXXXXX.tmp
Судя по тому что я могу найти это скорее всего MKSTEMPS не имеет определенного значения fopen
вызывается с помощью "wb", означающего, что он записывает двоичный файл, и возвращает нулевое значение, что означает, что он не открывается, и появляется сообщение об ошибке.
Итак - возможные причины:
Неверный путь в tmpdir Возможно, я плохо следую за C++ (вероятно), но, судя по всему, он должен быть идентичен исходному пути изображения. Но, возможно, он искажен для tmpdir, от jpegoptim? Путь ввода явно чистый, потому что jpegoptim фактически выдает его чисто в сообщении об ошибке.
Проблема с разрешениями кажется маловероятной. Пользователь ASP.Net, под которым он работает, может четко читать и писать, потому что он копирует в каталог до запуска jpegoptim, и единственный пользователь на машине, у которого есть какие-либо разрешения для этого каталога, - это пользователь, поэтому jpegoptim должен был произойти сбой до этого момента. если бы это были разрешения. Возможно, он пытается получить доступ к другому каталогу, но это действительно будет сценарий Bad tmpdir.
Что-то еще, о чем я не думал.
Идеи?
Примечание: этот вопрос похож:
Использование jpegtran, jpegoptim или другой оптимизации / сжатия jpeg в C#
Тем не менее, этот вопрос задает вопрос о совместном окружении в GoDaddy, что приводит к тому, что ответы закручиваются вокруг вероятности того, что он не сможет ускорить процессы. Мы полностью контролируем наш сервер, и, как должно быть ясно из вышесказанного, процесс jpegoptim определенно запускается успешно, так что это другой сценарий.
1 ответ
Как оказалось, мое чтение jpegoptim было неверным. Используемый tmpdir - это то место, куда указывает рабочий каталог исполняемого файла, а не место, где находятся входные образы, и место, где расположен исполняемый файл. Итак, решение было в 2 раза:
- Дайте exe-разрешениям на запись в свой собственный каталог * (но запретите ему доступ для изменения самого себя)
- Измените ProcessRunner для запуска процессов на месте - установите рабочий каталог, в котором находится исполняемый файл.
Вторая модификация выглядит так:
var processInfo = new ProcessStartInfo(command, args);
// Ensure the exe runs in the path where it sits, rather than somewhere
// less safe like the website root
processInfo.WorkingDirectory = (new FileInfo(command)).DirectoryName;
* Примечание: я случайно изолировал jpegoptim.exe на сервере от своего собственного каталога для ограничения риска. Если у вас это было где-то более глобально, например, Program Files, вам определенно не следует этого делать - вместо этого установите Рабочий каталог, как указано выше, но в каком-то изолированном / безопасном месте, например, в tmp dir или, что еще лучше, на "чистый диск". Если у вас есть оперативная память для него, RAMdrive будет самым быстрым.
** Второе примечание: из-за того, как работают жесткие диски и jpegoptim, если местоположение tmp не совпадает с конечным местом назначения вывода, существует потенциальное условие частичной гонки между jpegoptim и другим используемым вами кодом, которое зависит от его выводы. В частности, если вы используете тот же диск, когда jpegoptim завершен, вывод JPEG завершен - ОС изменяет запись в своей файловой таблице, но данные для изображения на жестком диске уже записаны до конца. Когда tmp и destination являются отдельными дисками, jpegoptim завершает работу, сообщая ОС, что нужно перейти от tmpdir к выходному каталогу. Это перемещение данных заканчивается через некоторое время после завершения работы jpegoptim. Если ваш код ожидания достаточно быстрый, он начнет работать с неполным JPEG.