|
@@ -1,9 +1,6 @@ |
|
|
import http from 'http'; |
|
|
import http from 'http'; |
|
|
import {BackendState} from './common'; |
|
|
import {BackendState} from './common'; |
|
|
import {Language, Resource, Charset, MediaType, LanguageStatusMessageMap} from '../common'; |
|
|
import {Language, Resource, Charset, MediaType, LanguageStatusMessageMap} from '../common'; |
|
|
import * as applicationJson from '../common/media-types/application/json'; |
|
|
|
|
|
import * as utf8 from '../common/charsets/utf-8'; |
|
|
|
|
|
import * as en from '../common/languages/en'; |
|
|
|
|
|
import https from 'https'; |
|
|
import https from 'https'; |
|
|
import Negotiator from 'negotiator'; |
|
|
import Negotiator from 'negotiator'; |
|
|
import {constants} from 'http2'; |
|
|
import {constants} from 'http2'; |
|
@@ -89,19 +86,19 @@ export interface CreateServerParams { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
export interface RequestContext extends http.IncomingMessage { |
|
|
export interface RequestContext extends http.IncomingMessage { |
|
|
backend: BackendState; |
|
|
|
|
|
|
|
|
backend?: BackendState; |
|
|
|
|
|
|
|
|
host: string; |
|
|
|
|
|
|
|
|
host?: string; |
|
|
|
|
|
|
|
|
scheme: string; |
|
|
|
|
|
|
|
|
scheme?: string; |
|
|
|
|
|
|
|
|
basePath: string; |
|
|
|
|
|
|
|
|
basePath?: string; |
|
|
|
|
|
|
|
|
method: string; |
|
|
|
|
|
|
|
|
method?: string; |
|
|
|
|
|
|
|
|
url: string; |
|
|
|
|
|
|
|
|
url?: string; |
|
|
|
|
|
|
|
|
rawUrl: string; |
|
|
|
|
|
|
|
|
rawUrl?: string; |
|
|
|
|
|
|
|
|
cn: { |
|
|
cn: { |
|
|
language: Language; |
|
|
language: Language; |
|
@@ -122,39 +119,6 @@ export interface Middleware<Req extends RequestContext = RequestContext> { |
|
|
(req: Req): undefined | Response | Promise<undefined | Response>; |
|
|
(req: Req): undefined | Response | Promise<undefined | Response>; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
class ServerYasumiRequest extends http.IncomingMessage implements RequestContext { |
|
|
|
|
|
host = 'localhost'; |
|
|
|
|
|
|
|
|
|
|
|
scheme = 'http'; |
|
|
|
|
|
|
|
|
|
|
|
basePath = ''; |
|
|
|
|
|
|
|
|
|
|
|
backend = {} as BackendState; |
|
|
|
|
|
|
|
|
|
|
|
resource = undefined as unknown as BackendResource; |
|
|
|
|
|
|
|
|
|
|
|
resourceId?: string; |
|
|
|
|
|
|
|
|
|
|
|
query = new URLSearchParams(); |
|
|
|
|
|
|
|
|
|
|
|
body?: unknown; |
|
|
|
|
|
|
|
|
|
|
|
method = ''; |
|
|
|
|
|
|
|
|
|
|
|
url = ''; |
|
|
|
|
|
|
|
|
|
|
|
rawUrl = ''; |
|
|
|
|
|
|
|
|
|
|
|
readonly cn: { |
|
|
|
|
|
language: Language; |
|
|
|
|
|
mediaType: MediaType; |
|
|
|
|
|
charset: Charset; |
|
|
|
|
|
} = { |
|
|
|
|
|
language: en, |
|
|
|
|
|
mediaType: applicationJson, |
|
|
|
|
|
charset: utf8, |
|
|
|
|
|
}; |
|
|
|
|
|
} |
|
|
|
|
|
const getAllowedMiddlewares = <T extends v.BaseSchema>(resource: Resource<T>, mainResourceId: string) => { |
|
|
const getAllowedMiddlewares = <T extends v.BaseSchema>(resource: Resource<T>, mainResourceId: string) => { |
|
|
const middlewares = [] as [string, Middleware, v.BaseSchema?][]; |
|
|
const middlewares = [] as [string, Middleware, v.BaseSchema?][]; |
|
|
if (mainResourceId === '') { |
|
|
if (mainResourceId === '') { |
|
@@ -210,13 +174,13 @@ const getAllowedMiddlewares = <T extends v.BaseSchema>(resource: Resource<T>, ma |
|
|
|
|
|
|
|
|
const adjustRequestForContentNegotiation = (req: RequestContext, res: http.ServerResponse<RequestContext>) => { |
|
|
const adjustRequestForContentNegotiation = (req: RequestContext, res: http.ServerResponse<RequestContext>) => { |
|
|
const negotiator = new Negotiator(req); |
|
|
const negotiator = new Negotiator(req); |
|
|
const availableLanguages = Array.from(req.backend.app.languages); |
|
|
|
|
|
const availableCharsets = Array.from(req.backend.app.charsets); |
|
|
|
|
|
const availableMediaTypes = Array.from(req.backend.app.mediaTypes); |
|
|
|
|
|
|
|
|
const availableLanguages = Array.from(req.backend!.app.languages); |
|
|
|
|
|
const availableCharsets = Array.from(req.backend!.app.charsets); |
|
|
|
|
|
const availableMediaTypes = Array.from(req.backend!.app.mediaTypes); |
|
|
|
|
|
|
|
|
const languageCandidate = negotiator.language(availableLanguages.map((l) => l.name)) ?? req.backend.cn.language.name; |
|
|
|
|
|
const charsetCandidate = negotiator.charset(availableCharsets.map((l) => l.name)) ?? req.backend.cn.charset.name; |
|
|
|
|
|
const mediaTypeCandidate = negotiator.mediaType(availableMediaTypes.map((l) => l.name)) ?? req.backend.cn.mediaType.name; |
|
|
|
|
|
|
|
|
const languageCandidate = negotiator.language(availableLanguages.map((l) => l.name)) ?? req.backend!.cn.language.name; |
|
|
|
|
|
const charsetCandidate = negotiator.charset(availableCharsets.map((l) => l.name)) ?? req.backend!.cn.charset.name; |
|
|
|
|
|
const mediaTypeCandidate = negotiator.mediaType(availableMediaTypes.map((l) => l.name)) ?? req.backend!.cn.mediaType.name; |
|
|
|
|
|
|
|
|
// TODO refactor |
|
|
// TODO refactor |
|
|
const currentLanguage = availableLanguages.find((l) => l.name === languageCandidate); |
|
|
const currentLanguage = availableLanguages.find((l) => l.name === languageCandidate); |
|
@@ -255,9 +219,9 @@ const adjustRequestForContentNegotiation = (req: RequestContext, res: http.Serve |
|
|
|
|
|
|
|
|
const responseBodyCharset = availableCharsets.find((l) => l.name === charsetCandidate); |
|
|
const responseBodyCharset = availableCharsets.find((l) => l.name === charsetCandidate); |
|
|
if (typeof responseBodyCharset === 'undefined') { |
|
|
if (typeof responseBodyCharset === 'undefined') { |
|
|
const data = req.backend?.cn.language.bodies.encodingNotAcceptable(); |
|
|
|
|
|
const responseRaw = req.backend?.cn.mediaType.serialize(data); |
|
|
|
|
|
const response = typeof responseRaw !== 'undefined' ? req.backend?.cn.charset.encode(responseRaw) : undefined; |
|
|
|
|
|
|
|
|
const data = req.backend!.cn.language.bodies.encodingNotAcceptable(); |
|
|
|
|
|
const responseRaw = req.backend!.cn.mediaType.serialize(data); |
|
|
|
|
|
const response = typeof responseRaw !== 'undefined' ? req.backend!.cn.charset.encode(responseRaw) : undefined; |
|
|
res.writeHead(constants.HTTP_STATUS_NOT_ACCEPTABLE, { |
|
|
res.writeHead(constants.HTTP_STATUS_NOT_ACCEPTABLE, { |
|
|
'Content-Language': req.backend?.cn.language.name, |
|
|
'Content-Language': req.backend?.cn.language.name, |
|
|
'Content-Type': [ |
|
|
'Content-Type': [ |
|
@@ -283,18 +247,18 @@ export const createServer = (backendState: BackendState, serverParams = {} as Cr |
|
|
key: serverParams.key, |
|
|
key: serverParams.key, |
|
|
cert: serverParams.cert, |
|
|
cert: serverParams.cert, |
|
|
requestTimeout: serverParams.requestTimeout, |
|
|
requestTimeout: serverParams.requestTimeout, |
|
|
IncomingMessage: ServerYasumiRequest, |
|
|
|
|
|
}) |
|
|
}) |
|
|
: http.createServer({ |
|
|
: http.createServer({ |
|
|
requestTimeout: serverParams.requestTimeout, |
|
|
requestTimeout: serverParams.requestTimeout, |
|
|
IncomingMessage: ServerYasumiRequest, |
|
|
|
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
server.on('request', async (req: RequestContext, res) => { |
|
|
|
|
|
|
|
|
server.on('request', async (req: RequestContext, res: http.ServerResponse<RequestContext>) => { |
|
|
req.backend = backendState; |
|
|
req.backend = backendState; |
|
|
req.basePath = serverParams.basePath ?? ''; |
|
|
req.basePath = serverParams.basePath ?? ''; |
|
|
req.host = serverParams.host ?? 'localhost'; |
|
|
req.host = serverParams.host ?? 'localhost'; |
|
|
req.scheme = isHttps ? 'https' : 'http'; |
|
|
req.scheme = isHttps ? 'https' : 'http'; |
|
|
|
|
|
req.cn = req.backend.cn; |
|
|
|
|
|
|
|
|
adjustRequestForContentNegotiation(req, res); |
|
|
adjustRequestForContentNegotiation(req, res); |
|
|
|
|
|
|
|
|
try { |
|
|
try { |
|
@@ -373,7 +337,7 @@ export const createServer = (backendState: BackendState, serverParams = {} as Cr |
|
|
return; |
|
|
return; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const [, resourceRouteName, resourceId = ''] = req.url.split('/') ?? []; |
|
|
|
|
|
|
|
|
const [, resourceRouteName, resourceId = ''] = req.url?.split('/') ?? []; |
|
|
const resource = Array.from(req.backend.app.resources).find((r) => r.state!.routeName === resourceRouteName); |
|
|
const resource = Array.from(req.backend.app.resources).find((r) => r.state!.routeName === resourceRouteName); |
|
|
if (typeof resource === 'undefined') { |
|
|
if (typeof resource === 'undefined') { |
|
|
res.statusCode = constants.HTTP_STATUS_NOT_FOUND; |
|
|
res.statusCode = constants.HTTP_STATUS_NOT_FOUND; |
|
@@ -414,8 +378,8 @@ export const createServer = (backendState: BackendState, serverParams = {} as Cr |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (schema) { |
|
|
if (schema) { |
|
|
const availableSerializers = Array.from(req.backend.app.mediaTypes); |
|
|
|
|
|
const availableCharsets = Array.from(req.backend.app.charsets); |
|
|
|
|
|
|
|
|
const availableSerializers = Array.from(req.backend!.app.mediaTypes); |
|
|
|
|
|
const availableCharsets = Array.from(req.backend!.app.charsets); |
|
|
const contentTypeHeader = req.headers['content-type'] ?? 'application/octet-stream'; |
|
|
const contentTypeHeader = req.headers['content-type'] ?? 'application/octet-stream'; |
|
|
const fragments = contentTypeHeader.split(';'); |
|
|
const fragments = contentTypeHeader.split(';'); |
|
|
const mediaType = fragments[0]; |
|
|
const mediaType = fragments[0]; |
|
|