Howdy! I’m doing some tests running an HTTP server and I keep running into a thrown exception.
Below is a method that opens the server and continuously responds to requests. Since the GetContext() method is blocking, the method runs on its own thread. The request/response process works 90% of the time without error, but every once in a while I will put the path into the web browser and it will not be able to load, spinning indefinitely. When I cancel the request from the browser or request something different, an uncaught IO exception will be thrown. This usually happens more frequently when I make a lot of requests quickly.
I was wondering if anyone could explain why this is happening, if there is a recommended change I should implement, or if this is just a bug. I am using an SC20260D SOM.
Here is the exception stack trace:
[(2025-01-06 09:59:56) Error - ThreadManager_HttpServer: Exception was thrown: System.IO.IOException.
-> System.Net.Sockets.NetworkStream::Write
-> System.Net.OutputNetworkStreamWrapper::Write
-> GHI_Framework.HttpController::ServerListenerTask
-> GHI_Framework.ThreadManager::RunThreadAsync
Here is my code example:
///<summary> Asynchronous HTTP server listener task. </summary>
private static void ServerListenerTask()
{
//Print a log message and enable the listener flag
Logger.LogMessage(Logger.Level.Info, ControllerId, $"Opening new HTTP server (IP: {EthernetController.IpAddress}, Port: 80)...");
//Create a new HTTP listener
_httpListener = new HttpListener("http", 80); //http = 80, https = 443
_httpListener.Start();
//Continuously listen until the network connection is closed
while (_httpListener != null && _httpListener.IsListening && EthernetController.ConnectionEstablished)
{
//Close any previous requests
_httpRequest?.Request?.Reset();
_httpRequest?.Response?.Close();
_httpRequest?.Reset();
_httpRequest = null;
try
{
//Wait for a new request (blocking)
Logger.LogMessage(Logger.Level.Debug, ControllerId, "Awaiting new HTTP request...");
_httpRequest = _httpListener.GetContext();
}
catch (Exception ex)
{
Logger.LogMessage(Logger.Level.Warning, ControllerId, $"ServerListenerTask() failed - {ex.Message}.");
continue;
}
//Print a log message and invoke the request received event
string urlPath = _httpRequest.Request.RawUrl.TrimStart('/');
Logger.LogMessage(Logger.Level.Debug, ControllerId, $"HTTP request received (IP: {_httpRequest.Request.RemoteEndPoint.Address}): {EthernetController.IpAddress}/{urlPath}");
EthernetController.NetworkIsHealthy();
OnHttpRequestReceived?.Invoke(_httpRequest);
//If the request is for the favicon, return the icon bytes
if (SdCardController.ExtractPathChild(urlPath, '/') == "favicon.ico")
{
Logger.LogMessage(Logger.Level.Debug, ControllerId, "Sending \"favicon.ico\"...");
/*byte[] faviconBytes = Resources.GetBitmap(Resources.BitmapResources.favicon2).GetBitmap();
_httpRequest.Response.OutputStream.Write(faviconBytes, 0, faviconBytes.Length);*/
_httpRequest.Response.Close();
Logger.LogMessage(Logger.Level.Debug, ControllerId, "Response sent successfully.");
}
//If the request is for the network config utility
else if (SdCardController.ExtractPathChild(urlPath, '/') == NetworkUtilityName)
{
Logger.LogMessage(Logger.Level.Debug, ControllerId, $"Sending \"{NetworkUtilityName}\"...");
byte[] utilityBytes = Encoding.UTF8.GetBytes(Resources.GetString(Resources.StringResources.NetworkConfigUtility));
_httpRequest.Response.OutputStream.Write(utilityBytes, 0, utilityBytes.Length);
_httpRequest.Response.Close();
Logger.LogMessage(Logger.Level.Debug, ControllerId, "Response sent successfully.");
}
//Otherwise process the URL as a file path
else
{
//Ensure the SD card is connected
if (!SdCardController.SdCardIsConnected)
{
byte[] nullResponse = Encoding.UTF8.GetBytes("\r\nERROR: SD CARD IS NOT AVAILABLE.");
_httpRequest.Response.OutputStream.Write(nullResponse, 0, nullResponse.Length);
_httpRequest.Response.Close();
continue;
}
//Extract the file path
string filePath = new StringBuilder(urlPath).Replace('/', '\\').ToString();
//Ensure the SD card contains the file
if (!string.IsNullOrEmpty(filePath) && !SdCardController.Exists(filePath))
{
byte[] nullResponse = Encoding.UTF8.GetBytes($"\r\nERROR: FILE PATH \"{urlPath}\" DOES NOT EXIST.\r\nFORMAT EXAMPLE: {EthernetController.IpAddress}/directory_name/file_example.txt");
_httpRequest.Response.OutputStream.Write(nullResponse, 0, nullResponse.Length);
_httpRequest.Response.Close();
continue;
}
//Determine the path attributes
FileAttributes attributes = File.GetAttributes($@"{SdCardController.RootDirectory.Name}" + filePath.ToLower());
//If the path is a directory, read the files in the directory
if ((attributes & FileAttributes.Directory) == FileAttributes.Directory)
{
//Extract all the files and directories in the provided path
DirectoryInfo[] allFolders = SdCardController.ReadAllSubDirectories(filePath);
FileInfo[] allFiles = SdCardController.ReadAllDirectoryFiles(filePath);
string[] allObjectLinks = new string[allFolders.Length + allFiles.Length + 1];
string[] utility = Resources.GetString(Resources.StringResources.UtilityInterface).Split('$');
//If the directory is empty, send an empty response
if (allObjectLinks.Length == 0)
{
byte[] emptyResponse = Encoding.UTF8.GetBytes(utility[0] + FormatHtmlListItem("N/A") + utility[1]);
_httpRequest.Response.OutputStream.Write(emptyResponse, 0, emptyResponse.Length);
_httpRequest.Response.Close();
continue;
}
//Add the list of folders
int index = 0;
foreach (DirectoryInfo folder in allFolders)
{
allObjectLinks[index] = FormatHtmlLink(folder.Name + "/", $"{urlPath}/{folder.Name.TrimEnd('\\')}", false);
index++;
}
//Add the list of files
foreach (FileInfo file in allFiles)
{
allObjectLinks[index] = FormatHtmlLink($"{file.Name} ({(file.Length / 1000):N0}kB)", $"{urlPath}/{file.Name.TrimEnd('\\')}", true);
index++;
}
//If the directory is the root directory, add the network utility link
if (string.IsNullOrEmpty(filePath))
{
allObjectLinks[allObjectLinks.Length - 1] = FormatHtmlLink("Network Config Tool", $"{urlPath}/{NetworkUtilityName}", false);
}
//Format the link list
StringBuilder formattedList = new StringBuilder();
for (int i = 0; i < allObjectLinks.Length; i++)
{
if (!string.IsNullOrEmpty(allObjectLinks[i]))
{
formattedList.Append(FormatHtmlListItem(allObjectLinks[i]));
}
}
//Send the response and close the stream
Logger.LogMessage(Logger.Level.Debug, ControllerId, $"Sending \"{SdCardController.ExtractPathChild(filePath, '\\')}\"...");
byte[] fullResponse = Encoding.UTF8.GetBytes(utility[0] + formattedList + utility[1]);
_httpRequest.Response.OutputStream.Write(fullResponse, 0, fullResponse.Length);
_httpRequest.Response.Close();
Logger.LogMessage(Logger.Level.Debug, ControllerId, "Response sent successfully.");
}
//If the path is a file
else
{
//Create a packet ready event subscription
SdCardController.OnFileTransferPacketReady += (filePacket, numBytes, packetIndex, totalPackets) =>
{
//Verify the packet is not empty
if (filePacket == null || filePacket.Length == 0)
{
return;
}
//Send the packet
try
{
_httpRequest.Response.OutputStream?.Write(filePacket, 0, numBytes);
}
catch (IOException ex)
{
SdCardController.UpdateFileTransferStatus(SdCardController.FileTransferStates.StopTransfer);
Logger.LogMessage(Logger.Level.Warning, ControllerId, "HTTP FILE TRANSFER EXCEPTION");
Logger.LogExceptionError(ControllerId, ex);
_httpRequest.Response.Close();
}
};
//Start an HTTP file transfer - synchronously
SdCardController.StartFileTransfer(ControllerId, filePath, (uint)(Memory.IsExtendedHeap() ? 1024 * 1024 : 1024 * 64), false, false);
_httpRequest.Response.Close();
}
}
}
}