1

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

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

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

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

D:\www\hplusf.com\b\pc\test.jpg 4096x2990 24bit N Adob​​e [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) 
{
    ...

这里有一些代码在寻找设置 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

从我能发现的情况来看,很可能 MKSTEMPS 未定义,意思fopen是用“wb”调用,这意味着它正在编写一个二进制文件,它返回 null 意味着它无法打开,并且出现错误消息。

所以 - 可能的原因:

  • tmpdir 中的路径错误 可能我对 C++ 的使用不佳(可能),但是从外观上看,它应该与图像的源路径相同。但也许它被 jpegoptim 破坏为 tmpdir 了?输入路径显然是干净的,因为 jpegoptim 实际上在错误消息中干净地发出它。

  • 权限问题似乎不太可能。正在运行的 ASP.Net 用户可以清楚地读取和写入,因为它在 jpegoptim 触发之前复制到该目录,并且机器上唯一对该目录具有任何权限的用户是该用户,因此 jpegoptim 在此之前应该已经失败了如果是权限。它可能正在尝试访问不同的目录,但这确实是 Bad tmpdir 场景。

  • 还有一些我没有想到的。

想法?

注意:这个问题类似:

在 C# 中使用 jpegtran、jpegoptim 或其他 jpeg 优化/压缩

然而,这个问题是关于 GoDaddy 上的共享环境,导致答案围绕着他无法启动进程的可能性。我们可以完全控制我们的服务器,从上面可以清楚地看出,jpegoptim 进程肯定是成功启动的,所以这是一个不同的场景。

4

1 回答 1

2

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

  1. 授予 exe 写入其自己目录的权限*(但拒绝其修改自身的权限)
  2. 修改 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 将是最快的。

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

于 2014-10-29T04:55:11.700 回答