text: // Copyright (c) 2016 GitHub, Inc.
  // Use of this source code is governed by the MIT license that can be
  // found in the LICENSE file.
  
  #ifndef ATOM_BROWSER_API_ATOM_API_URL_REQUEST_H_
  #define ATOM_BROWSER_API_ATOM_API_URL_REQUEST_H_
  
  #include <array>
  #include <string>
  
  #include "atom/browser/api/event_emitter.h"
  #include "atom/browser/api/trackable_object.h"
  #include "base/memory/weak_ptr.h"
  #include "native_mate/dictionary.h"
  #include "native_mate/handle.h"
  #include "native_mate/wrappable_base.h"
  #include "net/base/auth.h"
  #include "net/base/io_buffer.h"
  #include "net/http/http_response_headers.h"
  #include "net/url_request/url_request_context.h"
  
  namespace atom {
  
  class AtomURLRequest;
  
  namespace api {
  
  //
  // The URLRequest class implements the V8 binding between the JavaScript API
  // and Chromium native net library. It is responsible for handling HTTP/HTTPS
  // requests.
  //
  // The current class provides only the binding layer. Two other JavaScript
  // classes (ClientRequest and IncomingMessage) in the net module provide the
  // final API, including some state management and arguments validation.
  //
  // URLRequest's methods fall into two main categories: command and event
  // methods. They are always executed on the Browser's UI thread.
  // Command methods are called directly from JavaScript code via the API defined
  // in BuildPrototype. A command method is generally implemented by forwarding
  // the call to a corresponding method on AtomURLRequest which does the
  // synchronization on the Browser IO thread. The latter then calls into Chromium
  // net library. On the other hand, net library events originate on the IO
  // thread in AtomURLRequest and are synchronized back on the UI thread, then
  // forwarded to a corresponding event method in URLRequest and then to
  // JavaScript via the EmitRequestEvent/EmitResponseEvent helpers.
  //
  // URLRequest lifetime management: we followed the Wrapper/Wrappable pattern
  // defined in native_mate. However, we augment that pattern with a pin/unpin
  // mechanism. The main reason is that we want the JS API to provide a similar
  // lifetime guarantees as the XMLHttpRequest.
  // https://xhr.spec.whatwg.org/#garbage-collection
  //
  // The primary motivation is to not garbage collect a URLInstance as long as the
  // object is emitting network events. For instance, in the following JS code
  //
  // (function() {
  //   let request = new URLRequest(...);
  //   request.on('response', (response)=>{
  //    response.on('data', (data) = > {
  //      console.log(data.toString());
  //    });
  //  });
  // })();
  //
  // we still want data to be logged even if the response/request objects are n
  // more referenced in JavaScript.
  //
  // Binding by simply following the native_mate Wrapper/Wrappable pattern will
  // delete the URLRequest object when the corresponding JS object is collected.
  // The v8 handle is a private member in WrappableBase and it is always weak,
  // there is no way to make it strong without changing native_mate.
  // The solution we implement consists of maintaining some kind of state that
  // prevents collection of JS wrappers as long as the request is emitting network
  // events. At initialization, the object is unpinned. When the request starts,
  // it is pinned. When no more events would be emitted, the object is unpinned
  // and lifetime is again managed by the standard native mate Wrapper/Wrappable
  // pattern.
  //
  // pin/unpin: are implemented by constructing/reseting a V8 strong persistent
  // handle.
  //
  // The URLRequest/AtmURLRequest interaction could have been implemented in a
  // single class. However, it implies that the resulting class lifetime will be
  // managed by two conflicting mechanisms: JavaScript garbage collection and
  // Chromium reference counting. Reasoning about lifetime issues become much
  // more complex.
  //
  // We chose to split the implementation into two classes linked via a
  // reference counted/raw pointers. A URLRequest instance is deleted if it is
  // unpinned and the corresponding JS wrapper object is garbage collected. On the
  // other hand, an AtmURLRequest instance lifetime is totally governed by
  // reference counting.
  //
  class URLRequest : public mate::EventEmitter<URLRequest> {
   public:
    static mate::WrappableBase* New(mate::Arguments* args);
  
    static void BuildPrototype(v8::Isolate* isolate,
                               v8::Local<v8::FunctionTemplate> prototype);
  
    // Methods for reporting events into JavaScript.
    void OnReceivedRedirect(
        int status_code,
        const std::string& method,
        const GURL& url,
        scoped_refptr<net::HttpResponseHeaders> response_headers);
    void OnAuthenticationRequired(const net::AuthChallengeInfo& auth_info);
    void OnResponseStarted(
        scoped_refptr<net::HttpResponseHeaders> response_headers);
    void OnResponseData(scoped_refptr<const net::IOBufferWithSize> data);
    void OnResponseCompleted();
    void OnError(const std::string& error, bool isRequestError);
    mate::Dictionary GetUploadProgress(v8::Isolate* isolate);
  
   protected:
    explicit URLRequest(v8::Isolate* isolate, v8::Local<v8::Object> wrapper);
    ~URLRequest() override;
  
   private:
    template <typename Flags>
    class StateBase {
     public:
      void SetFlag(Flags flag);
  
     protected:
      explicit StateBase(Flags initialState);
      bool operator==(Flags flag) const;
      bool IsFlagSet(Flags flag) const;
  
     private:
      Flags state_;
    };
  
    enum class RequestStateFlags {
      kNotStarted = 0x0,
      kStarted = 0x1,
      kFinished = 0x2,
      kCanceled = 0x4,
      kFailed = 0x8,
      kClosed = 0x10
    };
  
    class RequestState : public StateBase<RequestStateFlags> {
     public:
      RequestState();
      bool NotStarted() const;
      bool Started() const;
      bool Finished() const;
      bool Canceled() const;
      bool Failed() const;
      bool Closed() const;
    };
  
    enum class ResponseStateFlags {
      kNotStarted = 0x0,
      kStarted = 0x1,
      kEnded = 0x2,
      kFailed = 0x4
    };
  
    class ResponseState : public StateBase<ResponseStateFlags> {
     public:
      ResponseState();
      bool NotStarted() const;
      bool Started() const;
      bool Ended() const;
      bool Canceled() const;
      bool Failed() const;
      bool Closed() const;
    };
  
    bool NotStarted() const;
    bool Finished() const;
    bool Canceled() const;
    bool Failed() const;
    bool Write(scoped_refptr<const net::IOBufferWithSize> buffer, bool is_last);
    void Cancel();
    void FollowRedirect();
    bool SetExtraHeader(const std::string& name, const std::string& value);
    void RemoveExtraHeader(const std::string& name);
    void SetChunkedUpload(bool is_chunked_upload);
    void SetLoadFlags(int flags);
  
    int StatusCode() const;
    std::string StatusMessage() const;
    net::HttpResponseHeaders* RawResponseHeaders() const;
    uint32_t ResponseHttpVersionMajor() const;
    uint32_t ResponseHttpVersionMinor() const;
  
    void Close();
    void Pin();
    void Unpin();
    template <typename... Args>
    void EmitRequestEvent(Args... args);
    template <typename... Args>
    void EmitResponseEvent(Args... args);
  
    scoped_refptr<AtomURLRequest> atom_request_;
    RequestState request_state_;
    ResponseState response_state_;
  
    // Used to implement pin/unpin.
    v8::Global<v8::Object> wrapper_;
    scoped_refptr<net::HttpResponseHeaders> response_headers_;
  
    DISALLOW_COPY_AND_ASSIGN(URLRequest);
  };
  
  }  // namespace api
  
  }  // namespace atom
  
  #endif  // ATOM_BROWSER_API_ATOM_API_URL_REQUEST_H_
  