Start routing; rehydratable interface; add verbose logging

This commit is contained in:
garrettmills
2020-07-28 09:11:48 -05:00
parent a6995c6a85
commit a27618d5a3
22 changed files with 396 additions and 17 deletions

View File

@@ -5,6 +5,7 @@ import { HTTPResponse } from './type/HTTPResponse.ts'
import Utility from '../service/utility/Utility.ts'
import { Injectable } from '../../../di/src/decorator/Injection.ts'
import SessionInterface from './session/SessionInterface.ts'
import ActivatedRoute from './routing/ActivatedRoute.ts'
@Injectable()
export class Request implements HTTPRequest {
@@ -13,6 +14,7 @@ export class Request implements HTTPRequest {
private _body: any
private _query: { [key: string]: any } = {}
private _session!: SessionInterface
private _activated_route!: ActivatedRoute
public readonly url: string
public readonly method: string
@@ -37,7 +39,17 @@ export class Request implements HTTPRequest {
}
set session(session: SessionInterface) {
this._session = session
if ( !this._session )
this._session = session
}
get route(): ActivatedRoute {
return this._activated_route
}
set route(route: ActivatedRoute) {
if ( !this._activated_route )
this._activated_route = route
}
constructor(
@@ -95,7 +107,7 @@ export class Request implements HTTPRequest {
}
get path() {
return this.url.split('?')[0]
return this.url.split('?')[0].split('#')[0]
}
get xhr() {

View File

@@ -4,6 +4,7 @@ import AppClass from '../../lifecycle/AppClass.ts'
import {Collection} from '../../collection/Collection.ts'
import {Service} from '../../../../di/src/decorator/Service.ts'
import {Request} from '../Request.ts'
import {Logging} from '../../service/logging/Logging.ts'
export interface ModuleRegistrationFluency {
before: (other?: Instantiable<Module>) => Kernel,
@@ -42,6 +43,7 @@ export default class Kernel extends AppClass {
}
public register(module: Instantiable<Module>): ModuleRegistrationFluency {
this.make(Logging).verbose(`Registering HTTP kernel module: ${module.name}`)
return {
before: (other?: Instantiable<Module>): Kernel => {
if ( !other ) {

View File

@@ -0,0 +1,53 @@
import Module from '../Module.ts'
import Kernel from '../Kernel.ts'
import PrepareRequest from './PrepareRequest.ts'
import Routing from '../../../unit/Routing.ts'
import {Logging} from '../../../service/logging/Logging.ts'
import {Request} from '../../Request.ts'
import Config from '../../../unit/Config.ts'
import ActivatedRoute from '../../routing/ActivatedRoute.ts'
import {Injectable} from '../../../../../di/src/decorator/Injection.ts'
@Injectable()
export default class MountActivatedRoute extends Module {
public static register(kernel: Kernel) {
kernel.register(this).after(PrepareRequest)
}
constructor(
protected readonly routing: Routing,
protected readonly config: Config,
protected readonly logger: Logging,
) {
super()
}
public async apply(request: Request): Promise<Request> {
let incoming = this.routing.resolve([request.path])
this.logger.info(`${request.method} ${incoming}`)
const prefix: string | undefined = this.config.get('server.prefix')
const allow_mount_without_prefix: boolean = this.config.get('server.allow_mount_without_prefix')
if ( prefix && this.routing.resolve([prefix]) !== '/' ) {
if ( incoming.startsWith(prefix) ) {
incoming = this.routing.resolve([incoming.replace(prefix, '')])
this.logger.verbose(`Resolved prefix: ${incoming}`)
} else if ( !allow_mount_without_prefix ) {
this.logger.warn(`Will not attempt to mount route with missing prefix: ${incoming}`)
this.logger.debug('To allow this, set the config server.allow_mount_without_prefix = true.')
return request
}
}
const activated_route: ActivatedRoute | undefined = this.routing.build(incoming)
if ( activated_route ) {
this.logger.verbose(`Resolved activated route: ${activated_route.route.route}`)
request.route = activated_route
} else {
this.logger.verbose(`Unable to resolve route to activated route. No matching route was found.`)
}
return request
}
}

View File

@@ -0,0 +1,16 @@
import ResponseFactory from './ResponseFactory.ts'
import {Rehydratable} from '../../support/Rehydratable.ts'
import {Request} from '../Request.ts'
export default class DehydratedStateResponseFactory extends ResponseFactory {
constructor(
public readonly rehydratable: Rehydratable
) {
super()
}
public async write(request: Request): Promise<Request> {
request.response.body = JSON.stringify(this.rehydratable.dehydrate())
return request
}
}

View File

@@ -0,0 +1,15 @@
import ResponseFactory from './ResponseFactory.ts'
import {Request} from '../Request.ts'
export default class JSONResponseFactory extends ResponseFactory {
constructor(
public readonly value: any
) {
super()
}
public async write(request: Request): Promise<Request> {
request.response.body = JSON.stringify(this.value)
return request
}
}

View File

@@ -0,0 +1,6 @@
import AppClass from '../../lifecycle/AppClass.ts'
import {Request} from '../Request.ts'
export default abstract class ResponseFactory extends AppClass {
public abstract async write(request: Request): Promise<Request>
}

View File

@@ -0,0 +1,15 @@
import ResponseFactory from './ResponseFactory.ts'
import {Request} from '../Request.ts'
export default class StringResponseFactory extends ResponseFactory {
constructor(
public readonly value: string,
) {
super()
}
public async write(request: Request): Promise<Request> {
request.response.body = this.value
return request
}
}

View File

@@ -0,0 +1,14 @@
import AppClass from '../../lifecycle/AppClass.ts'
import {Route, RouteParameters} from './Route.ts'
export default class ActivatedRoute extends AppClass {
public readonly params: RouteParameters
constructor(
public readonly incoming: string,
public readonly route: Route,
) {
super()
this.params = route.build_parameters(incoming)
}
}

View File

@@ -0,0 +1,29 @@
import {Route, RouteParameters, RouteSegment} from './Route.ts'
import Utility from '../../service/utility/Utility.ts'
import {make} from '../../../../di/src/global.ts'
export class ComplexRoute extends Route {
public match(incoming: string): boolean {
const base_length = this.split(this.base).length
const incoming_length = this.split(incoming).length
return base_length === incoming_length // FIXME match!
}
public build_parameters(incoming: string): RouteParameters {
const utility = make(Utility)
const params: RouteParameters = {}
let current_wildcard: number = 1
this.zip(incoming).forEach((segment: RouteSegment) => {
if ( segment.base.indexOf('*') >= 0 ) {
params[`$${current_wildcard}`] = utility.infer(segment.match)
current_wildcard += 1
} else if ( segment.base.startsWith(':') && segment.base.length > 1 ) {
const name = segment.base.substr(1)
params[name] = utility.infer(segment.match)
}
})
return params
}
}

View File

@@ -0,0 +1,41 @@
import {logger} from "../../service/logging/global.ts";
export type RouteParameters = { [key: string]: string }
export type RouteSegment = { base: string, match: string | undefined }
export type ZippedRouteSegments = RouteSegment[]
export abstract class Route {
constructor(
protected base: string
) { }
public abstract match(incoming: string): boolean
public abstract build_parameters(incoming: string): RouteParameters
public get route() {
return this.base
}
public split(incoming: string) {
return incoming.toLowerCase().split('/')
}
public zip(incoming: string) {
const incoming_parts: string[] = this.split(incoming)
const base_parts: string[] = this.split(this.base)
const zipped_parts: ZippedRouteSegments = []
if ( incoming_parts.length !== base_parts.length ) {
logger.warn(`Zipping routes with different part lengths! (Base: ${this.base}, Incoming: ${incoming}`)
}
base_parts.forEach((part, index) => {
zipped_parts.push({
base: part,
match: (index >= incoming_parts.length ? undefined : incoming_parts[index]),
})
})
return zipped_parts
}
}

View File

@@ -0,0 +1,11 @@
import {Route, RouteParameters} from './Route.ts'
export class SimpleRoute extends Route {
public match(incoming: string): boolean {
return incoming === this.base
}
public build_parameters(incoming: string): RouteParameters {
return {}
}
}

View File

@@ -1,6 +1,7 @@
import { ServerRequest } from '../../external/http.ts'
import {HTTPResponse} from './HTTPResponse.ts'
import SessionInterface from '../session/SessionInterface.ts'
import ActivatedRoute from '../routing/ActivatedRoute.ts'
export interface HTTPProtocol {
string: string,
@@ -30,4 +31,5 @@ export interface HTTPRequest {
secure: boolean
session: SessionInterface,
route?: ActivatedRoute,
}