Ejemplo n.º 1
0
 public ClickHouseTcpClient(
     TcpClient client,
     ClickHouseBinaryProtocolReader reader,
     ClickHouseBinaryProtocolWriter writer,
     ClickHouseConnectionSettings settings,
     ClickHouseServerInfo serverInfo,
     IClickHouseTypeInfoProvider typeInfoProvider)
 {
     _reader          = reader ?? throw new ArgumentNullException(nameof(reader));
     _writer          = writer ?? throw new ArgumentNullException(nameof(writer));
     _client          = client ?? throw new ArgumentNullException(nameof(client));
     _settings        = settings ?? throw new ArgumentNullException(nameof(settings));
     ServerInfo       = serverInfo;
     TypeInfoProvider = typeInfoProvider;
 }
Ejemplo n.º 2
0
        private async ValueTask Open(bool async, CancellationToken cancellationToken)
        {
            if (!BitConverter.IsLittleEndian)
            {
                throw new NotSupportedException(
                          "An architecture of the processor is not supported. Only little-endian architectures are supported." + Environment.NewLine +
                          "Please, report an issue if you see this message (https://github.com/Octonica/ClickHouseClient/issues).");
            }

            var connectionState = _connectionState;

            switch (connectionState.State)
            {
            case ConnectionState.Closed:
                break;

            case ConnectionState.Open:
                return;     // Re-entrance is allowed

            case ConnectionState.Connecting:
                throw new ClickHouseException(ClickHouseErrorCodes.InvalidConnectionState, "The connection is already opening.");

            case ConnectionState.Broken:
                throw new ClickHouseException(ClickHouseErrorCodes.InvalidConnectionState, "The connection is broken.");

            default:
                throw new NotSupportedException($"Internal error. The state {_connectionState} is not supported.");
            }

            if (!TryChangeConnectionState(connectionState, ConnectionState.Connecting, out connectionState, out var onStateChanged))
            {
                throw new ClickHouseException(ClickHouseErrorCodes.InvalidConnectionState, "The state of the connection was modified.");
            }

            var stateChangeEx      = onStateChanged(this);
            var connectionSettings = connectionState.Settings;

            if (stateChangeEx != null || connectionSettings == null)
            {
                var initialEx = stateChangeEx ?? new ClickHouseException(ClickHouseErrorCodes.InvalidConnectionState, "The connection is not initialized.");
                if (!TryChangeConnectionState(connectionState, ConnectionState.Closed, out _, out onStateChanged))
                {
                    throw new AggregateException(initialEx, new ClickHouseException(ClickHouseErrorCodes.InvalidConnectionState, "The state of the connection was modified."));
                }

                var stateChangeEx2 = onStateChanged(this);
                if (stateChangeEx2 != null)
                {
                    throw new AggregateException(initialEx, stateChangeEx2);
                }

                if (stateChangeEx != null)
                {
                    throw new ClickHouseException(ClickHouseErrorCodes.CallbackError, "External callback error. See the inner exception for details.", stateChangeEx);
                }

                throw initialEx;
            }

            const int defaultHttpPort             = 8123;
            TcpClient?client                      = null;
            SslStream?sslStream                   = null;
            ClickHouseBinaryProtocolWriter?writer = null;
            ClickHouseBinaryProtocolReader?reader = null;

            try
            {
                try
                {
                    client = new TcpClient {
                        SendTimeout = connectionSettings.ReadWriteTimeout, ReceiveTimeout = connectionSettings.ReadWriteTimeout
                    };

                    if (async)
                    {
                        await client.ConnectAsync(connectionSettings.Host, connectionSettings.Port);
                    }
                    else
                    {
                        client.Connect(connectionSettings.Host, connectionSettings.Port);
                    }
                }
                catch
                {
                    client?.Client?.Close(0);
                    client?.Dispose();
                    client = null;
                    throw;
                }

                if (connectionSettings.TlsMode == ClickHouseTlsMode.Require)
                {
                    var certValidationCallback = RemoteCertificateValidationCallback;
                    if (certValidationCallback == null && (connectionSettings.RootCertificate != null || !connectionSettings.ServerCertificateHash.IsEmpty))
                    {
                        certValidationCallback = (_, cert, chain, errors) => ValidateServerCertificate(connectionSettings, cert, chain, errors);
                    }

                    sslStream = new SslStream(client.GetStream(), true, certValidationCallback);

                    try
                    {
                        if (async)
                        {
                            await sslStream.AuthenticateAsClientAsync(new SslClientAuthenticationOptions { TargetHost = connectionSettings.Host }, cancellationToken);
                        }
                        else
                        {
                            sslStream.AuthenticateAsClient(connectionSettings.Host);
                        }
                    }
                    catch (AuthenticationException authEx)
                    {
                        throw new ClickHouseException(ClickHouseErrorCodes.TlsError, $"TLS handshake error.", authEx);
                    }
                }

                var stream = sslStream ?? (Stream)client.GetStream();
                writer = new ClickHouseBinaryProtocolWriter(stream, Math.Max(connectionSettings.BufferSize, MinBufferSize));

                var clientHello = new ClientHelloMessage.Builder
                {
                    ClientName       = connectionSettings.ClientName,
                    ClientVersion    = connectionSettings.ClientVersion,
                    User             = connectionSettings.User,
                    Database         = connectionSettings.Database,
                    Password         = connectionSettings.Password,
                    ProtocolRevision = ClickHouseProtocolRevisions.CurrentRevision
                }.Build();

                clientHello.Write(writer);

                await writer.Flush(async, cancellationToken);

                reader = new ClickHouseBinaryProtocolReader(stream, Math.Max(connectionSettings.BufferSize, MinBufferSize));
                var message = await reader.ReadMessage(false, async, cancellationToken);

                switch (message.MessageCode)
                {
                case ServerMessageCode.Hello:
                    var helloMessage = (ServerHelloMessage)message;

                    bool hasExtraByte = reader.TryPeekByte(out var extraByte);
                    if (!hasExtraByte && client.Available > 0)
                    {
                        hasExtraByte = true;
                        extraByte    = await reader.ReadByte(async, cancellationToken);
                    }

                    if (hasExtraByte)
                    {
                        throw new ClickHouseException(ClickHouseErrorCodes.ProtocolUnexpectedResponse, $"Expected the end of the data. Unexpected byte (0x{extraByte:X}) received from the server.");
                    }

                    var serverInfo = helloMessage.ServerInfo;
                    var configuredTypeInfoProvider = (_typeInfoProvider ?? DefaultTypeInfoProvider.Instance).Configure(serverInfo);
                    var tcpClient = new ClickHouseTcpClient(client, reader, writer, connectionSettings, serverInfo, configuredTypeInfoProvider, sslStream);

                    if (!TryChangeConnectionState(connectionState, ConnectionState.Open, tcpClient, out _, out onStateChanged))
                    {
                        throw new ClickHouseException(ClickHouseErrorCodes.InvalidConnectionState, "The state of the connection was modified.");
                    }

                    break;

                case ServerMessageCode.Error:
                    throw ((ServerErrorMessage)message).Exception;

                default:
                    if ((int)message.MessageCode == 'H')
                    {
                        // It looks like HTTP
                        string httpDetectedMessage;
                        if (connectionSettings.Port == defaultHttpPort)
                        {
                            // It's definitely HTTP
                            httpDetectedMessage = $"Detected an attempt to connect by HTTP protocol with the default port {defaultHttpPort}. ";
                        }
                        else
                        {
                            httpDetectedMessage =
                                $"Internal error. Unexpected message code (0x{message.MessageCode:X}) received from the server. " +
                                "This error may by caused by an attempt to connect with HTTP protocol. ";
                        }

                        httpDetectedMessage +=
                            $"{ClickHouseConnectionStringBuilder.DefaultClientName} supports only ClickHouse native protocol. " +
                            $"The default port for the native protocol is {ClickHouseConnectionStringBuilder.DefaultPort}.";

                        throw new ClickHouseException(ClickHouseErrorCodes.ProtocolUnexpectedResponse, httpDetectedMessage);
                    }

                    if ((int)message.MessageCode == 0x15)
                    {
                        // 0x15 stands for TLS alert message
                        var sslAlertMessage =
                            $"Unexpected message code (0x{message.MessageCode:X}) received from the server. " +
                            "This code may indicate that the server requires establishing a connection over TLS.";

                        throw new ClickHouseException(ClickHouseErrorCodes.ProtocolUnexpectedResponse, sslAlertMessage);
                    }

                    throw new ClickHouseException(ClickHouseErrorCodes.ProtocolUnexpectedResponse, $"Internal error. Unexpected message code (0x{message.MessageCode:X}) received from the server.");
                }
            }
            catch (Exception ex)
            {
                reader?.Dispose();
                writer?.Dispose();
                sslStream?.Dispose();
                client?.Client?.Close(0);
                client?.Dispose();

                if (TryChangeConnectionState(connectionState, ConnectionState.Closed, out _, out onStateChanged))
                {
                    stateChangeEx = onStateChanged(this);
                }

                if (connectionSettings.Port == defaultHttpPort && ex is IOException)
                {
                    var extraMessage =
                        $"{ex.Message} This error may be caused by an attempt to connect to the default HTTP port ({defaultHttpPort}). " +
                        $"{ClickHouseConnectionStringBuilder.DefaultClientName} supports only ClickHouse native protocol. " +
                        $"The default port for the native protocol is {ClickHouseConnectionStringBuilder.DefaultPort}.";

                    var extraEx = new IOException(extraMessage, ex);
                    if (stateChangeEx != null)
                    {
                        throw new AggregateException(extraEx, stateChangeEx);
                    }

                    throw extraEx;
                }

                if (stateChangeEx != null)
                {
                    throw new AggregateException(ex, stateChangeEx);
                }

                throw;
            }

            stateChangeEx = onStateChanged.Invoke(this);
            if (stateChangeEx != null)
            {
                throw new ClickHouseException(ClickHouseErrorCodes.CallbackError, "External callback error. See the inner exception for details.", stateChangeEx);
            }
        }