Javascript usually uses regular expressions to check URLs to determine whether the format is correct, for example:
/^https?:///.test(url);
Of course there are better detection methods such as based on RFC 3986, RFC 3966, RFC 4694 , RFC 4759, RFC 4904 and other standard valid-url libraries for verification.
Of course, verification based on the format cannot determine whether the url exists, so with url-valid, we verify based on HTTP requests.
Interface design
In fact, we only need a function to pass in a url address, and callback to return whether the link is available.
But requests are prone to unknown errors, so we pass in an error parameter in the callback function. If it is not empty, an error occurs.
We may also hope to obtain relevant data of the web page and use it to extract information from the page in the future.
Chain operations as much as possible.
So the final usage is probably like this:
valid(url)
.on('check', function (err, status) {
if (err) throw err;
status ?
console.log('url is available') :
console.log('url is not available');
})
.on('data', function (err, data) {
console.log(data);
})
.on('end', function (err, data) {
console.log('Request ended');
})
HTTP GET Or HTTP HEAD
Originally we wanted to use HTTP HEAD request to achieve this, because HEAD request will only return header information, which can reduce the request time, but HEAD request may not be supported by all links.
So in the end we use the HTTP GET method and abort the request immediately after getting the correct statusCode.
Processing 301-303
Because 301 to 303 are redirect states, we need to continue to check whether the corresponding Location still exists.
Use process.nextTick to execute asynchronously
In order to execute the code after registering the listener, we use process.nextTick to perform a one-step operation.
Implementation
/*!
* valid
* MIT Licensed
*/
module.exports = (function () {
'use strict';
var http = require('http')
, https = require('https')
, EventEmitter = require('events').EventEmitter
, URL = require('url')
, urlReg = /^(https?):///;
/**
* Valid
* @class
*/
function Valid(url, callback) {
var that = this;
this.url = url;
this.emitter = new EventEmitter();
process.nextTick(function () {
that.get(url);
});
this.fetch = false;
callback && this.emitter.on('check', callback);
}
Valid.prototype = {
constructor: Valid,
/**
* get
* @param {String} url
*/
get: function (url) {
var match = url.match(urlReg)
, that = this;
if (match) {
var httpLib = (match[1].toLowerCase() === 'http') ? http : https
, opts = URL.parse(url)
, req;
opts.agent = false;
opts.method = 'GET';
req = httpLib.request(opts, function (res) {
var statusCode = res.statusCode;
if (statusCode === 200) {
that.emitter.emit('check', null, true);
that.fetch ?
(res.on('data', function (data) {
that.emitter.emit('data', null, data);
}) && res.on('end', function () {
that.emitter.emit('end');
})) :
(req.abort() || that.emitter.emit('end'));
} else if (300 < statusCode && statusCode < 304) {
req.abort();
var emitter = that.emitter
, valid = one(URL.resolve(url, res.headers.location), function (err, valid) {
emitter.emit('check', err, valid);
});
that.fetch && valid.on('data', function (err, data) {
emitter.emit('data', err, data);
});
valid.on('error', function (err) {
that.emitter.emit('error', err);
});
valid.on('end', function () {
that.emitter.emit('end');
});
} else {
that.emitter.emit('check', null, false);
}
res.on('error', function (err) {
req.abort();
that.emitter.emit('data', err);
});
});
req.on('error', function (err) {
req.abort();
return that.emitter.emit('check', null, false);
});
req.end();
} else {
return that.emitter.emit('check', null, false);
}
},
/ **
* on
* @param {Stirng} 이벤트
* @param {Function} 콜백
*/
on: 함수(이벤트, 콜백) {
(이벤트 === '데이터') && (this.fetch = true);
this.emitter.on(event, callback);
return this;
},
/**
* 파괴
*/
destroy: function () {
this. Emitter.removeAllListeners();
this.url = undefine;
this.emitter = null;
this.fetch = undefine;
},
/**
* RemoveAllListeners
* @param
*/
RemoveAllListeners: 함수(이벤트) {
이벤트 ?
this.emitter.removeAllListeners(event) :
this.emitter.removeAllListeners();
return this;
},
/**
* 청취자
* @param
*/
청취자: function (event) {
if (event) {
return this.emitter.listeners(event);
} else {
var res = []
, that = this
, _push = Array.prototype.push;
Object.keys(this.emitter._events).forEach(function (key) {
_push.apply(res, that.emitter.listeners(key));
});
return res;
}
}
}
/**
* 하나
* @param {String} url
* @param {함수} 콜백
* @return {유효}
*/
함수 1(url, 콜백) {
return ( new Valid(url, 콜백));
}
one.one = one;
하나 반환;
})();