public async Task <HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { // TODO: ISSUE 31310: Cancellation support HttpConnectionResponseContent responseContent = new HttpConnectionResponseContent(); _response = new HttpResponseMessage() { Version = HttpVersion.Version20, RequestMessage = request, Content = responseContent }; // TODO: ISSUE 31312: Expect: 100-continue and early response handling // Note that in an "early response" scenario, where we get a response before we've finished sending the request body // (either with a 100-continue that timed out, or without 100-continue), // we can stop send a RST_STREAM on the request stream and stop sending the request without tearing down the entire connection. // TODO: ISSUE 31313: Avoid allocating a TaskCompletionSource repeatedly by using a resettable ValueTaskSource. // See: https://github.com/dotnet/corefx/blob/master/src/Common/tests/System/Threading/Tasks/Sources/ManualResetValueTaskSource.cs Debug.Assert(_responseDataAvailable == null); _responseDataAvailable = new TaskCompletionSource <bool>(); Task readDataAvailableTask = _responseDataAvailable.Task; // Send headers await _connection.SendHeadersAsync(_streamId, request).ConfigureAwait(false); // Send request body, if any if (request.Content != null) { using (Http2WriteStream writeStream = new Http2WriteStream(this)) { await request.Content.CopyToAsync(writeStream).ConfigureAwait(false); } } // Wait for response headers to be read. await readDataAvailableTask.ConfigureAwait(false); // Start to process the response body. bool emptyResponse = false; lock (_syncObject) { if (_responseComplete && _responseBuffer.ActiveSpan.Length == 0) { if (_responseAborted) { throw new IOException(SR.net_http_invalid_response); } emptyResponse = true; } } if (emptyResponse) { responseContent.SetStream(EmptyReadStream.Instance); } else { responseContent.SetStream(new Http2ReadStream(this)); } // Process Set-Cookie headers. if (_connection._pool.Settings._useCookies) { CookieHelper.ProcessReceivedCookies(_response, _connection._pool.Settings._cookieContainer); } return(_response); }
private static HttpResponseMessage ConvertResponse(RTHttpResponseMessage rtResponse) { HttpResponseMessage response = new HttpResponseMessage((HttpStatusCode)rtResponse.StatusCode); response.ReasonPhrase = rtResponse.ReasonPhrase; // Version if (rtResponse.Version == RTHttpVersion.Http11) { response.Version = HttpVersionInternal.Version11; } else if (rtResponse.Version == RTHttpVersion.Http10) { response.Version = HttpVersionInternal.Version10; } else if (rtResponse.Version == RTHttpVersion.Http20) { response.Version = HttpVersionInternal.Version20; } else { response.Version = new Version(0, 0); } bool success; // Headers foreach (KeyValuePair <string, string> headerPair in rtResponse.Headers) { if (headerPair.Key.Equals(HttpKnownHeaderNames.SetCookie, StringComparison.OrdinalIgnoreCase)) { // The Set-Cookie header always comes back with all of the cookies concatenated together. // For example if the response contains the following: // Set-Cookie A=1 // Set-Cookie B=2 // Then we will have a single header KeyValuePair of Key=Set-Cookie, Value=A=1, B=2. // However clients expect these headers to be separated(i.e. // httpResponseMessage.Headers.GetValues("Set-Cookie") should return two cookies not one // concatenated together). success = response.Headers.TryAddWithoutValidation(headerPair.Key, CookieHelper.GetCookiesFromHeader(headerPair.Value)); } else { success = response.Headers.TryAddWithoutValidation(headerPair.Key, headerPair.Value); } Debug.Assert(success); } // Content if (rtResponse.Content != null) { var rtResponseStream = rtResponse.Content.ReadAsInputStreamAsync().AsTask().Result; response.Content = new StreamContent(rtResponseStream.AsStreamForRead()); foreach (KeyValuePair <string, string> headerPair in rtResponse.Content.Headers) { success = response.Content.Headers.TryAddWithoutValidation(headerPair.Key, headerPair.Value); Debug.Assert(success); } } return(response); }