C
C#6mo ago
Halfbax

Pipe is being closed. Trying to pass request body to php process

I am trying to open a php process in my kestrel middleware. Without body everything works fine, but sending a body throws the exception Pipe is being closed. Do you know why?
try
{
Process phpProcess = new()
{
StartInfo = new ProcessStartInfo
{
FileName = _phpInterpreterPath,
Arguments = $"\"{filePath}\"",
RedirectStandardOutput = true,
RedirectStandardError = true,
RedirectStandardInput = true,
UseShellExecute = false,
CreateNoWindow = true,
Environment =
{
// ENV VARS
}
}
};

phpProcess.Start();

// Pass the request body to the PHP process
if (context.Request.ContentLength > 0 || context.Request.ContentType != null)
{
await using StreamWriter sw = new(phpProcess.StandardInput.BaseStream);
await context.Request.Body.CopyToAsync(sw.BaseStream);
await sw.FlushAsync(); // EXCEPTION IS BEING THROWN HERE
}

// Capture the output and errors
var outputTask = phpProcess.StandardOutput.ReadToEndAsync();
var errorTask = phpProcess.StandardError.ReadToEndAsync();

await phpProcess.WaitForExitAsync();

// RESPONSE HANDLING
}
catch (Exception ex)
{
// EXCEPTION HANDLING
}
try
{
Process phpProcess = new()
{
StartInfo = new ProcessStartInfo
{
FileName = _phpInterpreterPath,
Arguments = $"\"{filePath}\"",
RedirectStandardOutput = true,
RedirectStandardError = true,
RedirectStandardInput = true,
UseShellExecute = false,
CreateNoWindow = true,
Environment =
{
// ENV VARS
}
}
};

phpProcess.Start();

// Pass the request body to the PHP process
if (context.Request.ContentLength > 0 || context.Request.ContentType != null)
{
await using StreamWriter sw = new(phpProcess.StandardInput.BaseStream);
await context.Request.Body.CopyToAsync(sw.BaseStream);
await sw.FlushAsync(); // EXCEPTION IS BEING THROWN HERE
}

// Capture the output and errors
var outputTask = phpProcess.StandardOutput.ReadToEndAsync();
var errorTask = phpProcess.StandardError.ReadToEndAsync();

await phpProcess.WaitForExitAsync();

// RESPONSE HANDLING
}
catch (Exception ex)
{
// EXCEPTION HANDLING
}
9 Replies
occluder
occluder6mo ago
.StandardInput of phpProcess is already a StreamWriter, try eliminating the await using StreamWriter sw = new(phpProcess.StandardInput.BaseStream);
Halfbax
HalfbaxOP6mo ago
@occluder Like this?
if (context.Request.ContentLength > 0 || context.Request.ContentType != null)
{
using MemoryStream memoryStream = new();
await context.Request.Body.CopyToAsync(memoryStream);
memoryStream.Seek(0, SeekOrigin.Begin);

await phpProcess.StandardInput.BaseStream.WriteAsync(memoryStream.ToArray(), 0, (int)memoryStream.Length);
await phpProcess.StandardInput.FlushAsync();
}
if (context.Request.ContentLength > 0 || context.Request.ContentType != null)
{
using MemoryStream memoryStream = new();
await context.Request.Body.CopyToAsync(memoryStream);
memoryStream.Seek(0, SeekOrigin.Begin);

await phpProcess.StandardInput.BaseStream.WriteAsync(memoryStream.ToArray(), 0, (int)memoryStream.Length);
await phpProcess.StandardInput.FlushAsync();
}
Same exception
The pipe is being closed.

at System.IO.RandomAccess.WriteAtOffset(SafeFileHandle handle, ReadOnlySpan`1 buffer, Int64 fileOffset)
at System.IO.RandomAccess.<>c.<WriteAtOffsetAsync>b__21_0(ValueTuple`4 state)
at System.Threading.AsyncOverSyncWithIoCancellation.InvokeAsync[TState](Action`1 action, TState state, CancellationToken cancellationToken)
at System.Runtime.CompilerServices.PoolingAsyncValueTaskMethodBuilder`1.StateMachineBox`1.System.Threading.Tasks.Sources.IValueTaskSource.GetResult(Int16 token)
at System.IO.Strategies.BufferedFileStreamStrategy.WriteToNonSeekableAsync(ReadOnlyMemory`1 source, CancellationToken cancellationToken)
at ProjectName.ReverseProxy.Middlewares.PhpMiddleware.ExecutePhpFileAsync(HttpContext context, String filePath)
The pipe is being closed.

at System.IO.RandomAccess.WriteAtOffset(SafeFileHandle handle, ReadOnlySpan`1 buffer, Int64 fileOffset)
at System.IO.RandomAccess.<>c.<WriteAtOffsetAsync>b__21_0(ValueTuple`4 state)
at System.Threading.AsyncOverSyncWithIoCancellation.InvokeAsync[TState](Action`1 action, TState state, CancellationToken cancellationToken)
at System.Runtime.CompilerServices.PoolingAsyncValueTaskMethodBuilder`1.StateMachineBox`1.System.Threading.Tasks.Sources.IValueTaskSource.GetResult(Int16 token)
at System.IO.Strategies.BufferedFileStreamStrategy.WriteToNonSeekableAsync(ReadOnlyMemory`1 source, CancellationToken cancellationToken)
at ProjectName.ReverseProxy.Middlewares.PhpMiddleware.ExecutePhpFileAsync(HttpContext context, String filePath)
occluder
occluder6mo ago
@レオン Why not just phpProcess.StandardInput.WriteAsync ? you are flushing StandardInput without using any of the write methods on it
Halfbax
HalfbaxOP6mo ago
My fault, but hasnt changed anything. The exception occurs on the write method. Same for this one:
using StreamReader reader = new(context.Request.Body, Encoding.UTF8);
string requestBody = await reader.ReadToEndAsync();
await phpProcess.StandardInput.WriteAsync(requestBody);
// await phpProcess.StandardInput.FlushAsync();
using StreamReader reader = new(context.Request.Body, Encoding.UTF8);
string requestBody = await reader.ReadToEndAsync();
await phpProcess.StandardInput.WriteAsync(requestBody);
// await phpProcess.StandardInput.FlushAsync();
occluder
occluder6mo ago
Interesting, perhaps the process is somehow ending before you get a chance to write anything? I've come across this: https://learn.microsoft.com/en-us/dotnet/core/compatibility/core-libraries/8.0/filestream-disposed-pipe
.NET 8 breaking change: FileStream writes when pipe is closed - .NET
Learn about the .NET 8 breaking change in core .NET libraries where an exception is thrown if you write to a FileStream whose underlying pipe is closed.
occluder
occluder6mo ago
@レオン check phpProcess.HasExited before writing, otherwise your code looks like it should work. If it doesn't I would suggest using CliWrap from nuget rather than dealing with the process class
Halfbax
HalfbaxOP6mo ago
HasExited is true. That seems to be the problem. Now I have to check how can I let the process wait for my inputs
occluder
occluder6mo ago
Maybe you got the filename or arguments wrong? Process.Start() returns bool, that might help out
Halfbax
HalfbaxOP6mo ago
Process.Start() returned true. I have moved over to CliWrap. Thats my code. Sadly same result, blank screen while executing a script with a body
private async Task ExecutePhpFileAsync(HttpContext context, string filePath)
{
try
{
Dictionary<string, string?> environmentVariables = new()
{
["REQUEST_METHOD"] = context.Request.Method,
["QUERY_STRING"] = context.Request.QueryString.ToString(),
["SCRIPT_FILENAME"] = filePath,
["SCRIPT_NAME"] = context.Request.Path,
["REQUEST_URI"] = context.Request.Path + context.Request.QueryString,
["DOCUMENT_ROOT"] = _rootPath,
["REMOTE_ADDR"] = context.Connection.RemoteIpAddress?.ToString() ?? string.Empty,
["REMOTE_PORT"] = context.Connection.RemotePort.ToString(),
["SERVER_PROTOCOL"] = context.Request.Protocol
};

PipeSource inputPipe = PipeSource.FromStream(context.Request.Body);

Command cmd = Cli.Wrap(_phpInterpreterPath)
.WithArguments(args => args.Add(filePath))
.WithStandardInputPipe(inputPipe)
.WithEnvironmentVariables(environmentVariables);

BufferedCommandResult result = await cmd.ExecuteBufferedAsync();

if (result.StandardError != string.Empty)
{
_logger.LogError(result.StandardError);
}

if (!context.Response.HasStarted)
{
context.Response.ContentType = "text/html";
await context.Response.WriteAsync(result.StandardOutput);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "An error occurred while executing the PHP file.");

if (!context.Response.HasStarted)
{
context.Response.StatusCode = 500;
await context.Response.WriteAsync("An internal server error occurred.");
}
}
}
private async Task ExecutePhpFileAsync(HttpContext context, string filePath)
{
try
{
Dictionary<string, string?> environmentVariables = new()
{
["REQUEST_METHOD"] = context.Request.Method,
["QUERY_STRING"] = context.Request.QueryString.ToString(),
["SCRIPT_FILENAME"] = filePath,
["SCRIPT_NAME"] = context.Request.Path,
["REQUEST_URI"] = context.Request.Path + context.Request.QueryString,
["DOCUMENT_ROOT"] = _rootPath,
["REMOTE_ADDR"] = context.Connection.RemoteIpAddress?.ToString() ?? string.Empty,
["REMOTE_PORT"] = context.Connection.RemotePort.ToString(),
["SERVER_PROTOCOL"] = context.Request.Protocol
};

PipeSource inputPipe = PipeSource.FromStream(context.Request.Body);

Command cmd = Cli.Wrap(_phpInterpreterPath)
.WithArguments(args => args.Add(filePath))
.WithStandardInputPipe(inputPipe)
.WithEnvironmentVariables(environmentVariables);

BufferedCommandResult result = await cmd.ExecuteBufferedAsync();

if (result.StandardError != string.Empty)
{
_logger.LogError(result.StandardError);
}

if (!context.Response.HasStarted)
{
context.Response.ContentType = "text/html";
await context.Response.WriteAsync(result.StandardOutput);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "An error occurred while executing the PHP file.");

if (!context.Response.HasStarted)
{
context.Response.StatusCode = 500;
await context.Response.WriteAsync("An internal server error occurred.");
}
}
}
I found the main issue. The script sets the header location and this is not supported. I have removed the php script and replaced it with html/js + api. Thanks for help. Even we havnt found a solution I have learned few new things. The solution (havnt tried yet) is to call the fastcgi service e.g. php-fpm, but there is no active maintained wrapper for .net
Want results from more Discord servers?
Add your server