Skip to content

Commit

Permalink
Merge pull request #60 from TxtDot/proxy
Browse files Browse the repository at this point in the history
Proxy, config changes
  • Loading branch information
artegoser authored Sep 25, 2023
2 parents e6b61fb + 9ffba00 commit b486877
Show file tree
Hide file tree
Showing 13 changed files with 246 additions and 79 deletions.
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@ HOST=127.0.0.1 # 0.0.0.0 if txtdot is not behind reverse proxy
PORT=8080

REVERSE_PROXY=true # only for reverse proxy; see docs

PROXY_RES=true
SWAGGER=false # whether to add API docs route or not
44 changes: 23 additions & 21 deletions src/app.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { ConfigService } from "./config/config.service";

import path from "path";

import Fastify from "fastify";
Expand All @@ -9,25 +7,23 @@ import fastifySwagger from "@fastify/swagger";
import fastifySwaggerUi from "@fastify/swagger-ui";
import ejs from "ejs";

import indexRoute from "./routes/browser/index";
import getRoute from "./routes/browser/get";
import proxyRoute from "./routes/browser/proxy";
import parseRoute from "./routes/api/parse";
import indexRoute from "./routes/browser/index";
import rawHtml from "./routes/api/raw-html";

import publicConfig from "./publicConfig";
import errorHandler from "./errors/handler";
import getConfig from "./config/main";

class App {
config: ConfigService;

constructor() {
this.config = new ConfigService();
}

async init() {
const config = getConfig();

const fastify = Fastify({
logger: true,
trustProxy: this.config.reverse_proxy,
trustProxy: config.reverse_proxy,
});

fastify.register(fastifyStatic, {
Expand All @@ -41,26 +37,32 @@ class App {
},
});

await fastify.register(fastifySwagger, {
swagger: {
info: {
title: "TXTDot API",
description: publicConfig.description,
version: publicConfig.version,
},
}
});
await fastify.register(fastifySwaggerUi, { routePrefix: "/doc" });
if (config.swagger) {
await fastify.register(fastifySwagger, {
swagger: {
info: {
title: "TXTDot API",
description: publicConfig.description,
version: publicConfig.version,
},
}
});
await fastify.register(fastifySwaggerUi, { routePrefix: "/doc" });
}

fastify.register(indexRoute);
fastify.register(getRoute);

if (config.proxy_res)
fastify.register(proxyRoute);

fastify.register(parseRoute);
fastify.register(rawHtml);

fastify.setErrorHandler(errorHandler);

fastify.listen(
{ host: this.config.host, port: this.config.port },
{ host: config.host, port: config.port },
(err) => {
err && console.log(err);
}
Expand Down
12 changes: 11 additions & 1 deletion src/config/config.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,23 @@ export class ConfigService {
public readonly host: string;
public readonly port: number;
public readonly reverse_proxy: boolean;
public readonly proxy_res: boolean;
public readonly swagger: boolean;

constructor() {
config();

this.host = process.env.HOST || "0.0.0.0";
this.port = Number(process.env.PORT) || 8080;

this.reverse_proxy = Boolean(process.env.REVERSE_PROXY) || false;
this.reverse_proxy = this.parseBool(process.env.REVERSE_PROXY, false);

this.proxy_res = this.parseBool(process.env.PROXY_RES, true);
this.swagger = this.parseBool(process.env.SWAGGER, false);
}

parseBool(value: string | undefined, def: boolean): boolean {
if (!value) return def;
return value === "true" || value === "1";
}
}
12 changes: 12 additions & 0 deletions src/config/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { ConfigService } from "./config.service";

let configSvc: ConfigService | undefined;

export default function getConfig(): ConfigService {
if (configSvc) {
return configSvc;
}

configSvc = new ConfigService();
return configSvc;
}
13 changes: 5 additions & 8 deletions src/errors/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { NotHtmlMimetypeError, TxtDotError } from "./main";
import { getFastifyError } from "./validation";

import { IGetSchema } from "../types/requests/browser";
import getConfig from "../config/main";

export default function errorHandler(
error: Error,
Expand All @@ -29,10 +30,6 @@ function apiErrorHandler(error: Error, reply: FastifyReply) {
});
}

if (error instanceof NotHtmlMimetypeError) {
return generateResponse(501);
}

if (getFastifyError(error)?.statusCode === 400) {
return generateResponse(400);
}
Expand All @@ -45,10 +42,6 @@ function apiErrorHandler(error: Error, reply: FastifyReply) {
}

function htmlErrorHandler(error: Error, reply: FastifyReply, url: string) {
if (error instanceof NotHtmlMimetypeError) {
return reply.redirect(301, error.url);
}

if (getFastifyError(error)?.statusCode === 400) {
return reply.code(400).view("/templates/error.ejs", {
url,
Expand All @@ -62,6 +55,10 @@ function htmlErrorHandler(error: Error, reply: FastifyReply, url: string) {
url,
code: error.code,
description: error.description,
proxyBtn: (
error instanceof NotHtmlMimetypeError &&
getConfig().proxy_res
),
});
}

Expand Down
38 changes: 28 additions & 10 deletions src/errors/main.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import getConfig from "../config/main";

export abstract class TxtDotError extends Error {
code: number;
name: string;
description: string;

constructor(code: number, name: string, description: string) {
constructor(
code: number,
name: string,
description: string,
) {
super(description);
this.code = code;
this.name = name;
Expand All @@ -13,22 +19,34 @@ export abstract class TxtDotError extends Error {

export class EngineParseError extends TxtDotError {
constructor(message: string) {
super(422, "EngineParseError", `Parse error: ${message}`);
super(
422,
"EngineParseError",
`Parse error: ${message}`,
);
}
}

export class LocalResourceError extends TxtDotError {
constructor() {
super(403, "LocalResourceError", "Proxying local resources is forbidden.");
super(
403,
"LocalResourceError",
"Proxying local resources is forbidden.",
);
}
}

export class NotHtmlMimetypeError extends Error {
name: string = "NotHtmlMimetypeError";
url: string;

constructor(url: string) {
super();
this.url = url;
export class NotHtmlMimetypeError extends TxtDotError {
constructor() {
super(
421,
"NotHtmlMimetypeError",
"Received non-HTML content, " + (
getConfig().proxy_res ?
"use proxy instead of parser." :
"proxying is disabled by the instance admin."
),
);
}
}
33 changes: 4 additions & 29 deletions src/handlers/handler-input.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,20 @@
import { JSDOM } from "jsdom";
import { generateProxyUrl } from "../utils/generate";

export class HandlerInput {
private data: string;
private url: string;
private requestUrl: URL;
private engine?: string;
private redirectPath: string;
private dom?: JSDOM;

constructor(
data: string,
url: string,
requestUrl: URL,
engine?: string,
redirectPath: string = "get",
) {
this.data = data;
this.url = url;
this.requestUrl = requestUrl;
this.engine = engine;
this.redirectPath = redirectPath;
}

getUrl(): string {
return this.url;
}

parseDom(): JSDOM {
Expand All @@ -29,25 +23,6 @@ export class HandlerInput {
}

this.dom = new JSDOM(this.data, { url: this.url });

const links = this.dom.window.document.getElementsByTagName("a");
for (const link of links) {
try {
link.href = generateProxyUrl(
this.requestUrl,
link.href,
this.engine,
this.redirectPath,
);
} catch (_err) {
// ignore TypeError: Invalid URL
}
}

return this.dom;
}

getUrl(): string {
return this.url;
}
}
19 changes: 14 additions & 5 deletions src/handlers/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import axios from "../types/axios";

import micromatch from "micromatch";

import { JSDOM } from "jsdom";

import readability from "./readability";
import google, { GoogleDomains } from "./google";
import stackoverflow, { StackOverflowDomains } from "./stackoverflow/main";
Expand All @@ -14,6 +16,7 @@ import { LocalResourceError, NotHtmlMimetypeError } from "../errors/main";
import { HandlerInput } from "./handler-input";
import { Readable } from "stream";
import { decodeStream, parseEncodingName } from "../utils/http";
import replaceHref from "../utils/replace-href";

export default async function handlePage(
url: string, // remote URL
Expand All @@ -32,18 +35,24 @@ export default async function handlePage(
const mime: string | undefined = response.headers["content-type"]?.toString();

if (mime && mime.indexOf("text/html") === -1) {
throw new NotHtmlMimetypeError(url);
throw new NotHtmlMimetypeError();
}

return getFallbackEngine(urlObj.hostname, engine)(
const handler = getFallbackEngine(urlObj.hostname, engine);
const output = await handler(
new HandlerInput(
await decodeStream(data, parseEncodingName(mime)),
url,
requestUrl,
engine,
redirectPath,
)
);

// post-process
const dom = new JSDOM(output.content, { url });
replaceHref(dom, requestUrl, engine, redirectPath);
output.content = dom.serialize();
// TODO: DomPurify

return output;
}

function getFallbackEngine(host: string, specified?: string): EngineFunction {
Expand Down
18 changes: 18 additions & 0 deletions src/routes/browser/proxy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { FastifyInstance } from "fastify";
import { IProxySchema, ProxySchema } from "../../types/requests/browser";
import axios from "../../types/axios";

export default async function proxyRoute(fastify: FastifyInstance) {
fastify.get<IProxySchema>(
"/proxy",
{ schema: ProxySchema },
async (request, reply) => {
const response = await axios.get(request.query.url);
const mime: string | undefined = response.headers["content-type"]?.toString();
const clen: string | undefined = response.headers["content-length"]?.toString();
mime && reply.header("Content-Type", mime);
clen && reply.header("Content-Length", Number(clen));
return reply.send(response.data);
}
);
}
30 changes: 26 additions & 4 deletions src/types/requests/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,9 @@ export interface IGetSchema {
Querystring: IGetQuerySchema;
}

export const indexSchema = {
produces: ["text/html"],
hide: true
};
export interface IProxySchema {
Querystring: IProxyQuerySchema;
}

export const getQuerySchema = {
type: "object",
Expand All @@ -32,9 +31,32 @@ export const getQuerySchema = {
} as const;
export type IGetQuerySchema = FromSchema<typeof getQuerySchema>;

export const proxyQuerySchema = {
type: "object",
required: ["url"],
properties: {
url: {
type: "string",
description: "URL",
},
}
} as const;
export type IProxyQuerySchema = FromSchema<typeof proxyQuerySchema>;

export const indexSchema = {
hide: true,
produces: ["text/html"],
};

export const GetSchema: FastifySchema = {
description: "Get page",
hide: true,
querystring: getQuerySchema,
produces: ["text/html", "text/plain"],
};

export const ProxySchema: FastifySchema = {
description: "Proxy resource",
hide: true,
querystring: proxyQuerySchema,
}
Loading

0 comments on commit b486877

Please sign in to comment.