Posts tagged "Extramaze"urn:www-greghendershott-com:Extramaze2018-05-20T04:00:00ZExtramaze LLC: Using system fonts (not Google fonts)urn:www-greghendershott-com:-2018-05-extramaze-llc-using-system-fonts-not-google-fonts.html2018-05-20T04:00:00Z2018-05-20T04:00:00ZGreg Hendershott
<div>
<article>
<header>
<h1>Extramaze LLC: Using system fonts (not Google fonts)</h1>
<p class="date-and-tags">
<time datetime="2018-05-20" pubdate="true">2018-05-20</time> :: <span class="tags"><a href="/tags/Racket.html">Racket</a>, <a href="/tags/Extramaze.html">Extramaze</a></span></p></header>
<blockquote>
<p>Update: Due to lack of interest/use, in June 2021 this site was shut down and user data (emails, names, search alerts) deleted from all systems and backups.</p></blockquote>
<p>In my <a href="/2018/05/extramaze-llc-using-racket-postgresql-aws-but-no-ads-or-js.html">previous post</a> I discussed what I’m doing with <a href="https://deals.extramaze.com">deals.extramaze.com</a> — and what I’m intentionally <em>not</em> doing. Since then, I’m not-doing more. This improves performance and simplifies the content security policy.</p><!-- more-->
<p>In the <a href="/2018/05/extramaze-llc-using-racket-postgresql-aws-but-no-ads-or-js.html#trying-to-do-the-right-thing-privacy">privacy</a> section I described various third-party services the site is <em>not</em> using. On <a href="https://www.recurse.com/">Recurse Center’s</a> private chat (<a href="https://zulipchat.com">Zulip</a>), <a href="https://twitter.com/jab_______">Josh Bronson</a> pointed out that using Google-hosted fonts might not be the best idea:</p>
<ul>
<li>
<p>Privacy: Using <code>fonts.googleapis.com</code> tells Google what sites the user is visiting. It doesn’t make much sense to avoid Google Analytics but keep using Google Fonts.</p></li>
<li>
<p>Performance: Downloading custom fonts takes time. This can be especially annoying on slower mobile connections.</p></li></ul>
<p>Often these are two sides of the same coin: Leaking information to a third party usually consumes extra time and bandwidth to make extra connections and transfer data.</p>
<p>Instead host the font files locally? That would address privacy. But it could make performance worse not better. Using fonts hosted by Google <em>does</em> have the advantage that the user might already have downloaded them, as a result of some other web site using them.</p>
<p>Josh asked if I’d considered using system fonts, and shared <a href="https://furbo.org/2018/03/28/system-fonts-in-css/">two</a> <a href="http://markdotto.com/2018/02/07/github-system-fonts/">links</a>. Initially I was reluctant — but but but… my carefully chosen fonts! And yes, it changed the look and feel of the site, somewhat. But after living with it for about half an hour, I thought it was just fine.</p>
<h1 id="accessibility">Accessibility</h1>
<p>At the same time I was looking at fonts, I’d been starting to review accessibility issues. Such as making sure that:</p>
<ul>
<li><code>img</code> elements have <code>alt</code> attributes</li>
<li><code>svg</code> elements have child <code>title</code> elements</li>
<li>the <code>html</code> element has a <code>lang=en</code> attribute</li>
<li>all <code>id</code> attributes on a page are unique</li></ul>
<p>Another item on this list: Ensuring that font colors have sufficient contrast. So I did that while I had the hood open.</p>
<p>(I have more work to do for accessibility: I’ve only just started to use the site with the macOS screen reader. Maybe this should be another blog post.)</p>
<h1 id="content-security-policy">Content Security Policy</h1>
<p>Dropping Google hosted fonts let me simplify the content security policy. Indeed it seems to have cut down on the number of violation reports.</p>
<p>Since my last blog post, I had temporarily switched from <code>Content-Security-Policy</code> to <code>Content-Security-Policy-Report-Only</code>. I was nervous because I didn’t understand all the violations. <a href="https://report-uri.com">report-uri.com</a> helps by filtering things like violations caused by browser extensions. But even after such filtering, I had violations that made no sense to me.</p>
<p>Using system fonts, that situation improved significantly. I feel good about cranking it back up again to be enforced instead of report-only.<sup><a href="#extramaze-llc-using-system-fonts-not-google-fonts-footnote-1-definition" name="extramaze-llc-using-system-fonts-not-google-fonts-footnote-1-return">1</a></sup></p>
<p>I suppose this illustrates a concern using any third-party service. Effectively it mutates your web site with various scripts or fonts or styles. What are all its mutations? You may think you know from observation: Oh it needs a script from a certain URI. But oops, in a certain scenario it turns out that it also needs a style from another URI. Surprise. And that’s just today. What if the service changes in the future?</p>
<p>Really, every service ought to state exactly what content security policy settings it needs to work.</p>
<div class="footnotes">
<ol>
<li id="extramaze-llc-using-system-fonts-not-google-fonts-footnote-1-definition" class="footnote-definition">
<p>Of course you can use both headers. <code>Content-Security-Policy</code> is what you’re enforcing, and <code>Content-Security-Policy-Report-Only</code> can be used to dry-run changes. <a href="#extramaze-llc-using-system-fonts-not-google-fonts-footnote-1-return">↩</a></p></li></ol></div>
<footer></footer></article></div>Extramaze LLC: Using Racket, PostgreSQL, AWS (but no ads or JS)urn:www-greghendershott-com:-2018-05-extramaze-llc-using-racket-postgresql-aws-but-no-ads-or-js.html2018-05-04T04:00:00Z2018-05-04T04:00:00ZGreg Hendershott
<div>
<article>
<header>
<h1>Extramaze LLC: Using Racket, PostgreSQL, AWS (but no ads or JS)</h1>
<p class="date-and-tags">
<time datetime="2018-05-04" pubdate="true">2018-05-04</time> :: <span class="tags"><a href="/tags/Racket.html">Racket</a>, <a href="/tags/Extramaze.html">Extramaze</a></span></p></header>
<blockquote>
<p>Update: Due to lack of interest/use, in June 2021 this site was shut down and user data (emails, names, search alerts) deleted from all systems and backups.</p></blockquote>
<p>For Extramaze LLC I’m using Racket in a commercial project — a search engine with email alerts for deals on music gear — <a href="https://deals.extramaze.com">deals.extramaze.com</a>.</p>
<p>This blog post is a sort of whirlwind tour of the use case and business model, as well as how I use things like Racket, PostgreSQL, and AWS — but don’t use advertising or JavaScript.</p><!-- more-->
<h1 id="use-case">Use case</h1>
<p>Who needs this?</p>
<p>Let’s say I play guitar. In fact, I seem to collect guitars. My partner is skeptical of this use of financial and space resources. I have my eye on a certain 7-string guitar. I don’t <em>need</em> it. But I want it. If it goes on sale in the months ahead, I’d like to grab it. I can point out that I saved (say) $500. Although my partner will see right through this pathetic justification, they will appreciate that at least I am hearing them and making an effort. I hope.</p>
<p>Or more virtuously: I’m a parent and my child needs a trombone when school starts this autumn. I create an alert in case something goes on sale over the summer.</p>
<h1 id="business-model">Business model</h1>
<p>Currently you must join (create an account) to use the site. It’s free to search deals that are at least a day old. If you subscribe (pay), you can search the very newest deals — and save searches to get alerted when matching deals appear.</p>
<p>Why ask people to pay? I’m disenchanted with services that survive on money from advertising — where users are the product being sold, and the actual customers are advertisers.</p>
<p>Instead I want to do something where <strong>users = customers</strong>.</p>
<ul>
<li>
<p>I like making things that people like enough to pay for.</p></li>
<li>
<p>As a practical matter, it can be hard enough to make one constituency happy. Two is double the difficulty — even more when interests conflict. A small company has enough challenges; why build more into the foundation.</p></li></ul>
<p>If it turns out that not enough people want to pay for this service? I’d rather discontinue it than turn to advertising.</p>
<h1 id="picking-a-name">Picking a name</h1>
<p>The last time I picked a company name, the name was “Cakewalk”<sup><a href="#extramaze-llc-using-racket-postgresql-aws-but-no-ads-or-js-footnote-1-definition" name="extramaze-llc-using-racket-postgresql-aws-but-no-ads-or-js-footnote-1-return">1</a></sup> and the year was 1987. Nearly a decade passed, the internet grew popular, and we registered <code>cakewalk.com</code>. Easy.</p>
<p>The experience now is… different. You may be surprised to learn that many desirable domain names are already registered. I know, right? After too many days agonizing with dictionaries and a thesaurus, I settled on a contraction of “extra” and “amaze” — Extramaze.</p>
<p>It turns out that “extramaze” is sometimes used to describe cues outside a lab maze. Although that’s a bit creepy, I’m relieved the cues are <em>outside</em> the maze — this alludes to beacons of hope shining on those of us stuck inside our own mazes! Or something. Moving on….</p>
<h1 id="trying-to-do-the-right-thing-privacy">Trying to do the right thing: Privacy</h1>
<p>The web site does not use third-party JavaScript. No Google Analytics. No social buttons. Nothing.<sup><a href="#extramaze-llc-using-racket-postgresql-aws-but-no-ads-or-js-footnote-2-definition" name="extramaze-llc-using-racket-postgresql-aws-but-no-ads-or-js-footnote-2-return">2</a></sup> The one exception — if you subscribe — is Stripe for payments.</p>
<p>If you don’t believe me look at the relevant parts of the <code>Content-Security-Policy</code> response header value:</p>
<pre><code>default-src 'none'; script-src https://checkout.stripe.com;</code></pre>
<p>(Indeed notice that <code>script-src</code> doesn’t include <code>'self'</code> — the site itself supplies no JavaScript. If you ask “Why not?” I can only respond with another question: “Why?”. The UX doesn’t require it. Maybe someday. Meanwhile it is one less facet to develop, test, debug, and examine for vulnerabilities.)</p>
<p>Recently I experimented with Google and Twitter ads. Click-throughs were OK but conversion was poor. I wondered if account-creation was a speed bump, especially on mobile. So I added “Login with {Google, Twitter}” buttons. Of course if you use those, you are sharing some information with {Google, Twitter}. However you can still create and use a native Extramaze account.</p>
<p>So much for third-party data collection. How about first-party?</p>
<ul>
<li>The web site does basic web server request and response logging.</li>
<li>When people create an account:
<ul>
<li>The service asks for:
<ul>
<li>an email address</li>
<li>a how-would-you-like-to-be-addressed name</li>
<li>a password</li></ul></li>
<li>Successful and failed logins are stored — the latter to do an exponential back-off on subsequent attempts.</li></ul></li>
<li>When people also pay to subscribe:
<ul>
<li>Stripe asks for credit card and zip code information. Extramaze does not see or store this.</li>
<li>They gain the ability to create saved searches, and get email alerts. Of course we need to store the search phrase, e.g. <code>7-string left hand guitar</code>.</li></ul></li></ul>
<p>Beyond that, the service collects no personal information.</p>
<h1 id="trying-to-do-the-right-thing-security">Trying to do the right thing: Security</h1>
<p>I’ve spent a lot of time thinking through the privacy and security implications of account sign-up and password resets. This was the scariest part for me. I found Troy Hunt’s <a href="https://www.troyhunt.com/everything-you-ever-wanted-to-know/"><em>Everything you ever wanted to know about building a secure password reset feature</em></a> incredibly helpful and follow its recommendations. Also useful: <a href="https://stackoverflow.com/questions/549/the-definitive-guide-to-form-based-website-authentication"><em>The definitive guide to form-based website authentication</em></a>.</p>
<p>I already mentioned <code>Content-Security-Policy</code>. Information about this and other security headers is at <a href="https://securityheaders.com/?q=https%3A%2F%2Fdeals.extramaze.com&followRedirects=on">securityheaders.com</a>. Some headers let browsers report problems and <a href="https://report-uri.com">https://report-uri.com</a> is helpful here.</p>
<p>I won’t say, “We take security very seriously”, because that’s become a cliché. (Is there a web site that does for incident disclosures what OurIncredibleJourney.com does for acquihire notices?)</p>
<p>But. I think about security frequently. I read as much as I can about other people’s experiences and recommendations. I assume that vulnerabilities exist and the only question is who will find them; I need to exercise a commercially reasonable effort to find them first.</p>
<h1 id="ingredients">Ingredients</h1>
<p>There are three main pieces:</p>
<ul>
<li>Crawl certain music retailer web sites looking for deals (daily).</li>
<li>Send search alert emails (daily).</li>
<li>Run a web site (24/7).</li></ul>
<p>All use some mix of:</p>
<ul>
<li>Racket</li>
<li>PostgreSQL</li>
<li>AWS (EC2, ELB, ECS, SES, SNS, RDS, CloudWatch)</li></ul>
<h2 id="racket-crawling-and-scraping">Racket: Crawling and scraping</h2>
<p>Most music product web stores have a section — variously called “outlet”, “clearance”, “deal zone”, or “blowout” — where the deals are featured. Deals include demo units, price drops, and so on. For demo units the product might be “like new” condition, or there might be a cosmetic flaw which is described.</p>
<p>Currently we crawl these areas of <a href="https://www.sweetwater.com/dealzone/">Sweetwater</a> and <a href="https://www.zzounds.com/blowouts">Zzounds</a>.</p>
<p>More would be better. On the other hand, one step at a time. Furthermore, these days many web sites block crawlers by default. It doesn’t matter if you <code>GET /robots.txt</code> and follow its rules scrupulously. Instead you must ask a human for permission and get white-listed. When you ask, the human might say no, or simply not reply at all.</p>
<p>Racket has great “batteries included” for making HTTP requests, and parsing HTML responses into <a href="https://docs.racket-lang.org/xml/index.html#%28def._%28%28lib._xml%2Fprivate%2Fxexpr-core..rkt%29._xexpr~3f%29%29">x-expressions</a>. That is, HTML such as</p>
<div class="brush: html">
<table class="sourcetable">
<tbody>
<tr>
<td class="linenos">
<div class="linenodiv">
<pre>1
2
3
4
5
6</pre></div></td>
<td class="code">
<div class="source">
<pre><span></span><span class="p"><</span><span class="nt">p</span> <span class="na">class</span><span class="o">=</span><span class="s">"class"</span><span class="p">></span>
Some
<span class="p"><</span><span class="nt">em</span><span class="p">></span>awesome<span class="p"></</span><span class="nt">em</span><span class="p">></span>
HTML
<span class="ni">&tm;</span>
<span class="p"></</span><span class="nt">p</span><span class="p">></span>
</pre></div>
</td></tr></tbody></table>
</div>
<p>is equivalent to the x-expression</p>
<div class="brush: racket">
<table class="sourcetable">
<tbody>
<tr>
<td class="linenos">
<div class="linenodiv">
<pre>1
2
3
4
5</pre></div></td>
<td class="code">
<div class="source">
<pre><span></span><span class="o">'</span><span class="p">(</span><span class="ss">p</span> <span class="p">([</span><span class="ss"><a href="http://docs.racket-lang.org/reference/createclass.html#(form._((lib._racket/private/class-internal..rkt)._class))" style="color: inherit">class</a></span> <span class="s2">"class"</span><span class="p">])</span>
<span class="s2">"Some "</span>
<span class="p">(</span><span class="ss">em</span> <span class="p">()</span> <span class="s2">"awesome"</span><span class="p">)</span>
<span class="s2">" HTML"</span>
<span class="ss">tm</span><span class="p">)</span>
</pre></div>
</td></tr></tbody></table>
</div>
<p>As the Lisp Evangelism Task Force will point out on Hacker News every few weeks, x-expressions are what XML would be if it weren’t produced by the Department of Redundancy Department.</p>
<p>Finding the “interesting bits” within an x-expression can be done in various ways. In simple cases, you can use Racket’s <code><a href="http://docs.racket-lang.org/reference/match.html#(form._((lib._racket/match..rkt)._match))" style="color: inherit">match</a></code>, but for complicated HTML that can be tedious and/or brittle. <code><a href="http://docs.racket-lang.org/xml/index.html#(def._((lib._xml/path..rkt)._se-path*/list))" style="color: inherit">se-path*/list</a></code> is a better idea, but you can only select a direct path of element tags. You can’t express CSS selector things like, “Find all <code>p</code> elements somewhere under <code>div</code>s having a <code>"container"</code> <code>class</code>”.</p>
<p>So I wrote a function to do a depth-first folding walk of an x-expression. In addition to accumulating a result value, it accumulates a “path” — a list of full x-expressions from “this” one up through its ancestors to the root, full x-expression:</p>
<div class="brush: racket">
<table class="sourcetable">
<tbody>
<tr>
<td class="linenos">
<div class="linenodiv">
<pre> 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17</pre></div></td>
<td class="code">
<div class="source">
<pre><span></span><span class="p">(</span><span class="k"><a href="http://docs.racket-lang.org/reference/define.html#(form._((lib._racket/private/base..rkt)._define))" style="color: inherit">define</a></span> <span class="nb"><a href="http://docs.racket-lang.org/reference/Manipulating_Paths.html#(def._((quote._~23~25kernel)._path~3f))" style="color: inherit">path?</a></span> <span class="p">(</span><span class="nb"><a href="http://docs.racket-lang.org/reference/data-structure-contracts.html#(def._((lib._racket/contract/base..rkt)._listof))" style="color: inherit">listof</a></span> <span class="n"><a href="http://docs.racket-lang.org/xml/index.html#(def._((lib._xml/private/xexpr-core..rkt)._xexpr/c))" style="color: inherit">xexpr/c</a></span><span class="p">))</span>
<span class="c1">;; A depth-first folding walk of the xexpr. The "path" is a list of</span>
<span class="c1">;; xexpr from the current one to its ancestors. You can `match` on</span>
<span class="c1">;; this to do the equivalent of `se-path*/list`, but with the full</span>
<span class="c1">;; power of `match`.</span>
<span class="p">(</span><span class="k"><a href="http://docs.racket-lang.org/reference/define.html#(form._((lib._racket/private/base..rkt)._define))" style="color: inherit">define</a></span> <span class="p">(</span><span class="n">walk</span> <span class="n">f</span> <span class="n">v</span> <span class="n">x</span><span class="p">)</span>
<span class="p">(</span><span class="k"><a href="http://docs.racket-lang.org/reference/let.html#(form._((lib._racket/private/letstx-scheme..rkt)._let))" style="color: inherit">let</a></span> <span class="n">recur</span> <span class="p">([</span><span class="n">path</span> <span class="o">'</span><span class="p">()]</span>
<span class="p">[</span><span class="n">v</span> <span class="n">v</span><span class="p">]</span>
<span class="p">[</span><span class="n">x</span> <span class="n">x</span><span class="p">])</span>
<span class="p">(</span><span class="k"><a href="http://docs.racket-lang.org/reference/define.html#(form._((lib._racket/private/base..rkt)._define))" style="color: inherit">define</a></span> <span class="n">this-path</span> <span class="p">(</span><span class="nb"><a href="http://docs.racket-lang.org/reference/pairs.html#(def._((quote._~23~25kernel)._cons))" style="color: inherit">cons</a></span> <span class="n">x</span> <span class="n">path</span><span class="p">))</span>
<span class="p">(</span><span class="n">f</span> <span class="p">(</span><span class="k"><a href="http://docs.racket-lang.org/reference/match.html#(form._((lib._racket/match..rkt)._match))" style="color: inherit">match</a></span> <span class="n">x</span>
<span class="p">[(</span><span class="nb"><a href="http://docs.racket-lang.org/reference/pairs.html#(def._((quote._~23~25kernel)._list*))" style="color: inherit">list*</a></span> <span class="p">(</span><span class="n">?</span> <span class="nb"><a href="http://docs.racket-lang.org/reference/symbols.html#(def._((quote._~23~25kernel)._symbol~3f))" style="color: inherit">symbol?</a></span> <span class="k"><a href="http://docs.racket-lang.org/reference/creatingunits.html#(form._((lib._racket/unit..rkt)._tag))" style="color: inherit">tag</a></span><span class="p">)</span> <span class="p">(</span><span class="n">?</span> <span class="nb"><a href="http://docs.racket-lang.org/reference/pairs.html#(def._((quote._~23~25kernel)._list~3f))" style="color: inherit">list?</a></span><span class="p">)</span> <span class="n">xs</span><span class="p">)</span>
<span class="p">(</span><span class="k"><a href="http://docs.racket-lang.org/reference/for.html#(form._((lib._racket/private/base..rkt)._for/fold))" style="color: inherit">for/fold</a></span> <span class="p">([</span><span class="n">v</span> <span class="n">v</span><span class="p">])</span> <span class="p">([</span><span class="n">x</span> <span class="n">xs</span><span class="p">])</span>
<span class="p">(</span><span class="n">recur</span> <span class="n">this-path</span> <span class="n">v</span> <span class="n">x</span><span class="p">))]</span>
<span class="p">[</span><span class="k"><a href="http://docs.racket-lang.org/reference/stx-patterns.html#(form._((lib._racket/private/stxcase-scheme..rkt).__))" style="color: inherit">_</a></span> <span class="n">v</span><span class="p">])</span>
<span class="n">this-path</span><span class="p">)))</span>
</pre></div>
</td></tr></tbody></table>
</div>
<p>I wrapped this in a simple <code>select</code> function that <code>conjoin</code>s one or more predicates:</p>
<div class="brush: racket">
<table class="sourcetable">
<tbody>
<tr>
<td class="linenos">
<div class="linenodiv">
<pre> 1
2
3
4
5
6
7
8
9
10
11
12</pre></div></td>
<td class="code">
<div class="source">
<pre><span></span><span class="c1">;; Using quasiquoted match expressions with `walk` can be tedious and</span>
<span class="c1">;; error-prone. Often you end up specifying the match in more detail</span>
<span class="c1">;; than is really necessary. A sometimes friendlier way is to use</span>
<span class="c1">;; `select` with selector combinators grouped using `conjoin` (and</span>
<span class="c1">;; maybe `disjoin`).</span>
<span class="p">(</span><span class="k"><a href="http://docs.racket-lang.org/reference/define.html#(form._((lib._racket/private/base..rkt)._define))" style="color: inherit">define</a></span> <span class="p">(</span><span class="n">select</span> <span class="n">x</span> <span class="o">.</span> <span class="n">fs</span><span class="p">)</span> <span class="c1">;(-> xexpr/c (-> path? any) ...+ list?)</span>
<span class="p">(</span><span class="n">walk</span> <span class="p">(</span><span class="k"><a href="http://docs.racket-lang.org/reference/lambda.html#(form._((lib._racket/private/base..rkt)._~ce~bb))" style="color: inherit">λ</a></span> <span class="p">(</span><span class="n">vs</span> <span class="n">path</span><span class="p">)</span>
<span class="p">(</span><span class="k"><a href="http://docs.racket-lang.org/reference/match.html#(form._((lib._racket/match..rkt)._match))" style="color: inherit">match</a></span> <span class="p">((</span><span class="nb"><a href="http://docs.racket-lang.org/reference/procedures.html#(def._((lib._racket/private/base..rkt)._apply))" style="color: inherit">apply</a></span> <span class="nb"><a href="http://docs.racket-lang.org/reference/procedures.html#(def._((lib._racket/function..rkt)._conjoin))" style="color: inherit">conjoin</a></span> <span class="n">fs</span><span class="p">)</span> <span class="n">path</span><span class="p">)</span>
<span class="p">[</span><span class="no">#f</span> <span class="n">vs</span><span class="p">]</span>
<span class="p">[</span><span class="n">v</span> <span class="p">(</span><span class="nb"><a href="http://docs.racket-lang.org/reference/pairs.html#(def._((quote._~23~25kernel)._cons))" style="color: inherit">cons</a></span> <span class="n">v</span> <span class="n">vs</span><span class="p">)]))</span>
<span class="o">'</span><span class="p">()</span>
<span class="n">x</span><span class="p">))</span>
</pre></div>
</td></tr></tbody></table>
</div>
<p>I wrote a few functions intended to be used as combinators with <code>conjoin</code>, to do roughly CSS selector style matching. A few example pieces:</p>
<div class="brush: racket">
<table class="sourcetable">
<tbody>
<tr>
<td class="linenos">
<div class="linenodiv">
<pre> 1
2
3
4
5
6
7
8
9
10
11
12</pre></div></td>
<td class="code">
<div class="source">
<pre><span></span><span class="p">(</span><span class="k"><a href="http://docs.racket-lang.org/reference/define.html#(form._((lib._racket/private/base..rkt)._define))" style="color: inherit">define</a></span> <span class="p">(</span><span class="k"><a href="http://docs.racket-lang.org/reference/createclass.html#(form._((lib._racket/private/class-internal..rkt)._this))" style="color: inherit">this</a></span> <span class="n">path</span><span class="p">)</span>
<span class="p">(</span><span class="k"><a href="http://docs.racket-lang.org/reference/match.html#(form._((lib._racket/match..rkt)._match))" style="color: inherit">match</a></span> <span class="n">path</span>
<span class="p">[(</span><span class="nb"><a href="http://docs.racket-lang.org/reference/pairs.html#(def._((quote._~23~25kernel)._cons))" style="color: inherit">cons</a></span> <span class="n">v</span> <span class="k"><a href="http://docs.racket-lang.org/reference/stx-patterns.html#(form._((lib._racket/private/stxcase-scheme..rkt).__))" style="color: inherit">_</a></span><span class="p">)</span> <span class="n">v</span><span class="p">]</span>
<span class="p">[</span><span class="k"><a href="http://docs.racket-lang.org/reference/stx-patterns.html#(form._((lib._racket/private/stxcase-scheme..rkt).__))" style="color: inherit">_</a></span> <span class="no">#f</span><span class="p">]))</span>
<span class="p">(</span><span class="k"><a href="http://docs.racket-lang.org/reference/define.html#(form._((lib._racket/private/base..rkt)._define))" style="color: inherit">define</a></span> <span class="p">(</span><span class="k"><a href="http://docs.racket-lang.org/reference/creatingunits.html#(form._((lib._racket/unit..rkt)._tag))" style="color: inherit">tag</a></span> <span class="n">path</span><span class="p">)</span>
<span class="p">(</span><span class="k"><a href="http://docs.racket-lang.org/reference/match.html#(form._((lib._racket/match..rkt)._match))" style="color: inherit">match</a></span> <span class="p">(</span><span class="k"><a href="http://docs.racket-lang.org/reference/createclass.html#(form._((lib._racket/private/class-internal..rkt)._this))" style="color: inherit">this</a></span> <span class="n">path</span><span class="p">)</span>
<span class="p">[(</span><span class="nb"><a href="http://docs.racket-lang.org/reference/pairs.html#(def._((quote._~23~25kernel)._list))" style="color: inherit">list</a></span> <span class="p">(</span><span class="n">?</span> <span class="nb"><a href="http://docs.racket-lang.org/reference/symbols.html#(def._((quote._~23~25kernel)._symbol~3f))" style="color: inherit">symbol?</a></span> <span class="n">v</span><span class="p">)</span> <span class="k"><a href="http://docs.racket-lang.org/reference/stx-patterns.html#(form._((lib._racket/private/stxcase-scheme..rkt).__))" style="color: inherit">_</a></span> <span class="k"><a href="http://docs.racket-lang.org/reference/stx-patterns.html#(form._((lib._racket/private/stxcase-scheme..rkt)._......))" style="color: inherit">...</a></span><span class="p">)</span> <span class="n">v</span><span class="p">]</span>
<span class="p">[</span><span class="k"><a href="http://docs.racket-lang.org/reference/stx-patterns.html#(form._((lib._racket/private/stxcase-scheme..rkt).__))" style="color: inherit">_</a></span> <span class="no">#f</span><span class="p">]))</span>
<span class="p">(</span><span class="k"><a href="http://docs.racket-lang.org/reference/define.html#(form._((lib._racket/private/base..rkt)._define))" style="color: inherit">define</a></span> <span class="p">((</span><span class="n">tag?</span> <span class="n">sym</span><span class="p">)</span> <span class="n">path</span><span class="p">)</span>
<span class="p">(</span><span class="nb"><a href="http://docs.racket-lang.org/reference/Equality.html#(def._((quote._~23~25kernel)._equal~3f))" style="color: inherit">equal?</a></span> <span class="p">(</span><span class="k"><a href="http://docs.racket-lang.org/reference/creatingunits.html#(form._((lib._racket/unit..rkt)._tag))" style="color: inherit">tag</a></span> <span class="n">path</span><span class="p">)</span> <span class="n">sym</span><span class="p">))</span>
</pre></div>
</td></tr></tbody></table>
</div>
<p>For example, to extract the value of the <code>href</code> attribute for all <code>a.some-class</code> elements whose immediate parent is a <code>div.box-class</code>:</p>
<div class="brush: racket">
<table class="sourcetable">
<tbody>
<tr>
<td class="linenos">
<div class="linenodiv">
<pre>1
2
3
4
5
6</pre></div></td>
<td class="code">
<div class="source">
<pre><span></span><span class="p">(</span><span class="n">select</span> <span class="n">xexpr</span>
<span class="p">(</span><span class="n">tag?</span> <span class="o">'</span><span class="ss">a</span><span class="p">)</span>
<span class="p">(</span><span class="nb"><a href="http://docs.racket-lang.org/reference/objectutils.html#(def._((lib._racket/private/class-internal..rkt)._class~3f))" style="color: inherit">class?</a></span> <span class="s2">"some-class"</span><span class="p">)</span>
<span class="p">(</span><span class="n">parent?</span> <span class="p">(</span><span class="nb"><a href="http://docs.racket-lang.org/reference/procedures.html#(def._((lib._racket/function..rkt)._conjoin))" style="color: inherit">conjoin</a></span> <span class="p">(</span><span class="n">tag?</span> <span class="o">'</span><span class="ss">div</span><span class="p">)</span>
<span class="p">(</span><span class="nb"><a href="http://docs.racket-lang.org/reference/objectutils.html#(def._((lib._racket/private/class-internal..rkt)._class~3f))" style="color: inherit">class?</a></span> <span class="s2">"some-box"</span><span class="p">)))</span>
<span class="p">(</span><span class="n">attr-val</span> <span class="o">'</span><span class="ss">href</span><span class="p">))</span>
</pre></div>
</td></tr></tbody></table>
</div>
<p>Of course you can layer on some shorthand compositions like <code>tag.class?</code>:</p>
<div class="brush: racket">
<table class="sourcetable">
<tbody>
<tr>
<td class="linenos">
<div class="linenodiv">
<pre>1
2
3</pre></div></td>
<td class="code">
<div class="source">
<pre><span></span><span class="p">(</span><span class="k"><a href="http://docs.racket-lang.org/reference/define.html#(form._((lib._racket/private/base..rkt)._define))" style="color: inherit">define</a></span> <span class="p">(</span><span class="n">tag.class?</span> <span class="n">sym</span> <span class="k"><a href="http://docs.racket-lang.org/reference/createclass.html#(form._((lib._racket/private/class-internal..rkt)._class))" style="color: inherit">class</a></span><span class="p">)</span>
<span class="p">(</span><span class="nb"><a href="http://docs.racket-lang.org/reference/procedures.html#(def._((lib._racket/function..rkt)._conjoin))" style="color: inherit">conjoin</a></span> <span class="p">(</span><span class="n">tag?</span> <span class="n">sym</span><span class="p">)</span>
<span class="p">(</span><span class="nb"><a href="http://docs.racket-lang.org/reference/objectutils.html#(def._((lib._racket/private/class-internal..rkt)._class~3f))" style="color: inherit">class?</a></span> <span class="k"><a href="http://docs.racket-lang.org/reference/createclass.html#(form._((lib._racket/private/class-internal..rkt)._class))" style="color: inherit">class</a></span><span class="p">)))</span>
</pre></div>
</td></tr></tbody></table>
</div>
<p>And the example becomes:</p>
<div class="brush: racket">
<table class="sourcetable">
<tbody>
<tr>
<td class="linenos">
<div class="linenodiv">
<pre>1
2
3
4</pre></div></td>
<td class="code">
<div class="source">
<pre><span></span><span class="p">(</span><span class="n">select</span> <span class="n">xexpr</span>
<span class="p">(</span><span class="n">tag.class?</span> <span class="o">'</span><span class="ss">a</span> <span class="s2">"some-class"</span><span class="p">)</span>
<span class="p">(</span><span class="n">parent?</span> <span class="p">(</span><span class="n">tag.class?</span> <span class="o">'</span><span class="ss">div</span> <span class="s2">"some-box"</span><span class="p">)</span>
<span class="p">(</span><span class="n">attr-val</span> <span class="o">'</span><span class="ss">href</span><span class="p">))</span>
</pre></div>
</td></tr></tbody></table>
</div>
<p>You <em>could</em> also write a parser from CSS selector syntax to these <code>select</code> expressions — maybe even define a <code>#lang css-selector</code>. But I like s-expressions. Even if I didn’t, I’m not using this extensively enough to warrant that work.</p>
<p>Likewise, although I’d like to open-source this as a distinct, complete project, I just haven’t had time to review it thoroughly, write documentation, and so on.</p>
<h2 id="racket-web-server">Racket: web server</h2>
<p>Racket has great “batteries included” for making web site servers. In addition to Racket’s documentation for this, I can recommend Jesse Alama’s resources including his blog <a href="https://lisp.sh">lisp.sh</a> and his book <a href="https://serverracket.com/">Server: Racket</a>.</p>
<p>Racket provides a macro called <a href="https://docs.racket-lang.org/web-server/dispatch.html"><code>dispatch-rules</code></a> to define bi-directional “routes”. You give it rules, of each which is a URI pattern and a handler function. It defines two functions covering all of your rules:</p>
<ul>
<li>A <code>request? -> response?</code> dispatcher.</li>
<li>A <code>procedure? -> string?</code> URI path maker.</li></ul>
<div class="brush: racket">
<table class="sourcetable">
<tbody>
<tr>
<td class="linenos">
<div class="linenodiv">
<pre>1
2
3
4
5
6
7</pre></div></td>
<td class="code">
<div class="source">
<pre><span></span><span class="p">(</span><span class="k"><a href="http://docs.racket-lang.org/reference/define.html#(form._((quote._~23~25kernel)._define-values))" style="color: inherit">define-values</a></span> <span class="p">(</span><span class="n">dispatch</span> <span class="n">handler->path</span><span class="p">)</span>
<span class="p">(</span><span class="n">dispatch-rules</span>
<span class="p">[(</span><span class="s2">""</span><span class="p">)</span> <span class="n">home</span><span class="p">]</span>
<span class="p">[(</span><span class="s2">"about"</span><span class="p">)</span> <span class="n">about</span><span class="p">]</span>
<span class="p">[(</span><span class="s2">"path"</span> <span class="s2">"to"</span> <span class="s2">"foo"</span><span class="p">)</span> <span class="n">path/to/foo</span><span class="p">]</span>
<span class="p">[(</span><span class="s2">"user"</span> <span class="p">(</span><span class="n">string-arg</span><span class="p">))</span> <span class="n">user/id</span><span class="p">]</span>
<span class="p">[</span><span class="k"><a href="http://docs.racket-lang.org/reference/if.html#(form._((lib._racket/private/letstx-scheme..rkt)._else))" style="color: inherit">else</a></span> <span class="n">not-found</span><span class="p">]))</span>
</pre></div>
</td></tr></tbody></table>
</div>
<p>Because I needed to do authorization, I wrapped this in my own macro, <code>dispatch-rules+roles</code>. This also defines a third function, <code>request?
-> roles?</code>: Given a request that matches one of the patterns, what roles is a user required to have to be authorized to access it?</p>
<p>In the following example, we use three roles:</p>
<ul>
<li>
<p><code>'anon</code> means an anonymous user.</p></li>
<li>
<p><code>'free</code> is a user who is authenticated (logged in); certain routes should only be available to them.</p></li>
<li>
<p><code>'paid</code> is an authenticated user who has also subscribed (paid) and therefore is authorized for even more routes.</p></li></ul>
<div class="brush: racket">
<table class="sourcetable">
<tbody>
<tr>
<td class="linenos">
<div class="linenodiv">
<pre> 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17</pre></div></td>
<td class="code">
<div class="source">
<pre><span></span><span class="p">(</span><span class="k"><a href="http://docs.racket-lang.org/reference/define.html#(form._((quote._~23~25kernel)._define-values))" style="color: inherit">define-values</a></span> <span class="p">(</span><span class="n">dispatch</span> <span class="n">handler->path</span> <span class="n">request->roles</span><span class="p">)</span>
<span class="p">(</span><span class="n">dispatch-rules+roles</span>
<span class="c1">;; Routes requiring 'anon or 'free or 'paid roles</span>
<span class="p">[(</span><span class="n">anon</span> <span class="n"><a href="http://docs.racket-lang.org/foreign/foreign_pointer-funcs.html#(def._((quote._~23~25foreign)._free))" style="color: inherit">free</a></span> <span class="n">paid</span><span class="p">)</span>
<span class="p">[(</span><span class="s2">""</span><span class="p">)</span> <span class="n">home</span><span class="p">]</span>
<span class="p">[(</span><span class="s2">"about"</span><span class="p">)</span> <span class="n">about</span><span class="p">]</span>
<span class="p">[(</span><span class="s2">"join"</span><span class="p">)</span> <span class="n">join</span><span class="p">]]</span>
<span class="c1">;; Routes requiring 'free or 'paid roles</span>
<span class="p">[(</span><span class="n"><a href="http://docs.racket-lang.org/foreign/foreign_pointer-funcs.html#(def._((quote._~23~25foreign)._free))" style="color: inherit">free</a></span> <span class="n">paid</span><span class="p">)</span>
<span class="p">[(</span><span class="n">logout</span><span class="p">)</span> <span class="n">logout</span><span class="p">]</span>
<span class="p">[(</span><span class="n">preferences</span><span class="p">)</span> <span class="n">preferences</span><span class="p">]</span>
<span class="p">[(</span><span class="n">subscribe</span><span class="p">)</span> <span class="n">subscribe</span><span class="p">]</span>
<span class="c1">;; Routes requiring 'paid role</span>
<span class="p">[(</span><span class="n">paid</span><span class="p">)</span>
<span class="p">[(</span><span class="n">payment</span><span class="p">)</span> <span class="n">payment</span><span class="p">]</span>
<span class="p">[(</span><span class="n">unsubscribe</span><span class="p">)</span> <span class="n">unsubscribe</span><span class="p">]]</span>
<span class="p">[</span><span class="k"><a href="http://docs.racket-lang.org/reference/if.html#(form._((lib._racket/private/letstx-scheme..rkt)._else))" style="color: inherit">else</a></span> <span class="n">not-found</span><span class="p">]))</span>
</pre></div>
</td></tr></tbody></table>
</div>
<p>My <code>dispatch-rules+roles</code> macro doesn’t itself do authorization — it defines the same old <code>dispatch</code> function that plain <code>dispatch-rules</code> does. You need to wrap that <code>dispatch</code> with something that consults <code>request->roles</code> and calls <code>dispatch</code> — or returns a <code>403 Forbidden</code> response (for an API) or redirects to a login or subscribe page (web app).</p>
<p>Of course, <em>that</em> in turn should be wrapped in something that sets the current user (so we know their roles) from e.g. a session key — or returns a <code>401</code> response (for an API) or redirects to a login page (web app).</p>
<p>Speaking of multiple wrappers around <code>dispatch</code>, this is a nice way to compose functionality, which I’ve seen in the Clojure <a href="https://github.com/ring-clojure/ring/wiki/Concepts#middleware">Ring</a> community. It’s cleaner to have one wrapper per bit of functionality, as opposed to one handler with a monolithic hairball of conditionals.</p>
<p>So a <code>handler?</code> is a function from <code>request?</code> to <code>response?</code>, like <code>dispatch</code>. A <code>wrapper?</code> is a function that takes a <code>handler?</code>, and returns a new <code>handler?</code>.</p>
<div class="brush: racket">
<table class="sourcetable">
<tbody>
<tr>
<td class="linenos">
<div class="linenodiv">
<pre>1
2</pre></div></td>
<td class="code">
<div class="source">
<pre><span></span><span class="p">(</span><span class="k"><a href="http://docs.racket-lang.org/reference/define.html#(form._((lib._racket/private/base..rkt)._define))" style="color: inherit">define</a></span> <span class="n">handler?</span> <span class="p">(</span><span class="k"><a href="http://docs.racket-lang.org/reference/function-contracts.html#(form._((lib._racket/contract/base..rkt)._-~3e))" style="color: inherit">-></a></span> <span class="n">request?</span> <span class="n">response?</span><span class="p">))</span>
<span class="p">(</span><span class="k"><a href="http://docs.racket-lang.org/reference/define.html#(form._((lib._racket/private/base..rkt)._define))" style="color: inherit">define</a></span> <span class="n">wrapper?</span> <span class="p">(</span><span class="k"><a href="http://docs.racket-lang.org/reference/function-contracts.html#(form._((lib._racket/contract/base..rkt)._-~3e))" style="color: inherit">-></a></span> <span class="n">handler?</span> <span class="n">handler?</span><span class="p">))</span>
</pre></div>
</td></tr></tbody></table>
</div>
<p>For instance, a wrapper to enforce https:</p>
<div class="brush: racket">
<table class="sourcetable">
<tbody>
<tr>
<td class="linenos">
<div class="linenodiv">
<pre> 1
2
3
4
5
6
7
8
9
10
11
12
13
14</pre></div></td>
<td class="code">
<div class="source">
<pre><span></span><span class="c1">;; This assumes we're behind ELB or nginx which gets both http and</span>
<span class="c1">;; https, talks to us only via http, setting an X-Forwarded-Proto</span>
<span class="c1">;; header to say the original protocol and an X-Forwarded-For header</span>
<span class="c1">;; to say the original IP.</span>
<span class="p">(</span><span class="k"><a href="http://docs.racket-lang.org/reference/attaching-contracts-to-values.html#(form._((lib._racket/contract/region..rkt)._define/contract))" style="color: inherit">define/contract</a></span> <span class="p">((</span><span class="n">wrap-http->https</span> <span class="n">handler</span><span class="p">)</span> <span class="n">req</span><span class="p">)</span> <span class="n">wrapper?</span>
<span class="p">(</span><span class="k"><a href="http://docs.racket-lang.org/reference/match.html#(form._((lib._racket/match..rkt)._match))" style="color: inherit">match</a></span> <span class="p">(</span><span class="n">headers-assq*</span> <span class="s2">#"x-forwarded-proto"</span> <span class="p">(</span><span class="n">request-headers/raw</span> <span class="n">req</span><span class="p">))</span>
<span class="p">[(</span><span class="n">header</span> <span class="k"><a href="http://docs.racket-lang.org/reference/stx-patterns.html#(form._((lib._racket/private/stxcase-scheme..rkt).__))" style="color: inherit">_</a></span> <span class="s2">#"http"</span><span class="p">)</span>
<span class="p">(</span><span class="n">redirect-to</span> <span class="p">(</span><span class="n">path->external-uri</span>
<span class="p">(</span><span class="n"><a href="http://docs.racket-lang.org/net/url.html#(def._((lib._net/url..rkt)._url-~3estring))" style="color: inherit">url->string</a></span> <span class="p">(</span><span class="k"><a href="http://docs.racket-lang.org/reference/struct-copy.html#(form._((lib._racket/private/base..rkt)._struct-copy))" style="color: inherit">struct-copy</a></span> <span class="n">url</span> <span class="p">(</span><span class="n">request-uri</span> <span class="n">req</span><span class="p">)</span>
<span class="p">[</span><span class="n">scheme</span> <span class="no">#f</span><span class="p">]</span>
<span class="p">[</span><span class="n">port</span> <span class="no">#f</span><span class="p">])))</span>
<span class="n">permanently</span><span class="p">)]</span>
<span class="p">[</span><span class="k"><a href="http://docs.racket-lang.org/reference/stx-patterns.html#(form._((lib._racket/private/stxcase-scheme..rkt).__))" style="color: inherit">_</a></span> <span class="p">(</span><span class="n">handler</span> <span class="n">req</span><span class="p">)]))</span>
</pre></div>
</td></tr></tbody></table>
</div>
<p>A whole chain of such wrappers can be composed — using <code>compose</code> or the <code>~></code> threading macro — to wrap the original <code>dispatch</code> function when we start the Racket web server:</p>
<div class="brush: racket">
<table class="sourcetable">
<tbody>
<tr>
<td class="linenos">
<div class="linenodiv">
<pre> 1
2
3
4
5
6
7
8
9
10
11
12
13
14</pre></div></td>
<td class="code">
<div class="source">
<pre><span></span><span class="p">(</span><span class="n">serve/servlet</span> <span class="p">(</span><span class="n">~></span> <span class="c1">;Note: requests go UP this chain, responses DOWN</span>
<span class="n">dispatch</span>
<span class="n">wrap-gzip</span>
<span class="n">wrap-not-modified</span>
<span class="n">wrap-authorize</span>
<span class="n">wrap-authenticate</span>
<span class="n">wrap-http->https</span>
<span class="n">wrap-timed-and-logged</span><span class="p">)</span>
<span class="kd">#:servlet-path</span> <span class="s2">"/"</span>
<span class="kd">#:servlet-regexp</span> <span class="sr">#px""</span>
<span class="kd">#:listen-ip</span> <span class="no">#f</span>
<span class="kd">#:port</span> <span class="p">(</span><span class="n">current-internal-port</span><span class="p">)</span>
<span class="kd">#:launch-browser?</span> <span class="p">(</span><span class="nb"><a href="http://docs.racket-lang.org/reference/booleans.html#(def._((quote._~23~25kernel)._not))" style="color: inherit">not</a></span> <span class="p">(</span><span class="n">current-production</span><span class="p">))</span>
<span class="kd">#:servlet-responder</span> <span class="n">error-responder</span><span class="p">)</span>
</pre></div>
</td></tr></tbody></table>
</div>
<h1 id="postgresql">PostgreSQL</h1>
<p>I was skeptical about using PostgreSQL: Is it web scale?</p>
<p>Seriously, I don’t have anything very exciting to describe about using PostgreSQL for this — which is wonderful. Currently:</p>
<ul>
<li>
<p>I’m hosting at AWS RDS.</p></li>
<li>
<p>Initial capture from crawling goes into a simple <a href="https://en.wikipedia.org/wiki/Star_schema">star schema</a>. The fact table has information about each deal, including its first-seen and last-seen times. When we already have a row for a deal, we just update the last-seen time. Dimension tables are what you’d expect — brand, product, and so on.</p></li>
<li>
<p>A simple view joins to denormalize, fitting the kind of query done by the web site’s search page. This is already quite fast; a materialized view makes it even faster.</p></li>
<li>
<p>Full-text search is delicious.</p></li></ul>
<p>Toward the end of my time at Cakewalk, I got some experience with Microsoft SQL Server, including optimizations. As a result, I’m aware that I can likewise do much more with PostgreSQL — looking at query execution plans, tuning indexes and queries, and so on. For now I’m satisfied I know roughly what I can do if/as/when necessary.</p>
<p>Racket has an excellent <a href="http://docs.racket-lang.org/db/index.html">db</a> package. It also has a <a href="http://docs.racket-lang.org/sql/index.html">sql</a> package that lets you write SQL as s-expressions rather than blobs of text, for example:</p>
<div class="brush: sql">
<table class="sourcetable">
<tbody>
<tr>
<td class="linenos">
<div class="linenodiv">
<pre>1
2
3</pre></div></td>
<td class="code">
<div class="source">
<pre><span></span><span class="k">SELECT</span> <span class="k">first</span><span class="p">,</span> <span class="k">last</span>
<span class="k">FROM</span> <span class="n">tribbles</span>
<span class="k">WHERE</span> <span class="n">id</span> <span class="o">=</span> <span class="err">$</span><span class="mi">1</span>
</pre></div>
</td></tr></tbody></table>
</div>
<div class="brush: racket">
<table class="sourcetable">
<tbody>
<tr>
<td class="linenos">
<div class="linenodiv">
<pre>1
2
3</pre></div></td>
<td class="code">
<div class="source">
<pre><span></span><span class="p">(</span><span class="n">select</span> <span class="nb"><a href="http://docs.racket-lang.org/reference/pairs.html#(def._((lib._racket/list..rkt)._first))" style="color: inherit">first</a></span> <span class="nb"><a href="http://docs.racket-lang.org/reference/pairs.html#(def._((lib._racket/list..rkt)._last))" style="color: inherit">last</a></span>
<span class="kd">#:from</span> <span class="n">tribbles</span>
<span class="kd">#:where</span> <span class="p">(</span><span class="nb"><a href="http://docs.racket-lang.org/reference/generic-numbers.html#(def._((quote._~23~25kernel)._~3d))" style="color: inherit">=</a></span> <span class="n"><a href="http://docs.racket-lang.org/syntax/Library_Syntax_Classes_and_Literal_Sets.html#(form._((lib._syntax/parse..rkt)._id))" style="color: inherit">id</a></span> <span class="o">,</span><span class="n"><a href="http://docs.racket-lang.org/syntax/Library_Syntax_Classes_and_Literal_Sets.html#(form._((lib._syntax/parse..rkt)._id))" style="color: inherit">id</a></span><span class="p">))</span>
</pre></div>
</td></tr></tbody></table>
</div>
<h1 id="aws">AWS</h1>
<p>I’m using Amazon Web Services because I feel badly that it is such an unpopular choice and want to see them get at least a <em>little</em> business.</p>
<p>Seriously, this has been a reasonable choice to get going quickly and it is affordable initially within the free tier.</p>
<p>One of my earliest Racket packages is <a href="https://pkgs.racket-lang.org/package/aws">aws</a>. Although I’ve used it intermittently, lately I’m <em>really</em> eating my own dog food.</p>
<p>I made a local change to support getting AWS credentials from EC2 instance meta-data. After living with that in production for a couple months, I shared that back in <a href="https://github.com/greghendershott/aws/commit/84c28ba16f238a8c48c54a9e858ba4bb2ec53742">commit 84c28ba</a>.</p>
<p>Just a quick overview of other parts:</p>
<ul>
<li>
<p>ELB: This can distribute load among multiple web servers. Even with just one server (to start) it is a convenient way to handle SSL.</p></li>
<li>
<p>SES: I’m only sending “transactional” emails (for account creation, password reset, and search alerts) so it’s been easy so far to maintain a good reputation.</p></li>
<li>
<p>Docker and ECS. Very helpful: <a href="https://www.ybrikman.com/writing/2015/11/11/running-docker-aws-ground-up/"><em>Running Docker on AWS from the ground up</em></a>.</p></li>
<li>
<p>CloudWatch Logs:</p>
<ul>
<li>
<p>It is pretty easy to make a Racket <a href="https://docs.racket-lang.org/reference/logging.html#%28def._%28%28quote._~23~25kernel%29._make-log-receiver%29%29">log receiver</a> that accumulates things into batches and does <a href="https://docs.aws.amazon.com/AmazonCloudWatchLogs/latest/APIReference/API_PutLogEvents.html"><code>PutLogEvents</code></a>.</p></li>
<li>
<p>It is handy to use JSON format for request/response logs. Not only does this display nicely, there’s a decent query feature, e.g. <code>{$.response.duration > 100}</code> or <code>{$.request.headers.Host =
"deals.extramaze.com"}</code>.</p></li></ul></li></ul>
<h1 id="conclusion">Conclusion</h1>
<p>I hope this helps give a taste of what it’s like to start a small SaaS business c. 2018 using Racket, PostgreSQL, and AWS — but without using advertising or JavaScript.</p>
<p>I realize this post has a somewhat uneven level of detail, so maybe I will loop back later and drill down on some parts.</p>
<div class="footnotes">
<ol>
<li id="extramaze-llc-using-racket-postgresql-aws-but-no-ads-or-js-footnote-1-definition" class="footnote-definition">
<p>I’m simplifying for narrative flow. Day zero, the <em>product</em> name was Cakewalk. The company name was Twelve Tone Systems. Later we adopted Cakewalk as the company name, too. The point is, it was much easier to pick domain names in ye olden times. <a href="#extramaze-llc-using-racket-postgresql-aws-but-no-ads-or-js-footnote-1-return">↩</a></p></li>
<li id="extramaze-llc-using-racket-postgresql-aws-but-no-ads-or-js-footnote-2-definition" class="footnote-definition">
<p>UPDATE: When I wrote this, I overlooked that I was using Google-served fonts. Since then <a href="/2018/05/extramaze-llc-using-system-fonts-not-google-fonts.html">I stopped</a>. <a href="#extramaze-llc-using-racket-postgresql-aws-but-no-ads-or-js-footnote-2-return">↩</a></p></li></ol></div>
<footer></footer></article></div>