From 217ac902ed797729949f1ae84d183d23481c82f2 Mon Sep 17 00:00:00 2001 From: lqqyt2423 <974923609@qq.com> Date: Sat, 25 Jun 2022 18:41:00 +0800 Subject: [PATCH] web code refactoring --- web/client/src/App.tsx | 13 +- web/client/src/components/EditFlow.tsx | 7 +- web/client/src/components/FlowPreview.tsx | 4 +- web/client/src/components/ViewFlow.tsx | 5 +- web/client/src/flow.ts | 80 -------- web/client/src/lib/connection.ts | 33 ++++ web/client/src/{message.ts => lib/flow.ts} | 206 +++++++-------------- web/client/src/lib/message.ts | 133 +++++++++++++ web/client/src/{ => lib}/utils.ts | 2 +- 9 files changed, 252 insertions(+), 231 deletions(-) delete mode 100644 web/client/src/flow.ts create mode 100644 web/client/src/lib/connection.ts rename web/client/src/{message.ts => lib/flow.ts} (63%) create mode 100644 web/client/src/lib/message.ts rename web/client/src/{ => lib}/utils.ts (98%) diff --git a/web/client/src/App.tsx b/web/client/src/App.tsx index 369a0d2..083377f 100644 --- a/web/client/src/App.tsx +++ b/web/client/src/App.tsx @@ -8,9 +8,10 @@ import BreakPoint from './components/BreakPoint' import FlowPreview from './components/FlowPreview' import ViewFlow from './components/ViewFlow' -import { FlowManager } from './flow' -import { parseMessage, SendMessageType, buildMessageMeta, Flow, MessageType } from './message' -import { isInViewPort } from './utils' +import { Flow, FlowManager } from './lib/flow' +import { parseMessage, SendMessageType, buildMessageMeta, MessageType } from './lib/message' +import { isInViewPort } from './lib/utils' +import { ConnectionManager, IConnection } from './lib/connection' interface IState { flows: Flow[] @@ -25,6 +26,7 @@ const wsReconnIntervals = [1, 1, 2, 2, 4, 4, 8, 8, 16, 16, 32, 32] interface IProps {} class App extends React.Component { + private connMgr: ConnectionManager private flowMgr: FlowManager private ws: WebSocket | null private wsUnmountClose: boolean @@ -35,6 +37,7 @@ class App extends React.Component { constructor(props: IProps) { super(props) + this.connMgr = new ConnectionManager() this.flowMgr = new FlowManager() this.state = { @@ -106,7 +109,9 @@ class App extends React.Component { } // console.log('msg:', msg) - if (msg.type === MessageType.REQUEST) { + if (msg.type === MessageType.CONN) { + this.connMgr.set(msg.id, msg.content as IConnection) + } else if (msg.type === MessageType.REQUEST) { const flow = new Flow(msg) this.flowMgr.add(flow) diff --git a/web/client/src/components/EditFlow.tsx b/web/client/src/components/EditFlow.tsx index 6e76378..0d585a9 100644 --- a/web/client/src/components/EditFlow.tsx +++ b/web/client/src/components/EditFlow.tsx @@ -3,10 +3,9 @@ import Button from 'react-bootstrap/Button' import Modal from 'react-bootstrap/Modal' import Form from 'react-bootstrap/Form' import Alert from 'react-bootstrap/Alert' - -import { SendMessageType, buildMessageEdit, IRequest, IResponse, Header, Flow } from '../message' -import { isTextBody } from '../utils' - +import { SendMessageType, buildMessageEdit } from '../lib/message' +import { isTextBody } from '../lib/utils' +import type { Flow, Header, IRequest, IResponse } from '../lib/flow' const stringifyRequest = (request: IRequest) => { const firstLine = `${request.method} ${request.url}` diff --git a/web/client/src/components/FlowPreview.tsx b/web/client/src/components/FlowPreview.tsx index 8acb75a..e486562 100644 --- a/web/client/src/components/FlowPreview.tsx +++ b/web/client/src/components/FlowPreview.tsx @@ -1,6 +1,6 @@ import React from 'react' -import { IFlowPreview } from '../message' -import { shallowEqual } from '../utils' +import { shallowEqual } from '../lib/utils' +import type { IFlowPreview } from '../lib/flow' interface IProps { flow: IFlowPreview diff --git a/web/client/src/components/ViewFlow.tsx b/web/client/src/components/ViewFlow.tsx index 8c3922a..f1b2332 100644 --- a/web/client/src/components/ViewFlow.tsx +++ b/web/client/src/components/ViewFlow.tsx @@ -4,9 +4,8 @@ import FormCheck from 'react-bootstrap/FormCheck' import fetchToCurl from 'fetch-to-curl' import copy from 'copy-to-clipboard' import JSONPretty from 'react-json-pretty' -import { Flow, IResponse } from '../message' -import { isTextBody } from '../utils' - +import { isTextBody } from '../lib/utils' +import type { Flow, IResponse } from '../lib/flow' import EditFlow from './EditFlow' interface Iprops { diff --git a/web/client/src/flow.ts b/web/client/src/flow.ts deleted file mode 100644 index f679e72..0000000 --- a/web/client/src/flow.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { Flow } from './message' - -export class FlowManager { - private items: Flow[] - private _map: Map - private filterText: string - private filterTimer: number | null - private num: number - private max: number - - constructor() { - this.items = [] - this._map = new Map() - this.filterText = '' - this.filterTimer = null - this.num = 0 - - this.max = 1000 - } - - showList() { - let text = this.filterText - if (text) text = text.trim() - if (!text) return this.items - - // regexp - if (text.startsWith('/') && text.endsWith('/')) { - text = text.slice(1, text.length - 1).trim() - if (!text) return this.items - try { - const reg = new RegExp(text) - return this.items.filter(item => { - return reg.test(item.request.url) - }) - } catch (err) { - return this.items - } - } - - return this.items.filter(item => { - return item.request.url.includes(text) - }) - } - - add(item: Flow) { - item.no = ++this.num - this.items.push(item) - this._map.set(item.id, item) - - if (this.items.length > this.max) { - const oldest = this.items.shift() - if (oldest) this._map.delete(oldest.id) - } - } - - get(id: string) { - return this._map.get(id) - } - - changeFilter(text: string) { - this.filterText = text - } - - changeFilterLazy(text: string, callback: () => void) { - if (this.filterTimer) { - clearTimeout(this.filterTimer) - this.filterTimer = null - } - - this.filterTimer = setTimeout(() => { - this.filterText = text - callback() - }, 300) as any - } - - clear() { - this.items = [] - this._map = new Map() - } -} diff --git a/web/client/src/lib/connection.ts b/web/client/src/lib/connection.ts new file mode 100644 index 0000000..d3f2ca9 --- /dev/null +++ b/web/client/src/lib/connection.ts @@ -0,0 +1,33 @@ +export interface IConnection { + id: string + clientConn: { + id: string + tls: boolean + address: string + } + serverConn: { + id: string + address: string + peername: string + } +} + +export class ConnectionManager { + private _map: Map + + constructor() { + this._map = new Map() + } + + get(id: string) { + return this._map.get(id) + } + + set(id: string, conn: IConnection) { + this._map.set(id, conn) + } + + delete(id: string) { + this._map.delete(id) + } +} diff --git a/web/client/src/message.ts b/web/client/src/lib/flow.ts similarity index 63% rename from web/client/src/message.ts rename to web/client/src/lib/flow.ts index 9f3b415..2f62dd0 100644 --- a/web/client/src/message.ts +++ b/web/client/src/lib/flow.ts @@ -1,29 +1,8 @@ +import { IMessage, MessageType } from './message' import { arrayBufferToBase64, bufHexView, getSize, isTextBody } from './utils' -export enum MessageType { - CONN = 0, - REQUEST = 1, - REQUEST_BODY = 2, - RESPONSE = 3, - RESPONSE_BODY = 4, -} - export type Header = Record -export interface IConnection { - id: string - clientConn: { - id: string - tls: boolean - address: string - } - serverConn: { - id: string - address: string - peername: string - } -} - export interface IRequest { method: string url: string @@ -32,7 +11,7 @@ export interface IRequest { body?: ArrayBuffer } -interface IFlowRequest { +export interface IFlowRequest { connId: string request: IRequest } @@ -43,11 +22,9 @@ export interface IResponse { body?: ArrayBuffer } -export interface IMessage { - type: MessageType - id: string - waitIntercept: boolean - content?: ArrayBuffer | IFlowRequest | IResponse +export interface IPreviewBody { + type: 'image' | 'json' | 'binary' + data: string | null } export interface IFlowPreview { @@ -63,11 +40,6 @@ export interface IFlowPreview { contentType: string } -interface IPreviewBody { - type: 'image' | 'json' | 'binary' - data: string | null -} - export class Flow { public no: number public id: string @@ -278,121 +250,81 @@ export class Flow { } } -const allMessageBytes = [ - MessageType.CONN, - MessageType.REQUEST, - MessageType.REQUEST_BODY, - MessageType.RESPONSE, - MessageType.RESPONSE_BODY, -] - - -// type: 0/1/2/3/4 -// messageFlow -// version 1 byte + type 1 byte + id 36 byte + waitIntercept 1 byte + content left bytes -export const parseMessage = (data: ArrayBuffer): IMessage | null => { - if (data.byteLength < 39) return null - const meta = new Int8Array(data.slice(0, 39)) - const version = meta[0] - if (version !== 1) return null - const type = meta[1] as MessageType - if (!allMessageBytes.includes(type)) return null - const id = new TextDecoder().decode(data.slice(2, 38)) - const waitIntercept = meta[38] === 1 - - const resp: IMessage = { - type, - id, - waitIntercept, - } - if (data.byteLength === 39) return resp - if (type === MessageType.REQUEST_BODY || type === MessageType.RESPONSE_BODY) { - resp.content = data.slice(39) - return resp - } - - const contentStr = new TextDecoder().decode(data.slice(39)) - let content: any - try { - content = JSON.parse(contentStr) - } catch (err) { - return null +export class FlowManager { + private items: Flow[] + private _map: Map + private filterText: string + private filterTimer: number | null + private num: number + private max: number + + constructor() { + this.items = [] + this._map = new Map() + this.filterText = '' + this.filterTimer = null + this.num = 0 + + this.max = 1000 } - resp.content = content - return resp -} - - -export enum SendMessageType { - CHANGE_REQUEST = 11, - CHANGE_RESPONSE = 12, - DROP_REQUEST = 13, - DROP_RESPONSE = 14, - CHANGE_BREAK_POINT_RULES = 21, -} + showList() { + let text = this.filterText + if (text) text = text.trim() + if (!text) return this.items + + // regexp + if (text.startsWith('/') && text.endsWith('/')) { + text = text.slice(1, text.length - 1).trim() + if (!text) return this.items + try { + const reg = new RegExp(text) + return this.items.filter(item => { + return reg.test(item.request.url) + }) + } catch (err) { + return this.items + } + } -// type: 11/12/13/14 -// messageEdit -// version 1 byte + type 1 byte + id 36 byte + header len 4 byte + header content bytes + body len 4 byte + [body content bytes] -export const buildMessageEdit = (messageType: SendMessageType, flow: Flow) => { - if (messageType === SendMessageType.DROP_REQUEST || messageType === SendMessageType.DROP_RESPONSE) { - const view = new Uint8Array(38) - view[0] = 1 - view[1] = messageType - view.set(new TextEncoder().encode(flow.id), 2) - return view + return this.items.filter(item => { + return item.request.url.includes(text) + }) } - let header: Omit | Omit - let body: ArrayBuffer | Uint8Array | undefined + add(item: Flow) { + item.no = ++this.num + this.items.push(item) + this._map.set(item.id, item) - if (messageType === SendMessageType.CHANGE_REQUEST) { - ({ body, ...header } = flow.request) - } else if (messageType === SendMessageType.CHANGE_RESPONSE) { - ({ body, ...header } = flow.response as IResponse) - } else { - throw new Error('invalid message type') + if (this.items.length > this.max) { + const oldest = this.items.shift() + if (oldest) this._map.delete(oldest.id) + } } - if (body instanceof ArrayBuffer) body = new Uint8Array(body) - const bodyLen = (body && body.byteLength) ? body.byteLength : 0 - - if ('Content-Encoding' in header.header) delete header.header['Content-Encoding'] - if ('Transfer-Encoding' in header.header) delete header.header['Transfer-Encoding'] - header.header['Content-Length'] = [String(bodyLen)] - - const headerBytes = new TextEncoder().encode(JSON.stringify(header)) - const len = 2 + 36 + 4 + headerBytes.byteLength + 4 + bodyLen - const data = new ArrayBuffer(len) - const view = new Uint8Array(data) - view[0] = 1 - view[1] = messageType - view.set(new TextEncoder().encode(flow.id), 2) - view.set(headerBytes, 2 + 36 + 4) - if (bodyLen) view.set(body as Uint8Array, 2 + 36 + 4 + headerBytes.byteLength + 4) - - const view2 = new DataView(data) - view2.setUint32(2 + 36, headerBytes.byteLength) - view2.setUint32(2 + 36 + 4 + headerBytes.byteLength, bodyLen) + get(id: string) { + return this._map.get(id) + } - return view -} + changeFilter(text: string) { + this.filterText = text + } + changeFilterLazy(text: string, callback: () => void) { + if (this.filterTimer) { + clearTimeout(this.filterTimer) + this.filterTimer = null + } -// type: 21 -// messageMeta -// version 1 byte + type 1 byte + content left bytes -export const buildMessageMeta = (messageType: SendMessageType, rules: any) => { - if (messageType !== SendMessageType.CHANGE_BREAK_POINT_RULES) { - throw new Error('invalid message type') + this.filterTimer = setTimeout(() => { + this.filterText = text + callback() + }, 300) as any } - const rulesBytes = new TextEncoder().encode(JSON.stringify(rules)) - const view = new Uint8Array(2 + rulesBytes.byteLength) - view[0] = 1 - view[1] = messageType - view.set(rulesBytes, 2) - - return view + clear() { + this.items = [] + this._map = new Map() + } } diff --git a/web/client/src/lib/message.ts b/web/client/src/lib/message.ts new file mode 100644 index 0000000..3836443 --- /dev/null +++ b/web/client/src/lib/message.ts @@ -0,0 +1,133 @@ +import type { IConnection } from './connection' +import type { Flow, IFlowRequest, IRequest, IResponse } from './flow' + +export enum MessageType { + CONN = 0, + REQUEST = 1, + REQUEST_BODY = 2, + RESPONSE = 3, + RESPONSE_BODY = 4, +} + +const allMessageBytes = [ + MessageType.CONN, + MessageType.REQUEST, + MessageType.REQUEST_BODY, + MessageType.RESPONSE, + MessageType.RESPONSE_BODY, +] + +export interface IMessage { + type: MessageType + id: string + waitIntercept: boolean + content?: ArrayBuffer | IFlowRequest | IResponse | IConnection +} + +// type: 0/1/2/3/4 +// messageFlow +// version 1 byte + type 1 byte + id 36 byte + waitIntercept 1 byte + content left bytes +export const parseMessage = (data: ArrayBuffer): IMessage | null => { + if (data.byteLength < 39) return null + const meta = new Int8Array(data.slice(0, 39)) + const version = meta[0] + if (version !== 1) return null + const type = meta[1] as MessageType + if (!allMessageBytes.includes(type)) return null + const id = new TextDecoder().decode(data.slice(2, 38)) + const waitIntercept = meta[38] === 1 + + const resp: IMessage = { + type, + id, + waitIntercept, + } + if (data.byteLength === 39) return resp + if (type === MessageType.REQUEST_BODY || type === MessageType.RESPONSE_BODY) { + resp.content = data.slice(39) + return resp + } + + const contentStr = new TextDecoder().decode(data.slice(39)) + let content: any + try { + content = JSON.parse(contentStr) + } catch (err) { + return null + } + + resp.content = content + return resp +} + +export enum SendMessageType { + CHANGE_REQUEST = 11, + CHANGE_RESPONSE = 12, + DROP_REQUEST = 13, + DROP_RESPONSE = 14, + CHANGE_BREAK_POINT_RULES = 21, +} + +// type: 11/12/13/14 +// messageEdit +// version 1 byte + type 1 byte + id 36 byte + header len 4 byte + header content bytes + body len 4 byte + [body content bytes] +export const buildMessageEdit = (messageType: SendMessageType, flow: Flow) => { + if (messageType === SendMessageType.DROP_REQUEST || messageType === SendMessageType.DROP_RESPONSE) { + const view = new Uint8Array(38) + view[0] = 1 + view[1] = messageType + view.set(new TextEncoder().encode(flow.id), 2) + return view + } + + let header: Omit | Omit + let body: ArrayBuffer | Uint8Array | undefined + + if (messageType === SendMessageType.CHANGE_REQUEST) { + ({ body, ...header } = flow.request) + } else if (messageType === SendMessageType.CHANGE_RESPONSE) { + ({ body, ...header } = flow.response as IResponse) + } else { + throw new Error('invalid message type') + } + + if (body instanceof ArrayBuffer) body = new Uint8Array(body) + const bodyLen = (body && body.byteLength) ? body.byteLength : 0 + + if ('Content-Encoding' in header.header) delete header.header['Content-Encoding'] + if ('Transfer-Encoding' in header.header) delete header.header['Transfer-Encoding'] + header.header['Content-Length'] = [String(bodyLen)] + + const headerBytes = new TextEncoder().encode(JSON.stringify(header)) + const len = 2 + 36 + 4 + headerBytes.byteLength + 4 + bodyLen + const data = new ArrayBuffer(len) + const view = new Uint8Array(data) + view[0] = 1 + view[1] = messageType + view.set(new TextEncoder().encode(flow.id), 2) + view.set(headerBytes, 2 + 36 + 4) + if (bodyLen) view.set(body as Uint8Array, 2 + 36 + 4 + headerBytes.byteLength + 4) + + const view2 = new DataView(data) + view2.setUint32(2 + 36, headerBytes.byteLength) + view2.setUint32(2 + 36 + 4 + headerBytes.byteLength, bodyLen) + + return view +} + +// type: 21 +// messageMeta +// version 1 byte + type 1 byte + content left bytes +export const buildMessageMeta = (messageType: SendMessageType, rules: any) => { + if (messageType !== SendMessageType.CHANGE_BREAK_POINT_RULES) { + throw new Error('invalid message type') + } + + const rulesBytes = new TextEncoder().encode(JSON.stringify(rules)) + const view = new Uint8Array(2 + rulesBytes.byteLength) + view[0] = 1 + view[1] = messageType + view.set(rulesBytes, 2) + + return view +} diff --git a/web/client/src/utils.ts b/web/client/src/lib/utils.ts similarity index 98% rename from web/client/src/utils.ts rename to web/client/src/lib/utils.ts index 543e446..0fbf2fe 100644 --- a/web/client/src/utils.ts +++ b/web/client/src/lib/utils.ts @@ -1,4 +1,4 @@ -import { IRequest, IResponse } from './message' +import type { IRequest, IResponse } from './flow' export const isTextBody = (payload: IRequest | IResponse) => { if (!payload) return false