我怀疑我无法理解jpegoptim尝试在何处写入其临时文件。

我有运行ASP.Net 4 AppDomain的IIS 7.5。在其中,我有一个使用jpegoptim优化JPEG的过程,如下所示:

FileHelper.Copy(existingPath, optimizerPath);
var jpegOptimResult = await ImageHelper.JpegOptim(optimizerPath, 30);

在本地运行,我得到了优化的图像。在上面的服务器上运行我得到:



我可以显示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)
    {
        ...
    

    这里有一些代码正在寻找设置dest = 1的-d参数-这意味着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 \ b \ pc \ jpegoptim-1-2.XXXXXX.tmp

    从我发现的情况来看,可能 undefined MKSTEMPS,这意味着fopen被调用为“wb”,这意味着它正在写入二进制文件,并且返回null意味着它无法打开,并出现错误消息。

    所以-可能的原因:
  • tmpdir中的路径错误(可能),我对C++的理解不佳,但是从外观上看,它应该与图像的源路径相同。但是也许它是由jpegoptim的tmpdir破坏的?输入路径显然干净,因为jpegoptim实际上在错误消息中干净地发出了它。
  • 权限问题似乎不太可能。运行该ASP.Net的用户可以清楚地进行读写操作,因为它在jpegoptim触发之前已复制到dir,并且计算机上对该目录具有任何权限的唯一用户是该用户,因此jpegoptim应该在此之前失败如果是权限。可能正在尝试访问其他目录,但这确实是Bad tmpdir方案。
  • 我还没想到的其他事情。

  • 有想法吗?

    注意:此问题类似:

    Using jpegtran, jpegoptim, or other jpeg optimization/compression in C#

    但是,这个问题询问的是GoDaddy上的共享环境,导致围绕他无法启动进程的可能性的答案不断增加。我们已经完全控制了我们的服务器,并且从上面的内容应该可以清楚地看到,jpegoptim进程肯定已成功启动,因此是另一种情况。

    最佳答案

    事实证明,我对jpegoptim的阅读不正确。它使用的tmpdir是可执行文件的工作目录指向的位置,而不是输入镜像的位置,也不是可执行文件所在的位置。因此,解决方案是2倍:

  • 授予exe权限以写入其自己的目录*(但拒绝其修改自身的访问权限)
  • 修改ProcessRunner以就地运行进程-将工作目录设置为exe所在的位置。

  • 第二个修改如下所示:
    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隔离到其自己的目录,以限制风险。如果您将它放在某个更全局的位置(如“程序文件”),则绝对不应该这样做-而是如上所述设置工作目录,而应将其放置在隔离的/安全的位置,例如tmp目录或什至更好的暂存盘。如果您拥有RAM,则RAMdrive将是最快的。

    **第二注:由于如果tmp位置与输出的最终目的地不在同一磁盘上,那么硬盘驱动器和jpegoptim的工作方式是如何,因此jpegoptim与您可能使用的其他代码之间可能会引入部分竞争条件,具体取决于它的输出。特别是如果您使用同一张磁盘,则完成jpegoptim后,输出JPEG将完成-操作系统会更改其文件表中的条目,但硬盘驱动器上的图像数据已写入完毕。当tmp和destination是单独的磁盘时,jpegoptim会告诉操作系统将其从tmpdir移至输出dir,从而完成此操作。这是在jpegoptim完成运行后的某个时间完成的数据移动。如果您的等待代码足够快,它将以不完整的JPEG开始工作。

    关于c# - ASP.Net上的jpegoptim- “error opening temporary file”,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/26617890/

    10-09 06:24