// 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 SHELL_BROWSER_API_ATOM_API_URL_REQUEST_H_ #define SHELL_BROWSER_API_ATOM_API_URL_REQUEST_H_ #include #include #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" #include "shell/browser/api/event_emitter.h" #include "shell/browser/api/trackable_object.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 { public: static mate::WrappableBase* New(mate::Arguments* args); static void BuildPrototype(v8::Isolate* isolate, v8::Local prototype); // Methods for reporting events into JavaScript. void OnReceivedRedirect( int status_code, const std::string& method, const GURL& url, scoped_refptr response_headers); void OnAuthenticationRequired(const net::AuthChallengeInfo& auth_info); void OnResponseStarted( scoped_refptr response_headers); void OnResponseData(scoped_refptr 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 wrapper); ~URLRequest() override; private: template 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 { 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 { 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 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 void EmitRequestEvent(Args... args); template void EmitResponseEvent(Args... args); scoped_refptr atom_request_; RequestState request_state_; ResponseState response_state_; // Used to implement pin/unpin. v8::Global wrapper_; scoped_refptr response_headers_; DISALLOW_COPY_AND_ASSIGN(URLRequest); }; } // namespace api } // namespace atom #endif // SHELL_BROWSER_API_ATOM_API_URL_REQUEST_H_