123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450 |
- using System;
- using System.Net;
- using System.Threading;
- using System.Threading.Tasks;
- namespace NetCoreServer
- {
- /// <summary>
- /// HTTP client is used to communicate with HTTP Web server. It allows to send GET, POST, PUT, DELETE requests and receive HTTP result.
- /// </summary>
- /// <remarks>Thread-safe.</remarks>
- public class HttpClient : TcpClient
- {
- /// <summary>
- /// Initialize HTTP client with a given IP address and port number
- /// </summary>
- /// <param name="address">IP address</param>
- /// <param name="port">Port number</param>
- public HttpClient(IPAddress address, int port) : base(address, port) { Request = new HttpRequest(); Response = new HttpResponse(); }
- /// <summary>
- /// Initialize HTTP client with a given IP address and port number
- /// </summary>
- /// <param name="address">IP address</param>
- /// <param name="port">Port number</param>
- public HttpClient(string address, int port) : base(address, port) { Request = new HttpRequest(); Response = new HttpResponse(); }
- /// <summary>
- /// Initialize HTTP client with a given DNS endpoint
- /// </summary>
- /// <param name="endpoint">DNS endpoint</param>
- public HttpClient(DnsEndPoint endpoint) : base(endpoint) { Request = new HttpRequest(); Response = new HttpResponse(); }
- /// <summary>
- /// Initialize HTTP client with a given IP endpoint
- /// </summary>
- /// <param name="endpoint">IP endpoint</param>
- public HttpClient(IPEndPoint endpoint) : base(endpoint) { Request = new HttpRequest(); Response = new HttpResponse(); }
- /// <summary>
- /// Get the HTTP request
- /// </summary>
- public HttpRequest Request { get; protected set; }
- /// <summary>
- /// Get the HTTP response
- /// </summary>
- protected HttpResponse Response { get; set; }
- #region Send request / Send request body
- /// <summary>
- /// Send the current HTTP request (synchronous)
- /// </summary>
- /// <returns>Size of sent data</returns>
- public long SendRequest() => SendRequest(Request);
- /// <summary>
- /// Send the HTTP request (synchronous)
- /// </summary>
- /// <param name="request">HTTP request</param>
- /// <returns>Size of sent data</returns>
- public long SendRequest(HttpRequest request) => Send(request.Cache.Data, request.Cache.Offset, request.Cache.Size);
- /// <summary>
- /// Send the HTTP request body (synchronous)
- /// </summary>
- /// <param name="body">HTTP request body</param>
- /// <returns>Size of sent data</returns>
- public long SendRequestBody(string body) => Send(body);
- /// <summary>
- /// Send the HTTP request body (synchronous)
- /// </summary>
- /// <param name="body">HTTP request body as a span of characters</param>
- /// <returns>Size of sent data</returns>
- public long SendRequestBody(ReadOnlySpan<char> body) => Send(body);
- /// <summary>
- /// Send the HTTP request body (synchronous)
- /// </summary>
- /// <param name="buffer">HTTP request body buffer</param>
- /// <returns>Size of sent data</returns>
- public long SendRequestBody(byte[] buffer) => Send(buffer);
- /// <summary>
- /// Send the HTTP request body (synchronous)
- /// </summary>
- /// <param name="buffer">HTTP request body buffer</param>
- /// <param name="offset">HTTP request body buffer offset</param>
- /// <param name="size">HTTP request body size</param>
- /// <returns>Size of sent data</returns>
- public long SendRequestBody(byte[] buffer, long offset, long size) => Send(buffer, offset, size);
- /// <summary>
- /// Send the HTTP request body (synchronous)
- /// </summary>
- /// <param name="buffer">HTTP request body buffer as a span of bytes</param>
- /// <returns>Size of sent data</returns>
- public long SendRequestBody(ReadOnlySpan<byte> buffer) => Send(buffer);
- /// <summary>
- /// Send the current HTTP request (asynchronous)
- /// </summary>
- /// <returns>'true' if the current HTTP request was successfully sent, 'false' if the session is not connected</returns>
- public bool SendRequestAsync() => SendRequestAsync(Request);
- /// <summary>
- /// Send the HTTP request (asynchronous)
- /// </summary>
- /// <param name="request">HTTP request</param>
- /// <returns>'true' if the current HTTP request was successfully sent, 'false' if the session is not connected</returns>
- public bool SendRequestAsync(HttpRequest request) => SendAsync(request.Cache.Data, request.Cache.Offset, request.Cache.Size);
- /// <summary>
- /// Send the HTTP request body (asynchronous)
- /// </summary>
- /// <param name="body">HTTP request body</param>
- /// <returns>'true' if the HTTP request body was successfully sent, 'false' if the session is not connected</returns>
- public bool SendRequestBodyAsync(string body) => SendAsync(body);
- /// <summary>
- /// Send the HTTP request body (asynchronous)
- /// </summary>
- /// <param name="body">HTTP request body as a span of characters</param>
- /// <returns>'true' if the HTTP request body was successfully sent, 'false' if the session is not connected</returns>
- public bool SendRequestBodyAsync(ReadOnlySpan<char> body) => SendAsync(body);
- /// <summary>
- /// Send the HTTP request body (asynchronous)
- /// </summary>
- /// <param name="buffer">HTTP request body buffer</param>
- /// <returns>'true' if the HTTP request body was successfully sent, 'false' if the session is not connected</returns>
- public bool SendRequestBodyAsync(byte[] buffer) => SendAsync(buffer);
- /// <summary>
- /// Send the HTTP request body (asynchronous)
- /// </summary>
- /// <param name="buffer">HTTP request body buffer</param>
- /// <param name="offset">HTTP request body buffer offset</param>
- /// <param name="size">HTTP request body size</param>
- /// <returns>'true' if the HTTP request body was successfully sent, 'false' if the session is not connected</returns>
- public bool SendRequestBodyAsync(byte[] buffer, long offset, long size) => SendAsync(buffer, offset, size);
- /// <summary>
- /// Send the HTTP request body (asynchronous)
- /// </summary>
- /// <param name="buffer">HTTP request body buffer as a span of bytes</param>
- /// <returns>'true' if the HTTP request body was successfully sent, 'false' if the session is not connected</returns>
- public bool SendRequestBodyAsync(ReadOnlySpan<byte> 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;
- }
- }
- /// <summary>
- /// Handle HTTP response header received notification
- /// </summary>
- /// <remarks>Notification is called when HTTP response header was received from the server.</remarks>
- /// <param name="response">HTTP request</param>
- protected virtual void OnReceivedResponseHeader(HttpResponse response) {}
- /// <summary>
- /// Handle HTTP response received notification
- /// </summary>
- /// <remarks>Notification is called when HTTP response was received from the server.</remarks>
- /// <param name="response">HTTP response</param>
- protected virtual void OnReceivedResponse(HttpResponse response) {}
- /// <summary>
- /// Handle HTTP response error notification
- /// </summary>
- /// <remarks>Notification is called when HTTP response error was received from the server.</remarks>
- /// <param name="response">HTTP response</param>
- /// <param name="error">HTTP response error</param>
- protected virtual void OnReceivedResponseError(HttpResponse response, string error) {}
- #endregion
- }
- /// <summary>
- /// HTTP extended client make requests to HTTP Web server with returning Task as a synchronization primitive.
- /// </summary>
- /// <remarks>Thread-safe.</remarks>
- public class HttpClientEx : HttpClient
- {
- /// <summary>
- /// Initialize HTTP client with a given IP address and port number
- /// </summary>
- /// <param name="address">IP address</param>
- /// <param name="port">Port number</param>
- public HttpClientEx(IPAddress address, int port) : base(address, port) {}
- /// <summary>
- /// Initialize HTTP client with a given IP address and port number
- /// </summary>
- /// <param name="address">IP address</param>
- /// <param name="port">Port number</param>
- public HttpClientEx(string address, int port) : base(address, port) {}
- /// <summary>
- /// Initialize HTTP client with a given DNS endpoint
- /// </summary>
- /// <param name="endpoint">DNS endpoint</param>
- public HttpClientEx(DnsEndPoint endpoint) : base(endpoint) {}
- /// <summary>
- /// Initialize HTTP client with a given IP endpoint
- /// </summary>
- /// <param name="endpoint">IP endpoint</param>
- public HttpClientEx(IPEndPoint endpoint) : base(endpoint) {}
- #region Send request
- /// <summary>
- /// Send current HTTP request
- /// </summary>
- /// <param name="timeout">Current HTTP request timeout (default is 1 minute)</param>
- /// <returns>HTTP request Task</returns>
- public Task<HttpResponse> SendRequest(TimeSpan? timeout = null) => SendRequest(Request, timeout);
- /// <summary>
- /// Send HTTP request
- /// </summary>
- /// <param name="request">HTTP request</param>
- /// <param name="timeout">HTTP request timeout (default is 1 minute)</param>
- /// <returns>HTTP request Task</returns>
- public Task<HttpResponse> SendRequest(HttpRequest request, TimeSpan? timeout = null)
- {
- timeout ??= TimeSpan.FromMinutes(1);
- _tcs = new TaskCompletionSource<HttpResponse>();
- Request = request;
- // Check if the HTTP request is valid
- if (Request.IsEmpty || Request.IsErrorSet)
- {
- SetPromiseError("Invalid HTTP request!");
- return _tcs.Task;
- }
- if (!IsConnected)
- {
- // Connect to the Web server
- if (!ConnectAsync())
- {
- SetPromiseError("Connection failed!");
- return _tcs.Task;
- }
- }
- else
- {
- // Send prepared HTTP request
- if (!SendRequestAsync())
- {
- SetPromiseError("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;
- }
- /// <summary>
- /// Send HEAD request
- /// </summary>
- /// <param name="url">URL to request</param>
- /// <param name="timeout">Current HTTP request timeout (default is 1 minute)</param>
- /// <returns>HTTP request Task</returns>
- public Task<HttpResponse> SendHeadRequest(string url, TimeSpan? timeout = null) => SendRequest(Request.MakeHeadRequest(url), timeout);
- /// <summary>
- /// Send GET request
- /// </summary>
- /// <param name="url">URL to request</param>
- /// <param name="timeout">Current HTTP request timeout (default is 1 minute)</param>
- /// <returns>HTTP request Task</returns>
- public Task<HttpResponse> SendGetRequest(string url, TimeSpan? timeout = null) => SendRequest(Request.MakeGetRequest(url), timeout);
- /// <summary>
- /// Send POST request
- /// </summary>
- /// <param name="url">URL to request</param>
- /// <param name="content">Content</param>
- /// <param name="timeout">Current HTTP request timeout (default is 1 minute)</param>
- /// <returns>HTTP request Task</returns>
- public Task<HttpResponse> SendPostRequest(string url, string content, TimeSpan? timeout = null) => SendRequest(Request.MakePostRequest(url, content), timeout);
- /// <summary>
- /// Send PUT request
- /// </summary>
- /// <param name="url">URL to request</param>
- /// <param name="content">Content</param>
- /// <param name="timeout">Current HTTP request timeout (default is 1 minute)</param>
- /// <returns>HTTP request Task</returns>
- public Task<HttpResponse> SendPutRequest(string url, string content, TimeSpan? timeout = null) => SendRequest(Request.MakePutRequest(url, content), timeout);
- /// <summary>
- /// Send DELETE request
- /// </summary>
- /// <param name="url">URL to request</param>
- /// <param name="timeout">Current HTTP request timeout (default is 1 minute)</param>
- /// <returns>HTTP request Task</returns>
- public Task<HttpResponse> SendDeleteRequest(string url, TimeSpan? timeout = null) => SendRequest(Request.MakeDeleteRequest(url), timeout);
- /// <summary>
- /// Send OPTIONS request
- /// </summary>
- /// <param name="url">URL to request</param>
- /// <param name="timeout">Current HTTP request timeout (default is 1 minute)</param>
- /// <returns>HTTP request Task</returns>
- public Task<HttpResponse> SendOptionsRequest(string url, TimeSpan? timeout = null) => SendRequest(Request.MakeOptionsRequest(url), timeout);
- /// <summary>
- /// Send TRACE request
- /// </summary>
- /// <param name="url">URL to request</param>
- /// <param name="timeout">Current HTTP request timeout (default is 1 minute)</param>
- /// <returns>HTTP request Task</returns>
- public Task<HttpResponse> SendTraceRequest(string url, TimeSpan? timeout = null) => SendRequest(Request.MakeTraceRequest(url), timeout);
- #endregion
- #region Session handlers
- protected override void OnConnected()
- {
- // Send prepared HTTP request on connect
- if (!Request.IsEmpty && !Request.IsErrorSet)
- if (!SendRequestAsync())
- SetPromiseError("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);
- SetPromiseValue(response);
- }
- protected override void OnReceivedResponseError(HttpResponse response, string error)
- {
- // Cancel timeout check timer
- _timer?.Change(Timeout.Infinite, Timeout.Infinite);
- SetPromiseError(error);
- }
- #endregion
- private TaskCompletionSource<HttpResponse> _tcs = new TaskCompletionSource<HttpResponse>();
- private Timer _timer;
- private void SetPromiseValue(HttpResponse response)
- {
- Response = new HttpResponse();
- _tcs.SetResult(response);
- Request.Clear();
- }
- private void SetPromiseError(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
- }
- }
|