Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add OnSocketAvailableAsync Hook #647

Merged
merged 6 commits into from
Nov 13, 2024

Conversation

johannespfeiffer
Copy link
Contributor

@johannespfeiffer johannespfeiffer commented Oct 8, 2024

Motivation

This change adds the OnSocketAvailableAsync hook, allowing users to perform actions on the socket once it becomes available. This is particularly useful for scenarios where an HTTP proxy is required to connect to a NATS server.

Key Change

This hook enables users to interact with the socket directly, allowing custom actions such as sending an HTTP CONNECT request to establish a tunnel through a proxy.

Example Usage

_connection.OnSocketAvailableAsync = async (_socket) =>
{
    var hostname = "my-http-proxy-host";
    var port = 3128;
    string connectRequest = $"CONNECT {hostname}:{port} HTTP/1.1\r\nHost: {hostname}:{port}\r\n\r\n";
    byte[] requestBytes = Encoding.ASCII.GetBytes(connectRequest);
    await _socket.SendAsync(requestBytes);

    // Validate proxy response
    byte[] buffer = new byte[4096];
    int bytesRead = await _socket.ReceiveAsync(buffer);
    string response = Encoding.ASCII.GetString(buffer, 0, bytesRead);

    if (!response.Contains("200 Connection established"))
    {
        throw new Exception($"Proxy connection failed. Response: {response}");
    }
};

@@ -1,6 +1,6 @@
namespace NATS.Client.Core.Internal;

internal interface ISocketConnection : IAsyncDisposable
public interface ISocketConnection : IAsyncDisposable
Copy link
Collaborator

@mtmk mtmk Oct 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we need to make it public we should move the file out of internal

edit: also does this really need to be public?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, if we want to use it in public Func<ISocketConnection, ValueTask<ISocketConnection>>. Will move it.

@@ -34,6 +34,7 @@ public partial class NatsConnection : INatsConnection
/// Hook before TCP connection open.
/// </summary>
public Func<(string Host, int Port), ValueTask<(string Host, int Port)>>? OnConnectingAsync;
public Func<ISocketConnection, Task>? OnSocketAvailableAsync;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should pull this up to INatsConnection as well. also can we return ValueTask to be consistent?

@mtmk
Copy link
Collaborator

mtmk commented Oct 8, 2024

thanks @johannespfeiffer looks very useful. can you think of a way to write a test? (I think websocket callbacks have a test might be helpful)

@johannespfeiffer johannespfeiffer force-pushed the on-socket-available-hook branch 2 times, most recently from 23c9634 to 526f09a Compare October 8, 2024 12:26
@johannespfeiffer johannespfeiffer force-pushed the on-socket-available-hook branch from 1e30cea to 2fa95e6 Compare October 24, 2024 09:47
@mtmk
Copy link
Collaborator

mtmk commented Oct 28, 2024

I will try to test this soon. Do you have an example setup or some kind of instructions to try it out in a real setup e.g. with a proxy like Squid or something?

@johannespfeiffer
Copy link
Contributor Author

johannespfeiffer commented Oct 28, 2024

Yes, exactly like that.

I used squid on a separate machine (just used the standard settings).

Then on the machine running the nats-client I've added a firewall rule to only allow outgoing connections to the machine with the squid proxy.

@mtmk mtmk self-assigned this Nov 12, 2024
@mtmk
Copy link
Collaborator

mtmk commented Nov 13, 2024

finally got around to this. sorry for the delay. here is a working example augmenting the example above:

using System.Text;
using NATS.Client.Core;

var proxy = "localhost:3128";
var nats = "demo.nats.io:4222";

await using var connection = new NatsConnection(new NatsOpts
{
    Url = proxy,
    TlsOpts = new NatsTlsOpts { Mode = TlsMode.Disable },
});

// Must assign a delegate to OnSocketAvailableAsync before calling ConnectAsync or any other method
connection.OnSocketAvailableAsync = async socket =>
{
    // Send CONNECT request to proxy
    await socket.SendAsync(Encoding.ASCII.GetBytes($"CONNECT {nats} HTTP/1.1\r\nHost: {nats}\r\n\r\n"));

    // Validate proxy response
    var buffer = new byte[4096];
    var read = await socket.ReceiveAsync(buffer);
    var response = Encoding.ASCII.GetString(buffer, 0, read);
    if (!response.Contains("200 Connection established"))
    {
        throw new Exception($"Proxy connection failed. Response: {response}");
    }

    return socket;
};

// Establish connection to NATS server through the proxy
await connection.ConnectAsync();
Console.WriteLine($"Connected to server {connection.ServerInfo?.Name}");

// Work with the connection as usual
var rtt = await connection.PingAsync();
Console.WriteLine($"RTT {rtt}");

If you install a local Squid proxy server to forward traffic to the NATS server make sure to comment out the following line in the Squid configuration file:

# http_access deny CONNECT !SSL_ports

Copy link
Collaborator

@mtmk mtmk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM thanks @johannespfeiffer

@mtmk mtmk merged commit fcc8574 into nats-io:main Nov 13, 2024
10 checks passed
mtmk added a commit that referenced this pull request Nov 13, 2024
* Add OnSocketAvailableAsync Hook (#647)
* Refactor exception handling in NatsException (#677)
* Fixed an exception which happens when PutAsync is used more than once and activity logging is enabled in main project (#675)
* open Header.Writer class and create GetBytesLength method (#674)
* Fix date time serialization to use ISO8601 (#664)
@mtmk mtmk mentioned this pull request Nov 13, 2024
mtmk added a commit that referenced this pull request Nov 13, 2024
* Add OnSocketAvailableAsync Hook (#647)
* Refactor exception handling in NatsException (#677)
* Fixed an exception which happens when PutAsync is used more than once and activity logging is enabled in main project (#675)
* open Header.Writer class and create GetBytesLength method (#674)
* Fix date time serialization to use ISO8601 (#664)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants