Optimizing export
All checks were successful
the build was successful

This commit is contained in:
Fabian Stamm 2018-12-03 07:59:29 +01:00
parent 5bf7af6565
commit 07e2dff29f
4 changed files with 315 additions and 311 deletions

View File

@ -1,6 +1,6 @@
import { Request, parseInput } from "./request"; import { Request, parseInput } from "./request";
import { RDATARecord, RecourceRecord } from "./record"; import { RDATARecord, RecourceRecord } from "./record";
import { MonitoringPlugin, QuestionPlugin, DnsCore, AnswerHandler, ListenerPlugin, StoragePlugin, Record } from "."; import { MonitoringPlugin, QuestionPlugin, DnsCore, AnswerHandler, ListenerPlugin, StoragePlugin, Record } from "./core";
import assert = require("assert"); import assert = require("assert");
import { RecordTypes } from "./types"; import { RecordTypes } from "./types";
import { Question } from "./question"; import { Question } from "./question";

309
src/core.ts Normal file
View File

@ -0,0 +1,309 @@
import { Request } from "./request";
import { RecordTypes } from "./types";
import * as LRU from "lru-cache";
import { Writable } from "stream";
import { Question } from "./question";
import { RecourceRecord } from "./record";
// export class AnswerSteam extends Writable {
// }
export interface Record {
type: RecordTypes
domain: string
hostname: string
value: string
ttl: number
priority?: number
additional?: string
}
export interface Plugin {
/**
* This sets the priority of this Plugin.
*
* The lower the value, the higher priority.
* It is recommended to make this value changeable over the cosntructor options field.
*/
Priority: number
init(core: DnsCore): Promise<void>
}
export interface ListenerPlugin extends Plugin {
/**
* This method is for registering a callback that is called
* when packet is received and ready for parsing.
* The function returns the Answer data. Errors shouldn't be possible
* by this function
*/
registerCallback(callback: (data: Buffer, sender: string, max_size?: number) => Promise<Buffer>): void
registerCallback(callback: (data: Buffer, sender: string, answer: Writable) => Promise<void>)
}
export class ListenerManager {
private _listener: ListenerPlugin[] = []
add(listener: ListenerPlugin) {
this._listener.push(listener)
}
setup(core: DnsCore) {
return Promise.all(this._listener.map(e => e.init(core)))
}
registerCallback(callback: (data: Buffer, sender: string, max_size?: number) => Promise<Buffer>) {
this._listener.forEach(e => e.registerCallback(callback))
}
}
export interface MonitoringPlugin extends Plugin {
onRequest(domain: string, hostname: string, type: RecordTypes, additionalInformations): void
}
export class MonitoringManager {
private _monitoring: MonitoringPlugin[] = []
add(monitoring: MonitoringPlugin) {
this._monitoring.push(monitoring)
}
setup(core: DnsCore) {
return Promise.all(this._monitoring.map(e => e.init(core)))
}
onRequest(domain: string, hostname: string, type: RecordTypes, additionalInformations) {
this._monitoring.forEach(e => e.onRequest(domain, hostname, type, additionalInformations))
}
}
export interface StoragePlugin extends Plugin {
/**
* Here you can define wich record types this storage plugin is capable of.
*/
RecordTypes: RecordTypes[]
/**
* Disables record caching for this Storage
*/
NoCache: boolean;
/**
* Returns if storage plugin is responsible for this domain.
* This check is synchroneus because it should be quick.
*
* @param domain The domain for check
*/
isResponsible(domain: string): boolean
getRecords(domain: string, hostname: string, type: RecordTypes): Promise<Record[] | undefined>
getAllRecordsForDomain(domain: string): Promise<Record[]>
}
type SortedStorage = {
[P in keyof any]: StoragePlugin[] | undefined
}
const cacheoptions: LRU.Options = {
length: () => 1,
max: 500,
maxAge: 10 * 60 * 1000
}
class RecordCache {
private cache = new LRU<string, Record[]>(cacheoptions);
getRecords(domain: string, hostname: string, type: RecordTypes) {
let key = `${domain};;${hostname};;${RecordTypes[type]}`;
let rec = this.cache.get(key)
if (!rec) {
return undefined;
}
return rec;
}
addRecords(domain: string, hostname: string, type: RecordTypes, records: Record[], ttl: number) {
let key = `${domain};;${hostname};;${RecordTypes[type]}`;
this.cache.set(key, records, ttl);
}
}
export class StorageManager {
private _storages: StoragePlugin[] = []
private _sorted: SortedStorage;
private _cache = new RecordCache();
add(storage: StoragePlugin) {
this._storages.push(storage)
}
private presort() {
this._storages = this._storages.sort((e1, e2) => e1.Priority - e2.Priority)
this._sorted = <any>{}
for (let key in RecordTypes) {
let key_n = Number(key)
if (key_n !== Number.NaN) {
//The _storages list is sorted with priority.
this._sorted[key_n] = this._storages.filter(e => e.RecordTypes.find(t => t === key_n) !== undefined)
}
}
}
setup(core: DnsCore) {
this.presort()
return Promise.all(this._storages.map(e => e.init(core)))
}
async getAllRecordsForDomain(domain: string) {
return this._storages.find(e => e.isResponsible(domain)).getAllRecordsForDomain(domain);
}
async getRecords(domain: string, hostname: string, type: RecordTypes) {
// First check if record is cached. Since the dns entries should not chacnge quickly
// using the cach for quicker response times is the way to go.
let records = this._cache.getRecords(domain, hostname, type);
if (records) return records;
let storages = this._sorted[type].filter(s => s.isResponsible(domain));
if (!storages || storages.length <= 0) return undefined
let nocache = false;
for (let storage of storages) {
nocache = storage.NoCache || nocache;
records = await storage.getRecords(domain, hostname, type)
if (records) {
if (storage.NoCache) this._cache.addRecords(domain, hostname, type, records, records.length <= 0 ? 3600 : records[0].ttl)
return records;
}
}
// If any of the checked storage engines that is
// responsible for the domain has the noCache flag,
// caching will be disabled for all.
if (!nocache)
this._cache.addRecords(domain, hostname, type, [], 0);
return [];
}
}
export interface AnswerHandler {
addQuestion: (question: Question) => void;
addAnswer: (record: RecourceRecord) => void;
addAdditional: (record: RecourceRecord) => void;
addAutority: (record: RecourceRecord) => void
}
export interface QuestionPlugin extends Plugin {
/**
* This field defines wich question types this plugin is capable of handling.
*/
QuestionTypes: RecordTypes[];
init(core: DnsCore): Promise<void>
/**
*
* @param question The question this plugin should solve
* @param request This gives the plugin access to relevant
* functions for queing depending questions or adding answers etc.
* @param next This function should be called if the plugin is not able to resolve
* the request. This will trigger other handlers.
*/
handleQuestion(question: Question, request: AnswerHandler, next: () => void): Promise<void>
}
export class QuestionManager {
private _questions: QuestionPlugin[] = [];
private _sorted = <any>{};
add(question: QuestionPlugin) {
this._questions.push(question);
}
private sort() {
this._questions = this._questions.sort((e1, e2) => e1.Priority - e2.Priority);
this._sorted = {};
for (let key in RecordTypes) {
let key_n = Number(key)
if (key_n !== Number.NaN) {
this._sorted[key_n] = this._questions.filter(e => e.QuestionTypes.find(t => t === key_n) !== undefined)
}
}
}
setup(core: DnsCore) {
this.sort();
return Promise.all(this._questions.map(e => e.init(core)))
}
async handleQuestion(question: Question, request: AnswerHandler) {
let handlers: QuestionPlugin[] = this._sorted[question.QTYPE];
if (!handlers || handlers.length <= 0) return;
let index = 0;
while (index < handlers.length) {
let i = index;
await handlers[i].handleQuestion(question, request, () => {
index++;
})
if (i === index) break;
}
}
}
export class DnsCore {
storageManager: StorageManager;
addStorage(plugin: StoragePlugin) {
this.storageManager.add(plugin);
}
monitoringManager: MonitoringManager;
addMonitoring(plugin: MonitoringPlugin) {
this.monitoringManager.add(plugin);
}
listenerManager: ListenerManager;
addListener(plugin: ListenerPlugin) {
this.listenerManager.add(plugin);
}
questionManager: QuestionManager;
addQuestion(plugin: QuestionPlugin) {
this.questionManager.add(plugin);
}
constructor() {
this.listenerManager = new ListenerManager()
this.monitoringManager = new MonitoringManager()
this.storageManager = new StorageManager()
this.questionManager = new QuestionManager()
}
async start() {
await this.storageManager.setup(this);
await this.monitoringManager.setup(this);
await this.listenerManager.setup(this);
await this.questionManager.setup(this);
this.listenerManager.registerCallback(this.onMessage.bind(this));
}
private async onMessage(data: Buffer, sender: string, max_size: number = 0) {
let request = new Request(data, sender, max_size);
let questionqueue: Promise<void>[] = [];
let handleQuestion = question => {
let prom = this.questionManager.handleQuestion(question, {
addAdditional: record => {
request.addAdditionals(record)
},
addAnswer: record => {
request.addAnswer(record);
},
addAutority: record => {
request.addAuthorities(record);
},
addQuestion: question => {
handleQuestion(question)
}
})
questionqueue.push(prom);
}
request.questions.map(q => handleQuestion(q));
await Promise.all(questionqueue);
return request.serialize();
}
}

View File

@ -1,309 +1,4 @@
import { Request } from "./request"; export { DnsCore, ListenerPlugin, MonitoringPlugin, StoragePlugin, QuestionPlugin, AnswerHandler, Record } from "./core"
import { RecordTypes } from "./types"; export { RecourceRecord, RDATARecord } from "./record"
import * as LRU from "lru-cache"; export { Request } from "./request"
import { Writable } from "stream"; export { Label } from "./label"
import { Question } from "./question";
import { RecourceRecord } from "./record";
// export class AnswerSteam extends Writable {
// }
export interface Record {
type: RecordTypes
domain: string
hostname: string
value: string
ttl: number
priority?: number
additional?: string
}
export interface Plugin {
/**
* This sets the priority of this Plugin.
*
* The lower the value, the higher priority.
* It is recommended to make this value changeable over the cosntructor options field.
*/
Priority: number
init(core: DnsCore): Promise<void>
}
export interface ListenerPlugin extends Plugin {
/**
* This method is for registering a callback that is called
* when packet is received and ready for parsing.
* The function returns the Answer data. Errors shouldn't be possible
* by this function
*/
registerCallback(callback: (data: Buffer, sender: string, max_size?: number) => Promise<Buffer>): void
registerCallback(callback: (data: Buffer, sender: string, answer: Writable) => Promise<void>)
}
export class ListenerManager {
private _listener: ListenerPlugin[] = []
add(listener: ListenerPlugin) {
this._listener.push(listener)
}
setup(core: DnsCore) {
return Promise.all(this._listener.map(e => e.init(core)))
}
registerCallback(callback: (data: Buffer, sender: string, max_size?: number) => Promise<Buffer>) {
this._listener.forEach(e => e.registerCallback(callback))
}
}
export interface MonitoringPlugin extends Plugin {
onRequest(domain: string, hostname: string, type: RecordTypes, additionalInformations): void
}
export class MonitoringManager {
private _monitoring: MonitoringPlugin[] = []
add(monitoring: MonitoringPlugin) {
this._monitoring.push(monitoring)
}
setup(core: DnsCore) {
return Promise.all(this._monitoring.map(e => e.init(core)))
}
onRequest(domain: string, hostname: string, type: RecordTypes, additionalInformations) {
this._monitoring.forEach(e => e.onRequest(domain, hostname, type, additionalInformations))
}
}
export interface StoragePlugin extends Plugin {
/**
* Here you can define wich record types this storage plugin is capable of.
*/
RecordTypes: RecordTypes[]
/**
* Disables record caching for this Storage
*/
NoCache: boolean;
/**
* Returns if storage plugin is responsible for this domain.
* This check is synchroneus because it should be quick.
*
* @param domain The domain for check
*/
isResponsible(domain: string): boolean
getRecords(domain: string, hostname: string, type: RecordTypes): Promise<Record[] | undefined>
getAllRecordsForDomain(domain: string): Promise<Record[]>
}
type SortedStorage = {
[P in keyof any]: StoragePlugin[] | undefined
}
const cacheoptions: LRU.Options = {
length: () => 1,
max: 500,
maxAge: 10 * 60 * 1000
}
class RecordCache {
private cache = new LRU<string, Record[]>(cacheoptions);
getRecords(domain: string, hostname: string, type: RecordTypes) {
let key = `${domain};;${hostname};;${RecordTypes[type]}`;
let rec = this.cache.get(key)
if (!rec) {
return undefined;
}
return rec;
}
addRecords(domain: string, hostname: string, type: RecordTypes, records: Record[], ttl: number) {
let key = `${domain};;${hostname};;${RecordTypes[type]}`;
this.cache.set(key, records, ttl);
}
}
export class StorageManager {
private _storages: StoragePlugin[] = []
private _sorted: SortedStorage;
private _cache = new RecordCache();
add(storage: StoragePlugin) {
this._storages.push(storage)
}
private presort() {
this._storages = this._storages.sort((e1, e2) => e1.Priority - e2.Priority)
this._sorted = <any>{}
for (let key in RecordTypes) {
let key_n = Number(key)
if (key_n !== Number.NaN) {
//The _storages list is sorted with priority.
this._sorted[key_n] = this._storages.filter(e => e.RecordTypes.find(t => t === key_n) !== undefined)
}
}
}
setup(core: DnsCore) {
this.presort()
return Promise.all(this._storages.map(e => e.init(core)))
}
async getAllRecordsForDomain(domain: string) {
return this._storages.find(e => e.isResponsible(domain)).getAllRecordsForDomain(domain);
}
async getRecords(domain: string, hostname: string, type: RecordTypes) {
// First check if record is cached. Since the dns entries should not chacnge quickly
// using the cach for quicker response times is the way to go.
let records = this._cache.getRecords(domain, hostname, type);
if (records) return records;
let storages = this._sorted[type].filter(s => s.isResponsible(domain));
if (!storages || storages.length <= 0) return undefined
let nocache = false;
for (let storage of storages) {
nocache = storage.NoCache || nocache;
records = await storage.getRecords(domain, hostname, type)
if (records) {
if (storage.NoCache) this._cache.addRecords(domain, hostname, type, records, records.length <= 0 ? 3600 : records[0].ttl)
return records;
}
}
// If any of the checked storage engines that is
// responsible for the domain has the noCache flag,
// caching will be disabled for all.
if (!nocache)
this._cache.addRecords(domain, hostname, type, [], 0);
return [];
}
}
export interface AnswerHandler {
addQuestion: (question: Question) => void;
addAnswer: (record: RecourceRecord) => void;
addAdditional: (record: RecourceRecord) => void;
addAutority: (record: RecourceRecord) => void
}
export interface QuestionPlugin extends Plugin {
/**
* This field defines wich question types this plugin is capable of handling.
*/
QuestionTypes: RecordTypes[];
init(core: DnsCore): Promise<void>
/**
*
* @param question The question this plugin should solve
* @param request This gives the plugin access to relevant
* functions for queing depending questions or adding answers etc.
* @param next This function should be called if the plugin is not able to resolve
* the request. This will trigger other handlers.
*/
handleQuestion(question: Question, request: AnswerHandler, next: () => void): Promise<void>
}
export class QuestionManager {
private _questions: QuestionPlugin[] = [];
private _sorted = <any>{};
add(question: QuestionPlugin) {
this._questions.push(question);
}
private sort() {
this._questions = this._questions.sort((e1, e2) => e1.Priority - e2.Priority);
this._sorted = {};
for (let key in RecordTypes) {
let key_n = Number(key)
if (key_n !== Number.NaN) {
this._sorted[key_n] = this._questions.filter(e => e.QuestionTypes.find(t => t === key_n) !== undefined)
}
}
}
setup(core: DnsCore) {
this.sort();
return Promise.all(this._questions.map(e => e.init(core)))
}
async handleQuestion(question: Question, request: AnswerHandler) {
let handlers: QuestionPlugin[] = this._sorted[question.QTYPE];
if (!handlers || handlers.length <= 0) return;
let index = 0;
while (index < handlers.length) {
let i = index;
await handlers[i].handleQuestion(question, request, () => {
index++;
})
if (i === index) break;
}
}
}
export class DnsCore {
storageManager: StorageManager;
addStorage(plugin: StoragePlugin) {
this.storageManager.add(plugin);
}
monitoringManager: MonitoringManager;
addMonitoring(plugin: MonitoringPlugin) {
this.monitoringManager.add(plugin);
}
listenerManager: ListenerManager;
addListener(plugin: ListenerPlugin) {
this.listenerManager.add(plugin);
}
questionManager: QuestionManager;
addQuestion(plugin: QuestionPlugin) {
this.questionManager.add(plugin);
}
constructor() {
this.listenerManager = new ListenerManager()
this.monitoringManager = new MonitoringManager()
this.storageManager = new StorageManager()
this.questionManager = new QuestionManager()
}
async start() {
await this.storageManager.setup(this);
await this.monitoringManager.setup(this);
await this.listenerManager.setup(this);
await this.questionManager.setup(this);
this.listenerManager.registerCallback(this.onMessage.bind(this));
}
private async onMessage(data: Buffer, sender: string, max_size: number = 0) {
let request = new Request(data, sender, max_size);
let questionqueue: Promise<void>[] = [];
let handleQuestion = question => {
let prom = this.questionManager.handleQuestion(question, {
addAdditional: record => {
request.addAdditionals(record)
},
addAnswer: record => {
request.addAnswer(record);
},
addAutority: record => {
request.addAuthorities(record);
},
addQuestion: question => {
handleQuestion(question)
}
})
questionqueue.push(prom);
}
request.questions.map(q => handleQuestion(q));
await Promise.all(questionqueue);
return request.serialize();
}
}

View File

@ -338,7 +338,7 @@ describe("parser", function () {
}) })
}) })
import { DnsCore, StoragePlugin, Record, MonitoringPlugin, QuestionPlugin, AnswerHandler, ListenerPlugin } from "./index" import { DnsCore, StoragePlugin, Record, MonitoringPlugin, QuestionPlugin, AnswerHandler, ListenerPlugin } from "./core"
describe("DNS Core", function () { describe("DNS Core", function () {
it("Initialization", () => { it("Initialization", () => {