first commit, basic http server rending a sample svg is functioning
This commit is contained in:
		
							
								
								
									
										9
									
								
								.editorconfig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								.editorconfig
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | ||||
|  | ||||
| root = true | ||||
|  | ||||
| indent_style = tab | ||||
| indent_size = 4 | ||||
|  | ||||
| [*.{md,yaml,yml,json}] | ||||
| indent_style = space | ||||
| indent_size = 2 | ||||
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| node_modules | ||||
| build | ||||
							
								
								
									
										
											BIN
										
									
								
								image-templates/Disaster-Girl.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								image-templates/Disaster-Girl.jpg
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 28 KiB | 
							
								
								
									
										47
									
								
								package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,47 @@ | ||||
| { | ||||
| 	"name": "mini-macro-service", | ||||
| 	"lockfileVersion": 2, | ||||
| 	"requires": true, | ||||
| 	"packages": { | ||||
| 		"": { | ||||
| 			"name": "mini-macro-service", | ||||
| 			"devDependencies": { | ||||
| 				"@types/node": "^20.3.3", | ||||
| 				"typescript": "^5.1.6" | ||||
| 			} | ||||
| 		}, | ||||
| 		"node_modules/@types/node": { | ||||
| 			"version": "20.3.3", | ||||
| 			"resolved": "https://registry.npmjs.org/@types/node/-/node-20.3.3.tgz", | ||||
| 			"integrity": "sha512-wheIYdr4NYML61AjC8MKj/2jrR/kDQri/CIpVoZwldwhnIrD/j9jIU5bJ8yBKuB2VhpFV7Ab6G2XkBjv9r9Zzw==", | ||||
| 			"dev": true | ||||
| 		}, | ||||
| 		"node_modules/typescript": { | ||||
| 			"version": "5.1.6", | ||||
| 			"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", | ||||
| 			"integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", | ||||
| 			"dev": true, | ||||
| 			"bin": { | ||||
| 				"tsc": "bin/tsc", | ||||
| 				"tsserver": "bin/tsserver" | ||||
| 			}, | ||||
| 			"engines": { | ||||
| 				"node": ">=14.17" | ||||
| 			} | ||||
| 		} | ||||
| 	}, | ||||
| 	"dependencies": { | ||||
| 		"@types/node": { | ||||
| 			"version": "20.3.3", | ||||
| 			"resolved": "https://registry.npmjs.org/@types/node/-/node-20.3.3.tgz", | ||||
| 			"integrity": "sha512-wheIYdr4NYML61AjC8MKj/2jrR/kDQri/CIpVoZwldwhnIrD/j9jIU5bJ8yBKuB2VhpFV7Ab6G2XkBjv9r9Zzw==", | ||||
| 			"dev": true | ||||
| 		}, | ||||
| 		"typescript": { | ||||
| 			"version": "5.1.6", | ||||
| 			"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", | ||||
| 			"integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", | ||||
| 			"dev": true | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										14
									
								
								package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								package.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| { | ||||
| 	"name": "mini-macro-service", | ||||
| 	"private": true, | ||||
| 	"scripts": { | ||||
| 		"tsc": "tsc --build" | ||||
| 	}, | ||||
| 	"engines": { | ||||
| 		"node": ">=17" | ||||
| 	}, | ||||
| 	"devDependencies": { | ||||
| 		"@types/node": "^20.3.3", | ||||
| 		"typescript": "^5.1.6" | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										209
									
								
								readme.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										209
									
								
								readme.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,209 @@ | ||||
|  | ||||
| # Minimal Image Macro Server (mini-macro) | ||||
|  | ||||
| Self-hostable image macro creation and hosting service. | ||||
|  | ||||
| - Add and use whatever template images you want | ||||
| - Add as many pieces of text, formatted as you want, to each image | ||||
| - Images are stored and transferred efficiently as SVGs | ||||
| - SVG markup is screen-reader accessible | ||||
| - Web UI and [REST API](#rest-api) for managing image macros and templates | ||||
|  | ||||
|  | ||||
|  | ||||
| ## Efficient, Accessible SVG Image Macros | ||||
|  | ||||
| Rather than render new PNG (or other raster) formatted images, the renderer used here actually generates SVG files that look something like this: | ||||
|  | ||||
| ```xml | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <svg version="1.1" | ||||
|   width="800" height="600" | ||||
|   xmlns="http://www.w3.org/2000/svg" | ||||
|   xmlns:xlink="http://www.w3.org/1999/xlink" | ||||
|   style="user-select: none"> | ||||
|   <title>Image Macro Title Text</title> | ||||
|  | ||||
|   <!-- The image template is referenced as a separate, external file --> | ||||
|   <image x="0" y="0" | ||||
|     width="800" height="600" | ||||
|     xlink:href="http://example.com/abcdefghijkl"> | ||||
|     <title>Image Template Title Text</title> | ||||
|   </image> | ||||
|    | ||||
|   <!-- Each section of text gets its own <text> node --> | ||||
|   <text x="400" y="50" | ||||
|     text-anchor="middle" | ||||
|     font-size="48" | ||||
|     font-family="sans-serif" | ||||
|     font-weight="600" | ||||
|     stroke="#000" | ||||
|     stroke-width="2" | ||||
|     fill="#fff" | ||||
|     >Top Text</text> | ||||
|      | ||||
|   <text x="400" y="550" | ||||
|     text-anchor="middle" | ||||
|     font-size="48" | ||||
|     font-family="sans-serif" | ||||
|     font-weight="600" | ||||
|     stroke="#000" | ||||
|     stroke-width="2" | ||||
|     fill="#fff" | ||||
|     >Bottom Text</text> | ||||
| </svg> | ||||
| ``` | ||||
|  | ||||
| That sample XML above is about 800 bytes in total with the comments removed; The raster formats that might otherwise be used would typically be several kilobytes _at least_. | ||||
|  | ||||
| Obviously, the externally referenced template image still has to be downloaded. However that external image (the largest part of an image macro, and the part that gets reused over and over) can be cached separately by clients so that it does not need to be downloaded separately each time it gets used to make a new image. Additionally, only one copy of it actually gets stored on the server, regardless of how many times its used. | ||||
|  | ||||
| Additionally, since an SVG is markup rather than a raster image, they can be read by screen readers (including the additional `<title>` text for both the whole image, and the template). | ||||
|  | ||||
|  | ||||
|  | ||||
| ## Embedding in HTML | ||||
|  | ||||
| The one drawback to this approach is that, in web browsers, [SVGs do not load external resources when rendered in an image context](https://developer.mozilla.org/en-US/docs/Web/SVG/SVG_as_an_Image) (e.g. when used in an `<img>` tag). That said, you can embed it using either the `<embed>` or `<object>` elements, which will both render it as a document. | ||||
|  | ||||
| ```html | ||||
| <!-- NOT This: <img> doesn't work with external resources --> | ||||
| <img type="image/svg+xml" src="http://example.com/FrztglsLexLRWg"> | ||||
|  | ||||
| <!-- Either of these will work: --> | ||||
| <embed type="image/svg+xml" src="http://example.com/FrztglsLexLRWg"></embed> | ||||
| <object type="image/svg+xml" data="http://example.com/FrztglsLexLRWg"></object> | ||||
| ``` | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| ## REST API | ||||
|  | ||||
| ### Image Template List | ||||
|  | ||||
| #### Media Type: `application/vnd.mini-macro.image-templates+json` | ||||
|  | ||||
| _todo_ | ||||
|  | ||||
| Supports: `GET` | ||||
|  | ||||
| ```json | ||||
| { | ||||
|   "items": [ | ||||
|     { | ||||
|       "id": "<template_id>", | ||||
|       "title": "Image Template Title", | ||||
|       "width": 800, | ||||
|       "height": 600, | ||||
|       "links": { | ||||
|         "self": { | ||||
|           "href": "/templates/<template_id>", | ||||
|           "type": "application/vnd.mini-macro.image-template+json" | ||||
|         }, | ||||
|         "alternate": { | ||||
|           "href": "/templates/<template_id>", | ||||
|           "type": "image/png" | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   ], | ||||
|   "links": { | ||||
|     "next": { | ||||
|       "href": "/templates?anchor=<template_id>", | ||||
|       "type": "application/vnd.mini-macro.image-templates+json" | ||||
|     } | ||||
|   } | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ### Image Template | ||||
|  | ||||
| General Support: `DELETE` | ||||
|  | ||||
| #### Media Type: `application/vnd.mini-macro.image-template+json` | ||||
|  | ||||
| _todo_ | ||||
|  | ||||
| Supports: `GET`, `POST`, `PUT` | ||||
|  | ||||
| ```json | ||||
| { | ||||
|   "id": "<template_id>", | ||||
|   "title": "Image Template Title", | ||||
|   "width": 800, | ||||
|   "height": 600, | ||||
|   "links": { | ||||
|     "self": { | ||||
|       "href": "/templates/<template_id>", | ||||
|       "type": "application/vnd.mini-macro.image-template+json" | ||||
|     }, | ||||
|     "alternate": { | ||||
|       "href": "/templates/<template_id>", | ||||
|       "type": "image/png" | ||||
|     } | ||||
|   } | ||||
| } | ||||
| ``` | ||||
|  | ||||
| #### Other Media Types: `image/png`, `image/jpeg` | ||||
|  | ||||
| Supports: `GET`, `PUT` | ||||
|  | ||||
|  | ||||
|  | ||||
| ### Image Macro | ||||
|  | ||||
| General Support: `DELETE` | ||||
|  | ||||
| #### Media Type: `application/vnd.mini-macro.image-macro+json` | ||||
|  | ||||
| _todo_ | ||||
|  | ||||
| Supports: `GET`, `POST`, `PUT` | ||||
|  | ||||
| ```json | ||||
| { | ||||
|   "id": "<image_id>", | ||||
|   "name": "Image Macro Name", | ||||
|   "text_style": { | ||||
|     "fill": "#fff", | ||||
|     "stroke": "#000", | ||||
|     "stroke_width": 2, | ||||
|     "font_size": 48, | ||||
|     "font_family": "sans-serif", | ||||
|     "font_weight": 600 | ||||
|   }, | ||||
|   "text": [ | ||||
|     { | ||||
|       "text": "Top Text", | ||||
|       "top": 50, | ||||
|       "left": 400, | ||||
|     }, | ||||
|     { | ||||
|       "text": "Bottom Text", | ||||
|       "top": 550, | ||||
|       "left": 400, | ||||
|     }, | ||||
|   ] | ||||
|   "links": { | ||||
|     "self": { | ||||
|       "href": "/images/<template_id>", | ||||
|       "type": "application/vnd.mini-macro.image-macro+json" | ||||
|     }, | ||||
|     "alternate": { | ||||
|       "href": "/images/<template_id>", | ||||
|       "type": "image/png" | ||||
|     }, | ||||
|     "/rel/template": { | ||||
|       "href": "/templates/<template_id>", | ||||
|       "type": "application/vnd.mini-macro.image-template+json" | ||||
|     } | ||||
|   } | ||||
| } | ||||
| ``` | ||||
|  | ||||
| #### Other Media Types: `image/svg+xml` | ||||
|  | ||||
| Supports: `GET` | ||||
|  | ||||
							
								
								
									
										19
									
								
								src/config.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/config.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | ||||
|  | ||||
| export namespace http { | ||||
| 	export namespace images { | ||||
| 		export const port = 54320; | ||||
| 		export const address = '0.0.0.0'; | ||||
| 		export const public_url = 'http://me.local.jbrumond.me:54320'; | ||||
| 	} | ||||
| 	 | ||||
| 	export namespace api { | ||||
| 		export const port = 54321; | ||||
| 		export const address = '0.0.0.0'; | ||||
| 		export const public_url = 'http://me.local.jbrumond.me:54321'; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| export namespace storage { | ||||
| 	export type Mode = 'memory' | 'file' | 'sqlite'; | ||||
| 	export const mode: Mode = 'memory'; | ||||
| } | ||||
							
								
								
									
										6
									
								
								src/image-id.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								src/image-id.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
|  | ||||
| import { pseudoRandomBytes } from 'crypto'; | ||||
|  | ||||
| export function generate_image_id() { | ||||
| 	return pseudoRandomBytes(10).toString('base64url'); | ||||
| } | ||||
							
								
								
									
										53
									
								
								src/render-svg.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								src/render-svg.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | ||||
|  | ||||
| export interface ImageParams { | ||||
| 	title: string; | ||||
| 	image: { | ||||
| 		url: string; | ||||
| 		title: string; | ||||
| 		width: number; | ||||
| 		height: number; | ||||
| 	}; | ||||
| 	text: TextParams[]; | ||||
| 	text_style: { | ||||
| 		font_size: number; | ||||
| 		font_family: string; | ||||
| 		font_weight: 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900; | ||||
| 		fill: string; | ||||
| 		stroke: string; | ||||
| 		stroke_width: number; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| export interface TextParams { | ||||
| 	text: string; | ||||
| 	top: number; | ||||
| 	left: number; | ||||
| } | ||||
|  | ||||
| export const render_svg = (params: ImageParams) => ` | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <svg version="1.1" | ||||
| 	width="${params.image.width}" height="${params.image.height}" | ||||
| 	xmlns="http://www.w3.org/2000/svg" | ||||
| 	xmlns:xlink="http://www.w3.org/1999/xlink" | ||||
| 	style="user-select: none"> | ||||
| 	<title>${params.title}</title> | ||||
|  | ||||
| 	<image x="0" y="0" | ||||
| 		width="${params.image.width}" height="${params.image.height}" | ||||
| 		href="${params.image.url}" | ||||
| 		xlink:href="${params.image.url}"> | ||||
| 		<title>${params.image.title}</title> | ||||
| 	</image> | ||||
| 	${params.text.map((text) => ` | ||||
| 	<text x="${text.left}" y="${text.top}" | ||||
| 		text-anchor="middle" | ||||
| 		font-size="${params.text_style.font_size}" | ||||
| 		font-family="${params.text_style.font_family}" | ||||
| 		font-weight="${params.text_style.font_weight}" | ||||
| 		stroke="${params.text_style.stroke}" | ||||
| 		stroke-width="${params.text_style.stroke_width}" | ||||
| 		fill="${params.text_style.fill}" | ||||
| 		>${text.text}</text>` | ||||
| 	).join('')} | ||||
| </svg>`.trimStart(); | ||||
							
								
								
									
										9
									
								
								src/start.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/start.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | ||||
|  | ||||
| import { init_http_servers } from './web/server'; | ||||
| // import {  } from './api/server'; | ||||
|  | ||||
| main(); | ||||
|  | ||||
| async function main() { | ||||
| 	init_http_servers(); | ||||
| } | ||||
							
								
								
									
										33
									
								
								src/storage/interface.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/storage/interface.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | ||||
|  | ||||
| import { TextParams } from '../render-svg'; | ||||
|  | ||||
| export interface Store { | ||||
| 	get_image_by_id(image_id: string) : Promise<ImageTemplate | ImageMacro>; | ||||
| 	get_image_data_by_id(image_id: string) : Promise<ImageTemplate | ImageMacroData>; | ||||
| } | ||||
|  | ||||
| export type ImageMacroMediaType = 'image/svg+xml'; | ||||
| export type ImageTemplateMediaType = 'image/jpeg' | 'image/png'; | ||||
|  | ||||
| export interface ImageTemplate { | ||||
| 	id: string; | ||||
| 	title: string; | ||||
| 	width: number; | ||||
| 	height: number; | ||||
| 	media_type: ImageTemplateMediaType; | ||||
| 	content: Buffer; | ||||
| } | ||||
|  | ||||
| export interface ImageMacro { | ||||
| 	id: string; | ||||
| 	media_type: ImageMacroMediaType; | ||||
| 	content: string; | ||||
| } | ||||
|  | ||||
| export interface ImageMacroData { | ||||
| 	id: string; | ||||
| 	title: string; | ||||
| 	template_id: string; | ||||
| 	text_nodes: TextParams[]; | ||||
| 	content: string; | ||||
| } | ||||
							
								
								
									
										80
									
								
								src/storage/memory/images.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								src/storage/memory/images.ts
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										8
									
								
								src/storage/memory/store.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/storage/memory/store.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
|  | ||||
| import { Store } from '../interface'; | ||||
| import { get_image_by_id, get_image_data_by_id } from './images'; | ||||
|  | ||||
| export const memory_store: Store = { | ||||
| 	get_image_by_id, | ||||
| 	get_image_data_by_id, | ||||
| } | ||||
							
								
								
									
										24
									
								
								src/storage/store.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								src/storage/store.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | ||||
|  | ||||
| import * as conf from '../config'; | ||||
| import { Store } from './interface'; | ||||
| import { memory_store } from './memory/store'; | ||||
|  | ||||
| export let store: Store; | ||||
|  | ||||
| switch (conf.storage.mode) { | ||||
| 	case 'memory': | ||||
| 		store = memory_store; | ||||
| 		break; | ||||
|  | ||||
| 	case 'file': | ||||
| 		//  | ||||
| 		// break; | ||||
|  | ||||
| 	case 'sqlite': | ||||
| 		//  | ||||
| 		// break; | ||||
|  | ||||
| 	default: | ||||
| 		console.error('Unknown storage mode configured'); | ||||
| 		process.exit(1); | ||||
| } | ||||
							
								
								
									
										85
									
								
								src/web/request-handlers/image-request.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								src/web/request-handlers/image-request.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,85 @@ | ||||
|  | ||||
| import { ImageMacro, ImageTemplate } from '../../storage/interface'; | ||||
| import { store } from '../../storage/store'; | ||||
| import { IncomingMessage, ServerResponse } from 'http'; | ||||
|  | ||||
| export async function handle_image_request(req: IncomingMessage, res: ServerResponse) { | ||||
| 	if (req.url === '/' && req.method === 'GET') { | ||||
| 		res.writeHead(200, { | ||||
| 			'content-type': 'text/html', | ||||
| 		}); | ||||
| 		res.end(`<!doctype html> | ||||
| 		<html> | ||||
| 		<head></head> | ||||
| 		<body> | ||||
| 			<h2><embed></h2> | ||||
| 			<embed type="image/svg+xml" src="http://me.local.jbrumond.me:54320/FrztglsLexLRWg"></embed> | ||||
| 		 | ||||
| 			<h2><object></h2> | ||||
| 			<object type="image/svg+xml" data="http://me.local.jbrumond.me:54320/FrztglsLexLRWg"></object> | ||||
| 			 | ||||
| 			<h2><iframe></h2> | ||||
| 			<iframe src="http://me.local.jbrumond.me:54320/FrztglsLexLRWg"></iframe> | ||||
| 		</body> | ||||
| 		</html> | ||||
| 		`); | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	if (! req.url.startsWith('/')) { | ||||
| 		return send_404_not_found(res); | ||||
| 	} | ||||
|  | ||||
| 	switch (req.method) { | ||||
| 		case 'OPTIONS': return send_options_response(res); | ||||
| 		case 'GET': return send_image_response(res, await get_image(req)); | ||||
| 		case 'HEAD': return send_image_response(res, await get_image(req), false); | ||||
| 	} | ||||
|  | ||||
| 	return send_415_method_not_allowed(res); | ||||
| } | ||||
|  | ||||
| function send_options_response(res: ServerResponse) { | ||||
| 	res.writeHead(200, { | ||||
| 		'access-control-allow-origin': '*', | ||||
| 		'access-control-allow-methods': 'GET, HEAD, OPTIONS', | ||||
| 	}); | ||||
| 	res.end(); | ||||
| } | ||||
|  | ||||
| function send_image_response(res: ServerResponse, image: ImageTemplate | ImageMacro, send_content = true) { | ||||
| 	if (! image) { | ||||
| 		return send_404_not_found(res); | ||||
| 	} | ||||
|  | ||||
| 	const buf = image.media_type === 'image/svg+xml' | ||||
| 		? Buffer.from(image.content, 'utf8') | ||||
| 		: image.content; | ||||
|  | ||||
| 	res.writeHead(200, { | ||||
| 		'content-type': image.media_type, | ||||
| 		'content-length': buf.byteLength, | ||||
| 		'cache-control': 'public, max-age=31536000', | ||||
| 	}); | ||||
|  | ||||
| 	res.end(send_content ? buf : void 0); | ||||
| } | ||||
|  | ||||
| function send_404_not_found(res: ServerResponse) { | ||||
| 	res.writeHead(404, { | ||||
| 		'content-type': 'text/plain' | ||||
| 	}); | ||||
| 	res.end('Image not found'); | ||||
| } | ||||
|  | ||||
| function send_415_method_not_allowed(res: ServerResponse) { | ||||
| 	res.writeHead(415, { | ||||
| 		'content-type': 'text/plain' | ||||
| 	}); | ||||
| 	res.end('Method not allowed'); | ||||
| } | ||||
|  | ||||
| function get_image(req: IncomingMessage) { | ||||
| 	const image_id = req.url.slice(1); | ||||
| 	return store.get_image_by_id(image_id); | ||||
| } | ||||
							
								
								
									
										12
									
								
								src/web/server.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/web/server.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
|  | ||||
| import * as conf from '../config'; | ||||
| import { createServer } from 'http'; | ||||
| import { handle_image_request } from './request-handlers/image-request'; | ||||
|  | ||||
| export function init_http_servers() { | ||||
| 	const image_server = createServer(handle_image_request); | ||||
|  | ||||
| 	image_server.listen(conf.http.images.port, conf.http.images.address, () => { | ||||
| 		console.log('HTTP image server listening at %s:%d', conf.http.images.address, conf.http.images.port); | ||||
| 	}); | ||||
| } | ||||
							
								
								
									
										9
									
								
								tsconfig.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								tsconfig.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | ||||
| { | ||||
| 	"compilerOptions": { | ||||
| 		"outDir": "./build", | ||||
| 		"rootDir": "./src" | ||||
| 	}, | ||||
| 	"include": [ | ||||
| 		"./src/**/*.ts" | ||||
| 	] | ||||
| } | ||||
		Reference in New Issue
	
	Block a user