/// import {STATUS_CODES} from "http"; import {HttpIO} from "./http.server"; import {HttpFunction, HttpRouterMap} from "./http.router"; const CrossAbleMethods = ["options", "post"]; function crossAble(method) { return CrossAbleMethods.indexOf(method) >= 0; } export class HttpNavController { protected static instance: HttpNavController; static getInstance() { if (!HttpNavController.instance) HttpNavController.instance = new HttpNavController(); return HttpNavController.instance; } nav(routerMap: HttpRouterMap, io: HttpIO): Promise { return new Promise((resolve, reject) => { const endpoints = io.request.url.split("/"); // Make method to lower case const method = io.request.method.toLowerCase(); let getData: string; // Shift empty endpoints.shift(); // Check if url has params if (endpoints[endpoints.length].indexOf("?") >= 0) { getData = endpoints.pop().substring(1); } const result = this.matchingFn(routerMap, endpoints, method); if (result && result.fn) { const fn = result.fn; const params = this.mergeParams([ this.parseGetData(getData), this.parsePostData((io.request).data), result.urlParams, ]); const response = fn.instance[fn.port](params, io.request.headers); const headers = HttpResponseHeader.create(response.headers); if (crossAble(method)) headers.crossAble(); // 检测类型,并序列化数据 const data = headers.detectAndSerialize(response.data || response); io.response.writeHead(headers.code, headers.content); io.response.write(data); io.response.end(); resolve(); } else { // 返回 404 io.response.writeHead(404); io.response.end(STATUS_CODES[404]); reject(STATUS_CODES[404]); } }); } // 匹配最契合的接口 protected matchingFn(routerMap: HttpRouterMap, endpoints: string[], method: string): { urlParams?: string[], fn?: HttpFunction } { const urlParams = []; while (true) { const endpoint = "/" + endpoints.join("/"); const map = routerMap[endpoint]; if (map) { const fnc = map[method]; // 如果有参数的话 let params: any; if (urlParams.length > 0) { urlParams.reverse(); // 匹配 url params params = {}; fnc.params.forEach((key, index) => { params[key] = urlParams[index]; }); } return { urlParams: params, fn: fnc.fn, }; } else { urlParams.push(endpoints.pop()); if (endpoints.length === 0) return; } } } // make name=xxx & age=10 => {name:xxx, age: 10} protected parseGetData(data: string) { if (!data) return; data = decodeURIComponent(data.replace("/?", "")); const paramsArray = data.split("&"); const params: any = {}; paramsArray.forEach(param => { const key_value = param.split("="); params[key_value[0]] = key_value[1]; }); return params; } protected parsePostData(data: string) { if (!data) return; let params: any; try { params = JSON.parse(data); } catch (e) { params = this.parseGetData(data); } return params; } protected mergeParams(paramsList: any[]) { const masterParams: any = {}; paramsList.forEach(params => Object.assign(masterParams, params)); return masterParams; } } export class HttpResponseHeader { static create(headers?: any) { return new HttpResponseHeader(headers); } code = 200; protected constructor(protected headers?: any) { headers = Object.assign({ "Content-Type": ["charset=utf-8"], "X-Powered-By": "Kitchenet 0.0.1" }, headers); if (headers.code) { this.code = headers.code; delete headers.code; } this.headers = headers; } get content() { return this.headers; } detectAndSerialize(data: any) { const contentType = this.headers['content-type']; switch (typeof data) { case "object": try { // if can be json data = JSON.stringify(data); contentType.push("application/json"); } catch (e) { // if not } break; case "string": case "undefined": case "function": case "number": case "boolean": case "symbol": default: data = data.toString(); } return data; } crossAble() { this.headers = Object.assign(this.headers, { "Access-Control-Allow-Origin": ["*"], "Access-Control-Allow-Headers": ["*"], "Access-Control-Allow-Methods": ["*"], }); } }