import { _decorator, Component, Enum, EventHandler } from 'cc'; import { Log } from '../utils/Log'; import { RC4 } from '../utils/RC4'; import { HttpSystem, HostData, ErrorDataType } from './HttpSystem'; import { HttpRequest } from './HttpRequest'; const { ccclass, property, executionOrder } = _decorator; /** * Http请求方法 */ export enum HttpMethod { GET = "get", POST = "post" } /** * Http请求的数据类型 */ export enum ResponseType { Arraybuffer, Blob, Document, Json, Text } export enum HttpResponseCode { /** * 成功 */ Success, /** * 没有登录 */ NoLogin, /** * 未知错误 */ UnKnowError, /** * 密钥错误 */ KeyError } /** * Http组件 * @description 建议使用@requireComponent(Http)、this.getComponent(Http)来配合使用Http组件 * @description post模式暂时还不支持消息加密 * @author 袁浩 */ @ccclass('Http') @executionOrder(-1) export class Http extends Component { @property({ tooltip: "HttpConfig中配置的服务端别名,不填则取HttpConfig中的默认值" }) public hostAlias: string = ""; @property({ tooltip: "请求是否应当带有授权信息,如cookie或授权header头" }) public withCredentials: boolean = false; @property({ tooltip: "该请求的超时时间(毫秒),0为20秒", step: 1, min: 0 }) public timeout: number = 0; @property({ type: Enum(ResponseType), tooltip: "返回的数据格式" }) public responseType: ResponseType = ResponseType.Json; @property({ type: EventHandler, tooltip: "加载的进度回调函数" }) public onProgress: EventHandler; @property({ tooltip: "消息加密" }) public useEncrypt: boolean = true; @property({ tooltip: "是否打印消息" }) public useLog: boolean = true; /** * 收到数据后的公共回调,返回值为是否执行后续调用 */ public static onData: (data: any) => boolean = () => { return true; } /** * 密钥错误 */ public static onKeyError: () => void = () => { } private get config(): HostData { return HttpSystem.getConfig(this.hostAlias); } public getRootURL(): string { var port = `:${this.config.port}`; if (this.config.SSL && this.config.port == 443 || !this.config.SSL && this.config.port == 80) { port = ''; } return `${this.config.SSL ? "https://" : "http://"}${this.config.host}${port}`; } private headerObj: any; /** * 给指定的HTTP请求头赋值.在这之前,您必须确认已经调用 open() 方法打开了一个url. * @param header 将要被赋值的请求头名称. * @param value 给指定的请求头赋的值. */ public setRequestHeader(header: string, value: string): void { if (!this.headerObj) { this.headerObj = {}; } this.headerObj[header] = value; } /** * 所有的请求数据 */ protected requestMap: any = {}; /** * 发送请求 * @param path 路径 * @param data 数据 * @param method 方法 * @returns */ public async send(path: string, data: any = {}, method: HttpMethod = HttpMethod.GET, useEncrypt?: boolean, useTail = true) { let result = await this._send(path, data, method, useEncrypt, useTail); if (result && result.code == HttpResponseCode.KeyError) { HttpSystem.resetPublicKey(); result = await this._send(path, data, method, useEncrypt, useTail); if (result && result.code == HttpResponseCode.KeyError) {//续签密钥失败 Http.onKeyError(); return { code: HttpResponseCode.KeyError, data: HttpSystem.getErrorDataType() != ErrorDataType.Null ? {} : null }; } } return result; } private _send(path: string, data: any = {}, method: HttpMethod = HttpMethod.GET, useEncrypt?: boolean, useTail = true) { return new Promise((resolve: (data: any) => void, reject) => { if (useEncrypt === undefined) { useEncrypt = this.useEncrypt; } var self = this; var request = new HttpRequest((url: string) => { if (!this.isValid) { return; } console.error(`http请求[${url}]失败`); delete self.requestMap[url]; var errorDataType = HttpSystem.getErrorDataType(); if (errorDataType == ErrorDataType.Throw) { reject(); } else { resolve(errorDataType == ErrorDataType.Null ? null : {}); } }, (httpRequest: HttpRequest) => { if (!this.isValid) { return; } var errorDataType = HttpSystem.getErrorDataType(); var response = httpRequest.response; if (!response && errorDataType == ErrorDataType.Object) { response = {}; } if (useEncrypt) { response = HttpSystem.decrypt(response); self.responseType == ResponseType.Json && (response = JSON.parse(response)); } let url = httpRequest.url.substring(0, httpRequest.url.indexOf('?')); this.useLog && HttpSystem.useDebugLog && Log.info(`http请求[${url}]成功,数据为${self.responseType == ResponseType.Json ? JSON.stringify(response) : response}`); Http.onData(response) && resolve(response); delete self.requestMap[httpRequest.url]; }, (loaded: number, total: number): void => { self.onProgress.emit([loaded, total]); }); request.withCredentials = this.withCredentials; request.timeout = this.timeout || 20000; switch (this.responseType) { case ResponseType.Json: request.responseType = "json"; break; case ResponseType.Arraybuffer: request.responseType = "arraybuffer"; break; case ResponseType.Blob: request.responseType = "blob"; break; case ResponseType.Document: request.responseType = "document"; break; case ResponseType.Text: request.responseType = "text"; break; default: request.responseType = ""; break; } if (useEncrypt) {//如果消息加密 data = data || {}; data.timestamp = HttpSystem.serverTime; } let getData: string = typeof data == 'object' ? HttpSystem.dataToString(data) : data; let postData = method == HttpMethod.GET ? null : (typeof data == 'object' ? JSON.stringify(data) : data); let url: string = this.getRootURL() + (method == HttpMethod.GET ? path + "?" + getData : path); this.useLog && HttpSystem.useDebugLog && Log.info(`http请求[${url}]发送`); if (useEncrypt) {//如果消息加密 if (method == HttpMethod.GET) { getData = HttpSystem.encrypt(getData, method); } else { postData = HttpSystem.encrypt(postData, method); } } useTail && (getData += HttpSystem.tailString); url = this.getRootURL() + (method == HttpMethod.GET ? path + "?" + getData : path); if (this.headerObj) { for (let key in this.headerObj) { request.setRequestHeader(key, this.headerObj[key]); } } self.requestMap[url] = request; request.open(url, method); request.send(postData); }); } /** * 撤销http请求 * 在onDestroy中记得调用,防止页面关闭了,请求还在执行导致产生一系列错误和内存泄漏(需要详细测试,可能并不需要如此) * @param path {string} http请求的地址 * @param data {any} 请求所携带的数据 */ public cancel(path: string): void { for (var key in this.requestMap) { if (key.indexOf(this.getRootURL() + path) == 0) { var request: XMLHttpRequest = this.requestMap[key]; request.abort(); delete this.requestMap[key]; } } } /** * 撤销所有http请求 */ public cancelAll(): void { for (var key in this.requestMap) { var request: XMLHttpRequest = this.requestMap[key]; request.abort(); } this.requestMap = {}; } public onDestroy() { this.cancelAll(); } }