/**
|
* Created by yang on 2015/8/25.
|
*/
|
'use strict';
|
|
var Buffer = require('buffer').Buffer;
|
var _ = require('lodash');
|
var is = require('is-type-of');
|
var helpers = require('./helpers.js');
|
var logger = require('./logger.js');
|
|
var protocol = {};
|
|
module.exports = protocol;
|
|
Object.defineProperties(protocol, {
|
FDFS_PROTO_CMD_QUIT: helpers.buildConstProp(82),
|
TRACKER_PROTO_CMD_SERVER_LIST_GROUP: helpers.buildConstProp(91),
|
TRACKER_PROTO_CMD_SERVER_LIST_STORAGE: helpers.buildConstProp(92),
|
TRACKER_PROTO_CMD_SERVER_DELETE_STORAGE: helpers.buildConstProp(93),
|
|
TRACKER_PROTO_CMD_SERVICE_QUERY_STORE_WITHOUT_GROUP_ONE: helpers.buildConstProp(101),
|
TRACKER_PROTO_CMD_SERVICE_QUERY_FETCH_ONE: helpers.buildConstProp(102),
|
TRACKER_PROTO_CMD_SERVICE_QUERY_UPDATE: helpers.buildConstProp(103),
|
TRACKER_PROTO_CMD_SERVICE_QUERY_STORE_WITH_GROUP_ONE: helpers.buildConstProp(104),
|
TRACKER_PROTO_CMD_SERVICE_QUERY_FETCH_ALL: helpers.buildConstProp(105),
|
TRACKER_PROTO_CMD_SERVICE_QUERY_STORE_WITHOUT_GROUP_ALL: helpers.buildConstProp(106),
|
TRACKER_PROTO_CMD_SERVICE_QUERY_STORE_WITH_GROUP_ALL: helpers.buildConstProp(107),
|
TRACKER_PROTO_CMD_RESP: helpers.buildConstProp(100),
|
FDFS_PROTO_CMD_ACTIVE_TEST: helpers.buildConstProp(111),
|
STORAGE_PROTO_CMD_UPLOAD_FILE: helpers.buildConstProp(11),
|
STORAGE_PROTO_CMD_DELETE_FILE: helpers.buildConstProp(12),
|
STORAGE_PROTO_CMD_SET_METADATA: helpers.buildConstProp(13),
|
STORAGE_PROTO_CMD_DOWNLOAD_FILE: helpers.buildConstProp(14),
|
STORAGE_PROTO_CMD_GET_METADATA: helpers.buildConstProp(15),
|
STORAGE_PROTO_CMD_UPLOAD_SLAVE_FILE: helpers.buildConstProp(21),
|
STORAGE_PROTO_CMD_QUERY_FILE_INFO: helpers.buildConstProp(22),
|
STORAGE_PROTO_CMD_UPLOAD_APPENDER_FILE: helpers.buildConstProp(23), //create appender file
|
STORAGE_PROTO_CMD_APPEND_FILE: helpers.buildConstProp(24), //append file
|
STORAGE_PROTO_CMD_MODIFY_FILE: helpers.buildConstProp(34), //modify appender file
|
STORAGE_PROTO_CMD_TRUNCATE_FILE: helpers.buildConstProp(36), //truncate appender file
|
|
|
FDFS_STORAGE_STATUS_INIT: helpers.buildConstProp(0),
|
FDFS_STORAGE_STATUS_WAIT_SYNC: helpers.buildConstProp(1),
|
FDFS_STORAGE_STATUS_SYNCING: helpers.buildConstProp(2),
|
FDFS_STORAGE_STATUS_IP_CHANGED: helpers.buildConstProp(3),
|
FDFS_STORAGE_STATUS_DELETED: helpers.buildConstProp(4),
|
FDFS_STORAGE_STATUS_OFFLINE: helpers.buildConstProp(5),
|
FDFS_STORAGE_STATUS_ONLINE: helpers.buildConstProp(6),
|
FDFS_STORAGE_STATUS_ACTIVE: helpers.buildConstProp(7),
|
FDFS_STORAGE_STATUS_NONE: helpers.buildConstProp(99),
|
|
/**
|
* for overwrite all old metadata
|
*/
|
STORAGE_SET_METADATA_FLAG_OVERWRITE: helpers.buildConstProp('O'),
|
|
/**
|
* for replace, insert when the meta item not exist, otherwise update it
|
*/
|
STORAGE_SET_METADATA_FLAG_MERGE: helpers.buildConstProp('M'),
|
|
FDFS_PROTO_PKG_LEN_SIZE: helpers.buildConstProp(8),
|
FDFS_PROTO_CMD_SIZE: helpers.buildConstProp(1),
|
FDFS_GROUP_NAME_MAX_LEN: helpers.buildConstProp(16),
|
FDFS_IPADDR_SIZE: helpers.buildConstProp(16),
|
FDFS_DOMAIN_NAME_MAX_SIZE: helpers.buildConstProp(128),
|
FDFS_VERSION_SIZE: helpers.buildConstProp(6),
|
FDFS_STORAGE_ID_MAX_SIZE: helpers.buildConstProp(16),
|
|
FDFS_RECORD_SEPERATOR: helpers.buildConstProp('\u0001'),
|
FDFS_FIELD_SEPERATOR: helpers.buildConstProp('\u0002'),
|
|
FDFS_FILE_EXT_NAME_MAX_LEN: helpers.buildConstProp(6),
|
FDFS_FILE_PREFIX_MAX_LEN: helpers.buildConstProp(16),
|
FDFS_FILE_PATH_LEN: helpers.buildConstProp(10),
|
FDFS_FILENAME_BASE64_LENGTH: helpers.buildConstProp(27),
|
FDFS_TRUNK_FILE_INFO_LEN: helpers.buildConstProp(16),
|
|
ERR_NO_ENOENT: helpers.buildConstProp(2),
|
ERR_NO_EIO: helpers.buildConstProp(5),
|
ERR_NO_EBUSY: helpers.buildConstProp(16),
|
ERR_NO_EINVAL: helpers.buildConstProp(22),
|
ERR_NO_ENOSPC: helpers.buildConstProp(28),
|
ECONNREFUSED: helpers.buildConstProp(61),
|
ERR_NO_EALREADY: helpers.buildConstProp(114),
|
|
// 成功的STATUS
|
HEADER_STATUS_SUCCESS: helpers.buildConstProp(0)
|
});
|
|
Object.defineProperties(protocol, {
|
STORAGE_PROTO_CMD_RESP: helpers.buildConstProp(protocol.TRACKER_PROTO_CMD_RESP),
|
|
TRACKER_QUERY_STORAGE_FETCH_BODY_LEN: helpers.buildConstProp(protocol.FDFS_GROUP_NAME_MAX_LEN + protocol.FDFS_IPADDR_SIZE - 1 + protocol.FDFS_PROTO_PKG_LEN_SIZE),
|
TRACKER_QUERY_STORAGE_STORE_BODY_LEN: helpers.buildConstProp(protocol.FDFS_GROUP_NAME_MAX_LEN + protocol.FDFS_IPADDR_SIZE + protocol.FDFS_PROTO_PKG_LEN_SIZE),
|
|
PROTO_HEADER_CMD_INDEX: helpers.buildConstProp(protocol.FDFS_PROTO_PKG_LEN_SIZE),
|
PROTO_HEADER_STATUS_INDEX: helpers.buildConstProp(protocol.FDFS_PROTO_PKG_LEN_SIZE + 1),
|
|
HEADER_BYTE_LENGTH: helpers.buildConstProp(protocol.FDFS_PROTO_PKG_LEN_SIZE + 2)
|
});
|
|
// 协议相关的封装方法
|
_.extend(protocol, {
|
|
FDFS_METHOD_UPLOAD_FILE: 'upload',
|
FDFS_METHOD_UPLOAD_APPENDER_FILE: 'uploadAppender',
|
FDFS_METHOD_APPEND_FILE: 'append',
|
FDFS_METHOD_MODIFY_FILE: 'modify',
|
|
/**
|
* 封装协议头
|
* @param command
|
* @param bodyLength
|
* @param status
|
* @return {Buffer}
|
*/
|
packHeader: function(command, bodyLength, status) {
|
if (!bodyLength) {
|
bodyLength = 0;
|
}
|
|
if (!status) {
|
status = 0;
|
}
|
|
// ----------- 存放1字节的command和1字节的status
|
var buffer = new Buffer(2);
|
buffer.writeUInt8(command, 0);
|
buffer.writeUInt8(status, 1);
|
|
// 生成8bytes存放body length的buffer
|
var blBuffer = helpers.number2Buffer(bodyLength, 8);
|
|
// 拼接
|
return Buffer.concat([blBuffer, buffer]);
|
},
|
|
/**
|
* 解析返回的包
|
* @param socket
|
* @param expectedCommand
|
* @param expectedBodyLength
|
* @param callback
|
* @param headerOnly 是否指parse header,下载文件时,解析完header后需将data传递给callback,而非将body接收完
|
*/
|
recvPacket: function(socket, expectedCommand, expectedBodyLength, callback, headerOnly) {
|
var oriCallback = callback;
|
// 收包完毕则关闭掉连接
|
callback = function() {
|
cleanup();
|
if (is.function(oriCallback)) {
|
oriCallback.apply(null, arguments);
|
}
|
} ;
|
var headerBufferLen = protocol.HEADER_BYTE_LENGTH;
|
var headerBuffer = new Buffer(headerBufferLen);
|
// 已填充的length
|
var headerBufferFilled = 0;
|
// 解析后的header信息
|
// {status: , bodyLength: }
|
var header;
|
var bodyBuffer;
|
// 下1次要copy到bodyBuffer中的起始位置
|
var bodyBufferStart = 0;
|
|
socket.on('data', listener);
|
|
function listener(data) {
|
// --------------- 收到服务器发来消息
|
// -------- 解析header
|
if (!header) {
|
// header parsed
|
if (headerBufferFilled + data.length >= headerBufferLen) {
|
var len = headerBufferFilled + data.length;
|
// 只copy剩下的header部分
|
data.copy(headerBuffer, headerBufferFilled, 0, headerBufferLen - headerBufferFilled);
|
try {
|
header = _parseHeader(headerBuffer, expectedCommand, expectedBodyLength);
|
logger.debug('receive server packet header: %j', header);
|
// 无body时直接返回
|
if (header.bodyLength === 0) {
|
callback(null, header);
|
return;
|
}
|
|
if (headerOnly) {
|
oriCallback(null, header);
|
if (len > headerBufferLen) {
|
oriCallback(null, data.slice(headerBufferLen - headerBufferFilled));
|
}
|
return;
|
}
|
|
bodyBuffer = new Buffer(header.bodyLength);
|
|
// 还有body的数据则填充到body buffer中
|
if (len > headerBufferLen) {
|
data.copy(bodyBuffer, 0, headerBufferLen - headerBufferFilled);
|
bodyBufferStart = len - headerBufferLen;
|
// 读取完毕
|
if (bodyBufferStart >= header.bodyLength) {
|
callback(null, bodyBuffer);
|
}
|
}
|
|
} catch (err) {
|
callback(err);
|
}
|
|
} else {
|
data.copy(headerBuffer, headerBufferFilled);
|
headerBufferFilled += data.length;
|
}
|
|
} else {
|
// 交由外部处理
|
if (headerOnly) {
|
oriCallback(null, data);
|
return;
|
}
|
// ---------- 解析body
|
data.copy(bodyBuffer, bodyBufferStart);
|
bodyBufferStart += data.length;
|
// 读取完毕
|
if (bodyBufferStart >= header.bodyLength) {
|
callback(null, bodyBuffer);
|
}
|
}
|
};
|
|
function cleanup() {
|
socket.removeListener('data', listener);
|
// 关闭连接
|
protocol.closeSocket(socket);
|
};
|
},
|
|
/**
|
* 给服务器发送关闭指令,同时end socket
|
* @param socket
|
*/
|
closeSocket: function(socket) {
|
socket.end(protocol.packHeader(protocol.FDFS_PROTO_CMD_QUIT, 0, 0));
|
},
|
|
/**
|
* 封装只有fileId的包,下载和删除文件时使用
|
* @param command
|
* @param group
|
* @param filename
|
* @param charset
|
*/
|
packFileId: function(command, group, filename, charset) {
|
// --------- 封装header
|
var fnLength = Buffer.byteLength(filename, charset);
|
var bodyLength = protocol.FDFS_GROUP_NAME_MAX_LEN + fnLength;
|
var header = protocol.packHeader(command, bodyLength, 0);
|
|
// --------- 封装body
|
var body = new Buffer(bodyLength);
|
// 默认都填充上0
|
body.fill(0);
|
var groupBL = Buffer.byteLength(group, charset);
|
body.write(group, 0, groupBL, charset);
|
body.write(filename, protocol.FDFS_GROUP_NAME_MAX_LEN, fnLength, charset);
|
|
return Buffer.concat([header, body]);
|
},
|
|
/**
|
* 封装meta data
|
* 注:返回的是string,由外部写入到buffer中
|
* @param {Object} metaData
|
* @return {String}
|
*/
|
packMetaData: function(metaData) {
|
var first = true;
|
var result = '';
|
|
Object.keys(metaData).forEach(function(key) {
|
if (!first) {
|
result += protocol.FDFS_RECORD_SEPERATOR;
|
|
} else {
|
first = false;
|
}
|
var value = metaData[key];
|
result += key;
|
result += protocol.FDFS_FIELD_SEPERATOR;
|
result += value;
|
});
|
|
return result;
|
},
|
|
/**
|
* raw meta data to structure
|
* @param raw
|
*/
|
parseMetaData: function(raw) {
|
var result = {};
|
var md = raw.split(protocol.FDFS_RECORD_SEPERATOR);
|
md.forEach(function(item) {
|
var arr = item.split(protocol.FDFS_FIELD_SEPERATOR);
|
var key = helpers.trim(arr[0]);
|
var value = helpers.trim(arr[1]);
|
result[key] = value;
|
});
|
|
return result;
|
}
|
});
|
|
function _parseHeader(headerBuffer, expectedCommand, expectedBodyLength) {
|
// validate buffer length
|
if (headerBuffer.length !== protocol.FDFS_PROTO_PKG_LEN_SIZE + 2) {
|
throw new Error('receive packet size ' + headerBuffer.length + ' is not equal to the expected header size: ' + protocol.FDFS_PROTO_PKG_LEN_SIZE + 2);
|
}
|
|
// validate command
|
var command = Number('0x' + headerBuffer.toString('hex', protocol.PROTO_HEADER_CMD_INDEX, protocol.PROTO_HEADER_CMD_INDEX + 1));
|
if (expectedCommand !== command) {
|
throw new Error('receive command: ' + command + ' is not equal to the expected command: ' + expectedCommand);
|
}
|
|
// 应用层错误
|
var status = Number('0x' + headerBuffer.toString('hex', protocol.PROTO_HEADER_STATUS_INDEX, protocol.PROTO_HEADER_STATUS_INDEX + 1));
|
if (status !== protocol.HEADER_STATUS_SUCCESS) {
|
var err = new Error('receive packet errno is: ' + status);
|
err.code = err.errno = status;
|
throw err;
|
}
|
|
// validate body length
|
var bodyLength = helpers.buffer2Number(headerBuffer, 0);
|
if (expectedBodyLength != null && expectedBodyLength !== bodyLength) {
|
throw new Error('receive packet body length: ' + bodyLength + ' is not equal to the expected: ' + expectedBodyLength);
|
}
|
|
return {
|
status: protocol.HEADER_STATUS_SUCCESS,
|
bodyLength: bodyLength
|
};
|
}
|