Caching fonts proxy for Googel Fonts
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

Give it my hyperfocus and a few hours


Signed-off-by: MLC Bloeiman <mar@strawmelonjuice.com>

+552 -31
+3 -2
.gitignore
··· 2 2 *.ez 3 3 /build 4 4 erl_crash.dump 5 - 6 - # Ignore direnv cache 7 5 .direnv/ 6 + data/ 7 + priv/oat.min.css 8 + priv/oat.min.js
+2 -9
README.md
··· 3 3 [![Package Version](https://img.shields.io/hexpm/v/fonts)](https://hex.pm/packages/fonts) 4 4 [![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/fonts/) 5 5 6 - ```sh 7 - gleam add fonts@1 8 - ``` 9 - ```gleam 10 - import fonts 11 6 12 - pub fn main() -> Nil { 13 - // TODO: An example of the project in use 14 - } 15 - ``` 16 7 17 8 Further documentation can be found at <https://hexdocs.pm/fonts>. 18 9 19 10 ## Development 20 11 21 12 ```sh 13 + curl https://raw.githubusercontent.com/knadh/oat/refs/heads/gh-pages/oat.min.css -opriv/oat.min.css # Download OatUI css 14 + curl https://raw.githubusercontent.com/knadh/oat/refs/heads/gh-pages/oat.min.js -opriv/oat.min.js # Download OatUI js 22 15 gleam run # Run the project 23 16 gleam test # Run the tests 24 17 ```
+1
gleam.toml
··· 20 20 gleam_http = ">= 4.3.0 and < 5.0.0" 21 21 gleam_erlang = ">= 1.3.0 and < 2.0.0" 22 22 woof = ">= 1.6.0 and < 2.0.0" 23 + gleam_httpc = ">= 5.0.0 and < 6.0.0" 23 24 24 25 [dev_dependencies] 25 26 gleeunit = ">= 1.0.0 and < 2.0.0"
+2
manifest.toml
··· 10 10 { name = "gleam_crypto", version = "1.6.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_crypto", source = "hex", outer_checksum = "2DE9E4EF53CF6FEE049D4F765731F7178F7A11AEFAE00EEE63BF7536B354AD3F" }, 11 11 { name = "gleam_erlang", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "1124AD3AA21143E5AF0FC5CF3D9529F6DB8CA03E43A55711B60B6B7B3874375C" }, 12 12 { name = "gleam_http", version = "4.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_http", source = "hex", outer_checksum = "82EA6A717C842456188C190AFB372665EA56CE13D8559BF3B1DD9E40F619EE0C" }, 13 + { name = "gleam_httpc", version = "5.0.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_http", "gleam_stdlib"], otp_app = "gleam_httpc", source = "hex", outer_checksum = "C545172618D07811494E97AAA4A0FB34DA6F6D0061FDC8041C2F8E3BE2B2E48F" }, 13 14 { name = "gleam_otp", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "BA6A294E295E428EC1562DC1C11EA7530DCB981E8359134BEABC8493B7B2258E" }, 14 15 { name = "gleam_stdlib", version = "1.0.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "960090C2FB391784BB34267B099DC9315CC1B1F6013E7415BC763CEF1905D7D3" }, 15 16 { name = "gleam_yielder", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_yielder", source = "hex", outer_checksum = "8E4E4ECFA7982859F430C57F549200C7749823C106759F4A19A78AEA6687717A" }, ··· 32 33 ewe = { version = ">= 3.0.8 and < 4.0.0" } 33 34 gleam_erlang = { version = ">= 1.3.0 and < 2.0.0" } 34 35 gleam_http = { version = ">= 4.3.0 and < 5.0.0" } 36 + gleam_httpc = { version = ">= 5.0.0 and < 6.0.0" } 35 37 gleam_stdlib = { version = ">= 1.0.0 and < 2.0.0" } 36 38 gleeunit = { version = ">= 1.0.0 and < 2.0.0" } 37 39 hackney = { version = ">= 4.0.0 and < 5.0.0" }
+68
priv/icon.svg
··· 1 + <?xml version="1.0" encoding="UTF-8" standalone="no"?> 2 + <!-- Created with Inkscape (http://www.inkscape.org/) --> 3 + 4 + <svg 5 + width="32" 6 + height="32" 7 + viewBox="0 0 32 32" 8 + version="1.1" 9 + id="svg1" 10 + inkscape:version="1.4.3 (0d15f75042, 2025-12-25)" 11 + sodipodi:docname="icon.svg" 12 + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" 13 + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" 14 + xmlns="http://www.w3.org/2000/svg" 15 + xmlns:svg="http://www.w3.org/2000/svg"> 16 + <sodipodi:namedview 17 + id="namedview1" 18 + pagecolor="#ffffff" 19 + bordercolor="#000000" 20 + borderopacity="0.25" 21 + inkscape:showpageshadow="2" 22 + inkscape:pageopacity="0.0" 23 + inkscape:pagecheckerboard="0" 24 + inkscape:deskcolor="#d1d1d1" 25 + inkscape:document-units="px" 26 + inkscape:zoom="18.046875" 27 + inkscape:cx="22.469264" 28 + inkscape:cy="20.419048" 29 + inkscape:window-width="3440" 30 + inkscape:window-height="1364" 31 + inkscape:window-x="0" 32 + inkscape:window-y="0" 33 + inkscape:window-maximized="1" 34 + inkscape:current-layer="layer2" /> 35 + <defs 36 + id="defs1" /> 37 + <g 38 + inkscape:groupmode="layer" 39 + id="layer2" 40 + inkscape:label="bg" 41 + style="fill:#ffeeaa;fill-opacity:1;stroke:#000000;stroke-opacity:1"> 42 + <rect 43 + style="fill:#ffeeaa;fill-opacity:1;stroke:none;stroke-opacity:1" 44 + id="rect4" 45 + width="48.651081" 46 + height="41.170563" 47 + x="-8.6441555" 48 + y="-3.8233767" /> 49 + </g> 50 + <g 51 + inkscape:label="Layer 1" 52 + inkscape:groupmode="layer" 53 + id="layer1" 54 + style="display:inline"> 55 + <path 56 + style="fill:#216778" 57 + id="path1" 58 + d="m 15.672249,5.4509211 c -0.058,0.3471865 -0.193255,0.6757419 -0.321477,1.0016436 -0.249557,0.6066204 -0.571406,1.1782964 -0.932753,1.7244091 -0.455564,0.6344865 -0.891999,1.2828909 -1.298898,1.9496572 -0.475445,0.783729 -0.867356,1.613794 -1.263452,2.439313 -0.07179,0.150014 -0.144277,0.299697 -0.215377,0.45004 -0.155392,0.328585 -0.276936,0.600348 -0.43848,0.925893 -0.217733,0.438779 -0.456636,0.863091 -0.704827,1.285099 -0.47597,0.771819 -0.9715465,1.532607 -1.4967287,2.271956 -0.41292,0.560931 -0.8590136,1.095826 -1.2736516,1.655571 -0.3648141,0.54966 -0.7988323,1.046885 -1.2439598,1.531925 -0.3640794,0.407399 -0.7851682,0.755238 -1.2108828,1.095147 -0.2248852,0.184884 -0.1131451,0.09546 -0.3349876,0.26856 0,0 0,5.641805 0,5.641805 v 0 c 0.220777,-0.211374 0.109109,-0.104246 0.3349876,-0.321401 0.4100764,-0.419698 0.8221163,-0.838185 1.2108828,-1.277891 0.4266857,-0.493288 0.8624547,-0.982106 1.2439598,-1.511587 0.4174968,-0.647136 0.8372788,-1.292893 1.2736516,-1.927546 0.2502722,-0.378779 0.505328,-0.756136 0.7458333,-1.141209 0.2556254,-0.409282 0.4902184,-0.831822 0.7508954,-1.238075 0.487381,-0.867788 0.921067,-1.762423 1.358684,-2.656055 0.396792,-0.765198 0.804883,-1.52518 1.263452,-2.255619 0.413995,-0.634487 0.837798,-1.262264 1.298898,-1.8639 0.133042,-0.175322 0.271889,-0.346395 0.398962,-0.526089 0.197413,-0.279163 0.352508,-0.585076 0.533791,-0.87438 0.111134,-0.205379 0.22021,-0.411558 0.321477,-0.621861 z" /> 59 + <path 60 + style="fill:#6f7c91" 61 + id="path2" 62 + d="m 15.789924,4.6043488 c -0.08736,2.2983902 -0.490604,4.6680882 0.113275,6.8993762 0.242763,0.742082 0.439435,1.497754 0.65717,2.247254 0.247564,0.796115 0.544738,1.57611 0.846021,2.353189 0.329153,0.847239 0.685566,1.683714 1.049271,2.516614 0.450931,0.991818 0.942785,1.965916 1.451132,2.92954 0.388866,0.696146 0.78539,1.398317 1.326538,1.990169 0.342289,0.374359 0.552683,0.540208 0.936572,0.87789 0.197854,0.134836 0.387259,0.282994 0.593563,0.404508 0.232318,0.136836 0.47661,0.252558 0.719768,0.369046 0.721281,0.345539 0.944871,0.412407 1.690433,0.727039 0.751596,0.317179 0.951173,0.412664 1.686481,0.744695 1.075588,0.488548 2.140337,1.000215 3.199729,1.522782 0,0 0,-4.895684 0,-4.895684 v 0 C 28.990728,23.029687 27.919903,22.773734 26.860148,22.476213 26.23907,22.286053 25.776441,22.161627 25.173667,21.904343 24.00701,21.406373 23.051058,20.733776 22.169903,19.82454 22.003974,19.648829 21.832697,19.478017 21.672116,19.297406 20.98646,18.526226 20.403882,17.664522 19.906793,16.761889 19.648784,16.261266 19.602055,16.182543 19.364007,15.673093 19.04091,14.981629 18.75552,14.273377 18.455661,13.571744 18.092723,12.698947 17.734541,11.824095 17.40639,10.937478 17.11317,10.142993 16.811798,9.3505365 16.560369,8.5415683 16.323359,7.7373041 16.102458,6.9278411 15.903199,6.113417 15.87872,5.9887111 15.850352,5.8647059 15.82976,5.7392993 15.810882,5.6243288 15.78624,5.2761642 15.784852,5.392666 c -0.127367,10.686583 -0.233416,7.078182 0.0051,5.254191 z" /> 63 + <path 64 + style="fill:#ff8080" 65 + id="path3" 66 + d="m 11.056533,22.214282 c 0.202489,0.05894 0.366398,0.211595 0.539203,0.327453 0.05298,0.02657 0.217783,0.111246 0.270673,0.12912 0.05721,0.01934 0.117751,0.02701 0.17604,0.0428 0.203847,0.05521 0.406948,0.115197 0.61017,0.17284 0.427261,0.133769 0.869178,0.214101 1.30732,0.304344 0.205049,0.04323 0.408996,0.09979 0.619338,0.111911 0.07448,0.0043 0.149211,-7.78e-4 0.223809,1.47e-4 0.204988,0.0025 0.252841,0.0065 0.45672,0.01613 0.416772,0.03569 0.833816,-0.0085 1.248431,-0.05023 0.273124,-0.01616 0.53491,-0.09844 0.80186,-0.150772 0.04819,-0.0094 0.330858,-0.05956 0.38417,-0.06905 0.301547,-0.07551 0.604014,-0.147489 0.899102,-0.245893 0.103966,-0.03591 0.206762,-0.07459 0.308691,-0.115864 0,0 0,-5.887171 0,-5.887171 v 0 c -0.101341,0.06098 -0.20304,0.121451 -0.308691,0.174819 -0.291676,0.143936 -0.584437,0.28924 -0.899102,0.377178 -0.148868,0.04682 -0.230706,0.07571 -0.38417,0.110866 -0.264478,0.06058 -0.536364,0.07448 -0.80186,0.129797 -0.08165,0.01191 -0.322496,0.0479 -0.403511,0.05625 -0.280505,0.0289 -0.563764,0.03067 -0.84492,0.04998 -0.195953,-0.0012 -0.260874,0.004 -0.45672,-0.02024 -0.07507,-0.0093 -0.149013,-0.02617 -0.223809,-0.03743 -0.205857,-0.03099 -0.413275,-0.05144 -0.619338,-0.08095 -0.442872,-0.07351 -0.878524,-0.171415 -1.30732,-0.305861 -0.379053,-0.121692 -0.745632,-0.276015 -1.056883,-0.53045 -0.202769,-0.153921 -0.412065,-0.312954 -0.539203,-0.538867 z" /> 67 + </g> 68 + </svg>
+198
priv/index.html
··· 1 + <!doctype html> 2 + <html lang="en"> 3 + <head> 4 + <meta content="width=device-width, initial-scale=1.0" name="viewport" /> 5 + <title>Strawmelonfonts</title> 6 + <link href="/favicon.ico" rel="icon" /> 7 + <link href="/oat.min.css" rel="stylesheet" /> 8 + <script defer="defer" src="/oat.min.js"></script> 9 + <style type="text/css"> 10 + @font-face { 11 + font-family: "Lilex"; 12 + font-style: normal; 13 + font-weight: 400; 14 + font-display: swap; 15 + src: url(/https://fonts.gstatic.com/s/lilex/v1/DPExYwmezwMATC7iDMrR60bcpUcjXezUWw.ttf) 16 + format("truetype"); 17 + } 18 + 19 + :root { 20 + color-scheme: light dark; 21 + --accent: light-dark(#e2e8f0, #1e293b); 22 + --background: light-dark(#f8fafc, #0f172a); 23 + --bar-height: 0.5rem; 24 + --border: light-dark(#cbd5e1, #334155); 25 + --card: light-dark(#ffffff, #1e293b); 26 + --card-foreground: light-dark(#0f172a, #f1f5f9); 27 + --danger: light-dark(#e11d48, #fb7185); 28 + --danger-foreground: light-dark(#ffffff, #1e293b); 29 + --faint: light-dark(#f1f5f9, #1e293b); 30 + --faint-foreground: light-dark(#64748b, #94a3b8); 31 + --font-bold: 600; 32 + --font-medium: 500; 33 + --font-mono: Lilex; 34 + --font-normal: 400; 35 + --font-sans: Lilex; 36 + --font-semibold: 600; 37 + --foreground: light-dark(#334155, #f8fafc); 38 + --input: light-dark(#e2e8f0, #334155); 39 + --leading-normal: 1.5; 40 + --muted: light-dark(#f1f5f9, #1e293b); 41 + --muted-foreground: light-dark(#64748b, #94a3b8); 42 + --primary: light-dark(#0d9488, #2dd4bf); 43 + --primary-foreground: light-dark(#ffffff, #042f2e); 44 + --radius-full: 9999px; 45 + --radius-large: 0.75rem; 46 + --radius-medium: 0.375rem; 47 + --radius-small: 0.125rem; 48 + --ring: light-dark(#0d9488, #2dd4bf); 49 + --secondary: light-dark(#64748b, #334155); 50 + --secondary-foreground: light-dark(#ffffff, #f1f5f9); 51 + --shadow-large: 52 + 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); 53 + --shadow-medium: 54 + 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); 55 + --shadow-small: 0 1px 2px 0 rgb(0 0 0 / 0.05); 56 + --space-1: 0.25rem; 57 + --space-10: 2.5rem; 58 + --space-12: 3rem; 59 + --space-14: 3.5rem; 60 + --space-16: 4rem; 61 + --space-18: 4.5rem; 62 + --space-2: 0.5rem; 63 + --space-3: 0.75rem; 64 + --space-4: 1rem; 65 + --space-5: 1.25rem; 66 + --space-6: 1.5rem; 67 + --space-8: 2rem; 68 + --success: light-dark(#16a34a, #4ade80); 69 + --success-foreground: light-dark(#ffffff, #052e16); 70 + --text-1: clamp(1.75rem, 1.5rem + 1.1vw, 2.25rem); 71 + --text-2: clamp(1.5rem, 1.3rem + 0.8vw, 1.875rem); 72 + --text-3: clamp(1.25rem, 1.1rem + 0.5vw, 1.5rem); 73 + --text-4: clamp(1.125rem, 1.05rem + 0.3vw, 1.25rem); 74 + --text-5: 1.125rem; 75 + --text-6: 1rem; 76 + --text-7: 0.875rem; 77 + --text-8: 0.75rem; 78 + --text-regular: var(--text-6); 79 + --transition: 200ms cubic-bezier(0.4, 0, 0.2, 1); 80 + --transition-fast: 120ms cubic-bezier(0.4, 0, 0.2, 1); 81 + --warning: light-dark(#d97706, #fbbf24); 82 + --warning-foreground: #09090b; 83 + --z-dropdown: 50; 84 + --z-modal: 200; 85 + } 86 + </style> 87 + </head> 88 + 89 + <body> 90 + <div class="container"> 91 + <nav data-topnav> 92 + <div class="hstack"> 93 + <div class="hstack"> 94 + <a href="/" style="width: fit-content; height: fit-content"> 95 + <figure 96 + aria-label="strawmelonfonts" 97 + class="small" 98 + data-variant="avatar" 99 + style="display: flex" 100 + > 101 + <img alt="icon" src="/favicon.ico" /> 102 + </figure> 103 + </a> 104 + <div style="font-size: var(--text-2)">strawmelonfonts</div> 105 + </div> 106 + </div> 107 + </nav> 108 + <main> 109 + <div class="container" style="max-width: 850px"> 110 + <h3>hi there!</h3> 111 + <p> 112 + This is a very simple Google Fonts proxy, which also saves a little 113 + cache of fonts previously served. 114 + </p> 115 + <p> 116 + All you have to do to use it, is prepend the 117 + <code>https://fonts.googleapis.com...</code> or 118 + <code>https://fonts.gstatic.com/</code> with the url of this page. 119 + </p> 120 + 121 + <section 122 + style=" 123 + margin: var(--space-8) 0; 124 + padding: var(--space-4); 125 + border: 1px solid var(--border); 126 + border-radius: var(--radius-medium); 127 + " 128 + > 129 + <h4>Usage Example (Lilex)</h4> 130 + <p style="font-size: var(--text-7)"> 131 + To use Lilex via this proxy, your stylesheet link would look like 132 + this: 133 + </p> 134 + 135 + <code 136 + id="url-display" 137 + style=" 138 + display: block; 139 + background: var(--faint); 140 + padding: var(--space-2); 141 + border-radius: var(--radius-small); 142 + word-break: break-all; 143 + margin-bottom: var(--space-4); 144 + " 145 + >https://fonts.mar.ollie.earth/https://fonts.googleapis.com/css2?family=Lilex:ital,wght@0,100..700;1,100..700&display=swap</code 146 + > 147 + 148 + <p style="font-size: var(--text-7)">Instead of:</p> 149 + 150 + <code 151 + style=" 152 + display: block; 153 + background: var(--faint); 154 + padding: var(--space-2); 155 + border-radius: var(--radius-small); 156 + word-break: break-all; 157 + margin-bottom: var(--space-4); 158 + " 159 + >https://fonts.googleapis.com/css2?family=Lilex:ital,wght@0,100..700;1,100..700&display=swap</code 160 + > 161 + 162 + <p style="font-size: var(--text-7)">Dead simple!</p> 163 + </section> 164 + 165 + <script> 166 + document.addEventListener("DOMContentLoaded", () => { 167 + document.getElementById("url-display").textContent = 168 + `${window.location.origin}/https://fonts.googleapis.com/css2?family=Lilex:ital,wght@0,100..700;1,100..700&display=swap`; 169 + }); 170 + </script> 171 + <footer class="footer text-light"> 172 + <p> 173 + Built with <a href="https://gleam.run">[Gleam]</a> by 174 + <a href="https://strawmelonjuice.com/">[Maeryn Bloeiman]</a>, 175 + <a 176 + href="https://forge.blacksky.community/did:plc:jgtfsmv25thfs4zmydtbccnn/fonts" 177 + >[view source]</a 178 + > 179 + </p> 180 + <p> 181 + <a href="https://fonts.mar.ollie.earth">[Public instance]</a> 182 + hosted with thanks to <a href="https://olie.earth/">[Ollie]</a>. 183 + </p> 184 + <p> 185 + This homepage is styled with 186 + <a href="https://oat.ink">[OatUI]</a>. 187 + </p> 188 + <div class="carbonbadge" id="wcb"></div> 189 + <script 190 + defer 191 + src="https://unpkg.com/website-carbon-badges@1.1.3/b.min.js" 192 + ></script> 193 + </footer> 194 + </div> 195 + </main> 196 + </div> 197 + </body> 198 + </html>
+278 -20
src/fonts.gleam
··· 1 1 import ewe.{type Request, type Response} 2 2 import gleam/bit_array 3 3 import gleam/bool 4 + import gleam/erlang/application 4 5 import gleam/erlang/process 6 + import gleam/http 7 + import gleam/http/request 5 8 import gleam/http/response 6 - import gleam/option 9 + import gleam/httpc 10 + import gleam/option.{None, Some} 7 11 import gleam/result 8 12 import gleam/string 13 + import gleam/uri 9 14 import simplifile 10 15 import woof 11 16 12 17 pub fn main() { 13 - // woof.set_sink(woof.beam_logger_sink) 14 - woof.set_sink(woof.default_sink) 18 + woof.set_sinks([woof.default_sink, woof.beam_logger_sink]) 15 19 16 20 let assert Ok(_) = 17 21 ewe.new(handler) ··· 27 31 req.path 28 32 <> { req.query |> option.map(fn(m) { "?" <> m }) |> option.unwrap("") } 29 33 30 - use <- woof.with_context([ 34 + woof.info("Incoming request", [ 35 + woof.str("method", req.method |> http.method_to_string()), 31 36 woof.str("path+query", path_with_query), 32 37 ]) 33 - woof.info("Incoming request", []) 34 38 35 39 let le_error = 36 40 response.new(500) 37 41 |> response.set_header("content-type", "text/plain; charset=utf-8") 38 42 |> response.set_body(ewe.TextData( 39 - "An issue occured. If this keeps happening, open an issue at <https://tangled.org/strawmelonjuice.com/fonts/issues>.", 43 + "An internal issue occured. If this keeps happening, open an issue at <https://tangled.org/strawmelonjuice.com/fonts/issues>.", 40 44 )) 41 45 46 + use <- bool.guard(when: req.method != http.Get, return: { 47 + response.new(405) 48 + |> response.set_header("content-type", "text/plain; charset=utf-8") 49 + |> response.set_body(ewe.TextData( 50 + "Method not allowed!\n\nThis proxy only answers GET requests, got a " 51 + <> req.method |> http.method_to_string() 52 + <> " request.\n\nIf this is incorrect behaviour, open an issue at <https://tangled.org/strawmelonjuice.com/fonts/issues>.", 53 + )) 54 + }) 55 + use <- bool.lazy_guard(path_with_query == "/", fn() { 56 + let le_error = fn() { 57 + woof.error("Can not read '<priv>/index.html', something is wrong.", []) 58 + le_error 59 + } 60 + case application.priv_directory("fonts") { 61 + Error(_) -> le_error() 62 + Ok(priv) -> { 63 + case simplifile.read(priv <> "/index.html") { 64 + Ok(index_html) -> 65 + response.new(200) 66 + |> response.set_header("content-type", "text/html; charset=utf-8") 67 + |> response.set_body(ewe.TextData(index_html)) 68 + 69 + Error(_) -> le_error() 70 + } 71 + } 72 + } 73 + }) 74 + use <- bool.lazy_guard(path_with_query == "/oat.min.js", fn() { 75 + let le_error = fn() { 76 + woof.error("Can not read '<priv>/oat.min.js', something is wrong.", []) 77 + le_error 78 + } 79 + case application.priv_directory("fonts") { 80 + Error(_) -> le_error() 81 + Ok(priv) -> { 82 + case simplifile.read(priv <> "/oat.min.js") { 83 + Ok(index_html) -> 84 + response.new(200) 85 + |> response.set_header( 86 + "content-type", 87 + "application/javascript; charset=utf-8", 88 + ) 89 + |> response.set_body(ewe.TextData(index_html)) 90 + 91 + Error(_) -> le_error() 92 + } 93 + } 94 + } 95 + }) 96 + use <- bool.lazy_guard(path_with_query == "/oat.min.css", fn() { 97 + let le_error = fn() { 98 + woof.error("Can not read '<priv>/oat.min.css', something is wrong.", []) 99 + le_error 100 + } 101 + case application.priv_directory("fonts") { 102 + Error(_) -> le_error() 103 + Ok(priv) -> { 104 + case simplifile.read(priv <> "/oat.min.css") { 105 + Ok(index_html) -> 106 + response.new(200) 107 + |> response.set_header("content-type", "text/css; charset=utf-8") 108 + |> response.set_body(ewe.TextData(index_html)) 109 + 110 + Error(_) -> le_error() 111 + } 112 + } 113 + } 114 + }) 115 + use <- bool.lazy_guard(path_with_query == "/favicon.ico", fn() { 116 + let le_error = fn() { 117 + woof.error("Can not read '<priv>/icon.svg', something is wrong.", []) 118 + le_error 119 + } 120 + case application.priv_directory("fonts") { 121 + Error(_) -> le_error() 122 + Ok(priv) -> { 123 + case simplifile.read(priv <> "/icon.svg") { 124 + Ok(index_html) -> 125 + response.new(200) 126 + |> response.set_header( 127 + "content-type", 128 + "image/svg+xml; charset=utf-8", 129 + ) 130 + |> response.set_body(ewe.TextData(index_html)) 131 + 132 + Error(_) -> le_error() 133 + } 134 + } 135 + } 136 + }) 137 + 42 138 use <- bool.lazy_guard( 43 139 !{ 44 140 string.contains(path_with_query, "fonts.googleapis.com") 45 141 |> bool.or(string.contains(path_with_query, "fonts.gstatic.com")) 46 142 }, 47 143 fn() { 48 - woof.warning("400: Is not a Google Fonts url.", []) 144 + woof.warning("[http/400] Is not a Google Fonts url.", [ 145 + woof.str("path+query", path_with_query), 146 + ]) 49 147 response.new(400) 50 148 |> response.set_header("content-type", "text/plain; charset=utf-8") 51 149 |> response.set_body(ewe.TextData( ··· 54 152 }, 55 153 ) 56 154 57 - let item = path_with_query 58 - // An url (and file path) safe version of the request. 155 + // The path with queries is now officially a gfonts item_url. 156 + let item_url = path_with_query |> string.remove_prefix("/") 157 + // A url (and file path) safe version of the request. 59 158 let item_id = 60 - bit_array.from_string(item) 159 + bit_array.from_string(item_url) 61 160 |> bit_array.base64_url_encode(False) 161 + use <- woof.with_context([ 162 + woof.str("item-url", item_url), 163 + woof.str("item-id", item_id), 164 + ]) 62 165 63 166 case 64 167 simplifile.is_file("./data/" <> item_id <> "/payload"), 65 - simplifile.is_file("./data/" <> item_id <> "/mime") 168 + simplifile.is_file("./data/" <> item_id <> "/content_type") 66 169 { 67 170 // We cached from last time! 68 171 Ok(True), Ok(True) -> { 69 - case simplifile.read("./data/" <> item_id <> "/mime") { 70 - Ok(mime) -> { 172 + case simplifile.read("./data/" <> item_id <> "/content_type") { 173 + Ok(content_type) -> { 174 + use <- woof.with_context([woof.str("item-content-type", content_type)]) 71 175 case simplifile.read_bits("./data/" <> item_id <> "/payload") { 72 176 Ok(payload) -> { 177 + woof.info("[http/200] OK", []) 73 178 response.new(200) 74 - |> response.set_header("content-type", mime <> "; charset=utf-8") 179 + |> response.set_header( 180 + "item-content-type", 181 + content_type <> "; charset=utf-8", 182 + ) 75 183 |> response.set_body(ewe.BitsData(payload)) 76 184 } 77 185 Error(_) -> { 78 186 woof.emergency( 79 - "File system at '" 187 + "[http/500] File system at '" 80 188 <> "./data/" 81 189 <> item_id 82 - <> "/mime' seems unreachable!", 190 + <> "/content_type' seems unreachable!", 83 191 [], 84 192 ) 85 193 le_error ··· 88 196 } 89 197 Error(_) -> { 90 198 woof.emergency( 91 - "File system at '" 199 + "[http/500] File system at '" 92 200 <> "./data/" 93 201 <> item_id 94 - <> "/mime' seems unreachable!", 202 + <> "/content_type' seems unreachable!", 95 203 [], 96 204 ) 97 205 le_error ··· 99 207 } 100 208 } 101 209 // No cache or no valid cache found, time to see if we can fetch from Google. 102 - Ok(..), Ok(..) -> todo 210 + Ok(..), Ok(..) -> { 211 + case 212 + { 213 + use uri <- result.try(uri.parse(item_url)) 214 + use scheme <- result.try( 215 + uri.scheme 216 + |> option.unwrap("") 217 + |> http.scheme_from_string, 218 + ) 219 + use host <- result.try( 220 + uri.host 221 + |> option.to_result(Nil), 222 + ) 223 + let req = 224 + request.Request( 225 + method: http.Get, 226 + headers: [], 227 + body: <<>>, 228 + scheme: scheme, 229 + host: host, 230 + port: uri.port, 231 + path: uri.path, 232 + query: uri.query, 233 + ) 234 + Ok(req) 235 + } 236 + { 237 + Ok(origin_request) -> { 238 + case httpc.send_bits(origin_request) { 239 + Ok(resp) -> { 240 + case resp.status { 241 + 200 -> { 242 + let assert Ok(content_type) = 243 + response.get_header(resp, "content-type") 244 + use <- woof.with_context([ 245 + woof.str("item-content-type", content_type), 246 + ]) 247 + // We relink all Google mentions in CSS to ourself, if possible. 248 + let payload = case content_type { 249 + "text/css; charset=utf-8" -> 250 + resp.body 251 + |> bit_array.to_string() 252 + |> result.map(fn(origin_body) { 253 + string.replace(origin_body, "http", { 254 + uri.Uri( 255 + host: Some(req.host), 256 + scheme: Some(http.scheme_to_string(req.scheme)), 257 + userinfo: None, 258 + port: req.port, 259 + path: "", 260 + query: None, 261 + fragment: None, 262 + ) 263 + |> uri.to_string() 264 + <> "http" 265 + }) 266 + |> bit_array.from_string() 267 + }) 268 + |> result.unwrap(resp.body) 269 + _ -> { 270 + resp.body 271 + } 272 + } 273 + case simplifile.create_directory_all("./data/" <> item_id) { 274 + Ok(_) -> { 275 + case 276 + simplifile.write_bits( 277 + to: "./data/" <> item_id <> "/payload", 278 + bits: payload, 279 + ), 280 + simplifile.write( 281 + to: "./data/" <> item_id <> "/content_type", 282 + contents: content_type, 283 + ) 284 + { 285 + Ok(_), Ok(_) -> { 286 + woof.info("[http/200] OK", []) 287 + response.new(200) 288 + |> response.set_header("content-type", content_type) 289 + |> response.set_body(ewe.BitsData(payload)) 290 + } 291 + _, _ -> { 292 + woof.emergency( 293 + "[http/500] File system at '" 294 + <> "./data/" 295 + <> item_id 296 + <> "/' seems unreachable!", 297 + [], 298 + ) 299 + le_error 300 + } 301 + } 302 + } 303 + Error(_) -> { 304 + woof.emergency( 305 + "[http/500] File system at '" 306 + <> "./data/" 307 + <> item_id 308 + <> "/' seems unreachable!", 309 + [], 310 + ) 311 + le_error 312 + } 313 + } 314 + } 315 + 316 + code -> { 317 + response.new(code) 318 + |> response.set_header( 319 + "content-type", 320 + "text/plain; charset=utf-8", 321 + ) 322 + |> response.set_body(ewe.TextData( 323 + "Encountered a non 200-status code from Google Fonts.", 324 + )) 325 + } 326 + } 327 + } 328 + Error(httperror) -> { 329 + woof.error( 330 + "[http/599] Hit an HTTP error trying to contact origin.", 331 + [ 332 + woof.str("error name", { 333 + case httperror { 334 + httpc.InvalidUtf8Response -> "Invalid utf8 in response." 335 + httpc.FailedToConnect(..) -> "Failed to connect." 336 + httpc.ResponseTimeout -> "Response timed out." 337 + } 338 + }), 339 + ], 340 + ) 341 + response.new(599) 342 + |> response.set_header( 343 + "content-type", 344 + "text/plain; charset=utf-8", 345 + ) 346 + |> response.set_body(ewe.TextData( 347 + "An issue occured trying to talk to Google Fonts. If this keeps happening, open an issue at <https://tangled.org/strawmelonjuice.com/fonts/issues>.", 348 + )) 349 + } 350 + } 351 + } 352 + Error(_) -> { 353 + woof.error("Could not create a http request out of this item.", []) 354 + le_error 355 + } 356 + } 357 + } 103 358 104 359 _, _ -> { 105 360 woof.emergency( 106 - "File system at '" <> "./data/" <> item_id <> "' seems unreachable!", 361 + "[http/500] File system at '" 362 + <> "./data/" 363 + <> item_id 364 + <> "' seems unreachable!", 107 365 [], 108 366 ) 109 367 le_error