-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
3f20d43
commit 1b85eec
Showing
7 changed files
with
131 additions
and
52 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
# Fart Server | ||
|
||
## Usage | ||
|
||
### Spin up local Fart server | ||
|
||
```bash | ||
deno run --allow-env --allow-net fart_server/serve.ts | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
{ | ||
"design": "https://docs.google.com/document/d/1pGNLsDr-WysIIqB4nc1pTCL8FMmPxkJMNoGsRMkA0TY/edit", | ||
"github": "https://github.com/EthanThatOneKid/fart/", | ||
"author": "https://etok.codes/" | ||
"/design": "https://docs.google.com/document/d/1pGNLsDr-WysIIqB4nc1pTCL8FMmPxkJMNoGsRMkA0TY/edit", | ||
"/github": "https://github.com/EthanThatOneKid/fart/", | ||
"/author": "https://etok.codes/" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,20 @@ | ||
// TODO(@ethanthatonekid): write implementation for shortlinks.test.ts | ||
// NOTE: import data from shortlinks.json | ||
import shortlinks from "./shortlinks.json" assert { type: "json" }; | ||
|
||
const map = Object.entries(shortlinks) | ||
.reduce((result, [key, value]) => { | ||
result.set(key, value); | ||
return result; | ||
}, new Map<string, string>()); | ||
|
||
export const redirectIfShortlink = (request: Request): Response | null => { | ||
const { pathname } = new URL(request.url); | ||
if (pathname.includes("?")) { | ||
const query = pathname.slice(pathname.indexOf("?")); | ||
const shortlink = map.get(pathname.slice(0, pathname.indexOf("?"))); | ||
if (shortlink !== undefined) return Response.redirect(shortlink + query); | ||
} | ||
const shortlink = map.get(pathname.replace(/([^:]\/)\/+/g, "/")); | ||
if (shortlink !== undefined) return Response.redirect(shortlink); | ||
return null; | ||
}; |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import { redirectToDenoDeployPreviewUrl } from "./bonus_features/versions/mod.ts"; | ||
import { getSize, inject, register } from "./utils.ts"; | ||
|
||
export const handleRequest = async (event: Deno.RequestEvent) => { | ||
const { request, respondWith } = event; | ||
respondWith(await inject(request)); | ||
}; | ||
|
||
// redirect to another server running a different version of the Fart library | ||
register(redirectToDenoDeployPreviewUrl); | ||
|
||
// redirect to an external URL | ||
register(redirectIfShortlink); | ||
|
||
// show how many handlers are registered | ||
register((request) => { | ||
if (new URL(request.url).pathname === "/debug/size") { | ||
return new Response(String(getSize())); | ||
} | ||
return null; | ||
}); | ||
|
||
// show deployment ID if running on Deno Deploy | ||
register((request) => { | ||
if (new URL(request.url).pathname === "/debug/deployment") { | ||
return new Response(String(Deno.env.get("DENO_DEPLOYMENT_ID"))); | ||
} | ||
return null; | ||
}); | ||
|
||
if (Deno.env.get("DENO_DEPLOYMENT_ID") !== undefined) { | ||
// add the fetch listener if running on Deno Deploy | ||
addEventListener( | ||
"fetch", | ||
handleRequest as unknown as EventListenerOrEventListenerObject, | ||
); | ||
} else if (import.meta.main) { | ||
// serve the HTTP server if running locally | ||
const port = parseInt(Deno.env.get("PORT") || "8080"); | ||
console.log(`Access HTTP webserver at: http://localhost:${port}/`); | ||
for await (const connection of Deno.listen({ port })) { | ||
for await (const event of Deno.serveHttp(connection)) { | ||
await handleRequest(event); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,18 +1,43 @@ | ||
import { assertEquals } from "../deps/std/testing.ts"; | ||
import type { MatchingFunction } from "./utils.ts"; | ||
import { matchHome, matchSubroute, register, route } from "./utils.ts"; | ||
import { clear, getSize, inject, register } from "./utils.ts"; | ||
|
||
const assertMatches = async (matchingFn: MatchingFunction, url: string) => { | ||
const expectation = new Response("Hello, world!"); | ||
register(matchingFn, () => expectation); | ||
const reality = await route(new Request(url)); | ||
assertEquals(reality, expectation); | ||
}; | ||
// Note: Make sure each test clears the handlers if changes were made. | ||
|
||
Deno.test("matches a simple home route", async () => { | ||
await assertMatches(matchHome, "https://example.com/"); | ||
Deno.test("returns 404 without registering a handler", async () => { | ||
const { status } = await inject(new Request("https://example.com/")); | ||
assertEquals(status, 404); | ||
}); | ||
|
||
Deno.test("matches a custom route", async () => { | ||
await assertMatches(matchSubroute("/abc"), "http://example.com/abc"); | ||
Deno.test("size of handlers is 0 without registering a handler", () => { | ||
assertEquals(getSize(), 0); | ||
}); | ||
|
||
Deno.test("size is reduced to 0 when clear is called", () => { | ||
register(() => null); | ||
assertEquals(getSize(), 1); | ||
register(() => null, () => null); | ||
assertEquals(getSize(), 3); | ||
clear(); | ||
assertEquals(getSize(), 0); | ||
}); | ||
|
||
Deno.test("returns 404 when all handlers return null", async () => { | ||
register(() => null); | ||
const { status } = await inject(new Request("https://example.com/")); | ||
assertEquals(status, 404); | ||
clear(); | ||
}); | ||
|
||
Deno.test("returns data when a handler returns a response", async () => { | ||
register(() => new Response("abc")); | ||
const response = await inject(new Request("https://example.com/")); | ||
assertEquals(await response.text(), "abc"); | ||
clear(); | ||
}); | ||
|
||
Deno.test("returns data when a handler returns a response and cascades on null", async () => { | ||
register(() => null, () => null, () => null, () => new Response("abc")); | ||
const response = await inject(new Request("https://example.com/")); | ||
assertEquals(await response.text(), "abc"); | ||
clear(); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,41 +1,39 @@ | ||
export type MatchingFunction = (r: Request) => boolean; | ||
export type RequestHandler = (r: Request) => Response | Promise<Response>; | ||
export type Result = null | Response | Promise<null | Response>; | ||
|
||
export type Route = [MatchingFunction, RequestHandler]; | ||
export type RequestHandler = (r: Request) => Result; | ||
|
||
/** | ||
* In-memory storage of the Fart Server's configuration. | ||
*/ | ||
const routes: Route[] = []; | ||
const handlers: RequestHandler[] = []; | ||
|
||
/** | ||
* Routes a given HTTP request to the intended `bonus_features` and | ||
* sets the appropriate content type header. | ||
* @param request incoming http request | ||
* @returns routed Fart server response | ||
*/ | ||
export const route = async (request: Request): Promise<Response> => { | ||
for (const [match, handler] of routes) { | ||
if (match(request)) { | ||
return await handler(request); | ||
export const inject = async (request: Request): Promise<Response> => { | ||
for (const handler of handlers) { | ||
const result = await handler(request); | ||
if (result !== null) { | ||
return result; | ||
} | ||
} | ||
return new Response("404", { status: 404 }); | ||
}; | ||
|
||
export const register = ( | ||
matchRoute: MatchingFunction, | ||
handler: RequestHandler, | ||
) => { | ||
routes.push([matchRoute, handler]); | ||
export const register = (...gimmeHandlers: RequestHandler[]) => { | ||
handlers.push(...gimmeHandlers); | ||
}; | ||
|
||
export const matchHome = (r: Request): boolean => { | ||
return new URL(r.url).pathname === "/"; | ||
export const clear = () => { | ||
handlers.length = 0; | ||
}; | ||
|
||
export const matchSubroute = (subroute: string) => { | ||
return (r: Request): boolean => { | ||
return new URL(r.url).pathname === subroute; | ||
}; | ||
export const getSize = () => { | ||
return handlers.length; | ||
}; | ||
|
||
// TODO(@ethanthatonekid): Write new functions to access the Fart Server's | ||
// configuration. |