c4restfulclient
Version:
C4Framework RESTFulClient
419 lines (383 loc) • 17.2 kB
text/typescript
import Request = require("request");
import Content_Type = require('content-type');
import Content_Disposition = require('content-disposition');
import C4RESTFulParser from "./C4RESTFulParser";
import {ClientOption, RequestOption } from './C4RESTFulTypes';
import { TypeUtils, PathUtils, FSP } from 'c4utils';
import FS = require('fs');
import C4DefaultTextParser from "./C4DefaultRESTFulParser/C4DefaultTextParser";
import C4DefaultJSONParser from "./C4DefaultRESTFulParser/C4DefaultJSONParser";
import C4DefaultXMLParser from "./C4DefaultRESTFulParser/C4DefaultXMLParser";
import C4DefaultFileStreamParser from "./C4DefaultRESTFulParser/C4DefaultFileStreamParser";
var CloneDeep = require('clone-deep');
export default class C4RESTFulClient {
private m_DefaultOption : Request.Options;
private m_Logger : any;
private m_Jar : Request.CookieJar | null;
private m_ContentTypeParsers: { [key : string] : C4RESTFulParser };
private m_ContentDispositionParsers : { [key : string] : C4RESTFulParser };
private m_DownloadPath : string;
constructor(logger ?: any) {
this.m_DefaultOption = {
url : ""
};
this.m_Logger = logger;
this.m_Jar = null;
this.m_ContentTypeParsers = {};
this.m_ContentDispositionParsers = {};
this.m_DownloadPath = "";
}
/**
* 初始化
* @param option ClientOption
*/
async init(option ?: ClientOption) {
if (option) {
if (option.baseURL) {
this.m_DefaultOption.baseUrl = option.baseURL;
}
if (option.sslOption) {
this.m_DefaultOption.cert = option.sslOption.cert;
this.m_DefaultOption.key = option.sslOption.key;
if (option.sslOption.passphrase) {
this.m_DefaultOption.passphrase = option.sslOption.passphrase;
}
if (option.sslOption.ca) {
this.m_DefaultOption.ca = option.sslOption.ca
}
}
if (TypeUtils.isNumber(option.timeout)) {
this.m_DefaultOption.timeout = option.timeout;
}
if (TypeUtils.isBoolean(option.gzip)) {
this.m_DefaultOption.gzip = option.gzip;
}
if (option.cookiesOption) {
if (option.cookiesOption.enabled) {
if (option.cookiesOption.store) {
this.m_Jar = Request.jar(option.cookiesOption.store);
} else {
this.m_Jar = Request.jar();
}
}
}
this.m_DefaultOption.rejectUnauthorized = option.rejectUnauthorized === undefined ? false : option.rejectUnauthorized;
if (option.downloadPath) {
this.m_DownloadPath = await PathUtils.GetAbsolutePath(option.downloadPath);
}
if (this.m_DownloadPath === "")
this.m_DownloadPath = await PathUtils.GetAbsolutePath("./");
let IsInside = PathUtils.PathInside(process.cwd(), this.m_DownloadPath);
if (IsInside === false) {
throw new Error(`C4RESTFulClient set download path ${this.m_DownloadPath}, not in process run path.`);
}
await FSP.Access(this.m_DownloadPath, FS.constants.F_OK | FS.constants.W_OK).catch((err) => {
if (this.m_Logger) {
this.m_Logger.err ? this.m_Logger.err(err) : this.m_Logger.error(err);
throw err;
}
});
}
// 添加默认的Content-type的处理方法
let Self = this;
this.addParser(new C4DefaultTextParser(this.m_Logger)); // 默认的text/html, text/xml解析器
this.addParser(new C4DefaultJSONParser(this.m_Logger)); // 默认的application/json解析器
this.addParser(new C4DefaultXMLParser(this.m_Logger)); // 默认的application/xml解析器
this.addParser(new C4DefaultFileStreamParser(this.m_Logger, this.m_DownloadPath)); // 默认的content-disposition解析器
}
/**
* 增加Parser
* @param parser C4RESTFulParser
*/
addParser(parser : C4RESTFulParser) {
if (parser.contentTypes) {
for (let i = 0; i < parser.contentTypes.length; i++) {
this.m_ContentTypeParsers[parser.contentTypes[i]] = parser;
}
}
if (parser.contentDispositionTypes) {
for (let i = 0; i < parser.contentDispositionTypes.length; i++) {
this.m_ContentDispositionParsers[parser.contentDispositionTypes[i]] = parser;
}
}
}
/**
* 移除Parser
* @param name 要移除的Parser的名字
*/
removeParser(name : string) {
let keys = Object.keys(this.m_ContentTypeParsers);
for (let i = 0; i < keys.length; i++) {
if (this.m_ContentTypeParsers[keys[i]].name === name) {
delete this.m_ContentTypeParsers[keys[i]]
}
}
keys = Object.keys(this.m_ContentDispositionParsers);
for (let i = 0; i < keys.length; i++) {
if (this.m_ContentDispositionParsers[keys[i]].name === name) {
delete this.m_ContentDispositionParsers[keys[i]]
}
}
}
/**
* 请求
* @param url 请求的URL
* @param method 请求的METHOD
* @param option RequestOption
*/
request(url : string, method : string, option : RequestOption) : Promise<Request.Response> {
// NOTE: 不传递Callback,数据由外部处理
// 先将multipart、formData、form从option中移除
// 因为包含stream的部分CloneDeep会出问题
// 后面为了保证输入参数包含的内容不变,在配置完毕后会设置回来
let CurMultipart = option.multipart;
let CurFormData = option.formData;
let CurForm = option.form;
if (CurForm) option.form = undefined; // 不要使用delete,可能有性能问题
if (CurFormData) option.formData = undefined; // 不要使用delete,可能有性能问题
if (CurMultipart) option.multipart= undefined; // 不要使用delete,可能有性能问题
// 复制一下
let RequestOption : Request.CoreOptions = CloneDeep(option);
// 优先级Multipart > FormData > Form
RequestOption.method = method;
if (CurMultipart) {
RequestOption.multipart = CurMultipart;
} else if (CurFormData) {
RequestOption.formData = CurFormData;
} else if (CurForm) {
RequestOption.form = CurForm;
}
// 恢复输入参数
if (CurMultipart) option.multipart= CurMultipart;
if (CurFormData) option.formData = CurFormData;
if (CurForm) option.form = CurForm;
if (option.cookiesOption) {
if (option.cookiesOption.enabled) {
if (option.cookiesOption.store) {
RequestOption.jar = Request.jar(option.cookiesOption.store);
} else {
if (this.m_Jar) {
RequestOption.jar = this.m_Jar
} else {
RequestOption.jar = Request.jar();
}
}
}
// 实际的配置中是不会出现cookiesOption的
delete (<any>RequestOption).cookiesOption
} else {
// 使用默认的Cookie配置
if (this.m_Jar)
RequestOption.jar = this.m_Jar;
}
let downloadFileName = "";
if (option.downloadFileName) {
downloadFileName = option.downloadFileName;
delete (<any>RequestOption).downloadFileName;
}
let Req = Request(url, RequestOption);
let Self = this;
let MIMEType = "";
let Charset = "";
let Parser : C4RESTFulParser | null = null;
let ContentType : Content_Type.ParsedMediaType = <Content_Type.ParsedMediaType>{};
let ContentDisposition : Content_Disposition.ContentDisposition = <Content_Disposition.ContentDisposition>{};
let Rep : Request.Response;
let Data : Buffer = new Buffer(0);
let StrData : string = "";
return new Promise((resolve, reject) => {
Req.on('response', (rep : Request.Response) => {
// 接收到response,还没有开始传输数据
Rep = rep;
if (rep.headers) {
if ((<any>rep.headers)['content-type']) {
try {
ContentType = Content_Type.parse((<any>rep.headers)['content-type']);
// MIMEType
if (ContentType.type) {
MIMEType = ContentType.type;
let CurParser = Self.m_ContentTypeParsers[MIMEType];
if (CurParser) {
Parser = CurParser;
if (Parser.isStream) {
if (Parser.beforeStream) {
Parser.beforeStream(rep, ContentType, downloadFileName);
}
}
}
}
// Charset
if (ContentType.parameters && ContentType.parameters.charset) {
Charset = ContentType.parameters.charset;
}
} catch (error) {
if (Self.m_Logger) {
Self.m_Logger.err ? Self.m_Logger.err(error) : Self.m_Logger.error(error);
}
rep.destroy(error);
return;
}
}
if ((<any>rep.headers)['content-disposition']) {
try {
ContentDisposition = Content_Disposition.parse((<any>rep.headers)['content-disposition']);
let CurParser = Self.m_ContentDispositionParsers[ContentDisposition.type];
if (CurParser) {
Parser = CurParser;
if (Parser.isStream) {
if (Parser.beforeStream)
Parser.beforeStream(rep, ContentDisposition, downloadFileName);
}
}
} catch (error) {
if (Self.m_Logger) {
Self.m_Logger.err ? Self.m_Logger.err(error) : Self.m_Logger.error(error);
}
rep.destroy(error);
return;
}
}
}
});
Req.on('data', (data : Buffer) => {
// 如果是压缩过,这里的数据已经解压缩了
// 但是没有进行编码转换
if (Parser && Parser.isStream) {
try {
// 需要进行流式解析的解析器需要在接收到data后进行处理
Parser.parse(Rep, data, ContentDisposition, (result : any) => {
if (null !== result) {
if (Buffer.isBuffer(result)) {
Data = Buffer.concat([Data, result], Data.length + result.length)
} else if (TypeUtils.isString(result)) {
StrData += result;
}
}
});
} catch (error) {
if (Self.m_Logger) {
Self.m_Logger.err ? Self.m_Logger.err(error) : Self.m_Logger.error(error);
}
if (Rep)
Rep.destroy(error);
}
} else {
if (data) {
Data = Buffer.concat([Data, data], Data.length + data.length);
}
}
// 可以做数据流式输出,并限速
});
Req.on('end', (data : Buffer) => {
//
if (Parser) {
let CurData = data;
if (!Parser.isStream) {
if (data) {
Data = Buffer.concat([Data, data], Data.length + data.length);
}
CurData = Data;
}
try {
let CurContentInfo = ContentDisposition;
if (TypeUtils.isEmptyObj(ContentDisposition)) {
CurContentInfo = ContentType;
}
// 非流式处理在end时进行全部数据的处理,流式处理在这里处理最后剩余的数据
Parser.parse(Rep, CurData, CurContentInfo, (result : any) => {
if (null !== result) {
if (Buffer.isBuffer(result)) {
Data = Buffer.concat([Data, result], Data.length + result.length);
if (Rep) Rep.body = Data;
} else if (TypeUtils.isString(result)) {
StrData += result;
if (Rep) Rep.body = StrData;
} else {
if (Rep) Rep.body = result;
}
}
});
} catch (error) {
if (Self.m_Logger) {
Self.m_Logger.err ? Self.m_Logger.err(error) : Self.m_Logger.error(error);
}
if (Rep)
Rep.destroy(error);
}
// 流式处理的扫尾工作
if (Parser.isStream) {
if (Parser.afterStream) {
try {
Parser.afterStream(Rep, ContentDisposition);
} catch (error) {
if (Self.m_Logger) {
Self.m_Logger.err ? Self.m_Logger.err(error) : Self.m_Logger.error(error);
}
if (Rep)
Rep.destroy(error);
}
}
}
} else {
// 没有对应的解析器,直接输出Buffer
if (data)
Data = Buffer.concat([Data, data], Data.length + data.length);
if (Rep) Rep.body = Data;
}
});
Req.on('error', (err : Error) => {
//
reject(err);
})
Req.on('complete', (rep : Request.Response, body ?: string | Buffer) => {
// 完整的body,如果encoding设置为null,这里的body为Buffer,否则是string
resolve(rep);
})
return Req;
});
}
/**
* GET Method
* @param url 请求的URL
* @param option RequestOption
*/
get(url : string, option : RequestOption) {
return this.request(url, "get", option);
}
/**
* post Method
* @param url 请求的URL
* @param option RequestOption
*/
post(url : string, option : RequestOption) {
//
return this.request(url, "post", option);
}
/**
* put Method
* @param url 请求的URL
* @param option RequestOption
*/
put(url : string, option : RequestOption) {
//
return this.request(url, "put", option);
}
/**
* patch Method
* @param url 请求的URL
* @param option RequestOption
*/
patch(url : string, option : RequestOption) {
//
return this.request(url, "patch", option);
}
/**
* delete Method
* @param url 请求的URL
* @param option RequestOption
*/
delete(url : string, option : RequestOption) {
//
return this.request(url, "delete", option);
}
}