finalized project
This commit is contained in:
1 parent
b8b3e348e8
commit
4b87e9d20c
15 files changed
+1027
-4
No files matched your search
@@ -0,0 +1,49 @@
|
||||
# 3Dverse test task
|
||||
|
||||
## Solution
|
||||
|
||||
### Technology choosen
|
||||
|
||||
1. [deno](https://deno.com/) instead of nodejs as platform.
|
||||
2. [deno kv](https://deno.com/kv) for database.
|
||||
3. [nix](https://nixos.org/) flake as build system
|
||||
|
||||
|
||||
### Development environment
|
||||
If the developper has nix flake installed. `nix develop` will install
|
||||
all the system dependencies. Otherwise, `deno` is required; hey,
|
||||
and psrecord are two useful tool to benchmark the server.
|
||||
|
||||
### Running the tests.
|
||||
|
||||
The best way to build the test is to run `nix build .#test` which
|
||||
will output a report in `./result`. Otherwise,
|
||||
`deno test --unstable --allow-read=tests tests` should run all the tests.
|
||||
|
||||
|
||||
### Running the project.
|
||||
|
||||
|
||||
The main file to run the server is `src/server.ts`. To command line
|
||||
`deno run --unstable --allow-net src/server.ts` will run the server locally.
|
||||
One can add the flag `--v8-flags=--max-heap-size=50` to limit the
|
||||
allowed memory to, in this example, 50mb.
|
||||
|
||||
|
||||
In a similar fashion, it is possible to run the application without
|
||||
checking out the code using the command line
|
||||
|
||||
```shell
|
||||
deno run --v8-flags=--max-heap-size=50 --unstable --allow-net https://gitlab.com/didiercrunch/3dverse-testtask/-/raw/master/src/server.ts
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
title: Persist file
|
||||
client->>RestAPI: HTTP request
|
||||
RestAPI->>FileService: Persist file metadata
|
||||
FileService->>BlobService: Persist file
|
||||
```
|
||||
@@ -6,7 +6,7 @@
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, flake-utils, }:
|
||||
outputs = { self, nixpkgs, flake-utils }:
|
||||
flake-utils.lib.eachDefaultSystem (system:
|
||||
let
|
||||
pkgs = nixpkgs.legacyPackages.${system};
|
||||
@@ -22,12 +22,28 @@
|
||||
installPhase = ''
|
||||
mv test_results.txt $out
|
||||
'';
|
||||
|
||||
|
||||
};
|
||||
swagger-ui-src = pkgs.stdenv.mkDerivation {
|
||||
name = "swagger-ui-src";
|
||||
src = pkgs.fetchzip {
|
||||
url = "https://github.com/swagger-api/swagger-ui/archive/refs/tags/v5.0.0.zip";
|
||||
hash = "sha256-M/P/teEMqQ0gW0k/mvsr/VnkzuTI82+OMc8PAxCQAKM=";
|
||||
};
|
||||
installPhase = ''
|
||||
cp -r dist $out
|
||||
'';
|
||||
};
|
||||
swagger-ui = pkgs.writeShellApplication {
|
||||
name = "swagger-ui";
|
||||
runtimeInputs = [swagger-ui-src pkgs.python311];
|
||||
text = ''
|
||||
exec ${pkgs.python311}/bin/python -m http.server -d ${swagger-ui-src} 7766
|
||||
'';
|
||||
};
|
||||
in {
|
||||
packages = {
|
||||
test = test;
|
||||
swagger-ui = swagger-ui;
|
||||
};
|
||||
devShells.default = pkgs.mkShell {
|
||||
buildInputs = with pkgs; [deno
|
||||
|
||||
@@ -10,6 +10,8 @@ export {
|
||||
TextLineStream,
|
||||
} from 'https://deno.land/std@0.191.0/streams/mod.ts';
|
||||
export {Status} from "https://deno.land/std@0.191.0/http/http_status.ts";
|
||||
export { oakCors } from 'https://deno.land/x/cors/mod.ts';
|
||||
|
||||
|
||||
// oak
|
||||
export { Application, Context, Router } from 'https://deno.land/x/oak/mod.ts';
|
||||
@@ -3,6 +3,7 @@ import { FileMetaData, FileService } from './services/mod.ts';
|
||||
import { readerFromStreamReader } from './deps.ts';
|
||||
import { asyncIterableToArray } from './utils.ts';
|
||||
import { Vector3D, WaveFrontService } from './services/interfaces.ts';
|
||||
import { bigAndUglySwagger } from "./stagger.ts";
|
||||
|
||||
type Handler = (ctx: Context) => void;
|
||||
|
||||
@@ -161,6 +162,7 @@ function deleteFileHandlerFactory(fileService: FileService): Handler {
|
||||
}
|
||||
|
||||
export function buildAPI(router: Router, fileService: FileService, waveFrontService: WaveFrontService): Router {
|
||||
router.get('/swagger', (ctx) => ctx.response.body = bigAndUglySwagger);
|
||||
router.get('/files', listFileHandlerFactory(fileService));
|
||||
router.get('/files/:fileId', getFileHandlerFactory(fileService));
|
||||
router.delete('/files/:fileId', deleteFileHandlerFactory(fileService));
|
||||
|
||||
+2
-1
@@ -1,4 +1,4 @@
|
||||
import { Application, Router } from './deps.ts';
|
||||
import { Application, Router, oakCors } from './deps.ts';
|
||||
import { buildAPI } from './restapi.ts';
|
||||
import { DenoKVFileService } from './services/mod.ts';
|
||||
import { WaveFrontServiceImpl } from './services/wavefrontService.ts';
|
||||
@@ -10,6 +10,7 @@ async function main() {
|
||||
const waveFrontService = new WaveFrontServiceImpl();
|
||||
const router = new Router({ prefix: '/v1' });
|
||||
|
||||
app.use(oakCors({ origin: /^.+localhost:7766$/ }));
|
||||
app.use(
|
||||
buildAPI(router, fileService, waveFrontService).routes(),
|
||||
);
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
export const bigAndUglySwagger = `
|
||||
openapi: 3.0.3
|
||||
info:
|
||||
title: Test task
|
||||
description: |-
|
||||
This should be swagger documentation.
|
||||
version: 1.0.11
|
||||
|
||||
paths: {}
|
||||
`
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
import type { CorsOptions, CorsOptionsDelegate } from "./types.ts";
|
||||
import { Cors } from "./cors.ts";
|
||||
|
||||
interface Req {
|
||||
method: string;
|
||||
headers: {
|
||||
get(headerKey: string): string | null | undefined;
|
||||
};
|
||||
}
|
||||
|
||||
interface Res {
|
||||
status?: number | string;
|
||||
headers: {
|
||||
get(headerKey: string): string | null | undefined;
|
||||
set(headerKey: string, headerValue: string): any;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* abcCors middleware wrapper
|
||||
* @param o CorsOptions | CorsOptionsDelegate
|
||||
* @link https://github.com/tajpouria/cors/blob/master/README.md#cors
|
||||
*/
|
||||
export const abcCors = <
|
||||
RequestT extends Req = any,
|
||||
ResponseT extends Res = any,
|
||||
MiddlewareT extends (
|
||||
next: (...args: any) => any,
|
||||
) => (context: { request: RequestT; response: ResponseT }) => any = any,
|
||||
>(
|
||||
o?: CorsOptions | CorsOptionsDelegate<RequestT>,
|
||||
) => {
|
||||
const corsOptionsDelegate = Cors.produceCorsOptionsDelegate<
|
||||
CorsOptionsDelegate<RequestT>
|
||||
>(o);
|
||||
|
||||
return ((abcNext) =>
|
||||
async (context) => {
|
||||
const next = () => abcNext(context);
|
||||
|
||||
try {
|
||||
const { request, response } = context;
|
||||
|
||||
const options = await corsOptionsDelegate(request);
|
||||
|
||||
const corsOptions = Cors.produceCorsOptions(options || {});
|
||||
const originDelegate = Cors.produceOriginDelegate(corsOptions);
|
||||
|
||||
if (originDelegate) {
|
||||
const requestMethod = request.method;
|
||||
const getRequestHeader = (headerKey: string) =>
|
||||
request.headers.get(headerKey);
|
||||
const getResponseHeader = (headerKey: string) =>
|
||||
response.headers.get(headerKey);
|
||||
const setResponseHeader = (headerKey: string, headerValue: string) =>
|
||||
response.headers.set(headerKey, headerValue);
|
||||
const setStatus = (
|
||||
statusCode: number,
|
||||
) => (response.status = statusCode);
|
||||
const end = () => {};
|
||||
|
||||
const origin = await originDelegate(getRequestHeader("origin"));
|
||||
|
||||
if (!origin) return next();
|
||||
else {
|
||||
corsOptions.origin = origin;
|
||||
|
||||
return new Cors({
|
||||
corsOptions,
|
||||
requestMethod,
|
||||
getRequestHeader,
|
||||
getResponseHeader,
|
||||
setResponseHeader,
|
||||
setStatus,
|
||||
next,
|
||||
end,
|
||||
}).configureHeaders();
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
return next();
|
||||
}) as MiddlewareT;
|
||||
};
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
import type { CorsOptions, CorsOptionsDelegate } from "./types.ts";
|
||||
import { Cors } from "./cors.ts";
|
||||
|
||||
interface Req {
|
||||
method: string;
|
||||
headers: {
|
||||
get(headerKey: string): string | null | undefined;
|
||||
};
|
||||
}
|
||||
|
||||
interface Res {
|
||||
status: (status: number) => any;
|
||||
headers: {
|
||||
get(headerKey: string): string | null | undefined;
|
||||
set(headerKey: string, headerValue: string): any;
|
||||
};
|
||||
send: (body: any) => any;
|
||||
end(...args: any): any;
|
||||
}
|
||||
|
||||
/**
|
||||
* attainCors middleware wrapper
|
||||
* @param o CorsOptions | CorsOptionsDelegate
|
||||
* @link https://github.com/tajpouria/cors/blob/master/README.md#cors
|
||||
*/
|
||||
export const attainCors = <
|
||||
RequestT extends Req = any,
|
||||
ResponseT extends Res = any,
|
||||
MiddlewareT extends (request: RequestT, response: ResponseT) => any = any,
|
||||
>(
|
||||
o?: CorsOptions | CorsOptionsDelegate<RequestT>,
|
||||
) => {
|
||||
const corsOptionsDelegate = Cors.produceCorsOptionsDelegate<
|
||||
CorsOptionsDelegate<RequestT>
|
||||
>(o);
|
||||
|
||||
return async function cors(request, response) {
|
||||
try {
|
||||
const fakeNext = () => undefined;
|
||||
const options = await corsOptionsDelegate(request);
|
||||
|
||||
const corsOptions = Cors.produceCorsOptions(options || {});
|
||||
const originDelegate = Cors.produceOriginDelegate(corsOptions);
|
||||
|
||||
if (originDelegate) {
|
||||
const requestMethod = request.method;
|
||||
const getRequestHeader = (headerKey: string) =>
|
||||
request.headers.get(headerKey);
|
||||
const getResponseHeader = (headerKey: string) =>
|
||||
response.headers.get(headerKey);
|
||||
const setResponseHeader = (headerKey: string, headerValue: string) =>
|
||||
response.headers.set(headerKey, headerValue);
|
||||
const setStatus = (statusCode: number) => response.status(statusCode);
|
||||
const end = response.end();
|
||||
|
||||
const origin = await originDelegate(getRequestHeader("origin"));
|
||||
|
||||
if (origin) {
|
||||
corsOptions.origin = origin;
|
||||
|
||||
new Cors({
|
||||
corsOptions,
|
||||
requestMethod,
|
||||
getRequestHeader,
|
||||
getResponseHeader,
|
||||
setResponseHeader,
|
||||
setStatus,
|
||||
next: fakeNext,
|
||||
end,
|
||||
}).configureHeaders();
|
||||
}
|
||||
}
|
||||
|
||||
if (request.method === "OPTIONS") {
|
||||
response.send("");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
} as MiddlewareT;
|
||||
};
|
||||
+291
@@ -0,0 +1,291 @@
|
||||
import type {
|
||||
CorsOptions,
|
||||
CorsOptionsDelegate,
|
||||
OriginDelegate,
|
||||
} from "./types.ts";
|
||||
|
||||
interface DefaultCorsOptions {
|
||||
origin: string;
|
||||
methods: string;
|
||||
preflightContinue: boolean;
|
||||
optionsSuccessStatus: number;
|
||||
}
|
||||
|
||||
interface CorsProps {
|
||||
corsOptions: ReturnType<typeof Cors.produceCorsOptions>;
|
||||
requestMethod: string;
|
||||
getRequestHeader: (headerKey: string) => string | null | undefined;
|
||||
getResponseHeader: (headerKey: string) => string | null | undefined;
|
||||
setResponseHeader: (headerKey: string, headerValue: string) => any;
|
||||
setStatus: (statusCode: number) => any;
|
||||
next: (...args: any) => any;
|
||||
end: (...args: any) => any;
|
||||
}
|
||||
|
||||
export class Cors {
|
||||
constructor(private props: CorsProps) {}
|
||||
|
||||
public static produceCorsOptions = (
|
||||
corsOptions: CorsOptions = {},
|
||||
defaultCorsOptions: DefaultCorsOptions = {
|
||||
origin: "*",
|
||||
methods: "GET,HEAD,PUT,PATCH,POST,DELETE",
|
||||
preflightContinue: false,
|
||||
optionsSuccessStatus: 204,
|
||||
},
|
||||
) => ({
|
||||
...defaultCorsOptions,
|
||||
...corsOptions,
|
||||
});
|
||||
|
||||
public static produceCorsOptionsDelegate = <
|
||||
OptionsCallbackT = CorsOptionsDelegate<any>,
|
||||
>(
|
||||
o?: CorsOptions | OptionsCallbackT,
|
||||
) =>
|
||||
typeof o === "function"
|
||||
? (o as OptionsCallbackT)
|
||||
: ((((_request: any) => o) as unknown) as OptionsCallbackT);
|
||||
|
||||
public static produceOriginDelegate = (
|
||||
corsOptions: CorsProps["corsOptions"],
|
||||
) => {
|
||||
if (corsOptions.origin) {
|
||||
if (typeof corsOptions.origin === "function") {
|
||||
return corsOptions.origin as OriginDelegate;
|
||||
}
|
||||
|
||||
return ((_requestOrigin) => corsOptions.origin) as OriginDelegate;
|
||||
}
|
||||
};
|
||||
|
||||
public static isOriginAllowed = (
|
||||
requestOrigin: string | null | undefined,
|
||||
allowedOrigin: CorsOptions["origin"],
|
||||
): boolean => {
|
||||
if (Array.isArray(allowedOrigin)) {
|
||||
return allowedOrigin.some((ao) =>
|
||||
Cors.isOriginAllowed(requestOrigin, ao)
|
||||
);
|
||||
} else if (typeof allowedOrigin === "string") {
|
||||
return requestOrigin === allowedOrigin;
|
||||
} else if (
|
||||
allowedOrigin instanceof RegExp &&
|
||||
typeof requestOrigin === "string"
|
||||
) {
|
||||
return allowedOrigin.test(requestOrigin);
|
||||
} else return !!allowedOrigin;
|
||||
};
|
||||
|
||||
public configureHeaders = () => {
|
||||
const {
|
||||
props: {
|
||||
corsOptions,
|
||||
requestMethod,
|
||||
setResponseHeader,
|
||||
setStatus,
|
||||
next,
|
||||
end,
|
||||
},
|
||||
configureOrigin,
|
||||
} = this;
|
||||
|
||||
if (
|
||||
typeof requestMethod === "string" &&
|
||||
requestMethod.toUpperCase() === "OPTIONS"
|
||||
) {
|
||||
configureOrigin()
|
||||
.configureCredentials()
|
||||
.configureMethods()
|
||||
.configureAllowedHeaders()
|
||||
.configureMaxAge()
|
||||
.configureExposedHeaders();
|
||||
|
||||
if (corsOptions.preflightContinue) return next();
|
||||
else {
|
||||
setStatus(corsOptions.optionsSuccessStatus);
|
||||
setResponseHeader("Content-Length", "0");
|
||||
|
||||
return end();
|
||||
}
|
||||
} else {
|
||||
configureOrigin().configureCredentials().configureExposedHeaders();
|
||||
|
||||
return next();
|
||||
}
|
||||
};
|
||||
|
||||
private configureOrigin = () => {
|
||||
const {
|
||||
props: { corsOptions, getRequestHeader, setResponseHeader },
|
||||
setVaryHeader,
|
||||
} = this;
|
||||
|
||||
if (!corsOptions.origin || corsOptions.origin === "*") {
|
||||
setResponseHeader("Access-Control-Allow-Origin", "*");
|
||||
} else if (typeof corsOptions.origin === "string") {
|
||||
setResponseHeader("Access-Control-Allow-Origin", corsOptions.origin);
|
||||
setVaryHeader("Origin");
|
||||
} else {
|
||||
const requestOrigin = getRequestHeader("origin") ??
|
||||
getRequestHeader("Origin");
|
||||
|
||||
setResponseHeader(
|
||||
"Access-Control-Allow-Origin",
|
||||
Cors.isOriginAllowed(requestOrigin, corsOptions.origin)
|
||||
? (requestOrigin as string)
|
||||
: "false",
|
||||
);
|
||||
setVaryHeader("Origin");
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
private configureCredentials = () => {
|
||||
const { corsOptions, setResponseHeader } = this.props;
|
||||
|
||||
if (corsOptions.credentials === true) {
|
||||
setResponseHeader("Access-Control-Allow-Credentials", "true");
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
private configureMethods = () => {
|
||||
const { corsOptions, setResponseHeader } = this.props;
|
||||
|
||||
let methods = corsOptions.methods;
|
||||
|
||||
setResponseHeader(
|
||||
"Access-Control-Allow-Methods",
|
||||
Array.isArray(methods) ? methods.join(",") : methods,
|
||||
);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
private configureAllowedHeaders = () => {
|
||||
const {
|
||||
props: { corsOptions, getRequestHeader, setResponseHeader },
|
||||
setVaryHeader,
|
||||
} = this;
|
||||
|
||||
let allowedHeaders = corsOptions.allowedHeaders;
|
||||
|
||||
if (!allowedHeaders) {
|
||||
allowedHeaders = getRequestHeader("access-control-request-headers") ??
|
||||
getRequestHeader("Access-Control-Request-Headers") ??
|
||||
undefined;
|
||||
|
||||
setVaryHeader("Access-Control-request-Headers");
|
||||
}
|
||||
|
||||
if (allowedHeaders?.length) {
|
||||
setResponseHeader(
|
||||
"Access-Control-Allow-Headers",
|
||||
Array.isArray(allowedHeaders)
|
||||
? allowedHeaders.join(",")
|
||||
: allowedHeaders,
|
||||
);
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
private configureMaxAge = () => {
|
||||
const { corsOptions, setResponseHeader } = this.props;
|
||||
|
||||
const maxAge = (typeof corsOptions.maxAge === "number" ||
|
||||
typeof corsOptions.maxAge === "string") &&
|
||||
corsOptions.maxAge.toString();
|
||||
|
||||
if (maxAge && maxAge.length) {
|
||||
setResponseHeader("Access-Control-Max-Age", maxAge);
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
private configureExposedHeaders = () => {
|
||||
const { corsOptions, setResponseHeader } = this.props;
|
||||
|
||||
let exposedHeaders = corsOptions.exposedHeaders;
|
||||
|
||||
if (exposedHeaders?.length) {
|
||||
setResponseHeader(
|
||||
"Access-Control-Expose-Headers",
|
||||
Array.isArray(exposedHeaders)
|
||||
? exposedHeaders.join(",")
|
||||
: exposedHeaders,
|
||||
);
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
private setVaryHeader = (field: string) => {
|
||||
const {
|
||||
props: { getResponseHeader, setResponseHeader },
|
||||
appendVaryHeader,
|
||||
} = this;
|
||||
|
||||
let existingHeader = getResponseHeader("Vary") ?? "";
|
||||
|
||||
if (
|
||||
existingHeader &&
|
||||
typeof existingHeader === "string" &&
|
||||
(existingHeader = appendVaryHeader(existingHeader, field))
|
||||
) {
|
||||
setResponseHeader("Vary", existingHeader);
|
||||
}
|
||||
};
|
||||
|
||||
private appendVaryHeader = (header: string, field: string) => {
|
||||
const { parseVaryHeader } = this;
|
||||
|
||||
if (header === "*") return header;
|
||||
|
||||
let varyHeader = header;
|
||||
const fields = parseVaryHeader(field);
|
||||
const headers = parseVaryHeader(header.toLocaleLowerCase());
|
||||
|
||||
if (fields.includes("*") || headers.includes("*")) return "*";
|
||||
|
||||
fields.forEach((field) => {
|
||||
const fld = field.toLowerCase();
|
||||
|
||||
if (headers.includes(fld)) {
|
||||
headers.push(fld);
|
||||
varyHeader = varyHeader ? `${varyHeader}, ${field}` : field;
|
||||
}
|
||||
});
|
||||
|
||||
return varyHeader;
|
||||
};
|
||||
|
||||
private parseVaryHeader = (header: string) => {
|
||||
let end = 0;
|
||||
const list = [];
|
||||
let start = 0;
|
||||
|
||||
for (let i = 0, len = header.length; i < len; i++) {
|
||||
switch (header.charCodeAt(i)) {
|
||||
case 0x20 /* */:
|
||||
if (start === end) start = end = i + 1;
|
||||
break;
|
||||
case 0x2c /* , */:
|
||||
list.push(header.substring(start, end));
|
||||
start = end = i + 1;
|
||||
break;
|
||||
default:
|
||||
end = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
list.push(header.substring(start, end));
|
||||
|
||||
return list;
|
||||
};
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
import type { CorsOptions, CorsOptionsDelegate } from "./types.ts";
|
||||
import { Cors } from "./cors.ts";
|
||||
|
||||
interface Req {
|
||||
serverRequest: {
|
||||
method: string;
|
||||
headers: {
|
||||
get(headerKey: string): string | null | undefined;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
interface Res {
|
||||
status?: number | string;
|
||||
headers: {
|
||||
get(headerKey: string): string | null | undefined;
|
||||
set(headerKey: string, headerValue: string): any;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* mithCors middleware wrapper
|
||||
* @param o CorsOptions | CorsOptionsDelegate
|
||||
* @link https://github.com/tajpouria/cors/blob/master/README.md#cors
|
||||
*/
|
||||
export const mithCors = <
|
||||
RequestT extends Req = any,
|
||||
ResponseT extends Res = any,
|
||||
MiddlewareT extends (
|
||||
request: RequestT,
|
||||
response: ResponseT,
|
||||
next: (...args: any) => any,
|
||||
) => any = any,
|
||||
>(
|
||||
o?: CorsOptions | CorsOptionsDelegate<RequestT>,
|
||||
) => {
|
||||
const corsOptionsDelegate = Cors.produceCorsOptionsDelegate<
|
||||
CorsOptionsDelegate<RequestT>
|
||||
>(o);
|
||||
|
||||
return (async (request, response, next) => {
|
||||
const serverRequest = request.serverRequest;
|
||||
try {
|
||||
const options = await corsOptionsDelegate(request);
|
||||
|
||||
const corsOptions = Cors.produceCorsOptions(options || {});
|
||||
const originDelegate = Cors.produceOriginDelegate(corsOptions);
|
||||
|
||||
if (originDelegate) {
|
||||
const requestMethod = serverRequest.method;
|
||||
const getRequestHeader = (headerKey: string) =>
|
||||
serverRequest.headers.get(headerKey);
|
||||
const getResponseHeader = (headerKey: string) =>
|
||||
response.headers.get(headerKey);
|
||||
const setResponseHeader = (headerKey: string, headerValue: string) =>
|
||||
response.headers.set(headerKey, headerValue);
|
||||
const setStatus = (
|
||||
statusCode: number,
|
||||
) => (response.status = statusCode);
|
||||
const end = () => {};
|
||||
|
||||
const origin = await originDelegate(getRequestHeader("origin"));
|
||||
|
||||
if (!origin) return next();
|
||||
else {
|
||||
corsOptions.origin = origin;
|
||||
|
||||
return new Cors({
|
||||
corsOptions,
|
||||
requestMethod,
|
||||
getRequestHeader,
|
||||
getResponseHeader,
|
||||
setResponseHeader,
|
||||
setStatus,
|
||||
next,
|
||||
end,
|
||||
}).configureHeaders();
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
return next();
|
||||
}) as MiddlewareT;
|
||||
};
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
export * from "./types.ts";
|
||||
|
||||
export { oakCors } from "./oakCors.ts";
|
||||
export { abcCors } from "./abcCors.ts";
|
||||
export { opineCors } from "./opineCors.ts";
|
||||
export { mithCors } from "./mithCors.ts";
|
||||
export { attainCors } from "./attainCors.ts";
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
import type { CorsOptions, CorsOptionsDelegate } from "./types.ts";
|
||||
import { Cors } from "./cors.ts";
|
||||
|
||||
interface Req {
|
||||
method: string;
|
||||
headers: {
|
||||
get(headerKey: string): string | null | undefined;
|
||||
};
|
||||
}
|
||||
|
||||
interface Res {
|
||||
status?: number | string;
|
||||
headers: {
|
||||
get(headerKey: string): string | null | undefined;
|
||||
set(headerKey: string, headerValue: string): any;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* oakCors middleware wrapper
|
||||
* @param o CorsOptions | CorsOptionsDelegate
|
||||
* @link https://github.com/tajpouria/cors/blob/master/README.md#cors
|
||||
*/
|
||||
export const oakCors = <
|
||||
RequestT extends Req = any,
|
||||
ResponseT extends Res = any,
|
||||
MiddlewareT extends (
|
||||
context: { request: RequestT; response: ResponseT },
|
||||
next: (...args: any) => any,
|
||||
) => any = any,
|
||||
>(
|
||||
o?: CorsOptions | CorsOptionsDelegate<RequestT>,
|
||||
) => {
|
||||
const corsOptionsDelegate = Cors.produceCorsOptionsDelegate<
|
||||
CorsOptionsDelegate<RequestT>
|
||||
>(o);
|
||||
|
||||
return (async ({ request, response }, next) => {
|
||||
try {
|
||||
const options = await corsOptionsDelegate(request);
|
||||
|
||||
const corsOptions = Cors.produceCorsOptions(options || {});
|
||||
const originDelegate = Cors.produceOriginDelegate(corsOptions);
|
||||
|
||||
if (originDelegate) {
|
||||
const requestMethod = request.method;
|
||||
const getRequestHeader = (headerKey: string) =>
|
||||
request.headers.get(headerKey);
|
||||
const getResponseHeader = (headerKey: string) =>
|
||||
response.headers.get(headerKey);
|
||||
const setResponseHeader = (headerKey: string, headerValue: string) =>
|
||||
response.headers.set(headerKey, headerValue);
|
||||
const setStatus = (
|
||||
statusCode: number,
|
||||
) => (response.status = statusCode);
|
||||
const end = () => {};
|
||||
|
||||
const origin = await originDelegate(getRequestHeader("origin"));
|
||||
|
||||
if (!origin) next();
|
||||
else {
|
||||
corsOptions.origin = origin;
|
||||
|
||||
return new Cors({
|
||||
corsOptions,
|
||||
requestMethod,
|
||||
getRequestHeader,
|
||||
getResponseHeader,
|
||||
setResponseHeader,
|
||||
setStatus,
|
||||
next,
|
||||
end,
|
||||
}).configureHeaders();
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
next();
|
||||
}) as MiddlewareT;
|
||||
};
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
import type { CorsOptions, CorsOptionsDelegate } from "./types.ts";
|
||||
import { Cors } from "./cors.ts";
|
||||
|
||||
interface Req {
|
||||
method: string;
|
||||
headers: {
|
||||
get(name: string): string | null | undefined;
|
||||
};
|
||||
}
|
||||
|
||||
interface Res {
|
||||
setStatus(code: any): this;
|
||||
set(headerKey: string, headerValue: string): this;
|
||||
get(headerKey: string): string | null | undefined;
|
||||
end(...args: any): any;
|
||||
}
|
||||
|
||||
/**
|
||||
* opineCors middleware wrapper
|
||||
* @param o CorsOptions | CorsOptionsDelegate
|
||||
* @link https://github.com/tajpouria/cors/blob/master/README.md#cors
|
||||
*/
|
||||
export const opineCors = <
|
||||
RequestT extends Req = Req,
|
||||
ResponseT extends Res = any,
|
||||
MiddlewareT extends (
|
||||
request: RequestT,
|
||||
response: ResponseT,
|
||||
next: (...args: any) => any,
|
||||
) => any = any,
|
||||
>(
|
||||
o?: CorsOptions | CorsOptionsDelegate<RequestT>,
|
||||
) => {
|
||||
const corsOptionsDelegate = Cors.produceCorsOptionsDelegate<
|
||||
CorsOptionsDelegate<RequestT>
|
||||
>(o);
|
||||
|
||||
return (async (request, response, next) => {
|
||||
try {
|
||||
const options = await corsOptionsDelegate(request);
|
||||
|
||||
const corsOptions = Cors.produceCorsOptions(options || {});
|
||||
const originDelegate = Cors.produceOriginDelegate(corsOptions);
|
||||
|
||||
if (originDelegate) {
|
||||
const requestMethod = request.method;
|
||||
const getRequestHeader = (headerKey: string) =>
|
||||
request.headers.get(headerKey);
|
||||
const getResponseHeader = (headerKey: string) =>
|
||||
response.get(headerKey);
|
||||
const setResponseHeader = (headerKey: string, headerValue: string) =>
|
||||
response.set(headerKey, headerValue);
|
||||
const setStatus = (statusCode: number) =>
|
||||
response.setStatus(statusCode);
|
||||
const end = () => response.end();
|
||||
|
||||
const origin = await originDelegate(getRequestHeader("origin"));
|
||||
|
||||
if (!origin) return next();
|
||||
else {
|
||||
corsOptions.origin = origin;
|
||||
|
||||
return new Cors({
|
||||
corsOptions,
|
||||
requestMethod,
|
||||
getRequestHeader,
|
||||
getResponseHeader,
|
||||
setResponseHeader,
|
||||
setStatus,
|
||||
next,
|
||||
end,
|
||||
}).configureHeaders();
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
return next();
|
||||
}) as MiddlewareT;
|
||||
};
|
||||
+228
@@ -0,0 +1,228 @@
|
||||
/**
|
||||
* CorsOptions
|
||||
*
|
||||
* An Object that describes how CORS middleware should behave. The default configuration is the equivalent of:
|
||||
*
|
||||
* ```ts
|
||||
* {
|
||||
* "origin": "*",
|
||||
* "methods": "GET,HEAD,PUT,PATCH,POST,DELETE",
|
||||
* "preflightContinue": false,
|
||||
* "optionsSuccessStatus": 204,
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @link https://github.com/tajpouria/cors#configuration-options
|
||||
*/
|
||||
export type CorsOptions = {
|
||||
/**
|
||||
* Configures the Access-Control-Allow-Origin CORS header.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* - Boolean
|
||||
*
|
||||
* set origin to true to reflect the request origin, as defined by req.header('Origin'):
|
||||
*
|
||||
* ```ts
|
||||
* app.use(cors({ origin: true }));
|
||||
*
|
||||
* ```
|
||||
* or set it to false to disable CORS:
|
||||
*
|
||||
* ```ts
|
||||
* app.use(cors({ origin: false }));
|
||||
*
|
||||
* ```
|
||||
*
|
||||
* - String
|
||||
*
|
||||
* set origin to a specific origin. For example if you set it to "http://example.com" only requests from "http://example.com" will be allowed:
|
||||
*
|
||||
* ```ts
|
||||
* app.use(cors({ origin: 'http://example.com' }));
|
||||
*
|
||||
* ```
|
||||
*
|
||||
* - RegExp
|
||||
*
|
||||
* set origin to a regular expression pattern which will be used to test the request origin. If it's a match, the request origin will be reflected. For example the pattern /example\.com$/ will reflect any request that is coming from an origin ending with "example.com":
|
||||
*
|
||||
* ```ts
|
||||
* app.use(cors({ origin: /example\.com$/ }));
|
||||
*
|
||||
* ```
|
||||
*
|
||||
* - Array
|
||||
*
|
||||
* set origin to an array of valid origins. Each origin can be a String or a RegExp. For example ["http://example1.com", /\.example2\.com$/] will accept any request from "http://example1.com" or from a subdomain of "example2.com":
|
||||
*
|
||||
* ```ts
|
||||
* app.use(cors({ origin: ["http://example1.com", /\.example2\.com$/] }));
|
||||
*
|
||||
* ```
|
||||
*
|
||||
* - Function
|
||||
*
|
||||
* set origin to a function implementing some custom logic. The function takes the request origin as the first parameter and a callback (called as callback(err, origin), where origin is a non-function value of the origin option) as the second:
|
||||
*
|
||||
* ```ts
|
||||
* app.use(cors({
|
||||
* origin: async (requestOrigin) => {
|
||||
* await loadOriginsFromDataBase(); // Simulate asynchronous task
|
||||
* return ["http://example1.com", /\.example2\.com$/];
|
||||
* },
|
||||
* }));
|
||||
*
|
||||
* ```
|
||||
*
|
||||
*/
|
||||
origin?: boolean | string | RegExp | (string | RegExp)[] | OriginDelegate;
|
||||
|
||||
/**
|
||||
* Configures the Access-Control-Allow-Methods CORS header.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* - String
|
||||
*
|
||||
* Expects a comma-delimited string (ex: 'GET,PUT,POST'):
|
||||
*
|
||||
* ```ts
|
||||
* app.use(cors({ methods: 'GET,PUT,POST' }));
|
||||
*
|
||||
* ```
|
||||
*
|
||||
* - Array
|
||||
*
|
||||
* or an array (ex: ['GET', 'PUT', 'POST'])
|
||||
*
|
||||
* ```ts
|
||||
* app.use(cors({ methods: ['GET', 'PUT', 'POST'] }));
|
||||
*
|
||||
* ```
|
||||
*
|
||||
*/
|
||||
methods?: string | string[];
|
||||
|
||||
/**
|
||||
* Configures the Access-Control-Allow-Headers CORS header. If not specified, defaults to reflecting the headers specified in the request's Access-Control-Request-Headers header.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* - String
|
||||
*
|
||||
* Expects a comma-delimited string (ex: 'Content-Type,Authorization'):
|
||||
*
|
||||
* ```ts
|
||||
* app.use(cors({ allowedHeaders: 'Content-Type,Authorization' }));
|
||||
*
|
||||
* ```
|
||||
*
|
||||
* - Array
|
||||
*
|
||||
* or an array (ex: ['Content-Type', 'Authorization']):
|
||||
*
|
||||
* ```ts
|
||||
* app.use(cors({ allowedHeaders: ['Content-Type', 'Authorization'] }));
|
||||
*
|
||||
* ```
|
||||
*
|
||||
*/
|
||||
allowedHeaders?: string | string[];
|
||||
|
||||
/**
|
||||
* Configures the Access-Control-Expose-Headers CORS header. If not specified, no custom headers are exposed.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* - String
|
||||
*
|
||||
* Expects a comma-delimited string (ex: 'Content-Range,X-Content-Range'):
|
||||
*
|
||||
* ```ts
|
||||
* app.use(cors({ exposedHeaders: 'Content-Range,X-Content-Range' }));
|
||||
*
|
||||
* ```
|
||||
*
|
||||
*/
|
||||
exposedHeaders?: string | string[];
|
||||
|
||||
/**
|
||||
* Configures the Access-Control-Allow-Credentials CORS header. It is omitted by default.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* - Boolean
|
||||
*
|
||||
* Set to true to pass the header:
|
||||
*
|
||||
* ```ts
|
||||
* app.use(cors({ credentials: true }));
|
||||
*
|
||||
* ```
|
||||
*
|
||||
*/
|
||||
credentials?: boolean;
|
||||
|
||||
/**
|
||||
* Configures the Access-Control-Max-Age CORS header. It is omitted by default.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* - Number
|
||||
*
|
||||
* Set to an integer to pass the header:
|
||||
*
|
||||
* ```ts
|
||||
* app.use(cors({ maxAge: 1 }));
|
||||
*
|
||||
* ```
|
||||
*
|
||||
*/
|
||||
maxAge?: number;
|
||||
|
||||
/**
|
||||
* Pass the CORS preflight response to the next handler:
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* - Boolean
|
||||
*
|
||||
* ```ts
|
||||
* app.use(cors({ preflightContinue: true }));
|
||||
*
|
||||
* ```
|
||||
*
|
||||
*/
|
||||
preflightContinue?: boolean;
|
||||
|
||||
/**
|
||||
* Provides a status code to use for successful OPTIONS requests, since some legacy browsers (IE11, various SmartTVs) choke on 204.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* - Number
|
||||
*
|
||||
* ```ts
|
||||
* app.use(cors({ optionsSuccessStatus: 200 }));
|
||||
*
|
||||
* ```
|
||||
*
|
||||
*/
|
||||
optionsSuccessStatus?: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* @link https://github.com/tajpouria/cors#configuring-cors-w-dynamic-origin
|
||||
*/
|
||||
export type OriginDelegate = (
|
||||
requestOrigin: string | undefined | null,
|
||||
) => CorsOptions["origin"] | void | Promise<CorsOptions["origin"] | void>;
|
||||
|
||||
/**
|
||||
* @link https://github.com/tajpouria/cors#configuring-cors-asynchronously
|
||||
*/
|
||||
export type CorsOptionsDelegate<RequestT = any> = (
|
||||
request: RequestT,
|
||||
) => CorsOptions | void | Promise<CorsOptions | void>;
|
||||
Vendored
+1
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"imports": {
|
||||
"https://deno.land/x/cors/mod.ts": "./deno.land/x/cors@v1.2.2/mod.ts",
|
||||
"https://deno.land/x/oak/mod.ts": "./deno.land/x/oak@v12.1.0/mod.ts",
|
||||
"https://deno.land/std/testing/asserts.ts": "./deno.land/std@0.191.0/testing/asserts.ts",
|
||||
"https://deno.land/": "./deno.land/"
|
||||
|
||||
Reference in new issue
Block a user