metaweblog
Version:
MetaWeblog API on Nodejs
243 lines (232 loc) • 6.13 kB
JavaScript
/**
* Module dependencies.
*/
var http = require('http')
, https = require('https')
, urlutil = require('url')
, xml2json = require('xml2json');
/**
* HTML Special Characters
*
* http://tntluoma.com/files/codes.htm
*/
var escape = exports.escape = function(s) {
return String(s)
.replace(/&(?!\w+;)/g, '&')
.replace(/@/g, '@')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"');
};
function Client(rpc_url) {
var info = urlutil.parse(rpc_url);
this.http_request = http.request;
var port = info.port || 80;
if(info.protocol == 'https:') {
this.http_request = https.request;
if(!info.port) {
port = 443;
}
}
this.options = {
host: info.hostname,
path: info.pathname || '/',
method: 'POST',
port: port
};
if(info.query) {
this.options.path += info.search;
}
};
/**
* create xml rpc request data
*
* @param {String} method
* @param param1, param2, ...
* @param {Function} callback(result)
* @return {String} xml string
* @api public
*/
Client.prototype.request = function() {
var len = arguments.length - 1;
var callback = arguments[len];
var params = [];
for(var i = 0; i < len; i++) {
params.push(arguments[i]);
}
var body = this.get_xml.apply(this, params);
var options = this.options;
var that = this;
var req = this.http_request(options, function(res) {
var chunks = [], length = 0;
res.on('data', function(chunk) {
length += chunk.length;
chunks.push(chunk);
});
res.on('end', function(){
var data = new Buffer(length);
var error = null;
// 延后copy
for(var i=0, pos=0, l=chunks.length; i<l; i++) {
chunks[i].copy(data, pos);
pos += chunks[i].length;
}
data = data.toString();
if(res.statusCode == 200 || res.statusCode == 201) {
data = xml2json.toJson(data, {object: true});
data = that.parse(data);
if(data && data.faultString) {
error = data;
error.code = data.faultCode;
error.message = data.faultString;
data = null;
}
} else {
error = data;
data = null;
}
if(callback) {
callback(error, data, res);
}
});
});
req.write(body);
req.end();
};
/**
* create xml rpc request data
*
* <methodCall>
* <methodName>blogger.getUsersBlogs</methodName>
* <params>
* <param><value><string>fawave中文<>/?":</string></value></param>
* <param><value><string>fengmk2</string></value></param>
* <param><value><int>123</int></value></param>
* </params>
* </methodCall>
*
* @param {String} method
* @param param1, param2, ...
* @return {String} xml string
* @api public
*/
Client.prototype.get_xml = function() {
var method = arguments[0];
var doc = [];
doc.push('<methodCall>');
doc.push('<methodName>' + method + '</methodName>');
if(arguments.length > 1) {
doc.push('<params>');
for(var i = 1, len = arguments.length; i < len; i++) {
doc.push('<param><value>');
this.serialize(arguments[i], doc);
doc.push('</value></param>');
}
doc.push('</params>');
}
doc.push('</methodCall>');
return doc.join('\n');
};
function pad(n){
return n < 10 ? '0' + n : n;
};
// https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference:Global_Objects:Date
function ISODateString(d){
return d.getUTCFullYear() + '-'
+ pad(d.getUTCMonth() + 1) +'-'
+ pad(d.getUTCDate()) + 'T'
+ pad(d.getUTCHours()) + ':'
+ pad(d.getUTCMinutes()) + ':'
+ pad(d.getUTCSeconds()) + 'Z';
};
Client.prototype.serialize = function(data, d) {
switch (data.constructor.name) {
case 'Array':
d.push('<array><data>');
for(var i = 0, len = data.length; i < len; i++) {
d.push('<value>');
this.serialize(data[i], d);
d.push('</value>');
}
d.push('</data></array>');
break;
case 'Object':
d.push('<struct>');
for(var i in data) {
d.push('<member>');
d.push('<name>' + i + '</name>');
d.push('<value>');
this.serialize(data[i], d);
d.push('</value>');
d.push('</member>');
}
d.push('</struct>');
break;
case 'Number':
d.push('<int>' + data + '</int>');
break;
case 'String':
d.push('<string>' + escape(data) + '</string>');
break;
case 'Date':
d.push('<dateTime.iso8601>' + data.toISOString() + '</dateTime.iso8601>');
break;
case 'Boolean':
d.push('<boolean>' + (data ? '1' : '0') + '</boolean>');
break;
default:
d.push('<string>' + escape(data) + '</string>');
break;
}
};
/**
* rpc result parser
*
* @param res
* @returns
*/
Client.prototype.parse = function(res) {
if(res.methodResponse) {
res = res.methodResponse;
}
if(res.params) {
res = res.params.param.value;
} else if(res.fault) {
res = res.fault.value;
}
return this._parse(res);
};
Client.prototype._parse = function(res) {
for (var name in res) {
if (name === 'struct') {
var data = {};
var members = res.struct.member;
if(!members) {
return data;
}
if(members.constructor.name !== 'Array') {
members = [members];
}
for (var i = 0, l = members.length; i < l; i++) {
var member = members[i];
data[member.name] = this.parse(member.value);
}
return data;
} else if (name === 'array') {
var values = res.array.data.value;
if(values.constructor.name !== 'Array') {
values = [values];
}
var data = [];
for (var i = 0, l = values.length; i < l; i++) {
data[i] = this.parse(values[i]);
}
return data;
} else if (name === 'boolean') {
return res[name] === '1';
} else {
return res[name];
}
}
};
exports.Client = Client;