///// <summary> ///// Create a new HTTP request. ///// </summary> ///// <param name="HTTPMethod">A HTTP method.</param> ///// <param name="URI">An URL path.</param> ///// <param name="BuilderAction">A delegate to configure the new HTTP request builder.</param> ///// <returns>A new HTTPRequest object.</returns> //public HTTPRequestBuilder CreateRequest(String HTTPMethod, // String URI = "/", // Action<HTTPRequestBuilder> BuilderAction = null) //{ // var Builder = new HTTPRequestBuilder(this) { // HTTPMethod = new HTTPMethod(HTTPMethod), // URI = URI // }; // BuilderAction.FailSafeInvoke(Builder); // return Builder; //} #endregion #region Execute(HTTPRequestDelegate, RequestLogDelegate = null, ResponseLogDelegate = null, Timeout = null, CancellationToken = null) /// <summary> /// Execute the given HTTP request and return its result. /// </summary> /// <param name="HTTPRequestDelegate">A delegate for producing a HTTP request for a given HTTP client.</param> /// <param name="RequestLogDelegate">A delegate for logging the HTTP request.</param> /// <param name="ResponseLogDelegate">A delegate for logging the HTTP request/response.</param> /// <param name="Timeout">An optional timeout.</param> /// <param name="CancellationToken">A cancellation token.</param> public async Task <HTTPResponse> Execute(Func <HTTPClient, HTTPRequest> HTTPRequestDelegate, ClientRequestLogHandler RequestLogDelegate = null, ClientResponseLogHandler ResponseLogDelegate = null, TimeSpan?Timeout = null, CancellationToken?CancellationToken = null) { #region Initial checks if (HTTPRequestDelegate == null) { throw new ArgumentNullException(nameof(HTTPRequestDelegate), "The given delegate must not be null!"); } #endregion return(await Execute(HTTPRequestDelegate(this), RequestLogDelegate, ResponseLogDelegate, Timeout, CancellationToken)); }
/// <summary> /// Execute the given HTTP request and return its result. /// </summary> /// <param name="Request">A HTTP request.</param> /// <param name="RequestLogDelegate">A delegate for logging the HTTP request.</param> /// <param name="ResponseLogDelegate">A delegate for logging the HTTP request/response.</param> /// <param name="Timeout">An optional timeout.</param> /// <param name="CancellationToken">A cancellation token.</param> public async Task<HTTPResponse> Execute(HTTPRequest Request, ClientRequestLogHandler RequestLogDelegate = null, ClientResponseLogHandler ResponseLogDelegate = null, TimeSpan? Timeout = null, CancellationToken? CancellationToken = null) { #region Call the optional HTTP request log delegate try { RequestLogDelegate?.Invoke(DateTime.Now, this, Request); } catch (Exception e) { e.Log(nameof(HTTPClient) + "." + nameof(RequestLogDelegate)); } #endregion var task = Task<HTTPResponse>.Factory.StartNew(() => { HTTPResponse Response = null; try { if (Environment.MachineName.Contains("QUAD2QUANTOR") && Request.URI.Contains("eRoaming")) throw new Exception("Catch me if you can!"); Thread.CurrentThread.Priority = ThreadPriority.BelowNormal; #region Data var HTTPHeaderBytes = new Byte[0]; var sw = new Stopwatch(); if (!Timeout.HasValue) Timeout = TimeSpan.FromSeconds(60); #endregion #region Create TCP connection (possibly also do DNS lookups) if (TCPClient == null) { System.Net.IPEndPoint _FinalIPEndPoint = null; IIPAddress _ResolvedRemoteIPAddress = null; if (RemoteIPAddress == null) { if (Hostname.Trim() == "127.0.0.1") _ResolvedRemoteIPAddress = IPv4Address.Localhost; else { var RegExpr = new Regex(@"\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b"); if (RegExpr.IsMatch(Hostname)) _ResolvedRemoteIPAddress = IPv4Address.Parse(Hostname); } #region DNS lookup... if (_ResolvedRemoteIPAddress == null) { try { var IPv4AddressTask = DNSClient. Query<A>(Hostname). ContinueWith(QueryTask => QueryTask.Result. Select(ARecord => ARecord.IPv4Address). FirstOrDefault()); IPv4AddressTask.Wait(); _ResolvedRemoteIPAddress = IPv4AddressTask.Result; } catch (Exception e) { Debug.WriteLine("[" + DateTime.Now + "] " + e.Message); } } #endregion } else _ResolvedRemoteIPAddress = RemoteIPAddress; _FinalIPEndPoint = new System.Net.IPEndPoint(new System.Net.IPAddress(_ResolvedRemoteIPAddress.GetBytes()), RemotePort.ToInt32()); sw.Start(); TCPClient = new TcpClient(); TCPClient.Connect(_FinalIPEndPoint); TCPClient.ReceiveTimeout = (Int32) Timeout.Value.TotalMilliseconds; } #endregion #region Create (Crypto-)Stream TCPStream = TCPClient.GetStream(); TCPStream.ReadTimeout = (Int32)Timeout.Value.TotalMilliseconds; TLSStream = RemoteCertificateValidator != null ? new SslStream(TCPStream, false, RemoteCertificateValidator) // ClientCertificateSelector, //EncryptionPolicy.RequireEncryption) : null; if (TLSStream != null) TLSStream.ReadTimeout = (Int32)Timeout.Value.TotalMilliseconds; HTTPStream = null; if (RemoteCertificateValidator != null) { HTTPStream = TLSStream; TLSStream.AuthenticateAsClient(Hostname);//, new X509CertificateCollection(new X509Certificate[] { ClientCert }), SslProtocols.Default, false); } else HTTPStream = TCPStream; HTTPStream.ReadTimeout = (Int32)Timeout.Value.TotalMilliseconds; #endregion #region Send Request HTTPStream.Write(String.Concat(Request.EntireRequestHeader, Environment.NewLine, Environment.NewLine). ToUTF8Bytes()); var RequestBodyLength = Request.HTTPBody == null ? Request.ContentLength.HasValue ? (Int32) Request.ContentLength.Value : 0 : Request.ContentLength.HasValue ? Math.Min((Int32) Request.ContentLength.Value, Request.HTTPBody.Length) : Request.HTTPBody.Length; if (RequestBodyLength > 0) HTTPStream.Write(Request.HTTPBody, 0, RequestBodyLength); var _MemoryStream = new MemoryStream(); var _Buffer = new Byte[10485760]; // 10 MBytes, a smaller value leads to read errors! #endregion #region Wait timeout for the server to react! //Debug.WriteLine("[" + DateTime.Now + "] HTTPClient timeout: " + Timeout.Value.ToString()); while (!TCPStream.DataAvailable) { if (sw.ElapsedMilliseconds >= Timeout.Value.TotalMilliseconds) { TCPClient.Close(); throw new Exception("[" + DateTime.Now + "] Could not read from the TCP stream for " + sw.ElapsedMilliseconds + "ms!"); } Thread.Sleep(1); } //Debug.WriteLine("[" + DateTime.Now + "] HTTPClient (" + TCPClient.Client.LocalEndPoint.ToString() + " -> " + RemoteSocket.ToString() + ") got first response after " + sw.ElapsedMilliseconds + "ms!"); #endregion #region Read the entire HTTP header, and maybe some of the HTTP body var CurrentDataLength = 0; do { #region When data available, write it to the buffer... while (TCPStream.DataAvailable) { CurrentDataLength = HTTPStream.Read(_Buffer, 0, _Buffer.Length); if (CurrentDataLength > -1) { _MemoryStream.Write(_Buffer, 0, CurrentDataLength); // Debug.WriteLine("[" + DateTime.Now + "] Read " + CurrentDataLength + " bytes from HTTP connection (" + TCPClient.Client.LocalEndPoint.ToString() + " -> " + RemoteSocket.ToString() + ") (" + sw.ElapsedMilliseconds + "ms)!"); } } #endregion #region Check if the entire HTTP header was already read into the buffer if (_MemoryStream.Length > 4) { var MemoryCopy = _MemoryStream.ToArray(); for (var pos = 3; pos < MemoryCopy.Length; pos++) { if (MemoryCopy[pos ] == 0x0a && MemoryCopy[pos - 1] == 0x0d && MemoryCopy[pos - 2] == 0x0a && MemoryCopy[pos - 3] == 0x0d) { Array.Resize(ref HTTPHeaderBytes, pos - 3); Array.Copy(MemoryCopy, 0, HTTPHeaderBytes, 0, pos - 3); break; } } //if (HTTPHeaderBytes.Length > 0) // Debug.WriteLine("[" + DateTime.Now + "] End of (" + TCPClient.Client.LocalEndPoint.ToString() + " -> " + RemoteSocket.ToString() + ") HTTP header at " + HTTPHeaderBytes.Length + " bytes (" + sw.ElapsedMilliseconds + "ms)!"); } #endregion Thread.Sleep(1); } // Note: Delayed parts of the HTTP body may not be read into the buffer // => Must be read later! while (TCPStream.DataAvailable || ((sw.ElapsedMilliseconds < HTTPStream.ReadTimeout) && HTTPHeaderBytes.Length == 0)); //Debug.WriteLine("[" + DateTime.Now + "] Finally read " + _MemoryStream.Length + " bytes of HTTP client (" + TCPClient.Client.LocalEndPoint.ToString() + " -> " + RemoteSocket.ToString() + ") data (" + sw.ElapsedMilliseconds + "ms)!"); #endregion #region Copy HTTP header data and create HTTP response if (HTTPHeaderBytes.Length == 0) throw new ApplicationException(DateTime.Now + " Could not find the end of the HTTP protocol header!"); Response = HTTPResponse.Parse(HTTPHeaderBytes.ToUTF8String(), Request); #endregion #region Read 'Content-Length' bytes... // Copy only the number of bytes given within // the HTTP header element 'Content-Length'! if (Response.ContentLength.HasValue && Response.ContentLength.Value > 0) { _MemoryStream.Seek(HTTPHeaderBytes.Length + 4, SeekOrigin.Begin); var _Read = _MemoryStream.Read(_Buffer, 0, _Buffer.Length); var _StillToRead = (Int32)Response.ContentLength.Value - _Read; Response.HTTPBodyStream.Write(_Buffer, 0, _Read); var _CurrentBufferSize = 0; do { while (TCPStream.DataAvailable && _StillToRead > 0) { _CurrentBufferSize = Math.Min(_Buffer.Length, (Int32)_StillToRead); _Read = HTTPStream.Read(_Buffer, 0, _CurrentBufferSize); Response.HTTPBodyStream.Write(_Buffer, 0, _Read); _StillToRead -= _Read; } if (_StillToRead <= 0) break; Thread.Sleep(1); } while (sw.ElapsedMilliseconds < HTTPStream.ReadTimeout); Response.ContentStreamToArray(); } #endregion #region ...or read till timeout (e.g. for chunked transport)! else { try { _MemoryStream.Seek(HTTPHeaderBytes.Length + 4, SeekOrigin.Begin); Response.NewContentStream(); Response.HTTPBodyStream.Write(_Buffer, 0, _MemoryStream.Read(_Buffer, 0, _Buffer.Length)); var Retries = 0; while (Retries < 10) { while (TCPStream.DataAvailable) { Response.HTTPBodyStream.Write(_Buffer, 0, HTTPStream.Read(_Buffer, 0, _Buffer.Length)); Retries = 0; } Thread.Sleep(10); Retries++; } if (Response.TransferEncoding == "chunked") { //Debug.WriteLine(DateTime.Now + " Chunked encoding detected"); var TEContent = ((MemoryStream)Response.HTTPBodyStream).ToArray(); var TEString = TEContent.ToUTF8String(); var ReadBlockLength = true; var TEMemStram = new MemoryStream(); var LastPos = 0; var i = 0; do { if (i > 2 && ReadBlockLength && TEContent[i - 1] == '\n' && TEContent[i - 2] == '\r') { var len = TEContent.ReadTEBlockLength(LastPos, i - LastPos - 2); //Debug.WriteLine(DateTime.Now + " Chunked encoded block of length " + len + " bytes detected"); if (len == 0) break; if (i + len <= TEContent.Length) { TEMemStram.Write(TEContent, i, len); i = i + len; if (TEContent[i] == 0x0d) i++; if (i < TEContent.Length - 1) { if (TEContent[i] == 0x0a) i++; } else { } LastPos = i; ReadBlockLength = false; } else { // Reaching this point seems to be an endless loop! break; } } else { ReadBlockLength = true; i++; } } while (i < TEContent.Length); Response.ContentStreamToArray(TEMemStram); } else Response.ContentStreamToArray(); } catch (Exception e) { Debug.WriteLine(DateTime.Now + " " + e.Message); } } #endregion #region Close connection if requested! if (Response.Connection == null || Response.Connection == "close") { TCPClient.Close(); HTTPStream = null; TCPClient = null; } #endregion } catch (Exception e) { #region Create a HTTP response for the exception... while (e.InnerException != null) e = e.InnerException; Response = new HTTPResponseBuilder(Request, HTTPStatusCode.BadRequest) { ContentType = HTTPContentType.JSON_UTF8, Content = JSONObject.Create(new JProperty("Message", e.Message), new JProperty("StackTrace", e.StackTrace)). ToUTF8Bytes() }; #endregion } #region Call the optional HTTP response log delegate try { ResponseLogDelegate?.Invoke(DateTime.Now, this, Request, Response); } catch (Exception e2) { e2.Log(nameof(HTTPClient) + "." + nameof(ResponseLogDelegate)); } #endregion return Response; }, TaskCreationOptions.AttachedToParent); return await task; }
///// <summary> ///// Create a new HTTP request. ///// </summary> ///// <param name="HTTPMethod">A HTTP method.</param> ///// <param name="URI">An URL path.</param> ///// <param name="BuilderAction">A delegate to configure the new HTTP request builder.</param> ///// <returns>A new HTTPRequest object.</returns> //public HTTPRequestBuilder CreateRequest(String HTTPMethod, // String URI = "/", // Action<HTTPRequestBuilder> BuilderAction = null) //{ // var Builder = new HTTPRequestBuilder(this) { // HTTPMethod = new HTTPMethod(HTTPMethod), // URI = URI // }; // BuilderAction.FailSafeInvoke(Builder); // return Builder; //} #endregion #region Execute(HTTPRequestDelegate, RequestLogDelegate = null, ResponseLogDelegate = null, Timeout = null, CancellationToken = null) /// <summary> /// Execute the given HTTP request and return its result. /// </summary> /// <param name="HTTPRequestDelegate">A delegate for producing a HTTP request for a given HTTP client.</param> /// <param name="RequestLogDelegate">A delegate for logging the HTTP request.</param> /// <param name="ResponseLogDelegate">A delegate for logging the HTTP request/response.</param> /// <param name="Timeout">An optional timeout.</param> /// <param name="CancellationToken">A cancellation token.</param> public async Task<HTTPResponse> Execute(Func<HTTPClient, HTTPRequest> HTTPRequestDelegate, ClientRequestLogHandler RequestLogDelegate = null, ClientResponseLogHandler ResponseLogDelegate = null, TimeSpan? Timeout = null, CancellationToken? CancellationToken = null) { #region Initial checks if (HTTPRequestDelegate == null) throw new ArgumentNullException(nameof(HTTPRequestDelegate), "The given delegate must not be null!"); #endregion return await Execute(HTTPRequestDelegate(this), RequestLogDelegate, ResponseLogDelegate, Timeout, CancellationToken); }
Query <T>(JObject JSONRequest, Func <HTTPResponse <JObject>, HTTPResponse <T> > OnSuccess, Func <DateTime, Object, HTTPResponse <JObject>, HTTPResponse <T> > OnJSONFault, Func <DateTime, Object, HTTPResponse, HTTPResponse <T> > OnHTTPError, Func <DateTime, Object, Exception, HTTPResponse <T> > OnException, Action <HTTPRequestBuilder> HTTPRequestBuilder = null, ClientRequestLogHandler RequestLogDelegate = null, ClientResponseLogHandler ResponseLogDelegate = null, CancellationToken?CancellationToken = null, EventTracking_Id EventTrackingId = null, TimeSpan?RequestTimeout = null) { #region Initial checks if (JSONRequest == null) { throw new ArgumentNullException(nameof(JSONRequest), "The JSON request must not be null!"); } if (OnSuccess == null) { throw new ArgumentNullException(nameof(OnSuccess), "The 'OnSuccess'-delegate must not be null!"); } if (OnJSONFault == null) { throw new ArgumentNullException(nameof(OnJSONFault), "The 'OnJSONFault'-delegate must not be null!"); } if (OnHTTPError == null) { throw new ArgumentNullException(nameof(OnHTTPError), "The 'OnHTTPError'-delegate must not be null!"); } if (OnException == null) { throw new ArgumentNullException(nameof(OnException), "The 'OnException'-delegate must not be null!"); } #endregion var _RequestBuilder = this.POST(URIPrefix); _RequestBuilder.Host = HTTPVirtualHost; _RequestBuilder.Content = JSONRequest.ToUTF8Bytes(); _RequestBuilder.ContentType = HTTPContentType.JSON_UTF8; _RequestBuilder.UserAgent = UserAgent; _RequestBuilder.FakeURIPrefix = "https://" + HTTPVirtualHost; HTTPRequestBuilder?.Invoke(_RequestBuilder); return(this.Execute(_RequestBuilder, RequestLogDelegate, ResponseLogDelegate, RequestTimeout ?? TimeSpan.FromSeconds(60), CancellationToken.HasValue ? CancellationToken.Value : new CancellationTokenSource().Token). ContinueWith(HttpResponseTask => { if (HttpResponseTask.Result == null || HttpResponseTask.Result.HTTPStatusCode != HTTPStatusCode.OK || HttpResponseTask.Result.HTTPBody == null || HttpResponseTask.Result.HTTPBody.Length == 0) { var OnHTTPErrorLocal = OnHTTPError; if (OnHTTPErrorLocal != null) { return OnHTTPErrorLocal(DateTime.Now, this, HttpResponseTask?.Result); } return new HTTPResponse <JObject>(HttpResponseTask?.Result, new JObject(new JProperty("HTTPError", "")), IsFault: true) as HTTPResponse <T>; } try { var JSON = JObject.Parse(HttpResponseTask.Result.HTTPBody.ToUTF8String()); var OnSuccessLocal = OnSuccess; if (OnSuccessLocal != null) { return OnSuccessLocal(new HTTPResponse <JObject>(HttpResponseTask.Result, JSON)); } //var OnSOAPFaultLocal = OnSOAPFault; //if (OnSOAPFaultLocal != null) // return OnSOAPFaultLocal(DateTime.Now, this, new HTTPResponse<XElement>(HttpResponseTask.Result, SOAPXML)); return new HTTPResponse <JObject>(HttpResponseTask.Result, new JObject(new JProperty("fault", "")), IsFault: true) as HTTPResponse <T>; } catch (Exception e) { OnException?.Invoke(DateTime.Now, this, e); //var OnFaultLocal = OnSOAPFault; //if (OnFaultLocal != null) // return OnFaultLocal(new HTTPResponse<XElement>(HttpResponseTask.Result, e)); return new HTTPResponse <JObject>(HttpResponseTask.Result, new JObject(new JProperty("exception", e.Message)), IsFault: true) as HTTPResponse <T>; } })); }
/// <summary> /// Execute the given HTTP request and return its result. /// </summary> /// <param name="Request">A HTTP request.</param> /// <param name="RequestLogDelegate">A delegate for logging the HTTP request.</param> /// <param name="ResponseLogDelegate">A delegate for logging the HTTP request/response.</param> /// <param name="Timeout">An optional timeout.</param> /// <param name="CancellationToken">A cancellation token.</param> public async Task <HTTPResponse> Execute(HTTPRequest Request, ClientRequestLogHandler RequestLogDelegate = null, ClientResponseLogHandler ResponseLogDelegate = null, TimeSpan?Timeout = null, CancellationToken?CancellationToken = null) { #region Call the optional HTTP request log delegate try { RequestLogDelegate?.Invoke(DateTime.Now, this, Request); } catch (Exception e) { e.Log(nameof(HTTPClient) + "." + nameof(RequestLogDelegate)); } #endregion var task = Task <HTTPResponse> .Factory.StartNew(() => { HTTPResponse Response = null; try { if (Environment.MachineName.Contains("QUAD2QUANTOR") && Request.URI.Contains("eRoaming")) { throw new Exception("Catch me if you can!"); } Thread.CurrentThread.Priority = ThreadPriority.BelowNormal; #region Data var HTTPHeaderBytes = new Byte[0]; var sw = new Stopwatch(); if (!Timeout.HasValue) { Timeout = TimeSpan.FromSeconds(60); } #endregion #region Create TCP connection (possibly also do DNS lookups) if (TCPClient == null) { System.Net.IPEndPoint _FinalIPEndPoint = null; IIPAddress _ResolvedRemoteIPAddress = null; if (RemoteIPAddress == null) { if (Hostname.Trim() == "127.0.0.1") { _ResolvedRemoteIPAddress = IPv4Address.Localhost; } else { var RegExpr = new Regex(@"\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b"); if (RegExpr.IsMatch(Hostname)) { _ResolvedRemoteIPAddress = IPv4Address.Parse(Hostname); } } #region DNS lookup... if (_ResolvedRemoteIPAddress == null) { try { var IPv4AddressTask = DNSClient. Query <A>(Hostname). ContinueWith(QueryTask => QueryTask.Result. Select(ARecord => ARecord.IPv4Address). FirstOrDefault()); IPv4AddressTask.Wait(); _ResolvedRemoteIPAddress = IPv4AddressTask.Result; } catch (Exception e) { Debug.WriteLine("[" + DateTime.Now + "] " + e.Message); } } #endregion } else { _ResolvedRemoteIPAddress = RemoteIPAddress; } _FinalIPEndPoint = new System.Net.IPEndPoint(new System.Net.IPAddress(_ResolvedRemoteIPAddress.GetBytes()), RemotePort.ToInt32()); sw.Start(); TCPClient = new TcpClient(); TCPClient.Connect(_FinalIPEndPoint); TCPClient.ReceiveTimeout = (Int32)Timeout.Value.TotalMilliseconds; } #endregion #region Create (Crypto-)Stream TCPStream = TCPClient.GetStream(); TCPStream.ReadTimeout = (Int32)Timeout.Value.TotalMilliseconds; TLSStream = RemoteCertificateValidator != null ? new SslStream(TCPStream, false, RemoteCertificateValidator) // ClientCertificateSelector, //EncryptionPolicy.RequireEncryption) : null; if (TLSStream != null) { TLSStream.ReadTimeout = (Int32)Timeout.Value.TotalMilliseconds; } HTTPStream = null; if (RemoteCertificateValidator != null) { HTTPStream = TLSStream; TLSStream.AuthenticateAsClient(Hostname);//, new X509CertificateCollection(new X509Certificate[] { ClientCert }), SslProtocols.Default, false); } else { HTTPStream = TCPStream; } HTTPStream.ReadTimeout = (Int32)Timeout.Value.TotalMilliseconds; #endregion #region Send Request HTTPStream.Write(String.Concat(Request.EntireRequestHeader, Environment.NewLine, Environment.NewLine). ToUTF8Bytes()); var RequestBodyLength = Request.HTTPBody == null ? Request.ContentLength.HasValue ? (Int32)Request.ContentLength.Value : 0 : Request.ContentLength.HasValue ? Math.Min((Int32)Request.ContentLength.Value, Request.HTTPBody.Length) : Request.HTTPBody.Length; if (RequestBodyLength > 0) { HTTPStream.Write(Request.HTTPBody, 0, RequestBodyLength); } var _MemoryStream = new MemoryStream(); var _Buffer = new Byte[10485760]; // 10 MBytes, a smaller value leads to read errors! #endregion #region Wait timeout for the server to react! //Debug.WriteLine("[" + DateTime.Now + "] HTTPClient timeout: " + Timeout.Value.ToString()); while (!TCPStream.DataAvailable) { if (sw.ElapsedMilliseconds >= Timeout.Value.TotalMilliseconds) { TCPClient.Close(); throw new Exception("[" + DateTime.Now + "] Could not read from the TCP stream for " + sw.ElapsedMilliseconds + "ms!"); } Thread.Sleep(1); } //Debug.WriteLine("[" + DateTime.Now + "] HTTPClient (" + TCPClient.Client.LocalEndPoint.ToString() + " -> " + RemoteSocket.ToString() + ") got first response after " + sw.ElapsedMilliseconds + "ms!"); #endregion #region Read the entire HTTP header, and maybe some of the HTTP body var CurrentDataLength = 0; do { #region When data available, write it to the buffer... while (TCPStream.DataAvailable) { CurrentDataLength = HTTPStream.Read(_Buffer, 0, _Buffer.Length); if (CurrentDataLength > -1) { _MemoryStream.Write(_Buffer, 0, CurrentDataLength); // Debug.WriteLine("[" + DateTime.Now + "] Read " + CurrentDataLength + " bytes from HTTP connection (" + TCPClient.Client.LocalEndPoint.ToString() + " -> " + RemoteSocket.ToString() + ") (" + sw.ElapsedMilliseconds + "ms)!"); } } #endregion #region Check if the entire HTTP header was already read into the buffer if (_MemoryStream.Length > 4) { var MemoryCopy = _MemoryStream.ToArray(); for (var pos = 3; pos < MemoryCopy.Length; pos++) { if (MemoryCopy[pos] == 0x0a && MemoryCopy[pos - 1] == 0x0d && MemoryCopy[pos - 2] == 0x0a && MemoryCopy[pos - 3] == 0x0d) { Array.Resize(ref HTTPHeaderBytes, pos - 3); Array.Copy(MemoryCopy, 0, HTTPHeaderBytes, 0, pos - 3); break; } } //if (HTTPHeaderBytes.Length > 0) // Debug.WriteLine("[" + DateTime.Now + "] End of (" + TCPClient.Client.LocalEndPoint.ToString() + " -> " + RemoteSocket.ToString() + ") HTTP header at " + HTTPHeaderBytes.Length + " bytes (" + sw.ElapsedMilliseconds + "ms)!"); } #endregion Thread.Sleep(1); } // Note: Delayed parts of the HTTP body may not be read into the buffer // => Must be read later! while (TCPStream.DataAvailable || ((sw.ElapsedMilliseconds < HTTPStream.ReadTimeout) && HTTPHeaderBytes.Length == 0)); //Debug.WriteLine("[" + DateTime.Now + "] Finally read " + _MemoryStream.Length + " bytes of HTTP client (" + TCPClient.Client.LocalEndPoint.ToString() + " -> " + RemoteSocket.ToString() + ") data (" + sw.ElapsedMilliseconds + "ms)!"); #endregion #region Copy HTTP header data and create HTTP response if (HTTPHeaderBytes.Length == 0) { throw new ApplicationException(DateTime.Now + " Could not find the end of the HTTP protocol header!"); } Response = HTTPResponse.Parse(HTTPHeaderBytes.ToUTF8String(), Request); #endregion #region Read 'Content-Length' bytes... // Copy only the number of bytes given within // the HTTP header element 'Content-Length'! if (Response.ContentLength.HasValue && Response.ContentLength.Value > 0) { _MemoryStream.Seek(HTTPHeaderBytes.Length + 4, SeekOrigin.Begin); var _Read = _MemoryStream.Read(_Buffer, 0, _Buffer.Length); var _StillToRead = (Int32)Response.ContentLength.Value - _Read; Response.HTTPBodyStream.Write(_Buffer, 0, _Read); var _CurrentBufferSize = 0; do { while (TCPStream.DataAvailable && _StillToRead > 0) { _CurrentBufferSize = Math.Min(_Buffer.Length, (Int32)_StillToRead); _Read = HTTPStream.Read(_Buffer, 0, _CurrentBufferSize); Response.HTTPBodyStream.Write(_Buffer, 0, _Read); _StillToRead -= _Read; } if (_StillToRead <= 0) { break; } Thread.Sleep(1); }while (sw.ElapsedMilliseconds < HTTPStream.ReadTimeout); Response.ContentStreamToArray(); } #endregion #region ...or read till timeout (e.g. for chunked transport)! else { try { _MemoryStream.Seek(HTTPHeaderBytes.Length + 4, SeekOrigin.Begin); Response.NewContentStream(); Response.HTTPBodyStream.Write(_Buffer, 0, _MemoryStream.Read(_Buffer, 0, _Buffer.Length)); var Retries = 0; while (Retries < 10) { while (TCPStream.DataAvailable) { Response.HTTPBodyStream.Write(_Buffer, 0, HTTPStream.Read(_Buffer, 0, _Buffer.Length)); Retries = 0; } Thread.Sleep(10); Retries++; } if (Response.TransferEncoding == "chunked") { //Debug.WriteLine(DateTime.Now + " Chunked encoding detected"); var TEContent = ((MemoryStream)Response.HTTPBodyStream).ToArray(); var TEString = TEContent.ToUTF8String(); var ReadBlockLength = true; var TEMemStram = new MemoryStream(); var LastPos = 0; var i = 0; do { if (i > 2 && ReadBlockLength && TEContent[i - 1] == '\n' && TEContent[i - 2] == '\r') { var len = TEContent.ReadTEBlockLength(LastPos, i - LastPos - 2); //Debug.WriteLine(DateTime.Now + " Chunked encoded block of length " + len + " bytes detected"); if (len == 0) { break; } if (i + len <= TEContent.Length) { TEMemStram.Write(TEContent, i, len); i = i + len; if (TEContent[i] == 0x0d) { i++; } if (i < TEContent.Length - 1) { if (TEContent[i] == 0x0a) { i++; } } else { } LastPos = i; ReadBlockLength = false; } else { // Reaching this point seems to be an endless loop! break; } } else { ReadBlockLength = true; i++; } } while (i < TEContent.Length); Response.ContentStreamToArray(TEMemStram); } else { Response.ContentStreamToArray(); } } catch (Exception e) { Debug.WriteLine(DateTime.Now + " " + e.Message); } } #endregion #region Close connection if requested! if (Response.Connection == null || Response.Connection == "close") { TCPClient.Close(); HTTPStream = null; TCPClient = null; } #endregion } catch (Exception e) { #region Create a HTTP response for the exception... while (e.InnerException != null) { e = e.InnerException; } Response = new HTTPResponseBuilder(Request, HTTPStatusCode.BadRequest) { ContentType = HTTPContentType.JSON_UTF8, Content = JSONObject.Create(new JProperty("Message", e.Message), new JProperty("StackTrace", e.StackTrace)). ToUTF8Bytes() }; #endregion } #region Call the optional HTTP response log delegate try { ResponseLogDelegate?.Invoke(DateTime.Now, this, Request, Response); } catch (Exception e2) { e2.Log(nameof(HTTPClient) + "." + nameof(ResponseLogDelegate)); } #endregion return(Response); }, TaskCreationOptions.AttachedToParent); return(await task); }
Query <T>(XElement QueryXML, String SOAPAction, Func <HTTPResponse <XElement>, HTTPResponse <T> > OnSuccess, Func <DateTime, Object, HTTPResponse <XElement>, HTTPResponse <T> > OnSOAPFault, Func <DateTime, Object, HTTPResponse, HTTPResponse <T> > OnHTTPError, Func <DateTime, Object, Exception, HTTPResponse <T> > OnException, Action <HTTPRequestBuilder> HTTPRequestBuilder = null, ClientRequestLogHandler RequestLogDelegate = null, ClientResponseLogHandler ResponseLogDelegate = null, CancellationToken?CancellationToken = null, EventTracking_Id EventTrackingId = null, TimeSpan?QueryTimeout = null) { #region Initial checks if (QueryXML == null) { throw new ArgumentNullException(nameof(QueryXML), "The 'Query'-string must not be null!"); } if (SOAPAction.IsNullOrEmpty()) { throw new ArgumentNullException(nameof(SOAPAction), "The 'SOAPAction'-string must not be null or empty!"); } if (OnSuccess == null) { throw new ArgumentNullException(nameof(OnSuccess), "The 'OnSuccess'-delegate must not be null!"); } if (OnSOAPFault == null) { throw new ArgumentNullException(nameof(OnSOAPFault), "The 'OnSOAPFault'-delegate must not be null!"); } if (OnHTTPError == null) { throw new ArgumentNullException(nameof(OnHTTPError), "The 'OnHTTPError'-delegate must not be null!"); } if (OnException == null) { throw new ArgumentNullException(nameof(OnException), "The 'OnException'-delegate must not be null!"); } #endregion var _RequestBuilder = this.POST(_URIPrefix); _RequestBuilder.Host = HTTPVirtualHost; _RequestBuilder.Content = QueryXML.ToUTF8Bytes(); _RequestBuilder.ContentType = HTTPContentType.XMLTEXT_UTF8; _RequestBuilder.Set("SOAPAction", @"""" + SOAPAction + @""""); _RequestBuilder.UserAgent = UserAgent; _RequestBuilder.FakeURIPrefix = "https://" + HTTPVirtualHost; HTTPRequestBuilder?.Invoke(_RequestBuilder); return(this.Execute(_RequestBuilder, RequestLogDelegate, ResponseLogDelegate, QueryTimeout ?? TimeSpan.FromSeconds(60), CancellationToken.HasValue ? CancellationToken.Value : new CancellationTokenSource().Token). ContinueWith(HttpResponseTask => { if (HttpResponseTask.Result == null || HttpResponseTask.Result.HTTPStatusCode != HTTPStatusCode.OK || HttpResponseTask.Result.HTTPBody == null || HttpResponseTask.Result.HTTPBody.Length == 0) { var OnHTTPErrorLocal = OnHTTPError; if (OnHTTPErrorLocal != null) { return OnHTTPErrorLocal(DateTime.Now, this, HttpResponseTask?.Result); } return new HTTPResponse <XElement>(HttpResponseTask?.Result, new XElement("HTTPError"), IsFault: true) as HTTPResponse <T>; } try { var SOAPXML = XDocument.Parse(HttpResponseTask.Result.HTTPBody.ToUTF8String()). Root. Element(NS.SOAPEnvelope_v1_1 + "Body"). Descendants(). FirstOrDefault(); // <S:Fault xmlns:ns4="http://www.w3.org/2003/05/soap-envelope" xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"> // <faultcode>S:Client</faultcode> // <faultstring>Validation error: The request message is invalid</faultstring> // <detail> // <Validation> // <Errors> // <Error column="65" errorXpath="/OICP:Envelope/OICP:Body/EVSEStatus:eRoamingPullEvseStatusById/EVSEStatus:EvseId" line="3">Value '+45*045*010*0A96296' is not facet-valid with respect to pattern '([A-Za-z]{2}\*?[A-Za-z0-9]{3}\*?E[A-Za-z0-9\*]{1,30})|(\+?[0-9]{1,3}\*[0-9]{3,6}\*[0-9\*]{1,32})' for type 'EvseIDType'.</Error> // <Error column="65" errorXpath="/OICP:Envelope/OICP:Body/EVSEStatus:eRoamingPullEvseStatusById/EVSEStatus:EvseId" line="3">The value '+45*045*010*0A96296' of element 'EVSEStatus:EvseId' is not valid.</Error> // </Errors> // <OriginalDocument> // ... // </OriginalDocument> // </Validation> // </detail> // </S:Fault> if (SOAPXML.Name.LocalName != "Fault") { var OnSuccessLocal = OnSuccess; if (OnSuccessLocal != null) { return OnSuccessLocal(new HTTPResponse <XElement>(HttpResponseTask.Result, SOAPXML)); } } var OnSOAPFaultLocal = OnSOAPFault; if (OnSOAPFaultLocal != null) { return OnSOAPFaultLocal(DateTime.Now, this, new HTTPResponse <XElement>(HttpResponseTask.Result, SOAPXML)); } return new HTTPResponse <XElement>(HttpResponseTask.Result, new XElement("SOAPFault"), IsFault: true) as HTTPResponse <T>; } catch (Exception e) { OnException?.Invoke(DateTime.Now, this, e); //var OnFaultLocal = OnSOAPFault; //if (OnFaultLocal != null) // return OnFaultLocal(new HTTPResponse<XElement>(HttpResponseTask.Result, e)); return new HTTPResponse <XElement>(HttpResponseTask.Result, new XElement("exception", e.Message), IsFault: true) as HTTPResponse <T>; } })); }