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
}
}