123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679 |
- using System;
- using System.Collections;
- using System.Collections.Generic;
- using Best.HTTP.Cookies;
- using Best.HTTP.Hosts.Connections;
- using Best.HTTP.HostSetting;
- using Best.HTTP.Request.Authenticators;
- using Best.HTTP.Request.Settings;
- using Best.HTTP.Request.Timings;
- using Best.HTTP.Request.Upload;
- using Best.HTTP.Response.Decompression;
- using Best.HTTP.Shared;
- using Best.HTTP.Shared.Extensions;
- using Best.HTTP.Shared.Logger;
- namespace Best.HTTP
- {
- /// <summary>
- /// Delegate for a callback function that is called after the request is fully processed.
- /// </summary>
- public delegate void OnRequestFinishedDelegate(HTTPRequest req, HTTPResponse resp);
- /// <summary>
- /// Delegate for enumerating headers during request preparation.
- /// </summary>
- /// <param name="header">The header name.</param>
- /// <param name="values">A list of header values.</param>
- public delegate void OnHeaderEnumerationDelegate(string header, List<string> values);
- /// <summary>
- /// Represents an HTTP request that allows you to send HTTP requests to remote servers and receive responses asynchronously.
- /// </summary>
- /// <remarks>
- /// <list type="bullet">
- /// <item><term>Asynchronous HTTP requests</term><description>Utilize a Task-based API for performing HTTP requests asynchronously.</description></item>
- /// <item><term>Unity coroutine support</term><description>Seamlessly integrate with Unity's coroutine system for coroutine-based request handling.</description></item>
- /// <item><term>HTTP method support</term><description>Support for various HTTP methods including GET, POST, PUT, DELETE, and more.</description></item>
- /// <item><term>Compression and decompression</term><description>Automatic request and response compression and decompression for efficient data transfer.</description></item>
- /// <item><term>Timing information</term><description>Collect detailed timing information about the request for performance analysis.</description></item>
- /// <item><term>Upload and download support</term><description>Support for uploading and downloading files with progress tracking.</description></item>
- /// <item><term>Customizable</term><description>Extensive options for customizing request headers, handling cookies, and more.</description></item>
- /// <item><term>Redirection handling</term><description>Automatic handling of request redirections for a seamless experience.</description></item>
- /// <item><term>Proxy server support</term><description>Ability to route requests through proxy servers for enhanced privacy and security.</description></item>
- /// <item><term>Authentication</term><description>Automatic authentication handling using authenticators for secure communication.</description></item>
- /// <item><term>Cancellation support</term><description>Ability to cancel requests to prevent further processing and release resources.</description></item>
- /// </list>
- /// </remarks>
- public sealed class HTTPRequest : IEnumerator
- {
- /// <summary>
- /// Creates an <see cref="HTTPMethods.Get">HTTP GET</see> request with the specified URL.
- /// </summary>
- /// <param name="url">The URL of the request.</param>
- /// <returns>An HTTPRequest instance for the GET request.</returns>
- public static HTTPRequest CreateGet(string url) => new HTTPRequest(url);
- /// <summary>
- /// Creates an <see cref="HTTPMethods.Get">HTTP GET</see> request with the specified URI.
- /// </summary>
- /// <param name="uri">The URI of the request.</param>
- /// <returns>An HTTPRequest instance for the GET request.</returns>
- public static HTTPRequest CreateGet(Uri uri) => new HTTPRequest(uri);
- /// <summary>
- /// Creates an <see cref="HTTPMethods.Get">HTTP GET</see> request with the specified URL and registers a callback function to be called
- /// when the request is fully processed.
- /// </summary>
- /// <param name="url">The URL of the request.</param>
- /// <param name="callback">A callback function to be called when the request is finished.</param>
- /// <returns>An HTTPRequest instance for the GET request.</returns>
- public static HTTPRequest CreateGet(string url, OnRequestFinishedDelegate callback) => new HTTPRequest(url, callback);
- /// <summary>
- /// Creates an <see cref="HTTPMethods.Get">HTTP GET</see> request with the specified URI and registers a callback function to be called
- /// when the request is fully processed.
- /// </summary>
- /// <param name="uri">The URI of the request.</param>
- /// <param name="callback">A callback function to be called when the request is finished.</param>
- /// <returns>An HTTPRequest instance for the GET request.</returns>
- public static HTTPRequest CreateGet(Uri uri, OnRequestFinishedDelegate callback) => new HTTPRequest(uri, callback);
- /// <summary>
- /// Creates an <see cref="HTTPMethods.Post">HTTP POST</see> request with the specified URL.
- /// </summary>
- /// <param name="url">The URL of the request.</param>
- /// <returns>An HTTPRequest instance for the POST request.</returns>
- public static HTTPRequest CreatePost(string url) => new HTTPRequest(url, HTTPMethods.Post);
- /// <summary>
- /// Creates an <see cref="HTTPMethods.Post">HTTP POST</see> request with the specified URI.
- /// </summary>
- /// <param name="uri">The URI of the request.</param>
- /// <returns>An HTTPRequest instance for the POST request.</returns>
- public static HTTPRequest CreatePost(Uri uri) => new HTTPRequest(uri, HTTPMethods.Post);
- /// <summary>
- /// Creates an <see cref="HTTPMethods.Post">HTTP POST</see> request with the specified URL and registers a callback function to be called
- /// when the request is fully processed.
- /// </summary>
- /// <param name="url">The URL of the request.</param>
- /// <param name="callback">A callback function to be called when the request is finished.</param>
- /// <returns>An HTTPRequest instance for the POST request.</returns>
- public static HTTPRequest CreatePost(string url, OnRequestFinishedDelegate callback) => new HTTPRequest(url, HTTPMethods.Post, callback);
- /// <summary>
- /// Creates an <see cref="HTTPMethods.Post">HTTP POST</see> request with the specified URI and registers a callback function to be called
- /// when the request is fully processed.
- /// </summary>
- /// <param name="uri">The URI of the request.</param>
- /// <param name="callback">A callback function to be called when the request is finished.</param>
- /// <returns>An HTTPRequest instance for the POST request.</returns>
- public static HTTPRequest CreatePost(Uri uri, OnRequestFinishedDelegate callback) => new HTTPRequest(uri, HTTPMethods.Post, callback);
- /// <summary>
- /// Creates an <see cref="HTTPMethods.Put">HTTP PUT</see> request with the specified URL.
- /// </summary>
- /// <param name="url">The URL of the request.</param>
- /// <returns>An HTTPRequest instance for the PUT request.</returns>
- public static HTTPRequest CreatePut(string url) => new HTTPRequest(url, HTTPMethods.Put);
- /// <summary>
- /// Creates an <see cref="HTTPMethods.Put">HTTP PUT</see> request with the specified URI.
- /// </summary>
- /// <param name="uri">The URI of the request.</param>
- /// <returns>An HTTPRequest instance for the PUT request.</returns>
- public static HTTPRequest CreatePut(Uri uri) => new HTTPRequest(uri, HTTPMethods.Put);
- /// <summary>
- /// Creates an <see cref="HTTPMethods.Put">HTTP PUT</see> request with the specified URL and registers a callback function to be called
- /// when the request is fully processed.
- /// </summary>
- /// <param name="url">The URL of the request.</param>
- /// <param name="callback">A callback function to be called when the request is finished.</param>
- /// <returns>An HTTPRequest instance for the PUT request.</returns>
- public static HTTPRequest CreatePut(string url, OnRequestFinishedDelegate callback) => new HTTPRequest(url, HTTPMethods.Put, callback);
- /// <summary>
- /// Creates an <see cref="HTTPMethods.Put">HTTP PUT</see> request with the specified URI and registers a callback function to be called
- /// when the request is fully processed.
- /// </summary>
- /// <param name="uri">The URI of the request.</param>
- /// <param name="callback">A callback function to be called when the request is finished.</param>
- /// <returns>An HTTPRequest instance for the PUT request.</returns>
- public static HTTPRequest CreatePut(Uri uri, OnRequestFinishedDelegate callback) => new HTTPRequest(uri, HTTPMethods.Put, callback);
- /// <summary>
- /// Cached uppercase values to save some cpu cycles and GC alloc per request.
- /// </summary>
- public static readonly string[] MethodNames = {
- HTTPMethods.Get.ToString().ToUpper(),
- HTTPMethods.Head.ToString().ToUpper(),
- HTTPMethods.Post.ToString().ToUpper(),
- HTTPMethods.Put.ToString().ToUpper(),
- HTTPMethods.Delete.ToString().ToUpper(),
- HTTPMethods.Patch.ToString().ToUpper(),
- HTTPMethods.Merge.ToString().ToUpper(),
- HTTPMethods.Options.ToString().ToUpper(),
- HTTPMethods.Connect.ToString().ToUpper(),
- HTTPMethods.Query.ToString().ToUpper()
- };
- /// <summary>
- /// The method that how we want to process our request the server.
- /// </summary>
- public HTTPMethods MethodType { get; set; }
- /// <summary>
- /// The original request's Uri.
- /// </summary>
- public Uri Uri { get; set; }
- /// <summary>
- /// If redirected it contains the RedirectUri.
- /// </summary>
- public Uri CurrentUri { get { return this.RedirectSettings.IsRedirected ? this.RedirectSettings.RedirectUri : Uri; } }
- /// <summary>
- /// A host-key that can be used to find the right host-variant for the request.
- /// </summary>
- public HostKey CurrentHostKey { get => HostKey.From(this); }
- /// <summary>
- /// The response received from the server.
- /// </summary>
- /// <remarks>If an exception occurred during reading of the response stream or can't connect to the server, this will be null!</remarks>
- public HTTPResponse Response { get; set; }
- /// <summary>
- /// Download related options and settings.
- /// </summary>
- public DownloadSettings DownloadSettings = new DownloadSettings();
- /// <summary>
- /// Upload related options and settings.
- /// </summary>
- public UploadSettings UploadSettings = new UploadSettings();
- /// <summary>
- /// Timeout settings for the request.
- /// </summary>
- public TimeoutSettings TimeoutSettings;
- /// <summary>
- /// Retry settings for the request.
- /// </summary>
- public RetrySettings RetrySettings;
- /// <summary>
- /// Proxy settings for the request.
- /// </summary>
- public ProxySettings ProxySettings;
- /// <summary>
- /// Redirect settings for the request.
- /// </summary>
- public RedirectSettings RedirectSettings { get; private set; } = new RedirectSettings(10);
- /// <summary>
- /// The callback function that will be called after the request is fully processed.
- /// </summary>
- public OnRequestFinishedDelegate Callback { get; set; }
- /// <summary>
- /// Indicates if <see cref="Abort"/> is called on this request.
- /// </summary>
- public bool IsCancellationRequested { get => this.CancellationTokenSource != null ? this.CancellationTokenSource.IsCancellationRequested : true; }
- /// <summary>
- /// Gets the cancellation token source for this request.
- /// </summary>
- internal System.Threading.CancellationTokenSource CancellationTokenSource { get; private set; }
- /// <summary>
- /// Action called when <see cref="Abort"/> function is invoked.
- /// </summary>
- public Action<HTTPRequest> OnCancellationRequested;
- /// <summary>
- /// Stores any exception that occurs during processing of the request or response.
- /// </summary>
- /// <remarks>This property if for debugging purposes as <see href="https://github.com/Benedicht/BestHTTP-Issues/issues/174">seen here</see>!</remarks>
- public Exception Exception { get; internal set; }
- /// <summary>
- /// Any user-object that can be passed with the request.
- /// </summary>
- public object Tag { get; set; }
- /// <summary>
- /// Current state of this request.
- /// </summary>
- public HTTPRequestStates State {
- get { return this._state; }
- internal set {
- if (!HTTPUpdateDelegator.Instance.IsMainThread() && HTTPUpdateDelegator.Instance.CurrentThreadingMode == ThreadingMode.UnityUpdate)
- HTTPManager.Logger.Error(nameof(HTTPRequest), $"State.Set({this._state} => {value}) isn't called on the main thread({HTTPUpdateDelegator.Instance.MainThreadId})!", this.Context);
- // In a case where the request is aborted its state is set to a >= Finished state then,
- // on another thread the reqest processing will fail too queuing up a >= Finished state again.
- if (this._state >= HTTPRequestStates.Finished && value >= HTTPRequestStates.Finished)
- {
- HTTPManager.Logger.Warning(nameof(HTTPRequest), $"State.Set({this._state} => {value})", this.Context);
- return;
- }
- if (HTTPManager.Logger.IsDiagnostic)
- HTTPManager.Logger.Verbose(nameof(HTTPRequest), $"State.Set({this._state} => {value})", this.Context);
- this._state = value;
- }
- }
- private volatile HTTPRequestStates _state;
- /// <summary>
- /// Timing information about the request.
- /// </summary>
- public TimingCollector Timing { get; private set; }
- /// <summary>
- /// An IAuthenticator implementation that can be used to authenticate the request.
- /// </summary>
- /// <remarks>Out-of-the-box included authenticators are <see cref="CredentialAuthenticator"/> and <see cref="BearerTokenAuthenticator"/>.</remarks>
- public IAuthenticator Authenticator;
- #if UNITY_WEBGL
- /// <summary>
- /// Its value will be set to the XmlHTTPRequest's withCredentials field, required to send 3rd party cookies with the request.
- /// </summary>
- /// <remarks>
- /// More details can be found here:
- /// <list type="bullet">
- /// <item><description><see href="https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/withCredentials">Mozilla Developer Networks - XMLHttpRequest.withCredentials</see></description></item>
- /// </list>
- /// </remarks>
- public bool WithCredentials { get; set; }
- #endif
- /// <summary>
- /// Logging context of the request.
- /// </summary>
- public LoggingContext Context { get; private set; }
- private Dictionary<string, List<string>> Headers { get; set; }
- /// <summary>
- /// Creates an HTTP GET request with the specified URL.
- /// </summary>
- /// <param name="url">The URL of the request.</param>
- public HTTPRequest(string url)
- :this(new Uri(url)) {}
- /// <summary>
- /// Creates an HTTP GET request with the specified URL and registers a callback function to be called
- /// when the request is fully processed.
- /// </summary>
- /// <param name="url">The URL of the request.</param>
- /// <param name="callback">A callback function to be called when the request is finished.</param>
- public HTTPRequest(string url, OnRequestFinishedDelegate callback)
- : this(new Uri(url), callback) { }
- /// <summary>
- /// Creates an HTTP GET request with the specified URL and HTTP method type.
- /// </summary>
- /// <param name="url">The URL of the request.</param>
- /// <param name="methodType">The HTTP method type for the request (e.g., GET, POST, PUT).</param>
- public HTTPRequest(string url, HTTPMethods methodType)
- : this(new Uri(url), methodType) { }
- /// <summary>
- /// Creates an HTTP request with the specified URL, HTTP method type, and registers a callback function to be called
- /// when the request is fully processed.
- /// </summary>
- /// <param name="url">The URL of the request.</param>
- /// <param name="methodType">The HTTP method type for the request (e.g., GET, POST, PUT).</param>
- /// <param name="callback">A callback function to be called when the request is finished.</param>
- public HTTPRequest(string url, HTTPMethods methodType, OnRequestFinishedDelegate callback)
- : this(new Uri(url), methodType, callback) { }
- /// <summary>
- /// Creates an HTTP GET request with the specified URI.
- /// </summary>
- /// <param name="uri">The URI of the request.</param>
- public HTTPRequest(Uri uri)
- : this(uri, HTTPMethods.Get, null)
- {
- }
- /// <summary>
- /// Creates an HTTP GET request with the specified URI and registers a callback function to be called
- /// when the request is fully processed.
- /// </summary>
- /// <param name="uri">The URI of the request.</param>
- /// <param name="callback">A callback function to be called when the request is finished.</param>
- public HTTPRequest(Uri uri, OnRequestFinishedDelegate callback)
- : this(uri, HTTPMethods.Get, callback)
- {
- }
- /// <summary>
- /// Creates an HTTP request with the specified URI and HTTP method type.
- /// </summary>
- /// <param name="uri">The URI of the request.</param>
- /// <param name="methodType">The HTTP method type for the request (e.g., GET, POST, PUT).</param>
- public HTTPRequest(Uri uri, HTTPMethods methodType)
- : this(uri, methodType, null)
- {
- }
- /// <summary>
- /// Creates an HTTP request with the specified URI, HTTP method type, and registers a callback function
- /// to be called when the request is fully processed.
- /// </summary>
- /// <param name="uri">The URI of the request.</param>
- /// <param name="methodType">The HTTP method type for the request (e.g., GET, POST, PUT).</param>
- /// <param name="callback">A callback function to be called when the request is finished.</param>
- public HTTPRequest(Uri uri, HTTPMethods methodType, OnRequestFinishedDelegate callback)
- {
- this.Uri = uri;
- this.MethodType = methodType;
- this.TimeoutSettings = new TimeoutSettings(this);
- this.ProxySettings = new ProxySettings() { Proxy = HTTPManager.Proxy };
- this.RetrySettings = new RetrySettings(methodType == HTTPMethods.Get ? 1 : 0);
- this.Callback = callback;
- #if UNITY_WEBGL && !UNITY_EDITOR
- // Just because cookies are enabled, it doesn't justify creating XHR with WithCredentials == 1.
- //this.WithCredentials = this.CookieSettings.IsCookiesEnabled;
- #endif
- this.Context = new LoggingContext(this);
- this.Timing = new TimingCollector(this);
- this.CancellationTokenSource = new System.Threading.CancellationTokenSource();
- }
- /// <summary>
- /// Adds a header-value pair to the Headers. Use it to add custom headers to the request.
- /// </summary>
- /// <example>AddHeader("User-Agent', "FooBar 1.0")</example>
- public void AddHeader(string name, string value) => this.Headers = Headers.AddHeader(name, value);
- /// <summary>
- /// For the given header name, removes any previously added values and sets the given one.
- /// </summary>
- public void SetHeader(string name, string value) => this.Headers = this.Headers.SetHeader(name, value);
- /// <summary>
- /// Removes the specified header and all of its associated values. Returns <c>true</c>, if the header found and succesfully removed.
- /// </summary>
- public bool RemoveHeader(string name) => Headers.RemoveHeader(name);
- /// <summary>
- /// Returns <c>true</c> if the given head name is already in the <see cref="Headers"/>.
- /// </summary>
- public bool HasHeader(string name) => Headers.HasHeader(name);
- /// <summary>
- /// Returns the first header or <c>null</c> for the given header name.
- /// </summary>
- public string GetFirstHeaderValue(string name) => Headers.GetFirstHeaderValue(name);
- /// <summary>
- /// Returns all header values for the given header or <c>null</c>.
- /// </summary>
- public List<string> GetHeaderValues(string name) => Headers.GetHeaderValues(name);
- /// <summary>
- /// Removes all headers.
- /// </summary>
- public void RemoveHeaders() => Headers.RemoveHeaders();
- /// <summary>
- /// Sets the Range header to download the content from the given byte position. See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35
- /// </summary>
- /// <param name="firstBytePos">Start position of the download.</param>
- public void SetRangeHeader(long firstBytePos)
- {
- SetHeader("Range", string.Format("bytes={0}-", firstBytePos));
- }
- /// <summary>
- /// Sets the Range header to download the content from the given byte position to the given last position. See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35
- /// </summary>
- /// <param name="firstBytePos">Start position of the download.</param>
- /// <param name="lastBytePos">The end position of the download.</param>
- public void SetRangeHeader(long firstBytePos, long lastBytePos)
- {
- SetHeader("Range", string.Format("bytes={0}-{1}", firstBytePos, lastBytePos));
- }
- internal void RemoveUnsafeHeaders()
- {
- // https://www.rfc-editor.org/rfc/rfc9110.html#name-redirection-3xx
- /* 2. Remove header fields that were automatically generated by the implementation, replacing them with updated values as appropriate to the new request. This includes:
- 1. Connection-specific header fields (see Section 7.6.1),
- 2. Header fields specific to the client's proxy configuration, including (but not limited to) Proxy-Authorization,
- 3. Origin-specific header fields (if any), including (but not limited to) Host,
- 4. Validating header fields that were added by the implementation's cache (e.g., If-None-Match, If-Modified-Since), and
- 5. Resource-specific header fields, including (but not limited to) Referer, Origin, Authorization, and Cookie.
- 3. Consider removing header fields that were not automatically generated by the implementation
- (i.e., those present in the request because they were added by the calling context) where there are security implications;
- this includes but is not limited to Authorization and Cookie.
- * */
- // 2.1
- RemoveHeader("Connection");
- RemoveHeader("Proxy-Connection");
- RemoveHeader("Keep-Alive");
- RemoveHeader("TE");
- RemoveHeader("Transfer-Encoding");
- RemoveHeader("Upgrade");
- // 2.2
- RemoveHeader("Proxy-Authorization");
- // 2.3
- RemoveHeader("Host");
- // 2.4
- RemoveHeader("If-None-Match");
- RemoveHeader("If-Modified-Since");
- // 2.5 & 3
- RemoveHeader("Referer");
- RemoveHeader("Origin");
- RemoveHeader("Authorization");
- RemoveHeader("Cookie");
- RemoveHeader("Accept-Encoding");
-
- RemoveHeader("Content-Length");
- }
- internal void Prepare()
- {
- // Upload settings
- this.UploadSettings?.SetupRequest(this, true);
- }
- public void EnumerateHeaders(OnHeaderEnumerationDelegate callback, bool callBeforeSendCallback)
- {
- #if !UNITY_WEBGL || UNITY_EDITOR
- if (!HasHeader("Host"))
- {
- if (CurrentUri.Port == 80 || CurrentUri.Port == 443)
- SetHeader("Host", CurrentUri.Host);
- else
- SetHeader("Host", CurrentUri.Authority);
- }
- DecompressorFactory.SetupHeaders(this);
- if (!DownloadSettings.DisableCache)
- HTTPManager.LocalCache?.SetupValidationHeaders(this);
- var hostSettings = HTTPManager.PerHostSettings.Get(this.CurrentUri.Host);
- // Websocket would be very, very sad if its "connection: upgrade" header would be overwritten!
- if (!HasHeader("Connection"))
- AddHeader("Connection", hostSettings.HTTP1ConnectionSettings.TryToReuseConnections ? "Keep-Alive, TE" : "Close, TE");
- if (hostSettings.HTTP1ConnectionSettings.TryToReuseConnections /*&& !HasHeader("Keep-Alive")*/)
- {
- // Send the server a slightly larger value to make sure it's not going to close sooner than the client
- int seconds = (int)Math.Ceiling(hostSettings.HTTP1ConnectionSettings.MaxConnectionIdleTime.TotalSeconds + 1);
- AddHeader("Keep-Alive", "timeout=" + seconds);
- }
- if (!HasHeader("TE"))
- AddHeader("TE", "identity");
- if (!string.IsNullOrEmpty(HTTPManager.UserAgent) && !HasHeader("User-Agent"))
- AddHeader("User-Agent", HTTPManager.UserAgent);
- #endif
- long contentLength = -1;
- if (this.UploadSettings.UploadStream == null)
- {
- contentLength = 0;
- }
- else
- {
- contentLength = this.UploadSettings.UploadStream.Length;
- if (contentLength == BodyLengths.UnknownWithChunkedTransferEncoding)
- SetHeader("Transfer-Encoding", "chunked");
- if (!HasHeader("Content-Type"))
- SetHeader("Content-Type", "application/octet-stream");
- }
- // Always set the Content-Length header if possible
- // http://tools.ietf.org/html/rfc2616#section-4.4 : For compatibility with HTTP/1.0 applications, HTTP/1.1 requests containing a message-body MUST include a valid Content-Length header field unless the server is known to be HTTP/1.1 compliant.
- // 2018.06.03: Changed the condition so that content-length header will be included for zero length too.
- // 2022.05.25: Don't send a Content-Length (: 0) header if there's an Upgrade header. Upgrade is set for websocket, and it might be not true that the client doesn't send any bytes.
- if (contentLength >= BodyLengths.NoBody && !HasHeader("Content-Length") && !HasHeader("Upgrade"))
- SetHeader("Content-Length", contentLength.ToString());
- // Server authentication
- this.Authenticator?.SetupRequest(this);
- // Cookies
- //this.CookieSettings?.SetupRequest(this);
- CookieJar.SetupRequest(this);
- // Write out the headers to the stream
- if (callback != null && this.Headers != null)
- foreach (var kvp in this.Headers)
- callback(kvp.Key, kvp.Value);
- }
- /// <summary>
- /// Starts processing the request.
- /// </summary>
- public HTTPRequest Send()
- {
- // TODO: Are we really want to 'reset' the token source? Two problems i see:
- // 1.) User code will not know about this change
- // 2.) We might dispose the source while the DNS and TCP queries are running and checking the source request's Token.
- //if (this.IsRedirected)
- //{
- // this.CancellationTokenSource?.Dispose();
- // this.CancellationTokenSource = new System.Threading.CancellationTokenSource();
- //}
- this.Exception = null;
- return HTTPManager.SendRequest(this);
- }
- /// <summary>
- /// Cancels any further processing of the HTTP request.
- /// </summary>
- public void Abort()
- {
- HTTPManager.Logger.Verbose("HTTPRequest", $"Abort({this.State})", this.Context);
- if (this.State >= HTTPRequestStates.Finished || this.CancellationTokenSource == null)
- return;
- //this.IsCancellationRequested = true;
- this.CancellationTokenSource.Cancel();
- // There's a race-condition here too, another thread might set it too.
- // In this case, both state going to be queued up that we have to handle in RequestEvents.cs.
- if (this.TimeoutSettings.IsTimedOut(HTTPManager.CurrentFrameDateTime))
- {
- RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this, this.TimeoutSettings.IsConnectTimedOut(HTTPManager.CurrentFrameDateTime) ? HTTPRequestStates.ConnectionTimedOut : HTTPRequestStates.TimedOut, null));
- }
- else
- RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this, HTTPRequestStates.Aborted, null));
- if (this.OnCancellationRequested != null)
- {
- try
- {
- this.OnCancellationRequested(this);
- }
- catch { }
- }
- }
- /// <summary>
- /// Resets the request for a state where switching MethodType is possible.
- /// </summary>
- public void Clear()
- {
- RemoveHeaders();
- this.RedirectSettings.Reset();
- this.Exception = null;
- this.CancellationTokenSource?.Dispose();
- this.CancellationTokenSource = new System.Threading.CancellationTokenSource();
- this.UploadSettings?.Dispose();
- }
- #region System.Collections.IEnumerator implementation
- /// <summary>
- /// <see cref="IEnumerator.Current"/> implementation, required for <see cref="UnityEngine.Coroutine"/> support.
- /// </summary>
- public object Current { get { return null; } }
- /// <summary>
- /// <see cref="IEnumerator.MoveNext"/> implementation, required for <see cref="UnityEngine.Coroutine"/> support.
- /// </summary>
- /// <returns><c>true</c> if the request isn't finished yet.</returns>
- public bool MoveNext() => this.State < HTTPRequestStates.Finished;
- /// <summary>
- /// <see cref="IEnumerator.MoveNext"/> implementation throwing <see cref="NotImplementedException"/>, required for <see cref="UnityEngine.Coroutine"/> support.
- /// </summary>
- /// <exception cref="NotImplementedException"></exception>
- public void Reset() => throw new NotImplementedException();
- #endregion
- /// <summary>
- /// Disposes of resources used by the HTTPRequest instance.
- /// </summary>
- internal void Dispose()
- {
- this.UploadSettings?.Dispose();
- this.Response?.Dispose();
- this.CancellationTokenSource?.Dispose();
- this.CancellationTokenSource = null;
- }
- public override string ToString()
- {
- return $"[HTTPRequest {this.State}, {this.Context.Hash}, {this.CurrentUri}, {this.CurrentHostKey}]";
- }
- }
- }
|