implement basic JSON parsing
This commit is contained in:
parent
1033a29aa0
commit
de396de6b9
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"editor.formatOnSave": true
|
||||||
|
}
|
|
@ -1,3 +1,17 @@
|
||||||
import { foo } from "../src/utils";
|
import { endpoint } from "../src";
|
||||||
|
import { startServer } from "../src/core/server";
|
||||||
|
|
||||||
console.log(foo());
|
type Data = {
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
startServer(
|
||||||
|
endpoint<void, Data>(async (req) => {
|
||||||
|
return {
|
||||||
|
status: 200,
|
||||||
|
body: {
|
||||||
|
name: "foo",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
|
@ -13,5 +13,8 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^17.0.35",
|
"@types/node": "^17.0.35",
|
||||||
"esbuild": "^0.14.39"
|
"esbuild": "^0.14.39"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"tslog": "^3.3.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
1
src/compose.ts
Normal file
1
src/compose.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export type Server<Request, Response> = (request: Request) => Promise<Response>;
|
52
src/core/bufferedServer.ts
Normal file
52
src/core/bufferedServer.ts
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
import { Logger } from "tslog";
|
||||||
|
import type { Server } from "../compose";
|
||||||
|
import type { CoreRequest } from "./server";
|
||||||
|
|
||||||
|
export type Request = Omit<CoreRequest, "response">;
|
||||||
|
|
||||||
|
export type BufferedResponse = {
|
||||||
|
status: number;
|
||||||
|
body: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function bufferedResponse(
|
||||||
|
app: Server<Request, BufferedResponse>,
|
||||||
|
): Server<CoreRequest, void> {
|
||||||
|
return async function bufferedResp(request) {
|
||||||
|
const out = await app(request);
|
||||||
|
request.response.statusCode = out.status;
|
||||||
|
request.response.write(out.body);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export type BufferedRequest = Request & {
|
||||||
|
body: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function bufferedRequest<Response>(
|
||||||
|
app: Server<BufferedRequest, Response>,
|
||||||
|
): Server<Request, Response> {
|
||||||
|
return function bufferedReq(request) {
|
||||||
|
const logger = new Logger();
|
||||||
|
let body = "";
|
||||||
|
const response = new Promise<Response>((resolve) => {
|
||||||
|
request.request.on("data", (chunk) => {
|
||||||
|
body += chunk.toString();
|
||||||
|
});
|
||||||
|
request.request.on("end", () => {
|
||||||
|
app({
|
||||||
|
...request,
|
||||||
|
body,
|
||||||
|
})
|
||||||
|
.then((out) => {
|
||||||
|
resolve(out);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
logger.error("App did not return a response", err);
|
||||||
|
resolve(undefined);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return response;
|
||||||
|
};
|
||||||
|
}
|
48
src/core/server.ts
Normal file
48
src/core/server.ts
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
import { createServer } from "http";
|
||||||
|
import type { IncomingMessage, ServerResponse } from "http";
|
||||||
|
import { Logger } from "tslog";
|
||||||
|
import { Server } from "../compose";
|
||||||
|
|
||||||
|
/** A basic request. */
|
||||||
|
export type CoreRequest = {
|
||||||
|
/** The full URL for the request. */
|
||||||
|
url: string;
|
||||||
|
/** The HTTP method for the request. */
|
||||||
|
method: string;
|
||||||
|
/** The headers associated with the request. */
|
||||||
|
headers: { [key: string]: string | string[] };
|
||||||
|
/** The request body. */
|
||||||
|
request: IncomingMessage;
|
||||||
|
/** The response. */
|
||||||
|
response: ServerResponse;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function startServer(app: Server<CoreRequest, void>) {
|
||||||
|
const logger = new Logger();
|
||||||
|
const server = createServer((req, res) => {
|
||||||
|
logger.info(req.method, req.url);
|
||||||
|
app({
|
||||||
|
url: req.url,
|
||||||
|
method: req.method,
|
||||||
|
headers: req.headers,
|
||||||
|
request: req,
|
||||||
|
response: res,
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
logger.debug("request processed successfully");
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
res.statusCode = 500;
|
||||||
|
logger.error("server throw an unexpected error", error);
|
||||||
|
res.write("Error!\n");
|
||||||
|
res.write(JSON.stringify(error));
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
res.end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
server.on("clientError", (err, socket) => {
|
||||||
|
socket.end("HTTP/1.1 400 Bad CoreRequest\r\n\r\n");
|
||||||
|
});
|
||||||
|
server.listen(8000);
|
||||||
|
}
|
17
src/index.ts
17
src/index.ts
|
@ -1,9 +1,10 @@
|
||||||
import http from "http";
|
import type { Server } from "./compose";
|
||||||
|
import { bufferedRequest, bufferedResponse } from "./core/bufferedServer";
|
||||||
|
import type { CoreRequest } from "./core/server";
|
||||||
|
import { jsonRequest, JSONRequest, jsonResponse, JSONResponse } from "./json";
|
||||||
|
|
||||||
const server = http.createServer((req, res) => {
|
export function endpoint<ReqData, RespData>(
|
||||||
res.end();
|
app: Server<JSONRequest<ReqData>, JSONResponse<RespData>>,
|
||||||
});
|
): Server<CoreRequest, void> {
|
||||||
server.on('clientError', (err, socket) => {
|
return bufferedResponse(bufferedRequest(jsonResponse(jsonRequest(app))));
|
||||||
socket.end('HTTP/1.1 400 Bad Request\r\n\r\n');
|
}
|
||||||
});
|
|
||||||
server.listen(8000);
|
|
||||||
|
|
32
src/json.ts
Normal file
32
src/json.ts
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
import { Server } from "./compose";
|
||||||
|
import type { BufferedRequest, BufferedResponse } from "./core/bufferedServer";
|
||||||
|
|
||||||
|
export type JSONRequest<Data> = Omit<BufferedRequest, "body"> & { body: Data };
|
||||||
|
|
||||||
|
export type JSONResponse<Data> = Omit<BufferedResponse, "body"> & {
|
||||||
|
body: Data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function jsonResponse<Request, Data>(
|
||||||
|
app: Server<Request, JSONResponse<Data>>,
|
||||||
|
): Server<Request, BufferedResponse> {
|
||||||
|
return async function jsonResp(request) {
|
||||||
|
const out = await app(request);
|
||||||
|
return {
|
||||||
|
...out,
|
||||||
|
body: JSON.stringify(out.body),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function jsonRequest<Response, Data>(
|
||||||
|
app: Server<JSONRequest<Data | undefined>, Response>,
|
||||||
|
): Server<BufferedRequest, Response> {
|
||||||
|
return async function jsonReq(request) {
|
||||||
|
let body: any | undefined;
|
||||||
|
if (request.body.length > 0) {
|
||||||
|
body = JSON.parse(request.body);
|
||||||
|
}
|
||||||
|
return app({ ...request, body });
|
||||||
|
};
|
||||||
|
}
|
0
src/routing/router.ts
Normal file
0
src/routing/router.ts
Normal file
|
@ -1,7 +0,0 @@
|
||||||
export function foo() {
|
|
||||||
return "bar";
|
|
||||||
}
|
|
||||||
|
|
||||||
export function baz() {
|
|
||||||
return "baz";
|
|
||||||
}
|
|
25
yarn.lock
25
yarn.lock
|
@ -7,6 +7,11 @@
|
||||||
resolved "https://registry.npmjs.org/@types/node/-/node-17.0.35.tgz"
|
resolved "https://registry.npmjs.org/@types/node/-/node-17.0.35.tgz"
|
||||||
integrity sha512-vu1SrqBjbbZ3J6vwY17jBs8Sr/BKA+/a/WtjRG+whKg1iuLFOosq872EXS0eXWILdO36DHQQeku/ZcL6hz2fpg==
|
integrity sha512-vu1SrqBjbbZ3J6vwY17jBs8Sr/BKA+/a/WtjRG+whKg1iuLFOosq872EXS0eXWILdO36DHQQeku/ZcL6hz2fpg==
|
||||||
|
|
||||||
|
buffer-from@^1.0.0:
|
||||||
|
version "1.1.2"
|
||||||
|
resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5"
|
||||||
|
integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==
|
||||||
|
|
||||||
esbuild-android-64@0.14.39:
|
esbuild-android-64@0.14.39:
|
||||||
version "0.14.39"
|
version "0.14.39"
|
||||||
resolved "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.14.39.tgz#09f12a372eed9743fd77ff6d889ac14f7b340c21"
|
resolved "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.14.39.tgz#09f12a372eed9743fd77ff6d889ac14f7b340c21"
|
||||||
|
@ -132,3 +137,23 @@ esbuild@^0.14.39:
|
||||||
esbuild-windows-32 "0.14.39"
|
esbuild-windows-32 "0.14.39"
|
||||||
esbuild-windows-64 "0.14.39"
|
esbuild-windows-64 "0.14.39"
|
||||||
esbuild-windows-arm64 "0.14.39"
|
esbuild-windows-arm64 "0.14.39"
|
||||||
|
|
||||||
|
source-map-support@^0.5.21:
|
||||||
|
version "0.5.21"
|
||||||
|
resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f"
|
||||||
|
integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==
|
||||||
|
dependencies:
|
||||||
|
buffer-from "^1.0.0"
|
||||||
|
source-map "^0.6.0"
|
||||||
|
|
||||||
|
source-map@^0.6.0:
|
||||||
|
version "0.6.1"
|
||||||
|
resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
|
||||||
|
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
|
||||||
|
|
||||||
|
tslog@^3.3.3:
|
||||||
|
version "3.3.3"
|
||||||
|
resolved "https://registry.npmjs.org/tslog/-/tslog-3.3.3.tgz#751a469e0d36841bd7e03676c27e53e7ffe9bc3d"
|
||||||
|
integrity sha512-lGrkndwpAohZ9ntQpT+xtUw5k9YFV1DjsksiWQlBSf82TTqsSAWBARPRD9juI730r8o3Awpkjp2aXy9k+6vr+g==
|
||||||
|
dependencies:
|
||||||
|
source-map-support "^0.5.21"
|
||||||
|
|
Loading…
Reference in a new issue