Implementing authentication callback.

This commit is contained in:
ali.ibrahim 2016-09-21 17:35:03 +02:00
parent 2d9d4af98d
commit 2b3b41d5f9
5 changed files with 312 additions and 129 deletions

View file

@ -8,6 +8,9 @@
#include "native_mate/dictionary.h"
#include "atom/browser/net/atom_url_request.h"
#include "atom/common/node_includes.h"
#include "atom/common/native_mate_converters/net_converter.h"
#include "atom/common/native_mate_converters/string16_converter.h"
#include "atom/common/native_mate_converters/callback.h"
namespace {
@ -19,9 +22,9 @@ const char* const kEnd = "end";
namespace mate {
template<>
struct Converter<scoped_refptr<net::HttpResponseHeaders>> {
struct Converter<scoped_refptr<const net::HttpResponseHeaders>> {
static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
scoped_refptr<net::HttpResponseHeaders> val) {
scoped_refptr<const net::HttpResponseHeaders> val) {
mate::Dictionary dict = mate::Dictionary::CreateEmpty(isolate);
if (val) {
@ -37,10 +40,10 @@ struct Converter<scoped_refptr<net::HttpResponseHeaders>> {
};
template<>
struct Converter<scoped_refptr<net::IOBufferWithSize>> {
struct Converter<scoped_refptr<const net::IOBufferWithSize>> {
static v8::Local<v8::Value> ToV8(
v8::Isolate* isolate,
scoped_refptr<net::IOBufferWithSize> buffer) {
scoped_refptr<const net::IOBufferWithSize> buffer) {
return node::Buffer::Copy(isolate, buffer->data(), buffer->size()).ToLocalChecked();
}
};
@ -79,11 +82,13 @@ mate::WrappableBase* URLRequest::New(mate::Arguments* args) {
auto api_url_request = new URLRequest(args->isolate(), args->GetThis());
auto weak_ptr = api_url_request->weak_ptr_factory_.GetWeakPtr();
auto atom_url_request = AtomURLRequest::create(browser_context, url, weak_ptr);
atom_url_request->set_method(method);
auto atom_url_request = AtomURLRequest::Create(
browser_context,
method,
url,
weak_ptr);
api_url_request->atom_url_request_ = atom_url_request;
api_url_request->atom_request_ = atom_url_request;
return api_url_request;
@ -97,12 +102,12 @@ void URLRequest::BuildPrototype(v8::Isolate* isolate,
mate::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate())
// Request API
.MakeDestroyable()
.SetMethod("write", &URLRequest::Write)
.SetMethod("end", &URLRequest::End)
.SetMethod("_write", &URLRequest::Write)
.SetMethod("_end", &URLRequest::End)
.SetMethod("abort", &URLRequest::Abort)
.SetMethod("setHeader", &URLRequest::SetHeader)
.SetMethod("getHeader", &URLRequest::GetHeader)
.SetMethod("removaHeader", &URLRequest::RemoveHeader)
.SetMethod("_setHeader", &URLRequest::SetHeader)
.SetMethod("_getHeader", &URLRequest::GetHeader)
.SetMethod("_removaHeader", &URLRequest::RemoveHeader)
// Response APi
.SetProperty("statusCode", &URLRequest::StatusCode)
.SetProperty("statusMessage", &URLRequest::StatusMessage)
@ -114,92 +119,83 @@ void URLRequest::BuildPrototype(v8::Isolate* isolate,
}
void URLRequest::Write() {
atom_url_request_->Write();
atom_request_->Write();
}
void URLRequest::End() {
pin();
atom_url_request_->End();
atom_request_->End();
}
void URLRequest::Abort() {
atom_url_request_->Abort();
atom_request_->Abort();
}
void URLRequest::SetHeader() {
atom_url_request_->SetHeader();
void URLRequest::SetHeader(const std::string& name, const std::string& value) {
atom_request_->SetHeader(name, value);
}
void URLRequest::GetHeader() {
atom_url_request_->GetHeader();
std::string URLRequest::GetHeader(const std::string& name) {
return atom_request_->GetHeader(name);
}
void URLRequest::RemoveHeader() {
atom_url_request_->RemoveHeader();
void URLRequest::RemoveHeader(const std::string& name) {
atom_request_->RemoveHeader(name);
}
void URLRequest::OnAuthenticationRequired(
scoped_refptr<const net::AuthChallengeInfo> auth_info) {
EmitRequestEvent(
"login",
auth_info.get(),
base::Bind(&AtomURLRequest::PassLoginInformation, atom_request_));
}
void URLRequest::OnResponseStarted() {
//v8::Local<v8::Function> _emitResponse;
//auto wrapper = GetWrapper();
//if (mate::Dictionary(isolate(), wrapper).Get("_emitResponse", &_emitResponse))
// _emitResponse->Call(wrapper, 0, nullptr);
EmitRequestEvent("response");
}
void URLRequest::OnResponseData(scoped_refptr<net::IOBufferWithSize> buffer) {
void URLRequest::OnResponseData(
scoped_refptr<const net::IOBufferWithSize> buffer) {
if (!buffer || !buffer->data() || !buffer->size()) {
return;
}
EmitResponseEvent("data", buffer);
//v8::Local<v8::Function> _emitData;
//auto data = mate::ConvertToV8(isolate(), buffer);
//auto wrapper = GetWrapper();
//if (mate::Dictionary(isolate(), wrapper).Get("_emitData", &_emitData))
// _emitData->Call(wrapper, 1, &data);
}
void URLRequest::OnResponseCompleted() {
//v8::Local<v8::Function> _emitEnd;
//auto wrapper = GetWrapper();
//if (mate::Dictionary(isolate(), wrapper).Get("_emitEnd", &_emitEnd))
// _emitEnd->Call(wrapper, 0, nullptr);
EmitResponseEvent("end");
}
int URLRequest::StatusCode() {
if (auto response_headers = atom_url_request_->GetResponseHeaders()) {
int URLRequest::StatusCode() const {
if (auto response_headers = atom_request_->GetResponseHeaders()) {
return response_headers->response_code();
}
return -1;
}
std::string URLRequest::StatusMessage() {
std::string URLRequest::StatusMessage() const {
std::string result;
if (auto response_headers = atom_url_request_->GetResponseHeaders()) {
if (auto response_headers = atom_request_->GetResponseHeaders()) {
result = response_headers->GetStatusText();
}
return result;
}
scoped_refptr<net::HttpResponseHeaders> URLRequest::RawResponseHeaders() {
return atom_url_request_->GetResponseHeaders();
scoped_refptr<const net::HttpResponseHeaders> URLRequest::RawResponseHeaders() const {
return atom_request_->GetResponseHeaders();
}
uint32_t URLRequest::ResponseHttpVersionMajor() {
if (auto response_headers = atom_url_request_->GetResponseHeaders()) {
uint32_t URLRequest::ResponseHttpVersionMajor() const {
if (auto response_headers = atom_request_->GetResponseHeaders()) {
return response_headers->GetHttpVersion().major_value();
}
return 0;
}
uint32_t URLRequest::ResponseHttpVersionMinor() {
if (auto response_headers = atom_url_request_->GetResponseHeaders()) {
uint32_t URLRequest::ResponseHttpVersionMinor() const {
if (auto response_headers = atom_request_->GetResponseHeaders()) {
return response_headers->GetHttpVersion().minor_value();
}
return 0;

View file

@ -35,25 +35,27 @@ private:
void Write();
void End();
void Abort();
void SetHeader();
void GetHeader();
void RemoveHeader();
void SetHeader(const std::string& name, const std::string& value);
std::string GetHeader(const std::string& name);
void RemoveHeader(const std::string& name);
friend class AtomURLRequest;
void OnAuthenticationRequired(
scoped_refptr<const net::AuthChallengeInfo> auth_info);
void OnResponseStarted();
void OnResponseData(scoped_refptr<net::IOBufferWithSize> data);
void OnResponseData(scoped_refptr<const net::IOBufferWithSize> data);
void OnResponseCompleted();
int StatusCode();
std::string StatusMessage();
scoped_refptr<net::HttpResponseHeaders> RawResponseHeaders();
uint32_t ResponseHttpVersionMajor();
uint32_t ResponseHttpVersionMinor();
int StatusCode() const;
std::string StatusMessage() const;
scoped_refptr<const net::HttpResponseHeaders> RawResponseHeaders() const;
uint32_t ResponseHttpVersionMajor() const;
uint32_t ResponseHttpVersionMinor() const;
template <typename ... ArgTypes>
std::array<v8::Local<v8::Value>, sizeof...(ArgTypes)>
BuildArgsArray(ArgTypes... args);
BuildArgsArray(ArgTypes... args) const;
template <typename ... ArgTypes>
void EmitRequestEvent(ArgTypes... args);
@ -61,12 +63,10 @@ private:
template <typename ... ArgTypes>
void EmitResponseEvent(ArgTypes... args);
void pin();
void unpin();
scoped_refptr<AtomURLRequest> atom_url_request_;
scoped_refptr<const AtomURLRequest> atom_request_;
v8::Global<v8::Object> wrapper_;
base::WeakPtrFactory<URLRequest> weak_ptr_factory_;
@ -76,7 +76,7 @@ private:
template <typename ... ArgTypes>
std::array<v8::Local<v8::Value>, sizeof...(ArgTypes)>
URLRequest::BuildArgsArray(ArgTypes... args) {
URLRequest::BuildArgsArray(ArgTypes... args) const {
std::array<v8::Local<v8::Value>, sizeof...(ArgTypes)> result
= { mate::ConvertToV8(isolate(), args)... };
return result;

View file

@ -10,6 +10,7 @@
#include "content/public/browser/browser_thread.h"
#include "net/base/io_buffer.h"
namespace {
const int kBufferSize = 4096;
@ -18,19 +19,20 @@ const int kBufferSize = 4096;
namespace atom {
AtomURLRequest::AtomURLRequest(base::WeakPtr<api::URLRequest> delegate)
AtomURLRequest::AtomURLRequest(base::WeakPtr<api::URLRequest> delegate)
: delegate_(delegate)
, buffer_( new net::IOBuffer(kBufferSize)) {
, buffer_(new net::IOBuffer(kBufferSize)) {
}
AtomURLRequest::~AtomURLRequest() {
}
scoped_refptr<AtomURLRequest> AtomURLRequest::create(
scoped_refptr<AtomURLRequest> AtomURLRequest::Create(
AtomBrowserContext* browser_context,
const std::string& method,
const std::string& url,
base::WeakPtr<api::URLRequest> delegate) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(browser_context);
DCHECK(!url.empty());
@ -44,69 +46,117 @@ scoped_refptr<AtomURLRequest> AtomURLRequest::create(
scoped_refptr<AtomURLRequest> atom_url_request = new AtomURLRequest(delegate);
atom_url_request->url_request_ = context->CreateRequest(GURL(url),
atom_url_request->request_ = context->CreateRequest(GURL(url),
net::RequestPriority::DEFAULT_PRIORITY,
atom_url_request.get());
atom_url_request->request_->set_method(method);
return atom_url_request;
}
void AtomURLRequest::Write() {
void AtomURLRequest::Write() const {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
}
void AtomURLRequest::End() {
// Called on content::BrowserThread::UI
void AtomURLRequest::End() const {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
content::BrowserThread::PostTask(
content::BrowserThread::IO, FROM_HERE,
base::Bind(&AtomURLRequest::StartOnIOThread, this));
base::Bind(&AtomURLRequest::DoStart, this));
}
void AtomURLRequest::Abort() {
void AtomURLRequest::Abort() const {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
}
void AtomURLRequest::SetHeader() {
void AtomURLRequest::SetHeader(const std::string& name,
const std::string& value) const {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
request_->SetExtraRequestHeaderByName(name, value, true);
}
void AtomURLRequest::GetHeader() {
std::string AtomURLRequest::GetHeader(const std::string& name) const {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
std::string result;
const auto& extra_headers = request_->extra_request_headers();
if (!extra_headers.GetHeader(name, &result)) {
net::HttpRequestHeaders* request_headers = nullptr;
if (request_->GetFullRequestHeaders(request_headers) && request_headers) {
request_headers->GetHeader(name, &result);
}
}
return result;
}
void AtomURLRequest::RemoveHeader() {
void AtomURLRequest::RemoveHeader(const std::string& name) const {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
request_->RemoveRequestHeaderByName(name);
}
scoped_refptr<net::HttpResponseHeaders> AtomURLRequest::GetResponseHeaders() {
return url_request_->response_headers();
scoped_refptr<const net::HttpResponseHeaders>
AtomURLRequest::GetResponseHeaders() const {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
return request_->response_headers();
}
void AtomURLRequest::StartOnIOThread() {
// Called on content::BrowserThread::IO
url_request_->Start();
void AtomURLRequest::PassLoginInformation(const base::string16& username,
const base::string16& password) const {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (username.empty() || password.empty()) {
content::BrowserThread::PostTask(
content::BrowserThread::IO, FROM_HERE,
base::Bind(&AtomURLRequest::DoCancelAuth, this));
}
else {
content::BrowserThread::PostTask(
content::BrowserThread::IO, FROM_HERE,
base::Bind(&AtomURLRequest::DoSetAuth, this, username, password));
}
}
void AtomURLRequest::set_method(const std::string& method) {
url_request_->set_method(method);
void AtomURLRequest::DoStart() const {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
request_->Start();
}
void AtomURLRequest::DoSetAuth(const base::string16& username,
const base::string16& password) const {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
request_->SetAuth(net::AuthCredentials(username, password));
}
void AtomURLRequest::DoCancelAuth() const {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
request_->CancelAuth();
}
void AtomURLRequest::OnAuthRequired(net::URLRequest* request,
net::AuthChallengeInfo* auth_info) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
content::BrowserThread::PostTask(
content::BrowserThread::UI, FROM_HERE,
base::Bind(&AtomURLRequest::InformDelegateAuthenticationRequired,
this,
scoped_refptr<net::AuthChallengeInfo>(auth_info)));
}
void AtomURLRequest::OnResponseStarted(net::URLRequest* request) {
// Called on content::BrowserThread::IO
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
DCHECK_EQ(request, request_.get());
DCHECK_EQ(request, url_request_.get());
if (url_request_->status().is_success()) {
if (request_->status().is_success()) {
// Cache net::HttpResponseHeaders instance, a read-only objects
// so that headers and other http metainformation can be simultaneously
// read from UI thread while request data is simulataneously streaming
// on IO thread.
response_headers_ = url_request_->response_headers();
response_headers_ = request_->response_headers();
}
content::BrowserThread::PostTask(
@ -117,29 +167,28 @@ void AtomURLRequest::OnResponseStarted(net::URLRequest* request) {
}
void AtomURLRequest::ReadResponse() {
// Called on content::BrowserThread::IO
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
// Some servers may treat HEAD requests as GET requests. To free up the
// network connection as soon as possible, signal that the request has
// completed immediately, without trying to read any data back (all we care
// about is the response code and headers, which we already have).
int bytes_read = 0;
if (url_request_->status().is_success() /* TODO && (request_type_ != URLFetcher::HEAD)*/) {
if (!url_request_->Read(buffer_.get(), kBufferSize, &bytes_read))
if (request_->status().is_success() /* TODO && (request_type_ != URLFetcher::HEAD)*/) {
if (!request_->Read(buffer_.get(), kBufferSize, &bytes_read))
bytes_read = -1;
}
OnReadCompleted(url_request_.get(), bytes_read);
OnReadCompleted(request_.get(), bytes_read);
}
void AtomURLRequest::OnReadCompleted(net::URLRequest* request, int bytes_read) {
// Called on content::BrowserThread::IO
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
DCHECK_EQ(request, url_request_.get());
DCHECK_EQ(request, request_.get());
do {
if (!url_request_->status().is_success() || bytes_read <= 0)
if (!request_->status().is_success() || bytes_read <= 0)
break;
@ -148,9 +197,9 @@ void AtomURLRequest::OnReadCompleted(net::URLRequest* request, int bytes_read) {
// Failed to transfer data to UI thread.
return;
}
} while (url_request_->Read(buffer_.get(), kBufferSize, &bytes_read));
} while (request_->Read(buffer_.get(), kBufferSize, &bytes_read));
const auto status = url_request_->status();
const auto status = request_->status();
if (!status.is_io_pending() /* TODO || request_type_ == URLFetcher::HEAD*/ ) {
@ -161,9 +210,8 @@ void AtomURLRequest::OnReadCompleted(net::URLRequest* request, int bytes_read) {
}
bool AtomURLRequest::CopyAndPostBuffer(int bytes_read) {
// Called on content::BrowserThread::IO.
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
// data is only a wrapper for the async buffer_.
// Make a deep copy of payload and transfer ownership to the UI thread.
@ -176,16 +224,25 @@ bool AtomURLRequest::CopyAndPostBuffer(int bytes_read) {
}
void AtomURLRequest::InformDelegateResponseStarted() {
// Called on content::BrowserThread::UI.
void AtomURLRequest::InformDelegateAuthenticationRequired(
scoped_refptr<net::AuthChallengeInfo> auth_info) const {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (delegate_) {
delegate_->OnAuthenticationRequired(auth_info);
}
}
void AtomURLRequest::InformDelegateResponseStarted() const {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (delegate_) {
delegate_->OnResponseStarted();
}
}
void AtomURLRequest::InformDelegateResponseData(scoped_refptr<net::IOBufferWithSize> data) {
// Called on content::BrowserThread::IO.
void AtomURLRequest::InformDelegateResponseData(
scoped_refptr<net::IOBufferWithSize> data) const {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// Transfer ownership of the data buffer, data will be released
// by the delegate's OnResponseData.
@ -194,7 +251,9 @@ void AtomURLRequest::InformDelegateResponseData(scoped_refptr<net::IOBufferWithS
}
}
void AtomURLRequest::InformDelegateResponseCompleted() {
void AtomURLRequest::InformDelegateResponseCompleted() const {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (delegate_) {
delegate_->OnResponseCompleted();
}

View file

@ -27,43 +27,52 @@ namespace api {
class AtomURLRequest : public base::RefCountedThreadSafe<AtomURLRequest>,
public net::URLRequest::Delegate {
public:
static scoped_refptr<AtomURLRequest> create(
static scoped_refptr<AtomURLRequest> Create(
AtomBrowserContext* browser_context,
const std::string& method,
const std::string& url,
base::WeakPtr<api::URLRequest> delegate);
void set_method(const std::string& method);
void Write();
void End();
void Abort();
void SetHeader();
void GetHeader();
void RemoveHeader();
scoped_refptr<net::HttpResponseHeaders> GetResponseHeaders();
void Write() const;
void End() const;
void Abort() const;
void SetHeader(const std::string& name, const std::string& value) const;
std::string GetHeader(const std::string& name) const;
void RemoveHeader(const std::string& name) const;
void PassLoginInformation(const base::string16& username,
const base::string16& password) const;
scoped_refptr<const net::HttpResponseHeaders> GetResponseHeaders() const;
protected:
// Overrides of net::URLRequest::Delegate
virtual void OnAuthRequired(net::URLRequest* request,
net::AuthChallengeInfo* auth_info) override;
virtual void OnResponseStarted(net::URLRequest* request) override;
virtual void OnReadCompleted(net::URLRequest* request, int bytes_read) override;
virtual void OnReadCompleted(net::URLRequest* request,
int bytes_read) override;
private:
friend class base::RefCountedThreadSafe<AtomURLRequest>;
void StartOnIOThread();
void DoStart() const;
void DoSetAuth(const base::string16& username,
const base::string16& password) const;
void DoCancelAuth() const;
void ReadResponse();
bool CopyAndPostBuffer(int bytes_read);
void InformDelegateResponseStarted();
void InformDelegateResponseData(scoped_refptr<net::IOBufferWithSize> data);
void InformDelegateResponseCompleted();
void InformDelegateAuthenticationRequired(
scoped_refptr<net::AuthChallengeInfo> auth_info) const;
void InformDelegateResponseStarted() const;
void InformDelegateResponseData(
scoped_refptr<net::IOBufferWithSize> data) const;
void InformDelegateResponseCompleted() const;
AtomURLRequest(base::WeakPtr<api::URLRequest> delegate);
virtual ~AtomURLRequest();
base::WeakPtr<api::URLRequest> delegate_;
std::unique_ptr<net::URLRequest> url_request_;
std::unique_ptr<net::URLRequest> request_;
scoped_refptr<net::IOBuffer> buffer_;
scoped_refptr<net::HttpResponseHeaders> response_headers_;

View file

@ -55,6 +55,48 @@ Net.prototype.request = function(options, callback) {
return request
}
URLRequest.prototype._init = function() {
this._finished = false;
this._hasBody = true;
this._chunkedEncoding = false;
this._headersSent = false;
}
URLRequest.prototype.setHeader = function(name, value) {
if (typeof name !== 'string')
throw new TypeError('`name` should be a string in setHeader(name, value).');
if (value === undefined)
throw new Error('`value` required in setHeader("' + name + '", value).');
if (this._headersSent)
throw new Error('Can\'t set headers after they are sent.');
this._setHeader(name, value)
};
URLRequest.prototype.getHeader = function(name) {
if (arguments.length < 1) {
throw new Error('`name` is required for getHeader(name).');
}
return this._getHeader(name);
};
URLRequest.prototype.removeHeader = function(name) {
if (arguments.length < 1) {
throw new Error('`name` is required for removeHeader(name).');
}
if (this._headersSent) {
throw new Error('Can\'t remove headers after they are sent.');
}
this._removeHeader(name);
};
URLRequest.prototype._emitRequestEvent = function(name) {
if (name === 'response') {
this.response = new URLResponse(this);
@ -69,5 +111,82 @@ URLRequest.prototype._emitResponseEvent = function() {
}
URLRequest.prototype.write = function(chunk, encoding, callback) {
if (this.finished) {
var err = new Error('write after end');
process.nextTick(writeAfterEndNT, this, err, callback);
return true;
}
/* TODO
if (!this._header) {
this._implicitHeader();
}
*/
if (!this._hasBody) {
//debug('This type of response MUST NOT have a body. ' +
// 'Ignoring write() calls.');
return true;
}
if (typeof chunk !== 'string' && !(chunk instanceof Buffer)) {
throw new TypeError('First argument must be a string or Buffer');
}
// If we get an empty string or buffer, then just do nothing, and
// signal the user to keep writing.
if (chunk.length === 0) return true;
var len, ret;
if (this.chunkedEncoding) {
if (typeof chunk === 'string' &&
encoding !== 'hex' &&
encoding !== 'base64' &&
encoding !== 'binary') {
len = Buffer.byteLength(chunk, encoding);
chunk = len.toString(16) + CRLF + chunk + CRLF;
ret = this._send(chunk, encoding, callback);
} else {
// buffer, or a non-toString-friendly encoding
if (typeof chunk === 'string')
len = Buffer.byteLength(chunk, encoding);
else
len = chunk.length;
if (this.connection && !this.connection.corked) {
this.connection.cork();
process.nextTick(connectionCorkNT, this.connection);
}
this._send(len.toString(16), 'binary', null);
this._send(crlf_buf, null, null);
this._send(chunk, encoding, null);
ret = this._send(crlf_buf, null, callback);
}
} else {
ret = this._send(chunk, encoding, callback);
}
//debug('write ret = ' + ret);
return ret;
};
URLRequest.prototype.end = function() {
this._end();
};
function writeAfterEndNT(self, err, callback) {
self.emit('error', err);
if (callback) callback(err);
}
module.exports = net