/************************************************************************ * Copyright (c) 2023 sunking framework * Author : Shao * Mail : yi-shaoye@163.com * Date : 2023-01-13 * Use : http网络模块-客户端对象 ************************************************************************/ import { IncomingMessage, ServerResponse } from "http"; import { BaseSocket } from '../base/BaseSocket'; import { BaseConnector } from "../base/BaseConnector"; import querystring from 'querystring'; import myLogger from '@wingyi8/sk-logger'; import { HttpConnector } from "./HttpConnector"; import { SocketStatus } from "../util/define"; const logger = myLogger.getLogger('sunking/http/HttpSocket'); export class HttpSocket extends BaseSocket { /** * 客户端请求对象 */ readonly httpReq: IncomingMessage & { rawBody?: Buffer }; /** * 服务器响应对象 */ readonly httpRes: ServerResponse; /** * 请求链接 */ private url: string = ''; constructor(id: number, connector: BaseConnector, httpReq: IncomingMessage, httpRes: ServerResponse) { super(); this.connector = connector; this.sid = id; this.httpReq = httpReq; this.httpRes = httpRes; this.clientip = this.getClientIp(httpReq); this.httpRes.statusCode = 200; // 指定允许其他域名访问 this.httpRes.setHeader("Access-Control-Allow-Origin", "*"); this.httpRes.setHeader('Access-Control-Allow-Headers', 'Content-Type,*'); if (this.httpReq.method === 'OPTIONS') { this.httpRes.writeHead(200); this.httpRes.end(); return; } // 处理连接异常关闭的情况 this.httpRes.on('close', async () => { let isManual: boolean; let reason: string | undefined; // 异常断开:客户端 Abort if (this.httpReq.destroyed) { isManual = false; reason = 'Remote aborted'; } // 异常断开:Client 未正常 end else if (!this.httpReq.readableEnded) { isManual = false; reason = 'Socket closed before request end normally'; } // 异常断开:直到连接关闭,也未调用过 httpRes.end 方法 else if (!this.httpRes.writableEnded) { isManual = false; reason = 'Socket closed without any response'; } // 正常断开 else { isManual = true; } // this._disconnect(isManual, reason); logger.info(`客户端[ ${this.clientip} ] [ ${this.sid} ] 网络连接断开`); this.connector.emit('disconnect', this); }); logger.info(`客户端[${this.clientip}] [${this.sid}] 网络连接成功`); switch (this.httpReq.method) { case 'GET': { let str = this.httpReq.url; if (str.indexOf('?') > -1) { let arr = String.prototype.split.call(str, '?'); this.url = arr[0]; if (this.checkMethod()) { this.onData(Buffer.from(JSON.stringify(querystring.parse(arr[1])))); } } else { this.url = str; if (this.checkMethod()) { this.onData(Buffer.from('{}')); } } } break; case 'POST': { this.url = this.httpReq.url; if (this.checkMethod()) { // 等待数据 let chunks: Buffer[] = []; this.httpReq.on('data', (data: Buffer) => { chunks.push(data); }); this.httpReq.on('end', async () => { const buf = chunks.length === 1 ? chunks[0] : Buffer.concat(chunks); let header = this.httpReq.headers['content-type']; this.onData(buf); }); } } break; default: this.closed(); break; } } /** * 获取客户端IP */ protected getClientIp(httpReq: IncomingMessage): string { let ipAddress = httpReq.socket.remoteAddress; // Remove prefix ::ffff: return ipAddress ? ipAddress.replace(/^::ffff:/, '') : ''; } /** * 获取通讯url */ public getUrl(): string { return this.url; } /** * 检测路由注册方法 */ private checkMethod(): boolean { if ((this.connector as HttpConnector).checkMethod(this.httpReq.method, this.url)) { return true; } this.closed(); return false; } /** * 消息解析 * @param data 数据包 */ private onData = (data: Buffer) => { this.connector.handleMsg(this, data); } /** * 发送消息 * @param protocol 名字 * @param packet 数据包 */ public sendMessage(packet: any, protocol?: string): void { logger.info(`发送到客户端数据[ ${protocol} ][ ${JSON.stringify(packet)} ] [ ${this.uid} ]`); this.send(this.connector.protoEncode(protocol, packet)); } /** * 广播消息 * @param protocol 协议 * @param packet 数据包 */ public sendToAll(packet: any, protocol: string): void { } /** * 断开连接 * @param code 错误码 * @param message 错误信息 */ public closed(code?: number, message?: string): void { this._status = SocketStatus.ST_CLOSED; this.httpRes.statusCode = 500; this.httpRes.end(`Error ${this.getUrl()}`); } /** * 发包 * @param msg 消息 */ public send(msg: Buffer): void { this.httpRes.end(msg); } }