ASP.Net上的jpegoptim - "错误打开临时文件"

时间:2014-10-28 20:28:55

标签: c# c++ asp.net jpegoptim

我怀疑我无法理解jpegoptim尝试编写临时文件的位置。

我有IIS 7.5运行ASP.Net 4 AppDomain。在其中我有一个使用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 [确定] 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&#34;临时文件&#34;与上述选项一起使用时,它似乎只是目标文件。 jpegOptim源的相关行:

int dest = 0;

int main(int argc, char **argv) 
{
    ...

这里有一些代码正在寻找设置dest = 1的-d参数 - 这意味着dest保持为0.然后命中if分支,而dest == 0的else子句执行此操作:

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正在调用&#34; wb&#34;这意味着它正在编写一个二进制文件,并且它返回null意味着它无法打开,并且输出错误消息。

所以 - 可能的原因:

  • tmpdir中的错误路径我可能在C ++之后很可能(可能),但从它的外观来看,它应该与源相同图像的路径。但也许是因为jpegoptim对tmpdir造成了损害?输入路径显然是干净的,因为jpegoptim实际上在错误消息中干净地发出它。

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

  • 我还没有想到的其他事情。

想法?

注意:这个问题类似:

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

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

1 个答案:

答案 0 :(得分:1)

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

  1. 授予exe权限以写入其自己的目录*(但拒绝其访问以自行修改)
  2. 修改ProcessRunner以就地运行进程 - 将工作目录设置为exe所在的位置。
  3. 第二个修改如下:

    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移动到输出目录来完成。这是一个数据移动,在jpegoptim运行完成后的某个时间结束。如果您的等待代码足够快,它将以不完整的JPEG开始工作。