public IHttp Create(string namedClientName) { if (namedClientName == null) { // This is the old behaviour. NetStd20HttpEngine will use new HttpClient and new HttpClientHandler when // creating NetStd20HttpRequests, see NetStd20HttpEngine.CreateWebRequest(string method, Uri url). return(new NetStd20HttpEngine()); } else { // The new behaviour is to create a new NetStd20HttpEngine instance but link it to a NamedHttpClientHandlerWrapper // (the behaviour implemented by the subclass NamedClientHttpEngine). This way we can easily ensure that // all HttpClientHandlers for the same named client are initialised just once (because they are re-used from // within a pool, while HttpClients are created new for each request, and HttpClientHandler properties cannot // change once used.) // // Note however, that we don't know which named HttpClient is mapped to which HttpClientHandler, all we know // is that the HttpClient is newly created, and it is linked with one of the HttpClientHandlers in // NamedHttpClientHandlerWrapper (because they have the same name). And we know the HttpClientHandlers in // NamedHttpClientHandlerWrapper hasn't been configured yet (because once configured, they are removed // from the wrapper). // // Such a set up can ensure all HttpClientHandlers with the same name are configured the same way, so then // no matter whichever HttpClientHandler the HttpClient is paired with, it will work in an expected way. // // However, there is an assumption that the user of MiniRestSharp does not attempt to use the same named client // with different set up (specifically, set ups that require different HttpClientHandler configurations). // This call is thread-safe because mNamedHandlerWrappers is a ConcurrentDictionary. NamedHttpClientHandlerWrapper wrapper = mNamedHandlerWrappers.GetOrAdd(namedClientName, name => new NamedHttpClientHandlerWrapper(name)); return(new NamedClientHttpEngine(namedClientName, wrapper)); } }
/// <summary> /// This is the method that is called to configure the <see cref="NetStd20HttpRequest.RequestClient"/> /// and <see cref="NetStd20HttpRequest.RequestHandler"/> returned by <see cref="CreateWebRequest(string, Uri)"/>. /// </summary> /// <param name="request">Value returned by <see cref="CreateWebRequest(string, Uri)"/>.</param> protected override NetStd20HttpRequest ConfigureWebRequest(NetStd20HttpRequest request) { // Must ensure this method is thread-safe because the parent method will update // NetStd20HttpRequest.RequestHandler properties, which is really just calling NamedHttpClientHandlerWrapper. // We don't want multiple threads to be updating NamedHttpClientHandlerWrapper properties at the same // time because the wrapped HttpClientHandler properties are probably not thread-safe, and we don't want // another thread to add to the wrapper while we are updating wrapper's current set of handlers. NamedHttpClientHandlerWrapper wrapper = request.RequestHandler; // The lock will prevent new handlers from being added in the HttpEngineFactory.AddNamedClientHandler() method above. lock (wrapper) { // By this point, it is an error if we do not have access to the HttpClientHandler associated with the named client. // Because we clear the wrapper at the end of this code block, wrapper may be empty if we had previously // configured the named HttpClientHandler already. The IsAlwaysEmpty property gets around this issue. if (wrapper.IsAlwaysEmpty) { throw new InvalidOperationException(string.Format( "HttpClientHandler for the named client '{0}' has not been registered with MiniRestSharp. Did you forget to call RestSharpServiceCollectionExtensions.AddRestSharpClient(IServiceCollection, string)?", wrapper.Name)); } // I've confirmed that the only place the wrapped HttpClientHandlers are configured is in this method. NetStd20HttpRequest returnValue = base.ConfigureWebRequest(request); // Now that the wrapped HttpClientHandlers are configured, we remove them from the wrapper // since we can't configure them again anyway. It's of course possible that these handlers // get recycled by IHttpClientFactory and become configurable again. When this happens I think the // RestSharpServiceCollectionExtensions system will end up calling HttpEngineFactory.AddNamedClientHandler() // again, which will result in it being added to wrapper again for another round of configuration later. wrapper.Clear(); return(returnValue); } }
private void AppendCookies(NamedHttpClientHandlerWrapper handler, Uri requestUri) { handler.CookieContainer = this.CookieContainer ?? new CookieContainer(); foreach (HttpCookie httpCookie in this.Cookies) { Func <Cookie> createCookie = () => new Cookie { Name = httpCookie.Name, Value = httpCookie.Value, Domain = requestUri.Host }; handler.CookieContainer_Add(new Uri(string.Format("{0}://{1}", requestUri.Scheme, requestUri.Host)), createCookie); } if ((this.CookieContainer == null || this.CookieContainer.Count == 0) && this.Cookies.Count == 0) { //handler.CookieContainer = null; // In netstandard2.0 this property cannot be set to null. handler.UseCookies = false; } else { handler.UseCookies = true; } }
/// <summary> /// Re-use a named <see cref="HttpClient"/> and its <see cref="HttpClientHandler"/>. It's normally not possible /// to access the <see cref="HttpClientHandler"/> from the <see cref="HttpClient"/>, we had to do some /// configuration magic to get access to the handler. /// </summary> public NetStd20HttpRequest(HttpClient httpClient, NamedHttpClientHandlerWrapper httpClientHandler, string method, Uri url) { this.RequestHandler = httpClientHandler; this.RequestClient = httpClient; this.RequestMessage = new HttpRequestMessage(new HttpMethod(method), url); // Must set a HttpContent here to allow HttpContentHeader to be set. this.RequestMessage.Content = new ByteArrayContent(new byte[0]); }
/// <summary> /// <para> /// Creates a new <see cref="HttpClientHandler"/> instance and add it to a new <see cref="NamedHttpClientHandlerWrapper"/> /// instance; also creates a new <see cref="HttpClient"/> using the <see cref="HttpClient(HttpMessageHandler)"/> /// constructor, passing in the <see cref="HttpClientHandler"/> instance as the constructor parameter. /// </para> /// <para> /// Finally calls <see cref="NetStd20HttpRequest(HttpClient, NamedHttpClientHandlerWrapper, string, Uri)"/> /// passing in the prepared objects as parameters. /// </para> /// <para> /// Override this method to provide your own implementation of creating a new NetStd20HttpRequest instance. /// </para> /// </summary> protected virtual NetStd20HttpRequest CreateWebRequest(string method, Uri url) { var clientHandler = new HttpClientHandler(); var client = new HttpClient(clientHandler); // Because we're not really using this wrapper with a named client, we set its name to null. var wrapper = new NamedHttpClientHandlerWrapper(null); wrapper.Add(clientHandler); return(new NetStd20HttpRequest(client, wrapper, method, url)); }
/// <summary> /// This method will wrap the HttpClientHandler given in a NamedHttpClientHandlerWrapper for the given name. /// A single NamedHttpClientHandlerWrapper can wrap many HttpClientHandlers, but all for the same name. /// This method is thread-safe. /// </summary> public void AddNamedClientHandler(string name, HttpClientHandler httpClientHandler) { if (name == null) { throw new ArgumentNullException(nameof(name)); } if (httpClientHandler == null) { throw new ArgumentNullException(nameof(httpClientHandler)); } // Since we cannot guarantee this method won't be called concurrently (e.g. multiple requests for the same named // client), we must make sure it is thread-safe. Luckily mNamedHandlerWrappers is a ConcurrentDictionary. NamedHttpClientHandlerWrapper wrapper = mNamedHandlerWrappers.GetOrAdd(name, nam => new NamedHttpClientHandlerWrapper(nam)); // Add the handler to the wrapper for the given name. Wrapper object is NOT thread-safe so we must // synchronize on it before adding. An implication is that this may block if the same wrapper object is being // used (as NetStd20HttpRequest.RequestHandler) in the NamedClientHttpEngine.ConfigureWebRequest() method below. lock (wrapper) { wrapper.Add(httpClientHandler); } }
internal NamedClientHttpEngine(string name, NamedHttpClientHandlerWrapper httpClientHandlerWrapper) { mName = name; mHttpClientHandlerWrapper = httpClientHandlerWrapper; }