Skip to content

Commit

Permalink
#902: overhaul of the hidden TCP server type
Browse files Browse the repository at this point in the history
  • Loading branch information
Badgerati committed Apr 12, 2022
1 parent d399cce commit 785e5aa
Show file tree
Hide file tree
Showing 22 changed files with 796 additions and 225 deletions.
31 changes: 23 additions & 8 deletions examples/tcp-server.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,31 @@ Import-Module "$($path)/src/Pode.psm1" -Force -ErrorAction Stop
# create a server, and start listening on port 8999
Start-PodeServer -Threads 2 {

Add-PodeEndpoint -Address * -Port 8999 -Protocol TCP
# add two endpoints
Add-PodeEndpoint -Address * -Port 8999 -Protocol Tcp -Name 'EP1' -Acknowledge 'Hello there!' -CRLFMessageEnd
Add-PodeEndpoint -Address '127.0.0.2' -Hostname 'foo.pode.com' -Port 8999 -Protocol Tcp -Name 'EP2' -Acknowledge 'Hello there!' -CRLFMessageEnd

# allow the local ip
Add-PodeAccessRule -Access Allow -Type IP -Values 127.0.0.1
# enable logging
New-PodeLoggingMethod -Terminal | Enable-PodeErrorLogging

# setup a tcp handler
Add-PodeHandler -Type Tcp -Name 'Main' -ScriptBlock {
Write-PodeTcpClient -Message 'gief data'
$msg = (Read-PodeTcpClient)
Write-Host $msg
# hello verb for endpoint1
Add-PodeVerb -Verb 'HELLO :forename :surname' -EndpointName EP1 -ScriptBlock {
Write-PodeTcpClient -Message "HI 1, $($TcpEvent.Parameters.forename) $($TcpEvent.Parameters.surname)"
"HI 1, $($TcpEvent.Parameters.forename) $($TcpEvent.Parameters.surname)" | Out-Default
}

# hello verb for endpoint2
Add-PodeVerb -Verb 'HELLO :forename :surname' -EndpointName EP2 -ScriptBlock {
Write-PodeTcpClient -Message "HI 2, $($TcpEvent.Parameters.forename) $($TcpEvent.Parameters.surname)"
"HI 2, $($TcpEvent.Parameters.forename) $($TcpEvent.Parameters.surname)" | Out-Default
}

# catch-all verb for both endpoints
Add-PodeVerb -Verb '*' -ScriptBlock {
Write-PodeTcpClient -Message "Unrecognised verb sent"
}

# quit verb for both endpoints
Add-PodeVerb -Verb 'Quit' -Close

}
27 changes: 24 additions & 3 deletions src/Listener/PodeContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,10 @@ private void NewRequest()
Request = new PodeSmtpRequest(Socket, PodeSocket);
break;

case PodeProtocolType.Tcp:
Request = new PodeTcpRequest(Socket, PodeSocket);
break;

default:
Request = new PodeHttpRequest(Socket, PodeSocket);
break;
Expand Down Expand Up @@ -176,10 +180,17 @@ private void NewRequest()
: PodeContextState.SslError);
}

// if request is SMTP, send ACK
if (IsOpened && PodeSocket.IsSmtp)
// if request is SMTP or TCP, send ACK if available
if (IsOpened)
{
SmtpRequest.SendAck();
if (PodeSocket.IsSmtp)
{
SmtpRequest.SendAck();
}
else if (PodeSocket.IsTcp && !string.IsNullOrWhiteSpace(PodeSocket.AcknowledgeMessage))
{
Response.WriteLine(PodeSocket.AcknowledgeMessage, true);
}
}
}

Expand All @@ -203,6 +214,16 @@ private void SetContextType()
Type = PodeProtocolType.Smtp;
break;

// - only allow tcp
case PodeProtocolType.Tcp:
if (!Request.IsTcp)
{
throw new HttpRequestException("Request is not Tcp");
}

Type = PodeProtocolType.Tcp;
break;

// - only allow http
case PodeProtocolType.Http:
if (Request.IsWebSocket)
Expand Down
54 changes: 47 additions & 7 deletions src/Listener/PodeRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,18 @@ public class PodeRequest : PodeProtocol, IDisposable
public bool IsAborted => (Error != default(HttpRequestException));
public bool IsDisposed { get; private set; }

public virtual string Address
{
get => (Context.PodeSocket.HasHostnames
? $"{Context.PodeSocket.Hostname}:{((IPEndPoint)LocalEndPoint).Port}"
: $"{((IPEndPoint)LocalEndPoint).Address}:{((IPEndPoint)LocalEndPoint).Port}");
}

public virtual string Scheme
{
get => (SslUpgraded ? $"{Context.PodeSocket.Type}s" : $"{Context.PodeSocket.Type}");
}

private Socket Socket;
protected PodeContext Context;
protected static UTF8Encoding Encoding = new UTF8Encoding();
Expand Down Expand Up @@ -106,10 +118,10 @@ private bool ValidateCertificateCallback(object sender, X509Certificate certific
return true;
}

protected async Task<int> BeginRead(CancellationToken cancellationToken)
protected async Task<int> BeginRead(byte[] buffer, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
return await Task<int>.Factory.FromAsync(InputStream.BeginRead, InputStream.EndRead, Buffer, 0, BufferSize, null);
return await Task<int>.Factory.FromAsync(InputStream.BeginRead, InputStream.EndRead, buffer, 0, BufferSize, null);
}

public async Task<bool> Receive(CancellationToken cancellationToken)
Expand All @@ -124,7 +136,7 @@ public async Task<bool> Receive(CancellationToken cancellationToken)
var read = 0;
var close = true;

while ((read = await BeginRead(cancellationToken)) > 0)
while ((read = await BeginRead(Buffer, cancellationToken)) > 0)
{
cancellationToken.ThrowIfCancellationRequested();
BufferStream.Write(Buffer, 0, read);
Expand All @@ -134,16 +146,13 @@ public async Task<bool> Receive(CancellationToken cancellationToken)
continue;
}

var bytes = BufferStream.ToArray();
if (!Parse(bytes))
if (!Parse(BufferStream.ToArray()))
{
bytes = default(byte[]);
BufferStream.Dispose();
BufferStream = new MemoryStream();
continue;
}

bytes = default(byte[]);
close = false;
break;
}
Expand All @@ -170,6 +179,37 @@ public async Task<bool> Receive(CancellationToken cancellationToken)
return false;
}

public async Task<string> Read(CancellationToken cancellationToken)
{
var buffer = new byte[BufferSize];
var bufferStream = new MemoryStream();

try
{
var read = 0;
while ((read = await BeginRead(buffer, cancellationToken)) > 0)
{
cancellationToken.ThrowIfCancellationRequested();
bufferStream.Write(buffer, 0, read);

if (Socket.Available > 0)
{
continue;
}

break;
}

cancellationToken.ThrowIfCancellationRequested();
return Encoding.GetString(bufferStream.ToArray());
}
finally
{
bufferStream.Dispose();
buffer = default(byte[]);
}
}

protected virtual bool Parse(byte[] bytes)
{
throw new NotImplementedException();
Expand Down
6 changes: 5 additions & 1 deletion src/Listener/PodeSmtpRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,11 @@ private bool IsCommand(string content, string command)

public void SendAck()
{
Context.Response.WriteLine($"220 {Context.PodeSocket.Hostname} -- Pode Proxy Server", true);
var ack = string.IsNullOrWhiteSpace(Context.PodeSocket.AcknowledgeMessage)
? $"{Context.PodeSocket.Hostname} -- Pode Proxy Server"
: Context.PodeSocket.AcknowledgeMessage;

Context.Response.WriteLine($"220 {ack}", true);
}

protected override bool ValidateInput(byte[] bytes)
Expand Down
2 changes: 2 additions & 0 deletions src/Listener/PodeSocket.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ public class PodeSocket : PodeProtocol, IDisposable
public SslProtocols Protocols { get; private set; }
public PodeTlsMode TlsMode { get; private set; }
public Socket Socket { get; private set; }
public string AcknowledgeMessage { get; set; }
public bool CRLFMessageEnd { get; set; }

private ConcurrentQueue<SocketAsyncEventArgs> AcceptConnections;
private ConcurrentQueue<SocketAsyncEventArgs> ReceiveConnections;
Expand Down
85 changes: 85 additions & 0 deletions src/Listener/PodeTcpRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
using System.Net.Sockets;

namespace Pode
{
public class PodeTcpRequest : PodeRequest
{
public byte[] RawBody { get; private set; }

private string _body = string.Empty;
public string Body
{
get
{
if (RawBody != default(byte[]) && RawBody.Length > 0)
{
_body = Encoding.GetString(RawBody).Trim();
}

return _body;
}
}

public override bool CloseImmediately
{
get => (IsDisposed || RawBody == default(byte[]) || RawBody.Length == 0);
}

public PodeTcpRequest(Socket socket, PodeSocket podeSocket)
: base(socket, podeSocket)
{
IsKeepAlive = true;
Type = PodeProtocolType.Tcp;
}

protected override bool ValidateInput(byte[] bytes)
{
// we need more bytes!
if (bytes.Length < (Context.PodeSocket.CRLFMessageEnd ? 2 : 1))
{
return false;
}

// expect to end with <CR><LF>?
if (Context.PodeSocket.CRLFMessageEnd)
{
return (bytes[bytes.Length - 2] == (byte)13
&& bytes[bytes.Length - 1] == (byte)10);
}

return true;
}

protected override bool Parse(byte[] bytes)
{
RawBody = bytes;

// if there are no bytes, return (0 bytes read means we can close the socket)
if (bytes.Length == 0)
{
return true;
}

return true;
}

public void Reset()
{
PodeHelpers.WriteErrorMessage($"Request reset", Context.Listener, PodeLoggingLevel.Verbose, Context);
_body = string.Empty;
RawBody = default(byte[]);
}

public void Close()
{
Context.Dispose(true);
}

public override void Dispose()
{
RawBody = default(byte[]);
_body = string.Empty;
base.Dispose();
}
}
}
9 changes: 8 additions & 1 deletion src/Private/Context.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,11 @@ function New-PodeContext
# set the IP address details
$ctx.Server.Endpoints = @{}
$ctx.Server.EndpointsMap = @{}
$ctx.Server.FindRouteEndpoint = $false
$ctx.Server.FindEndpoints = @{
Route = $false
Smtp = $false
Tcp = $false
}

# general encoding for the server
$ctx.Server.Encoding = New-Object System.Text.UTF8Encoding
Expand Down Expand Up @@ -277,6 +281,9 @@ function New-PodeContext
'*' = [ordered]@{}
}

# verbs for tcp
$ctx.Server.Verbs = @{}

# custom view paths
$ctx.Server.Views = @{}

Expand Down
17 changes: 11 additions & 6 deletions src/Private/Endpoints.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ function Get-PodeEndpoints
}

'tcp' {
$endpoints += @($PodeContext.Server.Endpoints.Values | Where-Object { @('tcp') -icontains $_.Protocol })
$endpoints += @($PodeContext.Server.Endpoints.Values | Where-Object { @('tcp', 'tcps') -icontains $_.Protocol })
}
}
}
Expand All @@ -89,7 +89,7 @@ function Test-PodeEndpointProtocol
{
param(
[Parameter(Mandatory=$true)]
[ValidateSet('Http', 'Https', 'Ws', 'Wss', 'Smtp', 'Smtps', 'Tcp')]
[ValidateSet('Http', 'Https', 'Ws', 'Wss', 'Smtp', 'Smtps', 'Tcp', 'Tcps')]
[string]
$Protocol
)
Expand All @@ -102,7 +102,7 @@ function Get-PodeEndpointType
{
param(
[Parameter()]
[ValidateSet('Http', 'Https', 'Smtp', 'Smtps', 'Tcp', 'Ws', 'Wss')]
[ValidateSet('Http', 'Https', 'Smtp', 'Smtps', 'Tcp', 'Tcps', 'Ws', 'Wss')]
[string]
$Protocol
)
Expand All @@ -111,6 +111,7 @@ function Get-PodeEndpointType
{ $_ -iin @('http', 'https') } { 'Http' }
{ $_ -iin @('ws', 'wss') } { 'Ws' }
{ $_ -iin @('smtp', 'smtps') } { 'Smtp' }
{ $_ -iin @('tcp', 'tcps') } { 'Tcp' }
default { $Protocol }
}
}
Expand All @@ -119,7 +120,7 @@ function Get-PodeEndpointRunspacePoolName
{
param(
[Parameter()]
[ValidateSet('Http', 'Https', 'Smtp', 'Smtps', 'Tcp', 'Ws', 'Wss')]
[ValidateSet('Http', 'Https', 'Smtp', 'Smtps', 'Tcp', 'Tcps', 'Ws', 'Wss')]
[string]
$Protocol
)
Expand All @@ -128,6 +129,7 @@ function Get-PodeEndpointRunspacePoolName
{ $_ -iin @('http', 'https') } { 'Web' }
{ $_ -iin @('ws', 'wss') } { 'Signals' }
{ $_ -iin @('smtp', 'smtps') } { 'Smtp' }
{ $_ -iin @('tcp', 'tcps') } { 'Tcp' }
default { $Protocol }
}
}
Expand Down Expand Up @@ -165,10 +167,13 @@ function Find-PodeEndpointName
$Force,

[switch]
$ThrowError
$ThrowError,

[switch]
$Enabled
)

if (!$PodeContext.Server.FindRouteEndpoint -and !$Force) {
if (!$Enabled -and !$Force) {
return $null
}

Expand Down
Loading

0 comments on commit 785e5aa

Please sign in to comment.