using System; using System.IO; using Best.HTTP.Request.Upload; using Best.HTTP.Shared; using Best.HTTP.Shared.Extensions; namespace Best.HTTP.Request.Settings { public delegate void OnHeadersSentDelegate(HTTPRequest req); /// /// Options for sending the request headers and content, including upload progress monitoring. /// /// might be called when redirected or retried! public class UploadSettings : IDisposable { /// /// Size of the internal buffer, and upload progress will be fired when this size of data sent to the wire. Its default value is 4 KiB. /// public int UploadChunkSize = 4 * 1024; /// /// The stream that the plugin will use to send data to the server. /// /// /// The stream can be any regular implementation or a specialized one inheriting from : /// /// A specialized for data generated on-the-fly or periodically. The request remains active until the method is invoked, ensuring continuous data feed even during temporary empty states. /// An implementation to convert and upload the object as JSON data. It sets the "Content-Type" header to "application/json; charset=utf-8". /// An implementation representing a stream that prepares and sends data as URL-encoded form data in an HTTP request. /// An based implementation of the multipart/form-data Content-Type. It's very memory-effective, streams are read into memory in chunks. /// /// public Stream UploadStream; /// /// Set to false if the plugin MUST NOT dispose after the request is finished. /// public bool DisposeStream = true; /// /// Called periodically when data sent to the server. /// public OnProgressDelegate OnUploadProgress; /// /// This event is fired after the headers are sent to the server. /// public event OnHeadersSentDelegate OnHeadersSent { add { _onHeadersSent += value; } remove { _onHeadersSent -= value; } } private OnHeadersSentDelegate _onHeadersSent; // // Whether to send an "Expect: 100-continue" header and value when there's content to send ( != null). // By using "Expect: 100-continue" the server is able to respond with an error (like 401-unauthorized, 405-method not allowed, etc.) or redirect before the client sends the whole payload. // // // More details can be found here: // // RFC-9110 - Expect header // EXPECT: TWEAKS IN CURL (by Daniel Stenberg) // // //public bool SendExpect100Continue = true; // False by default, set to true only when "Expect: 100-continue" sent out. //internal bool Expect100Continue = false; //internal void ResetExpects() => this.SendExpect100Continue = this.Expect100Continue = false; private bool isDisposed; /// /// Called every time the request is sent out (redirected or retried). /// /// The being prepared. /// true if the can be fired. public virtual void SetupRequest(HTTPRequest request, bool dispatchHeadersSentCallback) { if (isDisposed) throw new ObjectDisposedException(nameof(UploadSettings)); if (this.UploadStream is UploadStreamBase upStream) upStream.BeforeSendHeaders(request); // Decide on whether append an "expect: 100-continue" or not. // https://www.rfc-editor.org/rfc/rfc9110#name-expect /* if (this.SendExpect100Continue && this.UploadStream != null) { request.AddHeader("expect", "100-continue"); this.Expect100Continue = true; } else request.RemoveHeader("expect"); */ if (dispatchHeadersSentCallback) { // Call the callback on the unity main thread if (HTTPUpdateDelegator.Instance.IsMainThread()) call_onBeforeHeaderSend(request); else new RunOnceOnMainThread(() => call_onBeforeHeaderSend(request), request.Context) .Subscribe(); } } protected void call_onBeforeHeaderSend(HTTPRequest request) { try { _onHeadersSent?.Invoke(request); } catch (Exception ex) { HTTPManager.Logger.Exception(nameof(UploadSettings), nameof(OnHeadersSent), ex, request.Context); } } protected virtual void Dispose(bool disposing) { if (!isDisposed) { if (disposing) { if (this.DisposeStream) { var stream = this.UploadStream; if (stream != null) { this.UploadStream?.Dispose(); this.UploadStream = null; isDisposed = true; } } } } } /// /// Dispose of resources used by the UploadSettings instance. /// public void Dispose() { Dispose(disposing: true); GC.SuppressFinalize(this); } } }