using System; using System.Net; using System.Threading; using System.Threading.Tasks; namespace NetCoreServer { /// /// HTTPS client is used to communicate with secured HTTPS Web server. It allows to send GET, POST, PUT, DELETE requests and receive HTTP result using secure transport. /// /// Thread-safe. public class HttpsClient : SslClient { /// /// Initialize HTTPS client with a given IP address and port number /// /// SSL context /// IP address /// Port number public HttpsClient(SslContext context, IPAddress address, int port) : base(context, address, port) { Request = new HttpRequest(); Response = new HttpResponse(); } /// /// Initialize HTTPS client with a given IP address and port number /// /// SSL context /// IP address /// Port number public HttpsClient(SslContext context, string address, int port) : base(context, address, port) { Request = new HttpRequest(); Response = new HttpResponse(); } /// /// Initialize HTTPS client with a given DNS endpoint /// /// SSL context /// DNS endpoint public HttpsClient(SslContext context, DnsEndPoint endpoint) : base(context, endpoint) { Request = new HttpRequest(); Response = new HttpResponse(); } /// /// Initialize HTTPS client with a given IP endpoint /// /// SSL context /// IP endpoint public HttpsClient(SslContext context, IPEndPoint endpoint) : base(context, endpoint) { Request = new HttpRequest(); Response = new HttpResponse(); } /// /// Get the HTTP request /// public HttpRequest Request { get; protected set; } /// /// Get the HTTP response /// protected HttpResponse Response { get; set; } #region Send request / Send request body /// /// Send the current HTTP request (synchronous) /// /// Size of sent data public long SendRequest() => SendRequest(Request); /// /// Send the HTTP request (synchronous) /// /// HTTP request /// Size of sent data public long SendRequest(HttpRequest request) => Send(request.Cache.Data, request.Cache.Offset, request.Cache.Size); /// /// Send the HTTP request body (synchronous) /// /// HTTP request body /// Size of sent data public long SendRequestBody(string body) => Send(body); /// /// Send the HTTP request body (synchronous) /// /// HTTP request body as a span of characters /// Size of sent data public long SendRequestBody(ReadOnlySpan body) => Send(body); /// /// Send the HTTP request body (synchronous) /// /// HTTP request body buffer /// Size of sent data public long SendRequestBody(byte[] buffer) => Send(buffer); /// /// Send the HTTP request body (synchronous) /// /// HTTP request body buffer /// HTTP request body buffer offset /// HTTP request body size /// Size of sent data public long SendRequestBody(byte[] buffer, long offset, long size) => Send(buffer, offset, size); /// /// Send the HTTP request body (synchronous) /// /// HTTP request body buffer as a span of bytes /// Size of sent data public long SendRequestBody(ReadOnlySpan buffer) => Send(buffer); /// /// Send the current HTTP request (asynchronous) /// /// 'true' if the current HTTP request was successfully sent, 'false' if the session is not connected public bool SendRequestAsync() => SendRequestAsync(Request); /// /// Send the HTTP request (asynchronous) /// /// HTTP request /// 'true' if the current HTTP request was successfully sent, 'false' if the session is not connected public bool SendRequestAsync(HttpRequest request) => SendAsync(request.Cache.Data, request.Cache.Offset, request.Cache.Size); /// /// Send the HTTP request body (asynchronous) /// /// HTTP request body /// 'true' if the HTTP request body was successfully sent, 'false' if the session is not connected public bool SendRequestBodyAsync(string body) => SendAsync(body); /// /// Send the HTTP request body (asynchronous) /// /// HTTP request body as a span of characters /// 'true' if the HTTP request body was successfully sent, 'false' if the session is not connected public bool SendRequestBodyAsync(ReadOnlySpan body) => SendAsync(body); /// /// Send the HTTP request body (asynchronous) /// /// HTTP request body buffer /// 'true' if the HTTP request body was successfully sent, 'false' if the session is not connected public bool SendRequestBodyAsync(byte[] buffer) => SendAsync(buffer); /// /// Send the HTTP request body (asynchronous) /// /// HTTP request body buffer /// HTTP request body buffer offset /// HTTP request body size /// 'true' if the HTTP request body was successfully sent, 'false' if the session is not connected public bool SendRequestBodyAsync(byte[] buffer, long offset, long size) => SendAsync(buffer, offset, size); /// /// Send the HTTP request body (asynchronous) /// /// HTTP request body buffer as a span of bytes /// 'true' if the HTTP request body was successfully sent, 'false' if the session is not connected public bool SendRequestBodyAsync(ReadOnlySpan buffer) => SendAsync(buffer); #endregion #region Session handlers protected override void OnReceived(byte[] buffer, long offset, long size) { // Receive HTTP response header if (Response.IsPendingHeader()) { if (Response.ReceiveHeader(buffer, (int)offset, (int)size)) OnReceivedResponseHeader(Response); size = 0; } // Check for HTTP response error if (Response.IsErrorSet) { OnReceivedResponseError(Response, "Invalid HTTP response!"); Response.Clear(); Disconnect(); return; } // Receive HTTP response body if (Response.ReceiveBody(buffer, (int)offset, (int)size)) { OnReceivedResponse(Response); Response.Clear(); return; } // Check for HTTP response error if (Response.IsErrorSet) { OnReceivedResponseError(Response, "Invalid HTTP response!"); Response.Clear(); Disconnect(); return; } } protected override void OnDisconnected() { // Receive HTTP response body if (Response.IsPendingBody()) { OnReceivedResponse(Response); Response.Clear(); return; } } /// /// Handle HTTP response header received notification /// /// Notification is called when HTTP response header was received from the server. /// HTTP request protected virtual void OnReceivedResponseHeader(HttpResponse response) {} /// /// Handle HTTP response received notification /// /// Notification is called when HTTP response was received from the server. /// HTTP response protected virtual void OnReceivedResponse(HttpResponse response) {} /// /// Handle HTTP response error notification /// /// Notification is called when HTTP response error was received from the server. /// HTTP response /// HTTP response error protected virtual void OnReceivedResponseError(HttpResponse response, string error) {} #endregion } /// /// HTTPS extended client make requests to HTTPS Web server with returning Task as a synchronization primitive. /// /// Thread-safe. public class HttpsClientEx : HttpsClient { /// /// Initialize HTTPS client with a given IP address and port number /// /// SSL context /// IP address /// Port number public HttpsClientEx(SslContext context, IPAddress address, int port) : base(context, address, port) {} /// /// Initialize HTTPS client with a given IP address and port number /// /// SSL context /// IP address /// Port number public HttpsClientEx(SslContext context, string address, int port) : base(context, address, port) {} /// /// Initialize HTTPS client with a given DNS endpoint /// /// SSL context /// DNS endpoint public HttpsClientEx(SslContext context, DnsEndPoint endpoint) : base(context, endpoint) {} /// /// Initialize HTTPS client with a given IP endpoint /// /// SSL context /// IP endpoint public HttpsClientEx(SslContext context, IPEndPoint endpoint) : base(context, endpoint) {} #region Send request /// /// Send current HTTP request /// /// Current HTTP request timeout (default is 1 minute) /// HTTP request Task public Task SendRequest(TimeSpan? timeout = null) => SendRequest(Request, timeout); /// /// Send HTTP request /// /// HTTP request /// HTTP request timeout (default is 1 minute) /// HTTP request Task public Task SendRequest(HttpRequest request, TimeSpan? timeout = null) { timeout ??= TimeSpan.FromMinutes(1); _tcs = new TaskCompletionSource(); Request = request; // Check if the HTTP request is valid if (Request.IsEmpty || Request.IsErrorSet) { SetResultError("Invalid HTTP request!"); return _tcs.Task; } if (!IsHandshaked) { // Connect to the Web server if (!ConnectAsync()) { SetResultError("Connection failed!"); return _tcs.Task; } } else { // Send prepared HTTP request if (!SendRequestAsync()) { SetResultError("Failed to send HTTP request!"); return _tcs.Task; } } void TimeoutHandler(object state) { // Disconnect on timeout OnReceivedResponseError(Response, "Timeout!"); Response.Clear(); DisconnectAsync(); } // Create a new timeout timer if (_timer == null) _timer = new Timer(TimeoutHandler, null, Timeout.Infinite, Timeout.Infinite); // Start the timeout timer _timer.Change((int)timeout.Value.TotalMilliseconds, Timeout.Infinite); return _tcs.Task; } /// /// Send HEAD request /// /// URL to request /// Current HTTP request timeout (default is 1 minute) /// HTTP request Task public Task SendHeadRequest(string url, TimeSpan? timeout = null) => SendRequest(Request.MakeHeadRequest(url), timeout); /// /// Send GET request /// /// URL to request /// Current HTTP request timeout (default is 1 minute) /// HTTP request Task public Task SendGetRequest(string url, TimeSpan? timeout = null) => SendRequest(Request.MakeGetRequest(url), timeout); /// /// Send POST request /// /// URL to request /// Content /// Current HTTP request timeout (default is 1 minute) /// HTTP request Task public Task SendPostRequest(string url, string content, TimeSpan? timeout = null) => SendRequest(Request.MakePostRequest(url, content), timeout); /// /// Send PUT request /// /// URL to request /// Content /// Current HTTP request timeout (default is 1 minute) /// HTTP request Task public Task SendPutRequest(string url, string content, TimeSpan? timeout = null) => SendRequest(Request.MakePutRequest(url, content), timeout); /// /// Send DELETE request /// /// URL to request /// Current HTTP request timeout (default is 1 minute) /// HTTP request Task public Task SendDeleteRequest(string url, TimeSpan? timeout = null) => SendRequest(Request.MakeDeleteRequest(url), timeout); /// /// Send OPTIONS request /// /// URL to request /// Current HTTP request timeout (default is 1 minute) /// HTTP request Task public Task SendOptionsRequest(string url, TimeSpan? timeout = null) => SendRequest(Request.MakeOptionsRequest(url), timeout); /// /// Send TRACE request /// /// URL to request /// Current HTTP request timeout (default is 1 minute) /// HTTP request Task public Task SendTraceRequest(string url, TimeSpan? timeout = null) => SendRequest(Request.MakeTraceRequest(url), timeout); #endregion #region Session handlers protected override void OnHandshaked() { // Send prepared HTTP request on connect if (!Request.IsEmpty && !Request.IsErrorSet) if (!SendRequestAsync()) SetResultError("Failed to send HTTP request!"); } protected override void OnDisconnected() { // Cancel timeout check timer _timer?.Change(Timeout.Infinite, Timeout.Infinite); base.OnDisconnected(); } protected override void OnReceivedResponse(HttpResponse response) { // Cancel timeout check timer _timer?.Change(Timeout.Infinite, Timeout.Infinite); SetResultValue(response); } protected override void OnReceivedResponseError(HttpResponse response, string error) { // Cancel timeout check timer _timer?.Change(Timeout.Infinite, Timeout.Infinite); SetResultError(error); } #endregion private TaskCompletionSource _tcs = new TaskCompletionSource(); private Timer _timer; private void SetResultValue(HttpResponse response) { Response = new HttpResponse(); _tcs.SetResult(response); Request.Clear(); } private void SetResultError(string error) { _tcs.SetException(new Exception(error)); Request.Clear(); } #region IDisposable implementation // Disposed flag. private bool _disposed; protected override void Dispose(bool disposingManagedResources) { if (!_disposed) { if (disposingManagedResources) { // Dispose managed resources here... _timer?.Dispose(); } // Dispose unmanaged resources here... // Set large fields to null here... // Mark as disposed. _disposed = true; } // Call Dispose in the base class. base.Dispose(disposingManagedResources); } // The derived class does not have a Finalize method // or a Dispose method without parameters because it inherits // them from the base class. #endregion } }