I want to test grpc online via the internet. For that purpose I need tunneling my laptop, so that local grpc can be accessed via the internet with the help of ngrok program.
I downloaded ngrok.exe from https://dashboard.ngrok.com/get-started/setup/windows
The authentication process always succeeds when I tested with C# (Step 1),
but when deploying the app online the result is always empty (Step 2)?
using CardData.Ext.Logger;
using CustomerCardServer.Ext.Mod.General;
using CustomerCardServer.Properties;
using System.Diagnostics;
using System.Text;
using System.Text.RegularExpressions;
using File = System.IO.File;
namespace CustomerCardServer.Ext.Mod.Tunnel
{
public class NgrokTunnelManager
{
private static NgrokTunnelManager? _instance;
private static object _instanceLocker = new object();
private String? _appPath;
private String? _ngrokTunnelFile;
private String? _cmdFile;
private List<int> _processIDs = new List<int>();
public List<int> ProcessIDs
{
get { return _processIDs; }
private set { _processIDs = value; }
}
public String? SessionStatus { get; private set; }
public String? PublicUrl { get; private set; }
public String? PrivateUrl { get; private set; }
private NgrokTunnelManager()
{
CopyCmdPromptFile();
CopyNgrokTunnelFile();
}
public static NgrokTunnelManager GetInstance()
{
if (_instance is null)
{
lock (_instanceLocker)
{
_instance = new NgrokTunnelManager();
}
}
return _instance;
}
public bool CopyNgrokTunnelFile()
{
try
{
// App path
_appPath = Application.StartupPath;
if (String.IsNullOrEmpty(_appPath))
{
MessageBox.Show("AppPath isn't valid!", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
return false;
}
// ngrok file
_ngrokTunnelFile = _appPath + Common.NGROK_NAME;
if (String.IsNullOrEmpty(_ngrokTunnelFile))
{
MessageBox.Show("Devtunnel.exe isn't found!", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
return false;
}
// create ngrok file
if (!File.Exists(_ngrokTunnelFile))
{
File.WriteAllBytes(_ngrokTunnelFile, Resources.ngrok);
}
// save ngrok file path
Settings.Default.NgrokFile = _ngrokTunnelFile;
Settings.Default.Save();
return true;
}
catch (Exception ex)
{
Log.Exception(ex);
}
return false;
}
public bool CopyCmdPromptFile()
{
try
{
// App path
_appPath = Application.StartupPath;
if (String.IsNullOrEmpty(_appPath))
{
MessageBox.Show("AppPath isn't valid!", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
return false;
}
// cmd shortcut file
_cmdFile = _appPath + Common.CMD_NAME;
if (String.IsNullOrEmpty(_cmdFile))
{
MessageBox.Show("Cmd.exe isn't found!", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
return false;
}
// create cmd shortcut file
if (!File.Exists(_cmdFile))
{
File.WriteAllBytes(_cmdFile, Resources.cmd);
}
// save cmd shortcut file path
Settings.Default.CmdFile = _cmdFile;
Settings.Default.Save();
return true;
}
catch (Exception ex)
{
Log.Exception(ex);
}
return false;
}
public async Task<bool> AuthAlsoStartAsync(String authToken, String ipAddress, int port)
{
try
{
var argument1 = $"ngrok config add-authtoken {authToken}";
var argument2 = $"ngrok http https://{ipAddress}:{port}";
var authResult = await RunCommandAsync(argument1, TimeSpan.FromSeconds(5));
if (!authResult.Contains("Authtoken saved to configuration file"))
{
return false;
}
// BUG IN HERE, startResult IS ALWAYS EMPTY?
var startResult = await RunCommandAsync(argument2, TimeSpan.FromSeconds(5));
if (String.IsNullOrEmpty(startResult))
{
return false;
}
// Regex to extract Session Status
String? sessionStatus = Regex.Match(startResult, @"Session Statuss+(w+)").Groups[1].Value;
// Regex to extract Forwarding URLs
Match? forwardingMatch = Regex.Match(startResult, @"Forwardings+(https?://[^s]+)s+->s+(https?://[^s]+)");
String? publicUrl = forwardingMatch.Groups[1].Value;
String? privateUrl = forwardingMatch.Groups[2].Value;
if (!String.IsNullOrEmpty(sessionStatus) && !String.IsNullOrEmpty(publicUrl) && !String.IsNullOrEmpty(privateUrl))
{
SessionStatus = sessionStatus;
PublicUrl = publicUrl;
PrivateUrl = privateUrl;
return true;
}
}
catch (Exception ex)
{
Log.Exception(ex);
}
return false;
}
private int GetNgrokTunnelProcessId(string processName)
{
try
{
// Get the list of processes with the specified name
Process[] processes = Process.GetProcessesByName(processName);
if (processes.Length > 0)
{
// Return the PID of the first process found
return processes[0].Id;
}
return -1; // Process not found
}
catch (Exception ex)
{
Log.Exception(ex);
}
return -1;
}
private void StopProcess()
{
try
{
// kill current devtunnel
Process[] processes = Process.GetProcessesByName(Path.GetFileNameWithoutExtension(Common.NGROK_NAME));
foreach (Process process in processes)
{
if (process is null)
{
continue;
}
if (_processIDs is null)
{
break;
}
if (_processIDs.Count == 0)
{
break;
}
if (_processIDs.Contains(process.Id))
{
process.Kill();
}
}
}
catch (Exception ex)
{
Log.Exception(ex);
}
}
public async Task<String> RunCommandAsync(String argument, TimeSpan timeout, bool isInputEnable = false)
{
try
{
StopProcess();
var startInfo = new ProcessStartInfo
{
FileName = "cmd.exe",
Arguments = $"/c {argument}",
RedirectStandardOutput = true,
RedirectStandardInput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true,
};
var process = new Process()
{
StartInfo = startInfo,
EnableRaisingEvents = true // Enable raising events to handle process exit
};
var tcs = new TaskCompletionSource<string>();
var fgThread = new Thread(() =>
{
var result = RunCommandXAsync(process, timeout, isInputEnable).ConfigureAwait(false).GetAwaiter().GetResult();
tcs.SetResult(result);
});
fgThread.IsBackground = false;
fgThread.SetApartmentState(ApartmentState.STA);
fgThread.Start();
fgThread.Join();
return tcs.Task.ConfigureAwait(false).GetAwaiter().GetResult();
}
catch (Exception ex)
{
Log.Exception(ex);
}
return String.Empty;
}
public async Task<String> RunCommandXAsync(Process process, TimeSpan timeout, bool isInputEnable = false)
{
try
{
var tcsExit = new TaskCompletionSource<int>();
var errorBuilder = new StringBuilder();
var outputBuilder = new StringBuilder();
// Event handler for error
process.ErrorDataReceived += (sender, e) =>
{
if (!String.IsNullOrEmpty(e.Data))
{
errorBuilder.AppendLine(e.Data);
}
};
// Event handler for output
process.OutputDataReceived += (sender, e) =>
{
if (!String.IsNullOrEmpty(e.Data))
{
outputBuilder.AppendLine(e.Data);
}
};
// Event handler for exit
process.Exited += (sender, e) =>
{
if (tcsExit.Task.IsCompleted)
{
tcsExit = new TaskCompletionSource<int>();
}
tcsExit.TrySetResult(process.ExitCode);
};
bool isProcessStarted = process.Start();
if (!isProcessStarted)
{
return String.Empty;
}
// Start the asynchronous read of the error stream.
process.BeginErrorReadLine();
// Start the asynchronous read of the output stream.
process.BeginOutputReadLine();
// Add process id devtunnel.exe
Thread.Sleep(TimeSpan.FromSeconds(2));
int processId = GetNgrokTunnelProcessId(Path.GetFileNameWithoutExtension(Common.NGROK_NAME));
if (processId != -1)
{
if (!_processIDs.Contains(processId))
{
_processIDs.Add(processId);
}
}
Task taskCompleted = await Task.WhenAny(tcsExit.Task, Task.Delay(timeout));
if (taskCompleted.IsCompletedSuccessfully)
{
var error = errorBuilder.ToString();
if (!String.IsNullOrEmpty(error))
{
// error
return error;
}
// output
var output = outputBuilder.ToString();
if (!String.IsNullOrEmpty(output))
{
// input
if (isInputEnable)
{
if (output.Contains("Are you sure? y/n"))
{
using (var writer = process.StandardInput)
{
if (writer is not null)
{
_ = writer.WriteLineAsync("Y");
writer.Close();
}
}
}
}
// output
return output;
}
}
}
catch (Exception ex)
{
Log.Exception(ex);
}
return String.Empty;
}
}
}
(Step 1) I use cmd prompt to call ngrok.exe to perform authentication with the command below:
ngrok config add-authtoken 2mW3vWujIv8d1nNJkHyhEPvFcFF_84yExzagwLyL3EuHH4xDR
Result expected:
Authtoken saved to configuration file:
C:UsersxxxxAppDataLocal/ngrok/ngrok.yml
(Step 2) I use cmd prompt to call ngrok.exe to deploy app online with the command below:
ngrok http https://localhost:8080
Result expected:
ngrok (Ctrl+C to quit) Found a bug? Let us know: https://github.com/ngrok/ngrok Session Status online
Account [email protected] (Plan: Free)
Version 3.16.0
Region Asia (ap)
Latency 42ms
Web Interface http://127.0.0.1:4040
Forwarding https://fb82-202-154-12-130.ngrok-free.app -> https://127.0.0.1:8080 Connections ttl opn rt1 rt5 p50 p90
0 0 0.00 0.00 0.00 0.00
2