private static void HandleAsyncException(RequestCompletionSource state, Exception ex) { if ((null == ex) && state.CancellationToken.IsCancellationRequested) { state.TrySetCanceled(state.CancellationToken); } if (null == ex) { return; } var oce = (ex as OperationCanceledException); if (oce != null) { // If the exception was due to the cancellation token being canceled, throw cancellation exception. Debug.Assert(state.CancellationToken.IsCancellationRequested); state.TrySetCanceled(oce.CancellationToken); } else if (ex is HttpRequestException) { state.TrySetException(ex); } else { state.TrySetException(new HttpRequestException(SR.net_http_client_execution_error, ex)); } }
private static void RemoveEasyHandle(SafeCurlMultiHandle multiHandle, GCHandle stateHandle, bool onMultiStack) { RequestCompletionSource state = (RequestCompletionSource)stateHandle.Target; SafeCurlHandle requestHandle = state.RequestHandle; if (onMultiStack) { lock (multiHandle) { Interop.libcurl.curl_multi_remove_handle(multiHandle, requestHandle); } state.SessionHandle = null; requestHandle.DangerousRelease(); } if (!state.RequestHeaderHandle.IsInvalid) { SafeCurlSlistHandle headerHandle = state.RequestHeaderHandle; SafeCurlSlistHandle.DisposeAndClearHandle(ref headerHandle); } SafeCurlHandle.DisposeAndClearHandle(ref requestHandle); stateHandle.Free(); }
private static void EndRequest(SafeCurlMultiHandle multiHandle, IntPtr statePtr, int result) { GCHandle stateHandle = GCHandle.FromIntPtr(statePtr); RequestCompletionSource state = (RequestCompletionSource)stateHandle.Target; try { // No more callbacks so no more data state.ResponseMessage.ContentStream.SignalComplete(); if (CURLcode.CURLE_OK == result) { state.TrySetResult(state.ResponseMessage); } else { state.TrySetException(new HttpRequestException(SR.net_http_client_execution_error, GetCurlException(result))); } } catch (Exception ex) { HandleAsyncException(state, ex); } finally { RemoveEasyHandle(multiHandle, stateHandle, true); } }
private void AddEasyHandle(RequestCompletionSource state) { bool gotReference = false; SafeCurlHandle requestHandle = state.RequestHandle; try { requestHandle.DangerousAddRef(ref gotReference); lock (_multiHandle) { int result = Interop.libcurl.curl_multi_add_handle(_multiHandle, requestHandle); if (result != CURLcode.CURLE_OK) { throw new HttpRequestException(SR.net_http_client_execution_error, GetCurlException(result, true)); } } state.SessionHandle = _multiHandle; // Note that we are deliberately not decreasing the ref counts of // the multi and easy handles since that will be done in RemoveEasyHandle // when the request is completed and the handles are used in an // unmanaged context till then // TODO: Investigate if SafeCurlHandle is really useful since we are not // avoiding any leaks due to the ref count increment } catch (Exception) { if (gotReference) { requestHandle.DangerousRelease(); } throw; } }
private static void RemoveEasyHandle(SafeCurlMultiHandle multiHandle, RequestCompletionSource state, bool onMultiStack) { SafeCurlHandle requestHandle = state.RequestHandle; if (onMultiStack) { Debug.Assert(!requestHandle.IsInvalid); lock (multiHandle) { Interop.libcurl.curl_multi_remove_handle(multiHandle, requestHandle); multiHandle.RequestCount = multiHandle.RequestCount - 1; multiHandle.SignalFdSetChange(state.SocketFd, true); } state.SessionHandle.DangerousRelease(); state.SessionHandle = null; requestHandle.DangerousRelease(); } if (state.RequestContentStream != null) { state.RequestContentStream.Dispose(); } if (!state.RequestHeaderHandle.IsInvalid) { SafeCurlSlistHandle headerHandle = state.RequestHeaderHandle; SafeCurlSlistHandle.DisposeAndClearHandle(ref headerHandle); } if (!requestHandle.IsInvalid) { SafeCurlHandle.DisposeAndClearHandle(ref requestHandle); } }
private SafeCurlHandle CreateRequestHandle(RequestCompletionSource state, GCHandle stateHandle) { // TODO: If this impacts perf, optimize using a handle pool SafeCurlHandle requestHandle = Interop.libcurl.curl_easy_init(); if (requestHandle.IsInvalid) { throw new HttpRequestException(SR.net_http_client_execution_error); } SetCurlOption(requestHandle, CURLoption.CURLOPT_URL, state.RequestMessage.RequestUri.AbsoluteUri); if (_automaticRedirection) { SetCurlOption(requestHandle, CURLoption.CURLOPT_FOLLOWLOCATION, 1L); // Set maximum automatic redirection option SetCurlOption(requestHandle, CURLoption.CURLOPT_MAXREDIRS, _maxAutomaticRedirections); } if (state.RequestMessage.Method == HttpMethod.Put) { SetCurlOption(requestHandle, CURLoption.CURLOPT_UPLOAD, 1L); } else if (state.RequestMessage.Method == HttpMethod.Head) { SetCurlOption(requestHandle, CURLoption.CURLOPT_NOBODY, 1L); } else if (state.RequestMessage.Method == HttpMethod.Post) { SetCurlOption(requestHandle, CURLoption.CURLOPT_POST, 1L); if (state.RequestMessage.Content == null) { SetCurlOption(requestHandle, CURLoption.CURLOPT_POSTFIELDSIZE, 0L); SetCurlOption(requestHandle, CURLoption.CURLOPT_COPYPOSTFIELDS, String.Empty); } } IntPtr statePtr = GCHandle.ToIntPtr(stateHandle); SetCurlOption(requestHandle, CURLoption.CURLOPT_PRIVATE, statePtr); SetCurlCallbacks(requestHandle, state.RequestMessage, statePtr); if (_supportsAutomaticDecompression) { SetRequestHandleDecompressionOptions(requestHandle); } SetProxyOptions(requestHandle, state.RequestMessage.RequestUri); SetRequestHandleCredentialsOptions(requestHandle, state); SetCookieOption(requestHandle, state.RequestMessage.RequestUri); state.RequestHeaderHandle = SetRequestHeaders(requestHandle, state.RequestMessage); return(requestHandle); }
private static void RemoveEasyHandle(SafeCurlMultiHandle multiHandle, GCHandle stateHandle, bool onMultiStack) { Debug.Assert(stateHandle.IsAllocated); RequestCompletionSource state = (RequestCompletionSource)stateHandle.Target; stateHandle.Free(); RemoveEasyHandle(multiHandle, state, onMultiStack); }
protected internal override Task <HttpResponseMessage> SendAsync( HttpRequestMessage request, CancellationToken cancellationToken) { if (request == null) { throw new ArgumentNullException("request", SR.net_http_handler_norequest); } if ((request.RequestUri.Scheme != UriSchemeHttp) && (request.RequestUri.Scheme != UriSchemeHttps)) { throw NotImplemented.ByDesignWithMessage(SR.net_http_client_http_baseaddress_required); } if (request.RequestUri.Scheme == UriSchemeHttps && !_supportsSSL) { throw new PlatformNotSupportedException(SR.net_http_unix_https_support_unavailable_libcurl); } if (request.Headers.TransferEncodingChunked.GetValueOrDefault() && (request.Content == null)) { throw new InvalidOperationException(SR.net_http_chunked_not_allowed_with_empty_content); } // TODO: Check that SendAsync is not being called again for same request object. // Probably fix is needed in WinHttpHandler as well CheckDisposed(); SetOperationStarted(); if (cancellationToken.IsCancellationRequested) { return(Task.FromCanceled <HttpResponseMessage>(cancellationToken)); } // Create RequestCompletionSource object and save current values of handler settings. RequestCompletionSource state = new RequestCompletionSource { CancellationToken = cancellationToken, RequestMessage = request, }; BeginRequest(state); return(state.Task); }
private void AddEasyHandle(RequestCompletionSource state) { bool gotReference = false; SafeCurlHandle requestHandle = state.RequestHandle; try { requestHandle.DangerousAddRef(ref gotReference); lock (_multiHandle) { int result = Interop.libcurl.curl_multi_add_handle(_multiHandle, requestHandle); if (result != CURLcode.CURLE_OK) { throw new HttpRequestException(SR.net_http_client_execution_error, GetCurlException(result, true)); } state.SessionHandle = _multiHandle; _multiHandle.RequestCount = _multiHandle.RequestCount + 1; if (_multiHandle.PollCancelled) { // TODO: Create single polling thread for all HttpClientHandler objects Task.Factory.StartNew(s => PollFunction(((CurlHandler)s)._multiHandle), this, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default); _multiHandle.PollCancelled = false; } bool ignored = false; _multiHandle.DangerousAddRef(ref ignored); } // Note that we are deliberately not decreasing the ref counts of // the multi and easy handles since that will be done in RemoveEasyHandle // when the request is completed and the handles are used in an // unmanaged context till then // TODO: Investigate if SafeCurlHandle is really useful since we are not // avoiding any leaks due to the ref count increment } catch (Exception) { if (gotReference) { requestHandle.DangerousRelease(); } throw; } }
private static void EndRequest(SafeCurlMultiHandle multiHandle, IntPtr statePtr, int result) { GCHandle stateHandle = GCHandle.FromIntPtr(statePtr); RequestCompletionSource state = (RequestCompletionSource)stateHandle.Target; try { // No more callbacks so no more data state.ResponseMessage.ContentStream.SignalComplete(); if (CURLcode.CURLE_OK == result) { state.TrySetResult(state.ResponseMessage); } else { state.TrySetException(new HttpRequestException(SR.net_http_client_execution_error, GetCurlException(result))); } if (state.ResponseMessage.StatusCode != HttpStatusCode.Unauthorized && state.Handler.PreAuthenticate) { ulong availedAuth; if (Interop.libcurl.curl_easy_getinfo(state.RequestHandle, CURLINFO.CURLINFO_HTTPAUTH_AVAIL, out availedAuth) == CURLcode.CURLE_OK) { state.Handler.AddCredentialToCache(state.RequestMessage.RequestUri, availedAuth, state.NetworkCredential); } // ignoring the exception in this case. // There is no point in killing the request for the sake of putting the credentials into the cache } } catch (Exception ex) { HandleAsyncException(state, ex); } finally { RemoveEasyHandle(multiHandle, stateHandle, true); } }
private SafeCurlHandle CreateRequestHandle(RequestCompletionSource state, GCHandle stateHandle) { // TODO: If this impacts perf, optimize using a handle pool SafeCurlHandle requestHandle = Interop.libcurl.curl_easy_init(); if (requestHandle.IsInvalid) { throw new HttpRequestException(SR.net_http_client_execution_error); } SetCurlOption(requestHandle, CURLoption.CURLOPT_URL, state.RequestMessage.RequestUri.AbsoluteUri); if (_automaticRedirection) { SetCurlOption(requestHandle, CURLoption.CURLOPT_FOLLOWLOCATION, 1L); } if (state.RequestMessage.Content != null) { SetCurlOption(requestHandle, CURLoption.CURLOPT_UPLOAD, 1L); } if (state.RequestMessage.Method == HttpMethod.Head) { SetCurlOption(requestHandle, CURLoption.CURLOPT_NOBODY, 1L); } IntPtr statePtr = GCHandle.ToIntPtr(stateHandle); SetCurlOption(requestHandle, CURLoption.CURLOPT_PRIVATE, statePtr); SetCurlCallbacks(requestHandle, state.RequestMessage, statePtr); SetRequestHandleDecompressionOptions(requestHandle); SetProxyOptions(requestHandle, state.RequestMessage.RequestUri); state.RequestHeaderHandle = SetRequestHeaders(requestHandle, state.RequestMessage); // TODO: Handle other options return(requestHandle); }
private async void BeginRequest(RequestCompletionSource state) { SafeCurlHandle requestHandle = new SafeCurlHandle(); GCHandle stateHandle = new GCHandle(); try { // Prepare context objects state.ResponseMessage = new CurlResponseMessage(state.RequestMessage); stateHandle = GCHandle.Alloc(state); requestHandle = CreateRequestHandle(state, stateHandle); state.RequestHandle = requestHandle; if (state.RequestMessage.Content != null) { Stream requestContentStream = await state.RequestMessage.Content.ReadAsStreamAsync().ConfigureAwait(false); state.RequestContentStream = requestContentStream; if (state.CancellationToken.IsCancellationRequested) { RemoveEasyHandle(_multiHandle, stateHandle, state); state.TrySetCanceled(state.CancellationToken); return; } state.RequestContentBuffer = new byte[s_requestBufferSize]; } AddEasyHandle(state); } catch (Exception ex) { RemoveEasyHandle(_multiHandle, stateHandle, state); HandleAsyncException(state, ex); } }
private static void RemoveEasyHandle(SafeCurlMultiHandle multiHandle, GCHandle stateHandle, RequestCompletionSource state) { if (stateHandle.IsAllocated) { stateHandle.Free(); } RemoveEasyHandle(multiHandle, state, false); }
private void SetRequestHandleCredentialsOptions(SafeCurlHandle requestHandle, RequestCompletionSource state) { NetworkCredential credentials = GetNetworkCredentials(state.Handler._serverCredentials, state.RequestMessage.RequestUri); if (credentials != null) { string userName = string.IsNullOrEmpty(credentials.Domain) ? credentials.UserName : string.Format("{0}\\{1}", credentials.Domain, credentials.UserName); SetCurlOption(requestHandle, CURLoption.CURLOPT_USERNAME, userName); SetCurlOption(requestHandle, CURLoption.CURLOPT_HTTPAUTH, CURLAUTH.AuthAny); if (credentials.Password != null) { SetCurlOption(requestHandle, CURLoption.CURLOPT_PASSWORD, credentials.Password); } state.NetworkCredential = credentials; } }
private async void BeginRequest(RequestCompletionSource state) { SafeCurlHandle requestHandle = new SafeCurlHandle(); GCHandle stateHandle = new GCHandle(); bool needCleanup = false; try { // Prepare context objects state.ResponseMessage = new CurlResponseMessage(state.RequestMessage); stateHandle = GCHandle.Alloc(state); requestHandle = CreateRequestHandle(state, stateHandle); state.RequestHandle = requestHandle; needCleanup = true; if (state.CancellationToken.IsCancellationRequested) { state.TrySetCanceled(state.CancellationToken); return; } if (state.RequestMessage.Content != null) { Stream requestContentStream = await state.RequestMessage.Content.ReadAsStreamAsync().ConfigureAwait(false); if (state.CancellationToken.IsCancellationRequested) { state.TrySetCanceled(state.CancellationToken); return; } state.RequestContentStream = requestContentStream; state.RequestContentBuffer = new byte[s_requestBufferSize]; } AddEasyHandle(state); needCleanup = false; } catch (Exception ex) { HandleAsyncException(state, ex); } finally { if (needCleanup) { RemoveEasyHandle(_multiHandle, stateHandle, false); } else if (state.Task.IsCompleted) { if (stateHandle.IsAllocated) { stateHandle.Free(); } if (!requestHandle.IsInvalid) { SafeCurlHandle.DisposeAndClearHandle(ref requestHandle); } } } }
private static bool TryParseStatusLine(HttpResponseMessage response, string responseHeader, RequestCompletionSource state) { if (!responseHeader.StartsWith(s_httpPrefix, StringComparison.OrdinalIgnoreCase)) { return(false); } // Clear the header if status line is recieved again. This signifies that there are multiple response headers (like in redirection). response.Headers.Clear(); response.Content.Headers.Clear(); int responseHeaderLength = responseHeader.Length; // Check if line begins with HTTP/1.1 or HTTP/1.0 int prefixLength = s_httpPrefix.Length; int versionIndex = prefixLength + 2; if ((versionIndex < responseHeaderLength) && (responseHeader[prefixLength] == '1') && (responseHeader[prefixLength + 1] == '.')) { if (responseHeader[versionIndex] == '1') { response.Version = HttpVersion.Version11; } else if (responseHeader[versionIndex] == '0') { response.Version = HttpVersion.Version10; } else { response.Version = new Version(0, 0); } } else { response.Version = new Version(0, 0); } // TODO: Parsing errors are treated as fatal. Find right behaviour int spaceIndex = responseHeader.IndexOf(SpaceChar); if (spaceIndex > -1) { int codeStartIndex = spaceIndex + 1; int statusCode = 0; // Parse first 3 characters after a space as status code if (TryParseStatusCode(responseHeader, codeStartIndex, out statusCode)) { response.StatusCode = (HttpStatusCode)statusCode; // For security reasons, we drop the server credential if it is a // NetworkCredential. But we allow credentials in a CredentialCache // since they are specifically tied to URI's. if ((response.StatusCode == HttpStatusCode.Redirect) && !(state.Handler.Credentials is CredentialCache)) { state.Handler.SetCurlOption(state.RequestHandle, CURLoption.CURLOPT_HTTPAUTH, CURLAUTH.None); state.Handler.SetCurlOption(state.RequestHandle, CURLoption.CURLOPT_USERNAME, IntPtr.Zero); state.Handler.SetCurlOption(state.RequestHandle, CURLoption.CURLOPT_PASSWORD, IntPtr.Zero); state.NetworkCredential = null; } int codeEndIndex = codeStartIndex + StatusCodeLength; int reasonPhraseIndex = codeEndIndex + 1; if (reasonPhraseIndex < responseHeaderLength && responseHeader[codeEndIndex] == SpaceChar) { int newLineCharIndex = responseHeader.IndexOfAny(s_newLineCharArray, reasonPhraseIndex); int reasonPhraseEnd = newLineCharIndex >= 0 ? newLineCharIndex : responseHeaderLength; response.ReasonPhrase = responseHeader.Substring(reasonPhraseIndex, reasonPhraseEnd - reasonPhraseIndex); } } } return(true); }
private static bool TryParseStatusLine(HttpResponseMessage response, string responseHeader, RequestCompletionSource state) { if (!responseHeader.StartsWith(s_httpPrefix, StringComparison.OrdinalIgnoreCase)) { return false; } // Clear the header if status line is recieved again. This signifies that there are multiple response headers (like in redirection). response.Headers.Clear(); response.Content.Headers.Clear(); int responseHeaderLength = responseHeader.Length; // Check if line begins with HTTP/1.1 or HTTP/1.0 int prefixLength = s_httpPrefix.Length; int versionIndex = prefixLength + 2; if ((versionIndex < responseHeaderLength) && (responseHeader[prefixLength] == '1') && (responseHeader[prefixLength + 1] == '.')) { if (responseHeader[versionIndex] == '1') { response.Version = HttpVersion.Version11; } else if (responseHeader[versionIndex] == '0') { response.Version = HttpVersion.Version10; } else { response.Version = new Version(0, 0); } } else { response.Version = new Version(0, 0); } // TODO: Parsing errors are treated as fatal. Find right behaviour int spaceIndex = responseHeader.IndexOf(SpaceChar); if (spaceIndex > -1) { int codeStartIndex = spaceIndex + 1; int statusCode = 0; // Parse first 3 characters after a space as status code if (TryParseStatusCode(responseHeader, codeStartIndex, out statusCode)) { response.StatusCode = (HttpStatusCode)statusCode; // For security reasons, we drop the server credential if it is a // NetworkCredential. But we allow credentials in a CredentialCache // since they are specifically tied to URI's. if ((response.StatusCode == HttpStatusCode.Redirect) && !(state.Handler.Credentials is CredentialCache)) { state.Handler.SetCurlOption(state.RequestHandle, CURLoption.CURLOPT_HTTPAUTH, CURLAUTH.None); state.Handler.SetCurlOption(state.RequestHandle, CURLoption.CURLOPT_USERNAME, IntPtr.Zero ); state.Handler.SetCurlOption(state.RequestHandle, CURLoption.CURLOPT_PASSWORD, IntPtr.Zero); state.NetworkCredential = null; } int codeEndIndex = codeStartIndex + StatusCodeLength; int reasonPhraseIndex = codeEndIndex + 1; if (reasonPhraseIndex < responseHeaderLength && responseHeader[codeEndIndex] == SpaceChar) { int newLineCharIndex = responseHeader.IndexOfAny(s_newLineCharArray, reasonPhraseIndex); int reasonPhraseEnd = newLineCharIndex >= 0 ? newLineCharIndex : responseHeaderLength; response.ReasonPhrase = responseHeader.Substring(reasonPhraseIndex, reasonPhraseEnd - reasonPhraseIndex); } } } return true; }